From d7c00502587117c202da2bc06a14534be541a7fc Mon Sep 17 00:00:00 2001 From: wikiboss Date: Mon, 21 Sep 2026 10:20:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20=E4=B8=93=E4=B8=9A=E5=87=BA?= =?UTF-8?q?=E6=B5=B7=E8=BF=90=E8=90=A5=E5=AE=98=E8=A7=86=E8=A7=92=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E5=B7=A5=E4=BD=9C=E5=8F=B0=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E9=9B=B7=E8=BE=BE=E5=AE=A2=E5=95=86=E6=B5=81?= =?UTF-8?q?=E3=80=81=E5=87=BA=E6=B5=B7=E8=BD=AC=E5=8C=96=E6=BC=8F=E6=96=97?= =?UTF-8?q?=E4=B8=8E=E8=BF=90=E8=90=A5=E5=B7=A5=E5=85=B7=E7=AE=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/workplace/announcement.tsx | 67 ---- .../dashboard/workplace/assets/calendar.svg | 86 ----- .../dashboard/workplace/assets/comments.svg | 71 ---- .../dashboard/workplace/assets/content.svg | 168 -------- .../dashboard/workplace/assets/increase.svg | 77 ---- .../pages/dashboard/workplace/carousel.tsx | 36 -- .../workplace/content-percentage.tsx | 88 ----- admin/src/pages/dashboard/workplace/docs.tsx | 33 -- admin/src/pages/dashboard/workplace/index.tsx | 363 ++++++++++++++++-- .../pages/dashboard/workplace/locale/index.ts | 73 +++- .../pages/dashboard/workplace/mock/index.ts | 117 ------ .../pages/dashboard/workplace/overview.tsx | 156 -------- .../dashboard/workplace/popular-contents.tsx | 115 ------ .../pages/dashboard/workplace/shortcuts.tsx | 117 ------ .../workplace/style/announcement.module.less | 19 - .../workplace/style/docs.module.less | 15 - .../workplace/style/index.module.less | 282 +++++++++++++- .../workplace/style/overview.module.less | 69 ---- .../style/popular-contents.module.less | 8 - .../workplace/style/shortcuts.module.less | 57 --- admin/tests/dashboard-workplace.test.ts | 37 ++ 21 files changed, 678 insertions(+), 1376 deletions(-) delete mode 100644 admin/src/pages/dashboard/workplace/announcement.tsx delete mode 100644 admin/src/pages/dashboard/workplace/assets/calendar.svg delete mode 100644 admin/src/pages/dashboard/workplace/assets/comments.svg delete mode 100644 admin/src/pages/dashboard/workplace/assets/content.svg delete mode 100644 admin/src/pages/dashboard/workplace/assets/increase.svg delete mode 100644 admin/src/pages/dashboard/workplace/carousel.tsx delete mode 100644 admin/src/pages/dashboard/workplace/content-percentage.tsx delete mode 100644 admin/src/pages/dashboard/workplace/docs.tsx delete mode 100644 admin/src/pages/dashboard/workplace/mock/index.ts delete mode 100644 admin/src/pages/dashboard/workplace/overview.tsx delete mode 100644 admin/src/pages/dashboard/workplace/popular-contents.tsx delete mode 100644 admin/src/pages/dashboard/workplace/shortcuts.tsx delete mode 100644 admin/src/pages/dashboard/workplace/style/announcement.module.less delete mode 100644 admin/src/pages/dashboard/workplace/style/docs.module.less delete mode 100644 admin/src/pages/dashboard/workplace/style/overview.module.less delete mode 100644 admin/src/pages/dashboard/workplace/style/popular-contents.module.less delete mode 100644 admin/src/pages/dashboard/workplace/style/shortcuts.module.less diff --git a/admin/src/pages/dashboard/workplace/announcement.tsx b/admin/src/pages/dashboard/workplace/announcement.tsx deleted file mode 100644 index d06849c..0000000 --- a/admin/src/pages/dashboard/workplace/announcement.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import axios from 'axios'; -import { Link, Card, Skeleton, Tag, Typography } from '@arco-design/web-react'; -import useLocale from '@/utils/useLocale'; -import locale from './locale'; -import styles from './style/announcement.module.less'; - -function Announcement() { - const [data, setData] = useState([]); - const [loading, setLoading] = useState(true); - - const t = useLocale(locale); - - const fetchData = () => { - setLoading(true); - axios - .get('/api/workplace/announcement') - .then((res) => { - setData(res.data); - }) - .finally(() => { - setLoading(false); - }); - }; - - useEffect(() => { - fetchData(); - }, []); - - function getTagColor(type) { - switch (type) { - case 'activity': - return 'orangered'; - case 'info': - return 'cyan'; - case 'notice': - return 'arcoblue'; - default: - return 'arcoblue'; - } - } - - return ( - -
- - {t['workplace.announcement']} - - {t['workplace.seeMore']} -
- -
- {data.map((d) => ( -
- - {t[`workplace.${d.type}`]} - - {d.content} -
- ))} -
-
-
- ); -} - -export default Announcement; diff --git a/admin/src/pages/dashboard/workplace/assets/calendar.svg b/admin/src/pages/dashboard/workplace/assets/calendar.svg deleted file mode 100644 index ceeb8ee..0000000 --- a/admin/src/pages/dashboard/workplace/assets/calendar.svg +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/admin/src/pages/dashboard/workplace/assets/comments.svg b/admin/src/pages/dashboard/workplace/assets/comments.svg deleted file mode 100644 index b537d09..0000000 --- a/admin/src/pages/dashboard/workplace/assets/comments.svg +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/admin/src/pages/dashboard/workplace/assets/content.svg b/admin/src/pages/dashboard/workplace/assets/content.svg deleted file mode 100644 index 204c749..0000000 --- a/admin/src/pages/dashboard/workplace/assets/content.svg +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/admin/src/pages/dashboard/workplace/assets/increase.svg b/admin/src/pages/dashboard/workplace/assets/increase.svg deleted file mode 100644 index 0d261ce..0000000 --- a/admin/src/pages/dashboard/workplace/assets/increase.svg +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/admin/src/pages/dashboard/workplace/carousel.tsx b/admin/src/pages/dashboard/workplace/carousel.tsx deleted file mode 100644 index 4bd1cf5..0000000 --- a/admin/src/pages/dashboard/workplace/carousel.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { Carousel } from '@arco-design/web-react'; - -const imageSrc = [ - '//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/f7e8fc1e09c42e30682526252365be1c.jpg~tplv-uwbnlip3yd-webp.webp', - '//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/94e8dd2d6dc4efb2c8cfd82c0ff02a2c.jpg~tplv-uwbnlip3yd-webp.webp', - '//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/ec447228c59ae1ebe185bab6cd776ca4.jpg~tplv-uwbnlip3yd-webp.webp', - '//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/1d1580d2a5a1e27415ff594c756eabd8.jpg~tplv-uwbnlip3yd-webp.webp', -]; -function C() { - return ( - - {imageSrc.map((src, index) => ( -
- -
- ))} -
- ); -} - -export default C; diff --git a/admin/src/pages/dashboard/workplace/content-percentage.tsx b/admin/src/pages/dashboard/workplace/content-percentage.tsx deleted file mode 100644 index e28fceb..0000000 --- a/admin/src/pages/dashboard/workplace/content-percentage.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { Card, Spin, Typography } from '@arco-design/web-react'; -import { DonutChart } from 'bizcharts'; -import axios from 'axios'; -import useLocale from '@/utils/useLocale'; -import locale from './locale'; - -function PopularContent() { - const t = useLocale(locale); - const [data, setData] = useState([]); - const [loading, setLoading] = useState(true); - - const fetchData = () => { - setLoading(true); - axios - .get('/api/workplace/content-percentage') - .then((res) => { - setData(res.data); - }) - .finally(() => { - setLoading(false); - }); - }; - - useEffect(() => { - fetchData(); - }, []); - - return ( - - - {t['workplace.contentPercentage']} - - - `${(v.percent * 100).toFixed(0)}%`, - style: { - fill: '#86909C', - fontSize: 14, - }, - }} - legend={{ - position: 'bottom', - }} - statistic={{ - title: { - style: { - fontSize: '14px', - lineHeight: 2, - color: 'rgb(--var(color-text-1))', - }, - formatter: () => '内容量', - }, - content: { - style: { - fontSize: '16px', - color: 'rgb(--var(color-text-1))', - }, - formatter: (_, data) => { - const sum = data.reduce((a, b) => a + b.count, 0); - return Number(sum).toLocaleString(); - }, - }, - }} - /> - - - ); -} - -export default PopularContent; diff --git a/admin/src/pages/dashboard/workplace/docs.tsx b/admin/src/pages/dashboard/workplace/docs.tsx deleted file mode 100644 index 972bde8..0000000 --- a/admin/src/pages/dashboard/workplace/docs.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { Link, Card, Typography } from '@arco-design/web-react'; -import useLocale from '@/utils/useLocale'; -import locale from './locale'; -import styles from './style/docs.module.less'; - -const links = { - react: 'https://arco.design/react/docs/start', - vue: 'https://arco.design/vue/docs/start', - designLab: 'https://arco.design/themes', - materialMarket: 'https://arco.design/material/', -}; -function QuickOperation() { - const t = useLocale(locale); - - return ( - -
- {t['workplace.docs']} - {t['workplace.seeMore']} -
-
- {Object.entries(links).map(([key, value]) => ( - - {t[`workplace.${key}`]} - - ))} -
-
- ); -} - -export default QuickOperation; diff --git a/admin/src/pages/dashboard/workplace/index.tsx b/admin/src/pages/dashboard/workplace/index.tsx index 6c0b66f..4427f18 100644 --- a/admin/src/pages/dashboard/workplace/index.tsx +++ b/admin/src/pages/dashboard/workplace/index.tsx @@ -8,13 +8,25 @@ import { Empty, Grid, Message, + Progress, + Radio, Spin, Statistic, Tag, Timeline, Typography, } from '@arco-design/web-react'; -import { IconPlus } from '@arco-design/web-react/icon'; +import { + IconArrowRise, + IconCompass, + IconDownload, + IconEye, + IconIdcard, + IconMobile, + IconPlus, + IconRobot, + IconUserAdd, +} from '@arco-design/web-react/icon'; import useLocale from '@/utils/useLocale'; import { getCssVariables, lightColors } from '@/style/tokens'; import type { GlobalState } from '@/store'; @@ -31,6 +43,7 @@ import styles from './style/index.module.less'; const { Row, Col } = Grid; const OVERVIEW_API = '/api/admin/crm/radar/workplace'; const LEAD_PAGE_API = '/api/admin/crm/lead/page'; + const EMPTY_KPI: WorkplaceKpi = { tapCount: 0, viewCount: 0, @@ -51,6 +64,8 @@ interface CoolPageData { list?: LeadEntity[]; } +type FeedFilter = 'all' | 'high_intent'; + function isTouchType(value: unknown): value is TouchType { return value === 'nfc' || value === 'qr' || value === 'link'; } @@ -66,6 +81,16 @@ function formatStaySeconds(ms: number): string { return String(Math.max(0, Math.round(ms / 1000))); } +function computeIntentScore(item: WorkplaceRadarItem): number { + const staySec = Math.round(item.stayDurationMs / 1000); + const blocksCount = item.viewedBlocks?.length || 0; + let score = 40; + if (staySec > 30) score += 20; + if (staySec > 90) score += 20; + score += Math.min(blocksCount * 10, 20); + return Math.min(score, 99); +} + function escapeCsvCell(value: string): string { if (value.includes(',') || value.includes('"') || value.includes('\n')) { return `"${value.replace(/"/g, '""')}"`; @@ -111,6 +136,7 @@ function Workplace(): React.ReactElement { const userInfo = useSelector((state: GlobalState) => state.userInfo); const [loading, setLoading] = useState(true); const [exporting, setExporting] = useState(false); + const [feedFilter, setFeedFilter] = useState('all'); const [overview, setOverview] = useState({ kpis: EMPTY_KPI, radarFeed: [], @@ -134,7 +160,7 @@ function Workplace(): React.ReactElement { try { const { data: payload } = await axios.post>( OVERVIEW_API, - { feedLimit: 12 } + { feedLimit: 15 } ); if (payload.data?.kpis) { setOverview({ @@ -165,6 +191,10 @@ function Workplace(): React.ReactElement { history.push('/dashboard/radar-map'); }; + const handleOpenAiConfig = () => { + history.push('/ai/concierge'); + }; + const handleExportLeads = async () => { setExporting(true); try { @@ -222,25 +252,67 @@ function Workplace(): React.ReactElement { value > 0 ? styles.deltaUp : value < 0 ? styles.deltaDown : styles.delta; return (
- {t['workplace.kpi.vsYesterday'].replace('{delta}', formatDelta(value))} + {value > 0 ? : null} + {t['workplace.kpi.vsYesterday'].replace('{delta}', formatDelta(value))}
); }; const kpis = overview.kpis; + // 渠道分布统计计算 + const channelStats = useMemo(() => { + const total = overview.radarFeed.length; + if (total === 0) { + return { nfc: 50, qr: 30, link: 20 }; + } + let nfc = 0; + let qr = 0; + let link = 0; + overview.radarFeed.forEach((item) => { + if (item.touchType === 'nfc') nfc += 1; + else if (item.touchType === 'qr') qr += 1; + else link += 1; + }); + const nfcPct = Math.round((nfc / total) * 100); + const qrPct = Math.round((qr / total) * 100); + const linkPct = link > 0 ? Math.max(0, 100 - nfcPct - qrPct) : 0; + return { + nfc: nfcPct, + qr: qrPct, + link: linkPct, + }; + }, [overview.radarFeed]); + + // 雷达客商列表(根据过滤器筛选) + const filteredFeed = useMemo(() => { + if (feedFilter === 'high_intent') { + return overview.radarFeed.filter((item) => computeIntentScore(item) >= 70); + } + return overview.radarFeed; + }, [feedFilter, overview.radarFeed]); + return (
+ {/* 顶部标题与唯 1 主 CTA */}
-
- - {t['workplace.title']} - +
+
+ + {t['workplace.title']} + + + + {t['workplace.radar.liveBadge']} + +
{t['workplace.subtitle']} {welcome}
+ + {/* 铁律:全屏唯 1 一个主 CTA 按钮 */}
+ {/* KPI 核心指标阵列 */} - +
+ {t['workplace.kpi.taps']} + + + +
+ {renderDelta(kpis.tapDelta)}
+ - +
+ {t['workplace.kpi.views']} + + + +
+ {renderDelta(kpis.viewDelta)}
+ - +
+ {t['workplace.kpi.leads']} + + + +
+ {renderDelta(kpis.newLeadDelta)}
+ +
+ {t['workplace.kpi.conversion']} + + + +
+
+ {t['workplace.kpi.conversionSub']} +
+ {/* 运营官转化漏斗与渠道矩阵 */} + + + +
+
+
{t['workplace.funnel.tap']}
+
{kpis.tapCount}
+
+
+
+
+ +
+
{t['workplace.funnel.view']}
+
{kpis.viewCount}
+
+
0 ? (kpis.viewCount / kpis.tapCount) * 100 : 80) + )}%`, + }} + /> +
+
+ +
+
{t['workplace.funnel.interact']}
+
+ {overview.radarFeed.filter((f) => f.stayDurationMs > 10000).length} +
+
+
0 + ? (overview.radarFeed.filter((f) => f.stayDurationMs > 10000).length / + kpis.viewCount) * + 100 + : 50 + ) + )}%`, + }} + /> +
+
+ +
+
{t['workplace.funnel.lead']}
+
{kpis.newLeadCount}
+
+
0 ? (kpis.newLeadCount / kpis.viewCount) * 100 : 25 + ) + )}%`, + }} + /> +
+
+
+ + + + + +
+
+ {t['workplace.channel.nfc']} + {channelStats.nfc}% +
+ +
+ +
+
+ {t['workplace.channel.qr']} + {channelStats.qr}% +
+ +
+ +
+
+ {t['workplace.channel.link']} + {channelStats.link}% +
+ +
+
+ + + + {/* 核心双栏:实时客商雷达流 VS 运营工具箱 */} + {t['workplace.radar.title']} + setFeedFilter(val as FeedFilter)} + > + {t['workplace.radar.filterAll']} + {t['workplace.radar.filterHighIntent']} + +
+ } > - {overview.radarFeed.length === 0 ? ( + {filteredFeed.length === 0 ? (
) : ( - - {overview.radarFeed.map((item: WorkplaceRadarItem) => { + + {filteredFeed.map((item: WorkplaceRadarItem) => { const touchType = isTouchType(item.touchType) ? item.touchType : 'link'; @@ -313,14 +534,21 @@ function Workplace(): React.ReactElement { .filter(Boolean) .join(', '); const blocks = (item.viewedBlocks || []).join(' / '); + const intentScore = computeIntentScore(item); return (
- {locationLabel || '—'} + {locationLabel || '—'} {getChannelLabel(touchType)} - + = 70 ? 'green' : 'gray'} + size="small" + > + {t['workplace.radar.intentScore'].replace('{score}', String(intentScore))} + + {t['workplace.radar.stay'].replace( '{seconds}', formatStaySeconds(item.stayDurationMs) @@ -339,37 +567,88 @@ function Workplace(): React.ReactElement { )} + + {/* 运营工具箱 */} - - +
+ + + + + + + +
diff --git a/admin/src/pages/dashboard/workplace/locale/index.ts b/admin/src/pages/dashboard/workplace/locale/index.ts index ac805f8..2a5f1a3 100644 --- a/admin/src/pages/dashboard/workplace/locale/index.ts +++ b/admin/src/pages/dashboard/workplace/locale/index.ts @@ -1,7 +1,7 @@ const i18n = { 'en-US': { 'workplace.title': 'Business overview', - 'workplace.subtitle': 'Today’s NFC taps, card views, new leads, and close rate', + 'workplace.subtitle': 'Real-time overview of NFC taps, page views, new leads, and close rate', 'workplace.welcome': 'Welcome back, {name}', 'workplace.welcomeGuest': 'Welcome back', 'workplace.cta.newCard': 'New card', @@ -9,20 +9,35 @@ const i18n = { 'workplace.kpi.views': 'Card views', 'workplace.kpi.leads': 'New leads', 'workplace.kpi.conversion': 'Close rate', + 'workplace.kpi.conversionSub': 'Taps to leads', 'workplace.kpi.vsYesterday': 'vs yesterday {delta}', 'workplace.radar.title': 'Live visitor radar', + 'workplace.radar.liveBadge': 'Live Radar Active', + 'workplace.radar.filterAll': 'All Activity', + 'workplace.radar.filterHighIntent': 'High Intent Only', + 'workplace.radar.intentScore': 'Intent {score}', 'workplace.radar.empty': 'No one has opened a card today.', 'workplace.radar.emptyAction': 'Open global radar', 'workplace.radar.stay': '{seconds}s stay', 'workplace.radar.viewing': 'Viewing {blocks}', + 'workplace.funnel.title': 'Conversion Funnel', + 'workplace.funnel.tap': 'Physical NFC', + 'workplace.funnel.view': 'Page Views', + 'workplace.funnel.interact': 'Interactions', + 'workplace.funnel.lead': 'CRM Leads', + 'workplace.channel.title': 'Touch Channels', 'workplace.shortcut.title': 'Shortcuts', 'workplace.shortcut.issueCard': 'Issue NFC cards', 'workplace.shortcut.issueCardHint': 'Generate short links and print files', 'workplace.shortcut.exportLeads': 'Export leads', - 'workplace.shortcut.exportLeadsHint': 'Download today’s CRM list as CSV', + 'workplace.shortcut.exportLeadsHint': 'Download latest CRM lead list as CSV', + 'workplace.shortcut.radarMap': 'Global Radar Map', + 'workplace.shortcut.radarMapHint': 'Explore real-time visitor geography and heatmaps', + 'workplace.shortcut.aiConfig': 'AI Concierge Strategy', + 'workplace.shortcut.aiConfigHint': 'Tune multi-lingual greeting and lead qualification', 'workplace.channel.nfc': 'NFC', - 'workplace.channel.qr': 'QR', - 'workplace.channel.link': 'Link', + 'workplace.channel.qr': 'QR Code', + 'workplace.channel.link': 'Direct Link', 'workplace.export.success': 'Lead list exported', 'workplace.export.empty': 'No leads to export yet', 'workplace.export.fail': 'Unable to export leads', @@ -30,7 +45,7 @@ const i18n = { }, 'zh-CN': { 'workplace.title': '商务概览', - 'workplace.subtitle': '今日触碰、浏览、新线索与成交转化', + 'workplace.subtitle': '实时追踪 NFC 触碰、微官网浏览、新客留资与商机转化率', 'workplace.welcome': '欢迎回来,{name}', 'workplace.welcomeGuest': '欢迎回来', 'workplace.cta.newCard': '新建名片', @@ -38,20 +53,35 @@ const i18n = { 'workplace.kpi.views': '浏览量', 'workplace.kpi.leads': '新线索', 'workplace.kpi.conversion': '成交转化', + 'workplace.kpi.conversionSub': '触碰转留资', 'workplace.kpi.vsYesterday': '较昨日 {delta}', 'workplace.radar.title': '今日实时雷达', + 'workplace.radar.liveBadge': '实时雷达在线', + 'workplace.radar.filterAll': '全量动态', + 'workplace.radar.filterHighIntent': '仅看高意向', + 'workplace.radar.intentScore': '意向分 {score}', 'workplace.radar.empty': '今天还没有访客打开名片。', 'workplace.radar.emptyAction': '查看全球雷达', 'workplace.radar.stay': '停留 {seconds} 秒', 'workplace.radar.viewing': '正在查看 {blocks}', - 'workplace.shortcut.title': '快捷入口', + 'workplace.funnel.title': '出海转化漏斗', + 'workplace.funnel.tap': 'NFC 触碰', + 'workplace.funnel.view': '官网浏览', + 'workplace.funnel.interact': '意向交互', + 'workplace.funnel.lead': '商机留资', + 'workplace.channel.title': '触达渠道分布', + 'workplace.shortcut.title': '运营工具箱', 'workplace.shortcut.issueCard': '批量发卡', - 'workplace.shortcut.issueCardHint': '生成短链与打印物料', + 'workplace.shortcut.issueCardHint': '生成短链与制卡打印物料', 'workplace.shortcut.exportLeads': '导出线索', - 'workplace.shortcut.exportLeadsHint': '下载潜客列表为 CSV', + 'workplace.shortcut.exportLeadsHint': '下载最新潜客名单为 CSV 表格', + 'workplace.shortcut.radarMap': '全球雷达大屏', + 'workplace.shortcut.radarMapHint': '查看全球访客地理分布与实时热力图', + 'workplace.shortcut.aiConfig': 'AI 顾问接待策略', + 'workplace.shortcut.aiConfigHint': '配置多语言接待话术与自动留资意向阈值', 'workplace.channel.nfc': 'NFC', 'workplace.channel.qr': '二维码', - 'workplace.channel.link': '链接', + 'workplace.channel.link': '直链', 'workplace.export.success': '线索已导出', 'workplace.export.empty': '暂无可导出的线索', 'workplace.export.fail': '线索导出失败', @@ -59,7 +89,7 @@ const i18n = { }, 'ar-SA': { 'workplace.title': 'نظرة العمل', - 'workplace.subtitle': 'لمسات NFC ومشاهدات البطاقة والعملاء الجدد ومعدل الإغلاق اليوم', + 'workplace.subtitle': 'نظرة فورية على لمسات NFC ومشاهدات البطاقة والعملاء الجدد ومعدل الإغلاق', 'workplace.welcome': 'مرحباً بعودتك، {name}', 'workplace.welcomeGuest': 'مرحباً بعودتك', 'workplace.cta.newCard': 'بطاقة جديدة', @@ -67,20 +97,35 @@ const i18n = { 'workplace.kpi.views': 'مشاهدات البطاقة', 'workplace.kpi.leads': 'عملاء جدد', 'workplace.kpi.conversion': 'معدل الإغلاق', + 'workplace.kpi.conversionSub': 'من لمس إلى عميل', 'workplace.kpi.vsYesterday': 'مقارنة بالأمس {delta}', 'workplace.radar.title': 'رادار الزوار المباشر', + 'workplace.radar.liveBadge': 'رادار حي نشط', + 'workplace.radar.filterAll': 'كافة الأنشطة', + 'workplace.radar.filterHighIntent': 'المهتمون فقط', + 'workplace.radar.intentScore': 'درجة {score}', 'workplace.radar.empty': 'لم يفتح أحد بطاقة اليوم.', 'workplace.radar.emptyAction': 'فتح الرادار العالمي', 'workplace.radar.stay': 'مكث {seconds} ث', 'workplace.radar.viewing': 'يشاهد {blocks}', - 'workplace.shortcut.title': 'اختصارات', + 'workplace.funnel.title': 'قمع التحويل', + 'workplace.funnel.tap': 'لمس NFC', + 'workplace.funnel.view': 'عرض الصفحة', + 'workplace.funnel.interact': 'تفاعل مهتم', + 'workplace.funnel.lead': 'عميل محتمل', + 'workplace.channel.title': 'قنوات الاتصال', + 'workplace.shortcut.title': 'صندوق أدوات التشغيل', 'workplace.shortcut.issueCard': 'إصدار بطاقات NFC', - 'workplace.shortcut.issueCardHint': 'إنشاء الروابط وملفات الطباعة', + 'workplace.shortcut.issueCardHint': 'إنشاء الروابط وملفات الطباعة للبطاقات', 'workplace.shortcut.exportLeads': 'تصدير العملاء', - 'workplace.shortcut.exportLeadsHint': 'تنزيل قائمة CRM بتنسيق CSV', + 'workplace.shortcut.exportLeadsHint': 'تنزيل قائمة CRM المحدثة بتنسيق CSV', + 'workplace.shortcut.radarMap': 'خريطة الرادار العالمية', + 'workplace.shortcut.radarMapHint': 'استكشف التوزيع الجغرافي للزوار وخريطة الحرارة', + 'workplace.shortcut.aiConfig': 'استراتيجية المساعد الذكي', + 'workplace.shortcut.aiConfigHint': 'تكوين الترحيب متعدد اللغات وتأهيل العملاء المحتملين', 'workplace.channel.nfc': 'NFC', 'workplace.channel.qr': 'رمز QR', - 'workplace.channel.link': 'رابط', + 'workplace.channel.link': 'رابط مباشر', 'workplace.export.success': 'تم تصدير قائمة العملاء', 'workplace.export.empty': 'لا توجد عملاء للتصدير', 'workplace.export.fail': 'تعذر تصدير العملاء', diff --git a/admin/src/pages/dashboard/workplace/mock/index.ts b/admin/src/pages/dashboard/workplace/mock/index.ts deleted file mode 100644 index 3a55f6f..0000000 --- a/admin/src/pages/dashboard/workplace/mock/index.ts +++ /dev/null @@ -1,117 +0,0 @@ -import Mock from 'mockjs'; -import qs from 'query-string'; -import setupMock from '@/utils/setupMock'; - -setupMock({ - setup: () => { - Mock.mock(new RegExp('/api/workplace/overview-content'), () => { - const year = new Date().getFullYear(); - const getLineData = () => { - return new Array(12).fill(0).map((_item, index) => ({ - date: `${year}-${index + 1}`, - count: Mock.Random.natural(20000, 75000), - })); - }; - return { - allContents: '373.5w+', - liveContents: '368', - increaseComments: '8874', - growthRate: '2.8%', - chartData: getLineData(), - }; - }); - - const getList = () => { - const { list } = Mock.mock({ - 'list|100': [ - { - 'rank|+1': 1, - title: () => - Mock.Random.pick([ - '经济日报:财政政策要精准提升效能', - '“双12”遇冷消费者厌倦了电商平台的促销“套路”', - '致敬坚守战“疫”一线的社区工作者', - '普高还是职高?家长们陷入选校难题', - ]), - pv: function () { - return 500000 - 3200 * this.rank; - }, - increase: '@float(-1, 1)', - }, - ], - }); - return list; - }; - const listText = getList(); - const listPic = getList(); - const listVideo = getList(); - - Mock.mock(new RegExp('/api/workplace/popular-contents'), (params) => { - const { - page = 1, - pageSize = 5, - category = 0, - } = qs.parseUrl(params.url).query as unknown as { - page?: number; - pageSize?: number; - category?: number; - }; - - const list = [listText, listPic, listVideo][Number(category)]; - return { - list: list.slice((page - 1) * pageSize, page * pageSize), - total: 100, - }; - }); - - Mock.mock(new RegExp('/api/workplace/content-percentage'), () => { - return [ - { - type: '纯文本', - count: 148564, - percent: 0.16, - }, - { - type: '图文类', - count: 334271, - percent: 0.36, - }, - { - type: '视频类', - count: 445695, - percent: 0.48, - }, - ]; - }); - - Mock.mock(new RegExp('/api/workplace/announcement'), () => { - return [ - { - type: 'activity', - key: '1', - content: '内容最新优惠活动', - }, - { - type: 'info', - key: '2', - content: '新增内容尚未通过审核,详情请点击查看。', - }, - { - type: 'notice', - key: '3', - content: '当前产品试用期即将结束,如需续费请点击查看。', - }, - { - type: 'notice', - key: '4', - content: '1 月新系统升级计划通知', - }, - { - type: 'info', - key: '5', - content: '新增内容已经通过审核,详情请点击查看。', - }, - ]; - }); - }, -}); diff --git a/admin/src/pages/dashboard/workplace/overview.tsx b/admin/src/pages/dashboard/workplace/overview.tsx deleted file mode 100644 index 9fec2f9..0000000 --- a/admin/src/pages/dashboard/workplace/overview.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import React, { useState, useEffect, ReactNode } from 'react'; -import { - Grid, - Card, - Typography, - Divider, - Skeleton, - Link, -} from '@arco-design/web-react'; -import { useSelector } from 'react-redux'; -import { IconCaretUp } from '@arco-design/web-react/icon'; -import OverviewAreaLine from '@/components/Chart/overview-area-line'; -import axios from 'axios'; -import locale from './locale'; -import useLocale from '@/utils/useLocale'; -import styles from './style/overview.module.less'; -import IconCalendar from './assets/calendar.svg'; -import IconComments from './assets/comments.svg'; -import IconContent from './assets/content.svg'; -import IconIncrease from './assets/increase.svg'; - -const { Row, Col } = Grid; - -type StatisticItemType = { - icon?: ReactNode; - title?: ReactNode; - count?: ReactNode; - loading?: boolean; - unit?: ReactNode; -}; - -function StatisticItem(props: StatisticItemType) { - const { icon, title, count, loading, unit } = props; - return ( -
-
{icon}
-
- -
{title}
-
- {count} - {unit} -
-
-
-
- ); -} - -type DataType = { - allContents?: string; - liveContents?: string; - increaseComments?: string; - growthRate?: string; - chartData?: { count?: number; date?: string }[]; - down?: boolean; -}; - -function Overview() { - const [data, setData] = useState({}); - const [loading, setLoading] = useState(true); - const t = useLocale(locale); - - const userInfo = useSelector((state: any) => state.userInfo || {}); - - const fetchData = () => { - setLoading(true); - axios - .get('/api/workplace/overview-content') - .then((res) => { - setData(res.data); - }) - .finally(() => { - setLoading(false); - }); - }; - - useEffect(() => { - fetchData(); - }, []); - - return ( - - - {t['workplace.welcomeBack']} - {userInfo.name} - - - - - } - title={t['workplace.totalOnlyData']} - count={data.allContents} - loading={loading} - unit={t['workplace.pecs']} - /> - - - - } - title={t['workplace.contentInMarket']} - count={data.liveContents} - loading={loading} - unit={t['workplace.pecs']} - /> - - - - } - title={t['workplace.comments']} - count={data.increaseComments} - loading={loading} - unit={t['workplace.pecs']} - /> - - - - } - title={t['workplace.growth']} - count={ - - {data.growthRate}{' '} - - - } - loading={loading} - /> - - - -
-
- - {t['workplace.contentData']} - - ({t['workplace.1year']}) - - - {t['workplace.seeMore']} -
- -
-
- ); -} - -export default Overview; diff --git a/admin/src/pages/dashboard/workplace/popular-contents.tsx b/admin/src/pages/dashboard/workplace/popular-contents.tsx deleted file mode 100644 index 3b9d33a..0000000 --- a/admin/src/pages/dashboard/workplace/popular-contents.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import { Link, Card, Radio, Table, Typography } from '@arco-design/web-react'; -import { IconCaretDown, IconCaretUp } from '@arco-design/web-react/icon'; -import axios from 'axios'; -import useLocale from '@/utils/useLocale'; -import locale from './locale'; -import styles from './style/popular-contents.module.less'; - -function PopularContent() { - const t = useLocale(locale); - const [type, setType] = useState(0); - const [data, setData] = useState([]); - const [loading, setLoading] = useState(true); - const [page, setPage] = useState(1); - const [total, setTotal] = useState(0); - - const fetchData = useCallback(() => { - setLoading(true); - axios - .get( - `/api/workplace/popular-contents?page=${page}&pageSize=5&category=${type}` - ) - .then((res) => { - setData(res.data.list); - setTotal(res.data.total); - }) - .finally(() => { - setLoading(false); - }); - }, [page, type]); - - useEffect(() => { - fetchData(); - }, [page, fetchData]); - - const columns = [ - { - title: t['workplace.column.rank'], - dataIndex: 'rank', - width: 65, - }, - { - title: t['workplace.column.title'], - dataIndex: 'title', - render: (x) => ( - - {x} - - ), - }, - { - title: t['workplace.column.pv'], - dataIndex: 'pv', - width: 100, - render: (text) => { - return `${text / 1000}k`; - }, - }, - { - title: t['workplace.column.increase'], - dataIndex: 'increase', - sorter: (a, b) => a.increase - b.increase, - width: 110, - render: (text) => { - return ( - - {`${(text * 100).toFixed(2)}%`} - - {text < 0 ? ( - - ) : ( - - )} - - - ); - }, - }, - ]; - - return ( - -
- - {t['workplace.popularContents']} - - {t['workplace.seeMore']} -
- - { - setPage(pagination.current); - }} - pagination={{ total, current: page, pageSize: 5, simple: true }} - /> - - ); -} - -export default PopularContent; diff --git a/admin/src/pages/dashboard/workplace/shortcuts.tsx b/admin/src/pages/dashboard/workplace/shortcuts.tsx deleted file mode 100644 index 84bc6e3..0000000 --- a/admin/src/pages/dashboard/workplace/shortcuts.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React from 'react'; -import { - Link, - Card, - Divider, - Message, - Typography, -} from '@arco-design/web-react'; -import { - IconFile, - IconStorage, - IconSettings, - IconMobile, - IconFire, -} from '@arco-design/web-react/icon'; -import useLocale from '@/utils/useLocale'; -import locale from './locale'; -import styles from './style/shortcuts.module.less'; - -function Shortcuts() { - const t = useLocale(locale); - - const shortcuts = [ - { - title: t['workplace.contentMgmt'], - key: 'Content Management', - icon: , - }, - { - title: t['workplace.contentStatistic'], - key: 'Content Statistic', - icon: , - }, - { - title: t['workplace.advancedMgmt'], - key: 'Advanced Management', - icon: , - }, - { - title: t['workplace.onlinePromotion'], - key: 'Online Promotion', - icon: , - }, - { - title: t['workplace.marketing'], - key: 'Marketing', - icon: , - }, - ]; - - const recentShortcuts = [ - { - title: t['workplace.contentStatistic'], - key: 'Content Statistic', - icon: , - }, - { - title: t['workplace.contentMgmt'], - key: 'Content Management', - icon: , - }, - { - title: t['workplace.advancedMgmt'], - key: 'Advanced Management', - icon: , - }, - ]; - - function onClickShortcut(key) { - Message.info({ - content: ( - - You clicked {key} - - ), - }); - } - - return ( - -
- - {t['workplace.shortcuts']} - - {t['workplace.seeMore']} -
-
- {shortcuts.map((shortcut) => ( -
onClickShortcut(shortcut.key)} - > -
{shortcut.icon}
-
{shortcut.title}
-
- ))} -
- -
{t['workplace.recent']}
-
- {recentShortcuts.map((shortcut) => ( -
onClickShortcut(shortcut.key)} - > -
{shortcut.icon}
-
{shortcut.title}
-
- ))} -
-
- ); -} - -export default Shortcuts; diff --git a/admin/src/pages/dashboard/workplace/style/announcement.module.less b/admin/src/pages/dashboard/workplace/style/announcement.module.less deleted file mode 100644 index 4f0bb31..0000000 --- a/admin/src/pages/dashboard/workplace/style/announcement.module.less +++ /dev/null @@ -1,19 +0,0 @@ -.item { - display: flex; - align-items: center; - width: 100%; - height: 24px; - margin-bottom: 4px; -} - -.link { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: 4px; - color: var(--color-text-2); - text-decoration: none; - font-size: 13px; - cursor: pointer; -} diff --git a/admin/src/pages/dashboard/workplace/style/docs.module.less b/admin/src/pages/dashboard/workplace/style/docs.module.less deleted file mode 100644 index e6cf8ed..0000000 --- a/admin/src/pages/dashboard/workplace/style/docs.module.less +++ /dev/null @@ -1,15 +0,0 @@ -.docs { - display: grid; - grid-template-columns: 50% 50%; -} - -.link { - color: var(--color-text-2); - padding: 4px; - box-sizing: border-box; - margin-bottom: 12px; - - &:hover { - color: rgb(var(--primary-6)); - } -} diff --git a/admin/src/pages/dashboard/workplace/style/index.module.less b/admin/src/pages/dashboard/workplace/style/index.module.less index 228efd2..3620f35 100644 --- a/admin/src/pages/dashboard/workplace/style/index.module.less +++ b/admin/src/pages/dashboard/workplace/style/index.module.less @@ -15,14 +15,69 @@ margin-bottom: var(--omni-space-24); } +.headerInfo { + flex: 1; + min-width: 280px; +} + +.titleRow { + display: flex; + align-items: center; + gap: var(--omni-space-12); + flex-wrap: wrap; +} + .title { margin: 0; color: var(--omni-color-text); + font-size: 24px; + font-weight: 600; + line-height: 1.25; +} + +.liveIndicator { + display: inline-flex; + align-items: center; + gap: var(--omni-space-8); + padding: 4px 10px; + border-radius: var(--omni-radius-button); + background: var(--omni-color-primary-soft); + border: 1px solid rgba(11, 92, 255, 0.15); +} + +.pulseDot { + width: 6px; + height: 6px; + border-radius: 50%; + background-color: var(--omni-color-primary); + animation: nfcPulse 400ms cubic-bezier(0.2, 0.8, 0.2, 1) 1; +} + +@keyframes nfcPulse { + 0% { + transform: scale(0.8); + box-shadow: 0 0 0 0 rgba(11, 92, 255, 0.4); + } + 70% { + transform: scale(1.1); + box-shadow: 0 0 0 6px rgba(11, 92, 255, 0); + } + 100% { + transform: scale(1); + box-shadow: 0 0 0 0 rgba(11, 92, 255, 0); + } +} + +.liveText { + color: var(--omni-color-primary); + font-size: 12px; + font-weight: 500; + line-height: 1; } .subtitle { display: block; - margin-top: var(--omni-space-4); + margin-top: var(--omni-space-8); color: var(--omni-color-text-2); font-size: 14px; line-height: 1.6; @@ -30,9 +85,10 @@ .welcome { display: block; - margin-top: var(--omni-space-8); + margin-top: var(--omni-space-4); color: var(--omni-color-text); - font-size: 16px; + font-size: 14px; + font-weight: 500; line-height: 1.6; } @@ -40,6 +96,8 @@ height: 40px; border-radius: var(--omni-radius-button); background-color: var(--omni-color-primary); + font-weight: 500; + box-shadow: 0 1px 2px rgba(11, 92, 255, 0.16); } :global(.arco-card).kpiCard, @@ -48,19 +106,61 @@ border-radius: var(--omni-radius-card); background: var(--omni-color-bg); box-shadow: var(--omni-shadow); + transition: border-color 150ms cubic-bezier(0.2, 0.8, 0.2, 1); } -.main { - margin-top: var(--omni-space-24); +.kpiCard:hover { + border-color: rgba(11, 92, 255, 0.3); +} + +.kpiHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--omni-space-8); +} + +.kpiTitle { + color: var(--omni-color-text-2); + font-size: 14px; + font-weight: 500; +} + +.kpiIconWrapper { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 8px; + background: var(--omni-color-surface); + color: var(--omni-color-primary); +} + +.kpiIcon { + font-size: 16px; +} + +.kpiSubText { + margin-top: var(--omni-space-8); + font-size: 12px; + color: var(--omni-color-text-2); } .delta { + display: flex; + align-items: center; + gap: var(--omni-space-4); margin-top: var(--omni-space-8); font-size: 12px; line-height: 1.6; color: var(--omni-color-text-2); } +.deltaIcon { + font-size: 12px; +} + .deltaUp { color: var(--omni-color-success); } @@ -69,6 +169,103 @@ color: var(--omni-color-error); } +/* 漏斗与渠道矩阵 */ +.middleRow { + margin-top: var(--omni-space-16); +} + +.funnelContainer { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: var(--omni-space-16); + padding: var(--omni-space-8) 0; +} + +@media (max-width: 768px) { + .funnelContainer { + grid-template-columns: repeat(2, 1fr); + } +} + +.funnelStep { + display: flex; + flex-direction: column; +} + +.funnelLabel { + font-size: 13px; + color: var(--omni-color-text-2); + margin-bottom: var(--omni-space-4); +} + +.funnelValue { + font-size: 20px; + 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; + overflow: hidden; +} + +.funnelBar { + height: 100%; + background: var(--omni-color-primary); + border-radius: 2px; + transition: width 300ms ease; +} + +.funnelBarLead { + background: var(--omni-color-success); +} + +/* 渠道占比 */ +.channelRow { + margin-bottom: var(--omni-space-16); +} + +.channelRow:last-child { + margin-bottom: 0; +} + +.channelMeta { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--omni-space-8); + font-size: 13px; +} + +.channelName { + color: var(--omni-color-text); + font-weight: 500; +} + +.channelPercent { + color: var(--omni-color-text-2); + font-weight: 600; +} + +/* 主内容区域 */ +.main { + margin-top: var(--omni-space-16); +} + +.panelHeader { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.timelineList { + padding-top: var(--omni-space-8); +} + .feedMeta { display: flex; align-items: center; @@ -76,6 +273,10 @@ gap: var(--omni-space-8); } +.stayTime { + font-size: 12px; +} + .feedExtra { margin-top: var(--omni-space-4); color: var(--omni-color-text-2); @@ -95,26 +296,37 @@ line-height: 1.6; } -.shortcut { +/* 运营工具箱 */ +.toolboxList { display: flex; flex-direction: column; - align-items: flex-start; - width: 100%; - min-height: 44px; - padding: var(--omni-space-12) 0; - border: 0; - border-bottom: 1px solid var(--omni-color-border); - background: transparent; - text-align: start; - cursor: pointer; + gap: var(--omni-space-12); } -.shortcut:last-child { - border-bottom: 0; +.shortcut { + display: flex; + align-items: flex-start; + gap: var(--omni-space-12); + width: 100%; + padding: var(--omni-space-12); + border: 1px solid var(--omni-color-border); + border-radius: 12px; + background: var(--omni-color-surface); + text-align: start; + cursor: pointer; + transition: all 150ms cubic-bezier(0.2, 0.8, 0.2, 1); } .shortcut:hover { - filter: brightness(0.98); + background: var(--omni-color-bg); + border-color: rgba(11, 92, 255, 0.3); + transform: translateY(-1px); +} + +.shortcut:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; } .shortcut:focus-visible { @@ -122,20 +334,48 @@ outline-offset: 2px; } +.shortcutIconBox { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 8px; + background: var(--omni-color-bg); + color: var(--omni-color-primary); + flex-shrink: 0; +} + +.toolIcon { + font-size: 16px; +} + +.shortcutContent { + flex: 1; + min-width: 0; +} + .shortcutLabel { + display: block; color: var(--omni-color-text); font-size: 14px; - line-height: 1.6; + font-weight: 500; + line-height: 1.4; } .shortcutHint { + display: block; + margin-top: var(--omni-space-4); color: var(--omni-color-text-2); font-size: 12px; - line-height: 1.6; + line-height: 1.4; } @media (prefers-reduced-motion: reduce) { .shortcut:hover { - filter: none; + transform: none; + } + .pulseDot { + animation: none; } } diff --git a/admin/src/pages/dashboard/workplace/style/overview.module.less b/admin/src/pages/dashboard/workplace/style/overview.module.less deleted file mode 100644 index 6a1a2eb..0000000 --- a/admin/src/pages/dashboard/workplace/style/overview.module.less +++ /dev/null @@ -1,69 +0,0 @@ -.container { - padding: 20px; - - :global(.arco-divider-horizontal) { - border-bottom: 1px solid var(--color-border-1); - } - - :global(.arco-divider-vertical) { - border-left: 1px solid var(--color-border-1); - } -} - -.item { - display: flex; - align-items: center; - padding-left: 20px; - color: var(--color-text-1); -} - -.icon { - display: flex; - align-items: center; - justify-content: center; - width: 54px; - height: 54px; - background-color: var(--color-fill-2); - border-radius: 50%; - margin-right: 12px; -} - -.title { - font-size: 12px; - color: var(--color-text-1); -} - -.count { - font-size: 22px; - font-weight: 600; - color: var(--color-text-1); - - .unit { - font-size: 12px; - font-weight: 400; - color: var(--color-text-2); - margin-left: 8px; - } -} - -.divider { - height: 60px; -} - -.ctw { - display: flex; - justify-content: space-between; - margin-bottom: 16px; -} - -.chart-title { - font-size: 16px; - font-weight: 500; -} - -.chart-sub-title { - font-size: 12px; - font-weight: 400; - margin-left: 4px; - color: var(--color-text-3); -} diff --git a/admin/src/pages/dashboard/workplace/style/popular-contents.module.less b/admin/src/pages/dashboard/workplace/style/popular-contents.module.less deleted file mode 100644 index 6b8a56f..0000000 --- a/admin/src/pages/dashboard/workplace/style/popular-contents.module.less +++ /dev/null @@ -1,8 +0,0 @@ -.symbol { - font-size: 10px; - margin-left: 4px; - - > svg { - vertical-align: 0; - } -} diff --git a/admin/src/pages/dashboard/workplace/style/shortcuts.module.less b/admin/src/pages/dashboard/workplace/style/shortcuts.module.less deleted file mode 100644 index 2d5a68f..0000000 --- a/admin/src/pages/dashboard/workplace/style/shortcuts.module.less +++ /dev/null @@ -1,57 +0,0 @@ -.shortcuts { - display: grid; - grid-template-columns: 33.33% 33.33% 33.33%; -} - -.item { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 12px; - box-sizing: border-box; - cursor: pointer; - - &:hover { - .icon { - background-color: var(--color-primary-light-1); - - svg { - color: rgb(var(--primary-6)); - } - } - - .title { - color: rgb(var(--primary-6)); - } - } -} - -.icon { - display: flex; - justify-content: center; - align-items: center; - width: 32px; - height: 32px; - border-radius: 6px; - background-color: var(--color-fill-2); - margin-bottom: 4px; - - svg { - font-size: 18px; - } -} - -.title { - font-size: 12px; - line-height: 20px; - color: var(--color-text-1); -} - -.recent { - font-weight: 500; - font-size: 16px; - line-height: 24px; - color: var(--color-text-1); - margin-bottom: 16px; -} diff --git a/admin/tests/dashboard-workplace.test.ts b/admin/tests/dashboard-workplace.test.ts index 59a3db8..e9d6f66 100644 --- a/admin/tests/dashboard-workplace.test.ts +++ b/admin/tests/dashboard-workplace.test.ts @@ -81,3 +81,40 @@ test('5. 工作台 i18n 必须覆盖 KPI、雷达动态与快捷入口,且含 assert.ok(localeContent.includes('workplace.shortcut.exportLeads')); assert.ok(!EMOJI_RE.test(localeContent), '语言包严禁 emoji'); }); + +test('6. 专业出海运营官视角:包含全球雷达监控、渠道漏斗与效率工具,全屏仅 1 个主 CTA', () => { + const content = fs.readFileSync(pagePath, 'utf-8'); + const localeContent = fs.readFileSync(localePath, 'utf-8'); + + // 1. 全球雷达大屏导航 + assert.ok( + content.includes('/dashboard/radar-map'), + '必须包含全球雷达热力图入口' + ); + + // 2. 运营词条在中英阿三语完整定义 + const requiredKeys = [ + 'workplace.radar.liveBadge', + 'workplace.funnel.title', + 'workplace.channel.title', + 'workplace.shortcut.radarMap', + 'workplace.shortcut.aiConfig', + ]; + for (const key of requiredKeys) { + assert.ok( + localeContent.includes(key), + `语言包必须包含运营官核心词条: ${key}` + ); + } + + // 3. 铁律:无论如何扩展,全屏主 CTA 数量必须严格为 1 + const primaryButtons = content.match(/type=["']primary["']/g) || []; + assert.equal( + primaryButtons.length, + 1, + '扩展后全屏依然必须严格保持唯 1 一个主 CTA (type="primary")' + ); + assert.ok(!EMOJI_RE.test(localeContent), '语言包严禁 emoji'); + assert.ok(!EMOJI_RE.test(content), '页面代码严禁 emoji'); +}); +