更新UI相关部分

This commit is contained in:
焦龙言
2026-06-23 09:13:13 +08:00
parent faa09101a1
commit f213848719
29 changed files with 1990 additions and 397 deletions
@@ -0,0 +1,51 @@
declare const process: { cwd(): string };
declare function require(name: string): {
readFileSync?: (path: string, encoding: string) => string;
join?: (...parts: string[]) => string;
};
const { readFileSync } = require("node:fs");
const { join } = require("node:path");
if (!readFileSync || !join) {
throw new Error("测试运行环境缺少文件读取能力");
}
const css = readFileSync(join(process.cwd(), "src/assets/styles.css"), "utf-8");
const rootBlock = cssBlock(":root");
assertIncludes(rootBlock, "--card-border: 1px solid var(--line);");
assertIncludes(rootBlock, "--card-shadow: 0 6px 16px rgba(var(--primary-rgb), 0.035);");
assertIncludes(rootBlock, "--card-inner-border: 1px solid var(--line-soft);");
assertIncludes(rootBlock, "--card-inner-bg: var(--surface);");
assertIncludes(rootBlock, "--card-inner-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.56);");
const mainCardBlock = cssBlock(".panel,\\s*\\n\\.action-card,\\s*\\n\\.salary-profile-summary-strip,\\s*\\n\\.organization-summary-strip");
assertIncludes(mainCardBlock, "border: var(--card-border);");
assertIncludes(mainCardBlock, "background: var(--card);");
assertIncludes(mainCardBlock, "box-shadow: var(--card-shadow);");
const summaryStripBlock = cssBlock(".summary-strip");
assertIncludes(summaryStripBlock, "border: var(--card-border);");
assertIncludes(summaryStripBlock, "background: var(--card);");
assertIncludes(summaryStripBlock, "box-shadow: var(--card-shadow);");
const innerCardBlock = cssBlock(".summary-metric,\\s*\\n\\.compact-stat,\\s*\\n\\.salary-profile-summary-item,\\s*\\n\\.organization-summary-strip div,\\s*\\n\\.organization-node-summary div,\\s*\\n\\.employee-inline-metric,\\s*\\n\\.workflow-steps span,\\s*\\n\\.recent-item,\\s*\\n\\.salary-profile-modal-section,\\s*\\n\\.report-total-item,\\s*\\n\\.report-inline-metrics span,\\s*\\n\\.report-attendance-card");
assertIncludes(innerCardBlock, "border: var(--card-inner-border);");
assertIncludes(innerCardBlock, "background: var(--card-inner-bg);");
assertIncludes(innerCardBlock, "box-shadow: var(--card-inner-shadow);");
function cssBlock(selector: string): string {
const pattern = new RegExp(`${selector}\\s*\\{([\\s\\S]*?)\\n\\}`, "m");
const match = css.match(pattern);
if (!match) {
throw new Error(`缺少样式块 ${selector}`);
}
return match[1];
}
function assertIncludes(content: string, expected: string): void {
if (!content.includes(expected)) {
throw new Error(`期望包含 ${expected}`);
}
}
@@ -0,0 +1,69 @@
declare const process: { cwd(): string };
declare function require(name: string): {
readFileSync?: (path: string, encoding: string) => string;
join?: (...parts: string[]) => string;
};
const { readFileSync } = require("node:fs");
const { join } = require("node:path");
if (!readFileSync || !join) {
throw new Error("测试运行环境缺少文件读取能力");
}
const root = process.cwd();
const css = readFileSync(join(root, "src/assets/styles.css"), "utf-8");
const statCard = readFileSync(join(root, "src/components/StatCard.vue"), "utf-8");
const compactPages = [
"DashboardView.vue",
"CommissionsView.vue",
"ConfigCenterView.vue",
"UsersView.vue",
"OperationLogsView.vue",
"PayrollView.vue",
];
for (const page of compactPages) {
const view = readFileSync(join(root, "src/views", page), "utf-8");
assertIncludes(view, "summary-strip", `${page} 应使用紧凑摘要条`);
assertNotIncludes(view, "<section class=\"stats-grid", `${page} 不应再使用大统计卡片区`);
}
assertIncludes(statCard, "summary-metric", "StatCard 应输出紧凑指标项");
assertNotIncludes(statCard, "class=\"stat-card\"", "StatCard 不应再输出旧大卡片类名");
const summaryStripBlock = cssBlock(".summary-strip");
assertIncludes(summaryStripBlock, "display: flex;");
assertIncludes(summaryStripBlock, "padding: 6px 8px;");
const summaryMetricBlock = cssBlock(".summary-metric");
assertIncludes(summaryMetricBlock, "min-height: 42px;");
assertIncludes(summaryMetricBlock, "padding: 5px 8px;");
assertNotIncludes(css, "min-height: 118px;", "旧大统计卡片高度不应保留");
function cssBlock(selector: string): string {
const pattern = new RegExp(`${escapeRegExp(selector)}\\s*\\{([\\s\\S]*?)\\n\\}`, "m");
const match = css.match(pattern);
if (!match) {
throw new Error(`缺少样式块 ${selector}`);
}
return match[1];
}
function assertIncludes(content: string, expected: string, message?: string): void {
if (!content.includes(expected)) {
throw new Error(message || `期望包含 ${expected}`);
}
}
function assertNotIncludes(content: string, unexpected: string, message?: string): void {
if (content.includes(unexpected)) {
throw new Error(message || `不应包含 ${unexpected}`);
}
}
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
+96
View File
@@ -0,0 +1,96 @@
declare const process: { cwd(): string };
declare function require(name: string): {
readFileSync?: (path: string, encoding: string) => string;
join?: (...parts: string[]) => string;
};
const { readFileSync } = require("node:fs");
const { join } = require("node:path");
if (!readFileSync || !join) {
throw new Error("测试运行环境缺少文件读取能力");
}
const root = process.cwd();
const css = readFileSync(join(root, "src/assets/styles.css"), "utf-8");
const themeStore = readFileSync(join(root, "src/stores/theme.ts"), "utf-8");
const rootBlock = cssBlock(":root");
assertIncludes(rootBlock, "--base-font-size: 14px;");
assertIncludes(rootBlock, "--page-padding: 20px;");
assertIncludes(rootBlock, "--content-gap: 14px;");
assertIncludes(rootBlock, "--panel-padding: 16px;");
const pageHeaderBlock = cssBlock(".page-header");
assertIncludes(pageHeaderBlock, "min-height: 52px;");
assertIncludes(pageHeaderBlock, "padding-bottom: 2px;");
const pageTitleBlock = cssBlock(".page-header h1");
assertIncludes(pageTitleBlock, "font-size: 26px;");
const summaryStripBlock = cssBlock(".summary-strip");
assertIncludes(summaryStripBlock, "display: flex;");
assertIncludes(summaryStripBlock, "padding: 6px 8px;");
const summaryMetricBlock = cssBlock(".summary-metric");
assertIncludes(summaryMetricBlock, "min-height: 42px;");
assertIncludes(summaryMetricBlock, "padding: 5px 8px;");
const summaryIconBlock = cssBlock(".summary-icon");
assertIncludes(summaryIconBlock, "width: 26px;");
assertIncludes(summaryIconBlock, "height: 26px;");
const panelHeaderBlock = cssBlock(".panel-header");
assertIncludes(panelHeaderBlock, "padding: 14px var(--panel-padding);");
const tableFilterBlock = cssBlock(".table-filter-bar");
assertIncludes(tableFilterBlock, "padding: 10px var(--panel-padding);");
assertIncludes(tableFilterBlock, "background: var(--surface);");
const tableHeaderBlock = cssBlock(".data-table th");
assertIncludes(tableHeaderBlock, "position: sticky;");
assertIncludes(tableHeaderBlock, "top: 0;");
const modalBackdropBlock = cssBlock(".modal-backdrop");
assertIncludes(modalBackdropBlock, "backdrop-filter: blur(8px);");
const employeeMetricBlock = cssBlock(".employee-inline-metric");
assertIncludes(employeeMetricBlock, "min-height: 42px;");
assertIncludes(employeeMetricBlock, "padding: 6px 10px;");
const userFormBlock = cssBlock(".user-form");
assertIncludes(userFormBlock, "grid-template-columns: repeat(4, minmax(0, 1fr));");
const paginationActionsBlock = cssBlock(".table-pagination .form-actions");
assertIncludes(paginationActionsBlock, "justify-content: flex-end;");
assertIncludes(paginationActionsBlock, "background: transparent;");
assertIncludes(themeStore, '"--base-font-size": "14px"');
assertIncludes(themeStore, '"--page-padding": "20px"');
assertIncludes(themeStore, '"--content-gap": "14px"');
assertIncludes(themeStore, '"--panel-padding": "16px"');
function cssBlock(selector: string): string {
const pattern = new RegExp(`${escapeRegExp(selector)}\\s*\\{([\\s\\S]*?)\\n\\}`, "m");
const match = css.match(pattern);
if (!match) {
throw new Error(`缺少样式块 ${selector}`);
}
return match[1];
}
function assertIncludes(content: string, expected: string): void {
if (!content.includes(expected)) {
throw new Error(`期望包含 ${expected}`);
}
}
function assertNotIncludes(content: string, unexpected: string): void {
if (content.includes(unexpected)) {
throw new Error(`不应包含 ${unexpected}`);
}
}
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
+87
View File
@@ -0,0 +1,87 @@
declare const process: { cwd(): string };
declare function require(name: string): {
readFileSync?: (path: string, encoding: string) => string;
join?: (...parts: string[]) => string;
};
const { readFileSync } = require("node:fs");
const { join } = require("node:path");
if (!readFileSync || !join) {
throw new Error("测试运行环境缺少文件读取能力");
}
const view = readFileSync(join(process.cwd(), "src/views/ReportsView.vue"), "utf-8");
const css = readFileSync(join(process.cwd(), "src/assets/styles.css"), "utf-8");
assertIncludes(view, "report-page");
assertIncludes(view, "report-ledger-workspace");
assertIncludes(view, "工资明细台账");
assertIncludes(view, "report-ledger-toolbar");
assertIncludes(view, "report-ledger-totals");
assertIncludes(view, "report-inline-metrics");
assertIncludes(view, "report-detail-grid");
assertIncludes(view, "report-record-panel");
assertIncludes(view, "最近工资明细");
assertNotIncludes(view, "<section class=\"stats-grid\">");
assertNotIncludes(view, "StatCard");
assertNotIncludes(view, "report-summary-panel");
assertNotIncludes(view, "report-metric-card");
assertNotIncludes(view, "先看明细再看拆分");
assertNotIncludes(view, "汇总金额和最近工资明细");
assertBefore(view, "report-ledger-workspace", "report-detail-grid");
assertBefore(view, "最近工资明细", "report-detail-grid");
assertIncludes(css, ".report-ledger-workspace");
assertIncludes(css, ".report-ledger-toolbar");
assertIncludes(css, ".report-ledger-totals");
assertIncludes(css, ".report-inline-metrics");
assertIncludes(css, ".report-record-panel");
assertIncludes(css, ".report-detail-grid");
assertNotIncludes(css, ".report-summary-panel");
assertNotIncludes(css, ".report-metric-card");
const ledgerToolbarBlock = cssBlock(".report-ledger-toolbar");
assertIncludes(ledgerToolbarBlock, "padding: 10px var(--panel-padding) 8px;");
const totalItemBlock = cssBlock(".report-total-item");
assertIncludes(totalItemBlock, "min-height: 52px;");
const inlineMetricsBlock = cssBlock(".report-inline-metrics");
assertIncludes(inlineMetricsBlock, "padding: 7px var(--panel-padding);");
const tableHeadingBlock = cssBlock(".report-table-heading");
assertIncludes(tableHeadingBlock, "padding: 9px var(--panel-padding) 6px;");
function assertIncludes(content: string, expected: string): void {
if (!content.includes(expected)) {
throw new Error(`期望包含 ${expected}`);
}
}
function assertNotIncludes(content: string, unexpected: string): void {
if (content.includes(unexpected)) {
throw new Error(`不应包含 ${unexpected}`);
}
}
function assertBefore(content: string, earlier: string, later: string): void {
const earlierIndex = content.indexOf(earlier);
const laterIndex = content.indexOf(later);
if (earlierIndex === -1 || laterIndex === -1 || earlierIndex >= laterIndex) {
throw new Error(`期望 ${earlier} 出现在 ${later} 之前`);
}
}
function cssBlock(selector: string): string {
const pattern = new RegExp(`${escapeRegExp(selector)}\\s*\\{([\\s\\S]*?)\\n\\}`, "m");
const match = css.match(pattern);
if (!match) {
throw new Error(`缺少样式块 ${selector}`);
}
return match[1];
}
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
@@ -0,0 +1,71 @@
declare const process: { cwd(): string };
declare function require(name: string): {
readFileSync?: (path: string, encoding: string) => string;
join?: (...parts: string[]) => string;
};
const { readFileSync } = require("node:fs");
const { join } = require("node:path");
if (!readFileSync || !join) {
throw new Error("测试运行环境缺少文件读取能力");
}
const view = readFileSync(join(process.cwd(), "src/views/SalaryProfilesView.vue"), "utf-8");
const css = readFileSync(join(process.cwd(), "src/assets/styles.css"), "utf-8");
assertIncludes(view, "salary-profile-page");
assertIncludes(view, "salary-profile-summary-strip");
assertIncludes(view, "salary-profile-summary-item");
assertIncludes(view, "salary-profile-toolbar");
assertIncludes(view, "salary-profile-modal-section");
assertIncludes(view, "salary-profile-modal-section-title");
assertBefore(view, "salary-profile-summary-strip", "薪资结构一览");
assertBefore(view, "薪资结构一览", "salary-profile-edit-modal");
const summaryBlock = cssBlock(".salary-profile-summary-strip");
assertIncludes(summaryBlock, "grid-template-columns: repeat(4, minmax(0, 1fr));");
assertIncludes(summaryBlock, "padding: 10px;");
assertNotIncludes(summaryBlock, "min-height: 178px;");
const toolbarBlock = cssBlock(".salary-profile-toolbar");
assertIncludes(toolbarBlock, "display: grid;");
assertIncludes(toolbarBlock, "grid-template-columns:");
const modalSectionBlock = cssBlock(".salary-profile-modal-section");
assertIncludes(modalSectionBlock, "border: var(--card-inner-border);");
assertIncludes(css, ".salary-profile-modal-section {\n display: grid;");
assertIncludes(css, "padding: 14px;");
assertIncludes(css, ".salary-profile-modal-fields");
assertIncludes(css, ".salary-profile-summary-strip,");
assertIncludes(css, ".salary-profile-toolbar");
function cssBlock(selector: string): string {
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const pattern = new RegExp(`${escaped}\\s*\\{([\\s\\S]*?)\\n\\}`, "m");
const match = css.match(pattern);
if (!match) {
throw new Error(`缺少样式块 ${selector}`);
}
return match[1];
}
function assertIncludes(content: string, expected: string): void {
if (!content.includes(expected)) {
throw new Error(`期望包含 ${expected}`);
}
}
function assertNotIncludes(content: string, unexpected: string): void {
if (content.includes(unexpected)) {
throw new Error(`不应包含 ${unexpected}`);
}
}
function assertBefore(content: string, earlier: string, later: string): void {
const earlierIndex = content.indexOf(earlier);
const laterIndex = content.indexOf(later);
if (earlierIndex === -1 || laterIndex === -1 || earlierIndex >= laterIndex) {
throw new Error(`期望 ${earlier} 出现在 ${later} 之前`);
}
}
@@ -0,0 +1,50 @@
declare const process: { cwd(): string };
declare function require(name: string): {
readFileSync?: (path: string, encoding: string) => string;
join?: (...parts: string[]) => string;
};
const { readFileSync } = require("node:fs");
const { join } = require("node:path");
if (!readFileSync || !join) {
throw new Error("测试运行环境缺少文件读取能力");
}
const view = readFileSync(join(process.cwd(), "src/views/SalaryProfilesView.vue"), "utf-8");
const css = readFileSync(join(process.cwd(), "src/assets/styles.css"), "utf-8");
assertIncludes(view, "salary-profile-list-tools");
assertIncludes(view, "salary-profile-edit-modal");
assertIncludes(view, "salary-profile-edit-card");
assertIncludes(view, "薪资结构一览");
assertIncludes(view, "编辑薪资");
assertIncludes(view, "<table class=\"data-table salary-profile-table\">");
assertNotIncludes(view, "salary-profile-card-grid");
assertNotIncludes(view, "salary-profile-form-panel");
assertBefore(view, "薪资结构一览", "salary-profile-edit-modal");
assertIncludes(css, ".salary-profile-list-tools");
assertIncludes(css, ".salary-profile-table");
assertIncludes(css, ".salary-profile-edit-modal");
assertIncludes(css, ".salary-profile-edit-card");
function assertIncludes(content: string, expected: string): void {
if (!content.includes(expected)) {
throw new Error(`期望包含 ${expected}`);
}
}
function assertNotIncludes(content: string, unexpected: string): void {
if (content.includes(unexpected)) {
throw new Error(`不应包含 ${unexpected}`);
}
}
function assertBefore(content: string, earlier: string, later: string): void {
const earlierIndex = content.indexOf(earlier);
const laterIndex = content.indexOf(later);
if (earlierIndex === -1 || laterIndex === -1 || earlierIndex >= laterIndex) {
throw new Error(`期望 ${earlier} 出现在 ${later} 之前`);
}
}
+46
View File
@@ -0,0 +1,46 @@
declare const process: { cwd(): string };
declare function require(name: string): {
readFileSync?: (path: string, encoding: string) => string;
join?: (...parts: string[]) => string;
};
const { readFileSync } = require("node:fs");
const { join } = require("node:path");
if (!readFileSync || !join) {
throw new Error("测试运行环境缺少文件读取能力");
}
const css = readFileSync(join(process.cwd(), "src/assets/styles.css"), "utf-8");
const messageBlock = cssBlock(".form-error,\\s*\\n\\.form-success");
assertIncludes(messageBlock, "position: fixed;");
assertIncludes(messageBlock, "z-index: 120;");
assertIncludes(messageBlock, "left: 50%;");
assertIncludes(messageBlock, "right: auto;");
assertIncludes(messageBlock, "animation: toast-autohide");
assertIncludes(messageBlock, "pointer-events: none;");
assertNotIncludes(messageBlock, "right: 28px;");
assertIncludes(css, "@keyframes toast-autohide");
assertIncludes(css, "visibility: hidden;");
function cssBlock(selector: string): string {
const pattern = new RegExp(`${selector}\\s*\\{([\\s\\S]*?)\\n\\}`, "m");
const match = css.match(pattern);
if (!match) {
throw new Error(`缺少样式块 ${selector}`);
}
return match[1];
}
function assertIncludes(content: string, expected: string): void {
if (!content.includes(expected)) {
throw new Error(`期望包含 ${expected}`);
}
}
function assertNotIncludes(content: string, unexpected: string): void {
if (content.includes(unexpected)) {
throw new Error(`不应包含 ${unexpected}`);
}
}
+7
View File
@@ -25,6 +25,13 @@ assertIncludes(ledgerVisual, "padding: 32px;");
const appearancePanel = cssBlock(".appearance-panel");
assertIncludes(appearancePanel, "width: min(480px, calc(100vw - 20px));");
const primaryFocus = cssBlock(".primary-button:focus-visible");
assertIncludes(primaryFocus, "outline: 0;");
assertIncludes(primaryFocus, "box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.16);");
const tableRows = cssBlock(".data-table tbody tr");
assertIncludes(tableRows, "transition: background var(--motion-duration-fast) ease;");
function cssBlock(selector: string): string {
const pattern = new RegExp(`${escapeRegExp(selector)}\\s*\\{([\\s\\S]*?)\\n\\}`, "m");
const match = css.match(pattern);