Files
cn-rdms-web/src/views/workbench/modules/workbench-todo-panel.vue

348 lines
8.1 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed, ref } from 'vue';
import type { RouteKey } from '@elegant-router/types';
import { useRouterPush } from '@/hooks/common/router';
import { filterWorkbenchTodoItems } from '../homepage';
import type { WorkbenchTodoItem, WorkbenchTodoTimeBucket } from '../homepage';
defineOptions({ name: 'WorkbenchTodoPanel' });
interface Props {
items: WorkbenchTodoItem[];
}
const props = defineProps<Props>();
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: '逾期' }
];
const bucketCounts = computed(() => ({
all: props.items.length,
today: filterWorkbenchTodoItems(props.items, 'today').length,
week: filterWorkbenchTodoItems(props.items, 'week').length,
overdue: filterWorkbenchTodoItems(props.items, 'overdue').length
}));
const filteredItems = computed(() => filterWorkbenchTodoItems(props.items, activeBucket.value));
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>
<ElCard class="workbench-todo card-wrapper" shadow="never">
<template #header>
<div class="workbench-todo__header">
<div class="workbench-todo__title-group">
<h3 class="workbench-todo__title">我的待办</h3>
<p class="workbench-todo__desc">需要我处理的需求评审任务工单与 @ 提醒</p>
</div>
<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>
</div>
</template>
<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" />
</ElCard>
</template>
<style scoped>
.workbench-todo {
overflow: hidden;
}
:deep(.el-card__header) {
padding: 16px 18px;
border-bottom: 1px solid rgb(226 232 240 / 80%);
}
.workbench-todo__header {
display: flex;
flex-direction: column;
gap: 14px;
}
.workbench-todo__title-group {
display: flex;
flex-direction: column;
gap: 4px;
}
.workbench-todo__title {
margin: 0;
color: rgb(15 23 42 / 98%);
font-size: 16px;
font-weight: 700;
}
.workbench-todo__desc {
margin: 0;
color: rgb(100 116 139 / 92%);
font-size: 13px;
line-height: 1.6;
}
.workbench-todo__tabs {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.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>