From 9785fb527bd6a6c7623b20ad13e3dd98be2b886a Mon Sep 17 00:00:00 2001 From: "DESKTOP-72TV0V4\\caoxiaozhu" Date: Wed, 6 May 2026 11:00:38 +0800 Subject: [PATCH] refactor: split project into web and server directories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move frontend to web/ directory - Add server/ directory for backend - Restructure project for前后端分离架构 Co-Authored-By: Claude Opus 4.7 --- .gitignore | 4 + README.md | 22 + .../AI报销操作层_影子报销账本_开发文档.md | 1493 ------------- server/README.md | 12 + server/src/.gitkeep | 1 + src/views/AuditView.vue | 1198 ----------- src/views/ChatView.vue | 341 --- src/views/OverviewView.vue | 610 ------ src/views/PoliciesView.vue | 1069 ---------- src/views/RequestsView.vue | 740 ------- src/views/TravelReimbursementCreateView.vue | 1514 ------------- src/views/TravelRequestDetailView.vue | 1898 ----------------- start.sh | 83 +- {demo => web/demo}/main_demo.html | 0 index.html => web/index.html | 0 package-lock.json => web/package-lock.json | 0 package.json => web/package.json | 0 {src => web/src}/App.vue | 219 +- {src => web/src}/assets/background.png | Bin {src => web/src}/assets/robot-assistant.png | Bin {src => web/src}/assets/security-shield.png | Bin web/src/assets/styles/app.css | 49 + {src => web/src}/assets/styles/global.css | 0 .../styles/views/approval-center-view.css | 753 ------- web/src/assets/styles/views/audit-view.css | 676 ++++++ web/src/assets/styles/views/chat-view.css | 77 + .../styles/views/employee-management-view.css | 658 ++++++ .../src/assets/styles/views/login-view.css | 168 -- web/src/assets/styles/views/overview-view.css | 387 ++++ web/src/assets/styles/views/policies-view.css | 643 ++++++ web/src/assets/styles/views/requests-view.css | 523 +++++ .../travel-reimbursement-create-view.css | 756 +++++++ .../views/travel-request-detail-view.css | 1164 ++++++++++ .../components/business/PersonalWorkbench.vue | 22 +- .../src}/components/business/RequestTable.vue | 0 .../src}/components/charts/BarChart.vue | 0 .../src}/components/charts/DonutChart.vue | 0 .../src}/components/charts/GaugeChart.vue | 0 .../src}/components/charts/TrendChart.vue | 0 .../src}/components/layout/DocFilterBar.vue | 0 .../src}/components/layout/FilterBar.vue | 0 .../src}/components/layout/SidebarRail.vue | 3 +- {src => web/src}/components/layout/TopBar.vue | 7 +- .../src}/components/shared/InfoRow.vue | 0 .../src}/components/shared/PanelHead.vue | 0 .../src}/components/shared/RollingValue.vue | 0 .../components/shared/ToastNotification.vue | 0 .../src}/composables/useAnimationProgress.js | 0 web/src/composables/useAppShell.js | 169 ++ {src => web/src}/composables/useChat.js | 0 web/src/composables/useLoginView.js | 36 + {src => web/src}/composables/useNavigation.js | 8 + web/src/composables/useOverviewView.js | 113 + {src => web/src}/composables/useRequests.js | 0 {src => web/src}/composables/useToast.js | 0 {src => web/src}/data/auditTrail.js | 0 {src => web/src}/data/icons.js | 1 + {src => web/src}/data/metrics.js | 0 {src => web/src}/data/policies.js | 0 {src => web/src}/data/requests.js | 0 {src => web/src}/main.js | 0 web/src/scripts/App.js | 209 ++ web/src/views/ApprovalCenterView.vue | 494 +++++ web/src/views/AuditView.vue | 286 +++ web/src/views/ChatView.vue | 178 ++ web/src/views/EmployeeManagementView.vue | 308 +++ web/src/views/LoginView.vue | 154 ++ web/src/views/OverviewView.vue | 149 ++ .../src}/views/PersonalWorkbenchView.vue | 0 web/src/views/PoliciesView.vue | 196 ++ web/src/views/RequestsView.vue | 127 ++ .../views/TravelReimbursementCreateView.vue | 338 +++ web/src/views/TravelRequestDetailView.vue | 317 +++ web/src/views/scripts/ApprovalCenterView.js | 284 +++ web/src/views/scripts/AuditView.js | 249 +++ web/src/views/scripts/ChatView.js | 101 + .../views/scripts/EmployeeManagementView.js | 202 ++ web/src/views/scripts/LoginView.js | 42 + web/src/views/scripts/OverviewView.js | 130 ++ web/src/views/scripts/PoliciesView.js | 244 +++ web/src/views/scripts/RequestsView.js | 116 + .../scripts/TravelReimbursementCreateView.js | 443 ++++ .../views/scripts/TravelRequestDetailView.js | 451 ++++ web/start.sh | 86 + vite.config.js => web/vite.config.js | 0 85 files changed, 10474 insertions(+), 10047 deletions(-) create mode 100644 README.md delete mode 100644 document/AI报销操作层_影子报销账本_开发文档.md create mode 100644 server/README.md create mode 100644 server/src/.gitkeep delete mode 100644 src/views/AuditView.vue delete mode 100644 src/views/ChatView.vue delete mode 100644 src/views/OverviewView.vue delete mode 100644 src/views/PoliciesView.vue delete mode 100644 src/views/RequestsView.vue delete mode 100644 src/views/TravelReimbursementCreateView.vue delete mode 100644 src/views/TravelRequestDetailView.vue rename {demo => web/demo}/main_demo.html (100%) rename index.html => web/index.html (100%) rename package-lock.json => web/package-lock.json (100%) rename package.json => web/package.json (100%) rename {src => web/src}/App.vue (51%) rename {src => web/src}/assets/background.png (100%) rename {src => web/src}/assets/robot-assistant.png (100%) rename {src => web/src}/assets/security-shield.png (100%) create mode 100644 web/src/assets/styles/app.css rename {src => web/src}/assets/styles/global.css (100%) rename src/views/ApprovalCenterView.vue => web/src/assets/styles/views/approval-center-view.css (50%) create mode 100644 web/src/assets/styles/views/audit-view.css create mode 100644 web/src/assets/styles/views/chat-view.css create mode 100644 web/src/assets/styles/views/employee-management-view.css rename src/views/LoginView.vue => web/src/assets/styles/views/login-view.css (67%) create mode 100644 web/src/assets/styles/views/overview-view.css create mode 100644 web/src/assets/styles/views/policies-view.css create mode 100644 web/src/assets/styles/views/requests-view.css create mode 100644 web/src/assets/styles/views/travel-reimbursement-create-view.css create mode 100644 web/src/assets/styles/views/travel-request-detail-view.css rename {src => web/src}/components/business/PersonalWorkbench.vue (98%) rename {src => web/src}/components/business/RequestTable.vue (100%) rename {src => web/src}/components/charts/BarChart.vue (100%) rename {src => web/src}/components/charts/DonutChart.vue (100%) rename {src => web/src}/components/charts/GaugeChart.vue (100%) rename {src => web/src}/components/charts/TrendChart.vue (100%) rename {src => web/src}/components/layout/DocFilterBar.vue (100%) rename {src => web/src}/components/layout/FilterBar.vue (100%) rename {src => web/src}/components/layout/SidebarRail.vue (98%) rename {src => web/src}/components/layout/TopBar.vue (98%) rename {src => web/src}/components/shared/InfoRow.vue (100%) rename {src => web/src}/components/shared/PanelHead.vue (100%) rename {src => web/src}/components/shared/RollingValue.vue (100%) rename {src => web/src}/components/shared/ToastNotification.vue (100%) rename {src => web/src}/composables/useAnimationProgress.js (100%) create mode 100644 web/src/composables/useAppShell.js rename {src => web/src}/composables/useChat.js (100%) create mode 100644 web/src/composables/useLoginView.js rename {src => web/src}/composables/useNavigation.js (87%) create mode 100644 web/src/composables/useOverviewView.js rename {src => web/src}/composables/useRequests.js (100%) rename {src => web/src}/composables/useToast.js (100%) rename {src => web/src}/data/auditTrail.js (100%) rename {src => web/src}/data/icons.js (91%) rename {src => web/src}/data/metrics.js (100%) rename {src => web/src}/data/policies.js (100%) rename {src => web/src}/data/requests.js (100%) rename {src => web/src}/main.js (100%) create mode 100644 web/src/scripts/App.js create mode 100644 web/src/views/ApprovalCenterView.vue create mode 100644 web/src/views/AuditView.vue create mode 100644 web/src/views/ChatView.vue create mode 100644 web/src/views/EmployeeManagementView.vue create mode 100644 web/src/views/LoginView.vue create mode 100644 web/src/views/OverviewView.vue rename {src => web/src}/views/PersonalWorkbenchView.vue (100%) create mode 100644 web/src/views/PoliciesView.vue create mode 100644 web/src/views/RequestsView.vue create mode 100644 web/src/views/TravelReimbursementCreateView.vue create mode 100644 web/src/views/TravelRequestDetailView.vue create mode 100644 web/src/views/scripts/ApprovalCenterView.js create mode 100644 web/src/views/scripts/AuditView.js create mode 100644 web/src/views/scripts/ChatView.js create mode 100644 web/src/views/scripts/EmployeeManagementView.js create mode 100644 web/src/views/scripts/LoginView.js create mode 100644 web/src/views/scripts/OverviewView.js create mode 100644 web/src/views/scripts/PoliciesView.js create mode 100644 web/src/views/scripts/RequestsView.js create mode 100644 web/src/views/scripts/TravelReimbursementCreateView.js create mode 100644 web/src/views/scripts/TravelRequestDetailView.js create mode 100644 web/start.sh rename vite.config.js => web/vite.config.js (100%) diff --git a/.gitignore b/.gitignore index e1051d8..ce24ab9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ node_modules/ dist/ +.vite/ +web/node_modules/ +web/dist/ +web/.vite/ .omc/ .omx/ .claude/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..c52a088 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# X-Financial + +项目结构已按前后端拆开: + +- `web/`:前端工程(当前 Vue + Vite 项目) +- `server/`:后端工程目录 +- `docs/`:方案和阶段文档 +- `UI/`:界面参考稿 +- `document/`:业务文档 + +从根目录启动前端: + +```bash +./start.sh +``` + +或手动进入前端目录: + +```bash +cd web +npm run dev +``` diff --git a/document/AI报销操作层_影子报销账本_开发文档.md b/document/AI报销操作层_影子报销账本_开发文档.md deleted file mode 100644 index 7849e20..0000000 --- a/document/AI报销操作层_影子报销账本_开发文档.md +++ /dev/null @@ -1,1493 +0,0 @@ -# AI 报销操作层 / 影子报销账本开发文档 - -> 版本:v0.1 -> 场景范围:仅聚焦“报销”场景,不包含合同审核、采购付款、总账、税务申报等后续场景。 -> 核心思路:以 Agent 层为业务中枢,在现有 OA / ERP / 费控系统之上构建 AI 原生的报销操作层与影子报销账本。 - ---- - -## 1. 项目定位 - -### 1.1 产品名称 - -暂定名称: - -- AI 报销操作层 -- 智能报销预审 Agent -- 影子报销账本 -- 报销政策 Agent - -推荐对外名称: - -> **AI 报销预审中台** - -推荐对内技术名称: - -> **AI Reimbursement Operation Layer** - ---- - -### 1.2 一句话定义 - -在企业现有 OA、ERP、费控系统之上,构建一个以 Agent 为核心的 AI 报销操作层,让员工不用直接面对复杂系统,只需上传票据和说明事项,由 Agent 自动完成材料整理、报销草稿生成、制度预审、缺件提醒、风险解释和后端系统同步。 - ---- - -### 1.3 核心目标 - -本系统不是替代 ERP,也不是重新做一套完整费控系统,而是作为现有系统之上的智能操作层。 - -核心目标包括: - -1. 降低员工填单难度。 -2. 减少财务审核打回。 -3. 提高报销制度执行一致性。 -4. 沉淀报销过程证据链。 -5. 为后续扩展付款、采购、合同付款等财务 Agent 场景打基础。 - ---- - -### 1.4 产品边界 - -#### 本期做 - -- 票据 / 附件上传 -- OCR 识别 -- 报销草稿生成 -- 费用类型识别 -- 制度规则预审 -- 缺件提醒 -- 风险解释 -- 用户补充确认 -- 影子报销账本记录 -- 后端系统同步接口预留 -- 审计日志与规则命中记录 - -#### 本期不做 - -- 不做正式总账 -- 不做凭证过账 -- 不做税务申报 -- 不做银行支付 -- 不做完整预算控制 -- 不做合同审核 -- 不替换原 OA / ERP / 费控系统 - ---- - -## 2. 总体架构 - -### 2.1 架构原则 - -系统采用“前台 AI 操作层 + 中台 Agent 编排 + 影子报销账本 + 后端系统同步”的架构。 - -核心原则: - -> **用户体验在 AI 报销操作层,正式账务仍在 ERP。** - -> **Agent 负责理解、编排与交互;规则负责判断;系统负责留痕;后端负责正式审批和入账。** - ---- - -### 2.2 架构分层 - -系统分为 6 层: - -1. 用户与入口层 -2. AI 报销操作层 -3. Agent 层 -4. 影子报销账本层 -5. Policy & Evidence 层 -6. System Adapter / 后端集成层 - ---- - -### 2.3 分层说明 - -#### 2.3.1 用户与入口层 - -面向对象: - -- 员工 -- 财务审核人员 -- 制度管理员 -- 系统管理员 - -入口方式: - -- Web -- 企业微信 -- 钉钉 -- OA 工作台 -- 移动端 H5 - -职责: - -- 接收用户报销意图 -- 上传票据和附件 -- 展示预审结果 -- 支持补件交互 -- 支持确认提交 - ---- - -#### 2.3.2 AI 报销操作层 - -这是用户直接感知的业务操作层。 - -主要功能: - -- 对话式报销入口 -- 票据 / 附件上传 -- 报销草稿展示 -- 风险与缺件提示 -- 预审结果解释 -- 用户确认提交 - -该层的目标是让用户不再直接面对复杂 ERP 字段,而是通过自然语言、文件上传和确认操作完成报销。 - ---- - -#### 2.3.3 Agent 层 - -Agent 层是系统核心中枢,所有业务流程围绕 Agent 层展开。 - -Agent 层负责: - -- 理解用户意图 -- 编排任务流程 -- 调用 OCR 工具 -- 调用规则引擎 -- 查询主数据 -- 生成报销草稿 -- 发起补件对话 -- 输出风险解释 -- 同步后端系统 - -核心组件: - -- 受理 Agent -- 单据解析 Agent -- 规则校验 Agent -- 解释与补件 Agent -- 同步执行 Agent -- Agent Orchestrator / Workflow - ---- - -#### 2.3.4 影子报销账本层 - -影子报销账本层用于沉淀报销业务事实,但不是正式总账。 - -它记录: - -- 报销申请 -- 报销明细 -- 票据索引 -- 附件索引 -- 风险记录 -- 规则命中记录 -- 补件会话记录 -- 后端同步状态 - -作用: - -- 为前台提供统一业务视图 -- 为 Agent 提供流程状态 -- 为审计提供过程留痕 -- 为后端系统同步提供中间状态 - -注意: - -> 影子报销账本不是会计账,不承担正式记账职责。 - ---- - -#### 2.3.5 Policy & Evidence 层 - -该层负责规则判断和证据归档。 - -包含: - -- 报销制度库 -- 费用标准库 -- 费用类型字典 -- 城市等级 / 补贴规则 -- 发票验真结果 -- 发票查重结果 -- 审计日志 -- 证据归档库 - -作用: - -- 为 Agent 提供制度依据 -- 为规则引擎提供结构化规则 -- 为财务审核提供可追溯证据 -- 为后续审计提供完整证据链 - ---- - -#### 2.3.6 System Adapter / 后端集成层 - -负责连接企业已有系统。 - -可对接系统: - -- OA / BPM 审批系统 -- ERP / 财务系统 -- 费控报销系统 -- 预算系统 -- 主数据系统 -- 发票平台 -- 税务接口 -- 财务共享中心 -- 企业微信 / 钉钉消息系统 - -职责: - -- 查询主数据 -- 查询预算信息 -- 查询员工信息 -- 写入报销草稿 -- 发起审批流程 -- 同步审批状态 -- 同步归档结果 - ---- - -## 3. 核心业务流程 - -### 3.1 流程总览 - -完整流程如下: - -1. 用户上传票据与出差信息 -2. Agent 受理并理解任务 -3. Agent 调用 OCR 和解析工具 -4. Agent 识别费用类型并生成影子报销记录 -5. Agent 调用规则引擎完成制度预审 -6. Agent 输出风险解释和缺件提醒 -7. 用户补充材料并确认 -8. Agent 生成标准报销单 -9. Agent 同步 OA / ERP / 费控系统 -10. 后端完成正式审批、入账与归档 - ---- - -### 3.2 主流程时序 - -```mermaid -sequenceDiagram - participant U as 用户 - participant UI as AI报销操作层 - participant AO as Agent Orchestrator - participant OCR as OCR/票据识别 - participant Rule as 规则引擎 - participant Ledger as 影子报销账本 - participant Adapter as 后端适配器 - participant ERP as OA/ERP/费控系统 - - U->>UI: 上传票据/说明报销事项 - UI->>AO: 创建报销任务 - AO->>OCR: 调用票据识别 - OCR-->>AO: 返回结构化票据信息 - AO->>Ledger: 创建影子报销记录 - AO->>Rule: 调用制度预审 - Rule-->>AO: 返回规则命中与风险结果 - AO->>UI: 展示风险/缺件/草稿 - U->>UI: 补充材料并确认 - UI->>AO: 提交确认 - AO->>Ledger: 更新报销状态 - AO->>Adapter: 生成标准报销单并同步 - Adapter->>ERP: 写入报销单/发起审批 - ERP-->>Adapter: 返回审批单号/状态 - Adapter-->>Ledger: 回写后端同步状态 -``` - ---- - -### 3.3 Agent 驱动运行逻辑 - -```mermaid -flowchart TD - A[用户上传票据与出差信息] --> B[受理 Agent 理解任务] - B --> C[单据解析 Agent 识别票据与附件] - C --> D[生成影子报销记录] - D --> E[规则校验 Agent 调用制度规则] - E --> F{是否存在风险或缺件} - F -- 是 --> G[解释与补件 Agent 发起补件交互] - G --> H[用户补充材料并确认] - H --> E - F -- 否 --> I[同步执行 Agent 生成标准报销单] - I --> J[同步 OA / ERP / 费控系统] - J --> K[后端正式审批、入账与归档] -``` - ---- - -## 4. Agent 设计 - -### 4.1 Agent 总体分工 - -| Agent | 职责 | 输入 | 输出 | -|---|---|---|---| -| 受理 Agent | 理解用户报销意图,收集上下文 | 用户自然语言、上传文件、历史任务 | 报销任务、材料清单、缺失信息 | -| 单据解析 Agent | OCR 识别、字段抽取、费用归类 | 发票、行程单、酒店单、支付截图 | 结构化票据信息、费用明细建议 | -| 规则校验 Agent | 制度预审、超标、查重、合规判断 | 报销草稿、规则库、主数据 | 规则命中记录、风险等级 | -| 解释与补件 Agent | 解释风险、提示缺件、引导修改 | 风险结果、缺件列表、制度依据 | 用户可理解的解释、补件任务 | -| 同步执行 Agent | 生成标准报销单,调用后端接口 | 最终确认数据、影子账本记录 | 后端报销单号、审批状态 | - ---- - -### 4.2 Agent Orchestrator - -Agent Orchestrator 是流程编排器,负责管理任务状态和 Agent 调度。 - -职责: - -- 创建任务 -- 维护任务状态 -- 决定下一步调用哪个 Agent -- 管理多轮对话上下文 -- 管理工具调用 -- 处理失败重试 -- 记录流程日志 -- 管理人工确认节点 - -建议状态机: - -```mermaid -stateDiagram-v2 - [*] --> Created: 创建任务 - Created --> MaterialCollecting: 收集材料 - MaterialCollecting --> Parsing: 解析单据 - Parsing --> DraftGenerated: 生成草稿 - DraftGenerated --> PreChecking: 制度预审 - PreChecking --> NeedSupplement: 存在缺件/风险 - NeedSupplement --> MaterialCollecting: 用户补件 - PreChecking --> PendingUserConfirm: 预审通过 - PendingUserConfirm --> Submitting: 用户确认 - Submitting --> Synced: 同步后端成功 - Submitting --> SyncFailed: 同步失败 - SyncFailed --> Submitting: 重试 - Synced --> [*] -``` - ---- - -### 4.3 Agent 调用原则 - -Agent 不直接决定最终财务结果,只负责预审和建议。 - -原则: - -1. 低风险问题可自动提示。 -2. 中风险问题需要用户确认。 -3. 高风险问题进入人工审核。 -4. 所有规则命中必须留痕。 -5. 所有 AI 解释必须能追溯到制度或证据。 -6. 正式审批和入账仍以后端系统为准。 - ---- - -## 5. 核心数据模型 - -### 5.1 主要实体 - -| 实体 | 说明 | -|---|---| -| ReimbursementTask | 报销任务,Agent 处理的最小任务单元 | -| ShadowReimbursement | 影子报销记录,表示一次报销业务事实 | -| ReimbursementItem | 报销明细 | -| ExpenseDocument | 票据 / 附件 | -| ExpensePolicy | 报销制度 | -| ExpenseRule | 报销规则 | -| RuleHit | 规则命中记录 | -| RiskFinding | 风险提示 | -| SupplementRequest | 补件请求 | -| ConversationRecord | 会话记录 | -| SyncRecord | 后端同步记录 | -| AuditLog | 审计日志 | - ---- - -### 5.2 表结构建议 - -#### 5.2.1 reimbursement_task 报销任务表 - -| 字段 | 类型 | 说明 | -|---|---|---| -| id | string | 任务ID | -| user_id | string | 发起人ID | -| company_id | string | 企业ID | -| task_type | string | 任务类型,如 travel_expense | -| status | string | 任务状态 | -| user_intent | text | 用户原始意图 | -| current_agent | string | 当前处理 Agent | -| created_at | datetime | 创建时间 | -| updated_at | datetime | 更新时间 | - ---- - -#### 5.2.2 shadow_reimbursement 影子报销记录表 - -| 字段 | 类型 | 说明 | -|---|---|---| -| id | string | 影子报销ID | -| task_id | string | 关联任务ID | -| applicant_id | string | 报销人 | -| department_id | string | 部门 | -| cost_center_id | string | 成本中心 | -| project_id | string | 项目ID | -| reimbursement_type | string | 报销类型 | -| reason | text | 报销事由 | -| total_amount | decimal | 总金额 | -| currency | string | 币种 | -| risk_level | string | 风险等级 | -| precheck_status | string | 预审状态 | -| backend_system | string | 目标后端系统 | -| backend_bill_id | string | 后端单据ID | -| sync_status | string | 同步状态 | -| created_at | datetime | 创建时间 | -| updated_at | datetime | 更新时间 | - ---- - -#### 5.2.3 reimbursement_item 报销明细表 - -| 字段 | 类型 | 说明 | -|---|---|---| -| id | string | 明细ID | -| reimbursement_id | string | 影子报销ID | -| expense_type | string | 费用类型 | -| amount | decimal | 金额 | -| tax_amount | decimal | 税额 | -| occurred_at | date | 费用发生日期 | -| city | string | 发生城市 | -| vendor_name | string | 商户名称 | -| invoice_id | string | 关联票据ID | -| policy_status | string | 规则校验状态 | -| risk_level | string | 风险等级 | -| remark | text | 备注 | - ---- - -#### 5.2.4 expense_document 票据附件表 - -| 字段 | 类型 | 说明 | -|---|---|---| -| id | string | 文件ID | -| reimbursement_id | string | 影子报销ID | -| item_id | string | 明细ID | -| document_type | string | 发票、行程单、酒店流水、支付截图等 | -| file_url | string | 文件地址 | -| ocr_status | string | OCR 状态 | -| extracted_json | json | OCR 抽取结果 | -| invoice_code | string | 发票代码 | -| invoice_number | string | 发票号码 | -| invoice_date | date | 开票日期 | -| amount | decimal | 金额 | -| tax_amount | decimal | 税额 | -| seller_name | string | 销售方 | -| buyer_name | string | 购买方 | -| verify_status | string | 验真状态 | -| duplicate_status | string | 查重状态 | -| created_at | datetime | 创建时间 | - ---- - -#### 5.2.5 expense_rule 规则表 - -| 字段 | 类型 | 说明 | -|---|---|---| -| id | string | 规则ID | -| rule_code | string | 规则编码 | -| rule_name | string | 规则名称 | -| expense_type | string | 适用费用类型 | -| condition_json | json | 条件表达式 | -| action | string | 命中动作 | -| severity | string | 风险等级 | -| message_template | text | 提示文案模板 | -| policy_ref | string | 制度依据 | -| enabled | boolean | 是否启用 | -| version | string | 规则版本 | - ---- - -#### 5.2.6 rule_hit 规则命中表 - -| 字段 | 类型 | 说明 | -|---|---|---| -| id | string | 命中ID | -| reimbursement_id | string | 报销ID | -| item_id | string | 明细ID | -| rule_id | string | 规则ID | -| severity | string | 风险等级 | -| hit_result | json | 命中详情 | -| explanation | text | 解释说明 | -| suggestion | text | 修改建议 | -| created_at | datetime | 创建时间 | - ---- - -#### 5.2.7 supplement_request 补件请求表 - -| 字段 | 类型 | 说明 | -|---|---|---| -| id | string | 补件请求ID | -| reimbursement_id | string | 报销ID | -| request_type | string | 补充附件、补充说明、修改字段 | -| target_item_id | string | 关联明细ID | -| message | text | 补件提示 | -| status | string | 待补充、已补充、已关闭 | -| user_response | text | 用户回复 | -| created_at | datetime | 创建时间 | -| resolved_at | datetime | 完成时间 | - ---- - -#### 5.2.8 sync_record 后端同步记录表 - -| 字段 | 类型 | 说明 | -|---|---|---| -| id | string | 同步ID | -| reimbursement_id | string | 报销ID | -| target_system | string | 目标系统 | -| request_payload | json | 请求报文 | -| response_payload | json | 响应报文 | -| sync_status | string | 成功、失败、重试中 | -| backend_bill_id | string | 后端单据ID | -| error_message | text | 错误信息 | -| created_at | datetime | 创建时间 | - ---- - -## 6. 枚举设计 - -### 6.1 报销任务状态 - -| 编码 | 名称 | -|---|---| -| created | 已创建 | -| material_collecting | 收集中 | -| parsing | 解析中 | -| draft_generated | 草稿已生成 | -| prechecking | 预审中 | -| need_supplement | 需要补件 | -| pending_user_confirm | 待用户确认 | -| submitting | 提交中 | -| synced | 已同步 | -| sync_failed | 同步失败 | -| closed | 已关闭 | - ---- - -### 6.2 费用类型 - -| 编码 | 名称 | -|---|---| -| travel_transport | 差旅交通费 | -| travel_hotel | 差旅住宿费 | -| travel_meal | 差旅餐补 | -| local_transport | 市内交通费 | -| business_meal | 业务招待费 | -| office_supply | 办公用品费 | -| communication | 通讯费 | -| other | 其他 | - ---- - -### 6.3 票据类型 - -| 编码 | 名称 | -|---|---| -| vat_invoice | 增值税发票 | -| train_ticket | 火车票 | -| flight_itinerary | 航空行程单 | -| taxi_receipt | 打车票据 | -| hotel_bill | 酒店流水 | -| payment_screenshot | 支付截图 | -| travel_order | 行程单 | -| other_attachment | 其他附件 | - ---- - -### 6.4 风险等级 - -| 编码 | 名称 | 处理方式 | -|---|---|---| -| low | 低风险 | 提醒用户 | -| medium | 中风险 | 需要用户确认或补充说明 | -| high | 高风险 | 建议人工审核 | -| blocked | 阻断 | 不允许提交 | - ---- - -### 6.5 规则动作 - -| 编码 | 名称 | -|---|---| -| pass | 通过 | -| warn | 警告 | -| require_explanation | 要求说明 | -| require_attachment | 要求补附件 | -| require_approval | 要求特殊审批 | -| block | 阻断提交 | - ---- - -## 7. 规则引擎设计 - -### 7.1 规则类型 - -MVP 阶段建议支持以下规则: - -1. 必填字段校验 -2. 附件完整性校验 -3. 票据验真校验 -4. 重复报销校验 -5. 金额超标校验 -6. 日期合理性校验 -7. 出差期间匹配校验 -8. 费用类型与票据类型匹配校验 -9. 发票抬头校验 -10. 项目 / 成本中心校验 - ---- - -### 7.2 规则示例 - -#### 规则 1:住宿费超标 - -```json -{ - "rule_code": "TRAVEL_HOTEL_LIMIT", - "rule_name": "住宿费标准校验", - "expense_type": "travel_hotel", - "condition": { - "compare": "amount_per_night", - "operator": ">", - "target": "policy.hotel_limit" - }, - "action": "require_explanation", - "severity": "medium", - "message_template": "住宿费超出当前城市和职级标准,请补充超标说明或发起特殊审批。", - "policy_ref": "差旅报销制度-住宿标准" -} -``` - -#### 规则 2:缺少酒店流水 - -```json -{ - "rule_code": "HOTEL_BILL_REQUIRED", - "rule_name": "住宿费必须上传酒店流水", - "expense_type": "travel_hotel", - "condition": { - "required_document_type": "hotel_bill" - }, - "action": "require_attachment", - "severity": "medium", - "message_template": "当前住宿费缺少酒店流水,请补充上传。", - "policy_ref": "差旅报销制度-附件要求" -} -``` - -#### 规则 3:票据疑似重复 - -```json -{ - "rule_code": "DUPLICATE_INVOICE_CHECK", - "rule_name": "重复发票检查", - "expense_type": "*", - "condition": { - "duplicate_keys": ["invoice_code", "invoice_number", "amount", "invoice_date"] - }, - "action": "block", - "severity": "blocked", - "message_template": "检测到该发票可能已被报销,请确认或联系财务处理。", - "policy_ref": "费用报销管理办法-重复报销" -} -``` - ---- - -### 7.3 规则结果结构 - -```json -{ - "precheck_status": "need_supplement", - "risk_level": "medium", - "rule_hits": [ - { - "rule_code": "HOTEL_BILL_REQUIRED", - "severity": "medium", - "message": "当前住宿费缺少酒店流水,请补充上传。", - "suggestion": "请上传酒店结账单或住宿明细流水。", - "policy_ref": "差旅报销制度-附件要求" - } - ] -} -``` - ---- - -## 8. API 设计 - -### 8.1 创建报销任务 - -```http -POST /api/reimbursement/tasks -``` - -请求: - -```json -{ - "user_id": "U001", - "company_id": "C001", - "user_intent": "我要报这次北京出差的费用", - "entry_channel": "web" -} -``` - -响应: - -```json -{ - "task_id": "T202604240001", - "status": "material_collecting" -} -``` - ---- - -### 8.2 上传票据附件 - -```http -POST /api/reimbursement/tasks/{task_id}/documents -``` - -请求: - -```json -{ - "document_type": "vat_invoice", - "file_url": "https://example.com/file/invoice001.pdf" -} -``` - -响应: - -```json -{ - "document_id": "D001", - "ocr_status": "pending" -} -``` - ---- - -### 8.3 启动 Agent 处理 - -```http -POST /api/reimbursement/tasks/{task_id}/agent/run -``` - -请求: - -```json -{ - "start_from": "intake", - "mode": "precheck" -} -``` - -响应: - -```json -{ - "task_id": "T202604240001", - "status": "parsing", - "current_agent": "document_parse_agent" -} -``` - ---- - -### 8.4 获取报销草稿 - -```http -GET /api/reimbursement/tasks/{task_id}/draft -``` - -响应: - -```json -{ - "reimbursement_id": "R001", - "reason": "北京出差", - "total_amount": 2380.00, - "items": [ - { - "item_id": "I001", - "expense_type": "travel_hotel", - "amount": 1200.00, - "occurred_at": "2026-04-20", - "risk_level": "medium" - } - ], - "precheck_status": "need_supplement" -} -``` - ---- - -### 8.5 获取预审结果 - -```http -GET /api/reimbursement/tasks/{task_id}/precheck-result -``` - -响应: - -```json -{ - "precheck_status": "need_supplement", - "risk_level": "medium", - "summary": "当前报销单存在 1 个缺件问题和 1 个超标风险。", - "rule_hits": [ - { - "rule_code": "HOTEL_BILL_REQUIRED", - "severity": "medium", - "message": "住宿费缺少酒店流水。", - "suggestion": "请上传酒店流水。" - } - ] -} -``` - ---- - -### 8.6 用户补件 - -```http -POST /api/reimbursement/tasks/{task_id}/supplements -``` - -请求: - -```json -{ - "supplement_request_id": "S001", - "response_text": "已补充酒店流水", - "document_ids": ["D003"] -} -``` - -响应: - -```json -{ - "status": "received", - "next_action": "rerun_precheck" -} -``` - ---- - -### 8.7 用户确认提交 - -```http -POST /api/reimbursement/tasks/{task_id}/submit -``` - -请求: - -```json -{ - "confirmed": true, - "submit_to": "expense_system" -} -``` - -响应: - -```json -{ - "status": "submitting", - "sync_id": "SYNC001" -} -``` - ---- - -### 8.8 查询同步状态 - -```http -GET /api/reimbursement/tasks/{task_id}/sync-status -``` - -响应: - -```json -{ - "sync_status": "success", - "target_system": "expense_system", - "backend_bill_id": "BX202604240001" -} -``` - ---- - -## 9. 前端页面设计 - -### 9.1 页面清单 - -MVP 阶段建议包含以下页面: - -1. 报销入口页 -2. 票据上传页 -3. 报销草稿页 -4. 预审结果页 -5. 补件交互页 -6. 提交确认页 -7. 任务详情页 -8. 财务审核辅助页 -9. 制度规则管理页 -10. 审计日志页 - ---- - -### 9.2 报销入口页 - -核心元素: - -- 对话输入框 -- 上传按钮 -- 最近任务 -- 常用报销类型 -- 智能引导问题 - -示例提示: - -- “我要报一次差旅” -- “帮我看这些票能不能报” -- “帮我生成报销单” -- “这张发票为什么不合规?” - ---- - -### 9.3 报销草稿页 - -展示内容: - -- 报销人 -- 部门 -- 成本中心 -- 项目 -- 报销事由 -- 费用明细 -- 票据附件 -- 自动识别结果 -- 可编辑字段 -- 预审状态 - ---- - -### 9.4 预审结果页 - -展示内容: - -- 总体结论 -- 风险等级 -- 通过项 -- 风险项 -- 缺件项 -- 命中规则 -- 制度依据 -- 修改建议 -- 一键补件按钮 -- 人工确认按钮 - ---- - -### 9.5 财务审核辅助页 - -展示内容: - -- 报销单摘要 -- AI 预审结论 -- 规则命中列表 -- 用户补件记录 -- 附件证据链 -- 同步状态 -- 审计日志 - ---- - -## 10. 后端服务设计 - -### 10.1 服务拆分建议 - -MVP 阶段可采用模块化单体,后续再微服务化。 - -建议模块: - -| 模块 | 说明 | -|---|---| -| user-service | 用户与权限 | -| task-service | 报销任务管理 | -| document-service | 文件与票据管理 | -| agent-service | Agent 编排 | -| ocr-service | OCR 调用封装 | -| rule-service | 规则引擎 | -| ledger-service | 影子账本 | -| evidence-service | 证据归档 | -| adapter-service | 后端系统适配 | -| audit-service | 审计日志 | - ---- - -### 10.2 推荐技术栈 - -可选方案: - -#### 前端 - -- React / Vue -- Ant Design / Element Plus -- 企业微信 / 钉钉 H5 SDK - -#### 后端 - -- Java Spring Boot -- Python FastAPI -- Node.js NestJS - -#### 数据库 - -- PostgreSQL / MySQL -- Redis -- MinIO / OSS 文件存储 -- Elasticsearch / OpenSearch,可选,用于检索 - -#### AI / Agent - -- Agent Orchestrator 自研 -- LangGraph / CrewAI / AutoGen / 自定义 Workflow,可选 -- 大模型 API -- OCR API -- 向量数据库,可选 - -#### 规则引擎 - -- JSON Rule Engine -- Drools -- 自研轻量规则引擎 - ---- - -## 11. 权限与安全 - -### 11.1 权限角色 - -| 角色 | 权限 | -|---|---| -| 员工 | 创建报销、上传材料、查看自己的任务 | -| 财务审核人员 | 查看待审核报销、查看预审结果、处理风险 | -| 制度管理员 | 维护制度、规则、费用标准 | -| 系统管理员 | 管理组织、用户、接口配置 | -| 审计人员 | 查看日志和证据链 | - ---- - -### 11.2 安全要求 - -- 文件上传需做病毒扫描。 -- 发票、身份证、银行卡等敏感信息需脱敏展示。 -- 所有 Agent 操作需记录日志。 -- 所有规则版本需保留历史。 -- 所有用户确认动作需留痕。 -- 后端接口需有签名、鉴权和重放保护。 -- 不允许 Agent 绕过人工确认直接提交高风险单据。 - ---- - -## 12. 审计与可解释性 - -### 12.1 必须留痕的动作 - -- 用户上传文件 -- OCR 识别结果 -- Agent 调用记录 -- 规则命中记录 -- 用户补件记录 -- 用户确认记录 -- 后端同步请求 -- 后端同步响应 -- 审核人处理记录 - ---- - -### 12.2 解释输出模板 - -风险解释建议采用统一格式: - -```text -问题类型:住宿费超标 -命中规则:TRAVEL_HOTEL_LIMIT -制度依据:差旅报销制度-住宿标准 -问题说明:当前住宿费为 680 元/晚,超过该城市标准 500 元/晚。 -建议处理:请补充超标说明,或发起特殊审批。 -风险等级:中风险 -``` - ---- - -## 13. MVP 版本规划 - -### 13.1 MVP 目标 - -用最小范围跑通“上传材料 → 识别 → 草稿 → 预审 → 补件 → 确认 → 同步”的完整闭环。 - ---- - -### 13.2 MVP 功能范围 - -#### 必做 - -- 用户创建报销任务 -- 上传票据 / 附件 -- OCR 识别 -- 自动生成报销草稿 -- 费用类型识别 -- 基础规则预审 -- 风险与缺件提示 -- 用户补件 -- 用户确认提交 -- 影子报销账本 -- 审计日志 -- 模拟后端同步 - -#### 可延后 - -- 多企业多租户 -- 复杂审批流 -- 深度 ERP 接口 -- 发票实时验真 -- 预算强控制 -- 移动端完整体验 -- 多语言 -- 行业模板市场 - ---- - -### 13.3 MVP 优先支持的报销类型 - -建议优先支持: - -> 差旅报销 - -原因: - -- 票据类型丰富 -- 规则典型 -- 缺件场景多 -- 价值容易演示 -- 客户容易理解 - -优先支持票据: - -- 增值税发票 -- 火车票 -- 机票行程单 -- 酒店流水 -- 打车票据 -- 支付截图 - ---- - -## 14. 里程碑计划 - -### 阶段 1:原型验证 - -目标: - -- 完成核心流程 Demo -- 完成影子账本基础表 -- 完成 OCR 接入 -- 完成 5 条基础规则 - -输出: - -- Web Demo -- 报销任务流 -- 草稿生成页面 -- 预审结果页面 - ---- - -### 阶段 2:MVP 可用版本 - -目标: - -- 完整跑通差旅报销 -- 支持多轮补件 -- 支持规则命中留痕 -- 支持模拟后端同步 - -输出: - -- MVP 系统 -- 规则配置后台 -- 审计日志 -- 财务审核辅助页 - ---- - -### 阶段 3:试点企业接入 - -目标: - -- 接入一家试点企业 -- 对接其 OA / 费控 / ERP 中至少一个系统 -- 根据真实制度配置规则 -- 验证打回率降低效果 - -输出: - -- 试点报告 -- 规则模板 -- 接口适配经验 -- ROI 数据 - ---- - -### 阶段 4:产品化 - -目标: - -- 多企业配置 -- 多制度模板 -- 多后端适配器 -- 管理后台完善 -- 权限与安全完善 - -输出: - -- 标准产品版本 -- 部署手册 -- 运维手册 -- 行业模板 - ---- - -## 15. 关键指标 - -### 15.1 业务指标 - -| 指标 | 说明 | -|---|---| -| 自动草稿生成率 | 上传材料后成功生成草稿的比例 | -| 预审命中率 | 规则识别出问题的比例 | -| 打回率降低 | 接入前后报销打回率变化 | -| 平均填单时间 | 员工完成报销所需时间 | -| 平均审核时间 | 财务审核单据所需时间 | -| 补件一次完成率 | 用户一次补齐材料的比例 | - ---- - -### 15.2 技术指标 - -| 指标 | 说明 | -|---|---| -| OCR 准确率 | 关键字段识别准确率 | -| 规则执行耗时 | 单次预审耗时 | -| Agent 成功完成率 | Agent 完整跑完流程的比例 | -| 后端同步成功率 | 同步 OA / ERP / 费控成功率 | -| 系统响应时间 | 主要接口响应时延 | -| 任务异常率 | Agent 流程失败比例 | - ---- - -## 16. 风险与应对 - -### 16.1 规则不准确 - -风险: - -- 制度复杂,规则配置错误会导致误判。 - -应对: - -- 规则上线前必须人工确认。 -- 规则命中要显示依据。 -- 高风险结果只建议,不自动阻断。 - ---- - -### 16.2 OCR 识别错误 - -风险: - -- 票据字段识别不准,导致草稿错误。 - -应对: - -- 关键字段允许人工修改。 -- 低置信度字段高亮提示。 -- 原图与识别结果并排展示。 - ---- - -### 16.3 后端系统难对接 - -风险: - -- 企业 ERP / OA 接口不统一。 - -应对: - -- 先做模拟同步。 -- 后端适配器标准化。 -- 支持 API 与 RPA 两种方式。 - ---- - -### 16.4 AI 幻觉 - -风险: - -- Agent 可能生成不符合制度的解释。 - -应对: - -- 所有解释必须基于规则命中和制度片段。 -- 没有依据时必须显示“未找到明确制度依据”。 -- 高风险判断进入人工审核。 - ---- - -### 16.5 客户担心替换原系统 - -风险: - -- 客户认为系统会冲击现有 ERP / 费控系统。 - -应对: - -- 明确定位为“前台 AI 操作层”。 -- 正式审批、入账、归档仍在后端系统完成。 -- 强调不替换原系统。 - ---- - -## 17. 后续扩展方向 - -本期只做报销,后续可扩展到: - -1. 付款申请 -2. 采购申请 -3. 合同付款 -4. 供应商对账 -5. 发票入账 -6. 预算执行分析 -7. 财务共享审核 Agent - -长期目标: - -> 构建企业财务运营 Agent Layer。 - ---- - -## 18. 附录:推荐目录结构 - -```text -ai-reimbursement-agent/ -├── frontend/ -│ ├── pages/ -│ ├── components/ -│ └── services/ -├── backend/ -│ ├── task-service/ -│ ├── document-service/ -│ ├── agent-service/ -│ ├── rule-service/ -│ ├── ledger-service/ -│ ├── evidence-service/ -│ ├── adapter-service/ -│ └── audit-service/ -├── database/ -│ ├── migrations/ -│ └── seed/ -├── docs/ -│ ├── api.md -│ ├── rules.md -│ ├── data-model.md -│ └── deployment.md -└── README.md -``` - ---- - -## 19. 附录:最小开发任务拆分 - -### 后端任务 - -- [ ] 创建报销任务接口 -- [ ] 上传附件接口 -- [ ] OCR 调用封装 -- [ ] 影子报销账本表 -- [ ] 报销明细表 -- [ ] 规则引擎基础能力 -- [ ] 规则命中记录 -- [ ] 补件请求接口 -- [ ] 后端同步模拟接口 -- [ ] 审计日志 - -### 前端任务 - -- [ ] 报销入口页 -- [ ] 上传附件组件 -- [ ] 报销草稿页 -- [ ] 预审结果页 -- [ ] 补件交互页 -- [ ] 提交确认页 -- [ ] 财务审核辅助页 - -### Agent 任务 - -- [ ] 受理 Agent -- [ ] 单据解析 Agent -- [ ] 规则校验 Agent -- [ ] 解释与补件 Agent -- [ ] 同步执行 Agent -- [ ] Orchestrator 状态机 - -### 规则任务 - -- [ ] 必填字段规则 -- [ ] 缺附件规则 -- [ ] 重复发票规则 -- [ ] 金额超标规则 -- [ ] 日期异常规则 -- [ ] 费用类型匹配规则 - ---- - -## 20. 总结 - -本系统的核心不是重新做一套报销系统,也不是替换 ERP,而是在企业现有系统之上构建一个以 Agent 为核心的 AI 报销操作层。 - -它的关键价值在于: - -- 员工只面对简单前台。 -- Agent 负责理解、编排和补件。 -- 影子报销账本沉淀业务事实。 -- Policy & Evidence 层保证判断可解释、证据可追溯。 -- 后端 OA / ERP / 费控系统继续负责正式审批、入账和归档。 - -一句话总结: - -> **前台体验在 AI 报销操作层,正式账务仍在 ERP;所有业务流程围绕 Agent 层展开。** diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..5b14913 --- /dev/null +++ b/server/README.md @@ -0,0 +1,12 @@ +# Server + +后端目录。 + +当前仓库还没有正式后端实现,这里先独立出 `server/`,后续服务端代码统一放在这里,避免再和前端工程混在根目录或 `web/` 里。 + +建议后续结构: + +- `server/src/`:业务代码 +- `server/config/`:配置 +- `server/scripts/`:启动、迁移、初始化脚本 +- `server/tests/`:后端测试 diff --git a/server/src/.gitkeep b/server/src/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/server/src/.gitkeep @@ -0,0 +1 @@ + diff --git a/src/views/AuditView.vue b/src/views/AuditView.vue deleted file mode 100644 index 80909a8..0000000 --- a/src/views/AuditView.vue +++ /dev/null @@ -1,1198 +0,0 @@ - - - - - diff --git a/src/views/ChatView.vue b/src/views/ChatView.vue deleted file mode 100644 index 35d6299..0000000 --- a/src/views/ChatView.vue +++ /dev/null @@ -1,341 +0,0 @@ - - - - - diff --git a/src/views/OverviewView.vue b/src/views/OverviewView.vue deleted file mode 100644 index 1be79e3..0000000 --- a/src/views/OverviewView.vue +++ /dev/null @@ -1,610 +0,0 @@ - - - - - diff --git a/src/views/PoliciesView.vue b/src/views/PoliciesView.vue deleted file mode 100644 index b34b136..0000000 --- a/src/views/PoliciesView.vue +++ /dev/null @@ -1,1069 +0,0 @@ - - - - - diff --git a/src/views/RequestsView.vue b/src/views/RequestsView.vue deleted file mode 100644 index 2e02c2b..0000000 --- a/src/views/RequestsView.vue +++ /dev/null @@ -1,740 +0,0 @@ - - - - - diff --git a/src/views/TravelReimbursementCreateView.vue b/src/views/TravelReimbursementCreateView.vue deleted file mode 100644 index ae3c2cb..0000000 --- a/src/views/TravelReimbursementCreateView.vue +++ /dev/null @@ -1,1514 +0,0 @@ - - - - - diff --git a/web/src/assets/styles/views/audit-view.css b/web/src/assets/styles/views/audit-view.css new file mode 100644 index 0000000..8ef0a03 --- /dev/null +++ b/web/src/assets/styles/views/audit-view.css @@ -0,0 +1,676 @@ +.skill-center { + height: 100%; + min-height: 0; +} + +.skill-view-enter-active, +.skill-view-leave-active { + transition: opacity 220ms ease, transform 300ms var(--ease); +} + +.skill-view-enter-from, +.skill-view-leave-to { + opacity: 0; + transform: translateY(14px); +} + +.skill-list, +.skill-detail { + height: 100%; + min-height: 0; +} + +.skill-detail { + display: grid; + grid-template-rows: minmax(0, 1fr) auto; + gap: 12px; +} + +.skill-list { + display: grid; + grid-template-rows: auto auto auto minmax(0, 1fr); + padding: 18px 20px; +} + +.status-tabs { + display: flex; + gap: 18px; + padding-bottom: 12px; + border-bottom: 1px solid #edf2f7; +} + +.status-tabs button { + position: relative; + border: 0; + background: transparent; + color: #64748b; + font-size: 14px; + font-weight: 760; +} + +.status-tabs button.active { + color: #0f172a; +} + +.status-tabs button.active::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: -13px; + height: 3px; + border-radius: 999px; + background: #10b981; +} + +.list-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 14px 0 10px; +} + +.filter-set { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.filter-btn, +.create-btn, +.row-action { + min-height: 36px; + border-radius: 8px; + font-size: 13px; + font-weight: 760; +} + +.filter-btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 0 12px; + border: 1px solid #d7e0ea; + background: #fff; + color: #334155; +} + +.create-btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 0 14px; + border: 0; + background: #059669; + color: #fff; + box-shadow: 0 8px 18px rgba(5, 150, 105, 0.18); +} + +.hint { + display: inline-flex; + align-items: center; + gap: 6px; + margin: 0 0 12px; + color: #64748b; + font-size: 12px; +} + +.hint .mdi { + color: #94a3b8; +} + +.table-wrap { + min-height: 0; + overflow: auto; + border: 1px solid #edf2f7; + border-radius: 12px; +} + +table { + width: 100%; + min-width: 1120px; + border-collapse: collapse; +} + +th, +td { + padding: 14px 12px; + border-bottom: 1px solid #edf2f7; + text-align: left; + vertical-align: middle; + color: #334155; + font-size: 12px; +} + +th { + background: #f8fafc; + color: #64748b; + font-weight: 800; + white-space: nowrap; +} + +tbody tr { + cursor: pointer; + transition: background 180ms ease; +} + +tbody tr:hover { + background: #f8fbff; +} + +tbody tr.spotlight { + background: linear-gradient(90deg, rgba(16, 185, 129, 0.05), rgba(59, 130, 246, 0.03)); +} + +.skill-name-cell { + display: grid; + grid-template-columns: 38px minmax(0, 1fr); + gap: 10px; + align-items: center; +} + +.skill-avatar { + width: 38px; + height: 38px; + display: grid; + place-items: center; + border-radius: 11px; + color: #fff; + font-size: 13px; + font-weight: 900; +} + +.skill-avatar.emerald { background: linear-gradient(135deg, #10b981, #059669); } +.skill-avatar.violet { background: linear-gradient(135deg, #8b5cf6, #7c3aed); } +.skill-avatar.blue { background: linear-gradient(135deg, #3b82f6, #2563eb); } +.skill-avatar.amber { background: linear-gradient(135deg, #f59e0b, #ea580c); } + +.skill-name-cell strong { + display: block; + color: #0f172a; + font-size: 13px; + font-weight: 850; +} + +.skill-name-cell span:last-child { + display: block; + margin-top: 4px; + color: #64748b; + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.scope-pill, +.status-pill { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 26px; + padding: 0 9px; + border-radius: 999px; + font-size: 11px; + font-weight: 800; + white-space: nowrap; +} + +.scope-pill { + background: #f1f5f9; + color: #475569; +} + +.status-pill.success { + background: #dcfce7; + color: #059669; +} + +.status-pill.warning { + background: #fff7ed; + color: #ea580c; +} + +.status-pill.draft { + background: #eef2ff; + color: #6366f1; +} + +.row-action { + padding: 0 12px; + border: 1px solid rgba(16, 185, 129, 0.32); + background: #fff; + color: #059669; +} + +.detail-scroll { + height: 100%; + overflow: auto; + display: grid; + gap: 16px; +} + +.detail-hero { + display: grid; + gap: 18px; + padding: 18px 20px; +} + +.hero-title { + min-width: 0; +} + +.skill-badge { + display: inline-flex; + align-items: center; + min-height: 24px; + padding: 0 8px; + border-radius: 999px; + color: #fff; + font-size: 11px; + font-weight: 800; +} + +.skill-badge.emerald { background: linear-gradient(135deg, #10b981, #059669); } +.skill-badge.violet { background: linear-gradient(135deg, #8b5cf6, #7c3aed); } +.skill-badge.blue { background: linear-gradient(135deg, #3b82f6, #2563eb); } +.skill-badge.amber { background: linear-gradient(135deg, #f59e0b, #ea580c); } + +.hero-title h2 { + margin-top: 10px; + color: #0f172a; + font-size: 24px; + font-weight: 850; +} + +.hero-title p { + margin-top: 8px; + max-width: 820px; + color: #64748b; + font-size: 14px; + line-height: 1.6; +} + +.hero-stats { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.hero-stat { + padding: 14px 16px; + border-radius: 12px; + background: linear-gradient(180deg, #ffffff, #f8fafc); + border: 1px solid #edf2f7; +} + +.hero-stat span { + display: block; + color: #64748b; + font-size: 12px; + font-weight: 700; +} + +.hero-stat strong { + display: block; + margin-top: 8px; + color: #0f172a; + font-size: 20px; + font-weight: 850; +} + +.detail-grid { + display: grid; + grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.78fr); + gap: 16px; +} + +.detail-main, +.detail-side { + display: grid; + gap: 16px; + align-content: start; +} + +.detail-card, +.side-card { + padding: 18px; +} + +.card-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 16px; +} + +.card-head h3 { + color: #0f172a; + font-size: 16px; + font-weight: 850; +} + +.card-head p { + margin-top: 6px; + color: #64748b; + font-size: 13px; + line-height: 1.5; +} + +.edit-badge { + display: inline-flex; + align-items: center; + min-height: 28px; + padding: 0 10px; + border-radius: 999px; + background: #ecfeff; + color: #0891b2; + font-size: 12px; + font-weight: 800; +} + +.form-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} + +.field { + display: grid; + gap: 7px; +} + +.field.span-2 { + grid-column: span 2; +} + +.field span { + color: #64748b; + font-size: 12px; + font-weight: 800; +} + +.field input, +.field textarea, +.prompt-block textarea { + width: 100%; + border: 1px solid #d7e0ea; + border-radius: 10px; + background: #fff; + color: #0f172a; + font-size: 13px; + line-height: 1.55; + padding: 10px 12px; + resize: vertical; +} + +.prompt-stack { + display: grid; + gap: 14px; +} + +.prompt-block { + padding: 14px; + border: 1px solid #edf2f7; + border-radius: 12px; + background: #fbfdff; +} + +.prompt-block header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-bottom: 10px; +} + +.prompt-block strong { + color: #0f172a; + font-size: 14px; + font-weight: 850; +} + +.prompt-block header span { + color: #64748b; + font-size: 12px; + font-weight: 700; +} + +.contract-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} + +.contract-panel { + padding: 14px; + border: 1px solid #edf2f7; + border-radius: 12px; + background: #fbfdff; +} + +.contract-panel h4 { + margin: 0 0 10px; + color: #0f172a; + font-size: 14px; + font-weight: 850; +} + +.contract-panel ul { + display: grid; + gap: 8px; + margin: 0; + padding-left: 18px; + color: #475569; + font-size: 13px; + line-height: 1.6; +} + +.test-row, +.tool-row, +.history-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 10px; + padding: 12px 0; + border-top: 1px solid #edf2f7; +} + +.test-row:first-child, +.tool-row:first-child, +.history-row:first-child { + border-top: 0; + padding-top: 0; +} + +.test-row strong, +.tool-row strong, +.history-row strong { + display: block; + color: #0f172a; + font-size: 13px; + font-weight: 800; +} + +.test-row span, +.tool-row span, +.history-row span, +.history-row small { + display: block; + margin-top: 4px; + color: #64748b; + font-size: 12px; + line-height: 1.5; +} + +.test-state, +.tool-state { + display: inline-flex; + align-items: center; + min-height: 24px; + padding: 0 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 800; + white-space: nowrap; +} + +.test-state.success, +.tool-state.safe { + background: #dcfce7; + color: #059669; +} + +.test-state.warning, +.tool-state.active { + background: #fff7ed; + color: #ea580c; +} + +.tag-list { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.tag-list span { + min-height: 28px; + display: inline-flex; + align-items: center; + padding: 0 10px; + border-radius: 999px; + background: #eff6ff; + color: #2563eb; + font-size: 12px; + font-weight: 800; +} + +.publish-card { + display: grid; + gap: 14px; +} + +.publish-card p, +.publish-summary span { + margin-top: 6px; + color: #64748b; + font-size: 13px; + line-height: 1.55; +} + +.publish-summary { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 42px; + padding: 0 12px; + border-radius: 10px; + background: #f8fafc; +} + +.publish-summary strong { + color: #059669; + font-size: 14px; + font-weight: 850; +} + +.detail-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px 0 0; + border-top: 1px solid #e5eaf0; +} + +.detail-action-group { + display: flex; + justify-content: flex-end; + gap: 8px; +} + +.back-action, +.minor-action, +.major-action { + height: 38px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 0 14px; + border-radius: 8px; + font-size: 13px; + font-weight: 760; +} + +.back-action { + border: 1px solid #d7e0ea; + background: #fff; + color: #475569; +} + +.minor-action { + border: 1px solid #d7e0ea; + background: #fff; + color: #334155; +} + +.major-action { + border: 1px solid #059669; + background: #059669; + color: #fff; + box-shadow: 0 4px 12px rgba(5, 150, 105, .16); +} + +.mini-btn.primary { + border-color: transparent; + background: #059669; + color: #fff; +} + +@media (max-width: 1320px) { + .hero-stats, + .form-grid, + .contract-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .detail-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 860px) { + .skill-list, + .detail-card, + .side-card, + .detail-hero { + padding: 16px; + } + + .list-toolbar, + .card-head, + .detail-actions, + .detail-action-group { + flex-direction: column; + align-items: stretch; + } + + .status-tabs, + .filter-set { + overflow-x: auto; + } + + .hero-stats, + .form-grid, + .contract-grid { + grid-template-columns: 1fr; + } + + .field.span-2 { + grid-column: span 1; + } +} diff --git a/web/src/assets/styles/views/chat-view.css b/web/src/assets/styles/views/chat-view.css new file mode 100644 index 0000000..7ca9352 --- /dev/null +++ b/web/src/assets/styles/views/chat-view.css @@ -0,0 +1,77 @@ +.qa-view { height: 100%; min-height: 0; display: grid; grid-template-rows: minmax(0, 1fr); gap: 0; overflow: hidden; animation: fadeUp 240ms var(--ease) both; } +.qa-layout { height: 100%; min-height: 0; display: grid; grid-template-columns: 330px minmax(0, 1fr) 395px; gap: 14px; overflow: hidden; } +.left-column, .right-column { height: 100%; min-height: 0; overflow: hidden; } +.left-column { display: grid; grid-template-rows: minmax(0, 1fr); } +.right-column { display: grid; grid-template-rows: minmax(0, 1.06fr) minmax(0, .94fr); gap: 12px; } +.side-panel, .info-panel { min-height: 0; padding: 16px 20px; overflow: hidden; } +.conversation-list, .info-panel { display: grid; grid-template-rows: auto minmax(0, 1fr); } +.side-panel header, .info-panel header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; } +.side-panel h3, .info-panel h3 { display: inline-flex; align-items: center; gap: 8px; color: #0f172a; font-size: 17px; font-weight: 850; } +.outline-action, .info-panel header button { height: 34px; display: inline-flex; align-items: center; gap: 6px; border: 1px solid #d8e3ed; border-radius: 8px; background: #fff; color: #0f9f78; font-size: 13px; font-weight: 750; white-space: nowrap; } +.outline-action { padding: 0 12px; } +.info-panel header button { border: 0; color: #64748b; } +.session-scroll, .top-question-list, .similar-scroll { min-height: 0; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #cbd5e1 transparent; } +.session-scroll { display: grid; align-content: start; gap: 4px; padding-right: 4px; } +.session-row { width: 100%; min-height: 48px; display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 0 10px; border: 0; border-radius: 8px; background: transparent; color: #334155; text-align: left; } +.session-row.active, .session-row:hover { background: #eaf8f1; color: #0f8f68; } +.session-row span { color: #10b981; } +.session-row strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; font-weight: 700; } +.session-row time { color: #94a3b8; font-size: 12px; } +.chat-panel { height: 100%; min-height: 0; display: grid; grid-template-rows: minmax(0, 1fr) auto; overflow: hidden; } +.message-stream { min-height: 0; display: grid; align-content: start; gap: 16px; padding: 16px 18px 8px; overflow-y: auto; scrollbar-width: thin; } +.talk-row { display: grid; grid-template-columns: 38px minmax(0, 1fr); gap: 12px; align-items: start; } +.avatar { width: 36px; height: 36px; display: grid; place-items: center; border-radius: 999px; color: #fff; font-size: 15px; font-weight: 850; } +.user-avatar { background: linear-gradient(135deg, #26364d, #61748a); } +.assistant-avatar { background: #10b981; font-size: 20px; } +.talk-content header { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; } +.talk-content header strong { color: #334155; font-size: 14px; font-weight: 800; } +.talk-content header time { color: #94a3b8; font-size: 12px; } +.user-question { display: inline-block; margin: 0; padding: 9px 16px; border-radius: 8px; background: #e8f5ef; color: #334155; font-size: 14px; line-height: 1.5; } +.answer-card, .agent-answer { max-width: 760px; border: 1px solid #dce5ef; border-radius: 10px; background: #fff; color: #334155; box-shadow: 0 8px 24px rgba(15, 23, 42, .04); } +.answer-card { display: grid; gap: 10px; padding: 13px 18px; } +.answer-card.compact { gap: 10px; } +.answer-card h4 { margin: 0 0 5px; color: #10a272; font-size: 13px; font-weight: 850; } +.answer-card p, .answer-card ul { margin: 0; font-size: 14px; line-height: 1.58; } +.answer-card ul { padding-left: 18px; } +.answer-card footer { display: flex; align-items: center; justify-content: flex-end; gap: 10px; color: #64748b; font-size: 12px; } +.answer-card footer button { width: 28px; height: 28px; display: grid; place-items: center; border: 0; border-radius: 6px; background: transparent; color: #64748b; } +.answer-card footer button:hover { background: #f1f5f9; color: #0f9f78; } +.agent-answer { margin: 0; padding: 12px 16px; font-size: 14px; line-height: 1.65; } +.composer-wrap { border-top: 1px solid #eef2f7; padding: 10px 14px 12px; background: #fff; } +.prompt-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; overflow-x: auto; } +.prompt-toolbar span { flex: 0 0 auto; color: #64748b; font-size: 13px; font-weight: 800; } +.prompt-toolbar button { height: 34px; flex: 0 0 auto; display: inline-flex; align-items: center; gap: 7px; padding: 0 14px; border: 1px solid #dce5ef; border-radius: 8px; background: #fff; color: #334155; font-size: 13px; font-weight: 750; } +.prompt-toolbar button i { color: #10b981; } +.prompt-toolbar .icon-refresh { width: 34px; padding: 0; justify-content: center; } +.composer { min-height: 64px; display: grid; grid-template-columns: minmax(0, 1fr) 48px; align-items: center; gap: 10px; padding: 8px; border: 1px solid #cbd8e5; border-radius: 8px; background: linear-gradient(180deg, #fff, #fbfdff); box-shadow: 0 1px 2px rgba(15, 23, 42, .04); transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease; } +.composer:focus-within { border-color: rgba(16, 185, 129, .58); background: #fff; box-shadow: 0 0 0 3px rgba(16, 185, 129, .11), 0 10px 24px rgba(15, 23, 42, .06); } +.composer textarea { height: 48px; min-height: 48px; resize: none; border: 0; padding: 5px 8px; background: transparent; color: #0f172a; font-size: 14px; line-height: 1.55; } +.composer textarea::placeholder { color: #94a3b8; } +.composer textarea:focus { outline: none; } +.send-button { width: 48px; height: 48px; display: grid; place-items: center; border: 0; border-radius: 8px; background: #10b981; color: #fff; font-size: 20px; box-shadow: 0 8px 18px rgba(16, 185, 129, .20); transition: background 160ms ease, transform 160ms ease, box-shadow 160ms ease; } +.send-button:hover { background: #0ea672; box-shadow: 0 10px 22px rgba(16, 185, 129, .24); } +.send-button:active { transform: scale(.96); } +.hot-top-panel h3 i { color: #ef4444; } +.top-question-list { display: grid; align-content: start; gap: 8px; padding-right: 4px; } +.top-question-list button { min-height: 42px; display: grid; grid-template-columns: 34px minmax(0, 1fr) 14px; align-items: center; gap: 10px; padding: 0 8px; border: 1px solid #e2e8f0; border-radius: 8px; background: #fff; color: #334155; text-align: left; } +.top-question-list button:hover { border-color: rgba(16, 185, 129, .32); color: #0f9f78; } +.top-question-list strong { color: #10b981; font-size: 13px; font-weight: 850; font-variant-numeric: tabular-nums; } +.top-question-list span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; font-weight: 750; } +.top-question-list i { color: #94a3b8; } +.similar-panel { display: grid; } +.similar-scroll { display: grid; align-content: start; padding-right: 4px; } +.similar-row { min-height: 46px; display: grid; grid-template-columns: minmax(0, 1fr) 48px 14px; align-items: center; gap: 10px; border: 0; border-top: 1px solid #eef2f7; background: transparent; color: #334155; text-align: left; } +.similar-row:first-child { border-top: 0; } +.similar-row span { min-width: 0; display: inline-flex; align-items: center; gap: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; font-weight: 700; } +.similar-row span i { color: #64748b; font-size: 17px; } +.similar-row strong { height: 26px; display: inline-flex; align-items: center; justify-content: center; border-radius: 8px; background: #e8f8f0; color: #15945f; font-size: 13px; font-weight: 850; } +.similar-row > i { color: #94a3b8; } +@media (max-width: 1480px) { .qa-layout { grid-template-columns: 300px minmax(0, 1fr) 360px; } } +@media (max-width: 1280px) { + .qa-layout { grid-template-columns: 1fr; overflow-y: auto; } + .left-column, .right-column { grid-template-columns: repeat(2, minmax(0, 1fr)); overflow: visible; } +} +@media (max-width: 760px) { + .left-column, .right-column { grid-template-columns: 1fr; } + .composer { grid-template-columns: minmax(0, 1fr) 48px; } +} diff --git a/web/src/assets/styles/views/employee-management-view.css b/web/src/assets/styles/views/employee-management-view.css new file mode 100644 index 0000000..53ec02f --- /dev/null +++ b/web/src/assets/styles/views/employee-management-view.css @@ -0,0 +1,658 @@ +.employee-center { + height: 100%; + min-height: 0; +} + +.employee-view-enter-active, +.employee-view-leave-active { + transition: opacity 220ms ease, transform 280ms var(--ease); +} + +.employee-view-enter-from, +.employee-view-leave-to { + opacity: 0; + transform: translateY(14px); +} + +.employee-list, +.employee-detail { + height: 100%; + min-height: 0; +} + +.employee-list { + display: grid; + grid-template-rows: auto auto auto minmax(0, 1fr); + padding: 18px 20px; +} + +.employee-detail { + display: grid; + grid-template-rows: minmax(0, 1fr) auto; + gap: 12px; +} + +.status-tabs { + display: flex; + gap: 18px; + padding-bottom: 12px; + border-bottom: 1px solid #edf2f7; +} + +.status-tabs button { + position: relative; + border: 0; + background: transparent; + color: #64748b; + font-size: 14px; + font-weight: 760; +} + +.status-tabs button.active { + color: #0f172a; +} + +.status-tabs button.active::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: -13px; + height: 3px; + border-radius: 999px; + background: #10b981; +} + +.list-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 14px 0 10px; +} + +.filter-set { + display: flex; + gap: 10px; + flex-wrap: wrap; + align-items: center; +} + +.list-search { + position: relative; + width: 240px; +} + +.list-search .mdi { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #64748b; + font-size: 15px; +} + +.list-search input { + width: 100%; + height: 38px; + padding: 0 12px 0 36px; + border: 1px solid #d7e0ea; + border-radius: 8px; + background: #fff; + color: #0f172a; + font-size: 13px; +} + +.filter-btn, +.create-btn, +.row-action { + min-height: 36px; + border-radius: 8px; + font-size: 13px; + font-weight: 760; +} + +.filter-btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 0 12px; + border: 1px solid #d7e0ea; + background: #fff; + color: #334155; +} + +.create-btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 0 14px; + border: 0; + background: #059669; + color: #fff; + box-shadow: 0 8px 18px rgba(5, 150, 105, 0.18); +} + +.hint { + display: inline-flex; + align-items: center; + gap: 6px; + margin: 0 0 12px; + color: #64748b; + font-size: 12px; +} + +.table-wrap { + min-height: 0; + overflow: auto; + border: 1px solid #edf2f7; + border-radius: 12px; +} + +table { + width: 100%; + min-width: 1320px; + border-collapse: collapse; +} + +th, +td { + padding: 14px 12px; + border-bottom: 1px solid #edf2f7; + text-align: center; + vertical-align: middle; + color: #334155; + font-size: 12px; +} + +th { + background: #f8fafc; + color: #64748b; + font-weight: 800; + white-space: nowrap; +} + +tbody tr { + cursor: pointer; + transition: background 180ms ease; +} + +tbody tr:hover { + background: #f8fbff; +} + +tbody tr.spotlight { + background: linear-gradient(90deg, rgba(16, 185, 129, 0.05), rgba(59, 130, 246, 0.03)); +} + +.employee-cell { + display: grid; + grid-template-columns: 38px minmax(0, 1fr); + gap: 10px; + align-items: center; + text-align: left; +} + +.employee-avatar { + width: 38px; + height: 38px; + display: grid; + place-items: center; + border-radius: 11px; + background: linear-gradient(135deg, #10b981, #059669); + color: #fff; + font-size: 13px; + font-weight: 900; +} + +.employee-cell strong { + display: block; + color: #0f172a; + font-size: 13px; + font-weight: 850; +} + +.employee-cell span:last-child { + display: block; + margin-top: 4px; + color: #64748b; + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.level-pill, +.status-pill, +.role-pill, +.more-pill { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 26px; + padding: 0 9px; + border-radius: 999px; + font-size: 11px; + font-weight: 800; + white-space: nowrap; +} + +.level-pill { + background: #eef2ff; + color: #4f46e5; +} + +.status-pill.success { + background: #dcfce7; + color: #059669; +} + +.status-pill.warning { + background: #fff7ed; + color: #ea580c; +} + +.status-pill.neutral { + background: #f1f5f9; + color: #475569; +} + +.role-stack { + display: flex; + gap: 6px; + flex-wrap: wrap; +} + +.role-pill { + background: #ecfdf5; + color: #059669; +} + +.more-pill { + background: #f1f5f9; + color: #475569; +} + +.row-action { + padding: 0 12px; + border: 1px solid rgba(16, 185, 129, 0.32); + background: #fff; + color: #059669; +} + +.detail-scroll { + height: 100%; + overflow: auto; + display: grid; + gap: 16px; +} + +.detail-hero { + display: grid; + gap: 18px; + padding: 18px 20px; +} + +.hero-profile { + display: flex; + align-items: center; + gap: 16px; +} + +.hero-avatar { + width: 64px; + height: 64px; + display: grid; + place-items: center; + border-radius: 18px; + background: linear-gradient(135deg, #10b981, #047857); + color: #fff; + font-size: 24px; + font-weight: 900; +} + +.hero-tag { + display: inline-flex; + align-items: center; + min-height: 26px; + padding: 0 10px; + border-radius: 999px; + background: #eff6ff; + color: #2563eb; + font-size: 12px; + font-weight: 800; +} + +.hero-copy h2 { + margin-top: 10px; + color: #0f172a; + font-size: 24px; + font-weight: 850; +} + +.hero-copy p { + margin-top: 8px; + color: #64748b; + font-size: 14px; + line-height: 1.6; +} + +.hero-stats { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.hero-stat { + padding: 14px 16px; + border-radius: 12px; + background: linear-gradient(180deg, #ffffff, #f8fafc); + border: 1px solid #edf2f7; +} + +.hero-stat span { + display: block; + color: #64748b; + font-size: 12px; + font-weight: 700; +} + +.hero-stat strong { + display: block; + margin-top: 8px; + color: #0f172a; + font-size: 18px; + font-weight: 850; +} + +.detail-grid { + display: grid; + grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.82fr); + gap: 16px; +} + +.detail-main, +.detail-side { + display: grid; + gap: 16px; + align-content: start; +} + +.detail-card, +.side-card { + padding: 18px; +} + +.card-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 16px; +} + +.card-head h3 { + color: #0f172a; + font-size: 16px; + font-weight: 850; +} + +.card-head p { + margin-top: 6px; + color: #64748b; + font-size: 13px; + line-height: 1.5; +} + +.count-badge { + display: inline-flex; + align-items: center; + min-height: 28px; + padding: 0 10px; + border-radius: 999px; + background: #ecfeff; + color: #0891b2; + font-size: 12px; + font-weight: 800; +} + +.form-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} + +.field { + display: grid; + gap: 7px; +} + +.field span { + color: #64748b; + font-size: 12px; + font-weight: 800; +} + +.field input { + width: 100%; + border: 1px solid #d7e0ea; + border-radius: 10px; + background: #fff; + color: #0f172a; + font-size: 13px; + line-height: 1.55; + padding: 10px 12px; +} + +.role-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} + +.role-card { + display: grid; + grid-template-columns: 18px minmax(0, 1fr); + gap: 12px; + align-items: start; + padding: 14px; + border: 1px solid #edf2f7; + border-radius: 14px; + background: #fbfdff; +} + +.role-card.active { + border-color: rgba(16, 185, 129, 0.32); + background: linear-gradient(180deg, rgba(240, 253, 244, 0.85), #ffffff); +} + +.role-card input { + margin-top: 3px; +} + +.role-copy strong { + display: block; + color: #0f172a; + font-size: 14px; + font-weight: 850; +} + +.role-copy p { + margin-top: 6px; + color: #64748b; + font-size: 12px; + line-height: 1.55; +} + +.tag-list { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.bullet-list { + display: grid; + gap: 8px; + margin: 14px 0 0; + padding-left: 18px; + color: #475569; + font-size: 13px; + line-height: 1.6; +} + +.history-list { + display: grid; +} + +.history-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 10px; + padding: 12px 0; + border-top: 1px solid #edf2f7; +} + +.history-row:first-child { + border-top: 0; + padding-top: 0; +} + +.history-row strong { + display: block; + color: #0f172a; + font-size: 13px; + font-weight: 800; +} + +.history-row span, +.history-row small { + display: block; + margin-top: 4px; + color: #64748b; + font-size: 12px; + line-height: 1.5; +} + +.publish-card { + display: grid; + gap: 14px; +} + +.publish-card p, +.publish-summary span { + margin-top: 6px; + color: #64748b; + font-size: 13px; + line-height: 1.55; +} + +.publish-summary { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 42px; + padding: 0 12px; + border-radius: 10px; + background: #f8fafc; +} + +.publish-summary strong { + color: #059669; + font-size: 14px; + font-weight: 850; +} + +.detail-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px 0 0; + border-top: 1px solid #e5eaf0; +} + +.detail-action-group { + display: flex; + justify-content: flex-end; + gap: 8px; +} + +.back-action, +.minor-action, +.major-action { + height: 38px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 0 14px; + border-radius: 8px; + font-size: 13px; + font-weight: 760; +} + +.back-action { + border: 1px solid #d7e0ea; + background: #fff; + color: #475569; +} + +.minor-action { + border: 1px solid #d7e0ea; + background: #fff; + color: #334155; +} + +.major-action { + border: 1px solid #059669; + background: #059669; + color: #fff; + box-shadow: 0 4px 12px rgba(5, 150, 105, 0.16); +} + +@media (max-width: 1320px) { + .hero-stats, + .form-grid, + .role-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .detail-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 860px) { + .employee-list, + .detail-card, + .side-card, + .detail-hero { + padding: 16px; + } + + .list-toolbar, + .card-head, + .detail-actions, + .detail-action-group, + .hero-profile { + flex-direction: column; + align-items: stretch; + } + + .status-tabs, + .filter-set { + overflow-x: auto; + } + + .list-search { + width: 100%; + } + + .hero-stats, + .form-grid, + .role-grid { + grid-template-columns: 1fr; + } +} diff --git a/src/views/LoginView.vue b/web/src/assets/styles/views/login-view.css similarity index 67% rename from src/views/LoginView.vue rename to web/src/assets/styles/views/login-view.css index 19e874f..39a3649 100644 --- a/src/views/LoginView.vue +++ b/web/src/assets/styles/views/login-view.css @@ -1,170 +1,3 @@ - - - - - diff --git a/web/src/assets/styles/views/overview-view.css b/web/src/assets/styles/views/overview-view.css new file mode 100644 index 0000000..e9f8d60 --- /dev/null +++ b/web/src/assets/styles/views/overview-view.css @@ -0,0 +1,387 @@ +.dashboard { + display: grid; + gap: 16px; + animation: fadeUp 260ms var(--ease) both; +} + +.kpi-grid { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 12px; +} + +.kpi-card { + position: relative; + padding: 12px 14px 10px; + display: flex; + flex-direction: column; + border-left: 3px solid var(--accent); + animation: dashboardItemIn 520ms var(--ease) both; + animation-delay: var(--delay, 0ms); + transition: box-shadow 200ms ease, transform 200ms ease; +} + +.kpi-card:hover { + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06); + transform: translateY(-1px); +} + +.kpi-head { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 8px; +} + +.kpi-icon { + width: 26px; + height: 26px; + display: grid; + place-items: center; + border-radius: 7px; + background: color-mix(in srgb, var(--accent) 10%, white); + color: var(--accent); + font-size: 14px; + flex: 0 0 auto; + animation: iconPop 560ms var(--ease) both; + animation-delay: calc(var(--delay, 0ms) + 100ms); +} + +.kpi-label { + color: #64748b; + font-size: 11px; + font-weight: 500; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.kpi-value { + display: block; + min-height: 22px; + color: #0f172a; + font-size: clamp(16px, 1.2vw, 20px); + line-height: 1; + font-weight: 800; + font-variant-numeric: tabular-nums; + white-space: nowrap; + margin-bottom: 6px; + letter-spacing: 0; +} + +.kpi-trend { + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; + padding-top: 6px; + border-top: 1px solid #f1f5f9; +} + +.kpi-badge { + display: inline-flex; + align-items: center; + gap: 2px; + padding: 1px 6px; + border-radius: 4px; + font-size: 11px; + font-weight: 700; + line-height: 1.45; +} + +.kpi-badge.up { + background: rgba(239, 68, 68, 0.08); + color: #dc2626; +} + +.kpi-badge.down { + background: rgba(22, 163, 74, 0.08); + color: #16a34a; +} + +.kpi-badge .mdi { + font-size: 11px; +} + +.kpi-delta { + color: #94a3b8; + font-size: 10px; + white-space: nowrap; +} + +.content-grid { + display: grid; + grid-template-columns: repeat(12, minmax(0, 1fr)); + gap: 24px; +} + +.dashboard-card { + padding: 20px; + transition: box-shadow 200ms ease, transform 200ms ease; + animation: dashboardItemIn 560ms var(--ease) both; +} + +.top-grid .dashboard-card:nth-child(1) { animation-delay: 80ms; } +.top-grid .dashboard-card:nth-child(2) { animation-delay: 150ms; } +.top-grid .dashboard-card:nth-child(3) { animation-delay: 220ms; } +.bottom-grid .dashboard-card:nth-child(1) { animation-delay: 290ms; } +.bottom-grid .dashboard-card:nth-child(2) { animation-delay: 360ms; } +.bottom-grid .dashboard-card:nth-child(3) { animation-delay: 430ms; } + +.dashboard-card:hover { + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06); + transform: translateY(-1px); +} + +.trend-panel, +.rank-panel { + grid-column: span 6; +} + +.donut-panel, +.bottleneck-panel, +.budget-panel { + grid-column: span 3; +} + +.card-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 16px; +} + +.card-head h3 { + color: #1e293b; + font-size: 16px; + font-weight: 600; + line-height: 1.35; +} + +.card-head .mdi { + color: #94a3b8; + font-size: 12px; + vertical-align: 1px; +} + +.card-select { + height: 30px; + min-width: 82px; + padding: 0 8px; + border: 1px solid #cbd5e1; + border-radius: 4px; + background: #fff; + color: #334155; + font-size: 14px; +} + +.panel-note { + margin-top: 8px; + color: #64748b; + font-size: 12px; + text-align: center; +} + +.bottleneck-panel, +.budget-panel { + display: flex; + flex-direction: column; +} + +.bottleneck-panel .text-link, +.budget-panel .text-link { + margin-top: auto; +} + +.bottleneck-list { + flex: 1; + display: grid; + gap: 16px; + align-content: center; +} + +.bottleneck-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + animation: listRowIn 460ms var(--ease) both; + animation-delay: var(--delay, 0ms); +} + +.reviewer { + display: flex; + align-items: center; + gap: 12px; + min-width: 0; +} + +.reviewer-avatar { + width: 32px; + height: 32px; + display: grid; + place-items: center; + border-radius: 999px; + background: #e2f6ef; + color: #047857; + font-size: 13px; + font-weight: 700; + flex: 0 0 auto; +} + +.reviewer strong, +.reviewer-stats strong { + display: block; + color: #1e293b; + font-size: 14px; + font-weight: 500; +} + +.reviewer span, +.reviewer-stats span { + display: block; + margin-top: 4px; + color: #64748b; + font-size: 12px; +} + +.reviewer-stats { + text-align: right; +} + +.status-tag { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; +} + +.status-tag.danger { + background: rgba(239,68,68,.10); + color: #ef4444; +} + +.status-tag.warning { + background: rgba(245,158,11,.10); + color: #f59e0b; +} + +.status-tag.success { + background: rgba(16,185,129,.10); + color: #16a34a; +} + +.text-link { + width: 100%; + margin-top: 16px; + display: inline-flex; + align-items: center; + justify-content: space-between; + padding: 16px 0 0; + border: 0; + border-top: 1px solid #f1f5f9; + background: transparent; + color: #10b981; + font-size: 14px; +} + +@keyframes dashboardItemIn { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes listRowIn { + from { + opacity: 0; + transform: translateX(-10px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes iconPop { + 0% { + opacity: 0; + transform: scale(.82); + } + 70% { + opacity: 1; + transform: scale(1.04); + } + 100% { + opacity: 1; + transform: scale(1); + } +} + +@media (prefers-reduced-motion: reduce) { + .kpi-card, + .dashboard-card, + .bottleneck-row { + animation: none; + } +} + +@media (max-width: 1320px) { + .kpi-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .trend-panel, + .rank-panel { + grid-column: span 12; + } + + .donut-panel, + .bottleneck-panel, + .budget-panel { + grid-column: span 6; + } +} + +@media (max-width: 860px) { + .kpi-grid, + .content-grid { + grid-template-columns: 1fr; + } + + .trend-panel, + .rank-panel, + .donut-panel, + .bottleneck-panel, + .budget-panel { + grid-column: span 1; + } + + .card-head { + align-items: flex-start; + flex-direction: column; + } + + .donut-wrap { + grid-template-columns: 1fr; + } + + .rank-row { + grid-template-columns: 24px 64px minmax(0, 1fr); + } + + .rank-value { + grid-column: 2 / -1; + } + + .budget-summary { + grid-template-columns: 1fr; + } +} diff --git a/web/src/assets/styles/views/policies-view.css b/web/src/assets/styles/views/policies-view.css new file mode 100644 index 0000000..f8efa87 --- /dev/null +++ b/web/src/assets/styles/views/policies-view.css @@ -0,0 +1,643 @@ +.knowledge-page { + height: 100%; + min-height: 0; + overflow: hidden; + animation: fadeUp 220ms var(--ease) both; +} + +.knowledge-grid { + height: 100%; + min-height: 0; + display: grid; + grid-template-columns: minmax(0, 1fr) 0; + gap: 0; + transition: grid-template-columns 320ms var(--ease), gap 320ms var(--ease); +} + +.knowledge-grid.has-preview { + grid-template-columns: minmax(560px, 1fr) minmax(420px, 0.82fr); + gap: 16px; +} + +.knowledge-main, +.preview-column { + min-width: 0; + min-height: 0; +} + +.knowledge-main { + overflow: hidden; +} + +.library-panel { + height: 100%; + min-height: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + padding: 16px 18px; + overflow: hidden; +} + +.panel-title { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; +} + +.panel-title h2, +.preview-head h2 { + color: #0f172a; + font-size: 16px; + font-weight: 850; +} + +.panel-title p, +.preview-head p { + margin-top: 6px; + color: #64748b; + font-size: 13px; + line-height: 1.5; +} + +.preview-hint { + min-height: 28px; + display: inline-flex; + align-items: center; + padding: 0 10px; + border-radius: 999px; + background: #f1f5f9; + color: #64748b; + font-size: 12px; + font-weight: 800; + white-space: nowrap; + transition: background 220ms ease, color 220ms ease; +} + +.preview-hint.active { + background: #dcfce7; + color: #059669; +} + +.library-body { + min-height: 0; + display: grid; + grid-template-columns: 180px minmax(0, 1fr); + gap: 14px; + margin-top: 16px; +} + +.folder-rail { + min-height: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; + gap: 12px; + border-right: 1px solid #edf2f7; + padding-right: 12px; +} + +.folder-search { + height: 36px; + display: grid; + grid-template-columns: 18px minmax(0, 1fr) 24px; + align-items: center; + gap: 6px; + padding: 0 8px; + border: 1px solid #d7e0ea; + border-radius: 8px; + color: #64748b; +} + +.folder-search input { + min-width: 0; + border: 0; + color: #0f172a; + font-size: 13px; +} + +.folder-search input:focus { + outline: none; +} + +.folder-search button { + border: 0; + background: transparent; + color: #64748b; +} + +.folder-tree { + min-height: 0; + display: grid; + align-content: start; + gap: 6px; + overflow-y: auto; +} + +.folder-tree button { + min-height: 34px; + display: grid; + grid-template-columns: 18px minmax(0, 1fr) auto; + align-items: center; + gap: 8px; + padding: 0 9px; + border: 0; + border-radius: 7px; + background: transparent; + color: #334155; + font-size: 13px; + text-align: left; +} + +.folder-tree button.active { + background: #dcfce7; + color: #059669; + font-weight: 850; +} + +.folder-tree b { + min-width: 24px; + height: 20px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 999px; + background: #f1f5f9; + color: #64748b; + font-size: 11px; +} + +.new-folder-btn { + min-height: 36px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + border: 1px solid rgba(16, 185, 129, .28); + border-radius: 8px; + background: #f0fdf4; + color: #059669; + font-size: 13px; + font-weight: 850; +} + +.document-area { + min-width: 0; + min-height: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; + gap: 12px; +} + +.upload-zone { + min-height: 112px; + display: grid; + place-items: center; + align-content: center; + gap: 8px; + border: 1px dashed #93c5fd; + border-radius: 10px; + background: #f8fbff; + color: #334155; + text-align: center; +} + +.upload-zone i { + color: #2563eb; + font-size: 31px; +} + +.upload-zone strong { + font-size: 13px; + font-weight: 850; +} + +.upload-zone span { + color: #64748b; + font-size: 12px; +} + +.doc-table-wrap { + min-height: 0; + overflow: auto; +} + +table { + width: 100%; + min-width: 690px; + border-collapse: collapse; +} + +th, +td { + padding: 12px 10px; + border-bottom: 1px solid #edf2f7; + color: #24324a; + font-size: 12px; + line-height: 1.35; + text-align: left; + vertical-align: middle; +} + +th { + background: #f7fafc; + color: #64748b; + font-weight: 800; + white-space: nowrap; +} + +.doc-row { + cursor: pointer; + transition: background 180ms ease, box-shadow 180ms ease; +} + +.doc-row:hover { + background: #f8fbff; +} + +.doc-row.selected { + background: linear-gradient(90deg, rgba(16, 185, 129, 0.08), rgba(59, 130, 246, 0.04)); + box-shadow: inset 3px 0 0 #10b981; +} + +.file-name { + display: inline-flex; + align-items: center; + gap: 7px; + font-weight: 750; + white-space: nowrap; +} + +.file-name .pdf, +.viewer-filetype.pdf { color: #ef4444; } +.file-name .word, +.viewer-filetype.word { color: #2563eb; } +.file-name .excel, +.viewer-filetype.excel { color: #10b981; } + +.doc-tag { + display: inline-flex; + align-items: center; + min-height: 22px; + padding: 0 7px; + border-radius: 6px; + background: #f1f5f9; + color: #64748b; + font-size: 11px; + font-weight: 750; +} + +.state-tag { + min-height: 22px; + display: inline-flex; + align-items: center; + padding: 0 8px; + border-radius: 6px; + font-size: 11px; + font-weight: 800; + white-space: nowrap; +} + +.state-tag.success { + background: #dcfce7; + color: #059669; +} + +.state-tag.warning { + background: #ffedd5; + color: #f97316; +} + +.more-btn { + border: 0; + background: transparent; + color: #2563eb; +} + +.list-foot { + display: grid; + grid-template-columns: auto auto 1fr auto; + align-items: center; + gap: 10px; + color: #64748b; + font-size: 13px; +} + +.list-foot button { + min-height: 32px; + border: 1px solid #d7e0ea; + border-radius: 8px; + background: #fff; + color: #334155; + font-weight: 750; +} + +.pager { + display: inline-flex; + gap: 6px; +} + +.pager button { + width: 32px; + padding: 0; +} + +.pager button.active { + border-color: #059669; + background: #059669; + color: #fff; +} + +.list-foot input { + width: 42px; + height: 30px; + border: 1px solid #d7e0ea; + border-radius: 7px; + text-align: center; +} + +.preview-column { + min-width: 0; + min-height: 0; + overflow: hidden; +} + +.preview-panel { + height: 100%; + min-height: 0; + display: grid; + grid-template-rows: auto auto minmax(0, 1fr); + padding: 18px; + overflow: hidden; +} + +.preview-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 14px; +} + +.preview-kicker { + display: inline-flex; + align-items: center; + min-height: 24px; + padding: 0 8px; + border-radius: 999px; + background: #ecfdf5; + color: #059669; + font-size: 11px; + font-weight: 800; +} + +.preview-actions { + display: flex; + align-items: center; + gap: 8px; +} + +.mini-action, +.icon-action, +.viewer-toolbar-actions button { + border: 1px solid #d7e0ea; + border-radius: 8px; + background: #fff; + color: #334155; +} + +.mini-action { + min-height: 34px; + display: inline-flex; + align-items: center; + gap: 6px; + padding: 0 12px; + font-size: 12px; + font-weight: 800; +} + +.icon-action { + width: 34px; + height: 34px; + display: grid; + place-items: center; +} + +.preview-meta { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + margin-top: 14px; + padding-top: 14px; + border-top: 1px solid #edf2f7; +} + +.preview-meta span { + display: inline-flex; + align-items: center; + gap: 6px; + min-height: 28px; + padding: 0 10px; + border-radius: 999px; + background: #f8fafc; + color: #475569; + font-size: 12px; + font-weight: 700; +} + +.preview-viewer { + min-height: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + gap: 12px; + margin-top: 14px; +} + +.viewer-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 44px; + padding: 0 12px; + border: 1px solid #e2e8f0; + border-radius: 10px; + background: #f8fafc; +} + +.viewer-filetype { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 12px; + font-weight: 800; +} + +.viewer-toolbar-actions { + display: inline-flex; + gap: 8px; +} + +.viewer-toolbar-actions button { + width: 32px; + height: 32px; + display: grid; + place-items: center; +} + +.page-stage { + min-height: 0; + overflow: auto; + display: grid; + gap: 16px; + padding-right: 4px; +} + +.page-sheet { + display: grid; + gap: 16px; + padding: 18px; + border: 1px solid #e2e8f0; + border-radius: 14px; + background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%); + box-shadow: 0 12px 28px rgba(15, 23, 42, 0.06); + animation: previewSheetIn 360ms var(--ease) both; + animation-delay: var(--page-delay, 0ms); +} + +.page-title { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; +} + +.page-title strong { + color: #0f172a; + font-size: 15px; + font-weight: 850; +} + +.page-title span, +.page-title b { + display: block; + margin-top: 4px; + color: #64748b; + font-size: 12px; + font-weight: 700; +} + +.summary-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 10px; +} + +.summary-item { + display: grid; + gap: 6px; + padding: 12px; + border-radius: 10px; + background: #f8fafc; +} + +.summary-item span { + color: #64748b; + font-size: 11px; + font-weight: 700; +} + +.summary-item strong { + color: #0f172a; + font-size: 14px; + font-weight: 850; +} + +.page-content { + display: grid; + gap: 12px; +} + +.content-block { + padding: 14px; + border-radius: 12px; + background: #ffffff; + border: 1px solid #edf2f7; +} + +.content-block h3 { + margin: 0 0 8px; + color: #0f172a; + font-size: 13px; + font-weight: 850; +} + +.content-block ul { + display: grid; + gap: 8px; + margin: 0; + padding-left: 18px; + color: #475569; + font-size: 12px; + line-height: 1.6; +} + +.preview-panel-enter-active, +.preview-panel-leave-active { + transition: opacity 240ms ease, transform 320ms var(--ease); +} + +.preview-panel-enter-from, +.preview-panel-leave-to { + opacity: 0; + transform: translateX(24px) scale(0.98); +} + +@keyframes previewSheetIn { + from { + opacity: 0; + transform: translateY(14px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 1320px) { + .knowledge-grid.has-preview { + grid-template-columns: minmax(0, 1fr) minmax(360px, 0.78fr); + } +} + +@media (max-width: 1080px) { + .knowledge-grid, + .knowledge-grid.has-preview { + grid-template-columns: 1fr; + gap: 16px; + overflow-y: auto; + } + + .library-body { + grid-template-columns: 1fr; + } + + .folder-rail { + border-right: 0; + border-bottom: 1px solid #edf2f7; + padding: 0 0 12px; + } +} + +@media (max-width: 760px) { + .panel-title, + .preview-head, + .viewer-toolbar { + flex-direction: column; + align-items: stretch; + } + + .summary-grid, + .list-foot { + grid-template-columns: 1fr; + } +} diff --git a/web/src/assets/styles/views/requests-view.css b/web/src/assets/styles/views/requests-view.css new file mode 100644 index 0000000..e523faa --- /dev/null +++ b/web/src/assets/styles/views/requests-view.css @@ -0,0 +1,523 @@ +.travel-page { + height: 100%; + min-height: 0; + display: grid; + grid-template-rows: minmax(0, 1fr); + gap: 14px; + animation: fadeUp 220ms var(--ease) both; + overflow: hidden; +} + +.travel-list { + min-height: 0; + display: grid; + grid-template-rows: auto auto auto minmax(0, 1fr) auto; + padding: 16px 18px; + overflow: hidden; +} + +.list-search { + position: relative; + width: 220px; +} + +.list-search .mdi { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #64748b; + font-size: 15px; +} + +.list-search input { + width: 100%; + height: 38px; + padding: 0 12px 0 36px; + border: 1px solid #d7e0ea; + border-radius: 8px; + background: #fff; + color: #0f172a; + font-size: 13px; + transition: border-color 160ms ease, box-shadow 160ms ease; +} + +.list-search input::placeholder { + color: #8da0b4; +} + +.list-search input:focus { + border-color: #10b981; + box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.14); + outline: none; +} + +.status-tabs { + display: flex; + gap: 28px; + margin-top: 14px; + border-bottom: 1px solid #dbe4ee; +} + +.status-tabs button { + position: relative; + min-height: 36px; + border: 0; + background: transparent; + color: #64748b; + font-size: 14px; + font-weight: 750; +} + +.status-tabs button.active { + color: #059669; +} + +.status-tabs button.active::after { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: -1px; + height: 3px; + border-radius: 999px 999px 0 0; + background: #10b981; +} + +.list-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-top: 14px; +} + +.filter-set { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.create-request-btn { + min-height: 40px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 0 18px; + border: 0; + border-radius: 10px; + background: linear-gradient(135deg, #10b981, #059669); + color: #fff; + font-size: 14px; + font-weight: 800; + white-space: nowrap; + box-shadow: 0 10px 24px rgba(5, 150, 105, 0.2); + transition: transform 160ms ease, box-shadow 160ms ease, filter 160ms ease; +} + +.create-request-btn:hover { + transform: translateY(-1px); + box-shadow: 0 14px 28px rgba(5, 150, 105, 0.24); + filter: saturate(1.02); +} + +.filter-btn, +.page-size { + min-height: 38px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 9px; + padding: 0 14px; + border-radius: 8px; + font-size: 14px; + font-weight: 750; + white-space: nowrap; + border: 1px solid #d7e0ea; + background: #fff; + color: #334155; +} + +.filter-btn { + min-width: 120px; + justify-content: space-between; +} + +.date-range-filter { + position: relative; +} + +.date-range-trigger { + min-width: 160px; +} + +.date-range-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 110px; +} + +.date-range-popover { + position: absolute; + top: calc(100% + 8px); + left: 0; + width: 320px; + z-index: 40; + display: grid; + gap: 14px; + padding: 16px; + border: 1px solid #d7e0ea; + border-radius: 12px; + background: #fff; + box-shadow: 0 18px 42px rgba(15, 23, 42, .16); +} + +.date-range-popover header, +.date-range-popover footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.date-range-popover header strong { + color: #0f172a; + font-size: 15px; +} + +.date-range-popover header button { + width: 30px; + height: 30px; + display: grid; + place-items: center; + border: 0; + border-radius: 8px; + background: transparent; + color: #64748b; +} + +.date-range-popover header button:hover { + background: #f1f5f9; + color: #0f172a; +} + +.date-range-fields { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} + +.date-range-fields label { + display: grid; + gap: 6px; +} + +.date-range-fields span { + color: #64748b; + font-size: 12px; + font-weight: 700; +} + +.date-range-fields input { + width: 100%; + height: 38px; + padding: 0 9px; + border: 1px solid #d7e0ea; + border-radius: 8px; + color: #0f172a; + font-size: 13px; +} + +.date-range-fields input:focus { + border-color: #10b981; + box-shadow: 0 0 0 3px rgba(16, 185, 129, .12); + outline: none; +} + +.ghost-btn, +.apply-btn { + height: 36px; + padding: 0 14px; + border-radius: 8px; + font-size: 13px; + font-weight: 750; +} + +.ghost-btn { + border: 1px solid #d7e0ea; + background: #fff; + color: #334155; +} + +.apply-btn { + border: 0; + background: #10b981; + color: #fff; +} + +.apply-btn:disabled { + cursor: not-allowed; + background: #cbd5e1; +} + +.filter-btn:hover, +.page-size:hover { + border-color: rgba(16, 185, 129, .32); + color: #0f9f78; +} + +.hint { + display: inline-flex; + align-items: center; + gap: 7px; + margin-top: 10px; + color: #64748b; + font-size: 13px; +} + +.hint .mdi { + color: #64748b; +} + +.table-wrap { + margin-top: 10px; + overflow-x: auto; + overflow-y: auto; + border: 1px solid #edf2f7; + border-radius: 10px; +} + +table { + height: 100%; + width: 100%; + min-width: 1140px; + border-collapse: collapse; + table-layout: fixed; +} + +colgroup col { + width: 10%; +} + +th, +td { + padding: 13px 12px; + border-bottom: 1px solid #edf2f7; + color: #24324a; + font-size: 14px; + line-height: 1.35; + text-align: center; + vertical-align: middle; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +th { + position: sticky; + top: 0; + z-index: 1; + background: #f7fafc; + color: #64748b; + font-size: 13px; + font-weight: 800; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +tbody tr { + cursor: pointer; +} + +tbody tr:hover { + background: linear-gradient(90deg, rgba(16, 185, 129, .08), rgba(16, 185, 129, .03)); +} + +tbody tr:last-child td { + border-bottom: 0; +} + +.doc-id { + color: #059669; + font-weight: 800; +} + +.status-tag { + min-height: 24px; + display: inline-flex; + align-items: center; + padding: 0 9px; + border: 1px solid transparent; + border-radius: 6px; + font-size: 12px; + font-weight: 750; + white-space: nowrap; +} + +.status-tag.info { + border-color: #bfdbfe; + background: #eff6ff; + color: #2563eb; +} + +.status-tag.success { + border-color: #bbf7d0; + background: #ecfdf5; + color: #059669; +} + +.status-tag.warning { + border-color: #fed7aa; + background: #fff7ed; + color: #f97316; +} + +.status-tag.neutral { + border-color: #cbd5e1; + background: #f8fafc; + color: #475569; +} + +.list-foot { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 16px; + margin-top: 24px; +} + +.page-summary { + color: #64748b; + font-size: 14px; + font-weight: 650; +} + +.pager { + display: inline-flex; + justify-content: center; + gap: 6px; + padding: 4px; + border: 1px solid #e2e8f0; + border-radius: 12px; + background: #f8fafc; +} + +.pager button { + width: 32px; + height: 32px; + border: 0; + border-radius: 9px; + background: transparent; + color: #334155; + font-size: 14px; + font-weight: 800; + transition: background 160ms ease, color 160ms ease, box-shadow 160ms ease; +} + +.pager button:hover:not(.active) { + background: #fff; + color: #059669; + box-shadow: 0 1px 4px rgba(15, 23, 42, .08); +} + +.pager button.active { + background: #059669; + color: #fff; + box-shadow: 0 8px 16px rgba(5, 150, 105, .20); +} + +.page-nav { + color: #64748b; +} + +.page-size { + justify-self: end; + min-width: 112px; + border-radius: 10px; + background: #fff; + box-shadow: 0 1px 2px rgba(15, 23, 42, .04); +} + +.page-size-wrap { + position: relative; + justify-self: end; +} + +.page-size-dropdown { + position: absolute; + bottom: calc(100% + 6px); + right: 0; + z-index: 40; + display: grid; + border: 1px solid #d7e0ea; + border-radius: 10px; + background: #fff; + box-shadow: 0 12px 32px rgba(15, 23, 42, .14); + overflow: hidden; +} + +.page-size-dropdown button { + height: 36px; + display: grid; + place-items: center; + border: 0; + border-radius: 0; + background: transparent; + color: #334155; + font-size: 13px; + font-weight: 750; + white-space: nowrap; + padding: 0 20px; + transition: background 120ms ease, color 120ms ease; +} + +.page-size-dropdown button:hover { + background: #f0fdf4; + color: #059669; +} + +.page-size-dropdown button.active { + background: #059669; + color: #fff; +} + +@media (max-width: 1200px) { + .list-toolbar, + .list-foot { + grid-template-columns: 1fr; + } +} + +@media (max-width: 760px) { + .travel-list { + padding: 16px; + } + + .status-tabs { + gap: 18px; + overflow-x: auto; + } + + .filter-btn, + .page-size { + width: 100%; + } + + .filter-set { + width: 100%; + } + + .list-foot { + display: grid; + justify-items: stretch; + } + + .pager, + .page-size { + justify-self: stretch; + } +} diff --git a/web/src/assets/styles/views/travel-reimbursement-create-view.css b/web/src/assets/styles/views/travel-reimbursement-create-view.css new file mode 100644 index 0000000..fd88589 --- /dev/null +++ b/web/src/assets/styles/views/travel-reimbursement-create-view.css @@ -0,0 +1,756 @@ +.assistant-overlay { + position: fixed; + inset: 0; + z-index: 9999; + display: grid; + place-items: center; + background: rgba(15, 23, 42, 0.46); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} + +.assistant-modal { + width: min(1480px, calc(100vw - 48px)); + height: min(920px, calc(100vh - 40px)); + display: grid; + grid-template-rows: auto minmax(0, 1fr); + border-radius: 28px; + background: + radial-gradient(circle at top right, rgba(16, 185, 129, 0.08), transparent 24%), + linear-gradient(180deg, #fbfdff 0%, #f6f9fc 100%); + box-shadow: + 0 24px 80px rgba(15, 23, 42, 0.22), + 0 2px 12px rgba(15, 23, 42, 0.08); + overflow: hidden; +} + +.assistant-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + padding: 24px 28px 20px; + border-bottom: 1px solid #e5edf5; + background: rgba(255, 255, 255, 0.82); +} + +.assistant-header-main { + display: flex; + align-items: flex-start; + gap: 16px; + min-width: 0; +} + +.assistant-badge { + min-height: 32px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 14px; + border-radius: 999px; + background: rgba(16, 185, 129, 0.12); + color: #059669; + font-size: 12px; + font-weight: 800; + white-space: nowrap; +} + +.assistant-header h2 { + color: #0f172a; + font-size: 24px; + font-weight: 900; +} + +.assistant-header p { + margin-top: 4px; + color: #64748b; + font-size: 14px; + line-height: 1.6; +} + +.assistant-header-actions { + display: flex; + align-items: center; + gap: 12px; +} + +.source-pill { + min-height: 34px; + display: inline-flex; + align-items: center; + padding: 0 14px; + border-radius: 999px; + background: #eff6ff; + color: #2563eb; + font-size: 13px; + font-weight: 800; + white-space: nowrap; +} + +.close-btn { + width: 38px; + height: 38px; + display: grid; + place-items: center; + border: 1px solid #d7e0ea; + border-radius: 999px; + background: #fff; + color: #64748b; + font-size: 18px; +} + +.assistant-layout { + min-height: 0; + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 18px; + padding: 18px; +} + +.assistant-layout.has-insight { + grid-template-columns: minmax(0, 1.18fr) 420px; +} + +.dialog-panel, +.insight-panel { + min-width: 0; + min-height: 0; + border: 1px solid #e7eef6; + border-radius: 24px; + background: rgba(255, 255, 255, 0.92); + box-shadow: 0 1px 4px rgba(15, 23, 42, 0.04); +} + +.dialog-panel { + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; + overflow: hidden; +} + +.dialog-toolbar { + display: flex; + gap: 10px; + flex-wrap: wrap; + padding: 18px 20px 14px; + border-bottom: 1px solid #eef2f7; +} + +.shortcut-chip { + min-height: 36px; + display: inline-flex; + align-items: center; + gap: 8px; + padding: 0 14px; + border: 1px solid #dbe6f0; + border-radius: 999px; + background: #fff; + color: #334155; + font-size: 13px; + font-weight: 800; + white-space: nowrap; +} + +.shortcut-chip i { + color: #059669; +} + +.message-list { + min-height: 0; + display: grid; + align-content: start; + gap: 16px; + padding: 20px; + overflow-y: auto; +} + +.message-row { + display: grid; + grid-template-columns: 38px minmax(0, 1fr); + align-items: start; + gap: 12px; +} + +.message-row.user { + grid-template-columns: minmax(0, 1fr) 38px; +} + +.message-row.user .message-avatar { + order: 2; + background: #dbeafe; + color: #2563eb; +} + +.message-row.user .message-bubble { + order: 1; + justify-self: end; + background: linear-gradient(135deg, rgba(37, 99, 235, 0.10), rgba(37, 99, 235, 0.04)); + border-color: rgba(37, 99, 235, 0.16); +} + +.message-avatar { + width: 38px; + height: 38px; + display: grid; + place-items: center; + border-radius: 999px; + background: #dff7ee; + color: #059669; + font-size: 20px; +} + +.message-bubble { + max-width: min(100%, 720px); + padding: 14px 16px; + border: 1px solid #e1e8f0; + border-radius: 20px; + background: #fff; + color: #24324a; + line-height: 1.65; +} + +.message-meta { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 8px; +} + +.message-meta strong { + color: #0f172a; + font-size: 13px; + font-weight: 850; +} + +.message-meta time { + color: #94a3b8; + font-size: 12px; +} + +.message-bubble p { + color: #334155; + font-size: 14px; +} + +.message-files, +.composer-files { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-top: 10px; +} + +.file-chip { + min-height: 28px; + display: inline-flex; + align-items: center; + gap: 6px; + padding: 0 10px; + border-radius: 999px; + background: #f1f5f9; + color: #475569; + font-size: 12px; + font-weight: 700; +} + +.file-chip.active { + background: #eef6ff; + color: #2563eb; +} + +.composer { + padding: 0 20px 20px; +} + +.hidden-file-input { + display: none; +} + +.composer-shell { + border: 1px solid #d6e1ea; + border-radius: 22px; + background: #fff; + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.05); + overflow: hidden; +} + +.composer-shell textarea { + width: 100%; + min-height: 84px; + resize: none; + border: 0; + padding: 18px 18px 8px; + background: transparent; + color: #0f172a; + font-size: 15px; + line-height: 1.65; +} + +.composer-shell textarea::placeholder { + color: #94a3b8; +} + +.composer-shell textarea:focus { + outline: none; +} + +.composer-foot { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 0 14px 14px; +} + +.composer-tools { + display: flex; + align-items: center; + gap: 10px; +} + +.tool-btn, +.send-btn { + width: 42px; + height: 42px; + display: grid; + place-items: center; + border: 0; + border-radius: 14px; +} + +.tool-btn { + background: #f1f5f9; + color: #475569; + font-size: 20px; +} + +.composer-tip { + color: #94a3b8; + font-size: 12px; + font-weight: 700; +} + +.send-btn { + background: #10b981; + color: #fff; + font-size: 18px; + box-shadow: 0 10px 22px rgba(16, 185, 129, 0.22); +} + +.send-btn:disabled { + opacity: 0.48; + cursor: not-allowed; + box-shadow: none; +} + +.insight-panel { + display: grid; + grid-template-rows: auto minmax(0, 1fr); + overflow: hidden; +} + +.insight-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + padding: 20px; + border-bottom: 1px solid #eef2f7; +} + +.intent-pill { + min-height: 28px; + display: inline-flex; + align-items: center; + padding: 0 12px; + border-radius: 999px; + font-size: 12px; + font-weight: 800; +} + +.intent-pill.welcome { + background: #eef2ff; + color: #4f46e5; +} + +.intent-pill.draft { + background: #ecfdf5; + color: #059669; +} + +.intent-pill.approval { + background: #fff7ed; + color: #ea580c; +} + +.intent-pill.recognition { + background: #eff6ff; + color: #2563eb; +} + +.intent-pill.note { + background: #fdf2f8; + color: #db2777; +} + +.insight-head h3 { + margin-top: 10px; + color: #0f172a; + font-size: 20px; + font-weight: 900; + line-height: 1.3; +} + +.insight-head p { + margin-top: 6px; + color: #64748b; + font-size: 13px; + line-height: 1.6; +} + +.confidence-card { + min-width: 92px; + padding: 10px 12px; + border-radius: 16px; + background: #f8fafc; + text-align: right; +} + +.confidence-card span { + display: block; + color: #94a3b8; + font-size: 11px; + font-weight: 800; +} + +.confidence-card strong { + display: block; + margin-top: 4px; + color: #0f172a; + font-size: 22px; + font-weight: 900; +} + +.insight-body { + min-height: 0; + display: grid; + align-content: start; + gap: 14px; + padding: 18px 20px 20px; + overflow-y: auto; +} + +.insight-card { + padding: 16px; + border: 1px solid #e7eef6; + border-radius: 20px; + background: #fff; +} + +.insight-card.primary { + background: linear-gradient(180deg, #ffffff 0%, #f9fbff 100%); +} + +.card-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 14px; +} + +.card-head h4 { + color: #0f172a; + font-size: 15px; + font-weight: 850; +} + +.status-pill { + min-height: 28px; + display: inline-flex; + align-items: center; + padding: 0 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 800; + white-space: nowrap; +} + +.status-pill.success { + background: #ecfdf5; + color: #059669; +} + +.status-pill.warning { + background: #fff7ed; + color: #ea580c; +} + +.status-pill.note { + background: #fdf2f8; + color: #db2777; +} + +.metric-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.metric-grid.single { + grid-template-columns: 1fr; +} + +.metric-item { + padding: 12px 14px; + border-radius: 16px; + background: #f8fafc; +} + +.metric-item span { + display: block; + color: #94a3b8; + font-size: 11px; + font-weight: 800; +} + +.metric-item strong { + display: block; + margin-top: 6px; + color: #0f172a; + font-size: 14px; + font-weight: 850; + line-height: 1.5; +} + +.timeline-list, +.bullet-list { + display: grid; + gap: 12px; + padding: 0; + margin: 0; + list-style: none; +} + +.timeline-list li { + display: grid; + grid-template-columns: 14px minmax(0, 1fr); + gap: 12px; + align-items: start; +} + +.timeline-dot { + width: 10px; + height: 10px; + margin-top: 5px; + border-radius: 999px; + background: #cbd5e1; +} + +.timeline-list li.done .timeline-dot, +.timeline-list li.current .timeline-dot { + background: #10b981; +} + +.timeline-list strong { + display: block; + color: #0f172a; + font-size: 13px; + font-weight: 800; +} + +.timeline-list p, +.bullet-list li, +.welcome-card p, +.note-block p { + color: #64748b; + font-size: 13px; + line-height: 1.6; +} + +.receipt-list { + display: grid; + gap: 10px; +} + +.receipt-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 12px; + align-items: center; + padding: 12px 14px; + border-radius: 16px; + background: #f8fafc; +} + +.receipt-row strong, +.welcome-card strong, +.note-block strong { + color: #0f172a; + font-size: 14px; + font-weight: 850; +} + +.receipt-row p, +.receipt-row span { + color: #64748b; + font-size: 12px; +} + +.receipt-side { + text-align: right; +} + +.receipt-side strong { + display: block; +} + +.note-block { + display: grid; + gap: 8px; + padding: 14px; + border-radius: 16px; + background: #f8fafc; +} + +.note-block span { + color: #94a3b8; + font-size: 11px; + font-weight: 800; +} + +.welcome-grid { + display: grid; + gap: 12px; +} + +.welcome-card { + padding: 14px; + border-radius: 18px; + background: #f8fafc; +} + +.welcome-card i { + color: #10b981; + font-size: 20px; +} + +.welcome-card strong { + display: block; + margin-top: 10px; +} + +.assistant-modal-enter-active, +.assistant-modal-leave-active { + transition: opacity 220ms ease; +} + +.assistant-modal-enter-active .assistant-modal, +.assistant-modal-leave-active .assistant-modal { + transition: transform 260ms ease, opacity 220ms ease; +} + +.assistant-modal-enter-from, +.assistant-modal-leave-to { + opacity: 0; +} + +.assistant-modal-enter-from .assistant-modal, +.assistant-modal-leave-to .assistant-modal { + transform: translateY(10px) scale(0.985); + opacity: 0; +} + +.insight-switch-enter-active, +.insight-switch-leave-active { + transition: opacity 180ms ease, transform 180ms ease; +} + +.insight-switch-enter-from, +.insight-switch-leave-to { + opacity: 0; + transform: translateY(8px); +} + +.insight-panel-enter-active, +.insight-panel-leave-active { + transition: opacity 220ms ease, transform 240ms ease; +} + +.insight-panel-enter-from, +.insight-panel-leave-to { + opacity: 0; + transform: translateX(18px); +} + +@media (max-width: 1280px) { + .assistant-layout { + grid-template-columns: 1fr; + } + + .insight-panel { + min-height: 320px; + } +} + +@media (max-width: 760px) { + .assistant-modal { + width: 100vw; + height: 100vh; + border-radius: 0; + } + + .assistant-header { + padding: 18px 18px 16px; + align-items: flex-start; + flex-direction: column; + } + + .assistant-header-actions { + width: 100%; + justify-content: space-between; + } + + .assistant-layout { + padding: 14px; + } + + .dialog-toolbar { + padding: 16px 16px 12px; + } + + .shortcut-chip { + width: 100%; + justify-content: center; + } + + .message-list { + padding: 16px; + } + + .message-row, + .message-row.user { + grid-template-columns: 34px minmax(0, 1fr); + } + + .message-row.user .message-avatar { + order: 0; + } + + .message-row.user .message-bubble { + order: 0; + justify-self: stretch; + } + + .composer { + padding: 0 16px 16px; + } + + .composer-foot { + align-items: flex-end; + } + + .metric-grid { + grid-template-columns: 1fr; + } +} diff --git a/web/src/assets/styles/views/travel-request-detail-view.css b/web/src/assets/styles/views/travel-request-detail-view.css new file mode 100644 index 0000000..dcf2a71 --- /dev/null +++ b/web/src/assets/styles/views/travel-request-detail-view.css @@ -0,0 +1,1164 @@ +.approval-page { + width: 100%; + height: 100%; + min-height: 0; + min-width: 0; + display: grid; + grid-template-rows: minmax(0, 1fr); + gap: 14px; + overflow: hidden; + animation: fadeUp 220ms var(--ease) both; +} + +.approval-detail { + width: 100%; + height: 100%; + min-height: 0; + min-width: 0; + display: grid; + grid-template-rows: minmax(0, 1fr) auto; + overflow: hidden; +} + +.detail-scroll { + min-width: 0; + min-height: 0; + display: grid; + align-content: start; + gap: 12px; + padding-right: 4px; + overflow: auto; +} + +.detail-hero { + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr 1.2fr; + gap: 18px; + padding: 22px; +} + +.applicant-card { + display: grid; + grid-template-columns: 78px minmax(0, 1fr); + align-items: center; + gap: 16px; +} + +.portrait { + width: 72px; + height: 72px; + display: grid; + place-items: center; + border-radius: 999px; + background: linear-gradient(135deg, #dbeafe, #ecfdf5); + color: #0f766e; + font-size: 26px; + font-weight: 900; +} + +.applicant-card h2 { + color: #0f172a; + font-size: 22px; + font-weight: 900; +} + +.applicant-card h2 span { + margin-left: 8px; + padding: 3px 9px; + border-radius: 6px; + background: #dcfce7; + color: #059669; + font-size: 12px; +} + +.applicant-card p { + margin-top: 6px; + color: #64748b; + font-size: 13px; +} + +.applicant-card p strong { + margin-left: 18px; + color: #334155; +} + +.hero-stat { + display: grid; + align-content: center; + gap: 8px; +} + +.hero-stat span { + color: #64748b; + font-size: 13px; + font-weight: 750; +} + +.hero-stat strong { + color: #0f172a; + font-size: 24px; + font-weight: 900; +} + +.risk-pill, +.state-pill { + width: max-content; + padding: 5px 10px; + border-radius: 6px; + font-size: 14px; + font-weight: 850; +} + +.risk-pill.high { background: #fee2e2; color: #ef4444; } +.risk-pill.medium { background: #ffedd5; color: #f97316; } +.risk-pill.low { background: #dcfce7; color: #059669; } +.state-pill { background: #dbeafe; color: #2563eb; } + +.countdown { + display: inline-flex; + align-items: center; + gap: 6px; + color: #f97316 !important; +} + +.hero-summary-panel { + grid-column: 1 / -1; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(152px, 1fr)); + gap: 4px 18px; + padding: 4px 0 0; + border-top: 1px solid #e5eaf0; + border-bottom: 1px solid #e5eaf0; +} + +.hero-summary-item { + min-width: 0; + display: grid; + gap: 8px; + align-content: start; + padding: 16px 0 14px; +} + +.hero-summary-label { + display: inline-flex; + align-items: center; + gap: 8px; + color: #64748b; + font-size: 13px; + font-weight: 750; +} + +.hero-summary-icon { + color: #059669; + font-size: 14px; +} + +.hero-summary-item strong { + display: block; + color: #0f172a; + font-size: 16px; + font-weight: 850; + line-height: 1.5; +} + +.progress-block { + grid-column: 1 / -1; + padding-top: 18px; + border-top: 1px solid #e5eaf0; +} + +.progress-head { + display: flex; + align-items: center; + justify-content: flex-start; + margin-bottom: 12px; +} + +.progress-head h3 { + display: inline-flex; + align-items: center; + gap: 8px; + color: #0f172a; + font-size: 14px; + font-weight: 850; +} + +.progress-head h3::before { + content: ""; + width: 8px; + height: 8px; + border-radius: 999px; + background: #10b981; + box-shadow: 0 0 0 4px rgba(16, 185, 129, .12); +} + +.progress-line { + grid-column: 1 / -1; + display: grid; + grid-template-columns: repeat(6, 1fr); +} + +.progress-step { + position: relative; + display: grid; + justify-items: center; + gap: 5px; + color: #94a3b8; +} + +.progress-step::before { + content: ""; + position: absolute; + top: 13px; + left: 0; + right: 0; + z-index: 0; + height: 2px; + background: #dbe4ee; +} + +.progress-step.active::before { background: #10b981; } +.progress-step:first-child::before { left: 50%; } +.progress-step:last-child::before { right: 50%; } + +.progress-step span { + position: relative; + z-index: 1; + width: 26px; + height: 26px; + display: grid; + place-items: center; + border-radius: 999px; + background: #e2e8f0; + color: #64748b; + font-size: 13px; + font-weight: 900; +} + +.current-progress-ring { + position: absolute; + inset: -4px; + z-index: -1; + border: 2px solid rgba(16, 185, 129, .42); + border-radius: 999px; + pointer-events: none; +} + +.progress-step.active span { + background: #059669; + color: #fff; +} + +.progress-step.current span { + background: #10b981 !important; + color: #fff !important; + box-shadow: 0 0 0 4px rgba(16, 185, 129, .15) !important; + animation: breathe-dot 3s ease-in-out infinite !important; + transform-origin: center !important; +} + +@keyframes breathe-dot { + 0%, 100% { + transform: scale(1); + box-shadow: 0 4px 12px rgba(16, 185, 129, .3), 0 0 0 4px rgba(16, 185, 129, .15); + } + 50% { + transform: scale(1.12); + box-shadow: 0 4px 20px rgba(16, 185, 129, .5), 0 0 0 10px rgba(16, 185, 129, .08); + } +} + +.progress-step strong { + color: #334155; + font-size: 12px; +} + +.progress-step.current strong { color: #059669; } +.progress-step small { font-size: 11px; } + +.progress-step.current small { + color: #059669; +} + +.detail-grid { + display: block; + min-width: 0; +} + +.detail-left { + min-width: 0; + display: grid; + gap: 12px; +} + +.detail-card { + min-width: 0; + padding: 16px 18px; +} + +.detail-card h3 { + margin: 0 0 12px; + color: #0f172a; + font-size: 16px; + font-weight: 850; +} + +.detail-card-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 14px; +} + +.detail-card-head h3 { + margin-bottom: 4px; +} + +.detail-card-head p { + color: #64748b; + font-size: 12px; + line-height: 1.5; +} + +.smart-entry-btn { + min-width: 104px; + min-height: 34px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 0 14px; + border: 1px solid #dbe4ee; + border-radius: 999px; + background: #f8fafc; + color: #0f172a; + font-size: 13px; + font-weight: 800; + white-space: nowrap; +} + +.smart-entry-btn:hover { + border-color: rgba(16, 185, 129, .36); + background: #f0fdf4; + color: #047857; +} + +.detail-total { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 102px; + min-height: 34px; + padding: 0 12px; + border-radius: 999px; + background: #ecfdf5; + color: #047857; + font-size: 14px; + font-weight: 900; +} + +.detail-card textarea { + width: 100%; + min-height: 84px; + resize: none; + border: 1px solid #d7e0ea; + border-radius: 8px; + padding: 12px; + font-size: 13px; + line-height: 1.5; +} + +.detail-expense-table { + min-width: 0; + overflow-x: auto; +} + +.detail-expense-table table { + width: 100%; + min-width: 0; + border-collapse: collapse; + table-layout: auto; +} + +.detail-expense-table th, +.detail-expense-table td { + padding: 12px 10px; + border-bottom: 1px solid #edf2f7; + color: #24324a; + text-align: left; + font-size: 12px; + vertical-align: top; + white-space: normal; +} + +.detail-expense-table th { + position: static; + background: #f7fafc; + color: #64748b; + font-weight: 800; + font-size: 11px; + letter-spacing: .04em; + text-transform: uppercase; +} + +.detail-expense-table th:nth-child(1), +.detail-expense-table td:nth-child(1) { width: 12%; } +.detail-expense-table th:nth-child(2), +.detail-expense-table td:nth-child(2) { width: 12%; } +.detail-expense-table th:nth-child(3), +.detail-expense-table td:nth-child(3) { width: 23%; } +.detail-expense-table th:nth-child(4), +.detail-expense-table td:nth-child(4) { width: 10%; } +.detail-expense-table th:nth-child(5), +.detail-expense-table td:nth-child(5) { width: 18%; } +.detail-expense-table th:nth-child(6), +.detail-expense-table td:nth-child(6) { width: 25%; } + +.expense-time strong, +.expense-type strong, +.expense-desc strong, +.expense-amount strong { + display: block; + color: #0f172a; + font-size: 13px; + font-weight: 850; + line-height: 1.4; +} + +.expense-time span, +.expense-type span, +.expense-desc span { + display: block; + margin-top: 4px; + color: #64748b; + line-height: 1.45; +} + +.expense-amount strong { + white-space: nowrap; +} + +.over-tag { + display: inline-flex; + align-items: center; + margin-top: 6px; + padding: 2px 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 800; + white-space: nowrap; +} + +.over-tag.bad { + background: #fff7ed; + color: #ea580c; +} + +.expense-attachment-main { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.expense-attachment .attachment-hint { + display: block; + margin-top: 3px; + color: #64748b; + font-size: 11px; + line-height: 1.5; +} + +.attachment-pill, +.risk-inline-tag { + display: inline-flex; + align-items: center; + min-height: 24px; + padding: 0 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 800; + white-space: nowrap; +} + +.attachment-pill.ok { background: #ecfdf5; color: #059669; } +.attachment-pill.partial { background: #fff7ed; color: #ea580c; } +.attachment-pill.missing { background: #fef2f2; color: #ef4444; } +.attachment-pill.neutral { background: #eef2ff; color: #4f46e5; } + +.inline-action { + min-height: 24px; + padding: 0 8px; + border: 1px solid #dbe4ee; + border-radius: 999px; + background: #fff; + color: #334155; + font-size: 11px; + font-weight: 800; +} + +.inline-action:hover { + border-color: rgba(16, 185, 129, .36); + color: #047857; +} + +.risk-inline-tag.low { background: #ecfdf5; color: #059669; } +.risk-inline-tag.medium { background: #fff7ed; color: #ea580c; } +.risk-inline-tag.high { background: #fef2f2; color: #dc2626; } + +.expense-risk p { + margin-top: 6px; + color: #475569; + font-size: 12px; + line-height: 1.55; +} + +.expense-expand-row td { + padding: 0 10px 12px; + background: #fbfdff; + border-bottom: 1px solid #edf2f7; +} + +.expense-files { + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 10px 0 0; +} + +.expense-file-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + border-radius: 999px; + background: #fff; + border: 1px solid #e2e8f0; + color: #334155; + font-size: 11px; + font-weight: 700; +} + +.expense-file-chip i { + color: #059669; + font-size: 12px; +} + +.total-row td { + color: #0f172a; + font-weight: 900; + background: #f8fafc; +} + +.detail-actions { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-top: 10px; + padding: 10px 0 0; + border-top: 1px solid #e5eaf0; + background: transparent; +} + +.detail-actions button { + height: 36px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border-radius: 8px; + font-size: 13px; + font-weight: 760; +} + +.back-action { + min-width: 104px; + border: 1px solid #d7e0ea; + background: #fff; + color: #475569; +} + +.approval-action-group { + display: flex; + justify-content: flex-end; + gap: 8px; + width: auto; +} + +.approve-action { + min-width: 92px; + border: 1px solid #059669; + background: #059669; + color: #fff; + box-shadow: 0 4px 10px rgba(5, 150, 105, .14); +} + +.reject-action { + min-width: 86px; + border: 1px solid #fecaca; + background: #fff; + color: #ef4444; +} + +.supplement-action { + min-width: 86px; + border: 1px solid #fed7aa; + background: #fff; + color: #ea580c; +} + +.detail-overlay { + position: fixed; + inset: 0; + z-index: 9999; + display: grid; + place-items: center; + background: rgba(15, 23, 42, .45); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); +} + +.detail-modal { + position: relative; + width: calc(100vw - 80px); + max-width: 1440px; + height: calc(100vh - 64px); + max-height: 960px; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + border-radius: 28px; + background: #f8fafc; + box-shadow: + 0 0 0 1px rgba(15, 23, 42, .08), + 0 20px 60px rgba(15, 23, 42, .18), + 0 4px 16px rgba(15, 23, 42, .06); + overflow: hidden; +} + +.ai-entry-modal { + max-width: 1320px; +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + padding: 24px 28px; + background: linear-gradient(135deg, #fff 0%, #f9fbff 100%); + border-bottom: 1px solid #e8eef6; +} + +.header-left { + display: flex; + align-items: center; + gap: 16px; +} + +.req-badge { + padding: 6px 14px; + border-radius: 999px; + background: #eff6ff; + border: 1px solid rgba(29, 78, 216, .16); + color: #1d4ed8; + font-size: 13px; + font-weight: 850; + letter-spacing: .02em; +} + +.header-title-group h2 { + color: #0f172a; + font-size: 20px; + font-weight: 900; + letter-spacing: -.01em; +} + +.header-title-group p { + margin-top: 3px; + color: #64748b; + font-size: 13px; +} + +.header-right { + display: flex; + align-items: center; + gap: 10px; +} + +.close-btn { + width: 36px; + height: 36px; + display: grid; + place-items: center; + border: 1px solid #e2e8f0; + border-radius: 999px; + background: #fff; + color: #64748b; + font-size: 18px; + transition: all 160ms ease; +} + +.close-btn:hover { + border-color: #cbd5e1; + background: #f1f5f9; + color: #0f172a; +} + +.modal-body { + min-height: 0; + padding: 20px 28px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: #cbd5e1 transparent; +} + +.ai-entry-grid { + min-height: 100%; + display: grid; + grid-template-columns: minmax(0, 1.2fr) 360px; + gap: 20px; +} + +.ai-chat-card, +.ai-preview-card { + min-height: 0; + border-radius: 22px; + background: #fff; + border: 1px solid #edf2f7; + box-shadow: 0 1px 3px rgba(0, 0, 0, .04); +} + +.ai-chat-card { + display: grid; + grid-template-rows: minmax(0, 1fr) auto; + overflow: hidden; +} + +.ai-chat-scroll { + min-height: 0; + display: grid; + align-content: start; + gap: 12px; + padding: 18px; + overflow: auto; + background: + linear-gradient(180deg, rgba(240, 253, 244, .5) 0%, rgba(255, 255, 255, 0) 140px), + #fff; +} + +.ai-chat-bubble { + display: grid; + grid-template-columns: 34px minmax(0, 1fr); + gap: 10px; +} + +.ai-chat-bubble.user { + grid-template-columns: minmax(0, 1fr) 34px; +} + +.ai-chat-bubble.user .ai-chat-avatar { + order: 2; + background: #dbeafe; + color: #2563eb; +} + +.ai-chat-bubble.user .ai-chat-content { + order: 1; + justify-self: end; + background: #eff6ff; +} + +.ai-chat-avatar { + width: 34px; + height: 34px; + display: grid; + place-items: center; + border-radius: 999px; + background: #ecfdf5; + color: #059669; + font-size: 18px; +} + +.ai-chat-content { + max-width: min(100%, 640px); + padding: 12px 14px; + border-radius: 18px; + background: #f8fafc; + border: 1px solid #edf2f7; +} + +.ai-chat-content header { + margin-bottom: 6px; +} + +.ai-chat-content strong { + color: #0f172a; + font-size: 13px; + font-weight: 850; +} + +.ai-chat-content p { + color: #334155; + font-size: 13px; + line-height: 1.6; +} + +.ai-composer { + display: grid; + gap: 12px; + padding: 14px 16px 16px; + border-top: 1px solid #edf2f7; + background: linear-gradient(180deg, #fff, #fbfdff); +} + +.ai-file-input { + display: none; +} + +.ai-composer-surface { + min-height: 78px; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: end; + gap: 12px; + padding: 8px 8px 8px 14px; + border: 1px solid #cbd8e5; + border-radius: 22px; + background: linear-gradient(180deg, #fff, #fbfdff); + box-shadow: 0 1px 2px rgba(15, 23, 42, .04); + transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease; +} + +.ai-composer-surface:focus-within { + border-color: rgba(16, 185, 129, .58); + background: #fff; + box-shadow: 0 0 0 3px rgba(16, 185, 129, .11), 0 10px 24px rgba(15, 23, 42, .06); +} + +.ai-composer textarea { + width: 100%; + min-height: 60px; + height: 60px; + resize: none; + border: 0; + border-radius: 0; + padding: 8px 0; + background: transparent; + color: #0f172a; + font-size: 14px; + line-height: 1.6; +} + +.ai-composer textarea:focus { + outline: none; +} + +.ai-composer-actions { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 8px; + padding-bottom: 2px; +} + +.ai-upload-list { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.ai-upload-chip { + display: inline-flex; + align-items: center; + gap: 6px; + min-height: 32px; + padding: 0 12px; + border-radius: 999px; + background: #eef6ff; + border: 1px solid #d7e8fb; + color: #334155; + font-size: 12px; + font-weight: 700; +} + +.ai-upload-chip i { + color: #2563eb; +} + +.ai-upload-btn, +.ai-send-btn { + width: 48px; + height: 48px; + display: grid; + place-items: center; + padding: 0; + border-radius: 12px; + font-size: 20px; + transition: background 160ms ease, transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease, color 160ms ease; +} + +.ai-upload-btn { + border: 0; + background: #f1f5f9; + color: #475569; + box-shadow: none; +} + +.ai-upload-btn:hover { + background: #e2e8f0; + color: #0f172a; +} + +.ai-send-btn { + border: 0; + background: #10b981; + color: #fff; + box-shadow: 0 8px 18px rgba(16, 185, 129, .20); +} + +.ai-send-btn:hover { + background: #0ea672; + box-shadow: 0 10px 22px rgba(16, 185, 129, .24); +} + +.ai-upload-btn:active, +.ai-send-btn:active { + transform: scale(.96); +} + +.ai-preview-card { + padding: 18px; + display: grid; + align-content: start; + gap: 16px; +} + +.ai-preview-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; +} + +.ai-preview-head h3 { + margin: 0; + color: #0f172a; + font-size: 16px; + font-weight: 850; +} + +.ai-preview-head p { + margin-top: 4px; + color: #64748b; + font-size: 12px; + line-height: 1.5; +} + +.ai-preview-fields { + display: grid; + gap: 10px; +} + +.preview-field { + padding: 12px 14px; + border-radius: 18px; + background: #f8fafc; + border: 1px solid #edf2f7; +} + +.preview-field.full { + min-height: 90px; +} + +.preview-field span { + display: block; + color: #64748b; + font-size: 11px; + font-weight: 800; + text-transform: uppercase; + letter-spacing: .04em; +} + +.preview-field strong { + display: block; + margin-top: 5px; + color: #0f172a; + font-size: 14px; + font-weight: 850; + line-height: 1.5; +} + +.preview-field p { + margin-top: 4px; + color: #475569; + font-size: 12px; + line-height: 1.55; +} + +.ai-preview-empty { + min-height: 280px; + display: grid; + place-items: center; + gap: 10px; + padding: 24px; + border: 1px dashed #cbd5e1; + border-radius: 20px; + color: #94a3b8; + text-align: center; +} + +.ai-preview-empty i { + font-size: 32px; + color: #10b981; +} + +.ai-preview-actions { + display: flex; + gap: 10px; + margin-top: 4px; +} + +.ai-preview-secondary, +.ai-preview-primary { + height: 40px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 7px; + padding: 0 20px; + border-radius: 999px; + font-size: 13px; + font-weight: 800; + transition: all 180ms ease; + flex: 1; +} + +.ai-preview-secondary { + border: 1px solid #fed7aa; + background: #fff7ed; + color: #c2410c; +} + +.ai-preview-primary { + border: 1px solid #059669; + background: #059669; + color: #fff; + box-shadow: 0 8px 20px rgba(5, 150, 105, .18); +} + +.ai-preview-secondary:hover { + background: #ffedd5; +} + +.ai-preview-primary:hover { + background: #047857; +} + +.ai-preview-secondary:disabled, +.ai-preview-primary:disabled, +.approve-action:disabled, +.ai-send-btn:disabled { + opacity: .45; + cursor: not-allowed; + box-shadow: none; +} + +.detail-modal-enter-active { transition: opacity 260ms ease; } +.detail-modal-enter-active .detail-modal { transition: transform 320ms cubic-bezier(.2, .8, .2, 1), opacity 280ms ease; } + +.detail-modal-leave-active { transition: opacity 200ms ease; } +.detail-modal-leave-active .detail-modal { transition: transform 220ms ease, opacity 200ms ease; } + +.detail-modal-enter-from { opacity: 0; } +.detail-modal-enter-from .detail-modal { transform: translateY(16px); opacity: 0; } + +.detail-modal-leave-to { opacity: 0; } +.detail-modal-leave-to .detail-modal { transform: translateY(8px); opacity: 0; } + +@media (max-width: 1320px) { + .detail-hero { + grid-template-columns: minmax(0, 1.8fr) repeat(4, minmax(0, 1fr)); + } + + .hero-summary-panel { + grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); + } + + .detail-expense-table { + overflow-x: auto; + } + + .detail-expense-table table { + min-width: 980px; + } + + .ai-entry-grid { + grid-template-columns: 1fr; + } + + .detail-modal { + width: calc(100vw - 40px); + height: calc(100vh - 40px); + } +} + +@media (max-width: 760px) { + .detail-hero { + grid-template-columns: 1fr 1fr; + gap: 12px; + padding: 16px; + } + + .applicant-card, + .hero-summary-panel, + .progress-line { + grid-column: 1 / -1; + } + + .hero-summary-panel { + grid-template-columns: 1fr; + } + + .hero-summary-item { + padding: 14px 0; + } + + .detail-card { + padding: 14px 16px; + } + + .detail-card-head { + flex-direction: column; + align-items: stretch; + } + + .smart-entry-btn { align-self: flex-start; } + + .detail-expense-table table { + min-width: 980px; + } + + .detail-actions { + flex-direction: column; + } + + .approval-action-group { + width: 100%; + } + + .detail-modal { + width: 100vw; + height: 100vh; + max-width: 100vw; + max-height: 100vh; + border-radius: 0; + } + + .modal-header { padding: 16px 18px; flex-wrap: wrap; } + .modal-body { padding: 16px 18px; } + .ai-composer-actions { flex-direction: column; align-items: stretch; } + .ai-preview-actions { flex-direction: column; } +} diff --git a/src/components/business/PersonalWorkbench.vue b/web/src/components/business/PersonalWorkbench.vue similarity index 98% rename from src/components/business/PersonalWorkbench.vue rename to web/src/components/business/PersonalWorkbench.vue index fc150ae..5d79194 100644 --- a/src/components/business/PersonalWorkbench.vue +++ b/web/src/components/business/PersonalWorkbench.vue @@ -22,7 +22,7 @@
+
+ + + + + + + + + +
+ + +
+ + + + + +
+
+
+
+
{{ selectedRow.avatar }}
+
+

