chore: make codes the repository root
This commit is contained in:
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,11 +1,12 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
codes/.env
|
.env
|
||||||
codes/configs/config.local.yml
|
configs/config.local.yml
|
||||||
codes/web/node_modules/
|
web/node_modules/
|
||||||
codes/bin/
|
bin/
|
||||||
codes/coverage.out
|
coverage.out
|
||||||
|
docs/
|
||||||
|
|
||||||
# ---> Go
|
# ---> Go
|
||||||
# If you prefer the allow list template instead of the deny list, see community template:
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
.metric-strip[data-v-233c2a06]{grid-template-columns:repeat(4,1fr);display:grid;overflow:hidden}.metric[data-v-233c2a06]{border-right:1px solid var(--line);align-items:center;gap:16px;min-height:116px;padding:22px;display:flex}.metric[data-v-233c2a06]:last-child{border-right:0}.metric-icon[data-v-233c2a06],.completion[data-v-233c2a06]{border-radius:5px;place-items:center;width:42px;height:42px;font-size:19px;display:grid}.metric-icon.green[data-v-233c2a06]{color:#16775b;background:#e5f3ee}.metric-icon.coral[data-v-233c2a06]{color:#c4573f;background:#faece7}.metric-icon.amber[data-v-233c2a06]{color:#a86e1e;background:#f8efdf}.completion[data-v-233c2a06]{color:#202825;background:#e8ecea;font-size:12px;font-weight:800}.metric div[data-v-233c2a06]{flex-direction:column;display:flex}.metric b[data-v-233c2a06]{font-family:Noto Serif SC,serif;font-size:28px;line-height:1}.metric small[data-v-233c2a06]{color:var(--muted);margin-top:8px}.work-grid[data-v-233c2a06]{grid-template-columns:1.05fr .95fr;gap:18px;margin-top:18px;display:grid}.action-panel[data-v-233c2a06],.doctrine-panel[data-v-233c2a06]{padding:22px}.panel-kicker[data-v-233c2a06]{color:#7c8883;text-transform:uppercase;margin-bottom:14px;font-size:11px;font-weight:700}.action-panel button[data-v-233c2a06]{width:100%;color:var(--ink);text-align:left;border:0;border-bottom:1px solid var(--line);cursor:pointer;background:0 0;justify-content:space-between;align-items:center;padding:15px 0;display:flex}.action-panel button[data-v-233c2a06]:last-child{border-bottom:0}.action-panel button[data-v-233c2a06]:hover{color:var(--green)}.action-panel button span[data-v-233c2a06]{flex-direction:column;font-weight:650;display:flex}.action-panel button small[data-v-233c2a06]{color:var(--muted);margin-top:4px;font-weight:400}.doctrine-panel[data-v-233c2a06]{color:#fff;background:#202825;border-color:#202825}.doctrine-panel .panel-kicker[data-v-233c2a06]{color:#89a099}blockquote[data-v-233c2a06]{margin:30px 0 42px;font-family:Noto Serif SC,serif;font-size:22px;line-height:1.65}.flow-note[data-v-233c2a06]{color:#9cafaa;align-items:center;font-size:12px;display:flex}.flow-note i[data-v-233c2a06]{background:#4a5a54;flex:1;height:1px;margin:0 10px}@media (width<=900px){.metric-strip[data-v-233c2a06]{grid-template-columns:repeat(2,1fr)}.metric[data-v-233c2a06]:nth-child(2){border-right:0}.metric[data-v-233c2a06]:nth-child(-n+2){border-bottom:1px solid var(--line)}.work-grid[data-v-233c2a06]{grid-template-columns:1fr}}@media (width<=520px){.metric[data-v-233c2a06]{min-height:100px;padding:16px}.metric b[data-v-233c2a06]{font-size:24px}.metric-icon[data-v-233c2a06],.completion[data-v-233c2a06]{display:none}}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.execution-shell[data-v-6a3af49b]{max-width:1220px}.sop-catalog[data-v-6a3af49b]{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;display:grid}.sop-entry[data-v-6a3af49b]{text-align:left;cursor:pointer;grid-template-columns:40px 1fr 42px;align-items:start;gap:14px;min-height:150px;padding:22px;display:grid}.sop-entry[data-v-6a3af49b]:hover{border-color:#9ac8b8;box-shadow:0 8px 24px #20282512}.entry-seq[data-v-6a3af49b]{color:#8c9893;font-family:monospace;font-size:12px}.sop-entry small[data-v-6a3af49b]{color:var(--green);font-weight:650}.sop-entry h2[data-v-6a3af49b]{margin:8px 0 7px;font-family:Noto Serif SC,serif;font-size:20px}.sop-entry p[data-v-6a3af49b]{color:var(--muted);margin:0;line-height:1.6}.play[data-v-6a3af49b]{color:#fff;background:#202825;border-radius:4px;place-items:center;width:38px;height:38px;font-size:18px;display:grid}.run-workspace[data-v-6a3af49b]{grid-template-columns:270px minmax(0,1fr);gap:18px;display:grid}.run-context[data-v-6a3af49b]{color:#fff;background:#202825;border-radius:6px;align-self:start;padding:24px;position:sticky;top:86px}.run-label[data-v-6a3af49b]{color:#72c9ab;font-size:11px;font-weight:700}.run-context h2[data-v-6a3af49b]{margin:12px 0 8px;font-family:Noto Serif SC,serif}.run-context>p[data-v-6a3af49b]{color:#aebdb7;margin:0 0 28px;line-height:1.6}.context-meta[data-v-6a3af49b]{border-top:1px solid #3a4742;justify-content:space-between;align-items:center;padding:13px 0;display:flex}.context-meta span[data-v-6a3af49b]{color:#9eada7;font-size:12px}.privacy-note[data-v-6a3af49b]{color:#889b93;gap:8px;margin-top:28px;font-size:11px;display:flex}.conversation[data-v-6a3af49b]{min-height:590px;padding:30px 34px}.conversation-progress[data-v-6a3af49b]{color:#66736d;align-items:center;gap:8px;font-size:12px;display:flex}.conversation-progress span[data-v-6a3af49b]{background:#d08736;border-radius:50%;width:8px;height:8px;box-shadow:0 0 0 4px #faefe1}.conversation-progress span.done[data-v-6a3af49b]{background:var(--green);box-shadow:0 0 0 4px #e4f1ec}.node-content[data-v-6a3af49b]{padding:44px 0 26px}.node-content small[data-v-6a3af49b]{color:var(--green);font-size:10px;font-weight:800}.node-content h1[data-v-6a3af49b]{margin:8px 0 22px;font-family:Noto Serif SC,serif;font-size:28px}.node-content blockquote[data-v-6a3af49b]{color:#29342f;border-left:3px solid var(--green);background:#f2f6f4;margin:0;padding:18px 20px;font-size:17px;line-height:1.8}.answer-form[data-v-6a3af49b]{max-width:680px}.choice-group[data-v-6a3af49b]{flex-wrap:wrap;display:flex}.run-actions[data-v-6a3af49b]{border-top:1px solid var(--line);color:var(--muted);justify-content:space-between;align-items:center;gap:16px;margin:24px -34px -30px;padding:18px 34px;font-size:12px;display:flex}.completed-state[data-v-6a3af49b]{text-align:center;padding:40px 0}.completed-state>span[data-v-6a3af49b]{color:var(--green);font-size:50px}.completed-state h3[data-v-6a3af49b]{margin:14px 0 8px;font-family:Noto Serif SC,serif;font-size:24px}.completed-state p[data-v-6a3af49b]{color:var(--muted);margin:0 0 24px}@media (width<=800px){.sop-catalog[data-v-6a3af49b],.run-workspace[data-v-6a3af49b]{grid-template-columns:1fr}.run-context[data-v-6a3af49b]{position:static}.conversation[data-v-6a3af49b]{padding:22px}.run-actions[data-v-6a3af49b]{margin:24px -22px -22px;padding:16px 22px}.run-actions span[data-v-6a3af49b]{display:none}}
|
|
||||||
1
codes/web/dist/assets/LoginView-BEx5f_cA.css
vendored
1
codes/web/dist/assets/LoginView-BEx5f_cA.css
vendored
@@ -1 +0,0 @@
|
|||||||
.login-page[data-v-265399d7]{background:#f8faf9;grid-template-columns:minmax(420px,1.05fr) minmax(420px,.95fr);min-height:100vh;display:grid}.identity-panel[data-v-265399d7]{color:#fff;background:#202825;flex-direction:column;min-height:640px;padding:42px 54px;display:flex;position:relative;overflow:hidden}.identity-grid[data-v-265399d7]{opacity:.12;background-image:linear-gradient(#b7c4be 1px,#0000 1px),linear-gradient(90deg,#b7c4be 1px,#0000 1px);background-size:44px 44px;position:absolute;inset:0}.brand-lockup[data-v-265399d7]{align-items:center;gap:12px;font-family:Noto Serif SC,serif;font-size:18px;font-weight:700;display:flex;position:relative}.brand-symbol[data-v-265399d7]{border:1px solid #ffffff59;border-radius:5px;grid-template-columns:repeat(3,1fr);align-items:end;gap:3px;width:34px;height:34px;padding:6px;display:grid}.brand-symbol i[data-v-265399d7]{background:#6cc7a8;height:8px;display:block}.brand-symbol i[data-v-265399d7]:nth-child(2){height:15px}.brand-symbol i[data-v-265399d7]:nth-child(3){background:#ed8e6a;height:22px}.identity-copy[data-v-265399d7]{max-width:600px;margin:auto 0;position:relative}.eyebrow[data-v-265399d7]{font-size:12px;font-weight:700;color:#86d3b8!important}.identity-copy h1[data-v-265399d7]{letter-spacing:0;margin:20px 0 24px;font-family:Noto Serif SC,Songti SC,serif;font-size:clamp(38px,4.2vw,64px);line-height:1.2}.identity-copy p[data-v-265399d7]{color:#bdc8c3;max-width:510px;font-size:16px;line-height:1.8}.signal-line[data-v-265399d7]{color:#91a29b;align-items:center;gap:12px;font-size:12px;display:flex;position:relative}.signal-line b[data-v-265399d7]{background:#52615b;width:70px;height:1px}.login-panel[data-v-265399d7]{place-items:center;padding:40px;display:grid}.login-form-wrap[data-v-265399d7]{width:min(100%,390px)}.login-form-wrap h2[data-v-265399d7]{color:#202825;letter-spacing:0;margin:0;font-family:Noto Serif SC,serif;font-size:28px}.login-form-wrap>p[data-v-265399d7]{color:#728079;margin:8px 0 30px}.login-form-wrap[data-v-265399d7] .ant-form-item-label label{color:#46514c;font-weight:600}.login-form-wrap[data-v-265399d7] .ant-input-affix-wrapper{padding:10px 12px}.login-form-wrap[data-v-265399d7] .ant-btn-lg{height:44px;margin-top:8px}.login-note[data-v-265399d7]{color:#8a9691;align-items:center;gap:8px;margin-top:20px;font-size:12px;display:flex}.login-note span[data-v-265399d7]{background:#d5644a;border-radius:50%;width:7px;height:7px}.mobile-brand[data-v-265399d7]{display:none}@media (width<=860px){.login-page[data-v-265399d7]{grid-template-columns:1fr}.identity-panel[data-v-265399d7]{display:none}.login-panel[data-v-265399d7]{min-height:100vh;padding:28px}.mobile-brand[data-v-265399d7]{color:#16775b;margin-bottom:48px;font-family:Noto Serif SC,serif;font-weight:700;display:block}}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
code[data-v-4d911d56]{color:#17654f;background:#eef5f2;border-radius:3px;padding:2px 5px}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.back-link[data-v-a1877f4c]{color:var(--muted);margin:-4px 0 12px -14px}.detail-heading[data-v-a1877f4c]{color:#fff;background:#202825;border-radius:6px;justify-content:space-between;align-items:flex-end;gap:24px;min-height:150px;padding:28px;display:flex}.detail-heading h1[data-v-a1877f4c]{letter-spacing:0;margin:10px 0 8px;font-family:Noto Serif SC,serif;font-size:28px}.detail-heading p[data-v-a1877f4c]{color:#b8c4bf;max-width:720px;margin:0}.heading-tags[data-v-a1877f4c]{color:#a8b6b0;align-items:center;gap:10px;font-size:12px;display:flex}.scenario-code[data-v-a1877f4c]{color:#70817a;align-self:flex-start;font-size:12px}.detail-tabs[data-v-a1877f4c]{margin-top:18px}.toolbar>div[data-v-a1877f4c]{align-items:baseline;gap:12px;display:flex}.toolbar-note[data-v-a1877f4c]{color:var(--muted);font-size:12px}.field-grid[data-v-a1877f4c]{grid-template-columns:1fr 1fr;gap:14px;display:grid}code[data-v-a1877f4c]{color:#17654f;background:#eef5f2;border-radius:3px;padding:2px 5px}.sop-list button[data-v-a1877f4c]{text-align:left;border:0;border-bottom:1px solid var(--line);cursor:pointer;background:#fff;grid-template-columns:42px 1fr auto 100px;align-items:center;gap:14px;width:100%;min-height:74px;padding:12px 18px;display:grid}.sop-list button[data-v-a1877f4c]:hover{background:#f8faf9}.sop-icon[data-v-a1877f4c]{width:38px;height:38px;color:var(--green);background:#e8f3ef;border-radius:4px;place-items:center;display:grid}.sop-copy[data-v-a1877f4c]{flex-direction:column;min-width:0;display:flex}.sop-copy small[data-v-a1877f4c]{color:var(--muted);text-overflow:ellipsis;white-space:nowrap;margin-top:4px;overflow:hidden}.sop-date[data-v-a1877f4c]{color:var(--muted);text-align:right;font-size:12px}.empty-state[data-v-a1877f4c]{color:#82908a;text-align:center;padding:64px 20px}.empty-state>span[data-v-a1877f4c]{font-size:32px}.empty-state p[data-v-a1877f4c]{margin:12px 0 0}@media (width<=650px){.detail-heading[data-v-a1877f4c]{flex-direction:column;align-items:flex-start}.scenario-code[data-v-a1877f4c]{display:none}.field-grid[data-v-a1877f4c]{grid-template-columns:1fr;gap:0}.sop-list button[data-v-a1877f4c]{grid-template-columns:36px 1fr auto}.sop-date[data-v-a1877f4c]{display:none}}
|
|
||||||
@@ -1,493 +0,0 @@
|
|||||||
# 销冠平台技术实现方案
|
|
||||||
|
|
||||||
## 1. 文档范围
|
|
||||||
|
|
||||||
本文档用于指导“场景化销售话术与 SOP 平台”的第一阶段实现。
|
|
||||||
|
|
||||||
第一阶段的核心闭环是:
|
|
||||||
|
|
||||||
```text
|
|
||||||
创建场景 -> 配置自定义字段 -> 编排 SOP -> 审核发布 -> 一线人员执行 -> 记录反馈
|
|
||||||
```
|
|
||||||
|
|
||||||
场景不写死在代码中。宠物医生问诊问药只是一个配置示例,后续可以创建保险咨询、房地产销售、教育课程顾问等其他场景。
|
|
||||||
|
|
||||||
本阶段暂不实现文件存储、AI 自动生成、CRM 集成和微服务拆分。
|
|
||||||
|
|
||||||
## 2. 技术选型
|
|
||||||
|
|
||||||
### 2.1 后端
|
|
||||||
|
|
||||||
- Go:业务服务和流程执行引擎
|
|
||||||
- Gin:HTTP 路由、中间件和请求处理
|
|
||||||
- Zap:结构化日志
|
|
||||||
- cleanenv:YAML 配置解析和环境变量覆盖
|
|
||||||
- GORM:MySQL 数据访问
|
|
||||||
- golang-migrate:数据库迁移
|
|
||||||
- `go-playground/validator`:请求参数校验
|
|
||||||
- JWT + Refresh Token:登录认证
|
|
||||||
|
|
||||||
第一阶段采用模块化单体。所有业务模块运行在一个 Go 服务中,通过清晰的内部边界保持可维护性,后续再根据实际负载拆分服务。
|
|
||||||
|
|
||||||
配置加载使用 `github.com/ilyakaznacheev/cleanenv`。它的行为简单明确:先读取 YAML 文件,再读取环境变量,环境变量值覆盖文件中的同名配置。
|
|
||||||
|
|
||||||
### 2.2 前端
|
|
||||||
|
|
||||||
- Vue 3
|
|
||||||
- Vite
|
|
||||||
- TypeScript
|
|
||||||
- Ant Design Vue
|
|
||||||
- Vue Router
|
|
||||||
- Pinia
|
|
||||||
- Axios
|
|
||||||
|
|
||||||
SOP 编辑器第一版使用步骤卡片和条件配置,不立即引入复杂的画布编辑器。需要图形化节点编排时,使用 Vue 生态的流程图组件,不使用 React Flow。
|
|
||||||
|
|
||||||
### 2.3 数据库和运行环境
|
|
||||||
|
|
||||||
- MySQL 8.0+
|
|
||||||
- Docker Compose:本地开发
|
|
||||||
- Nginx:生产环境反向代理
|
|
||||||
- Linux 容器部署
|
|
||||||
|
|
||||||
本阶段不引入 Redis。登录会话、场景配置和执行状态先使用 MySQL;只有出现异步任务、缓存或高并发需求时再增加 Redis。
|
|
||||||
|
|
||||||
## 3. 配置管理
|
|
||||||
|
|
||||||
配置结构统一放在 `internal/config/`,业务代码不能直接读取环境变量。
|
|
||||||
|
|
||||||
配置优先级如下:
|
|
||||||
|
|
||||||
```text
|
|
||||||
结构体默认值 < config.yml < 当前环境配置文件 < 环境变量
|
|
||||||
```
|
|
||||||
|
|
||||||
配置文件约定如下:
|
|
||||||
|
|
||||||
```text
|
|
||||||
configs/config.example.yml # 配置模板,可提交,不包含敏感信息
|
|
||||||
configs/config.yml # 基础配置
|
|
||||||
configs/config.test.yml # 测试环境覆盖配置
|
|
||||||
configs/config.prod.yml # 生产环境覆盖配置
|
|
||||||
```
|
|
||||||
|
|
||||||
运行环境通过 `APP_ENV` 或启动参数 `--env` 选择,优先级为启动参数高于环境变量:
|
|
||||||
|
|
||||||
```text
|
|
||||||
未指定环境 -> config.yml
|
|
||||||
APP_ENV=test -> config.yml + config.test.yml
|
|
||||||
APP_ENV=prod -> config.yml + config.prod.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
`config.yml` 作为基础文件必须存在。`test` 和 `prod` 环境对应的覆盖文件也必须存在;缺失时服务直接退出。环境配置文件只填写与基础配置不同的字段,未填写的字段沿用 `config.yml`。
|
|
||||||
|
|
||||||
配置文件路径通过 `--config-dir` 指定,默认使用当前工作目录下的 `configs/`。部署时可以挂载外部配置目录,不需要把真实生产配置提交到代码仓库。
|
|
||||||
|
|
||||||
环境变量名称通过结构体标签显式声明,例如:
|
|
||||||
|
|
||||||
```text
|
|
||||||
APP_SERVER_HOST
|
|
||||||
APP_SERVER_PORT
|
|
||||||
APP_DATABASE_HOST
|
|
||||||
APP_DATABASE_PORT
|
|
||||||
APP_DATABASE_USER
|
|
||||||
APP_DATABASE_PASSWORD
|
|
||||||
APP_DATABASE_NAME
|
|
||||||
```
|
|
||||||
|
|
||||||
配置字段约定:
|
|
||||||
|
|
||||||
- `yaml`:YAML 文件中的字段名。
|
|
||||||
- `env`:环境变量名称,必须显式声明。
|
|
||||||
- `env-default`:没有文件值和环境变量时使用的默认值。
|
|
||||||
- `env-required:"true"`:必须由环境变量提供的敏感或关键配置。
|
|
||||||
- `env-prefix`:为嵌套配置统一增加环境变量前缀。
|
|
||||||
- `env-layout`:日期、时间等特殊类型的解析格式。
|
|
||||||
|
|
||||||
生产环境中,数据库密码、JWT 密钥等敏感字段必须使用 `env-required:"true"`,不写入 YAML 文件。配置加载失败或必填项缺失时,服务直接退出,不使用不完整配置继续启动。
|
|
||||||
|
|
||||||
加载过程必须保持以下顺序:先加载基础配置,再加载当前环境配置,最后执行环境变量覆盖。环境变量不能被后续 YAML 文件覆盖。
|
|
||||||
|
|
||||||
## 4. 目录规划
|
|
||||||
|
|
||||||
代码统一放在 `codes/` 目录下。按照 `golang-standards/project-layout` 的组织方式,同时遵循“根目录放置 main.go”和“前端放在 web 目录”的项目约束。
|
|
||||||
|
|
||||||
```text
|
|
||||||
codes/
|
|
||||||
├── main.go # Go 服务入口,位于代码根目录
|
|
||||||
├── go.mod
|
|
||||||
├── go.sum
|
|
||||||
├── internal/
|
|
||||||
│ ├── config/ # 配置加载
|
|
||||||
│ ├── logger/ # Zap 初始化和日志字段规范
|
|
||||||
│ ├── middleware/ # 认证、租户、请求日志、异常恢复
|
|
||||||
│ ├── router/ # 路由注册
|
|
||||||
│ ├── handler/ # HTTP 接口层
|
|
||||||
│ ├── service/ # 业务服务层
|
|
||||||
│ ├── repository/ # 数据访问层
|
|
||||||
│ ├── model/ # 数据库模型和请求响应模型
|
|
||||||
│ ├── engine/ # SOP 流程执行引擎
|
|
||||||
│ └── webassets/ # 前端嵌入和静态资源处理
|
|
||||||
├── pkg/
|
|
||||||
│ └── response/ # 可被外部复用的通用响应结构
|
|
||||||
├── configs/
|
|
||||||
│ ├── config.yml
|
|
||||||
│ ├── config.test.yml
|
|
||||||
│ ├── config.prod.yml
|
|
||||||
│ └── config.example.yml
|
|
||||||
├── migrations/ # MySQL 数据库迁移文件
|
|
||||||
├── scripts/ # 构建、检查和发布脚本
|
|
||||||
└── web/
|
|
||||||
├── package.json
|
|
||||||
├── vite.config.ts
|
|
||||||
├── index.html
|
|
||||||
├── src/
|
|
||||||
│ ├── api/
|
|
||||||
│ ├── components/
|
|
||||||
│ ├── layouts/
|
|
||||||
│ ├── router/
|
|
||||||
│ ├── stores/
|
|
||||||
│ ├── types/
|
|
||||||
│ └── views/
|
|
||||||
└── dist/ # 前端构建产物,由 Go 使用 go:embed 嵌入
|
|
||||||
```
|
|
||||||
|
|
||||||
说明:`cmd/` 目录不使用,Go 入口固定为 `codes/main.go`。`codes/web/dist/` 是构建产物目录,前端构建完成后由 Go 服务作为静态资源提供。
|
|
||||||
|
|
||||||
```text
|
|
||||||
浏览器
|
|
||||||
├── 管理端:场景、字段、SOP、知识卡、审核
|
|
||||||
└── 执行端:按 SOP 执行和记录客户回答
|
|
||||||
|
|
|
||||||
v
|
|
||||||
Go HTTP 服务
|
|
||||||
├── 身份认证与租户权限
|
|
||||||
├── 场景配置服务
|
|
||||||
├── SOP 版本服务
|
|
||||||
├── 流程执行引擎
|
|
||||||
├── 知识卡服务
|
|
||||||
└── 执行记录与统计
|
|
||||||
|
|
|
||||||
v
|
|
||||||
MySQL
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. 系统架构
|
|
||||||
|
|
||||||
Go 服务同时承担 API 服务和前端静态资源服务。前端构建后通过 `go:embed` 打入 Go 二进制,生产环境只需要部署一个服务包和配置文件。
|
|
||||||
|
|
||||||
## 6. go:embed 方案
|
|
||||||
|
|
||||||
前端构建输出到 `codes/web/dist/`,Go 服务通过 `go:embed` 嵌入该目录。
|
|
||||||
|
|
||||||
构建流程为:
|
|
||||||
|
|
||||||
```text
|
|
||||||
进入 codes/web -> 安装依赖 -> 执行前端构建 -> 生成 web/dist
|
|
||||||
-> 回到 codes -> 执行 Go 构建 -> 生成包含前端资源的二进制
|
|
||||||
```
|
|
||||||
|
|
||||||
服务端静态资源处理规则:
|
|
||||||
|
|
||||||
- `/api/` 路径只进入 Go API 路由。
|
|
||||||
- 静态文件路径优先读取 `web/dist` 中对应文件。
|
|
||||||
- 非 API 且找不到文件的路径回退到 `index.html`,支持 Vue Router 的 history 模式。
|
|
||||||
- 前端资源由 Nginx 或应用服务设置长期缓存,`index.html` 不使用长期缓存。
|
|
||||||
|
|
||||||
前端 `dist` 不存在时,Go 项目不能完成编译,因此代码仓库需要保留一个可构建的前端产物占位文件,或者在 CI 中强制先执行前端构建。
|
|
||||||
|
|
||||||
## 7. 场景模型
|
|
||||||
|
|
||||||
场景是平台的一级可配置对象,不能在代码中写死行业字段。
|
|
||||||
|
|
||||||
一个场景至少包含:
|
|
||||||
|
|
||||||
- 场景名称
|
|
||||||
- 所属行业
|
|
||||||
- 适用角色
|
|
||||||
- 使用目标
|
|
||||||
- 触发条件
|
|
||||||
- 可见范围
|
|
||||||
- 自定义字段
|
|
||||||
- 一个或多个 SOP
|
|
||||||
- 关联知识卡
|
|
||||||
- 发布状态
|
|
||||||
|
|
||||||
例如宠物医生问诊问药场景可以配置:
|
|
||||||
|
|
||||||
```text
|
|
||||||
pet_type 宠物种类
|
|
||||||
pet_age 年龄
|
|
||||||
pet_weight 体重
|
|
||||||
symptom 症状
|
|
||||||
symptom_duration 症状持续时间
|
|
||||||
has_emergency_sign 是否存在急症
|
|
||||||
```
|
|
||||||
|
|
||||||
保险咨询场景则可以配置客户年龄、职业、预算和保险类型。新增场景只需要增加配置数据,不需要修改业务代码。
|
|
||||||
|
|
||||||
## 8. SOP 模型
|
|
||||||
|
|
||||||
SOP 由版本、节点和连线组成。
|
|
||||||
|
|
||||||
第一阶段支持以下节点类型:
|
|
||||||
|
|
||||||
- `start`:流程开始
|
|
||||||
- `message`:展示标准话术
|
|
||||||
- `question`:提问并写入一个场景字段
|
|
||||||
- `form`:一次收集多个字段
|
|
||||||
- `choice`:让执行人员选择客户回答
|
|
||||||
- `condition`:根据已收集字段进行分支
|
|
||||||
- `knowledge`:展示已审核知识卡
|
|
||||||
- `escalate`:转人工、转医生或线下处理
|
|
||||||
- `finish`:结束流程
|
|
||||||
|
|
||||||
条件必须保存为结构化规则,不能让用户输入或执行任意 JavaScript。条件规则支持等于、不等于、包含、大于、小于、全部满足、任一满足等操作。
|
|
||||||
|
|
||||||
发布前需要校验:
|
|
||||||
|
|
||||||
- 只有一个开始节点
|
|
||||||
- 所有节点都可从开始节点访问
|
|
||||||
- 所有分支都有出口
|
|
||||||
- 不存在无法结束的路径
|
|
||||||
- 必填字段存在对应采集节点
|
|
||||||
- 关联知识卡已经审核
|
|
||||||
- 急症或高风险分支有明确的转人工或转诊动作
|
|
||||||
|
|
||||||
## 9. MySQL 数据表
|
|
||||||
|
|
||||||
核心表如下:
|
|
||||||
|
|
||||||
```text
|
|
||||||
users 用户
|
|
||||||
tenants 企业或组织
|
|
||||||
tenant_members 企业成员和角色
|
|
||||||
roles 角色权限
|
|
||||||
|
|
||||||
scenarios 场景
|
|
||||||
scenario_fields 场景自定义字段
|
|
||||||
|
|
||||||
sops SOP 基础信息
|
|
||||||
sop_versions SOP 版本
|
|
||||||
sop_nodes SOP 节点
|
|
||||||
sop_edges SOP 连线和条件
|
|
||||||
|
|
||||||
knowledge_cards 话术或知识卡
|
|
||||||
knowledge_card_versions 知识卡版本
|
|
||||||
|
|
||||||
sop_runs SOP 执行实例
|
|
||||||
sop_run_events SOP 节点执行事件
|
|
||||||
sop_feedback 执行反馈
|
|
||||||
audit_logs 操作审计
|
|
||||||
```
|
|
||||||
|
|
||||||
关键设计:
|
|
||||||
|
|
||||||
- 所有租户相关数据必须能追溯到 `tenant_id`。
|
|
||||||
- 场景字段使用行记录保存,避免把场景字段写死在表结构中。
|
|
||||||
- 节点配置和条件规则使用 MySQL JSON 字段保存。
|
|
||||||
- 已发布版本不可直接修改,修改时复制为新的草稿版本。
|
|
||||||
- 版本状态流转为 `draft -> reviewing -> published -> offline`;管理员也可以从草稿直接发布。
|
|
||||||
- 执行过程保存事件记录,便于复盘和统计。
|
|
||||||
- 需要参与筛选和统计的字段不能只存 JSON,应在事件表或统计表中建立结构化字段。
|
|
||||||
|
|
||||||
## 10. 后端接口规划
|
|
||||||
|
|
||||||
### 场景接口
|
|
||||||
|
|
||||||
```text
|
|
||||||
POST /api/v1/scenarios
|
|
||||||
GET /api/v1/scenarios
|
|
||||||
GET /api/v1/scenarios/:id
|
|
||||||
PUT /api/v1/scenarios/:id
|
|
||||||
DELETE /api/v1/scenarios/:id
|
|
||||||
```
|
|
||||||
|
|
||||||
### 自定义字段接口
|
|
||||||
|
|
||||||
```text
|
|
||||||
POST /api/v1/scenarios/:id/fields
|
|
||||||
PUT /api/v1/scenario-fields/:id
|
|
||||||
DELETE /api/v1/scenario-fields/:id
|
|
||||||
```
|
|
||||||
|
|
||||||
### SOP 接口
|
|
||||||
|
|
||||||
```text
|
|
||||||
POST /api/v1/scenarios/:id/sops
|
|
||||||
GET /api/v1/scenarios/:id/sops
|
|
||||||
PUT /api/v1/sops/:id/draft
|
|
||||||
POST /api/v1/sops/:id/validate
|
|
||||||
POST /api/v1/sops/:id/submit-review
|
|
||||||
POST /api/v1/sops/:id/publish
|
|
||||||
POST /api/v1/sops/:id/offline
|
|
||||||
```
|
|
||||||
|
|
||||||
### SOP 执行接口
|
|
||||||
|
|
||||||
```text
|
|
||||||
POST /api/v1/runs
|
|
||||||
GET /api/v1/runs/:id
|
|
||||||
POST /api/v1/runs/:id/answer
|
|
||||||
POST /api/v1/runs/:id/finish
|
|
||||||
POST /api/v1/runs/:id/feedback
|
|
||||||
```
|
|
||||||
|
|
||||||
## 11. 流程执行引擎
|
|
||||||
|
|
||||||
执行流程如下:
|
|
||||||
|
|
||||||
1. 创建执行实例,绑定一个已发布的 SOP 版本。
|
|
||||||
2. 加载开始节点并返回给前端。
|
|
||||||
3. 前端展示话术、表单或选项。
|
|
||||||
4. 后端校验客户回答和字段类型。
|
|
||||||
5. 在事务中保存回答和节点事件。
|
|
||||||
6. 根据结构化条件计算下一节点。
|
|
||||||
7. 返回下一节点或结束动作。
|
|
||||||
|
|
||||||
流程引擎只处理通用节点和规则,不理解“宠物”“保险”等具体业务。行业知识放在场景字段、话术内容和知识卡中。
|
|
||||||
|
|
||||||
## 12. Zap 日志规范
|
|
||||||
|
|
||||||
使用 Zap 输出结构化 JSON 日志到 stdout,由 Docker、Kubernetes 或云平台采集,不由应用自己维护日志文件。
|
|
||||||
|
|
||||||
日志至少包含:
|
|
||||||
|
|
||||||
- `service`
|
|
||||||
- `env`
|
|
||||||
- `request_id`
|
|
||||||
- `tenant_id`
|
|
||||||
- `user_id`
|
|
||||||
- `action`
|
|
||||||
- `resource`
|
|
||||||
- `resource_id`
|
|
||||||
- `duration_ms`
|
|
||||||
- `err`
|
|
||||||
|
|
||||||
日志等级约定:
|
|
||||||
|
|
||||||
- `DEBUG`:本地开发细节
|
|
||||||
- `INFO`:正常请求、发布、执行等业务事件
|
|
||||||
- `WARN`:参数异常、权限拒绝、客户端错误
|
|
||||||
- `ERROR`:服务异常、数据库失败、流程执行失败
|
|
||||||
|
|
||||||
禁止记录密码、Token、完整客户聊天内容和未脱敏的个人信息。
|
|
||||||
|
|
||||||
Gin 请求日志中需要记录请求方法、路径、状态码、耗时和 Request ID。业务日志使用结构化字段,不使用字符串拼接。
|
|
||||||
|
|
||||||
## 13. 前端页面规划
|
|
||||||
|
|
||||||
### 管理端
|
|
||||||
|
|
||||||
- 场景列表
|
|
||||||
- 创建/编辑场景
|
|
||||||
- 自定义字段配置
|
|
||||||
- SOP 列表
|
|
||||||
- SOP 步骤编辑器
|
|
||||||
- 节点条件配置
|
|
||||||
- 知识卡管理
|
|
||||||
- 审核发布中心
|
|
||||||
- 执行数据看板
|
|
||||||
|
|
||||||
### 执行端
|
|
||||||
|
|
||||||
- 场景选择
|
|
||||||
- SOP 当前节点
|
|
||||||
- 客户信息录入
|
|
||||||
- 标准话术展示
|
|
||||||
- 分支选择
|
|
||||||
- 转人工或转诊提醒
|
|
||||||
- 执行结果和反馈
|
|
||||||
|
|
||||||
Ant Design Vue 主要使用 `Form`、`Table`、`Drawer`、`Modal`、`Steps`、`Tree`、`Tabs` 和 `Descriptions`。动态字段表单由场景字段配置生成,字段校验规则也由配置生成。
|
|
||||||
|
|
||||||
## 14. 安全与权限
|
|
||||||
|
|
||||||
- 用户登录后从 Token 中获取 `tenant_id`,不能信任前端传入的租户 ID。
|
|
||||||
- 所有查询必须自动附加租户条件。
|
|
||||||
- 场景、SOP、知识卡、执行记录都需要做资源级权限判断。
|
|
||||||
- 发布、下线、审核和修改权限分离。
|
|
||||||
- 已发布 SOP 版本只读,修改时复制为新的草稿版本。
|
|
||||||
- 记录关键操作到 `audit_logs`。
|
|
||||||
- 条件规则禁止执行任意代码。
|
|
||||||
|
|
||||||
## 15. 第一阶段开发顺序
|
|
||||||
|
|
||||||
### 第 1 阶段:基础服务
|
|
||||||
|
|
||||||
- Go 服务初始化
|
|
||||||
- Zap 日志
|
|
||||||
- 配置加载
|
|
||||||
- MySQL 连接
|
|
||||||
- 登录和租户权限
|
|
||||||
- 前端构建和 go:embed
|
|
||||||
|
|
||||||
### 第 2 阶段:场景配置
|
|
||||||
|
|
||||||
- 场景 CRUD
|
|
||||||
- 自定义字段 CRUD
|
|
||||||
- 场景权限
|
|
||||||
- 场景状态管理
|
|
||||||
|
|
||||||
### 第 3 阶段:SOP 编排
|
|
||||||
|
|
||||||
- 节点管理
|
|
||||||
- 连线和条件配置
|
|
||||||
- 草稿保存
|
|
||||||
- 流程校验
|
|
||||||
- 版本复制
|
|
||||||
|
|
||||||
### 第 4 阶段:SOP 执行
|
|
||||||
|
|
||||||
- 创建执行实例
|
|
||||||
- 节点展示
|
|
||||||
- 回答提交
|
|
||||||
- 条件跳转
|
|
||||||
- 执行事件记录
|
|
||||||
|
|
||||||
### 第 5 阶段:审核和反馈
|
|
||||||
|
|
||||||
- 提交审核
|
|
||||||
- 审核发布
|
|
||||||
- 下线和回滚
|
|
||||||
- 执行反馈
|
|
||||||
- 基础数据统计
|
|
||||||
|
|
||||||
## 16. 构建和部署约定
|
|
||||||
|
|
||||||
首次初始化本地 MySQL 数据库时执行:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd codes
|
|
||||||
mysql -uroot -p < scripts/create-databases.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
服务启动时会自动执行 `migrations/` 中的迁移,创建业务表并记录 `schema_migrations` 版本。
|
|
||||||
|
|
||||||
本地开发时前后端可以分别启动:
|
|
||||||
|
|
||||||
```text
|
|
||||||
前端开发服务:codes/web
|
|
||||||
Go API 服务:codes
|
|
||||||
```
|
|
||||||
|
|
||||||
生产构建时先构建前端,再构建 Go 服务。最终产物只需要:
|
|
||||||
|
|
||||||
- Go 二进制
|
|
||||||
- 配置文件
|
|
||||||
- MySQL 数据库
|
|
||||||
|
|
||||||
前端静态资源已经通过 `go:embed` 编译进入 Go 二进制,不需要单独部署前端静态目录。
|
|
||||||
|
|
||||||
## 17. 暂不实现的内容
|
|
||||||
|
|
||||||
- 文件上传和对象存储
|
|
||||||
- AI 自动提炼话术
|
|
||||||
- AI 客户模拟陪练
|
|
||||||
- CRM、企业微信和客服系统集成
|
|
||||||
- 多服务拆分
|
|
||||||
- Redis 缓存
|
|
||||||
- 消息队列
|
|
||||||
- 复杂 BI 分析
|
|
||||||
|
|
||||||
这些内容等核心闭环验证成功后再加入,避免第一版架构过度复杂。
|
|
||||||
@@ -1 +1 @@
|
|||||||
import{Bt as e,F as t,I as n,O as r,P as i,Q as a,Tt as o,Y as s,i as c,j as l,lt as u,r as d,t as f,tt as p,vt as m}from"./client-CO11mUW5.js";import{t as h}from"./PlusOutlined-5Urx-Evx.js";import{n as g,t as _}from"./AppstoreOutlined-BOx6VBGQ.js";import{t as v}from"./PlayCircleOutlined-C2UGH7h6.js";import{h as y}from"./useApi-CROJJdhE-BlzMTLF9.js";var b={class:`page-shell`},x={class:`page-heading`},S={class:`page-actions`},C={class:`metric-strip surface`},w={class:`metric`},T={class:`metric-icon green`},E={class:`metric`},D={class:`metric-icon coral`},O={class:`metric`},k={class:`metric-icon amber`},A={class:`metric`},j={class:`completion`},M={class:`work-grid`},N={class:`surface action-panel`},P=d(n({__name:`DashboardView`,setup(n){let d=y(),P=m(!0),F=m({scenarios:0,published_sops:0,runs:0,completed_runs:0});return s(async()=>{try{F.value=await f.get(`/dashboard/summary`)}finally{P.value=!1}}),(n,s)=>{let f=p(`a-button`),m=p(`a-skeleton`);return a(),l(`div`,b,[r(`div`,x,[s[5]||=r(`div`,null,[r(`h1`,null,`今天从经验开始`),r(`p`,null,`查看知识沉淀和一线执行的最新状态。`)],-1),r(`div`,S,[t(f,{type:`primary`,onClick:s[0]||=e=>o(d).push(`/scenarios`)},{default:u(()=>[t(o(h)),s[4]||=i(`创建场景`,-1)]),_:1})])]),t(m,{loading:P.value,active:``},{default:u(()=>[r(`section`,C,[r(`div`,w,[r(`span`,T,[t(o(_))]),r(`div`,null,[r(`b`,null,e(F.value.scenarios),1),s[6]||=r(`small`,null,`可用场景`,-1)])]),r(`div`,E,[r(`span`,D,[t(o(c))]),r(`div`,null,[r(`b`,null,e(F.value.published_sops),1),s[7]||=r(`small`,null,`已发布 SOP`,-1)])]),r(`div`,O,[r(`span`,k,[t(o(v))]),r(`div`,null,[r(`b`,null,e(F.value.runs),1),s[8]||=r(`small`,null,`累计执行`,-1)])]),r(`div`,A,[r(`span`,j,e(F.value.runs?Math.round(F.value.completed_runs/F.value.runs*100):0)+`%`,1),r(`div`,null,[r(`b`,null,e(F.value.completed_runs),1),s[9]||=r(`small`,null,`完成执行`,-1)])])])]),_:1},8,[`loading`]),r(`section`,M,[r(`div`,N,[s[13]||=r(`div`,{class:`panel-kicker`},`常用入口`,-1),r(`button`,{onClick:s[1]||=e=>o(d).push(`/scenarios`)},[s[10]||=r(`span`,null,[i(`配置新的业务场景`),r(`small`,null,`定义字段、目标和触发条件`)],-1),t(o(g))]),r(`button`,{onClick:s[2]||=e=>o(d).push(`/execute`)},[s[11]||=r(`span`,null,[i(`开始执行已发布 SOP`),r(`small`,null,`根据客户回答逐步推进`)],-1),t(o(g))]),r(`button`,{onClick:s[3]||=e=>o(d).push(`/knowledge`)},[s[12]||=r(`span`,null,[i(`维护审核知识卡`),r(`small`,null,`统一口径与风险提示`)],-1),t(o(g))])]),s[14]||=r(`div`,{class:`surface doctrine-panel`},[r(`div`,{class:`panel-kicker`},`平台原则`),r(`blockquote`,null,`一句好话术不应只被收藏,它应该知道何时出现、下一步去哪,以及是否真的有效。`),r(`div`,{class:`flow-note`},[r(`span`,null,`创建`),r(`i`),r(`span`,null,`发布`),r(`i`),r(`span`,null,`执行`),r(`i`),r(`span`,null,`复盘`)])],-1)])])}}}),[[`__scopeId`,`data-v-233c2a06`]]);export{P as default};
|
import{Bt as e,F as t,I as n,O as r,P as i,Q as a,Tt as o,Y as s,i as c,j as l,lt as u,r as d,t as f,tt as p,vt as m}from"./client-CO11mUW5.js";import{t as h}from"./PlusOutlined-5Urx-Evx.js";import{n as g,t as _}from"./AppstoreOutlined-BOx6VBGQ.js";import{t as v}from"./PlayCircleOutlined-C2UGH7h6.js";import{h as y}from"./useApi-CROJJdhE-BlzMTLF9.js";var b={class:`page-shell`},x={class:`page-heading`},S={class:`page-actions`},C={class:`metric-strip surface`},w={class:`metric`},T={class:`metric-icon green`},E={class:`metric`},D={class:`metric-icon coral`},O={class:`metric`},k={class:`metric-icon amber`},A={class:`metric`},j={class:`completion`},M={class:`work-grid`},N={class:`surface action-panel`},P=d(n({__name:`DashboardView`,setup(n){let d=y(),P=m(!0),F=m({scenarios:0,published_sops:0,runs:0,completed_runs:0});return s(async()=>{try{F.value=await f.get(`/dashboard/summary`)}finally{P.value=!1}}),(n,s)=>{let f=p(`a-button`),m=p(`a-skeleton`);return a(),l(`div`,b,[r(`div`,x,[s[5]||=r(`div`,null,[r(`h1`,null,`今天从经验开始`),r(`p`,null,`查看知识沉淀和一线执行的最新状态。`)],-1),r(`div`,S,[t(f,{type:`primary`,onClick:s[0]||=e=>o(d).push(`/scenarios`)},{default:u(()=>[t(o(h)),s[4]||=i(`创建场景`,-1)]),_:1})])]),t(m,{loading:P.value,active:``},{default:u(()=>[r(`section`,C,[r(`div`,w,[r(`span`,T,[t(o(_))]),r(`div`,null,[r(`b`,null,e(F.value.scenarios),1),s[6]||=r(`small`,null,`可用场景`,-1)])]),r(`div`,E,[r(`span`,D,[t(o(c))]),r(`div`,null,[r(`b`,null,e(F.value.published_sops),1),s[7]||=r(`small`,null,`已发布 SOP`,-1)])]),r(`div`,O,[r(`span`,k,[t(o(v))]),r(`div`,null,[r(`b`,null,e(F.value.runs),1),s[8]||=r(`small`,null,`累计执行`,-1)])]),r(`div`,A,[r(`span`,j,e(F.value.runs?Math.round(F.value.completed_runs/F.value.runs*100):0)+`%`,1),r(`div`,null,[r(`b`,null,e(F.value.completed_runs),1),s[9]||=r(`small`,null,`完成执行`,-1)])])])]),_:1},8,[`loading`]),r(`section`,M,[r(`div`,N,[s[13]||=r(`div`,{class:`panel-kicker`},`常用入口`,-1),r(`button`,{onClick:s[1]||=e=>o(d).push(`/scenarios`)},[s[10]||=r(`span`,null,[i(`配置新的业务场景`),r(`small`,null,`定义字段、目标和触发条件`)],-1),t(o(g))]),r(`button`,{onClick:s[2]||=e=>o(d).push(`/execute`)},[s[11]||=r(`span`,null,[i(`开始执行已发布 SOP`),r(`small`,null,`根据客户回答逐步推进`)],-1),t(o(g))]),r(`button`,{onClick:s[3]||=e=>o(d).push(`/knowledge`)},[s[12]||=r(`span`,null,[i(`维护审核知识卡`),r(`small`,null,`统一口径与风险提示`)],-1),t(o(g))])]),s[14]||=r(`div`,{class:`surface doctrine-panel`},[r(`div`,{class:`panel-kicker`},`平台原则`),r(`blockquote`,null,`一句好话术不应只被收藏,它应该知道何时出现、下一步去哪,以及是否真的有效。`),r(`div`,{class:`flow-note`},[r(`span`,null,`创建`),r(`i`),r(`span`,null,`发布`),r(`i`),r(`span`,null,`执行`),r(`i`),r(`span`,null,`复盘`)])],-1)])])}}}),[[`__scopeId`,`data-v-911c7dcb`]]);export{P as default};
|
||||||
1
web/dist/assets/DashboardView-S5ziOJC3.css
vendored
Normal file
1
web/dist/assets/DashboardView-S5ziOJC3.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.metric-strip[data-v-911c7dcb]{grid-template-columns:repeat(4,1fr);display:grid;overflow:hidden}.metric[data-v-911c7dcb]{border-right:1px solid var(--line);align-items:center;gap:16px;min-height:116px;padding:22px;display:flex}.metric[data-v-911c7dcb]:last-child{border-right:0}.metric-icon[data-v-911c7dcb],.completion[data-v-911c7dcb]{border-radius:5px;place-items:center;width:42px;height:42px;font-size:19px;display:grid}.metric-icon.green[data-v-911c7dcb]{color:#16775b;background:#e5f3ee}.metric-icon.coral[data-v-911c7dcb]{color:#c4573f;background:#faece7}.metric-icon.amber[data-v-911c7dcb]{color:#a86e1e;background:#f8efdf}.completion[data-v-911c7dcb]{color:#202825;background:#e8ecea;font-size:12px;font-weight:800}.metric div[data-v-911c7dcb]{flex-direction:column;display:flex}.metric b[data-v-911c7dcb]{font-family:Noto Serif SC,serif;font-size:28px;line-height:1}.metric small[data-v-911c7dcb]{color:var(--muted);margin-top:8px}.work-grid[data-v-911c7dcb]{grid-template-columns:1.05fr .95fr;gap:18px;margin-top:18px;display:grid}.action-panel[data-v-911c7dcb],.doctrine-panel[data-v-911c7dcb]{padding:22px}.panel-kicker[data-v-911c7dcb]{color:#7c8883;text-transform:uppercase;margin-bottom:14px;font-size:11px;font-weight:700}.action-panel button[data-v-911c7dcb]{width:100%;color:var(--ink);text-align:left;border:0;border-bottom:1px solid var(--line);cursor:pointer;background:0 0;justify-content:space-between;align-items:center;padding:15px 0;display:flex}.action-panel button[data-v-911c7dcb]:last-child{border-bottom:0}.action-panel button[data-v-911c7dcb]:hover{color:var(--green)}.action-panel button span[data-v-911c7dcb]{flex-direction:column;font-weight:650;display:flex}.action-panel button small[data-v-911c7dcb]{color:var(--muted);margin-top:4px;font-weight:400}.doctrine-panel[data-v-911c7dcb]{color:#fff;background:#202825;border-color:#202825}.doctrine-panel .panel-kicker[data-v-911c7dcb]{color:#89a099}blockquote[data-v-911c7dcb]{margin:30px 0 42px;font-family:Noto Serif SC,serif;font-size:22px;line-height:1.65}.flow-note[data-v-911c7dcb]{color:#9cafaa;align-items:center;font-size:12px;display:flex}.flow-note i[data-v-911c7dcb]{background:#4a5a54;flex:1;height:1px;margin:0 10px}@media (width<=900px){.metric-strip[data-v-911c7dcb]{grid-template-columns:repeat(2,1fr)}.metric[data-v-911c7dcb]:nth-child(2){border-right:0}.metric[data-v-911c7dcb]:nth-child(-n+2){border-bottom:1px solid var(--line)}.work-grid[data-v-911c7dcb]{grid-template-columns:1fr}}@media (width<=520px){.metric[data-v-911c7dcb]{min-height:100px;padding:16px}.metric b[data-v-911c7dcb]{font-size:24px}.metric-icon[data-v-911c7dcb],.completion[data-v-911c7dcb]{display:none}}
|
||||||
File diff suppressed because one or more lines are too long
1
web/dist/assets/ExecuteView-iUcBfH4l.css
vendored
Normal file
1
web/dist/assets/ExecuteView-iUcBfH4l.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.execution-shell[data-v-d94f7da9]{max-width:1220px}.sop-catalog[data-v-d94f7da9]{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;display:grid}.sop-entry[data-v-d94f7da9]{text-align:left;cursor:pointer;grid-template-columns:40px 1fr 42px;align-items:start;gap:14px;min-height:150px;padding:22px;display:grid}.sop-entry[data-v-d94f7da9]:hover{border-color:#9ac8b8;box-shadow:0 8px 24px #20282512}.entry-seq[data-v-d94f7da9]{color:#8c9893;font-family:monospace;font-size:12px}.sop-entry small[data-v-d94f7da9]{color:var(--green);font-weight:650}.sop-entry h2[data-v-d94f7da9]{margin:8px 0 7px;font-family:Noto Serif SC,serif;font-size:20px}.sop-entry p[data-v-d94f7da9]{color:var(--muted);margin:0;line-height:1.6}.play[data-v-d94f7da9]{color:#fff;background:#202825;border-radius:4px;place-items:center;width:38px;height:38px;font-size:18px;display:grid}.run-workspace[data-v-d94f7da9]{grid-template-columns:270px minmax(0,1fr);gap:18px;display:grid}.run-context[data-v-d94f7da9]{color:#fff;background:#202825;border-radius:6px;align-self:start;padding:24px;position:sticky;top:86px}.run-label[data-v-d94f7da9]{color:#72c9ab;font-size:11px;font-weight:700}.run-context h2[data-v-d94f7da9]{margin:12px 0 8px;font-family:Noto Serif SC,serif}.run-context>p[data-v-d94f7da9]{color:#aebdb7;margin:0 0 28px;line-height:1.6}.context-meta[data-v-d94f7da9]{border-top:1px solid #3a4742;justify-content:space-between;align-items:center;padding:13px 0;display:flex}.context-meta span[data-v-d94f7da9]{color:#9eada7;font-size:12px}.privacy-note[data-v-d94f7da9]{color:#889b93;gap:8px;margin-top:28px;font-size:11px;display:flex}.conversation[data-v-d94f7da9]{min-height:590px;padding:30px 34px}.conversation-progress[data-v-d94f7da9]{color:#66736d;align-items:center;gap:8px;font-size:12px;display:flex}.conversation-progress span[data-v-d94f7da9]{background:#d08736;border-radius:50%;width:8px;height:8px;box-shadow:0 0 0 4px #faefe1}.conversation-progress span.done[data-v-d94f7da9]{background:var(--green);box-shadow:0 0 0 4px #e4f1ec}.node-content[data-v-d94f7da9]{padding:44px 0 26px}.node-content small[data-v-d94f7da9]{color:var(--green);font-size:10px;font-weight:800}.node-content h1[data-v-d94f7da9]{margin:8px 0 22px;font-family:Noto Serif SC,serif;font-size:28px}.node-content blockquote[data-v-d94f7da9]{color:#29342f;border-left:3px solid var(--green);background:#f2f6f4;margin:0;padding:18px 20px;font-size:17px;line-height:1.8}.answer-form[data-v-d94f7da9]{max-width:680px}.choice-group[data-v-d94f7da9]{flex-wrap:wrap;display:flex}.run-actions[data-v-d94f7da9]{border-top:1px solid var(--line);color:var(--muted);justify-content:space-between;align-items:center;gap:16px;margin:24px -34px -30px;padding:18px 34px;font-size:12px;display:flex}.completed-state[data-v-d94f7da9]{text-align:center;padding:40px 0}.completed-state>span[data-v-d94f7da9]{color:var(--green);font-size:50px}.completed-state h3[data-v-d94f7da9]{margin:14px 0 8px;font-family:Noto Serif SC,serif;font-size:24px}.completed-state p[data-v-d94f7da9]{color:var(--muted);margin:0 0 24px}@media (width<=800px){.sop-catalog[data-v-d94f7da9],.run-workspace[data-v-d94f7da9]{grid-template-columns:1fr}.run-context[data-v-d94f7da9]{position:static}.conversation[data-v-d94f7da9]{padding:22px}.run-actions[data-v-d94f7da9]{margin:24px -22px -22px;padding:16px 22px}.run-actions span[data-v-d94f7da9]{display:none}}
|
||||||
1
web/dist/assets/LoginView-BYK68e31.css
vendored
Normal file
1
web/dist/assets/LoginView-BYK68e31.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.login-page[data-v-b1da8a6b]{background:#f8faf9;grid-template-columns:minmax(420px,1.05fr) minmax(420px,.95fr);min-height:100vh;display:grid}.identity-panel[data-v-b1da8a6b]{color:#fff;background:#202825;flex-direction:column;min-height:640px;padding:42px 54px;display:flex;position:relative;overflow:hidden}.identity-grid[data-v-b1da8a6b]{opacity:.12;background-image:linear-gradient(#b7c4be 1px,#0000 1px),linear-gradient(90deg,#b7c4be 1px,#0000 1px);background-size:44px 44px;position:absolute;inset:0}.brand-lockup[data-v-b1da8a6b]{align-items:center;gap:12px;font-family:Noto Serif SC,serif;font-size:18px;font-weight:700;display:flex;position:relative}.brand-symbol[data-v-b1da8a6b]{border:1px solid #ffffff59;border-radius:5px;grid-template-columns:repeat(3,1fr);align-items:end;gap:3px;width:34px;height:34px;padding:6px;display:grid}.brand-symbol i[data-v-b1da8a6b]{background:#6cc7a8;height:8px;display:block}.brand-symbol i[data-v-b1da8a6b]:nth-child(2){height:15px}.brand-symbol i[data-v-b1da8a6b]:nth-child(3){background:#ed8e6a;height:22px}.identity-copy[data-v-b1da8a6b]{max-width:600px;margin:auto 0;position:relative}.eyebrow[data-v-b1da8a6b]{font-size:12px;font-weight:700;color:#86d3b8!important}.identity-copy h1[data-v-b1da8a6b]{letter-spacing:0;margin:20px 0 24px;font-family:Noto Serif SC,Songti SC,serif;font-size:clamp(38px,4.2vw,64px);line-height:1.2}.identity-copy p[data-v-b1da8a6b]{color:#bdc8c3;max-width:510px;font-size:16px;line-height:1.8}.signal-line[data-v-b1da8a6b]{color:#91a29b;align-items:center;gap:12px;font-size:12px;display:flex;position:relative}.signal-line b[data-v-b1da8a6b]{background:#52615b;width:70px;height:1px}.login-panel[data-v-b1da8a6b]{place-items:center;padding:40px;display:grid}.login-form-wrap[data-v-b1da8a6b]{width:min(100%,390px)}.login-form-wrap h2[data-v-b1da8a6b]{color:#202825;letter-spacing:0;margin:0;font-family:Noto Serif SC,serif;font-size:28px}.login-form-wrap>p[data-v-b1da8a6b]{color:#728079;margin:8px 0 30px}.login-form-wrap[data-v-b1da8a6b] .ant-form-item-label label{color:#46514c;font-weight:600}.login-form-wrap[data-v-b1da8a6b] .ant-input-affix-wrapper{padding:10px 12px}.login-form-wrap[data-v-b1da8a6b] .ant-btn-lg{height:44px;margin-top:8px}.login-note[data-v-b1da8a6b]{color:#8a9691;align-items:center;gap:8px;margin-top:20px;font-size:12px;display:flex}.login-note span[data-v-b1da8a6b]{background:#d5644a;border-radius:50%;width:7px;height:7px}.mobile-brand[data-v-b1da8a6b]{display:none}@media (width<=860px){.login-page[data-v-b1da8a6b]{grid-template-columns:1fr}.identity-panel[data-v-b1da8a6b]{display:none}.login-panel[data-v-b1da8a6b]{min-height:100vh;padding:28px}.mobile-brand[data-v-b1da8a6b]{color:#16775b;margin-bottom:48px;font-family:Noto Serif SC,serif;font-weight:700;display:block}}
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
import{A as e,Bt as t,F as n,I as r,O as i,P as a,Q as o,S as s,Y as c,j as l,k as u,lt as d,n as f,r as p,t as m,tt as h,vt as g}from"./client-CO11mUW5.js";import{a as _}from"./config-provider-kwhtQ-D4.js";var v={class:`page-shell`},y={class:`surface`},b={key:0},x=p(r({__name:`RunHistoryView`,setup(r){let p=g(!1),x=g([]);async function S(){p.value=!0;try{x.value=(await m.get(`/runs`)).items}catch(e){_.error(f(e))}finally{p.value=!1}}function C(e){return e===`completed`?`已完成`:`进行中`}return c(S),(r,c)=>{let f=h(`a-tag`),m=h(`a-table`);return o(),l(`div`,v,[c[1]||=i(`div`,{class:`page-heading`},[i(`div`,null,[i(`h1`,null,`执行记录`),i(`p`,null,`查看 SOP 的实际使用情况和执行结果。`)])],-1),i(`section`,y,[n(m,{"row-key":`id`,loading:p.value,"data-source":x.value,pagination:{pageSize:20},columns:[{title:`执行编号`,dataIndex:`id`,width:130},{title:`SOP`,dataIndex:`sop_name`},{title:`状态`,dataIndex:`status`,width:110},{title:`结果`,dataIndex:`result`,width:120},{title:`开始时间`,dataIndex:`started_at`,width:190},{title:`完成时间`,dataIndex:`completed_at`,width:190}],scroll:{x:900}},{bodyCell:d(({column:n,record:r})=>[n.dataIndex===`id`?(o(),l(`code`,b,`RUN-`+t(String(r.id).padStart(5,`0`)),1)):n.dataIndex===`status`?(o(),u(f,{key:1,color:r.status===`completed`?`green`:`orange`},{default:d(()=>[a(t(C(r.status)),1)]),_:2},1032,[`color`])):n.dataIndex===`result`?(o(),l(s,{key:2},[a(t(r.result||`-`),1)],64)):n.dataIndex===`started_at`||n.dataIndex===`completed_at`?(o(),l(s,{key:3},[a(t(r[n.dataIndex]?new Date(r[n.dataIndex]).toLocaleString(`zh-CN`):`-`),1)],64)):e(``,!0)]),emptyText:d(()=>[...c[0]||=[i(`div`,{class:`empty-copy`},`还没有执行记录。发布 SOP 后即可开始执行。`,-1)]]),_:1},8,[`loading`,`data-source`])])])}}}),[[`__scopeId`,`data-v-4d911d56`]]);export{x as default};
|
import{A as e,Bt as t,F as n,I as r,O as i,P as a,Q as o,S as s,Y as c,j as l,k as u,lt as d,n as f,r as p,t as m,tt as h,vt as g}from"./client-CO11mUW5.js";import{a as _}from"./config-provider-kwhtQ-D4.js";var v={class:`page-shell`},y={class:`surface`},b={key:0},x=p(r({__name:`RunHistoryView`,setup(r){let p=g(!1),x=g([]);async function S(){p.value=!0;try{x.value=(await m.get(`/runs`)).items}catch(e){_.error(f(e))}finally{p.value=!1}}function C(e){return e===`completed`?`已完成`:`进行中`}return c(S),(r,c)=>{let f=h(`a-tag`),m=h(`a-table`);return o(),l(`div`,v,[c[1]||=i(`div`,{class:`page-heading`},[i(`div`,null,[i(`h1`,null,`执行记录`),i(`p`,null,`查看 SOP 的实际使用情况和执行结果。`)])],-1),i(`section`,y,[n(m,{"row-key":`id`,loading:p.value,"data-source":x.value,pagination:{pageSize:20},columns:[{title:`执行编号`,dataIndex:`id`,width:130},{title:`SOP`,dataIndex:`sop_name`},{title:`状态`,dataIndex:`status`,width:110},{title:`结果`,dataIndex:`result`,width:120},{title:`开始时间`,dataIndex:`started_at`,width:190},{title:`完成时间`,dataIndex:`completed_at`,width:190}],scroll:{x:900}},{bodyCell:d(({column:n,record:r})=>[n.dataIndex===`id`?(o(),l(`code`,b,`RUN-`+t(String(r.id).padStart(5,`0`)),1)):n.dataIndex===`status`?(o(),u(f,{key:1,color:r.status===`completed`?`green`:`orange`},{default:d(()=>[a(t(C(r.status)),1)]),_:2},1032,[`color`])):n.dataIndex===`result`?(o(),l(s,{key:2},[a(t(r.result||`-`),1)],64)):n.dataIndex===`started_at`||n.dataIndex===`completed_at`?(o(),l(s,{key:3},[a(t(r[n.dataIndex]?new Date(r[n.dataIndex]).toLocaleString(`zh-CN`):`-`),1)],64)):e(``,!0)]),emptyText:d(()=>[...c[0]||=[i(`div`,{class:`empty-copy`},`还没有执行记录。发布 SOP 后即可开始执行。`,-1)]]),_:1},8,[`loading`,`data-source`])])])}}}),[[`__scopeId`,`data-v-31292c9e`]]);export{x as default};
|
||||||
1
web/dist/assets/RunHistoryView-DFSCNdR7.css
vendored
Normal file
1
web/dist/assets/RunHistoryView-DFSCNdR7.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
code[data-v-31292c9e]{color:#17654f;background:#eef5f2;border-radius:3px;padding:2px 5px}
|
||||||
File diff suppressed because one or more lines are too long
1
web/dist/assets/ScenarioDetailView-EbGYJBey.css
vendored
Normal file
1
web/dist/assets/ScenarioDetailView-EbGYJBey.css
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.back-link[data-v-4169a902]{color:var(--muted);margin:-4px 0 12px -14px}.detail-heading[data-v-4169a902]{color:#fff;background:#202825;border-radius:6px;justify-content:space-between;align-items:flex-end;gap:24px;min-height:150px;padding:28px;display:flex}.detail-heading h1[data-v-4169a902]{letter-spacing:0;margin:10px 0 8px;font-family:Noto Serif SC,serif;font-size:28px}.detail-heading p[data-v-4169a902]{color:#b8c4bf;max-width:720px;margin:0}.heading-tags[data-v-4169a902]{color:#a8b6b0;align-items:center;gap:10px;font-size:12px;display:flex}.scenario-code[data-v-4169a902]{color:#70817a;align-self:flex-start;font-size:12px}.detail-tabs[data-v-4169a902]{margin-top:18px}.toolbar>div[data-v-4169a902]{align-items:baseline;gap:12px;display:flex}.toolbar-note[data-v-4169a902]{color:var(--muted);font-size:12px}.field-grid[data-v-4169a902]{grid-template-columns:1fr 1fr;gap:14px;display:grid}code[data-v-4169a902]{color:#17654f;background:#eef5f2;border-radius:3px;padding:2px 5px}.sop-list button[data-v-4169a902]{text-align:left;border:0;border-bottom:1px solid var(--line);cursor:pointer;background:#fff;grid-template-columns:42px 1fr auto 100px;align-items:center;gap:14px;width:100%;min-height:74px;padding:12px 18px;display:grid}.sop-list button[data-v-4169a902]:hover{background:#f8faf9}.sop-icon[data-v-4169a902]{width:38px;height:38px;color:var(--green);background:#e8f3ef;border-radius:4px;place-items:center;display:grid}.sop-copy[data-v-4169a902]{flex-direction:column;min-width:0;display:flex}.sop-copy small[data-v-4169a902]{color:var(--muted);text-overflow:ellipsis;white-space:nowrap;margin-top:4px;overflow:hidden}.sop-date[data-v-4169a902]{color:var(--muted);text-align:right;font-size:12px}.empty-state[data-v-4169a902]{color:#82908a;text-align:center;padding:64px 20px}.empty-state>span[data-v-4169a902]{font-size:32px}.empty-state p[data-v-4169a902]{margin:12px 0 0}@media (width<=650px){.detail-heading[data-v-4169a902]{flex-direction:column;align-items:flex-start}.scenario-code[data-v-4169a902]{display:none}.field-grid[data-v-4169a902]{grid-template-columns:1fr;gap:0}.sop-list button[data-v-4169a902]{grid-template-columns:36px 1fr auto}.sop-date[data-v-4169a902]{display:none}}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#202825" />
|
<meta name="theme-color" content="#202825" />
|
||||||
<title>销冠 SOP 平台</title>
|
<title>销冠 SOP 平台</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DL8F6dio.js"></script>
|
<script type="module" crossorigin src="/assets/index-DquTypIs.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/client-CO11mUW5.js">
|
<link rel="modulepreload" crossorigin href="/assets/client-CO11mUW5.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/config-provider-kwhtQ-D4.js">
|
<link rel="modulepreload" crossorigin href="/assets/config-provider-kwhtQ-D4.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/auth-D7KJ41TQ.js">
|
<link rel="modulepreload" crossorigin href="/assets/auth-D7KJ41TQ.js">
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<link rel="modulepreload" crossorigin href="/assets/BookOutlined-CNUY9qCc.js">
|
<link rel="modulepreload" crossorigin href="/assets/BookOutlined-CNUY9qCc.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/PlayCircleOutlined-C2UGH7h6.js">
|
<link rel="modulepreload" crossorigin href="/assets/PlayCircleOutlined-C2UGH7h6.js">
|
||||||
<link rel="modulepreload" crossorigin href="/assets/useApi-CROJJdhE-BlzMTLF9.js">
|
<link rel="modulepreload" crossorigin href="/assets/useApi-CROJJdhE-BlzMTLF9.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BM4AsfIr.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BXFcLq5Y.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
Reference in New Issue
Block a user