diff --git a/.stitch/DESIGN.md b/.stitch/DESIGN.md new file mode 100644 index 0000000..61057eb --- /dev/null +++ b/.stitch/DESIGN.md @@ -0,0 +1,116 @@ +# Design System: Sino-MCI 消息中台 + +## 1. Visual Theme & Atmosphere + +A modern, high-density administration dashboard with a calm, professional atmosphere. The interface feels like a precision cockpit: information-dense but breathable, predictable but polished. Visual hierarchy is established through restrained color, generous whitespace in headers, and tight data grids in tables. The personality is trustworthy and operational — no marketing fluff, no decorative gradients, no visual noise. + +- **Density:** 7/10 — Cockpit-dense tables and forms, airy page headers. +- **Variance:** 3/10 — Predictable, symmetric layouts for operational efficiency. +- **Motion:** 4/10 — Fluid, purposeful micro-interactions; no cinematic delays. + +## 2. Color Palette & Roles + +- **Canvas White** (#F8FAFC) — Primary background surface behind all content. +- **Pure Surface** (#FFFFFF) — Cards, modals, drawers, table rows, input backgrounds. +- **Charcoal Ink** (#0F172A) — Primary text, headings, key labels. +- **Muted Steel** (#64748B) — Secondary text, placeholders, metadata, disabled states. +- **Whisper Border** (#E2E8F0) — 1px structural borders, table dividers, section separators. +- **Soft Tint** (#F1F5F9) — Hover backgrounds, zebra striping, subtle section fills. +- **Signal Blue** (#2563EB) — Single accent for primary buttons, active nav items, focus rings, links, status "online". +- **Signal Red** (#DC2626) — Destructive actions, errors, failed status. +- **Signal Green** (#16A34A) — Success states, sent/delivered status. +- **Signal Amber** (#D97706) — Warnings, pending status, cautions. + +No neon glows, no purple accents, no gradient text, no pure black. + +## 3. Typography Rules + +- **Display / Headlines:** `Geist, "Noto Sans SC", system-ui, sans-serif` — tight tracking, weight-driven hierarchy. H1 24px/700, H2 20px/600, H3 16px/600. +- **Body:** `Geist, "Noto Sans SC", system-ui, sans-serif` — 14px/1.6 line-height for Chinese readability, max 65ch in prose. +- **Mono / Data:** `"Geist Mono", "JetBrains Mono", "SF Mono", monospace` — timestamps, IDs, counts, JSON preview. 13px. +- **Banned:** Inter as the primary font, generic serifs, decorative display fonts. + +Numbers in tables and metrics use monospace for alignment. + +## 4. Component Stylings + +### Buttons +- **Primary:** Signal Blue fill (#2563EB), white text, 6px border-radius, 32px height, 12px horizontal padding. Active: -1px translateY, darker fill (#1D4ED8). No outer glow. +- **Secondary:** White fill, Whisper Border, Charcoal Ink text. Hover: Soft Tint background. +- **Danger:** Signal Red fill or ghost style depending on context. +- **Text/Link buttons:** Signal Blue text, no underline, hover underline. + +### Cards / Sections +- White surface, 1px Whisper Border, 8px border-radius, subtle shadow `0 1px 3px rgba(15, 23, 42, 0.06)`. +- Used to group related controls (filter bar, stats row, form sections). +- In high-density tables, replace nested cards with border-top dividers. + +### Inputs / Forms +- Label above input, 12px/500 Muted Steel. +- Input height 32px, 6px border-radius, Whisper Border, focus ring 2px Signal Blue at 20% opacity. +- Error text below input in Signal Red, 12px. +- No floating labels, no placeholder-only inputs. + +### Tables +- Header: Soft Tint background, 13px/600 Muted Steel uppercase tracking. +- Rows: White, 44px min-height, hover Soft Tint. +- Cell padding: 12px 16px. +- Status tags: small rounded pills with tinted backgrounds. +- Row actions: icon buttons or link buttons grouped at right. + +### Navigation +- Left sidebar: 220px width, White surface, Whisper Border right. +- Menu items: 40px height, 12px/14px text, Signal Blue left-border active indicator, Soft Tint active background. +- Submenu: indented 16px, lighter Muted Steel. +- Top header: 56px height, White surface, bottom Whisper Border, page title left, global search + user avatar right. + +### Drawers / Modals +- Drawer: 560px width from right, White surface, Whisper Border left, 16px padding. +- Modal: 520px max-width, 8px border-radius, centered, backdrop rgba(15,23,42,0.4). +- Close icon top-right. + +### Tags / Pills +- Small rounded pills (12px height, 4px radius) with tinted background. +- Examples: blue tint for active, gray tint for default, green tint for success, red tint for error. + +### Empty States +- Centered composed illustration + headline + single CTA. +- No generic "No data" alone; explain what to do next. + +## 5. Layout Principles + +- **Sidebar + Main:** Fixed left sidebar, main content scrolls. Main has 24px padding. +- **Page Header:** H1 page title + short description + top-right primary action button. +- **Filter Bar:** Collapsible card directly below header; inline form items with 12px gaps. +- **Data Table:** Full-width card below filter bar. +- **Max Width:** None for tables (fluid); prose max-width 720px. +- **Grid:** Use CSS Grid for dashboard cards; tables are full-width. +- **Responsive:** Below 768px, sidebar collapses to hamburger, tables horizontal-scroll with sticky first column, drawers become full-screen. +- **Spacing Scale:** 4px base. Common: 8, 12, 16, 24, 32, 48. + +## 6. Motion & Interaction + +- **Spring default:** `cubic-bezier(0.25, 0.46, 0.45, 0.94)` for UI transitions; 150ms for hovers, 200ms for drawer/modal open. +- **Micro-interactions:** + - Buttons: -1px lift on hover, press-down on active. + - Table rows: background-color transition on hover. + - Status dots: subtle pulse when in "sending" / "online" state. + - Drawers: slide from right with backdrop fade. +- **Loading:** Skeleton shimmer matching exact row dimensions in tables; no generic spinners. +- **Performance:** Animate only `transform` and `opacity`. + +## 7. Anti-Patterns (Banned) + +- No emojis in UI copy or icons. +- No Inter as the primary typeface. +- No pure black (#000000). +- No neon/outer glow shadows. +- No gradient text or hero gradients. +- No 3-column equal feature cards. +- No centered hero sections. +- No AI copywriting clichés: "Seamless", "Elevate", "Unleash", "Next-Gen". +- No filler text like "Scroll to explore" or bouncing arrows. +- No fabricated metrics or fake statistics. +- No overlapping elements — every component has clear spatial separation. +- No custom mouse cursors. +- No broken Unsplash links — use abstract SVG patterns or neutral placeholders. diff --git a/.stitch/designs/sino-mci-prototype.html b/.stitch/designs/sino-mci-prototype.html new file mode 100644 index 0000000..20af8d8 --- /dev/null +++ b/.stitch/designs/sino-mci-prototype.html @@ -0,0 +1,656 @@ + + + + + + Sino-MCI 消息中台 - 完整交互原型 + + + + + + + + + + + + + +
+ +
+
概览
+
+ +
+ + +
+
+
+
+
平台管理员
+
admin@sino-mci
+
+
+
+
+ + +
+ + +
+
+
今日发送消息
12,458
↑ 12% 较昨日
+
今日接收消息
8,932
↑ 8% 较昨日
+
渠道账号在线
24 / 26
2 个离线
+
待处理入站会话
156
需人工跟进
+
+
+
+

