- {overview.radarFeed.filter((f) => f.stayDurationMs > 10000).length}
+
+ {t['workplace.funnel.interact']}
+ {funnelMetrics.interact.ratioText}
+
{funnelMetrics.interact.value}
-
0
- ? (overview.radarFeed.filter((f) => f.stayDurationMs > 10000).length /
- kpis.viewCount) *
- 100
- : 50
- )
- )}%`,
- }}
- />
+
+
+
+ Views → Engaged
-
{t['workplace.funnel.lead']}
-
{kpis.newLeadCount}
+
+ {t['workplace.funnel.lead']}
+ {funnelMetrics.lead.ratioText}
+
+
{funnelMetrics.lead.value}
0 ? (kpis.newLeadCount / kpis.viewCount) * 100 : 25
- )
- )}%`,
- }}
+ style={{ width: funnelMetrics.lead.barWidth }}
/>
+
+ Engaged → Leads
+
diff --git a/admin/src/pages/dashboard/workplace/style/index.module.less b/admin/src/pages/dashboard/workplace/style/index.module.less
index 109bd35..0402f76 100644
--- a/admin/src/pages/dashboard/workplace/style/index.module.less
+++ b/admin/src/pages/dashboard/workplace/style/index.module.less
@@ -1,8 +1,7 @@
.page {
min-height: 100%;
width: 100%;
- max-width: 1200px;
- margin: 0 auto;
+ margin: 0;
font-family: var(--omni-font-family);
box-sizing: border-box;
}
@@ -191,32 +190,46 @@
.funnelStep {
display: flex;
flex-direction: column;
+ position: relative;
+}
+
+.funnelHeader {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ margin-bottom: var(--omni-space-4);
}
.funnelLabel {
font-size: 13px;
+ font-weight: 500;
color: var(--omni-color-text-2);
- margin-bottom: var(--omni-space-4);
+}
+
+.funnelRate {
+ font-size: 12px;
+ color: var(--omni-color-primary);
+ font-weight: 600;
}
.funnelValue {
- font-size: 20px;
+ font-size: 22px;
font-weight: 600;
color: var(--omni-color-text);
margin-bottom: var(--omni-space-8);
}
.funnelBarWrapper {
- height: 4px;
- background: var(--omni-color-surface);
- border-radius: 2px;
+ height: 6px;
+ background: rgba(11, 92, 255, 0.08);
+ border-radius: 3px;
overflow: hidden;
}
.funnelBar {
height: 100%;
background: var(--omni-color-primary);
- border-radius: 2px;
+ border-radius: 3px;
transition: width 300ms ease;
}
@@ -224,6 +237,15 @@
background: var(--omni-color-success);
}
+.funnelMeta {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: var(--omni-space-4);
+ font-size: 11px;
+ color: var(--omni-color-text-2);
+}
+
.radarCard {
margin-top: var(--omni-space-16);
}
diff --git a/admin/tests/workplace-layout-alignment.test.ts b/admin/tests/workplace-layout-alignment.test.ts
new file mode 100644
index 0000000..d092b8c
--- /dev/null
+++ b/admin/tests/workplace-layout-alignment.test.ts
@@ -0,0 +1,79 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import fs from 'node:fs';
+import path from 'path';
+import { fileURLToPath } from 'node:url';
+
+const adminRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
+
+function read(relPath: string): string {
+ return fs.readFileSync(path.join(adminRoot, relPath), 'utf-8');
+}
+
+const EMOJI_RE =
+ /[\u{1F300}-\u{1F5FF}\u{1F600}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}\u{1F780}-\u{1F7FF}\u{1F800}-\u{1F8FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u;
+
+test('1. Workplace 根容器必须全宽自然左对齐,严禁 margin: 0 auto 产生脱节白区', () => {
+ const styles = read('src/pages/dashboard/workplace/style/index.module.less');
+
+ // 必须全宽
+ assert.ok(styles.includes('width: 100%'), 'Workplace 根元素必须 width: 100%');
+
+ // 严禁在 .page 根容器使用 margin: 0 auto,防止在大屏产生 200px+ 的左侧留白与错位
+ const pageRuleMatch = styles.match(/\.page\s*\{([^}]+)\}/);
+ assert.ok(pageRuleMatch, '必须包含 .page 规则');
+ const pageRule = pageRuleMatch[1];
+ assert.ok(
+ !pageRule.includes('margin: 0 auto') && !pageRule.includes('margin:0 auto'),
+ 'Workplace .page 严禁使用 margin: 0 auto 导致与面包屑脱节错位'
+ );
+ assert.ok(
+ !pageRule.includes('max-width: 1200px'),
+ 'Workplace 作为复杂工作台仪表盘不应在宽屏下硬锁 1200px 造成两侧悬空'
+ );
+});
+
+test('2. 转化漏斗 (Conversion Funnel) 必须具有高对比度轨道底色、增厚条与流转连贯设计', () => {
+ const styles = read('src/pages/dashboard/workplace/style/index.module.less');
+ const tsx = read('src/pages/dashboard/workplace/index.tsx');
+
+ // 轨道背景不可仅为近乎隐形的 omni-color-surface,必须有明显底色(如 rgba(11, 92, 255, 0.08) 或边框)
+ assert.ok(
+ styles.includes('rgba(11, 92, 255') || styles.includes('--omni-color-primary-soft'),
+ '漏斗条轨道槽必须具备清晰可见的对比度底色'
+ );
+
+ // 漏斗条高度提升至至少 6px
+ assert.ok(
+ styles.includes('height: 6px') || styles.includes('height: 8px'),
+ '漏斗条高度必须至少 6px 提升视觉分量'
+ );
+
+ // 必须展示漏斗转化比率或流转指标
+ assert.ok(
+ tsx.includes('funnelRate') || tsx.includes('funnelRatio') || tsx.includes('funnelMeta'),
+ '漏斗各步骤必须展示转化比率或流转说明'
+ );
+});
+
+test('3. 快捷导航卡片副标题必须具备单行文本防截断样式', () => {
+ const styles = read('src/pages/dashboard/workplace/style/index.module.less');
+
+ assert.ok(
+ styles.includes('text-overflow: ellipsis') || styles.includes('overflow: hidden'),
+ '快捷卡片副标题必须具备溢出隐藏保护'
+ );
+ assert.ok(
+ styles.includes('white-space: nowrap'),
+ '快捷卡片副标题必须保持单行不发生难看的断字折行'
+ );
+});
+
+test('4. 零 any,零 emoji,符合出海国际化商务规范', () => {
+ const tsx = read('src/pages/dashboard/workplace/index.tsx');
+ const styles = read('src/pages/dashboard/workplace/style/index.module.less');
+
+ assert.ok(!/\bany\b/.test(tsx), 'Workplace 代码严禁使用 any');
+ assert.ok(!EMOJI_RE.test(tsx), 'Workplace 代码严禁使用 emoji');
+ assert.ok(!EMOJI_RE.test(styles), 'Workplace 样式严禁使用 emoji');
+});