2026-05-14 09:05:08 +08:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, ref } from 'vue';
|
|
|
|
|
import type { RouteKey } from '@elegant-router/types';
|
|
|
|
|
import { useRouterPush } from '@/hooks/common/router';
|
2026-05-21 21:42:23 +08:00
|
|
|
import {
|
|
|
|
|
type WorkbenchTodoItem,
|
|
|
|
|
type WorkbenchTodoTimeBucket,
|
|
|
|
|
buildWorkbenchTodoItems,
|
|
|
|
|
filterWorkbenchTodoItems
|
|
|
|
|
} from '../homepage';
|
|
|
|
|
import { workbenchTodoMock } from '../mock';
|
|
|
|
|
import WorkbenchModuleCard from './workbench-module-card.vue';
|
2026-05-14 09:05:08 +08:00
|
|
|
|
|
|
|
|
defineOptions({ name: 'WorkbenchTodoPanel' });
|
|
|
|
|
|
|
|
|
|
interface Props {
|
2026-05-21 21:42:23 +08:00
|
|
|
editing?: boolean;
|
|
|
|
|
collapsed?: boolean;
|
2026-05-14 09:05:08 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-21 21:42:23 +08:00
|
|
|
withDefaults(defineProps<Props>(), { editing: false, collapsed: false });
|
|
|
|
|
|
|
|
|
|
defineEmits<{
|
|
|
|
|
(e: 'hide'): void;
|
|
|
|
|
(e: 'toggle-collapse'): void;
|
|
|
|
|
}>();
|
2026-05-14 09:05:08 +08:00
|
|
|
|
|
|
|
|
const { routerPushByKey } = useRouterPush();
|
|
|
|
|
|
|
|
|
|
const activeBucket = ref<WorkbenchTodoTimeBucket>('all');
|
|
|
|
|
|
|
|
|
|
const buckets: Array<{ key: WorkbenchTodoTimeBucket; label: string }> = [
|
|
|
|
|
{ key: 'all', label: '全部' },
|
|
|
|
|
{ key: 'today', label: '今日' },
|
|
|
|
|
{ key: 'week', label: '本周' },
|
|
|
|
|
{ key: 'overdue', label: '逾期' }
|
|
|
|
|
];
|
|
|
|
|
|
2026-05-21 21:42:23 +08:00
|
|
|
const items = computed(() => buildWorkbenchTodoItems(workbenchTodoMock));
|
|
|
|
|
|
2026-05-14 09:05:08 +08:00
|
|
|
const bucketCounts = computed(() => ({
|
2026-05-21 21:42:23 +08:00
|
|
|
all: items.value.length,
|
|
|
|
|
today: filterWorkbenchTodoItems(items.value, 'today').length,
|
|
|
|
|
week: filterWorkbenchTodoItems(items.value, 'week').length,
|
|
|
|
|
overdue: filterWorkbenchTodoItems(items.value, 'overdue').length
|
2026-05-14 09:05:08 +08:00
|
|
|
}));
|
|
|
|
|
|
2026-05-21 21:42:23 +08:00
|
|
|
const filteredItems = computed(() => filterWorkbenchTodoItems(items.value, activeBucket.value));
|
2026-05-14 09:05:08 +08:00
|
|
|
|
|
|
|
|
function handleClickItem(item: WorkbenchTodoItem) {
|
|
|
|
|
if (!item.routeKey) return;
|
|
|
|
|
routerPushByKey(item.routeKey as RouteKey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getDeadlineToneClass(item: WorkbenchTodoItem) {
|
|
|
|
|
if (item.overdue || (item.remainingDays !== null && item.remainingDays < 0)) {
|
|
|
|
|
return 'workbench-todo__deadline--rose';
|
|
|
|
|
}
|
|
|
|
|
if (item.remainingDays === 0) return 'workbench-todo__deadline--amber';
|
|
|
|
|
return 'workbench-todo__deadline--slate';
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-05-21 21:42:23 +08:00
|
|
|
<WorkbenchModuleCard
|
|
|
|
|
title="我的待办"
|
|
|
|
|
icon="mdi:clipboard-text-clock-outline"
|
|
|
|
|
:editing="editing"
|
|
|
|
|
:collapsed="collapsed"
|
|
|
|
|
@hide="$emit('hide')"
|
|
|
|
|
@toggle-collapse="$emit('toggle-collapse')"
|
|
|
|
|
>
|
|
|
|
|
<div class="workbench-todo__tabs">
|
|
|
|
|
<button
|
|
|
|
|
v-for="bucket in buckets"
|
|
|
|
|
:key="bucket.key"
|
|
|
|
|
type="button"
|
|
|
|
|
class="workbench-todo__tab"
|
|
|
|
|
:class="{ 'workbench-todo__tab--active': activeBucket === bucket.key }"
|
|
|
|
|
@click="activeBucket = bucket.key"
|
|
|
|
|
>
|
|
|
|
|
<span>{{ bucket.label }}</span>
|
|
|
|
|
<span class="workbench-todo__tab-count">{{ bucketCounts[bucket.key] }}</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-05-14 09:05:08 +08:00
|
|
|
|
|
|
|
|
<div v-if="filteredItems.length" class="workbench-todo__list">
|
|
|
|
|
<article
|
|
|
|
|
v-for="item in filteredItems"
|
|
|
|
|
:key="item.id"
|
|
|
|
|
class="workbench-todo__item"
|
|
|
|
|
:class="{ 'workbench-todo__item--clickable': Boolean(item.routeKey) }"
|
|
|
|
|
@click="handleClickItem(item)"
|
|
|
|
|
>
|
|
|
|
|
<div class="workbench-todo__leading">
|
|
|
|
|
<span class="workbench-todo__category" :class="`workbench-todo__category--${item.categoryTone}`">
|
|
|
|
|
{{ item.categoryLabel }}
|
|
|
|
|
</span>
|
|
|
|
|
<span v-if="item.highPriority" class="workbench-todo__priority">高</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="workbench-todo__body">
|
|
|
|
|
<h4 class="workbench-todo__item-title">{{ item.title }}</h4>
|
|
|
|
|
<div class="workbench-todo__meta">
|
|
|
|
|
<span class="workbench-todo__source">{{ item.source }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="workbench-todo__trailing">
|
|
|
|
|
<span class="workbench-todo__deadline" :class="getDeadlineToneClass(item)">
|
|
|
|
|
{{ item.deadlineLabel }}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</article>
|
|
|
|
|
</div>
|
|
|
|
|
<ElEmpty v-else description="当前筛选下暂无待办" :image-size="72" />
|
2026-05-21 21:42:23 +08:00
|
|
|
</WorkbenchModuleCard>
|
2026-05-14 09:05:08 +08:00
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.workbench-todo__tabs {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
flex-wrap: wrap;
|
2026-05-21 21:42:23 +08:00
|
|
|
margin-bottom: 14px;
|
2026-05-14 09:05:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__tab {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
border: 1px solid rgb(226 232 240 / 92%);
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background-color: rgb(255 255 255 / 96%);
|
|
|
|
|
color: rgb(71 85 105 / 94%);
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 160ms ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__tab:hover {
|
|
|
|
|
border-color: rgb(14 116 144 / 64%);
|
|
|
|
|
color: rgb(14 116 144 / 96%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__tab--active {
|
|
|
|
|
border-color: rgb(14 116 144 / 92%);
|
|
|
|
|
background-color: rgb(14 116 144 / 96%);
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__tab--active:hover {
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__tab-count {
|
|
|
|
|
padding: 1px 6px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
background-color: rgb(241 245 249 / 96%);
|
|
|
|
|
color: rgb(71 85 105 / 94%);
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__tab--active .workbench-todo__tab-count {
|
|
|
|
|
background-color: rgb(255 255 255 / 22%);
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__list {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__item {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 14px;
|
|
|
|
|
padding: 14px 16px;
|
|
|
|
|
border: 1px solid rgb(226 232 240 / 90%);
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
background-color: rgb(255 255 255 / 98%);
|
|
|
|
|
transition:
|
|
|
|
|
border-color 160ms ease,
|
|
|
|
|
background-color 160ms ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__item--clickable {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__item--clickable:hover {
|
|
|
|
|
border-color: rgb(14 116 144 / 60%);
|
|
|
|
|
background-color: rgb(240 253 250 / 84%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__leading {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__category {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 3px 10px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__category--sky {
|
|
|
|
|
background-color: rgb(224 242 254 / 96%);
|
|
|
|
|
color: rgb(14 116 144 / 96%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__category--emerald {
|
|
|
|
|
background-color: rgb(220 252 231 / 96%);
|
|
|
|
|
color: rgb(5 150 105 / 96%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__category--amber {
|
|
|
|
|
background-color: rgb(254 243 199 / 96%);
|
|
|
|
|
color: rgb(180 83 9 / 96%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__category--rose {
|
|
|
|
|
background-color: rgb(255 228 230 / 96%);
|
|
|
|
|
color: rgb(190 18 60 / 96%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__category--violet {
|
|
|
|
|
background-color: rgb(237 233 254 / 96%);
|
|
|
|
|
color: rgb(109 40 217 / 96%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__priority {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
width: 20px;
|
|
|
|
|
height: 20px;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
background-color: rgb(254 226 226 / 96%);
|
|
|
|
|
color: rgb(220 38 38 / 96%);
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__body {
|
|
|
|
|
min-width: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__item-title {
|
|
|
|
|
margin: 0;
|
|
|
|
|
color: rgb(15 23 42 / 98%);
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__meta {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__source {
|
|
|
|
|
color: rgb(100 116 139 / 92%);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__trailing {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__deadline {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 4px 10px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__deadline--slate {
|
|
|
|
|
background-color: rgb(241 245 249 / 96%);
|
|
|
|
|
color: rgb(71 85 105 / 94%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__deadline--amber {
|
|
|
|
|
background-color: rgb(254 243 199 / 96%);
|
|
|
|
|
color: rgb(180 83 9 / 96%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__deadline--rose {
|
|
|
|
|
background-color: rgb(255 228 230 / 96%);
|
|
|
|
|
color: rgb(190 18 60 / 96%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (width <= 600px) {
|
|
|
|
|
.workbench-todo__item {
|
|
|
|
|
grid-template-columns: auto minmax(0, 1fr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.workbench-todo__trailing {
|
|
|
|
|
grid-column: 1 / -1;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|