{{ selectedRow.applicant }} {{ selectedRow.department }}

+

提交时间 {{ selectedRow.time }}

+
+
+ +
+ 金额 + {{ selectedRow.amount }} +
+
+ 风险等级 + {{ selectedRow.risk }} +
+
+ 当前状态 + {{ selectedRow.node }} +
+
+ SLA 剩余时间 + 剩余 {{ selectedRow.sla }} +
+ +
+
+
+ + {{ item.label }} +
+ {{ item.value }} +
+
+ +
+
+

当前进度

+
+
+
+ + + + + + {{ step.label }} + {{ step.time }} +
+
+
+
+ +
+
+
+
+
+

费用明细

+

按发生时间逐笔展示,附件与 AI 风险直接在表内完成核对。

+
+ {{ expenseTotal }} +
+
+ + + + + + + + + + + + + + + + + + + + +
时间费用项目说明金额附件材料AI 风险识别
合计{{ expenseTotal }}{{ uploadedExpenseCount }} 项已上传票据1 项待补材料,1 项需补充超标说明
+
+
+ +
+

审批意见

+ +
+
+
+
+ +
+ +
+ + + +
+
+
+ + +
+ + +
+
+ +
+
+ +

点击单据行查看审批详情

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
单号申请人申请部门报销类型金额提交时间 风险等级SLA剩余当前节点状态操作
{{ row.id }} + + {{ row.avatar }} + {{ row.applicant }} + + {{ row.department }}{{ row.type }}{{ row.amount }}{{ row.time }}{{ row.risk }}{{ row.sla }}{{ row.node }}{{ row.status }} + +
+
+ +
+ 共 126 条,当前第 1 页 +
+ + + + + + + ... + + +
+ +
+
+ + + + + + diff --git a/web/src/views/AuditView.vue b/web/src/views/AuditView.vue new file mode 100644 index 0000000..dc37127 --- /dev/null +++ b/web/src/views/AuditView.vue @@ -0,0 +1,286 @@ + + + + + diff --git a/web/src/views/ChatView.vue b/web/src/views/ChatView.vue new file mode 100644 index 0000000..6966abc --- /dev/null +++ b/web/src/views/ChatView.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/web/src/views/EmployeeManagementView.vue b/web/src/views/EmployeeManagementView.vue new file mode 100644 index 0000000..1bea629 --- /dev/null +++ b/web/src/views/EmployeeManagementView.vue @@ -0,0 +1,308 @@ + + + + + diff --git a/web/src/views/LoginView.vue b/web/src/views/LoginView.vue new file mode 100644 index 0000000..a995308 --- /dev/null +++ b/web/src/views/LoginView.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/web/src/views/OverviewView.vue b/web/src/views/OverviewView.vue new file mode 100644 index 0000000..03b7d42 --- /dev/null +++ b/web/src/views/OverviewView.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/src/views/PersonalWorkbenchView.vue b/web/src/views/PersonalWorkbenchView.vue similarity index 100% rename from src/views/PersonalWorkbenchView.vue rename to web/src/views/PersonalWorkbenchView.vue diff --git a/web/src/views/PoliciesView.vue b/web/src/views/PoliciesView.vue new file mode 100644 index 0000000..5b98743 --- /dev/null +++ b/web/src/views/PoliciesView.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/web/src/views/RequestsView.vue b/web/src/views/RequestsView.vue new file mode 100644 index 0000000..69cf016 --- /dev/null +++ b/web/src/views/RequestsView.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/web/src/views/TravelReimbursementCreateView.vue b/web/src/views/TravelReimbursementCreateView.vue new file mode 100644 index 0000000..7bfd43d --- /dev/null +++ b/web/src/views/TravelReimbursementCreateView.vue @@ -0,0 +1,338 @@ +