mirror of
https://gitee.com/blossom-editor/blossom.git
synced 2025-12-06 16:58:26 +08:00
博客支持查看待办事项与日历计划
This commit is contained in:
parent
2e6df9491b
commit
8ceb7ace71
8
blossom-web/components.d.ts
vendored
8
blossom-web/components.d.ts
vendored
@ -11,13 +11,17 @@ declare module 'vue' {
|
|||||||
BLRow: typeof import('./src/components/BLRow.vue')['default']
|
BLRow: typeof import('./src/components/BLRow.vue')['default']
|
||||||
BLTag: typeof import('./src/components/BLTag.vue')['default']
|
BLTag: typeof import('./src/components/BLTag.vue')['default']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
|
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||||
|
ElCalendar: typeof import('element-plus/es')['ElCalendar']
|
||||||
|
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElInputPassword: typeof import('element-plus/es')['ElInputPassword']
|
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||||
|
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||||
|
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||||
|
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
|
||||||
NotFound: typeof import('./src/components/NotFound.vue')['default']
|
NotFound: typeof import('./src/components/NotFound.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
|||||||
1
blossom-web/declaration.d.ts
vendored
1
blossom-web/declaration.d.ts
vendored
@ -0,0 +1 @@
|
|||||||
|
declare module 'element-plus/dist/locale/zh-cn.mjs'
|
||||||
@ -9,7 +9,6 @@
|
|||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
font-family: 'ubuntu mono', 'Consolas', 'Menlo', 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
font-family: 'ubuntu mono', 'Consolas', 'Menlo', 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||||
/* font-family: 'Consolas', 'Menlo', 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; */
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-config-provider :size="'small'">
|
<el-config-provider :locale="zhCn">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</el-config-provider>
|
</el-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElConfigProvider } from 'element-plus'
|
import { ElConfigProvider } from 'element-plus'
|
||||||
|
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||||
</script>
|
</script>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<span
|
<div
|
||||||
class="tag-root"
|
class="tag-root"
|
||||||
:style="{
|
:style="{
|
||||||
color: props.color,
|
color: props.color,
|
||||||
@ -7,12 +7,9 @@
|
|||||||
fontSize: props.size + 'px',
|
fontSize: props.size + 'px',
|
||||||
fontWeight: props.weight
|
fontWeight: props.weight
|
||||||
}">
|
}">
|
||||||
<!-- {{ !!slots.default }}| -->
|
|
||||||
<span v-if="props.icon" :class="['tag-iconfont iconbl', props.icon, !!slots.default ? 'tag-icon-margin' : '']" />
|
<span v-if="props.icon" :class="['tag-iconfont iconbl', props.icon, !!slots.default ? 'tag-icon-margin' : '']" />
|
||||||
<span class="tag-content">
|
<span class="tag-content"><slot /></span>
|
||||||
<slot />
|
</div>
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -56,15 +53,16 @@ const props = defineProps({
|
|||||||
@include flex(row, center, center);
|
@include flex(row, center, center);
|
||||||
box-shadow: 2px 2px 3px 0 #999999;
|
box-shadow: 2px 2px 3px 0 #999999;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 1px 4px;
|
padding: 0px 4px;
|
||||||
margin: 3px;
|
margin: 3px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
min-height: 15px;
|
min-height: 15px;
|
||||||
max-height: 15px;
|
max-height: 15px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
width: auto;
|
||||||
|
|
||||||
.tag-iconfont {
|
.tag-iconfont {
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-icon-margin {
|
.tag-icon-margin {
|
||||||
@ -72,8 +70,10 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tag-content {
|
.tag-content {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
line-height: 12px;
|
line-height: 12px;
|
||||||
transform: scale(0.9);
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -11,6 +11,8 @@ const Index = () => import('../views/Index.vue')
|
|||||||
const Home = () => import('../views/index/Home.vue')
|
const Home = () => import('../views/index/Home.vue')
|
||||||
const Login = () => import('@/views/index/Login.vue')
|
const Login = () => import('@/views/index/Login.vue')
|
||||||
const Articles = () => import('@/views/article/Articles.vue')
|
const Articles = () => import('@/views/article/Articles.vue')
|
||||||
|
const TodoIndex = () => import('@/views/todo/TodoIndex.vue')
|
||||||
|
const PlanIndex = () => import('@/views/plan/PlanIndex.vue')
|
||||||
|
|
||||||
router.addRoute({ path: '/404', component: NotFound })
|
router.addRoute({ path: '/404', component: NotFound })
|
||||||
router.addRoute({ path: '/:pathMatch(.*)', redirect: '/404' })
|
router.addRoute({ path: '/:pathMatch(.*)', redirect: '/404' })
|
||||||
@ -23,6 +25,8 @@ router.addRoute({
|
|||||||
children: [
|
children: [
|
||||||
{ path: '/home', name: 'Home', component: Home, meta: { keepAlive: true } },
|
{ path: '/home', name: 'Home', component: Home, meta: { keepAlive: true } },
|
||||||
{ path: '/login', name: 'Login', component: Login, meta: { keepAlive: true } },
|
{ path: '/login', name: 'Login', component: Login, meta: { keepAlive: true } },
|
||||||
{ path: '/articles', name: 'Articles', component: Articles, meta: { keepAlive: false } }
|
{ path: '/articles', name: 'Articles', component: Articles, meta: { keepAlive: false } },
|
||||||
|
{ path: '/todo', name: 'TodoIndex', component: TodoIndex, meta: { keepAlive: false } },
|
||||||
|
{ path: '/plan', name: 'PlanIndex', component: PlanIndex, meta: { keepAlive: false } }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@ -47,6 +47,14 @@ export const useUserStore = defineStore('userStore', {
|
|||||||
auth: Local.get(storeKey) || initAuth(),
|
auth: Local.get(storeKey) || initAuth(),
|
||||||
userinfo: Local.get(userinfoKey) || initUserinfo()
|
userinfo: Local.get(userinfoKey) || initUserinfo()
|
||||||
}),
|
}),
|
||||||
|
getters: {
|
||||||
|
isLogin: (state): boolean => {
|
||||||
|
if (!state.auth) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return state.auth.status === AuthStatus.Succ
|
||||||
|
}
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// /**
|
// /**
|
||||||
// * 根据用户名密码登录
|
// * 根据用户名密码登录
|
||||||
|
|||||||
@ -28,46 +28,60 @@
|
|||||||
<div v-for="L1 in docTreeData" :key="L1.i" class="menu-level-one">
|
<div v-for="L1 in docTreeData" :key="L1.i" class="menu-level-one">
|
||||||
<el-menu-item v-if="isEmpty(L1.children)" :index="L1.i">
|
<el-menu-item v-if="isEmpty(L1.children)" :index="L1.i">
|
||||||
<template #title>
|
<template #title>
|
||||||
<DocTitle :trees="L1" @click-doc="clickCurDoc" />
|
<div class="menu-item-wrapper" @click="clickCurDoc(L1)">
|
||||||
|
<DocTitle :trees="L1" :level="1" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
|
||||||
<el-sub-menu v-else :expand-open-icon="ArrowDownBold" :expand-close-icon="ArrowRightBold" :index="L1.i">
|
<el-sub-menu v-else :expand-open-icon="ArrowDownBold" :expand-close-icon="ArrowRightBold" :index="L1.i">
|
||||||
<template #title>
|
<template #title>
|
||||||
<DocTitle :trees="L1" @click-doc="clickCurDoc" style="font-size: 15px" />
|
<div class="menu-item-wrapper">
|
||||||
|
<DocTitle :trees="L1" :level="1" style="font-size: 15px" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- ================================================ L2 ================================================ -->
|
<!-- ================================================ L2 ================================================ -->
|
||||||
<div v-for="L2 in L1.children" :key="L2.i">
|
<div v-for="L2 in L1.children" :key="L2.i">
|
||||||
<el-menu-item v-if="isEmpty(L2.children)" :index="L2.i">
|
<el-menu-item v-if="isEmpty(L2.children)" :index="L2.i">
|
||||||
<template #title>
|
<template #title>
|
||||||
<DocTitle :trees="L2" @click-doc="clickCurDoc" />
|
<div class="menu-item-wrapper" @click="clickCurDoc(L2)">
|
||||||
|
<DocTitle :trees="L2" :level="2" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
|
||||||
<el-sub-menu v-else :expand-open-icon="ArrowDownBold" :expand-close-icon="ArrowRightBold" :index="L2.i">
|
<el-sub-menu v-else :expand-open-icon="ArrowDownBold" :expand-close-icon="ArrowRightBold" :index="L2.i">
|
||||||
<template #title>
|
<template #title>
|
||||||
<DocTitle :trees="L2" @click-doc="clickCurDoc" />
|
<div class="menu-item-wrapper">
|
||||||
|
<DocTitle :trees="L2" :level="2" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- ================================================ L3 ================================================ -->
|
<!-- ================================================ L3 ================================================ -->
|
||||||
<div v-for="L3 in L2.children" :key="L3.i">
|
<div v-for="L3 in L2.children" :key="L3.i">
|
||||||
<el-menu-item v-if="isEmpty(L3.children)" :index="L3.i">
|
<el-menu-item v-if="isEmpty(L3.children)" :index="L3.i">
|
||||||
<template #title>
|
<template #title>
|
||||||
<DocTitle :trees="L3" @click-doc="clickCurDoc" />
|
<div class="menu-item-wrapper" @click="clickCurDoc(L3)">
|
||||||
|
<DocTitle :trees="L3" :level="3" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
|
||||||
<el-sub-menu v-else :expand-open-icon="ArrowDownBold" :expand-close-icon="ArrowRightBold" :index="L3.i">
|
<el-sub-menu v-else :expand-open-icon="ArrowDownBold" :expand-close-icon="ArrowRightBold" :index="L3.i">
|
||||||
<template #title>
|
<template #title>
|
||||||
<DocTitle :trees="L3" @click-doc="clickCurDoc" />
|
<div class="menu-item-wrapper">
|
||||||
|
<DocTitle :trees="L3" :level="3" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- ================================================ L4 ================================================ -->
|
<!-- ================================================ L4 ================================================ -->
|
||||||
<div v-for="L4 in L3.children" :key="L4.i">
|
<div v-for="L4 in L3.children" :key="L4.i">
|
||||||
<el-menu-item v-if="isEmpty(L4.children)" :index="L4.i">
|
<el-menu-item v-if="isEmpty(L4.children)" :index="L4.i">
|
||||||
<template #title>
|
<template #title>
|
||||||
<DocTitle :trees="L4" @click-doc="clickCurDoc" />
|
<div class="menu-item-wrapper" @click="clickCurDoc(L4)">
|
||||||
|
<DocTitle :trees="L4" :level="4" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
</div>
|
</div>
|
||||||
@ -117,12 +131,14 @@
|
|||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { ref, onActivated, onUnmounted, onMounted } from 'vue'
|
import { ref, onActivated, onUnmounted, onMounted } from 'vue'
|
||||||
import { ArrowDownBold, ArrowRightBold } from '@element-plus/icons-vue'
|
import { ArrowDownBold, ArrowRightBold } from '@element-plus/icons-vue'
|
||||||
import { articleInfoOpenApi, docTreeApi } from '@/api/blossom'
|
import { articleInfoOpenApi, articleInfoApi, docTreeOpenApi, docTreeApi } from '@/api/blossom'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
import { isNull, isEmpty, isNotNull } from '@/assets/utils/obj'
|
import { isNull, isEmpty, isNotNull } from '@/assets/utils/obj'
|
||||||
import DocTitle from './DocTitle.vue'
|
import DocTitle from './DocTitle.vue'
|
||||||
import IndexHeader from '../index/IndexHeader.vue'
|
import IndexHeader from '../index/IndexHeader.vue'
|
||||||
import 'katex/dist/katex.min.css'
|
import 'katex/dist/katex.min.css'
|
||||||
import { toRoute } from '@/router'
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.onHtmlEventDispatch = onHtmlEventDispatch
|
window.onHtmlEventDispatch = onHtmlEventDispatch
|
||||||
@ -186,16 +202,23 @@ const getDocTree = () => {
|
|||||||
docTreeLoading.value = true
|
docTreeLoading.value = true
|
||||||
docTreeData.value = []
|
docTreeData.value = []
|
||||||
defaultOpeneds.value = []
|
defaultOpeneds.value = []
|
||||||
docTreeApi({ onlyOpen: true })
|
|
||||||
.then((resp) => {
|
const then = (resp: any) => {
|
||||||
docTreeData.value = resp.data
|
docTreeData.value = resp.data
|
||||||
docTreeData.value.forEach((l1: DocTree) => {
|
docTreeData.value.forEach((l1: DocTree) => {
|
||||||
defaultOpeneds.value.push(l1.i.toString())
|
defaultOpeneds.value.push(l1.i.toString())
|
||||||
})
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
docTreeLoading.value = false
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userStore.isLogin) {
|
||||||
|
docTreeApi()
|
||||||
|
.then((resp) => then(resp))
|
||||||
|
.finally(() => (docTreeLoading.value = false))
|
||||||
|
} else {
|
||||||
|
docTreeOpenApi()
|
||||||
|
.then((resp) => then(resp))
|
||||||
|
.finally(() => (docTreeLoading.value = false))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const clickCurDoc = async (tree: DocTree) => {
|
const clickCurDoc = async (tree: DocTree) => {
|
||||||
@ -210,13 +233,16 @@ const clickCurDoc = async (tree: DocTree) => {
|
|||||||
* 如果点击的是文章, 则查询文章信息和正文, 并在编辑器中显示.
|
* 如果点击的是文章, 则查询文章信息和正文, 并在编辑器中显示.
|
||||||
*/
|
*/
|
||||||
const getCurEditArticle = async (id: number) => {
|
const getCurEditArticle = async (id: number) => {
|
||||||
await articleInfoOpenApi({ id: id, showToc: true, showMarkdown: false, showHtml: true })
|
const then = (resp: any) => {
|
||||||
.then((resp) => {
|
if (isNull(resp.data)) return
|
||||||
if (isNull(resp.data)) return
|
article.value = resp.data
|
||||||
article.value = resp.data
|
tocList.value = JSON.parse(resp.data.toc)
|
||||||
tocList.value = JSON.parse(resp.data.toc)
|
}
|
||||||
})
|
if (userStore.isLogin) {
|
||||||
.finally(() => {})
|
await articleInfoApi({ id: id, showToc: true, showMarkdown: false, showHtml: true }).then((resp) => then(resp))
|
||||||
|
} else {
|
||||||
|
await articleInfoOpenApi({ id: id, showToc: true, showMarkdown: false, showHtml: true }).then((resp) => then(resp))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toScroll = (level: number, content: string) => {
|
const toScroll = (level: number, content: string) => {
|
||||||
@ -384,23 +410,32 @@ const onresize = () => {
|
|||||||
border-right: 1px solid var(--el-border-color);
|
border-right: 1px solid var(--el-border-color);
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
transition: 0.1s;
|
transition: 0.1s;
|
||||||
|
&:hover {
|
||||||
|
:deep(.folder-level-line) {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.menu-level-one {
|
.menu-level-one {
|
||||||
margin-top: 10px;
|
margin-top: 8px;
|
||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 8px;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-item-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.doc-trees {
|
.doc-trees {
|
||||||
@include box(100%, 100%);
|
@include box(100%, 100%);
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
overflow-y: overlay;
|
overflow-y: scroll;
|
||||||
// padding-right: 6px;
|
// padding-right: 6px;
|
||||||
// 基础的 padding
|
// 基础的 padding
|
||||||
--el-menu-base-level-padding: 25px;
|
--el-menu-base-level-padding: 25px;
|
||||||
@ -433,8 +468,8 @@ const onresize = () => {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
||||||
.el-sub-menu__icon-arrow {
|
.el-sub-menu__icon-arrow {
|
||||||
right: calc(220px - var(--el-menu-level) * 10px);
|
right: calc(220px - var(--el-menu-level) * 14px);
|
||||||
font-size: 12px;
|
color: #b3b3b3;
|
||||||
width: 0.8em;
|
width: 0.8em;
|
||||||
height: 0.8em;
|
height: 0.8em;
|
||||||
}
|
}
|
||||||
@ -566,7 +601,7 @@ const onresize = () => {
|
|||||||
width: 1260px;
|
width: 1260px;
|
||||||
max-width: 1260px;
|
max-width: 1260px;
|
||||||
overflow-y: overlay;
|
overflow-y: overlay;
|
||||||
padding: 0 20px;
|
padding: 0 30px;
|
||||||
|
|
||||||
.bl-preview {
|
.bl-preview {
|
||||||
$borderRadius: 4px;
|
$borderRadius: 4px;
|
||||||
@ -1010,7 +1045,7 @@ const onresize = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.article {
|
.article {
|
||||||
padding: 0 20px;
|
padding: 0 10px;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
.bl-preview {
|
.bl-preview {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="['doc-title', props.trees.t?.includes('subject') ? 'subject-title' : '']" @click="handlClick">
|
<div :class="['doc-title']">
|
||||||
<div class="doc-name">
|
<div class="doc-name">
|
||||||
<img
|
<img
|
||||||
class="menu-icon-img"
|
class="menu-icon-img"
|
||||||
@ -8,13 +8,18 @@
|
|||||||
<svg v-else-if="isNotBlank(props.trees.icon)" class="icon menu-icon" aria-hidden="true">
|
<svg v-else-if="isNotBlank(props.trees.icon)" class="icon menu-icon" aria-hidden="true">
|
||||||
<use :xlink:href="'#' + props.trees.icon"></use>
|
<use :xlink:href="'#' + props.trees.icon"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<el-tooltip :content="props.trees.n" placement="top" :show-after="1000" :hide-after="0" :transition="'none'" :offset="2" :persistent="false">
|
<div class="name-wrapper" :style="nameWrapperStyle">
|
||||||
<div class="name-wrapper" :style="nameWrapperStyle">
|
{{ props.trees.n }}
|
||||||
{{ props.trees.n }}
|
</div>
|
||||||
</div>
|
|
||||||
</el-tooltip>
|
|
||||||
<bl-tag v-for="tag in tags" style="margin-top: 5px" :bg-color="tag.bgColor" :icon="tag.icon">{{ tag.content }}</bl-tag>
|
<bl-tag v-for="tag in tags" style="margin-top: 5px" :bg-color="tag.bgColor" :icon="tag.icon">{{ tag.content }}</bl-tag>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="level === 2" class="folder-level-line" style="left: -26px"></div>
|
||||||
|
<div v-if="level === 3" class="folder-level-line" style="left: -36px"></div>
|
||||||
|
<div v-if="level === 3" class="folder-level-line" style="left: -22px"></div>
|
||||||
|
<!-- -->
|
||||||
|
<div v-if="level === 4" class="folder-level-line" style="left: -46px"></div>
|
||||||
|
<div v-if="level === 4" class="folder-level-line" style="left: -32px"></div>
|
||||||
|
<div v-if="level === 4" class="folder-level-line" style="left: -18px"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -22,12 +27,12 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import { isNotBlank } from '@/assets/utils/obj'
|
import { isNotBlank } from '@/assets/utils/obj'
|
||||||
import BLTag from '@/components/BLTag.vue'
|
|
||||||
|
|
||||||
//#region ----------------------------------------< 标题信息 >--------------------------------------
|
//#region ----------------------------------------< 标题信息 >--------------------------------------
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
trees: { type: Object as PropType<DocTree>, default: {} }
|
trees: { type: Object as PropType<DocTree>, default: {} },
|
||||||
|
level: { type: Number, required: true }
|
||||||
})
|
})
|
||||||
|
|
||||||
const nameWrapperStyle = computed(() => {
|
const nameWrapperStyle = computed(() => {
|
||||||
@ -53,16 +58,7 @@ const tags = computed(() => {
|
|||||||
return icons
|
return icons
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* 点击文档菜单标题后的回调
|
|
||||||
*/
|
|
||||||
const handlClick = () => {
|
|
||||||
emits('clickDoc', props.trees)
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const emits = defineEmits(['clickDoc'])
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@ -96,33 +92,33 @@ $icon-size: 17px;
|
|||||||
@include ellipsis();
|
@include ellipsis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sort {
|
||||||
|
position: absolute;
|
||||||
|
padding: 0 2px;
|
||||||
|
right: 0px;
|
||||||
|
top: 2px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.folder-level-line {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 专题样式, 包括边框和文字样式
|
.folder-level-line {
|
||||||
.subject-title {
|
width: 1.5px;
|
||||||
position: relative;
|
background-color: var(--el-border-color);
|
||||||
padding: 2px 5px;
|
box-sizing: border-box;
|
||||||
margin: 5px 0 10px 0;
|
position: absolute;
|
||||||
border-radius: 4px;
|
opacity: 0;
|
||||||
box-shadow: 1px 1px 5px #a2a2a2;
|
transition: opacity 0.5s;
|
||||||
background: linear-gradient(135deg, #ffffff, #f0f0f0, #cacaca);
|
|
||||||
|
|
||||||
.doc-name {
|
|
||||||
min-width: 145px;
|
|
||||||
max-width: 145px;
|
|
||||||
color: #4a545e;
|
|
||||||
text-shadow: 2px 2px 3px #9393939d;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 专题样式, 包括边框和文字样式
|
// 专题样式, 包括边框和文字样式
|
||||||
.subject-title {
|
.subject-title {
|
||||||
@include flex(row, flex-start, flex-start);
|
@include flex(row, flex-start, flex-start);
|
||||||
box-shadow: 1px 1px 5px #a2a2a2;
|
|
||||||
background: linear-gradient(135deg, #ffffff, #f0f0f0, #cacaca);
|
background: linear-gradient(135deg, #ffffff, #f0f0f0, #cacaca);
|
||||||
|
box-shadow: 1px 1px 5px #a2a2a2;
|
||||||
max-width: calc(100% - 15px);
|
max-width: calc(100% - 15px);
|
||||||
min-width: calc(100% - 15px);
|
min-width: calc(100% - 15px);
|
||||||
padding: 2px 5px;
|
padding: 2px 5px;
|
||||||
@ -132,6 +128,7 @@ $icon-size: 17px;
|
|||||||
|
|
||||||
.doc-name {
|
.doc-name {
|
||||||
@include flex(row, flex-start, flex-start);
|
@include flex(row, flex-start, flex-start);
|
||||||
|
color: var(--el-color-primary);
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -153,5 +150,15 @@ $icon-size: 17px;
|
|||||||
min-width: calc(100% - 25px);
|
min-width: calc(100% - 25px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sort {
|
||||||
|
position: absolute;
|
||||||
|
right: -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-level-line {
|
||||||
|
height: calc(100% + 25px);
|
||||||
|
top: -5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -39,9 +39,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="popper-content">
|
<div class="popper-content">
|
||||||
<div class="item"><span class="iconbl bl-a-texteditorhighlightcolor-line"></span>文章列表</div>
|
<div class="item" @click="toRoute('/home')"><span class="iconbl bl-a-home1-line"></span>首页</div>
|
||||||
<div class="item"><span class="iconbl bl-calendar-line"></span>日历计划</div>
|
<div class="item-divider"></div>
|
||||||
<div class="item"><span class="iconbl bl-a-labellist-line"></span>待办事项</div>
|
<div class="item" @click="toRoute('/articles')"><span class="iconbl bl-a-texteditorhighlightcolor-line"></span>文章列表</div>
|
||||||
|
<div class="item" @click="toRoute('/todo')"><span class="iconbl bl-a-labellist-line"></span>待办事项</div>
|
||||||
|
<div class="item" @click="toRoute('/plan')"><span class="iconbl bl-calendar-line"></span>日历计划</div>
|
||||||
<div class="item"><span class="iconbl bl-note-line"></span>便签</div>
|
<div class="item"><span class="iconbl bl-note-line"></span>便签</div>
|
||||||
<div class="item-divider"></div>
|
<div class="item-divider"></div>
|
||||||
<div class="item" @click="handlLogout"><span class="iconbl bl-logout-circle-line"></span>退出登录</div>
|
<div class="item" @click="handlLogout"><span class="iconbl bl-logout-circle-line"></span>退出登录</div>
|
||||||
@ -144,6 +146,12 @@ const handlLogout = () => {
|
|||||||
color: #909090;
|
color: #909090;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
text-shadow: 3px 3px 5px #000;
|
text-shadow: 3px 3px 5px #000;
|
||||||
|
user-select: none;
|
||||||
|
transition: color 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #fff3fc;
|
||||||
|
}
|
||||||
|
|
||||||
.iconbl {
|
.iconbl {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
@ -158,6 +166,7 @@ const handlLogout = () => {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
|
text-shadow: 3px 3px 5px #1d1d1d;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@ -171,8 +180,8 @@ const handlLogout = () => {
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #e8e8e8;
|
color: #fff3fc;
|
||||||
text-shadow: 3px 3px 10px #cccccc;
|
// text-shadow: 3px 3px 10px #cccccc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,46 +29,14 @@ import { ref } from 'vue'
|
|||||||
import { toRoute } from '@/router'
|
import { toRoute } from '@/router'
|
||||||
import { login } from '@/scripts/auth'
|
import { login } from '@/scripts/auth'
|
||||||
|
|
||||||
// const userStore = useUserStore()
|
|
||||||
// const { auth, userinfo } = storeToRefs(userStore)
|
|
||||||
const formLogin = ref({
|
const formLogin = ref({
|
||||||
username: '',
|
username: '',
|
||||||
password: ''
|
password: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const logingIn = ref(false)
|
|
||||||
|
|
||||||
const handleLogin = () => {
|
const handleLogin = () => {
|
||||||
login(formLogin.value.username, formLogin.value.password)
|
login(formLogin.value.username, formLogin.value.password)
|
||||||
}
|
}
|
||||||
|
|
||||||
// const login = async () => {
|
|
||||||
// if (logingIn.value) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// logingIn.value = true
|
|
||||||
// auth.value.status = AuthStatus.Loging
|
|
||||||
// await loginApi({ username: formLogin.value.username, password: formLogin.value.password, clientId: 'blossom', grantType: 'password' })
|
|
||||||
// .then((resp: any) => {
|
|
||||||
// auth.value = { token: resp.data.token, status: AuthStatus.Succ }
|
|
||||||
// Local.set(storeKey, auth)
|
|
||||||
// getUserinfo()
|
|
||||||
// toRoute('/home')
|
|
||||||
// })
|
|
||||||
// .catch((_e) => {
|
|
||||||
// userStore.reset()
|
|
||||||
// // 登录失败的状态需要特别更改
|
|
||||||
// auth.value = { token: '', status: AuthStatus.Fail }
|
|
||||||
// })
|
|
||||||
// .finally(() => (logingIn.value = false))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const getUserinfo = () => {
|
|
||||||
// userinfoApi().then((resp) => {
|
|
||||||
// userinfo.value = resp.data
|
|
||||||
// Local.set(userinfoKey, resp.data)
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
47
blossom-web/src/views/plan/PlanColor.scss
Normal file
47
blossom-web/src/views/plan/PlanColor.scss
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
.gray {
|
||||||
|
background-color: #858585;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gray.hl {
|
||||||
|
background-color: #646464;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
background-color: #fb0036;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red.hl {
|
||||||
|
background-color: #c9002c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow {
|
||||||
|
background-color: #d8a600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellow.hl {
|
||||||
|
background-color: #ffc400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue {
|
||||||
|
background-color: #00a3cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue.hl {
|
||||||
|
background-color: #00657e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
background-color: #339a00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green.hl {
|
||||||
|
background-color: #2d8700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple {
|
||||||
|
background-color: #ad8cf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purple.hl {
|
||||||
|
background-color: #9665ff;
|
||||||
|
}
|
||||||
362
blossom-web/src/views/plan/PlanIndex.vue
Normal file
362
blossom-web/src/views/plan/PlanIndex.vue
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
<template>
|
||||||
|
<div class="plan-index-root">
|
||||||
|
<div class="header">
|
||||||
|
<IndexHeader :bg="true"></IndexHeader>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<bl-row just="space-between" class="workbench" height="45px">
|
||||||
|
<div class="month">{{ calendarDate.getMonth() + 1 }}月</div>
|
||||||
|
<el-button-group size="small">
|
||||||
|
<el-button @click="selectDate('prev-month')">上月</el-button>
|
||||||
|
<el-button @click="selectDate('today')">今日</el-button>
|
||||||
|
<el-button @click="selectDate('next-month')">下月</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</bl-row>
|
||||||
|
<el-calendar class="bl-calendar" v-model="calendarDate" ref="CalendarRef">
|
||||||
|
<template #header="{ date }"><div></div></template>
|
||||||
|
<template #date-cell="{ data }">
|
||||||
|
<div class="date-title">
|
||||||
|
<span>{{ data.day.split('-').slice(2).join('-') }}</span>
|
||||||
|
<span class="iconbl bl-a-addline-line" @click="handleShowPlanAddDialog(data.day)"></span>
|
||||||
|
</div>
|
||||||
|
<div class="plan-group">
|
||||||
|
<div v-for="(plan, index) in planDays[data.day + ' 00:00:00']" :key="plan.id">
|
||||||
|
<el-popover
|
||||||
|
placement="right"
|
||||||
|
popper-class="plan-popover"
|
||||||
|
:width="200"
|
||||||
|
trigger="click"
|
||||||
|
:hide-after="0"
|
||||||
|
:disabled="plan.id < 0"
|
||||||
|
:persistent="false">
|
||||||
|
<!-- 触发元素 -->
|
||||||
|
<template #reference>
|
||||||
|
<div :class="'plan-line ' + plan.color + ' ' + plan.position + ' ' + plan.hl" :style="{ top: index * 21 + 'px' }">
|
||||||
|
<div v-if="plan.position == 'head' || plan.position == 'all'" class="plan-title">
|
||||||
|
{{ plan.title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 弹出框内容 -->
|
||||||
|
<bl-col class="plan-popover-inner">
|
||||||
|
<div :class="['plan-popover-title', plan.color]">
|
||||||
|
{{ plan.title }}
|
||||||
|
</div>
|
||||||
|
<div class="plan-popover-time">
|
||||||
|
<div><span class="iconbl bl-date-line"></span> {{ data.day }}</div>
|
||||||
|
<span class="iconbl bl-a-clock3-line"></span> {{ plan.planStartTime }} - {{ plan.planEndTime }}
|
||||||
|
</div>
|
||||||
|
<div class="plan-popover-content">
|
||||||
|
{{ plan.content }}
|
||||||
|
</div>
|
||||||
|
</bl-col>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-calendar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { nextTick, onMounted, ref, watch } from 'vue'
|
||||||
|
import type { CalendarDateType, CalendarInstance } from 'element-plus'
|
||||||
|
import { planListDayApi, planDelApi } from '@/api/plan'
|
||||||
|
import { getDateTimeFormat, getNextDay, timestampToDatetime } from '@/assets/utils/util'
|
||||||
|
import IndexHeader from '@/views/index/IndexHeader.vue'
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getPlanAll(getDateTimeFormat().substring(0, 7))
|
||||||
|
})
|
||||||
|
|
||||||
|
const PlanDayInfoRef = ref()
|
||||||
|
// 计划列表
|
||||||
|
const planDays = ref<any>({})
|
||||||
|
// 上次点击选择的月份, 不同月份时才查询接口
|
||||||
|
let lastMonth: string = ''
|
||||||
|
|
||||||
|
const calendarDate = ref<Date>(new Date())
|
||||||
|
const CalendarRef = ref<CalendarInstance>()
|
||||||
|
const selectDate = (val: CalendarDateType) => {
|
||||||
|
if (!CalendarRef.value) return
|
||||||
|
CalendarRef.value.selectDate(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => calendarDate.value,
|
||||||
|
(data) => {
|
||||||
|
getPlanAll(timestampToDatetime(data).substring(0, 7))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const getPlanAll = (month: string, force: boolean = false) => {
|
||||||
|
if (!force && month == lastMonth) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastMonth = month
|
||||||
|
planListDayApi({ month: month }).then((resp) => {
|
||||||
|
planDays.value = resp.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region ----------------------------------------< 新增删除 >-------------------------------------
|
||||||
|
const isShowPlanAddDialog = ref(false)
|
||||||
|
|
||||||
|
const handleShowPlanAddDialog = (ymd: string) => {
|
||||||
|
isShowPlanAddDialog.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
PlanDayInfoRef.value.setPlanDate(ymd)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedCallback = () => {
|
||||||
|
getPlanAll(lastMonth, true)
|
||||||
|
isShowPlanAddDialog.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const delDay = (groupId: number) => {
|
||||||
|
planDelApi({ groupId: groupId }).then((_resp) => {
|
||||||
|
getPlanAll(lastMonth, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.plan-index-root {
|
||||||
|
@include box(100%, 100%);
|
||||||
|
@include flex(column, flex-start, center);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
@include box(100%, 60px);
|
||||||
|
}
|
||||||
|
.workbench,
|
||||||
|
.bl-calendar {
|
||||||
|
max-width: 900px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workbench {
|
||||||
|
padding: 10px 10px;
|
||||||
|
|
||||||
|
:deep(.el-button, .el-radio-button__inner) {
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bl-calendar {
|
||||||
|
--el-calendar-border: 1px solid var(--el-border-color);
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-calendar__header) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-calendar__body) {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 3px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-calendar-table {
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
thead {
|
||||||
|
th {
|
||||||
|
border-bottom: var(--el-calendar-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
@include box(100%, calc(100% - 45px));
|
||||||
|
|
||||||
|
tr {
|
||||||
|
td.prev,
|
||||||
|
td.next {
|
||||||
|
.date-title {
|
||||||
|
filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.date-title {
|
||||||
|
.iconbl {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td.current {
|
||||||
|
.date-title {
|
||||||
|
color: var(--el-color-primary-light-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td.is-today {
|
||||||
|
background-color: var(--el-color-primary-light-8) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.is-selected {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
border-right: 0;
|
||||||
|
border-left: 0;
|
||||||
|
border-top: 0;
|
||||||
|
overflow: scroll;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
.el-calendar-day {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-calendar-day {
|
||||||
|
min-height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
border-right: var(--el-calendar-border);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
.date-title {
|
||||||
|
.iconbl {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-title {
|
||||||
|
@include box(100%, 25px);
|
||||||
|
@include flex(row, space-between, center);
|
||||||
|
padding: 3px 10px;
|
||||||
|
|
||||||
|
.iconbl {
|
||||||
|
opacity: 0;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-group {
|
||||||
|
@include box(100%, calc(100% - 25px));
|
||||||
|
@include flex(column, flex-start, center);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.plan-line {
|
||||||
|
@include flex(row, flex-start, center);
|
||||||
|
@include box(calc(100% + 2px), 15px);
|
||||||
|
box-shadow: 0 2px 3px 1px rgba(104, 104, 104, 0.5);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
padding-left: 4px;
|
||||||
|
color: #fff;
|
||||||
|
transition: 0.3s;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.plan-title {
|
||||||
|
width: calc(100% - 10px);
|
||||||
|
font-size: 0.6rem;
|
||||||
|
text-align: left;
|
||||||
|
overflow: visible;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hold {
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bolder-radius: 20px;
|
||||||
|
|
||||||
|
.head {
|
||||||
|
width: calc(100% - 4px);
|
||||||
|
border-top-left-radius: $bolder-radius;
|
||||||
|
border-bottom-left-radius: $bolder-radius;
|
||||||
|
margin-left: 5px;
|
||||||
|
z-index: 100;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tail {
|
||||||
|
width: calc(100% - 5px);
|
||||||
|
border-top-right-radius: $bolder-radius;
|
||||||
|
border-bottom-right-radius: $bolder-radius;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.all {
|
||||||
|
width: calc(100% - 10px);
|
||||||
|
border-radius: $bolder-radius;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tail,
|
||||||
|
.all {
|
||||||
|
&:hover {
|
||||||
|
.bl-delete-line {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import url('./PlanColor.scss');
|
||||||
|
|
||||||
|
.plan-popover {
|
||||||
|
--el-popover-padding: 0 !important;
|
||||||
|
border: 0 !important;
|
||||||
|
width: 170px !important;
|
||||||
|
|
||||||
|
.plan-popover-inner {
|
||||||
|
.iconbl {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-popover-title {
|
||||||
|
@include font(15px, 500);
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-popover-time {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px 10px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-popover-content {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0 10px 10px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
610
blossom-web/src/views/todo/TodoIndex.vue
Normal file
610
blossom-web/src/views/todo/TodoIndex.vue
Normal file
@ -0,0 +1,610 @@
|
|||||||
|
<template>
|
||||||
|
<div class="todo-root">
|
||||||
|
<div class="header">
|
||||||
|
<IndexHeader :bg="true"></IndexHeader>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<bl-row just="space-between" class="workbench" height="45px">
|
||||||
|
<el-radio-group v-model="todoType" size="small">
|
||||||
|
<el-radio-button :label="10">每日</el-radio-button>
|
||||||
|
<el-radio-button :label="20">阶段性</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<div class="month" v-show="todoType === 10">{{ calendarDate.getMonth() + 1 }}月</div>
|
||||||
|
<el-button-group v-show="todoType === 10" size="small">
|
||||||
|
<el-button @click="selectDate('prev-month')">上月</el-button>
|
||||||
|
<el-button @click="selectDate('today')">今日</el-button>
|
||||||
|
<el-button @click="selectDate('next-month')">下月</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</bl-row>
|
||||||
|
|
||||||
|
<!-- 日历 -->
|
||||||
|
<el-calendar v-show="todoType === 10" v-model="calendarDate" class="task-day-calendar" ref="CalendarRef">
|
||||||
|
<template #header="{ date }"><div></div></template>
|
||||||
|
<template #date-cell="{ data }">
|
||||||
|
<bl-col just="space-around" class="cell-wrapper" @click="toTask(data.day)">
|
||||||
|
<div class="day">{{ data.day.split('-')[2] }}</div>
|
||||||
|
<div v-if="getCount(data.day) > 0">
|
||||||
|
<bl-tag :size="14">{{ getCount(data.day) }}</bl-tag>
|
||||||
|
</div>
|
||||||
|
</bl-col>
|
||||||
|
</template>
|
||||||
|
</el-calendar>
|
||||||
|
|
||||||
|
<div v-show="todoType === 20" class="phaseds">
|
||||||
|
<bl-row v-for="phased in todoPhased" class="phased" just="space-between" width="90%" @click="toTask(phased.todoId)">
|
||||||
|
<span style="padding-left: 5px">{{ phased.todoName }}</span>
|
||||||
|
<bl-tag v-if="phased.taskCount > 0">{{ phased.taskCount }}</bl-tag>
|
||||||
|
</bl-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="segmented-control" @change="updatePillPosition">
|
||||||
|
<span class="selection"></span>
|
||||||
|
<div class="option">
|
||||||
|
<input type="radio" id="WAITING" name="sample" value="WAITING" v-model="taskShowStatus" checked />
|
||||||
|
<label for="WAITING"><span>待 办</span></label>
|
||||||
|
<span v-show="countWait > 0" class="count">{{ countWait }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<input type="radio" id="PROCESSING" name="sample" value="PROCESSING" v-model="taskShowStatus" />
|
||||||
|
<label for="PROCESSING"><span>进行中</span></label>
|
||||||
|
<span v-show="countProc > 0" class="count">{{ countProc }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<input type="radio" id="COMPLETED" name="sample" value="COMPLETED" v-model="taskShowStatus" />
|
||||||
|
<label for="COMPLETED"><span>完 成</span></label>
|
||||||
|
<span v-show="countComp > 0" class="count">{{ countComp }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<bl-col class="tasks" just="flex-start" align="center">
|
||||||
|
<div v-for="task in taskShow" class="task" @click="showDetail(task)">
|
||||||
|
<bl-row just="space-between">
|
||||||
|
<div class="title">{{ task.taskName }}</div>
|
||||||
|
<div class="title">{{ task.deadLine }}</div>
|
||||||
|
</bl-row>
|
||||||
|
<div class="content">{{ task.taskContent }}</div>
|
||||||
|
<div class="cornor" :style="{ borderTop: `20px solid ${isBlank(task.color) ? '#E5E5E5' : task.color}` }"></div>
|
||||||
|
</div>
|
||||||
|
</bl-col>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<el-drawer v-model="isShowDetail" direction="btt" :with-header="true" size="345px">
|
||||||
|
<template #header="{ close, titleId, titleClass }">
|
||||||
|
<div class="drawer-header">
|
||||||
|
<el-input style="--el-input-border-color: #ffffff00" v-model="curTask.taskName"></el-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="detail">
|
||||||
|
<el-input type="textarea" placeholder="无内容" v-model="curTask.taskContent" resize="none" :rows="4"></el-input>
|
||||||
|
<bl-row class="tags">
|
||||||
|
<bl-tag v-for="tag in curTask.taskTags" :bg-color="isBlank(curTask.color) ? '#000' : curTask.color" :size="13" style="min-height: 20px">
|
||||||
|
{{ tag }}
|
||||||
|
</bl-tag>
|
||||||
|
</bl-row>
|
||||||
|
<bl-col class="times">
|
||||||
|
<bl-row style="color: red">截止至:{{ curTask.deadLine }}</bl-row>
|
||||||
|
<!-- <bl-row>创建于:{{ curTask.creTime }} </bl-row>
|
||||||
|
<bl-row>开始于:{{ curTask.startTime }} </bl-row>
|
||||||
|
<bl-row>完成于:{{ curTask.endTime }} </bl-row> -->
|
||||||
|
</bl-col>
|
||||||
|
<bl-row>
|
||||||
|
<el-slider v-model="curTask.process" :show-tooltip="false" size="small" />
|
||||||
|
<div class="process">{{ curTask.process }}%</div>
|
||||||
|
</bl-row>
|
||||||
|
<bl-row class="btns" just="space-between">
|
||||||
|
<el-button-group>
|
||||||
|
<el-button @click="updTask"><i class="iconbl bl-a-texteditorsave-line"></i></el-button>
|
||||||
|
<el-button @click="updTask">123</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
<el-button-group>
|
||||||
|
<el-button color="#000" v-if="curTask.taskStatus === 'WAITING'" @click="toProcessing">
|
||||||
|
<span class="iconbl bl-a-boxaddition-line"></span>开 始
|
||||||
|
</el-button>
|
||||||
|
<el-button color="#000" v-if="curTask.taskStatus === 'WAITING' || curTask.taskStatus === 'PROCESSING'" @click="toCompleted">
|
||||||
|
<span class="iconbl bl-a-boxchoice-line"></span>完 成
|
||||||
|
</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</bl-row>
|
||||||
|
<div class="status">
|
||||||
|
{{ curTask.taskStatus }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import type { CalendarDateType, CalendarInstance } from 'element-plus'
|
||||||
|
import { tasksApi, todosApi, toProcessingApi, toCompletedApi, updTaskApi } from '@/api/todo'
|
||||||
|
import type { TodoList, TaskInfo, TodoType, TaskStatus } from './scripts/types'
|
||||||
|
import { isBlank } from '@/assets/utils/obj'
|
||||||
|
import IndexHeader from '@/views/index/IndexHeader.vue'
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getTodos()
|
||||||
|
})
|
||||||
|
|
||||||
|
//#region ----------------------------------------< 待办事项 >--------------------------------------
|
||||||
|
|
||||||
|
const calendarDate = ref<Date>(new Date())
|
||||||
|
const CalendarRef = ref<CalendarInstance>()
|
||||||
|
const selectDate = (val: CalendarDateType) => {
|
||||||
|
if (!CalendarRef.value) return
|
||||||
|
CalendarRef.value.selectDate(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
const todoType = ref<TodoType>(10)
|
||||||
|
const todoDayMaps = ref<Map<string, TodoList>>(new Map())
|
||||||
|
const todoPhased = ref<TodoList[]>([])
|
||||||
|
const getTodos = () => {
|
||||||
|
todosApi().then((resp) => {
|
||||||
|
todoDayMaps.value.clear()
|
||||||
|
for (let key in resp.data.todoDays) {
|
||||||
|
let todo = resp.data.todoDays[key] as TodoList
|
||||||
|
todoDayMaps.value.set(key, {
|
||||||
|
todoId: todo.todoId,
|
||||||
|
todoName: todo.todoName,
|
||||||
|
todoStatus: 1,
|
||||||
|
todoType: 10,
|
||||||
|
today: false,
|
||||||
|
taskCount: todo.taskCount > 0 ? todo.taskCount : 0,
|
||||||
|
updTodoName: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
todoPhased.value = resp.data.taskPhased
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCount = (day: string): number => {
|
||||||
|
if (!todoDayMaps.value) return 0
|
||||||
|
if (!todoDayMaps.value.get(day)) return 0
|
||||||
|
return todoDayMaps.value.get(day)!.taskCount
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region ----------------------------------------< 任务 >--------------------------------------
|
||||||
|
// 当前待办事项
|
||||||
|
const curTask = ref<TaskInfo>({
|
||||||
|
id: '',
|
||||||
|
todoId: '',
|
||||||
|
todoName: '',
|
||||||
|
todoType: 10,
|
||||||
|
taskName: '',
|
||||||
|
taskContent: 'content 张三李四',
|
||||||
|
taskTags: ['张三', '李四', 'Article'],
|
||||||
|
deadLine: '今天下午两点30分',
|
||||||
|
creTime: '2023-01-01 12:21:32',
|
||||||
|
startTime: '2023-01-01 12:21:32',
|
||||||
|
endTime: '2023-01-01 12:21:32',
|
||||||
|
process: 0,
|
||||||
|
color: '#000',
|
||||||
|
taskStatus: 'WAITING',
|
||||||
|
updTaskName: false,
|
||||||
|
updTaskContent: false
|
||||||
|
})
|
||||||
|
const taskWait = ref<TaskInfo[]>([])
|
||||||
|
const taskProc = ref<TaskInfo[]>([])
|
||||||
|
const taskComp = ref<TaskInfo[]>([])
|
||||||
|
const countWait = computed<number>(() => taskWait.value.length)
|
||||||
|
const countProc = computed<number>(() => taskProc.value.filter((t: { todoType: number }) => t.todoType != 99).length)
|
||||||
|
const countComp = computed<number>(() => taskComp.value.filter((t: { todoType: number }) => t.todoType != 99).length)
|
||||||
|
const taskShowStatus = ref<TaskStatus>('WAITING')
|
||||||
|
const taskShow = computed(() => {
|
||||||
|
if (taskShowStatus.value === 'WAITING') {
|
||||||
|
return taskWait.value
|
||||||
|
}
|
||||||
|
if (taskShowStatus.value === 'PROCESSING') {
|
||||||
|
return taskProc.value.filter((t: { todoType: number }) => t.todoType != 99)
|
||||||
|
}
|
||||||
|
if (taskShowStatus.value === 'COMPLETED') {
|
||||||
|
return taskComp.value.filter((t: { todoType: number }) => t.todoType != 99)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const updatePillPosition = () => {
|
||||||
|
Array.from(document.querySelectorAll('.segmented-control .option input')).forEach((elem: Element, index) => {
|
||||||
|
let input = elem as HTMLInputElement
|
||||||
|
if (input.checked) {
|
||||||
|
let selection = document.querySelector('.segmented-control .selection')
|
||||||
|
if (selection) {
|
||||||
|
;(selection as HTMLElement).style.transform = 'translateX(' + input.offsetWidth * index + 'px)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查看指定待办事项
|
||||||
|
* @param todoId 待办事项ID
|
||||||
|
* @param todoName 待办事项名称
|
||||||
|
* @param todoType 待办事项类型
|
||||||
|
*/
|
||||||
|
const toTask = (todoId: string) => {
|
||||||
|
tasksApi({ todoId: todoId }).then((resp) => {
|
||||||
|
taskWait.value = resp.data.waiting
|
||||||
|
taskProc.value = resp.data.processing
|
||||||
|
taskComp.value = resp.data.completed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedCallback = (data: any) => {
|
||||||
|
taskWait.value = data.waiting
|
||||||
|
taskProc.value = data.processing
|
||||||
|
taskComp.value = data.completed
|
||||||
|
}
|
||||||
|
|
||||||
|
const toProcessing = () => {
|
||||||
|
toProcessingApi({ id: curTask.value.id!, todoId: curTask.value.todoId }).then((resp) => {
|
||||||
|
ElMessage('任务已开始')
|
||||||
|
savedCallback(resp.data)
|
||||||
|
isShowDetail.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const toCompleted = () => {
|
||||||
|
toCompletedApi({ id: curTask.value.id!, todoId: curTask.value.todoId }).then((resp) => {
|
||||||
|
ElMessage('任务已完成')
|
||||||
|
savedCallback(resp.data)
|
||||||
|
isShowDetail.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updTask = () => {
|
||||||
|
updTaskApi(curTask.value).then((resp) => {
|
||||||
|
ElMessage('修改成功')
|
||||||
|
isShowDetail.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const isShowDetail = ref(false)
|
||||||
|
const showDetail = (task: TaskInfo) => {
|
||||||
|
isShowDetail.value = true
|
||||||
|
curTask.value = task
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region 类型
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.todo-root {
|
||||||
|
@include box(100%, 100%);
|
||||||
|
@include flex(column, flex-start, center);
|
||||||
|
overflow: hidden;
|
||||||
|
--primary-light: #8abdff;
|
||||||
|
--primary: #6d5dfc;
|
||||||
|
--primary-dark: #5b0eeb;
|
||||||
|
|
||||||
|
--white: #ffffff;
|
||||||
|
--greyLight-1: #e4ebf5;
|
||||||
|
--greyLight-2: #c8d0e7;
|
||||||
|
--greyLight-3: #bec8e4;
|
||||||
|
--greyDark: #9baacf;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
@include box(100%, 60px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workbench,
|
||||||
|
.task-day-calendar,
|
||||||
|
.types,
|
||||||
|
.tasks,
|
||||||
|
.phaseds {
|
||||||
|
max-width: 900px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workbench {
|
||||||
|
padding: 10px 10px;
|
||||||
|
|
||||||
|
:deep(.el-button, .el-radio-button__inner) {
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-day-calendar {
|
||||||
|
min-height: 230px;
|
||||||
|
background-color: transparent;
|
||||||
|
--el-calendar-cell-width: 40px;
|
||||||
|
|
||||||
|
:deep(.el-calendar__header) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-calendar__body) {
|
||||||
|
padding: 0;
|
||||||
|
th {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 3px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-calendar-table {
|
||||||
|
tr:first-child td {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:first-child {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-calendar-table__row {
|
||||||
|
.prev,
|
||||||
|
.current,
|
||||||
|
.next {
|
||||||
|
.el-calendar-day {
|
||||||
|
padding: 0;
|
||||||
|
.cell-wrapper {
|
||||||
|
@include box(100%, 100%);
|
||||||
|
.day {
|
||||||
|
@include font(14px, 300);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.tag-root {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.phaseds {
|
||||||
|
@include box(100%, 230px);
|
||||||
|
@include flex(row, center, flex-start);
|
||||||
|
align-content: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding-left: 10px;
|
||||||
|
overflow: scroll;
|
||||||
|
.phased {
|
||||||
|
line-height: 33px;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 0 10px 10px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 3px 5px;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #d8d8d8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.segmented-control {
|
||||||
|
--background: rgba(239, 239, 240, 1);
|
||||||
|
background: var(--background);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 2px;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
grid-auto-columns: 1fr;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.selection {
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
border: 0.5px solid rgba(0, 0, 0, 0.04);
|
||||||
|
box-shadow: 0 3px 8px 0 rgba(0, 0, 0, 0.12), 0 3px 1px 0 rgba(0, 0, 0, 0.04);
|
||||||
|
border-radius: 5px;
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
z-index: 2;
|
||||||
|
will-change: transform;
|
||||||
|
-webkit-transition: transform 0.2s ease;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
label::before {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
&:checked + label::before,
|
||||||
|
&:checked + label::after {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::checked + label {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
padding: 3px 20px;
|
||||||
|
background: rgba(255, 255, 255, 0);
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgb(105, 105, 105);
|
||||||
|
font-size: 14px;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
right: 0;
|
||||||
|
transform: translateX(0.5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
-webkit-transition: all 0.2s ease;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
will-change: transform;
|
||||||
|
padding: 5px;
|
||||||
|
line-height: 18px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.count {
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
color: #bebebe;
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
right: 5px;
|
||||||
|
z-index: 3;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tasks {
|
||||||
|
width: 95%;
|
||||||
|
max-width: 650px;
|
||||||
|
padding: 0 10px 10px 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.task {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
position: relative;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: #d8d8d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
width: 70%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
&:last-child {
|
||||||
|
width: 30%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-top: 3px;
|
||||||
|
color: #a3a3a3;
|
||||||
|
word-break: break-all;
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cornor {
|
||||||
|
// @include box(20px, 20px);
|
||||||
|
// transform: rotate(45deg);
|
||||||
|
// border-top-left-radius: 10px;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
border-left: 20px solid transparent;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-header {
|
||||||
|
--el-font-size-base: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail {
|
||||||
|
@include flex(column, space-between, flex-start);
|
||||||
|
--el-font-size-base: 15px;
|
||||||
|
.el-textarea {
|
||||||
|
--el-input-border-color: #ffffff00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
height: 44px;
|
||||||
|
padding: 10px 0 3px 0;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.times {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
.bl-row-root {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.process {
|
||||||
|
width: 50px;
|
||||||
|
font-style: italic;
|
||||||
|
color: #bebebe;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btns {
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.iconbl {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
color: #bebebe;
|
||||||
|
position: absolute;
|
||||||
|
right: 30px;
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
84
blossom-web/src/views/todo/scripts/types.ts
Normal file
84
blossom-web/src/views/todo/scripts/types.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* 事项类型
|
||||||
|
* 10: 每日待办事项
|
||||||
|
* 20: 阶段性事项
|
||||||
|
* 99: 中午12点分割线
|
||||||
|
*/
|
||||||
|
export type TodoType = 10 | 20 | 99
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事项的状态, 专用于阶段性事项的分组, 所以当 TodoType = 1 时, 只有 1
|
||||||
|
* 1: 未完成
|
||||||
|
* 2: 完成
|
||||||
|
*/
|
||||||
|
export type TodoStatus = 1 | 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务状态
|
||||||
|
*/
|
||||||
|
export type TaskStatus = 'WAITING' | 'PROCESSING' | 'COMPLETED'
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 事项 todo 列表
|
||||||
|
*/
|
||||||
|
export interface TodoList {
|
||||||
|
id?: string
|
||||||
|
/**
|
||||||
|
* type = 1 时, todoId 为日期
|
||||||
|
* type = 2 时, todoId 为雪花ID
|
||||||
|
*/
|
||||||
|
todoId: string
|
||||||
|
/**
|
||||||
|
* type = 1 时, todoName 为日期
|
||||||
|
* type = 2 时, todoName 为标题
|
||||||
|
*/
|
||||||
|
todoName: string
|
||||||
|
/**
|
||||||
|
* 是否修改名称
|
||||||
|
*/
|
||||||
|
updTodoName: boolean
|
||||||
|
/**
|
||||||
|
* 事项状态
|
||||||
|
*/
|
||||||
|
todoStatus: TodoStatus
|
||||||
|
/**
|
||||||
|
* 事项类型
|
||||||
|
*/
|
||||||
|
todoType: TodoType
|
||||||
|
/**
|
||||||
|
* 是否当天, 用于 type = 1
|
||||||
|
*/
|
||||||
|
today: boolean
|
||||||
|
/**
|
||||||
|
* 任务数量
|
||||||
|
*/
|
||||||
|
taskCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务 task
|
||||||
|
*/
|
||||||
|
export interface TaskInfo {
|
||||||
|
id?: string
|
||||||
|
todoId: string
|
||||||
|
todoName: string
|
||||||
|
todoType: TodoType
|
||||||
|
taskName: string
|
||||||
|
taskContent: string
|
||||||
|
taskTags: string[]
|
||||||
|
deadLine: string
|
||||||
|
creTime: string
|
||||||
|
startTime?: string
|
||||||
|
endTime?: string
|
||||||
|
/**
|
||||||
|
* 处理百分比, 0 ~ 100
|
||||||
|
*/
|
||||||
|
process: number
|
||||||
|
color: string
|
||||||
|
taskStatus: TaskStatus
|
||||||
|
|
||||||
|
//
|
||||||
|
updTaskName: boolean
|
||||||
|
updTaskContent: boolean
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user