最近消息

+ + + + + + +
时间渠道发送方内容摘要状态
10:23企微客户 A我要查询订单状态,订单号是 2025...待处理
10:18个微客户 B[图片] 车牌号 京 A12345已归档
10:05企微客户 C帮我发一份报价单到邮箱处理中
+
+
+

渠道状态

+
+
企业微信主体-01
正常
+
个人微信机器人-A
正常
+
钉钉(预留)
未启用
+
飞书(预留)
未启用
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4f1f38c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,94 @@ +# Sino-MCI 项目约定 + +> 本文件用于固定开发/部署时的关键操作,避免反复踩坑。 + +## 1. Java 与 Maven + +- **必须使用 JDK 21**:`/Library/Java/JavaVirtualMachines/zulu-21.jdk/Contents/Home` +- 项目已配置 Maven Toolchain,**必须在 `backend/` 目录下执行完整 reactor 构建**: + ```bash + cd /Users/marsal/Projects/sino-project/sino-mci/backend + mvn clean install -DskipTests + ``` +- **不要**用 `-pl` 单模块编译,会跳过 `.mvn/maven.config` 中的 toolchains 配置,导致编译报 JDK 17 错误。 +- 当前环境 `java -version` 可能显示 JDK 17,但 Maven 会通过 toolchain 自动切换到 JDK 21,无需手动修改 `JAVA_HOME`。 + +## 2. Docker Compose 项目名(重要) + +Docker Compose 的 `-p` 参数决定容器前缀和命名卷前缀。反复换 `-p` 会产生多个 MySQL 数据卷,导致数据“丢失”的假象。 + +**统一使用项目名 `sino-mci`**: + +```bash +cd /Users/marsal/Projects/sino-project/sino-mci + +# 启动全部基础设施 + 应用 +# 使用 --profile 同时启用 infra 和 app,否则带 profile 的服务不会启动 +docker compose -p sino-mci --profile infra --profile app up -d + +# 只启动基础设施(MySQL/Redis/RabbitMQ/SeaweedFS) +docker compose -p sino-mci --profile infra up -d + +# 只启动应用(mci-server / admin-web / channel-service) +docker compose -p sino-mci --profile app up -d + +# 停止整个项目(不删除数据卷) +docker compose -p sino-mci --profile infra --profile app down +``` + +- 不带 `-p` 时,项目名默认就是目录名 `sino-mci`,与上面等价。 +- **不要**再使用 `-p infrastructure` 或其他名字启动,避免产生新的数据卷。 + +## 3. 数据卷 + +当前项目使用的命名卷: + +- `sino-mci_mci-mysql-data` +- `sino-mci_mci-seaweedfs-data` +- `sino-mci_channel-service-data` + +### 检查当前 MySQL 容器挂载的是哪个卷 + +```bash +docker inspect -f '{{ range .Mounts }}{{ if eq .Destination "/var/lib/mysql" }}{{ .Name }}{{ end }}{{ end }}' mci-mysql +``` + +### 查看所有 sino-mci 相关卷 + +```bash +docker volume ls | grep sino-mci +``` + +### 历史 `infrastructure_*` 卷 + +早期用 `-p infrastructure` 启动过,留下以下卷,**目前未被使用**: + +- `infrastructure_mci-mysql-data` +- `infrastructure_mci-seaweedfs-data` +- `infrastructure_channel-service-data` + +如需迁移/核对其中的数据,请先停止当前 `mci-mysql`,再临时挂载该卷启动容器查询,查完立即恢复。 + +## 4. 常用命令 + +```bash +# 查看服务状态 +docker compose -p sino-mci ps + +# 查看 mci-server 日志 +docker logs -f mci-server --tail 100 + +# 进入 MySQL +docker exec -it mci-mysql mysql -uroot -proot sino_mci + +# 进入 Redis +docker exec -it mci-redis redis-cli + +# 重新构建 mci-server 镜像(先执行 mvn clean install -DskipTests) +docker compose -p sino-mci --profile app build mci-server +``` + +## 5. 环境变量 + +- 复制 `.env.example` 为 `.env` 后再修改本地配置。 +- 默认 root 密码为 `root`,仅本地开发使用。 diff --git a/admin-web/REFACTOR_GUIDE.md b/admin-web/REFACTOR_GUIDE.md new file mode 100644 index 0000000..bc0a1d8 --- /dev/null +++ b/admin-web/REFACTOR_GUIDE.md @@ -0,0 +1,47 @@ +# admin-web 视图重构指南 + +## 目标 +把现有 Element Plus 页面统一为新的 Sino-MCI 设计语言:现代、统一、操作便捷、信息密度高。 + +## 设计参考 +参考原型:`.stitch/designs/sino-mci-prototype.html` + +## 通用规范 + +### 页面结构 +1. 顶部工具栏:左侧放搜索/筛选,右侧放唯一主操作按钮(新增/创建/测试发送等)。 +2. 内容区:白色卡片(`el-card` 或自定义 `.bg-white rounded-xl border`)包裹表格。 +3. 表格: + - 删除无意义复选框; + - 删除每行多个操作按钮; + - 行可点击,点击后从右侧滑出抽屉(`el-drawer`)查看/编辑详情; + - 保留状态开关、标签等必要展示字段; + - 需要时显示分页(`el-pagination`)。 +4. 新增用弹窗(`el-dialog`),编辑用抽屉,保持两者形式统一:标题栏 + 表单 + 底部确认/取消。 + +### 视觉 +- 主色:`#2563EB` +- 背景:`#F8FAFC` +- 卡片/表面:`#FFFFFF`,圆角 12px,边框 `#E2E8F0` +- 文字主色:`#0F172A`,次要:`#64748B` +- 字号:正文 14px,标题 16-18px,辅助 12-13px +- 间距:页面内边距 24px,卡片内部 16-20px + +### 交互 +- 行 hover 变灰(`hover:bg-slate-50` 等效样式)。 +- 按钮层级:主操作 `type="primary"`,次级 `link` 或默认。 +- 删除操作放在抽屉底部或二次确认弹窗中,不要直接显示在列表行。 +- 状态变更尽量使用行内 `el-switch`,切换后调用 API 并刷新列表。 + +### 代码约束 +- 保留原有 API 调用和业务逻辑,只改模板和样式。 +- 使用 ` diff --git a/admin-web/src/components/PageCard.vue b/admin-web/src/components/PageCard.vue new file mode 100644 index 0000000..f2d2e61 --- /dev/null +++ b/admin-web/src/components/PageCard.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/admin-web/src/router/index.ts b/admin-web/src/router/index.ts index 84472df..528791c 100644 --- a/admin-web/src/router/index.ts +++ b/admin-web/src/router/index.ts @@ -2,19 +2,16 @@ import { createRouter, createWebHistory } from 'vue-router' import Layout from '../components/Layout.vue' import Dashboard from '../views/Dashboard.vue' import ChannelSubject from '../views/ChannelSubject.vue' +import ChannelApp from '../views/ChannelApp.vue' import ChannelAccount from '../views/ChannelAccount.vue' -import Conversation from '../views/Conversation.vue' import Person from '../views/Person.vue' +import Conversation from '../views/Conversation.vue' import Tag from '../views/Tag.vue' import SendRecord from '../views/SendRecord.vue' +import Messages from '../views/Messages.vue' import InboundFlow from '../views/InboundFlow.vue' -import IvrFlow from '../views/IvrFlow.vue' -import FlowTrace from '../views/FlowTrace.vue' -import MessageTest from '../views/MessageTest.vue' import TenantManagement from '../views/TenantManagement.vue' import UserManagement from '../views/UserManagement.vue' -import InboundWebhook from '../views/InboundWebhook.vue' -import SendPolicy from '../views/SendPolicy.vue' import Login from '../views/Login.vue' import { useAuthStore } from '../stores/auth' @@ -30,20 +27,17 @@ const routes = [ redirect: '/dashboard', children: [ { path: 'dashboard', component: Dashboard, meta: { title: '概览' } }, - { path: 'channel-subject', component: ChannelSubject, meta: { title: '渠道主体' } }, - { path: 'channel-account', component: ChannelAccount, meta: { title: '渠道账号' } }, - { path: 'conversation', component: Conversation, meta: { title: '会话/群聊' } }, - { path: 'person', component: Person, meta: { title: '联系人' } }, - { path: 'tag', component: Tag, meta: { title: '标签' } }, - { path: 'send-record', component: SendRecord, meta: { title: '发送记录' } }, - { path: 'inbound-flow', component: InboundFlow, meta: { title: '入站流程' } }, - { path: 'ivr-flow', component: IvrFlow, meta: { title: 'IVR 流程' } }, - { path: 'flow-trace', component: FlowTrace, meta: { title: '流程追踪' } }, - { path: 'message-test', component: MessageTest, meta: { title: '消息测试' } }, - { path: 'inbound-webhook', component: InboundWebhook, meta: { title: '入站 Webhook' } }, - { path: 'send-policy', component: SendPolicy, meta: { title: '发送策略' } }, - { path: 'tenant-management', component: TenantManagement, meta: { title: '租户管理' } }, - { path: 'user-management', component: UserManagement, meta: { title: '用户管理' } }, + { path: 'channel/subject', component: ChannelSubject, meta: { title: '渠道主体' } }, + { path: 'channel/app', component: ChannelApp, meta: { title: '渠道应用' } }, + { path: 'channel/account', component: ChannelAccount, meta: { title: '渠道账号' } }, + { path: 'customer/person', component: Person, meta: { title: '联系人' } }, + { path: 'customer/group', component: Conversation, meta: { title: '群聊' } }, + { path: 'customer/tag', component: Tag, meta: { title: '标签体系' } }, + { path: 'message/send-record', component: SendRecord, meta: { title: '发送记录' } }, + { path: 'message/query', component: Messages, meta: { title: '消息查询' } }, + { path: 'flow/inbound', component: InboundFlow, meta: { title: '入站流程' } }, + { path: 'system/tenant', component: TenantManagement, meta: { title: '租户管理' } }, + { path: 'system/user', component: UserManagement, meta: { title: '用户管理' } }, ], }, ] diff --git a/admin-web/src/stores/auth.ts b/admin-web/src/stores/auth.ts index 5f74f6c..6b0bdee 100644 --- a/admin-web/src/stores/auth.ts +++ b/admin-web/src/stores/auth.ts @@ -30,6 +30,11 @@ export const useAuthStore = defineStore('auth', () => { localStorage.setItem('mci-role-codes', JSON.stringify(roles)) } + function setTenant(code: string) { + tenantCode.value = code + localStorage.setItem('mci-tenant-code', code) + } + function clearAuth() { token.value = '' tenantCode.value = '' @@ -41,5 +46,5 @@ export const useAuthStore = defineStore('auth', () => { localStorage.removeItem('mci-role-codes') } - return { token, tenantCode, username, roleCodes, isLoggedIn, setAuth, clearAuth } + return { token, tenantCode, username, roleCodes, isLoggedIn, setAuth, setTenant, clearAuth } }) diff --git a/admin-web/src/style.css b/admin-web/src/style.css index 76a7198..aa68bd0 100644 --- a/admin-web/src/style.css +++ b/admin-web/src/style.css @@ -4,7 +4,7 @@ body { margin: 0; - font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-family: 'Inter', 'Noto Sans SC', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } #app { @@ -12,6 +12,65 @@ body { min-height: 100vh; } +/* Sino-MCI shared page styles */ +.sino-page { + min-height: 100%; +} + +.sino-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 16px; +} + +.sino-toolbar-left, +.sino-toolbar-right { + display: flex; + align-items: center; + gap: 12px; +} + +.sino-card { + background: #ffffff; + border: 1px solid #e2e8f0; + border-radius: 12px; + overflow: hidden; +} + +.sino-pagination { + display: flex; + justify-content: flex-end; + align-items: center; + padding: 12px 20px; + background: #ffffff; + border-top: 1px solid #f1f5f9; +} + +/* Table header unify */ +.sino-table .el-table__header-wrapper th.el-table__cell { + background: #f8fafc !important; + color: #64748b !important; + font-weight: 500 !important; +} + +.sino-table .el-table__row { + cursor: pointer; +} + +/* Status cell */ +.status-cell { + display: flex; + align-items: center; + gap: 8px; +} + +.status-text { + font-size: 13px; + color: #475569; +} + .mt-4 { margin-top: 20px; } diff --git a/admin-web/src/views/ChannelAccount.vue b/admin-web/src/views/ChannelAccount.vue index 0c30cbb..cfb9534 100644 --- a/admin-web/src/views/ChannelAccount.vue +++ b/admin-web/src/views/ChannelAccount.vue @@ -1,10 +1,22 @@ + +
+
+ 初始化 + 同步联系人 + 同步会话 + 刷新状态 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
- + diff --git a/admin-web/src/views/ChannelSubject.vue b/admin-web/src/views/ChannelSubject.vue index ceacfda..7af762c 100644 --- a/admin-web/src/views/ChannelSubject.vue +++ b/admin-web/src/views/ChannelSubject.vue @@ -1,46 +1,64 @@ - - - {{ currentSubject.id }} - {{ channelLabel(currentSubject.channelType) }} - {{ currentSubject.subjectCode }} - {{ currentSubject.subjectName }} - {{ mask(currentSubject.corpId) }} - {{ mask(currentSubject.corpSecret) }} - {{ mask(currentSubject.agentId) }} - {{ mask(currentSubject.agentSecret) }} - {{ currentSubject.sessionArchiveEnabled === 1 ? '开启' : '关闭' }} - {{ currentSubject.status === 1 ? '启用' : '禁用' }} - {{ currentSubject.createTime }} - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/admin-web/src/views/Conversation.vue b/admin-web/src/views/Conversation.vue index 5db3984..b577b3a 100644 --- a/admin-web/src/views/Conversation.vue +++ b/admin-web/src/views/Conversation.vue @@ -1,10 +1,26 @@ - + diff --git a/admin-web/src/views/Dashboard.vue b/admin-web/src/views/Dashboard.vue index fba6dc9..777a75f 100644 --- a/admin-web/src/views/Dashboard.vue +++ b/admin-web/src/views/Dashboard.vue @@ -1,41 +1,50 @@ diff --git a/admin-web/src/views/InboundFlow.vue b/admin-web/src/views/InboundFlow.vue index 834af80..3791f19 100644 --- a/admin-web/src/views/InboundFlow.vue +++ b/admin-web/src/views/InboundFlow.vue @@ -1,209 +1,281 @@ diff --git a/admin-web/src/views/Messages.vue b/admin-web/src/views/Messages.vue new file mode 100644 index 0000000..43cebe5 --- /dev/null +++ b/admin-web/src/views/Messages.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/admin-web/src/views/Person.vue b/admin-web/src/views/Person.vue index 5b8e285..f7fda42 100644 --- a/admin-web/src/views/Person.vue +++ b/admin-web/src/views/Person.vue @@ -1,48 +1,63 @@ - + diff --git a/admin-web/src/views/SendRecord.vue b/admin-web/src/views/SendRecord.vue index ee93354..432a9ad 100644 --- a/admin-web/src/views/SendRecord.vue +++ b/admin-web/src/views/SendRecord.vue @@ -1,14 +1,14 @@