From e080105f9fe9ede53220d30d4b361a2c7e729d0c Mon Sep 17 00:00:00 2001 From: caoxiaozhu Date: Fri, 29 May 2026 13:17:39 +0800 Subject: [PATCH] feat(ui): finalize shared shells and loading states --- .../finance-rules/公司通信费报销规则.xlsx | Bin 9791 -> 14480 bytes web/package-lock.json | 7 - web/package.json | 1 - web/src/assets/styles/app.css | 88 ---- .../components/enterprise-page-shell.css | 273 +++++++++++- web/src/assets/styles/global.css | 49 +-- .../styles/views/digital-employees-view.css | 82 ++-- web/src/assets/styles/views/logs-view.css | 20 + web/src/assets/styles/views/policies-view.css | 8 - web/src/assets/styles/views/settings-view.css | 23 + web/src/assets/styles/views/setup-view.css | 28 +- .../audit/DigitalEmployeeListPanel.vue | 113 +---- .../audit/DigitalEmployeeWorkRecords.vue | 404 ++++++++++-------- web/src/components/layout/SidebarRail.vue | 34 +- web/src/components/layout/TopBar.vue | 40 +- .../shared/FloatingLightBandWindow.vue | 267 ++++++++++++ .../components/shared/TableLoadingState.vue | 171 +++----- web/src/composables/useAppShell.js | 57 +-- web/src/composables/useDocumentCenterInbox.js | 29 +- web/src/composables/useMinimumVisibleState.js | 78 ++++ web/src/composables/useNavigation.js | 18 +- web/src/composables/useRequests.js | 8 +- web/src/composables/useSettings.js | 66 ++- web/src/composables/useSetupView.js | 10 +- web/src/main.js | 1 - web/src/router/index.js | 24 +- web/src/utils/accessControl.js | 26 +- web/src/utils/refreshIntervalOptions.js | 16 + web/src/utils/settingsModelHelper.js | 15 +- web/src/views/AppShellRouteView.vue | 50 ++- web/src/views/BudgetCenterView.vue | 8 + web/src/views/DigitalEmployeesView.vue | 2 +- web/src/views/DocumentsCenterView.vue | 9 +- web/src/views/EmployeeManagementView.vue | 1 + web/src/views/LogDetailView.vue | 14 +- web/src/views/LogsView.vue | 36 +- web/src/views/PoliciesView.vue | 21 +- web/src/views/SettingsView.vue | 77 +--- web/src/views/SetupView.vue | 24 +- web/src/views/scripts/AuditView.js | 13 +- web/src/views/scripts/BudgetCenterView.js | 5 +- web/src/views/scripts/LogsView.js | 21 +- web/src/views/scripts/SettingsView.js | 4 + .../views/scripts/auditViewDetailTopBar.js | 15 +- web/src/views/scripts/auditViewModel.js | 4 + .../views/scripts/auditViewRiskRuleState.js | 12 +- web/tests/accessControl.test.mjs | 1 + web/tests/minimum-visible-state.test.mjs | 56 +++ .../navigation-route-resolution.test.mjs | 9 +- web/tests/refresh-interval-controls.test.mjs | 34 ++ .../settings-system-logs-section.test.mjs | 22 + web/vite.config.js | 26 +- 52 files changed, 1559 insertions(+), 861 deletions(-) create mode 100644 web/src/components/shared/FloatingLightBandWindow.vue create mode 100644 web/src/composables/useMinimumVisibleState.js create mode 100644 web/src/utils/refreshIntervalOptions.js create mode 100644 web/tests/minimum-visible-state.test.mjs create mode 100644 web/tests/refresh-interval-controls.test.mjs create mode 100644 web/tests/settings-system-logs-section.test.mjs diff --git a/server/rules/finance-rules/公司通信费报销规则.xlsx b/server/rules/finance-rules/公司通信费报销规则.xlsx index 63bf7ef7c3750acece9f2089fefacac911d8aa31..510877a23e24479a8c36f7213a3ecd992783f991 100644 GIT binary patch literal 14480 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 diff --git a/web/package-lock.json b/web/package-lock.json index efae457..9afab4f 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -17,7 +17,6 @@ "element-plus": "^2.14.0", "markdown-it": "^14.1.1", "pg": "^8.13.1", - "primeicons": "^7.0.0", "vite": "^5.4.19", "vue": "^3.5.13", "vue-chartjs": "^5.3.3", @@ -2754,12 +2753,6 @@ "node": ">=0.10.0" } }, - "node_modules/primeicons": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-7.0.0.tgz", - "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==", - "license": "MIT" - }, "node_modules/punycode.js": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz", diff --git a/web/package.json b/web/package.json index 1fdabb9..64d8ee3 100644 --- a/web/package.json +++ b/web/package.json @@ -19,7 +19,6 @@ "element-plus": "^2.14.0", "markdown-it": "^14.1.1", "pg": "^8.13.1", - "primeicons": "^7.0.0", "vite": "^5.4.19", "vue": "^3.5.13", "vue-chartjs": "^5.3.3", diff --git a/web/src/assets/styles/app.css b/web/src/assets/styles/app.css index d54784c..2faa116 100644 --- a/web/src/assets/styles/app.css +++ b/web/src/assets/styles/app.css @@ -69,68 +69,6 @@ pointer-events: none; } -.login-entry-card { - width: min(360px, calc(100% - 48px)); - display: grid; - grid-template-columns: 42px minmax(0, 1fr); - gap: 12px 14px; - align-items: center; - padding: 22px 24px 20px; - border: 1px solid rgba(148, 163, 184, 0.26); - border-radius: 4px; - background: #fff; - box-shadow: 0 20px 46px rgba(15, 23, 42, 0.14); - animation: loginEntryCardIn 360ms cubic-bezier(0.16, 1, 0.3, 1) both; -} - -.login-entry-mark { - width: 42px; - height: 42px; - display: inline-grid; - place-items: center; - border: 1px solid rgba(var(--theme-primary-rgb, 58, 124, 165), 0.2); - border-radius: 4px; - background: var(--theme-primary-soft); - color: var(--theme-primary-active); - font-size: 22px; -} - -.login-entry-copy { - min-width: 0; - display: grid; - gap: 4px; -} - -.login-entry-copy strong { - color: var(--ink); - font-size: 16px; - line-height: 1.35; - font-weight: 750; -} - -.login-entry-copy span { - color: var(--muted); - font-size: 13px; - line-height: 1.45; -} - -.login-entry-progress { - grid-column: 1 / -1; - height: 3px; - overflow: hidden; - background: #edf2f7; -} - -.login-entry-progress::after { - content: ''; - display: block; - width: 100%; - height: 100%; - background: var(--theme-primary); - transform-origin: left center; - animation: loginEntryProgress 840ms cubic-bezier(0.2, 0, 0, 1) both; -} - .login-entry-veil-enter-active { transition: opacity 180ms var(--ease); } @@ -231,7 +169,6 @@ .main.policies-main, .main.audit-main, .main.digital-employees-main, -.main.logs-main, .main.employees-main, .main.settings-main { height: var(--desktop-stage-height, 100dvh); @@ -250,7 +187,6 @@ .workarea.policies-workarea, .workarea.audit-workarea, .workarea.digital-employees-workarea, -.workarea.logs-workarea, .workarea.employees-workarea, .workarea.settings-workarea { min-height: 0; @@ -267,28 +203,6 @@ background: #fff; } -@keyframes loginEntryCardIn { - from { - opacity: 0; - transform: scale3d(0.92, 0.92, 1); - } - - to { - opacity: 1; - transform: scale3d(1, 1, 1); - } -} - -@keyframes loginEntryProgress { - from { - transform: scaleX(0); - } - - to { - transform: scaleX(1); - } -} - @keyframes loginEntrySidebarIn { from { opacity: 0; @@ -401,8 +315,6 @@ transition-duration: 120ms, 120ms !important; } - .login-entry-card, - .login-entry-progress::after, .app.login-entry-active .app-sidebar, .app.login-entry-active > .main { animation: none !important; diff --git a/web/src/assets/styles/components/enterprise-page-shell.css b/web/src/assets/styles/components/enterprise-page-shell.css index d098090..8ae497c 100644 --- a/web/src/assets/styles/components/enterprise-page-shell.css +++ b/web/src/assets/styles/components/enterprise-page-shell.css @@ -13,6 +13,12 @@ margin-top: 14px; border-bottom: 1px solid #dbe4ee; overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; +} + +.enterprise-list-page .status-tabs::-webkit-scrollbar { + display: none; } .enterprise-list-page .status-tabs button { @@ -65,6 +71,7 @@ } .enterprise-list-page .filter-set { + flex: 1 1 auto; display: flex; align-items: center; gap: 12px; @@ -74,7 +81,14 @@ .enterprise-list-page .list-search { position: relative; - width: min(280px, 100%); + flex: 0 1 280px; + width: 280px; + min-width: 220px; + display: block; +} + +.enterprise-list-page .picker-filter { + flex: 0 0 auto; } .enterprise-list-page .list-search .mdi { @@ -202,13 +216,32 @@ color: #64748b; } +.enterprise-list-page .active-filter-strip { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 12px; +} + +.enterprise-list-page .active-filter-chip { + min-height: 28px; + display: inline-flex; + align-items: center; + padding: 0 10px; + border-radius: 4px; + background: var(--theme-primary-soft); + color: var(--theme-primary-active); + font-size: 12px; + font-weight: 800; +} + .enterprise-list-page .table-wrap { min-height: 400px; margin-top: 10px; overflow: auto; border: 1px solid #edf2f7; - border-radius: 4px; - background: linear-gradient(180deg, #fcfeff 0%, var(--theme-primary-light-9) 100%); + border-radius: 10px; + background: linear-gradient(180deg, #fcfefd 0%, #f4f8f6 100%); display: flex; flex-direction: column; align-items: stretch; @@ -263,7 +296,8 @@ font-weight: 750; } -.enterprise-list-page table { +.enterprise-list-page table, +.enterprise-list-page .table-wrap > table { width: 100%; min-width: 1080px; align-self: flex-start; @@ -272,7 +306,9 @@ } .enterprise-list-page th, -.enterprise-list-page td { +.enterprise-list-page td, +.enterprise-list-page .table-wrap th, +.enterprise-list-page .table-wrap td { padding: 13px 12px; border-bottom: 1px solid #edf2f7; color: #24324a; @@ -285,7 +321,8 @@ text-overflow: ellipsis; } -.enterprise-list-page th { +.enterprise-list-page th, +.enterprise-list-page .table-wrap th { position: sticky; top: 0; z-index: 1; @@ -295,16 +332,20 @@ font-weight: 800; } -.enterprise-list-page tbody tr { +.enterprise-list-page tbody tr, +.enterprise-list-page .table-wrap tbody tr { cursor: pointer; } .enterprise-list-page tbody tr:hover, -.enterprise-list-page tbody tr.spotlight { +.enterprise-list-page tbody tr.spotlight, +.enterprise-list-page .table-wrap tbody tr:hover, +.enterprise-list-page .table-wrap tbody tr.spotlight { background: linear-gradient(90deg, rgba(var(--theme-primary-rgb, 58, 124, 165), 0.08), rgba(var(--theme-primary-rgb, 58, 124, 165), 0.03)); } -.enterprise-list-page tbody tr:last-child td { +.enterprise-list-page tbody tr:last-child td, +.enterprise-list-page .table-wrap tbody tr:last-child td { border-bottom: 0; } @@ -313,11 +354,119 @@ font-weight: 800; } +.enterprise-list-page .doc-kind-tag, +.enterprise-list-page .type-tag, +.enterprise-list-page .status-tag { + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; +} + +.enterprise-list-page .doc-kind-tag { + min-height: 26px; + padding: 0 10px; + border-radius: 7px; + font-size: 12px; + font-weight: 800; +} + +.enterprise-list-page .doc-kind-tag.reimbursement { + background: var(--theme-primary-light-9); + color: var(--theme-primary-active); +} + +.enterprise-list-page .doc-kind-tag.application { + background: #eff6ff; + color: #2563eb; +} + +.enterprise-list-page .type-tag { + min-height: 26px; + padding: 0 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 800; +} + +.enterprise-list-page .type-tag.travel, +.enterprise-list-page .type-tag.hotel, +.enterprise-list-page .type-tag.transport { + background: var(--theme-primary-light-9); + color: var(--theme-primary-active); +} + +.enterprise-list-page .type-tag.entertainment, +.enterprise-list-page .type-tag.meal { + background: #fff7ed; + color: #ea580c; +} + +.enterprise-list-page .type-tag.office { + background: #eff6ff; + color: #2563eb; +} + +.enterprise-list-page .type-tag.meeting, +.enterprise-list-page .type-tag.training { + background: #eef2ff; + color: #4f46e5; +} + +.enterprise-list-page .type-tag.other { + background: #f8fafc; + color: #475569; +} + +.enterprise-list-page .status-tag { + min-height: 24px; + padding: 0 9px; + border: 1px solid transparent; + border-radius: 6px; + font-size: 12px; + font-weight: 750; +} + +.enterprise-list-page .status-tag.info { + border-color: #bfdbfe; + background: #eff6ff; + color: #2563eb; +} + +.enterprise-list-page .status-tag.success, +.enterprise-list-page .status-tag.archived { + border-color: var(--success-line); + background: var(--success-soft); + color: var(--success-active); +} + +.enterprise-list-page .status-tag.warning, +.enterprise-list-page .status-tag.draft { + border-color: #fed7aa; + background: #fff7ed; + color: #f97316; +} + +.enterprise-list-page .status-tag.danger { + border-color: #fecaca; + background: #fef2f2; + color: #dc2626; +} + +.enterprise-list-page .status-tag.neutral, +.enterprise-list-page .status-tag.muted, +.enterprise-list-page .status-tag.disabled { + border-color: #cbd5e1; + background: #f8fafc; + color: #475569; +} + .enterprise-pagination { flex: 0 0 auto; } -.enterprise-list-page .list-foot { +.enterprise-list-page .list-foot, +.enterprise-list-page.enterprise-list-page .list-foot { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; @@ -325,27 +474,30 @@ margin-top: 12px; } -.enterprise-list-page .page-summary { +.enterprise-list-page .page-summary, +.enterprise-list-page.enterprise-list-page .page-summary { color: #64748b; font-size: 14px; font-weight: 650; } -.enterprise-list-page .pager { +.enterprise-list-page .pager, +.enterprise-list-page.enterprise-list-page .pager { display: inline-flex; justify-content: center; gap: 6px; padding: 4px; border: 1px solid #e2e8f0; - border-radius: 4px; + border-radius: 12px; background: #f8fafc; } -.enterprise-list-page .pager button { +.enterprise-list-page .pager button, +.enterprise-list-page.enterprise-list-page .pager button { width: 32px; height: 32px; border: 0; - border-radius: 4px; + border-radius: 9px; background: transparent; color: #334155; font-size: 14px; @@ -353,19 +505,22 @@ transition: background 160ms ease, color 160ms ease, box-shadow 160ms ease; } -.enterprise-list-page .pager button:hover:not(.active) { +.enterprise-list-page .pager button:hover:not(.active), +.enterprise-list-page.enterprise-list-page .pager button:hover:not(.active) { background: #fff; color: var(--theme-primary-active); box-shadow: 0 1px 4px rgba(15, 23, 42, 0.08); } -.enterprise-list-page .pager button.active { - background: var(--theme-primary); +.enterprise-list-page .pager button.active, +.enterprise-list-page.enterprise-list-page .pager button.active { + background: var(--theme-primary-active); color: #fff; box-shadow: 0 8px 16px var(--theme-primary-shadow); } -.enterprise-list-page .pager button:disabled { +.enterprise-list-page .pager button:disabled, +.enterprise-list-page.enterprise-list-page .pager button:disabled { cursor: not-allowed; opacity: 0.45; box-shadow: none; @@ -394,6 +549,86 @@ overflow: auto; } +.enterprise-detail-page .detail-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px 0 0; + border-top: 1px solid #e5eaf0; +} + +.enterprise-detail-page .detail-action-group { + display: flex; + justify-content: flex-end; + gap: 8px; +} + +.enterprise-detail-page .back-action, +.enterprise-detail-page .minor-action, +.enterprise-detail-page .major-action { + height: 38px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 0 14px; + border-radius: 4px; + font-size: 13px; + font-weight: 760; + transition: transform 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease; +} + +.enterprise-detail-page .back-action, +.enterprise-detail-page .minor-action { + border: 1px solid #d7e0ea; + background: #fff; + color: #334155; +} + +.enterprise-detail-page .minor-action.success-action { + border-color: var(--success-line); + background: var(--success-soft); + color: var(--success-hover); +} + +.enterprise-detail-page .minor-action.enable-action { + border-color: rgba(100, 116, 139, 0.26); + color: #64748b; +} + +.enterprise-detail-page .minor-action.enable-action.is-on { + border-color: rgba(var(--success-rgb), 0.26); + color: var(--success-hover); +} + +.enterprise-detail-page .minor-action.danger-action { + border-color: rgba(220, 38, 38, 0.2); + color: #dc2626; +} + +.enterprise-detail-page .major-action { + border: 1px solid var(--theme-primary); + background: var(--theme-primary); + color: #fff; + box-shadow: 0 4px 12px var(--theme-primary-shadow); +} + +.enterprise-detail-page .back-action:hover, +.enterprise-detail-page .minor-action:hover, +.enterprise-detail-page .major-action:hover { + transform: translateY(-1px); +} + +.enterprise-detail-page .back-action:disabled, +.enterprise-detail-page .minor-action:disabled, +.enterprise-detail-page .major-action:disabled { + opacity: 0.52; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + .enterprise-detail-card .card-head { align-items: flex-start; } diff --git a/web/src/assets/styles/global.css b/web/src/assets/styles/global.css index 4bde223..7fca2f9 100644 --- a/web/src/assets/styles/global.css +++ b/web/src/assets/styles/global.css @@ -147,40 +147,25 @@ h1 { margin-top: 4px; color: var(--ink); font-size: 24px; line-height: 1.25; fon *, *::before, *::after { animation-duration: 1ms !important; transition-duration: 1ms !important; scroll-behavior: auto !important; } } -.table-loading__spinner { - width: 38px; - height: 38px; - display: inline-grid; - place-items: center; - border: 3px solid #e2e8f0; - border-top-color: var(--primary); - border-radius: 50%; - animation: table-spinner-rotate .8s linear infinite !important; +.table-state:has(.table-loading.screen-floating), +.table-state:has(.table-loading-anchor), +.table-loading-row:has(.table-loading.screen-floating), +.table-loading-row:has(.table-loading-anchor) { + min-height: 0 !important; + padding: 0 !important; + background: transparent !important; } -.table-loading.sky .table-loading__spinner { - border-top-color: var(--primary); -} - -.table-loading.detail .table-loading__spinner { - width: 34px; - height: 34px; -} - -.table-loading.banner .table-loading__spinner { - width: 18px; - height: 18px; - border-width: 2px; -} - -.table-loading__spinner i { - display: none; -} - -@keyframes table-spinner-rotate { - to { - transform: rotate(360deg); - } +tr:has(> .table-loading-row .table-loading.screen-floating), +tr:has(> .table-loading-row .table-loading-anchor), +tr:has(> .table-loading-row .table-loading.screen-floating) > td, +tr:has(> .table-loading-row .table-loading-anchor) > td, +.table-loading-row:has(.table-loading.screen-floating), +.table-loading-row:has(.table-loading-anchor) { + height: 0 !important; + border: 0 !important; + line-height: 0 !important; + background: transparent !important; } /* Global Scrollbar Styles */ diff --git a/web/src/assets/styles/views/digital-employees-view.css b/web/src/assets/styles/views/digital-employees-view.css index 67dcde7..c7f4884 100644 --- a/web/src/assets/styles/views/digital-employees-view.css +++ b/web/src/assets/styles/views/digital-employees-view.css @@ -5,49 +5,81 @@ .digital-employees-list { height: 100%; + min-height: 0; + display: flex; + flex-direction: column; + padding: 16px 18px; + overflow: hidden; } .digital-employees-list > .status-tabs { flex: 0 0 auto; + display: flex; + gap: 28px; + margin-top: 14px; + padding-bottom: 0; + border-bottom: 1px solid #dbe4ee; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; } -.digital-employees-list .table-wrap { - min-height: 0; +.digital-employees-list > .status-tabs::-webkit-scrollbar { + display: none; +} + +.digital-employees-list > .status-tabs button { + position: relative; + min-height: 36px; + display: inline-flex; + align-items: center; + gap: 7px; + border: 0; + background: transparent; + color: #64748b; + font-size: 14px; + font-weight: 750; + white-space: nowrap; +} + +.digital-employees-list > .status-tabs button.active { + color: var(--theme-primary-active); +} + +.digital-employees-list > .status-tabs button.active::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: -1px; + height: 3px; + border-radius: 999px 999px 0 0; + background: var(--theme-primary); } .digital-employees-table { min-width: 1060px; - table-layout: fixed; } -.digital-employees-table .col-skill { width: 27%; } -.digital-employees-table .col-schedule { width: 16%; } -.digital-employees-table .col-mode { width: 12%; } +/* Default first column left alignment */ +.digital-employees-table th:first-child, +.digital-employees-table td:first-child { + text-align: left; +} + +.digital-employees-table .col-skill { width: 22%; } .digital-employees-table .col-skill-type { width: 11%; } -.digital-employees-table .col-status { width: 11%; } -.digital-employees-table .col-enabled { width: 11%; } -.digital-employees-table .col-updated { width: 12%; } - -.digital-employees-table td { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.digital-employees-table tbody tr { - cursor: pointer; -} +.digital-employees-table .col-owner { width: 11%; } +.digital-employees-table .col-schedule { width: 16%; } +.digital-employees-table .col-mode { width: 10%; } +.digital-employees-table .col-status { width: 10%; } +.digital-employees-table .col-enabled { width: 10%; } +.digital-employees-table .col-updated { width: 10%; } .digital-refresh-action i { font-size: 16px; } -.skill-type-pill { - border-color: #dbeafe; - background: #eff6ff; - color: #1d4ed8; -} - .digital-employee-detail { height: 100%; } diff --git a/web/src/assets/styles/views/logs-view.css b/web/src/assets/styles/views/logs-view.css index b6ec537..f7287ac 100644 --- a/web/src/assets/styles/views/logs-view.css +++ b/web/src/assets/styles/views/logs-view.css @@ -54,6 +54,26 @@ color: var(--theme-primary); } +.system-logs-list .refresh-interval-filter, +.system-logs-list .refresh-interval-trigger, +.system-logs-list .refresh-interval-menu { + min-width: 148px; +} + +.system-logs-list .refresh-interval-trigger > .mdi:first-child { + color: var(--theme-primary); +} + +.system-logs-list .icon-refresh-action { + width: 40px; + min-width: 40px; + padding: 0; +} + +.system-logs-list .icon-refresh-action .mdi { + font-size: 18px; +} + .system-logs-list .document-filter-menu { position: absolute; top: calc(100% + 8px); diff --git a/web/src/assets/styles/views/policies-view.css b/web/src/assets/styles/views/policies-view.css index 7677fc2..42fd40c 100644 --- a/web/src/assets/styles/views/policies-view.css +++ b/web/src/assets/styles/views/policies-view.css @@ -423,14 +423,6 @@ th { text-align: center; } -.table-loading-row { - padding: 0; -} - -.table-loading-row > .table-loading { - min-height: 220px; -} - .list-foot { display: grid; grid-template-columns: 1fr auto 1fr; diff --git a/web/src/assets/styles/views/settings-view.css b/web/src/assets/styles/views/settings-view.css index 218a1d8..eefa4b1 100644 --- a/web/src/assets/styles/views/settings-view.css +++ b/web/src/assets/styles/views/settings-view.css @@ -206,6 +206,16 @@ padding: 20px 24px; } +.settings-content-fill { + overflow: hidden; + align-content: stretch; +} + +.settings-content-fill .settings-logs-view, +.settings-content-fill .settings-log-detail-view { + min-height: 0; +} + .model-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -680,6 +690,19 @@ margin-bottom: 20px; } +.log-policy-card .card-head { + padding-top: 12px; + padding-bottom: 12px; +} + +.log-policy-card > *:not(.card-head) { + margin-top: 18px; +} + +.log-policy-card > *:not(.card-head):last-child { + margin-bottom: 20px; +} + /* 大语言模型配置卡片图标与标题排版 */ .card-title-with-icon { display: flex; diff --git a/web/src/assets/styles/views/setup-view.css b/web/src/assets/styles/views/setup-view.css index 717eb66..b9acd57 100644 --- a/web/src/assets/styles/views/setup-view.css +++ b/web/src/assets/styles/views/setup-view.css @@ -591,9 +591,9 @@ background: rgba(6, 78, 59, 0.34); } -.setup-startup-spinner .pi { - font-size: 22px; -} +.setup-startup-spinner .mdi { + font-size: 22px; +} .setup-startup-spinner strong { color: #ffffff; @@ -626,10 +626,10 @@ background: rgba(15, 23, 42, 0.24); } -.setup-startup-step .pi { - margin-top: 2px; +.setup-startup-step .mdi { + margin-top: 2px; color: color-mix(in srgb, var(--theme-primary-soft) 46%, transparent); -} +} .setup-startup-step strong { color: #f8fffb; @@ -648,25 +648,25 @@ border-color: rgba(59, 130, 246, 0.34); } -.setup-startup-step.is-running .pi { - color: #93c5fd; -} +.setup-startup-step.is-running .mdi { + color: #93c5fd; +} .setup-startup-step.is-success { border-color: rgba(var(--theme-primary-rgb), 0.32); } -.setup-startup-step.is-success .pi { +.setup-startup-step.is-success .mdi { color: var(--theme-primary-light-5); -} +} .setup-startup-step.is-error { border-color: rgba(248, 113, 113, 0.36); } -.setup-startup-step.is-error .pi { - color: #f87171; -} +.setup-startup-step.is-error .mdi { + color: #f87171; +} .setup-startup-console { min-height: 0; diff --git a/web/src/components/audit/DigitalEmployeeListPanel.vue b/web/src/components/audit/DigitalEmployeeListPanel.vue index 80cf598..c6b4e4e 100644 --- a/web/src/components/audit/DigitalEmployeeListPanel.vue +++ b/web/src/components/audit/DigitalEmployeeListPanel.vue @@ -6,13 +6,21 @@ :error="errorMessage" :empty="!visibleEmployees.length" :empty-state="emptyState" + :show-pagination="!loading && !errorMessage && visibleEmployees.length > 0" + :current-page="currentPage" + :page-size="pageSize" + :pages="pageNumbers" + :show-page-size="false" + :summary="paginationSummary" + :total="visibleEmployees.length" + :total-pages="totalPages" loading-title="数字员工资产同步中" loading-message="正在加载数字员工资产" loading-icon="mdi mdi-view-list-outline" - hint="归集后台自动执行的数字员工技能,可查看技能内容、执行计划、启动状态和最近版本。" + @update:current-page="currentPage = $event" > - - @@ -228,6 +206,9 @@ const pageNumbers = computed(() => { const start = Math.max(1, Math.min(currentPage.value - 3, total - 6)) return Array.from({ length: 7 }, (_, index) => start + index) }) +const paginationSummary = computed(() => + `共 ${props.visibleEmployees.length} 条,目前第 ${currentPage.value} / ${totalPages.value} 页` +) const emptyState = { eyebrow: '数字员工', title: '暂无匹配的数字员工', @@ -261,7 +242,7 @@ function selectFilter(type, value) { } - + diff --git a/web/src/components/audit/DigitalEmployeeWorkRecords.vue b/web/src/components/audit/DigitalEmployeeWorkRecords.vue index eaf5057..9783714 100644 --- a/web/src/components/audit/DigitalEmployeeWorkRecords.vue +++ b/web/src/components/audit/DigitalEmployeeWorkRecords.vue @@ -2,104 +2,114 @@
-
-
-
- - - + + + + + + + + + +
@@ -298,11 +283,15 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' import AuditPickerFilter from './AuditPickerFilter.vue' -import TableEmptyState from '../shared/TableEmptyState.vue' +import EnterpriseListPage from '../shared/EnterpriseListPage.vue' import TableLoadingState from '../shared/TableLoadingState.vue' import { fetchAgentRunDetail, fetchAgentRuns } from '../../services/agentAssets.js' import { useToast } from '../../composables/useToast.js' -import { AGENT_RUN_POLL_INTERVAL_MS } from '../../utils/agentRunMonitor.js' +import { + DEFAULT_REFRESH_INTERVAL_MS, + REFRESH_INTERVAL_OPTIONS, + formatRefreshInterval +} from '../../utils/refreshIntervalOptions.js' import { formatWorkRecordDateTime, formatWorkRecordSummary, @@ -330,6 +319,7 @@ const detailLoading = ref(false) const detailError = ref('') const selectedRunId = ref('') const selectedRunDetail = ref(null) +const refreshInterval = ref(DEFAULT_REFRESH_INTERVAL_MS) watch(detailOpen, (newVal) => { emit('detail-open-change', newVal) @@ -392,6 +382,14 @@ const listKeyword = ref('') const activeModule = ref('全部') const activeStatus = ref('全部') const activeFilterPopover = ref('') +const workRecordsEmptyState = { + eyebrow: '工作记录', + title: '暂无匹配的工作记录', + description: '当前没有符合搜索条件的数字员工工作记录。', + icon: 'mdi mdi-clipboard-text-clock-outline', + tone: 'theme', + artLabel: 'RECORDS' +} const modulePickerOptions = computed(() => { const set = new Set(runs.value.map((run) => resolveWorkRecordModuleLabel(run))) @@ -408,6 +406,11 @@ const statusPickerOptions = computed(() => { ...Array.from(set).map(s => ({ label: s, value: s })) ] }) +const refreshIntervalPickerOptions = REFRESH_INTERVAL_OPTIONS.map((option) => ({ + label: `每 ${option.label}`, + value: option.value +})) +const refreshIntervalLabel = computed(() => formatRefreshInterval(refreshInterval.value)) const activeFilterTokens = computed(() => { const tokens = [] @@ -449,6 +452,9 @@ const pageNumbers = computed(() => { const start = Math.max(1, Math.min(currentPage.value - 3, total - 6)) return Array.from({ length: 7 }, (_, index) => start + index) }) +const paginationSummary = computed(() => + `共 ${filteredRuns.value.length} 条,目前第 ${currentPage.value} / ${totalPages.value} 页` +) watch( () => [listKeyword.value, activeModule.value, activeStatus.value], @@ -486,6 +492,12 @@ function selectStatus(val) { closeFilterPopover() } +function changeRefreshInterval(value) { + refreshInterval.value = Number(value) || DEFAULT_REFRESH_INTERVAL_MS + closeFilterPopover() + startPolling() +} + function resetFilters() { listKeyword.value = '' activeModule.value = '全部' @@ -564,7 +576,7 @@ function startPolling() { stopPolling() pollTimer = window.setInterval(() => { loadWorkRecords(false) - }, AGENT_RUN_POLL_INTERVAL_MS) + }, refreshInterval.value) } function stopPolling() { @@ -586,86 +598,104 @@ onBeforeUnmount(() => { - - diff --git a/web/src/components/layout/SidebarRail.vue b/web/src/components/layout/SidebarRail.vue index 2623f24..158f0f5 100644 --- a/web/src/components/layout/SidebarRail.vue +++ b/web/src/components/layout/SidebarRail.vue @@ -150,6 +150,7 @@ const { startDocumentInboxPolling, stopDocumentInboxPolling } = useDocumentCenterInbox() +let inboxInitialRefreshTimer = null const sidebarMeta = { overview: { label: '分析看板' }, @@ -159,7 +160,6 @@ const sidebarMeta = { policies: { label: '知识管理' }, audit: { label: '规则中心' }, digitalEmployees: { label: '数字员工' }, - logs: { label: '系统日志' }, employees: { label: '员工管理' }, settings: { label: '系统设置' } } @@ -173,8 +173,27 @@ const decoratedNavItems = computed(() => })) ) +function clearInboxInitialRefreshTimer() { + if (inboxInitialRefreshTimer && typeof window !== 'undefined') { + window.clearTimeout(inboxInitialRefreshTimer) + inboxInitialRefreshTimer = null + } +} + +function scheduleInboxInitialRefresh() { + if (typeof window === 'undefined') { + return + } + + clearInboxInitialRefreshTimer() + inboxInitialRefreshTimer = window.setTimeout(() => { + inboxInitialRefreshTimer = null + void refreshDocumentInbox() + }, props.activeView === 'documents' ? 1200 : 6000) +} + onMounted(() => { - void refreshDocumentInbox() + scheduleInboxInitialRefresh() startDocumentInboxPolling() }) @@ -271,7 +290,18 @@ watch( } ) +watch( + () => props.activeView, + (activeView, previousView) => { + if (activeView === 'documents' && previousView !== 'documents') { + clearInboxInitialRefreshTimer() + void refreshDocumentInbox({ force: true }) + } + } +) + onBeforeUnmount(() => { + clearInboxInitialRefreshTimer() stopDocumentInboxPolling() closeCollapsedUserMenuNow() }) diff --git a/web/src/components/layout/TopBar.vue b/web/src/components/layout/TopBar.vue index d886e75..395f847 100644 --- a/web/src/components/layout/TopBar.vue +++ b/web/src/components/layout/TopBar.vue @@ -149,16 +149,6 @@
- - - - + return 'mdi mdi-circle-outline' +} + + + diff --git a/web/src/views/scripts/AuditView.js b/web/src/views/scripts/AuditView.js index 724827f..dfb387e 100644 --- a/web/src/views/scripts/AuditView.js +++ b/web/src/views/scripts/AuditView.js @@ -177,12 +177,6 @@ export default { () => normalizeText(selectedSkill.value?.ruleDocument?.file_name) || '未上传规则表' ) - const selectedSpreadsheetModeLabel = computed(() => { - if (selectedSkill.value?.isPreviewMock) { - return canEditSpreadsheetInline.value ? '可编辑' : '只读' - } - return canEditSpreadsheetInline.value ? '在线可编辑' : '只读' - }) const { versionSwitchTarget, versionTimelineOpen, @@ -438,11 +432,7 @@ export default { const auditDetailTopBar = computed(() => buildAuditDetailTopBar({ skill: selectedSkill.value, - usesJsonRiskRule: selectedSkillUsesJsonRisk.value, - usesSpreadsheetRule: selectedSkillUsesSpreadsheet.value, - spreadsheetModeLabel: selectedSpreadsheetModeLabel.value, - spreadsheetFileName: selectedSpreadsheetFileName.value, - canEditSpreadsheetInline: canEditSpreadsheetInline.value + usesJsonRiskRule: selectedSkillUsesJsonRisk.value }) ) @@ -711,7 +701,6 @@ export default { selectedSkillUsesSpreadsheet, selectedSkillUsesJsonRisk, selectedSpreadsheetFileName, - selectedSpreadsheetModeLabel, selectedVersionTimelineItems, selectedSpreadsheetChangeRecords, detailBusy, diff --git a/web/src/views/scripts/BudgetCenterView.js b/web/src/views/scripts/BudgetCenterView.js index 2e0b2ae..175778d 100644 --- a/web/src/views/scripts/BudgetCenterView.js +++ b/web/src/views/scripts/BudgetCenterView.js @@ -3,6 +3,7 @@ import { ElButton, ElInput, ElPagination, ElTable, ElTableColumn } from 'element import BudgetTrendChart from '../../components/charts/BudgetTrendChart.vue' import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue' +import TableLoadingState from '../../components/shared/TableLoadingState.vue' import { fetchBudgetSummary } from '../../services/budgets.js' import { fetchEmployeeMeta } from '../../services/employees.js' import { @@ -217,6 +218,7 @@ export default { components: { BudgetTrendChart, EnterpriseSelect, + TableLoadingState, ElButton, ElInput, ElPagination, @@ -238,7 +240,7 @@ export default { const budgetTableKeyword = ref('') const budgetRows = ref([]) const budgetSummary = ref(null) - const budgetLoading = ref(false) + const budgetLoading = ref(true) const budgetError = ref('') const canEditBudget = computed(() => canEditBudgetCenter(props.currentUser)) const canSwitchDepartments = computed(() => canSwitchBudgetDepartments(props.currentUser)) @@ -424,6 +426,7 @@ export default { } async function loadDepartments() { + budgetLoading.value = true try { const payload = await fetchEmployeeMeta() const options = Array.isArray(payload?.organizationOptions) ? payload.organizationOptions : [] diff --git a/web/src/views/scripts/LogsView.js b/web/src/views/scripts/LogsView.js index d530d9e..b89df98 100644 --- a/web/src/views/scripts/LogsView.js +++ b/web/src/views/scripts/LogsView.js @@ -5,8 +5,12 @@ import EnterpriseListPage from '../../components/shared/EnterpriseListPage.vue' import { useSystemState } from '../../composables/useSystemState.js' import { useToast } from '../../composables/useToast.js' import { fetchSystemLogEntries } from '../../services/systemLogs.js' -import { AGENT_RUN_POLL_INTERVAL_MS } from '../../utils/agentRunMonitor.js' import { isManagerUser } from '../../utils/accessControl.js' +import { + DEFAULT_REFRESH_INTERVAL_MS, + REFRESH_INTERVAL_OPTIONS, + formatRefreshInterval +} from '../../utils/refreshIntervalOptions.js' function formatDateTime(value) { if (!value) { @@ -79,6 +83,8 @@ export default { const pageSize = ref(10) const pageSizes = [10, 20, 50] const pageSizeOptions = pageSizes.map((size) => ({ label: `${size} 条/页`, value: size })) + const refreshInterval = ref(DEFAULT_REFRESH_INTERVAL_MS) + const refreshIntervalOptions = REFRESH_INTERVAL_OPTIONS let pollTimer = 0 const isAdmin = computed(() => isManagerUser(currentUser.value)) @@ -102,6 +108,7 @@ export default { const systemEventTypeFilterLabel = computed(() => systemEventTypeFilterOptions.value.find((item) => item.value === systemEventTypeFilter.value)?.label || '全部类型' ) + const refreshIntervalLabel = computed(() => formatRefreshInterval(refreshInterval.value)) const hasActiveFilters = computed(() => Boolean(systemSearchKeyword.value.trim() || systemLevelFilter.value || systemEventTypeFilter.value) ) @@ -175,6 +182,12 @@ export default { openFilterKey.value = '' } + function changeRefreshInterval(value) { + refreshInterval.value = Number(value) || DEFAULT_REFRESH_INTERVAL_MS + openFilterKey.value = '' + startPolling() + } + function resetFilters() { systemSearchKeyword.value = '' systemLevelFilter.value = '' @@ -211,7 +224,7 @@ export default { stopPolling() pollTimer = window.setInterval(() => { loadSystemLogs(false) - }, AGENT_RUN_POLL_INTERVAL_MS) + }, refreshInterval.value) } function stopPolling() { @@ -253,6 +266,7 @@ export default { return { changePageSize, + changeRefreshInterval, currentPage, filteredSystemLogEntries, formatDateTime, @@ -263,6 +277,9 @@ export default { openFilterKey, pageSize, pageSizeOptions, + refreshInterval, + refreshIntervalLabel, + refreshIntervalOptions, resetFilters, resolveSystemLevelTone, resolveSystemOutcomeTone, diff --git a/web/src/views/scripts/SettingsView.js b/web/src/views/scripts/SettingsView.js index 81a0521..0d638b5 100644 --- a/web/src/views/scripts/SettingsView.js +++ b/web/src/views/scripts/SettingsView.js @@ -1,5 +1,7 @@ import HermesEmployeeSettingsPanel from '../HermesEmployeeSettingsPanel.vue' import LlmSettingsPanel from '../LlmSettingsPanel.vue' +import LogDetailView from '../LogDetailView.vue' +import LogsView from '../LogsView.vue' import MailSettingsPanel from '../MailSettingsPanel.vue' import EnterpriseSelect from '../../components/shared/EnterpriseSelect.vue' import { useSettings } from '../../composables/useSettings.js' @@ -10,6 +12,8 @@ export default { HermesEmployeeSettingsPanel, EnterpriseSelect, LlmSettingsPanel, + LogDetailView, + LogsView, MailSettingsPanel }, setup() { diff --git a/web/src/views/scripts/auditViewDetailTopBar.js b/web/src/views/scripts/auditViewDetailTopBar.js index b264d07..9b1c703 100644 --- a/web/src/views/scripts/auditViewDetailTopBar.js +++ b/web/src/views/scripts/auditViewDetailTopBar.js @@ -10,11 +10,7 @@ function resolveRiskScoreCardColor(level) { export function buildAuditDetailTopBar({ skill, - usesJsonRiskRule = false, - usesSpreadsheetRule = false, - spreadsheetModeLabel = '', - spreadsheetFileName = '', - canEditSpreadsheetInline = false + usesJsonRiskRule = false } = {}) { if (!skill) return null @@ -39,15 +35,6 @@ export function buildAuditDetailTopBar({ : 'up', color: resolveRiskScoreCardColor(scoreLevel) }) - } else if (usesSpreadsheetRule) { - kpis.push({ - label: '编辑模式', - value: spreadsheetModeLabel, - unit: '', - meta: spreadsheetFileName, - trend: canEditSpreadsheetInline ? 'up' : 'down', - color: canEditSpreadsheetInline ? 'var(--success)' : '#64748b' - }) } return { diff --git a/web/src/views/scripts/auditViewModel.js b/web/src/views/scripts/auditViewModel.js index 0387a03..3eed016 100644 --- a/web/src/views/scripts/auditViewModel.js +++ b/web/src/views/scripts/auditViewModel.js @@ -7,7 +7,10 @@ import { VERSION_STATE_META } from './auditViewMetadata.js' import { + formatRiskRuleAge, + resolveRiskRuleFlow, resolveRiskRuleScore, + resolveRiskRuleScoreDetail, resolveRiskRuleScoreLabel, resolveRiskRuleScoreLevel, resolveRiskRuleSeverity, @@ -71,6 +74,7 @@ import { applyRiskRuleJsonState, resolveRiskRuleBusinessStage, resolveRiskRuleEnabled, + resolveLastOperationLabel, resolveRiskRuleOnlineMeta } from './auditViewRiskRuleState.js' import { diff --git a/web/src/views/scripts/auditViewRiskRuleState.js b/web/src/views/scripts/auditViewRiskRuleState.js index 3e69fe1..056f7db 100644 --- a/web/src/views/scripts/auditViewRiskRuleState.js +++ b/web/src/views/scripts/auditViewRiskRuleState.js @@ -20,6 +20,7 @@ import { } from './auditViewDataUtils.js' import { formatDateTime } from './auditViewFormatters.js' import { + buildRiskListSubtitle, resolveRiskRuleCategory, resolveRiskRuleDescription, resolveRiskRuleSourceRef @@ -83,7 +84,7 @@ export function resolveRiskRuleOnlineMeta(statusValue) { return { label: '待上线', tone: 'draft', online: false } } -function resolveLastOperationLabel(source, fallback = {}) { +export function resolveLastOperationLabel(source, fallback = {}) { const configJson = readConfigJson(source) const operation = isPlainObject(configJson.last_operation) ? configJson.last_operation : {} const action = normalizeText(operation.action) || normalizeText(fallback.action) || 'create' @@ -129,9 +130,12 @@ export function applyRiskRuleJsonState(target, payload, apiPayload) { let publishedAt = target.publishedAt || '-' if (apiPayload?.recent_versions) { - const history = buildHistory(apiPayload.recent_versions, { ...target, config_json: payload }) - const publishedVersionObj = history.find((item) => item.isPublished || item.lifecycleState === 'published') - publishedAt = publishedVersionObj ? publishedVersionObj.time : (apiPayload?.latest_review?.reviewed_at ? formatDateTime(apiPayload.latest_review.reviewed_at) : '-') + const publishedVersionObj = apiPayload.recent_versions.find((item) => + item?.is_current || item?.version === apiPayload?.published_version + ) + publishedAt = publishedVersionObj?.created_at + ? formatDateTime(publishedVersionObj.created_at) + : (apiPayload?.latest_review?.reviewed_at ? formatDateTime(apiPayload.latest_review.reviewed_at) : '-') } else if (apiPayload?.latest_review?.reviewed_at) { publishedAt = formatDateTime(apiPayload.latest_review.reviewed_at) } diff --git a/web/tests/accessControl.test.mjs b/web/tests/accessControl.test.mjs index 9479727..f17d843 100644 --- a/web/tests/accessControl.test.mjs +++ b/web/tests/accessControl.test.mjs @@ -65,6 +65,7 @@ test('legacy reimbursement approval and archive centers are no longer accessible assert.equal(canAccessAppView(adminUser, 'requests'), false) assert.equal(canAccessAppView(adminUser, 'approval'), false) assert.equal(canAccessAppView(adminUser, 'archive'), false) + assert.equal(canAccessAppView(adminUser, 'logs'), false) assert.equal(canAccessAppView(adminUser, 'documents'), true) }) diff --git a/web/tests/minimum-visible-state.test.mjs b/web/tests/minimum-visible-state.test.mjs new file mode 100644 index 0000000..bb2ce73 --- /dev/null +++ b/web/tests/minimum-visible-state.test.mjs @@ -0,0 +1,56 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { effectScope, nextTick, ref } from 'vue' + +import { useMinimumVisibleState } from '../src/composables/useMinimumVisibleState.js' + +function wait(ms) { + return new Promise((resolve) => globalThis.setTimeout(resolve, ms)) +} + +test('minimum visible state stays visible after a fast loading toggle', async () => { + const scope = effectScope() + const state = scope.run(() => { + const loading = ref(false) + const visible = useMinimumVisibleState(loading, { minVisibleMs: 35 }) + return { loading, visible } + }) + + state.loading.value = true + await nextTick() + assert.equal(state.visible.value, true) + + state.loading.value = false + await nextTick() + assert.equal(state.visible.value, true) + + await wait(50) + assert.equal(state.visible.value, false) + scope.stop() +}) + +test('minimum visible state cancels a pending hide when loading restarts', async () => { + const scope = effectScope() + const state = scope.run(() => { + const loading = ref(false) + const visible = useMinimumVisibleState(loading, { minVisibleMs: 40 }) + return { loading, visible } + }) + + state.loading.value = true + await nextTick() + state.loading.value = false + await nextTick() + await wait(10) + + state.loading.value = true + await nextTick() + await wait(40) + assert.equal(state.visible.value, true) + + state.loading.value = false + await nextTick() + await wait(50) + assert.equal(state.visible.value, false) + scope.stop() +}) diff --git a/web/tests/navigation-route-resolution.test.mjs b/web/tests/navigation-route-resolution.test.mjs index c9af1f8..21dae01 100644 --- a/web/tests/navigation-route-resolution.test.mjs +++ b/web/tests/navigation-route-resolution.test.mjs @@ -8,9 +8,9 @@ import { } from '../src/composables/useNavigation.js' function testDerivesViewFromRouteName() { - assert.equal(resolveAppViewFromRoute({ name: 'app-log-detail', meta: {} }), 'logs') + assert.equal(resolveAppViewFromRoute({ name: 'app-log-detail', meta: {} }), 'settings') assert.equal(resolveAppViewFromRoute({ name: 'app-request-detail', meta: {} }), 'documents') - assert.equal(resolveAppViewFromRoute({ name: 'app-policies', meta: { appView: 'logs' } }), 'policies') + assert.equal(resolveAppViewFromRoute({ name: 'app-policies', meta: { appView: 'settings' } }), 'policies') } function testFallsBackToValidMeta() { @@ -19,7 +19,7 @@ function testFallsBackToValidMeta() { } function testResolvesMainRouteNames() { - assert.equal(resolveTargetRouteName('logs'), 'app-logs') + assert.equal(resolveTargetRouteName('logs'), 'app-settings') assert.equal(resolveTargetRouteName('policies'), 'app-policies') assert.equal(resolveTargetRouteName('requests'), 'app-overview') assert.equal(resolveTargetRouteName('approval'), 'app-overview') @@ -31,7 +31,8 @@ function testLegacyCentersAreRemovedFromNavigation() { assert.equal(appViews.includes('requests'), false) assert.equal(appViews.includes('approval'), false) assert.equal(appViews.includes('archive'), false) - assert.equal(navItems.some((item) => ['requests', 'approval', 'archive'].includes(item.id)), false) + assert.equal(appViews.includes('logs'), false) + assert.equal(navItems.some((item) => ['requests', 'approval', 'archive', 'logs'].includes(item.id)), false) } function run() { diff --git a/web/tests/refresh-interval-controls.test.mjs b/web/tests/refresh-interval-controls.test.mjs new file mode 100644 index 0000000..0946047 --- /dev/null +++ b/web/tests/refresh-interval-controls.test.mjs @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import test from 'node:test' + +const refreshOptions = readFileSync(new URL('../src/utils/refreshIntervalOptions.js', import.meta.url), 'utf8') +const logsView = readFileSync(new URL('../src/views/LogsView.vue', import.meta.url), 'utf8') +const logsScript = readFileSync(new URL('../src/views/scripts/LogsView.js', import.meta.url), 'utf8') +const workRecords = readFileSync( + new URL('../src/components/audit/DigitalEmployeeWorkRecords.vue', import.meta.url), + 'utf8' +) + +test('shared refresh interval options default to 60 seconds', () => { + assert.match(refreshOptions, /DEFAULT_REFRESH_INTERVAL_MS\s*=\s*60000/) + for (const value of [1000, 3000, 5000, 10000, 30000, 60000, 180000]) { + assert.match(refreshOptions, new RegExp(`value:\\s*${value}`)) + } +}) + +test('system logs list exposes refresh interval control', () => { + assert.match(logsScript, /refreshInterval\s*=\s*ref\(DEFAULT_REFRESH_INTERVAL_MS\)/) + assert.match(logsScript, /window\.setInterval\([\s\S]*refreshInterval\.value/) + assert.match(logsView, /刷新时间 \{\{ refreshIntervalLabel \}\}/) + assert.match(logsView, /v-for="option in refreshIntervalOptions"/) + assert.doesNotMatch(logsView, /刷新日志/) +}) + +test('digital employee work records expose refresh interval control', () => { + assert.match(workRecords, /refreshInterval\s*=\s*ref\(DEFAULT_REFRESH_INTERVAL_MS\)/) + assert.match(workRecords, /refreshIntervalPickerOptions\s*=\s*REFRESH_INTERVAL_OPTIONS/) + assert.match(workRecords, /window\.setInterval\([\s\S]*refreshInterval\.value/) + assert.match(workRecords, /刷新时间 \$\{refreshIntervalLabel\}/) + assert.doesNotMatch(workRecords, /AGENT_RUN_POLL_INTERVAL_MS/) +}) diff --git a/web/tests/settings-system-logs-section.test.mjs b/web/tests/settings-system-logs-section.test.mjs new file mode 100644 index 0000000..c699c5a --- /dev/null +++ b/web/tests/settings-system-logs-section.test.mjs @@ -0,0 +1,22 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { readFileSync } from 'node:fs' + +const settingsModel = readFileSync(new URL('../src/utils/settingsModelHelper.js', import.meta.url), 'utf8') +const settingsView = readFileSync(new URL('../src/views/SettingsView.vue', import.meta.url), 'utf8') +const settingsScript = readFileSync(new URL('../src/views/scripts/SettingsView.js', import.meta.url), 'utf8') +const router = readFileSync(new URL('../src/router/index.js', import.meta.url), 'utf8') +const logDetailView = readFileSync(new URL('../src/views/LogDetailView.vue', import.meta.url), 'utf8') + +test('system logs are nested under system settings instead of sidebar navigation', () => { + assert.match(settingsModel, /id:\s*'systemLogs'[\s\S]*label:\s*'系统日志'/) + assert.match(settingsView, /activeSection === 'systemLogs'/) + assert.match(settingsView, //) + assert.match(settingsScript, /import LogsView from '\.\.\/LogsView\.vue'/) +}) + +test('log detail keeps the settings context and legacy logs URLs redirect', () => { + assert.match(router, /path:\s*'\/app\/settings\/logs\/:logKind\/:logId'[\s\S]*appView:\s*'settings'/) + assert.match(router, /path:\s*'\/app\/logs'[\s\S]*section:\s*'systemLogs'/) + assert.match(logDetailView, /router\.push\(\{ name:\s*'app-settings', query:\s*\{ section:\s*'systemLogs' \} \}\)/) +}) diff --git a/web/vite.config.js b/web/vite.config.js index 1a8132f..03ffaa9 100644 --- a/web/vite.config.js +++ b/web/vite.config.js @@ -1053,17 +1053,31 @@ export default defineConfig({ if (!id.includes('node_modules')) { return undefined } - if (id.includes('element-plus') || id.includes('@element-plus')) { + const normalizedId = id.replace(/\\/g, '/') + if ( + normalizedId.includes('/node_modules/vue/') || + normalizedId.includes('/node_modules/@vue/') || + normalizedId.includes('/node_modules/vue-router/') + ) { + return 'vendor-vue' + } + if (normalizedId.includes('element-plus') || normalizedId.includes('@element-plus')) { return 'vendor-element-plus' } - if (id.includes('echarts') || id.includes('zrender')) { + if (normalizedId.includes('echarts') || normalizedId.includes('zrender')) { return 'vendor-echarts' } - if (id.includes('@vueuse')) { - return 'vendor-vueuse' + if (normalizedId.includes('@antv/g6')) { + return 'vendor-g6' } - if (id.includes('primeicons') || id.includes('primevue')) { - return 'vendor-prime' + if (normalizedId.includes('chart.js') || normalizedId.includes('vue-chartjs')) { + return 'vendor-chartjs' + } + if (normalizedId.includes('markdown-it')) { + return 'vendor-markdown' + } + if (normalizedId.includes('@vueuse')) { + return 'vendor-vueuse' } return 'vendor' }