From 4b92ecb4227018a2ef74a9b3291fe23b4c1a0370 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Sat, 18 Jul 2026 04:47:12 +0000 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20=E5=AE=8C=E5=96=84=E4=B8=AA?= =?UTF-8?q?=E4=BA=BA=E8=A7=86=E8=A7=92=E7=A9=BA=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- language/original-web.txt | 10 + .../assets/js/pages/manage/dashboard.vue | 166 +++++++++++++---- resources/assets/js/store/actions.js | 2 + resources/assets/js/store/state.js | 3 +- .../assets/sass/pages/page-dashboard.scss | 176 +++++++++++++++++- 5 files changed, 315 insertions(+), 42 deletions(-) diff --git a/language/original-web.txt b/language/original-web.txt index 7476e8e67..b84b459be 100644 --- a/language/original-web.txt +++ b/language/original-web.txt @@ -2589,3 +2589,13 @@ AI任务分析 我的部门 待开始 暂无待开始任务 +太棒了,任务全部清空 +本周完成了 (*) 项任务 +欢迎使用 DooTask +项目是任务与协作的起点,创建一个开始 +创建第一个项目 +已有团队?请同事把你加入项目即可 +近期完成 +本周 (*) 项 +当前没有待处理任务 +可以新建一项任务,或等待新的工作安排 diff --git a/resources/assets/js/pages/manage/dashboard.vue b/resources/assets/js/pages/manage/dashboard.vue index 0dff2175d..d0254ec77 100644 --- a/resources/assets/js/pages/manage/dashboard.vue +++ b/resources/assets/js/pages/manage/dashboard.vue @@ -89,8 +89,37 @@
- - {{$L('太棒了,当前没有需要处理的任务')}} +
+
+ + + + {{emptyStateTitle}} + {{emptyStateSummary}} +
+ +
+
+ +
+ {{$L('已有团队?请同事把你加入项目即可')}} +
+
+ {{$L('近期完成')}} + {{$L('本周 (*) 项', weeklyCompletedCount)}} +
+
+ + {{task.name}} + {{projectName(task)}} + {{completedText(task.complete_at)}} +
+
+
@@ -118,7 +147,7 @@ {{listEmptyText(group.type)}}
{{$L('还有 (*) 项', group.list.length - groupLimit)}} →
+ @click="expandedGroups.push(group.type)">{{$L('还有 (*) 项', group.list.length - listLimit(group))}} →
@@ -259,13 +288,11 @@ export default { // 手动展开/收起的当天记忆:{type: 'open'|'close'},未记录的组走策略快照 collapsedOverrides: prefsCache.overrides, strategySnapshot: null, - // 列表布局显示哪些分组的快照(空组隐藏),进入时定格,停留期间不闪变 - visibleSnapshot: null, - snapshotPending: false, teamFocusPref: prefsCache.focus, expandedGroups: [], groupLimit: 10, + completedLimit: 5, quadLimit: 5, flashType: '', flashTimer: null, @@ -297,12 +324,9 @@ export default { }, activated() { - this.$store.dispatch("getTaskForDashboard", 600); this.loadInterval(true); this.loadLicense(true); - // 进入时按当前数据定策略快照,数据拉取完成后再校准一次;停留期间不随数据变化自动开合 this.takeSnapshot(); - this.snapshotPending = true; }, deactivated() { @@ -390,12 +414,77 @@ export default { return list; }, - // 列表布局显示的分组(空组隐藏,按快照定格) - listColumns({listAllColumns, visibleSnapshot}) { - if (visibleSnapshot === null) { - return listAllColumns.filter(group => group.count > 0); + // 当前任务分组按实际列表内容显示;临时完成的划线任务仍留在原分组中 + currentListColumns({listAllColumns}) { + return listAllColumns.filter(group => group.list.length > 0); + }, + + // 本周已完成任务:本周一至下周一,排除仍显示在原分组中的临时完成任务 + weeklyCompleted({cacheTasks, taskCompleteTemps, nowTime}) { + const current = $A.daytz(nowTime); + const start = current.clone().startOf('day').subtract((current.day() + 6) % 7, 'day'); + const next = start.clone().add(7, 'day'); + return cacheTasks.filter(task => { + if (task.archived_at || !task.complete_at || task.owner != 1 || taskCompleteTemps.includes(task.id)) { + return false; + } + const complete = $A.dayjs(task.complete_at); + return complete.valueOf() >= start.valueOf() && complete.valueOf() < next.valueOf(); + }).sort((a, b) => $A.sortDay(b.complete_at, a.complete_at)); + }, + + weeklyCompletedCount({weeklyCompleted}) { + return weeklyCompleted.length; + }, + + recentCompleted({weeklyCompleted, completedLimit}) { + return weeklyCompleted.slice(0, completedLimit); + }, + + completedColumn({weeklyCompleted, weeklyCompletedCount}) { + return { + type: 'completed', + title: this.getTitle('completed'), + hidden: this.isCollapsed('completed'), + count: weeklyCompletedCount, + list: weeklyCompleted, + }; + }, + + // 只要还有当前任务分组,本周完成就作为同款任务分组附加在末尾 + listColumns({currentListColumns, completedColumn}) { + if (currentListColumns.length === 0) { + return []; } - return listAllColumns.filter(group => visibleSnapshot.includes(group.type)); + return completedColumn.count > 0 ? [...currentListColumns, completedColumn] : currentListColumns; + }, + + emptyState({weeklyCompletedCount, cacheProjects, cacheTasks}) { + if (weeklyCompletedCount > 0) { + return 'completed'; + } + const hasVisibleTask = cacheTasks.some(task => !task.archived_at); + return cacheProjects.length === 0 && !hasVisibleTask ? 'new' : 'idle'; + }, + + emptyStateTitle({emptyState}) { + if (emptyState === 'completed') { + return this.$L('太棒了,任务全部清空'); + } + if (emptyState === 'new') { + return this.$L('欢迎使用 DooTask'); + } + return this.$L('当前没有待处理任务'); + }, + + emptyStateSummary({emptyState, weeklyCompletedCount}) { + if (emptyState === 'completed') { + return this.$L('本周完成了 (*) 项任务', weeklyCompletedCount); + } + if (emptyState === 'new') { + return this.$L('项目是任务与协作的起点,创建一个开始'); + } + return this.$L('可以新建一项任务,或等待新的工作安排'); }, overdueMaxDays({dashboardTask, nowTime}) { @@ -494,16 +583,6 @@ export default { } this.loadInterval(active) this.loadLicense(active) - if (active) { - this.$store.dispatch("getTaskForDashboard", 600) - } - }, - - loadDashboardTasks(loading) { - if (!loading && this.snapshotPending) { - this.snapshotPending = false - this.takeSnapshot() - } } }, @@ -520,6 +599,8 @@ export default { return this.$L('待开始'); case 'assist': return this.$L('我协助的'); + case 'completed': + return this.$L('本周完成'); default: return ''; } @@ -550,7 +631,6 @@ export default { /** * 策略快照(行动优先): - * - 空组不显示,全部为空时列表整卡显示缺省态 * - 显示的组默认展开;「待完成」量大且行动优先级低,超期或今日到期有数据时默认收起 */ takeSnapshot() { @@ -561,7 +641,6 @@ export default { upcoming: this.upcomingTask.count, assist: this.assistTask.length, }; - this.visibleSnapshot = ['overdue', 'today', 'todo', 'upcoming', 'assist'].filter(type => counts[type] > 0); const collapsed = []; if (counts.todo > 0 && (counts.overdue > 0 || counts.today > 0)) { collapsed.push('todo'); @@ -596,6 +675,10 @@ export default { return group.list.slice(0, limit); }, + listLimit(group) { + return group.type === 'completed' ? this.completedLimit : this.groupLimit; + }, + listEmptyText(type) { switch (type) { case 'overdue': @@ -633,10 +716,6 @@ export default { if (!column || column.count === 0) { return; } - // 快照后新出现数据的隐藏组,用户主动点击时补入显示 - if (this.visibleSnapshot !== null && !this.visibleSnapshot.includes(type)) { - this.visibleSnapshot = [...this.visibleSnapshot, type]; - } if (this.isCollapsed(type)) { this.toggleGroup(type); } @@ -672,6 +751,29 @@ export default { this.$store.dispatch("openTask", task) }, + createTask() { + this.$emit('on-click', 'addTask'); + }, + + createProject() { + this.$emit('on-click', 'addProject'); + }, + + completedText(completeAt) { + if (!completeAt) { + return ''; + } + const date = $A.dayjs(completeAt); + const now = $A.daytz(this.nowTime); + if (date.format('YYYY-MM-DD') === now.format('YYYY-MM-DD')) { + return `${this.$L('今天')} ${date.format('HH:mm')}`; + } + if (date.format('YYYY-MM-DD') === now.clone().subtract(1, 'day').format('YYYY-MM-DD')) { + return `${this.$L('昨天')} ${date.format('HH:mm')}`; + } + return `${this.$L('(*)月(*)日', date.month() + 1, date.date())} ${date.format('HH:mm')}`; + }, + openMenu(event, task) { this.$store.state.taskOperation = {event, task} }, diff --git a/resources/assets/js/store/actions.js b/resources/assets/js/store/actions.js index 887c07128..53631205d 100644 --- a/resources/assets/js/store/actions.js +++ b/resources/assets/js/store/actions.js @@ -1113,6 +1113,8 @@ export default { */ handleClearCache({state, dispatch}, userData) { return new Promise(async resolve => { + state.loadDashboardTasks = null; + // localStorage const keys = ['themeConf', 'languageName', 'keyboardConf']; const savedData = keys.reduce((acc, key) => ({ diff --git a/resources/assets/js/store/state.js b/resources/assets/js/store/state.js index abdb25829..e08e1ba74 100644 --- a/resources/assets/js/store/state.js +++ b/resources/assets/js/store/state.js @@ -75,7 +75,8 @@ export default { // 加载状态 loads: [], - loadDashboardTasks: false, + // null: 尚未初始化,true: 加载中,false: 已完成首次加载 + loadDashboardTasks: null, loadUserBasic: false, loadProjects: 0, loadDialogs: 0, diff --git a/resources/assets/sass/pages/page-dashboard.scss b/resources/assets/sass/pages/page-dashboard.scss index ce8143ae2..26e1f6a22 100644 --- a/resources/assets/sass/pages/page-dashboard.scss +++ b/resources/assets/sass/pages/page-dashboard.scss @@ -656,6 +656,18 @@ color: #8ba3c9; } } + &.group-completed { + background: rgba(231, 245, 236, 0.72); + .group-dot { + background: #1e9e55; + } + .group-title { + color: #287a4b; + } + .group-chevron { + color: #8db89c; + } + } &.group-flash { box-shadow: inset 0 0 0 2px #1e9e55; @@ -668,21 +680,134 @@ flex-direction: column; align-items: center; justify-content: center; - gap: 12px; - padding: 60px 20px; + padding: 48px 30px 42px; color: #a2a89f; font-size: 13px; + .empty-state-loading { + min-height: 260px; + display: flex; + align-items: center; + justify-content: center; + } + + .empty-state-content { + width: min(100%, 720px); + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + } + .empty-icon { display: flex; align-items: center; justify-content: center; - width: 40px; - height: 40px; + width: 48px; + height: 48px; border-radius: 50%; background: #e7f5ec; color: #1e9e55; - font-size: 20px; + font-size: 23px; + + &.empty-icon-new { + background: #fff4e5; + color: #c47316; + } + } + + .empty-state-title { + margin-top: 16px; + color: #22261f; + font-size: 18px; + font-weight: 600; + line-height: 1.4; + } + + .empty-state-summary { + margin-top: 8px; + color: #7a8078; + font-size: 13px; + line-height: 1.6; + } + + .empty-state-actions { + margin-top: 19px; + + .ivu-btn { + min-width: 112px; + } + } + + .empty-state-hint { + margin-top: 14px; + color: #a2a89f; + font-size: 12px; + } + + .recent-completed-list { + width: min(100%, 680px); + margin-top: 30px; + border-top: 1px solid #ecefe9; + text-align: left; + } + + .recent-completed-head { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 0 8px; + color: #6f776e; + font-size: 12px; + + > em { + color: #a2a89f; + font-size: 11px; + font-style: normal; + } + } + + .recent-completed-row { + display: grid; + grid-template-columns: 22px minmax(0, 1fr) 130px 82px; + align-items: center; + gap: 10px; + padding: 10px 0; + border-top: 1px solid #f0f2ee; + color: #4f584f; + cursor: pointer; + text-align: left; + + &:hover { + color: #22261f; + } + } + + .recent-completed-check { + color: $primary-color; + font-size: 18px; + } + + .recent-completed-name, + .recent-completed-project, + .recent-completed-time { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .recent-completed-name { + font-size: 13px; + } + + .recent-completed-project, + .recent-completed-time { + color: #a2a89f; + font-size: 11.5px; + } + + .recent-completed-time { + text-align: right; } } @@ -1213,12 +1338,21 @@ } .f-priority { - display: block; + display: flex; + align-items: center; + gap: 5px; color: #7a8078; font-size: 12px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + + > i { + flex-shrink: 0; + } + + > span { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } } .f-status { @@ -1280,6 +1414,10 @@ display: none; } } + + .recent-completed-list .recent-completed-row { + grid-template-columns: 22px minmax(0, 1fr) 100px 78px; + } } .dashboard-team { @@ -1312,6 +1450,14 @@ display: inline-block; } } + + .recent-completed-list .recent-completed-row { + grid-template-columns: 22px minmax(0, 1fr) 78px; + + .recent-completed-project { + display: none; + } + } } .dashboard-team { @@ -1438,6 +1584,18 @@ body.window-portrait { } } + .table-all-empty { + padding: 38px 14px 30px; + + .empty-state-title { + font-size: 16px; + } + + .recent-completed-list { + margin-top: 24px; + } + } + .table-group { padding: 10px 14px; }