2026-05-14 09:05:08 +08:00
|
|
|
|
<script setup lang="ts">
|
2026-06-01 21:37:08 +08:00
|
|
|
|
import { computed, markRaw, onMounted, ref, watch } from 'vue';
|
2026-05-14 09:05:08 +08:00
|
|
|
|
import type { RouteKey } from '@elegant-router/types';
|
2026-06-01 21:37:08 +08:00
|
|
|
|
import {
|
|
|
|
|
|
fetchApproveOvertimeApplication,
|
|
|
|
|
|
fetchGetOvertimeApplicationApprovalPage,
|
|
|
|
|
|
fetchRejectOvertimeApplication
|
|
|
|
|
|
} from '@/service/api';
|
2026-05-14 09:05:08 +08:00
|
|
|
|
import { useRouterPush } from '@/hooks/common/router';
|
2026-05-25 14:30:44 +08:00
|
|
|
|
import PersonalItemOperateDialog from '@/views/personal-center/my-item/modules/personal-item-operate-dialog.vue';
|
2026-06-01 21:37:08 +08:00
|
|
|
|
import OvertimeApplicationActionDialog from '@/views/personal-center/overtime-application/modules/overtime-application-action-dialog.vue';
|
|
|
|
|
|
import OvertimeApplicationDetailDialog from '@/views/personal-center/overtime-application/modules/overtime-application-detail-dialog.vue';
|
|
|
|
|
|
import OvertimeApplicationStatusLogDialog from '@/views/personal-center/overtime-application/modules/overtime-application-status-log-dialog.vue';
|
2026-05-21 21:42:23 +08:00
|
|
|
|
import {
|
2026-05-25 14:30:44 +08:00
|
|
|
|
type WorkbenchTodoDeadlineFilter,
|
2026-05-21 21:42:23 +08:00
|
|
|
|
type WorkbenchTodoItem,
|
2026-05-25 14:30:44 +08:00
|
|
|
|
type WorkbenchTodoMainTab,
|
2026-05-21 21:42:23 +08:00
|
|
|
|
buildWorkbenchTodoItems,
|
2026-05-25 14:30:44 +08:00
|
|
|
|
filterWorkbenchTodoItemsByCategory,
|
|
|
|
|
|
filterWorkbenchTodoItemsByDeadline,
|
|
|
|
|
|
isWorkbenchTodoOverdue,
|
|
|
|
|
|
sortWorkbenchTodoItemsByPriority
|
2026-05-21 21:42:23 +08:00
|
|
|
|
} from '../homepage';
|
|
|
|
|
|
import { workbenchTodoMock } from '../mock';
|
2026-06-04 11:26:51 +08:00
|
|
|
|
import { useWorkbenchRefresh } from '../composables/use-workbench-refresh';
|
2026-05-21 21:42:23 +08:00
|
|
|
|
import WorkbenchModuleCard from './workbench-module-card.vue';
|
2026-06-01 21:37:08 +08:00
|
|
|
|
import IconMdiCheckCircleOutline from '~icons/mdi/check-circle-outline';
|
|
|
|
|
|
import IconMdiCloseCircleOutline from '~icons/mdi/close-circle-outline';
|
|
|
|
|
|
import IconMdiEyeOutline from '~icons/mdi/eye-outline';
|
|
|
|
|
|
import IconMdiHistory from '~icons/mdi/history';
|
2026-05-14 09:05:08 +08:00
|
|
|
|
|
2026-05-25 14:30:44 +08:00
|
|
|
|
type SortKey = 'created' | 'priority' | 'deadline';
|
2026-06-01 21:37:08 +08:00
|
|
|
|
type OvertimeApprovalActionType = 'approve' | 'reject';
|
|
|
|
|
|
type ApprovalBizType = 'overtime_application';
|
2026-05-25 14:30:44 +08:00
|
|
|
|
|
2026-05-14 09:05:08 +08:00
|
|
|
|
defineOptions({ name: 'WorkbenchTodoPanel' });
|
|
|
|
|
|
|
|
|
|
|
|
interface Props {
|
2026-05-21 21:42:23 +08:00
|
|
|
|
editing?: boolean;
|
2026-05-14 09:05:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 11:26:51 +08:00
|
|
|
|
withDefaults(defineProps<Props>(), { editing: false });
|
2026-05-21 21:42:23 +08:00
|
|
|
|
|
|
|
|
|
|
defineEmits<{
|
|
|
|
|
|
(e: 'hide'): void;
|
|
|
|
|
|
}>();
|
2026-05-14 09:05:08 +08:00
|
|
|
|
|
|
|
|
|
|
const { routerPushByKey } = useRouterPush();
|
|
|
|
|
|
|
2026-06-04 11:26:51 +08:00
|
|
|
|
const { loading, refresh } = useWorkbenchRefresh();
|
|
|
|
|
|
|
2026-05-25 14:30:44 +08:00
|
|
|
|
const PAGE_SIZE = 5;
|
|
|
|
|
|
|
|
|
|
|
|
const activeTab = ref<WorkbenchTodoMainTab>('all');
|
|
|
|
|
|
const activeDeadlineFilter = ref<WorkbenchTodoDeadlineFilter>(null);
|
2026-06-01 21:37:08 +08:00
|
|
|
|
const activeApprovalBizType = ref<ApprovalBizType>('overtime_application');
|
2026-05-25 14:30:44 +08:00
|
|
|
|
const activeSort = ref<SortKey>('deadline');
|
|
|
|
|
|
const currentPage = ref(1);
|
|
|
|
|
|
|
|
|
|
|
|
const sortOptions = computed<Array<{ key: SortKey; label: string }>>(() => {
|
|
|
|
|
|
const base: Array<{ key: SortKey; label: string }> = [
|
|
|
|
|
|
{ key: 'deadline', label: '截止时间' },
|
|
|
|
|
|
{ key: 'created', label: '创建时间' }
|
|
|
|
|
|
];
|
|
|
|
|
|
if (activeTab.value === 'task') {
|
|
|
|
|
|
base.push({ key: 'priority', label: '优先级' });
|
|
|
|
|
|
}
|
|
|
|
|
|
return base;
|
|
|
|
|
|
});
|
2026-05-14 09:05:08 +08:00
|
|
|
|
|
2026-05-25 14:30:44 +08:00
|
|
|
|
const mainTabs: Array<{ key: WorkbenchTodoMainTab; label: string }> = [
|
2026-05-14 09:05:08 +08:00
|
|
|
|
{ key: 'all', label: '全部' },
|
2026-05-25 14:30:44 +08:00
|
|
|
|
{ key: 'task', label: '任务' },
|
|
|
|
|
|
{ key: 'ticket', label: '工单' },
|
|
|
|
|
|
{ key: 'personal', label: '个人事项' },
|
2026-05-28 08:20:01 +08:00
|
|
|
|
{ key: 'approval', label: '待审批' }
|
2026-05-14 09:05:08 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
2026-05-25 14:30:44 +08:00
|
|
|
|
const deadlineFilters: Array<{ key: Exclude<WorkbenchTodoDeadlineFilter, null>; label: string }> = [
|
|
|
|
|
|
{ key: 'overdue', label: '已逾期' },
|
|
|
|
|
|
{ key: 'today', label: '今日到期' },
|
|
|
|
|
|
{ key: 'week', label: '本周到期' }
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-06-01 21:37:08 +08:00
|
|
|
|
const approvalBizTabs: Array<{ key: ApprovalBizType; label: string }> = [
|
|
|
|
|
|
{ key: 'overtime_application', label: '加班申请' }
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-05-25 14:30:44 +08:00
|
|
|
|
const allItems = computed(() => buildWorkbenchTodoItems(workbenchTodoMock));
|
2026-06-01 21:37:08 +08:00
|
|
|
|
const overtimeApprovalItems = ref<WorkbenchTodoItem[]>([]);
|
|
|
|
|
|
const overtimeApprovalRows = ref<Api.OvertimeApplication.OvertimeApplication[]>([]);
|
|
|
|
|
|
const mergedItems = computed(() => {
|
|
|
|
|
|
const mockItems = allItems.value.filter(item => item.category !== 'approval');
|
|
|
|
|
|
|
|
|
|
|
|
return [...mockItems, ...overtimeApprovalItems.value];
|
|
|
|
|
|
});
|
2026-05-25 14:30:44 +08:00
|
|
|
|
|
|
|
|
|
|
const addDialogVisible = ref(false);
|
2026-06-01 21:37:08 +08:00
|
|
|
|
const overtimeDetailVisible = ref(false);
|
|
|
|
|
|
const overtimeStatusLogVisible = ref(false);
|
|
|
|
|
|
const overtimeActionVisible = ref(false);
|
|
|
|
|
|
const overtimeActionSubmitting = ref(false);
|
|
|
|
|
|
const currentOvertimeApplication = ref<Api.OvertimeApplication.OvertimeApplication | null>(null);
|
|
|
|
|
|
const currentOvertimeActionType = ref<OvertimeApprovalActionType>('approve');
|
|
|
|
|
|
|
|
|
|
|
|
const OVERTIME_APPROVAL_ACTION_ICONS = {
|
|
|
|
|
|
detail: markRaw(IconMdiEyeOutline),
|
|
|
|
|
|
approve: markRaw(IconMdiCheckCircleOutline),
|
|
|
|
|
|
reject: markRaw(IconMdiCloseCircleOutline),
|
|
|
|
|
|
statusLog: markRaw(IconMdiHistory)
|
|
|
|
|
|
};
|
2026-05-25 14:30:44 +08:00
|
|
|
|
|
|
|
|
|
|
function handleOpenAdd() {
|
|
|
|
|
|
addDialogVisible.value = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleAddSubmitted() {
|
|
|
|
|
|
activeTab.value = 'personal';
|
|
|
|
|
|
activeDeadlineFilter.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const tabCounts = computed(() => {
|
|
|
|
|
|
const counts: Record<WorkbenchTodoMainTab, number> = {
|
2026-06-01 21:37:08 +08:00
|
|
|
|
all: mergedItems.value.length,
|
2026-05-25 14:30:44 +08:00
|
|
|
|
task: 0,
|
|
|
|
|
|
ticket: 0,
|
|
|
|
|
|
personal: 0,
|
2026-05-28 08:20:01 +08:00
|
|
|
|
approval: 0
|
2026-05-25 14:30:44 +08:00
|
|
|
|
};
|
2026-06-01 21:37:08 +08:00
|
|
|
|
mergedItems.value.forEach(item => {
|
2026-05-25 14:30:44 +08:00
|
|
|
|
counts[item.category] += 1;
|
|
|
|
|
|
});
|
|
|
|
|
|
return counts;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const tabOverdueCount = computed(() => {
|
|
|
|
|
|
const map: Record<WorkbenchTodoMainTab, number> = {
|
|
|
|
|
|
all: 0,
|
|
|
|
|
|
task: 0,
|
|
|
|
|
|
ticket: 0,
|
|
|
|
|
|
personal: 0,
|
2026-05-28 08:20:01 +08:00
|
|
|
|
approval: 0
|
2026-05-25 14:30:44 +08:00
|
|
|
|
};
|
2026-06-01 21:37:08 +08:00
|
|
|
|
mergedItems.value.forEach(item => {
|
2026-05-25 14:30:44 +08:00
|
|
|
|
if (!isWorkbenchTodoOverdue(item)) return;
|
|
|
|
|
|
map.all += 1;
|
|
|
|
|
|
map[item.category] += 1;
|
|
|
|
|
|
});
|
|
|
|
|
|
return map;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-06-01 21:37:08 +08:00
|
|
|
|
const itemsInTab = computed(() => filterWorkbenchTodoItemsByCategory(mergedItems.value, activeTab.value));
|
|
|
|
|
|
|
|
|
|
|
|
const filteredItems = computed(() => {
|
|
|
|
|
|
if (activeTab.value === 'approval') {
|
|
|
|
|
|
return itemsInTab.value.filter(item => item.approvalBizType === activeApprovalBizType.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return filterWorkbenchTodoItemsByDeadline(itemsInTab.value, activeDeadlineFilter.value);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const approvalBizTabCounts = computed(() => {
|
|
|
|
|
|
const counts: Record<ApprovalBizType, number> = {
|
|
|
|
|
|
overtime_application: 0
|
|
|
|
|
|
};
|
2026-05-25 14:30:44 +08:00
|
|
|
|
|
2026-06-01 21:37:08 +08:00
|
|
|
|
itemsInTab.value.forEach(item => {
|
|
|
|
|
|
if (item.approvalBizType === 'overtime_application') {
|
|
|
|
|
|
counts.overtime_application += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return counts;
|
|
|
|
|
|
});
|
2026-05-25 14:30:44 +08:00
|
|
|
|
|
|
|
|
|
|
const sortedItems = computed(() => {
|
|
|
|
|
|
const base = filteredItems.value;
|
|
|
|
|
|
if (activeSort.value === 'priority') {
|
|
|
|
|
|
return sortWorkbenchTodoItemsByPriority(base, 'desc');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (activeSort.value === 'deadline') {
|
|
|
|
|
|
return [...base].sort((left, right) => {
|
|
|
|
|
|
const leftValue = left.remainingDays === null ? Number.POSITIVE_INFINITY : left.remainingDays;
|
|
|
|
|
|
const rightValue = right.remainingDays === null ? Number.POSITIVE_INFINITY : right.remainingDays;
|
|
|
|
|
|
return leftValue - rightValue;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
return base;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const currentSortLabel = computed(
|
|
|
|
|
|
() => sortOptions.value.find(option => option.key === activeSort.value)?.label ?? '排序'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const pagedItems = computed(() => {
|
|
|
|
|
|
const start = (currentPage.value - 1) * PAGE_SIZE;
|
|
|
|
|
|
return sortedItems.value.slice(start, start + PAGE_SIZE);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
watch([activeTab, activeDeadlineFilter, activeSort], () => {
|
|
|
|
|
|
currentPage.value = 1;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
function handleSelectTab(key: WorkbenchTodoMainTab) {
|
|
|
|
|
|
if (activeTab.value === key) return;
|
|
|
|
|
|
activeTab.value = key;
|
2026-05-28 08:20:01 +08:00
|
|
|
|
if (key === 'approval') activeDeadlineFilter.value = null;
|
2026-05-25 14:30:44 +08:00
|
|
|
|
activeDeadlineFilter.value = null;
|
|
|
|
|
|
if (key !== 'task' && activeSort.value === 'priority') {
|
|
|
|
|
|
activeSort.value = 'deadline';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-21 21:42:23 +08:00
|
|
|
|
|
2026-05-25 14:30:44 +08:00
|
|
|
|
function handleSelectDeadlineFilter(key: Exclude<WorkbenchTodoDeadlineFilter, null>) {
|
|
|
|
|
|
activeDeadlineFilter.value = activeDeadlineFilter.value === key ? null : key;
|
|
|
|
|
|
}
|
2026-05-14 09:05:08 +08:00
|
|
|
|
|
2026-06-01 21:37:08 +08:00
|
|
|
|
function handleSelectApprovalBizType(key: ApprovalBizType) {
|
|
|
|
|
|
activeApprovalBizType.value = key;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 14:30:44 +08:00
|
|
|
|
function handleSelectSort(key: SortKey) {
|
|
|
|
|
|
activeSort.value = key;
|
|
|
|
|
|
}
|
2026-05-14 09:05:08 +08:00
|
|
|
|
|
|
|
|
|
|
function handleClickItem(item: WorkbenchTodoItem) {
|
2026-06-01 21:37:08 +08:00
|
|
|
|
if (item.approvalBizType === 'overtime_application') {
|
|
|
|
|
|
openOvertimeDetail(item);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-14 09:05:08 +08:00
|
|
|
|
if (!item.routeKey) return;
|
|
|
|
|
|
routerPushByKey(item.routeKey as RouteKey);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 21:37:08 +08:00
|
|
|
|
function findOvertimeApprovalRow(item: WorkbenchTodoItem) {
|
|
|
|
|
|
if (!item.approvalBizId) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return overtimeApprovalRows.value.find(row => row.id === item.approvalBizId) || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openOvertimeDetail(item: WorkbenchTodoItem) {
|
|
|
|
|
|
const row = findOvertimeApprovalRow(item);
|
|
|
|
|
|
if (!row) return;
|
|
|
|
|
|
|
|
|
|
|
|
currentOvertimeApplication.value = row;
|
|
|
|
|
|
overtimeDetailVisible.value = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openOvertimeStatusLog(item: WorkbenchTodoItem) {
|
|
|
|
|
|
const row = findOvertimeApprovalRow(item);
|
|
|
|
|
|
if (!row) return;
|
|
|
|
|
|
|
|
|
|
|
|
currentOvertimeApplication.value = row;
|
|
|
|
|
|
overtimeStatusLogVisible.value = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openOvertimeAction(item: WorkbenchTodoItem, actionType: OvertimeApprovalActionType) {
|
|
|
|
|
|
const row = findOvertimeApprovalRow(item);
|
|
|
|
|
|
if (!row) return;
|
|
|
|
|
|
|
|
|
|
|
|
currentOvertimeApplication.value = row;
|
|
|
|
|
|
currentOvertimeActionType.value = actionType;
|
|
|
|
|
|
overtimeActionVisible.value = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function handleOvertimeActionSubmit(reason: string | null) {
|
|
|
|
|
|
if (!currentOvertimeApplication.value) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
overtimeActionSubmitting.value = true;
|
|
|
|
|
|
const result =
|
|
|
|
|
|
currentOvertimeActionType.value === 'approve'
|
|
|
|
|
|
? await fetchApproveOvertimeApplication(currentOvertimeApplication.value.id, { reason })
|
|
|
|
|
|
: await fetchRejectOvertimeApplication(currentOvertimeApplication.value.id, { reason });
|
|
|
|
|
|
overtimeActionSubmitting.value = false;
|
|
|
|
|
|
|
|
|
|
|
|
if (result.error) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
overtimeActionVisible.value = false;
|
|
|
|
|
|
overtimeDetailVisible.value = false;
|
|
|
|
|
|
window.$message?.success(currentOvertimeActionType.value === 'approve' ? '加班申请已通过' : '加班申请已退回');
|
|
|
|
|
|
await loadOvertimeApprovalItems();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadOvertimeApprovalItems() {
|
|
|
|
|
|
const { error, data } = await fetchGetOvertimeApplicationApprovalPage({
|
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
|
pageSize: 20,
|
|
|
|
|
|
statusCode: 'pending',
|
|
|
|
|
|
keyword: undefined,
|
|
|
|
|
|
applicantName: undefined,
|
|
|
|
|
|
approverId: undefined,
|
|
|
|
|
|
approverName: undefined,
|
|
|
|
|
|
overtimeDate: undefined,
|
|
|
|
|
|
createTime: undefined
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (error || !data) {
|
|
|
|
|
|
overtimeApprovalRows.value = [];
|
|
|
|
|
|
overtimeApprovalItems.value = [];
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
overtimeApprovalRows.value = data.list;
|
|
|
|
|
|
overtimeApprovalItems.value = buildWorkbenchTodoItems(
|
|
|
|
|
|
data.list.map(item => ({
|
|
|
|
|
|
id: `overtime-application-${item.id}`,
|
|
|
|
|
|
category: 'approval',
|
|
|
|
|
|
title: `${item.applicantName} · ${item.overtimeDate.slice(5, 7)} 月加班 ${item.overtimeDuration} 申请待审批`,
|
|
|
|
|
|
createdTime: item.submitTime || item.createTime,
|
|
|
|
|
|
deadline: item.submitTime || item.createTime,
|
|
|
|
|
|
source: `加班申请 · ${item.applicantName}`,
|
|
|
|
|
|
priority: 'mid',
|
|
|
|
|
|
approvalBizType: 'overtime_application',
|
|
|
|
|
|
approvalBizId: item.id
|
|
|
|
|
|
}))
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-14 09:05:08 +08:00
|
|
|
|
function getDeadlineToneClass(item: WorkbenchTodoItem) {
|
2026-05-25 14:30:44 +08:00
|
|
|
|
if (isWorkbenchTodoOverdue(item)) return 'workbench-todo__deadline--rose';
|
2026-05-14 09:05:08 +08:00
|
|
|
|
if (item.remainingDays === 0) return 'workbench-todo__deadline--amber';
|
|
|
|
|
|
return 'workbench-todo__deadline--slate';
|
|
|
|
|
|
}
|
2026-06-01 21:37:08 +08:00
|
|
|
|
|
|
|
|
|
|
onMounted(loadOvertimeApprovalItems);
|
2026-05-14 09:05:08 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-05-21 21:42:23 +08:00
|
|
|
|
<WorkbenchModuleCard
|
2026-06-04 11:26:51 +08:00
|
|
|
|
v-loading="loading"
|
2026-05-21 21:42:23 +08:00
|
|
|
|
title="我的待办"
|
|
|
|
|
|
icon="mdi:clipboard-text-clock-outline"
|
|
|
|
|
|
:editing="editing"
|
|
|
|
|
|
@hide="$emit('hide')"
|
2026-06-04 11:26:51 +08:00
|
|
|
|
@refresh="refresh"
|
2026-05-21 21:42:23 +08:00
|
|
|
|
>
|
|
|
|
|
|
<div class="workbench-todo__tabs">
|
2026-05-25 14:30:44 +08:00
|
|
|
|
<div class="workbench-todo__tabs-group">
|
|
|
|
|
|
<ElTooltip
|
|
|
|
|
|
v-for="tab in mainTabs"
|
|
|
|
|
|
:key="tab.key"
|
|
|
|
|
|
:content="`已逾期 ${tabOverdueCount[tab.key]} 项,建议尽快处理`"
|
|
|
|
|
|
:disabled="tabOverdueCount[tab.key] === 0"
|
|
|
|
|
|
placement="top"
|
|
|
|
|
|
effect="dark"
|
|
|
|
|
|
>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="workbench-todo__tab"
|
|
|
|
|
|
:class="{ 'workbench-todo__tab--active': activeTab === tab.key }"
|
|
|
|
|
|
@click="handleSelectTab(tab.key)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>{{ tab.label }}</span>
|
|
|
|
|
|
<span class="workbench-todo__tab-count">{{ tabCounts[tab.key] }}</span>
|
|
|
|
|
|
<span v-if="tabOverdueCount[tab.key] > 0" class="workbench-todo__tab-dot" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</ElTooltip>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<button type="button" class="workbench-todo__add" @click="handleOpenAdd">
|
|
|
|
|
|
<SvgIcon icon="mdi:plus" class="workbench-todo__add-icon" />
|
|
|
|
|
|
<span>个人事项</span>
|
2026-05-21 21:42:23 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-14 09:05:08 +08:00
|
|
|
|
|
2026-05-25 14:30:44 +08:00
|
|
|
|
<div class="workbench-todo__filters">
|
2026-05-28 08:20:01 +08:00
|
|
|
|
<div v-if="activeTab !== 'approval'" class="workbench-todo__filters-left">
|
2026-05-25 14:30:44 +08:00
|
|
|
|
<button
|
|
|
|
|
|
v-for="filter in deadlineFilters"
|
|
|
|
|
|
:key="filter.key"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="workbench-todo__filter"
|
|
|
|
|
|
:class="{ 'workbench-todo__filter--active': activeDeadlineFilter === filter.key }"
|
|
|
|
|
|
@click="handleSelectDeadlineFilter(filter.key)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ filter.label }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-06-01 21:37:08 +08:00
|
|
|
|
<div v-else class="workbench-todo__filters-left">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="tab in approvalBizTabs"
|
|
|
|
|
|
:key="tab.key"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="workbench-todo__filter"
|
|
|
|
|
|
:class="{ 'workbench-todo__filter--active': activeApprovalBizType === tab.key }"
|
|
|
|
|
|
@click="handleSelectApprovalBizType(tab.key)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ tab.label }}
|
|
|
|
|
|
<span class="workbench-todo__filter-count">{{ approvalBizTabCounts[tab.key] }}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-25 14:30:44 +08:00
|
|
|
|
|
|
|
|
|
|
<ElDropdown trigger="click" placement="bottom-end" @command="handleSelectSort">
|
|
|
|
|
|
<span class="workbench-todo__sort">
|
|
|
|
|
|
排序:{{ currentSortLabel }}
|
|
|
|
|
|
<SvgIcon icon="mdi:chevron-down" class="workbench-todo__sort-icon" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<template #dropdown>
|
|
|
|
|
|
<ElDropdownMenu>
|
|
|
|
|
|
<ElDropdownItem
|
|
|
|
|
|
v-for="option in sortOptions"
|
|
|
|
|
|
:key="option.key"
|
|
|
|
|
|
:command="option.key"
|
|
|
|
|
|
:class="{ 'is-active': activeSort === option.key }"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ option.label }}
|
|
|
|
|
|
</ElDropdownItem>
|
|
|
|
|
|
</ElDropdownMenu>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</ElDropdown>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="workbench-todo__content">
|
|
|
|
|
|
<div v-if="pagedItems.length" class="workbench-todo__list">
|
|
|
|
|
|
<article
|
|
|
|
|
|
v-for="item in pagedItems"
|
|
|
|
|
|
:key="item.id"
|
|
|
|
|
|
class="workbench-todo__item"
|
2026-06-01 21:37:08 +08:00
|
|
|
|
:class="{ 'workbench-todo__item--clickable': Boolean(item.routeKey || item.approvalBizType) }"
|
2026-05-25 14:30:44 +08:00
|
|
|
|
@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.priority === 'high'" 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>
|
2026-05-14 09:05:08 +08:00
|
|
|
|
</div>
|
2026-05-25 14:30:44 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="workbench-todo__trailing">
|
2026-06-01 21:37:08 +08:00
|
|
|
|
<div v-if="item.approvalBizType === 'overtime_application'" class="workbench-todo__actions" @click.stop>
|
|
|
|
|
|
<ElTooltip content="详情">
|
|
|
|
|
|
<ElButton link type="primary" class="workbench-todo__action-btn" @click="openOvertimeDetail(item)">
|
|
|
|
|
|
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.detail" class="text-15px" />
|
|
|
|
|
|
</ElButton>
|
|
|
|
|
|
</ElTooltip>
|
|
|
|
|
|
<ElTooltip content="通过">
|
|
|
|
|
|
<ElButton
|
|
|
|
|
|
link
|
|
|
|
|
|
type="success"
|
|
|
|
|
|
class="workbench-todo__action-btn"
|
|
|
|
|
|
@click="openOvertimeAction(item, 'approve')"
|
|
|
|
|
|
>
|
|
|
|
|
|
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.approve" class="text-15px" />
|
|
|
|
|
|
</ElButton>
|
|
|
|
|
|
</ElTooltip>
|
|
|
|
|
|
<ElTooltip content="退回">
|
|
|
|
|
|
<ElButton
|
|
|
|
|
|
link
|
|
|
|
|
|
type="danger"
|
|
|
|
|
|
class="workbench-todo__action-btn"
|
|
|
|
|
|
@click="openOvertimeAction(item, 'reject')"
|
|
|
|
|
|
>
|
|
|
|
|
|
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.reject" class="text-15px" />
|
|
|
|
|
|
</ElButton>
|
|
|
|
|
|
</ElTooltip>
|
|
|
|
|
|
<ElTooltip content="状态日志">
|
|
|
|
|
|
<ElButton link type="info" class="workbench-todo__action-btn" @click="openOvertimeStatusLog(item)">
|
|
|
|
|
|
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.statusLog" class="text-15px" />
|
|
|
|
|
|
</ElButton>
|
|
|
|
|
|
</ElTooltip>
|
|
|
|
|
|
</div>
|
2026-05-25 14:30:44 +08:00
|
|
|
|
<span class="workbench-todo__deadline" :class="getDeadlineToneClass(item)">
|
|
|
|
|
|
{{ item.deadlineLabel }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<ElEmpty v-else description="当前筛选下暂无待办" :image-size="72" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="workbench-todo__pager">
|
|
|
|
|
|
<ElPagination
|
|
|
|
|
|
v-if="filteredItems.length > PAGE_SIZE"
|
|
|
|
|
|
v-model:current-page="currentPage"
|
|
|
|
|
|
:page-size="PAGE_SIZE"
|
|
|
|
|
|
:total="filteredItems.length"
|
|
|
|
|
|
background
|
|
|
|
|
|
small
|
|
|
|
|
|
layout="prev, pager, next"
|
|
|
|
|
|
/>
|
2026-05-14 09:05:08 +08:00
|
|
|
|
</div>
|
2026-05-25 14:30:44 +08:00
|
|
|
|
|
2026-06-04 11:26:51 +08:00
|
|
|
|
<!-- append-to-body:脱离 grid item 的 transform 容器,弹窗才能正常全屏居中 -->
|
2026-05-25 14:30:44 +08:00
|
|
|
|
<PersonalItemOperateDialog
|
|
|
|
|
|
v-model:visible="addDialogVisible"
|
|
|
|
|
|
operate-type="add"
|
|
|
|
|
|
:row-data="null"
|
2026-06-04 11:26:51 +08:00
|
|
|
|
append-to-body
|
2026-05-25 14:30:44 +08:00
|
|
|
|
@submitted="handleAddSubmitted"
|
|
|
|
|
|
/>
|
2026-06-01 21:37:08 +08:00
|
|
|
|
|
|
|
|
|
|
<OvertimeApplicationDetailDialog v-model:visible="overtimeDetailVisible" :row-data="currentOvertimeApplication" />
|
|
|
|
|
|
<OvertimeApplicationStatusLogDialog
|
|
|
|
|
|
v-model:visible="overtimeStatusLogVisible"
|
|
|
|
|
|
:row-data="currentOvertimeApplication"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<OvertimeApplicationActionDialog
|
|
|
|
|
|
v-model:visible="overtimeActionVisible"
|
|
|
|
|
|
:action-type="currentOvertimeActionType"
|
|
|
|
|
|
:loading="overtimeActionSubmitting"
|
|
|
|
|
|
@submit="handleOvertimeActionSubmit"
|
|
|
|
|
|
/>
|
2026-05-21 21:42:23 +08:00
|
|
|
|
</WorkbenchModuleCard>
|
2026-05-14 09:05:08 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.workbench-todo__tabs {
|
2026-05-25 14:30:44 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__tabs-group {
|
2026-05-14 09:05:08 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
flex-wrap: wrap;
|
2026-05-25 14:30:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__add {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
padding: 5px 12px;
|
|
|
|
|
|
border: 1px solid rgb(14 116 144 / 60%);
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background-color: rgb(240 253 250 / 80%);
|
|
|
|
|
|
color: rgb(14 116 144 / 96%);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 160ms ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__add:hover {
|
|
|
|
|
|
background-color: rgb(14 116 144 / 96%);
|
|
|
|
|
|
border-color: rgb(14 116 144 / 96%);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__add-icon {
|
|
|
|
|
|
font-size: 14px;
|
2026-05-14 09:05:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__tab {
|
2026-05-25 14:30:44 +08:00
|
|
|
|
position: relative;
|
2026-05-14 09:05:08 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 14:30:44 +08:00
|
|
|
|
.workbench-todo__tab-dot {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 4px;
|
|
|
|
|
|
right: 6px;
|
|
|
|
|
|
width: 7px;
|
|
|
|
|
|
height: 7px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background-color: rgb(225 29 72 / 96%);
|
|
|
|
|
|
box-shadow: 0 0 0 2px rgb(255 255 255 / 96%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__tab--active .workbench-todo__tab-dot {
|
|
|
|
|
|
box-shadow: 0 0 0 2px rgb(14 116 144 / 96%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__filters {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__filters-left {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__sort {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 2px;
|
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
color: rgb(100 116 139 / 92%);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 160ms ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__sort:hover {
|
|
|
|
|
|
background-color: rgb(241 245 249 / 96%);
|
|
|
|
|
|
color: rgb(14 116 144 / 96%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__sort-icon {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-dropdown-menu__item.is-active) {
|
|
|
|
|
|
color: var(--el-color-primary);
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__filter {
|
|
|
|
|
|
padding: 3px 10px;
|
|
|
|
|
|
border: 1px dashed rgb(203 213 225 / 92%);
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background-color: transparent;
|
|
|
|
|
|
color: rgb(100 116 139 / 92%);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 160ms ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__filter:hover {
|
|
|
|
|
|
border-style: solid;
|
|
|
|
|
|
border-color: rgb(14 116 144 / 60%);
|
|
|
|
|
|
color: rgb(14 116 144 / 96%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__filter--active {
|
|
|
|
|
|
border-style: solid;
|
|
|
|
|
|
border-color: rgb(190 18 60 / 80%);
|
|
|
|
|
|
background-color: rgb(255 228 230 / 96%);
|
|
|
|
|
|
color: rgb(190 18 60 / 96%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__filter--active:hover {
|
|
|
|
|
|
border-color: rgb(190 18 60 / 92%);
|
|
|
|
|
|
color: rgb(190 18 60 / 96%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-01 21:37:08 +08:00
|
|
|
|
.workbench-todo__filter-count {
|
|
|
|
|
|
margin-left: 4px;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 14:30:44 +08:00
|
|
|
|
.workbench-todo__content {
|
2026-06-04 11:26:51 +08:00
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-height: 0;
|
2026-05-25 14:30:44 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2026-06-04 11:26:51 +08:00
|
|
|
|
overflow: auto;
|
2026-05-25 14:30:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__content :deep(.el-empty) {
|
|
|
|
|
|
margin: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-14 09:05:08 +08:00
|
|
|
|
.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;
|
2026-06-01 21:37:08 +08:00
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__actions {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.workbench-todo__actions :deep(.el-button + .el-button) {
|
|
|
|
|
|
margin-left: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.workbench-todo__action-btn) {
|
|
|
|
|
|
min-width: auto;
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
padding: 3px;
|
|
|
|
|
|
line-height: 1;
|
2026-05-14 09:05:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.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%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-25 14:30:44 +08:00
|
|
|
|
.workbench-todo__pager {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
min-height: 32px;
|
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-14 09:05:08 +08:00
|
|
|
|
@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>
|