diff --git a/docs/superpowers/plans/2026-08-11-mobile-oa-start-approval-phase1-sink.md b/docs/superpowers/plans/2026-08-11-mobile-oa-start-approval-phase1-sink.md new file mode 100644 index 00000000..e808137e --- /dev/null +++ b/docs/superpowers/plans/2026-08-11-mobile-oa-start-approval-phase1-sink.md @@ -0,0 +1,837 @@ +# 移动端发起审批 · 阶段一(水槽 Spike)Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 搭好独立 OA 表单 H5(`@form-create/vant`)与小程序整页 web-view 入口,用「按 zkxw-sc 真实用量」的厨房水槽 rule 在微信真机验证渲染与交互。 + +**Architecture:** 小程序首页硬编码测试菜单 → `/pages/oa/form-sink/index` 整页 `web-view` → 独立工程 `mobile-oa-h5` 的 `/oa-form-sink`。水槽 type 以测试库已发布 `form_schema_json` 统计为准(P0 先通),不为未使用 type 空转。正式模板列表 / 草稿提交 / 菜单注册见后续阶段二计划。 + +**Tech Stack:** Vue3、Vite、Vant4、`@form-create/vant`、uni-app(HBuilderX)微信小程序、`web-view` + +**Spec:** `docs/superpowers/specs/2026-08-11-mobile-oa-start-approval-design.md` + +**Out of scope (本计划):** 正式发起 API、flow-preview 真联调、移动端菜单后台注册、全量套件精修(P2 可先 stub 占位)。 + +--- + +## File map + +| 路径 | 职责 | +|------|------| +| `scripts/oa_form_type_stats.py` | 统计 `zkxw-sc` 已发布模板中的 `type` 频率 | +| `mobile-oa-h5/package.json` | H5 依赖与脚本 | +| `mobile-oa-h5/vite.config.js` | Vite + `@` 别名 + 开发 server | +| `mobile-oa-h5/index.html` | H5 入口 HTML | +| `mobile-oa-h5/src/main.js` | 挂载 Vue、Vant、form-create/vant、自研 type 注册 | +| `mobile-oa-h5/src/App.vue` | 路由出口 | +| `mobile-oa-h5/src/router.js` | `/oa-form-sink`、`/oa-start`(后者可占位) | +| `mobile-oa-h5/src/styles/tokens.css` | 三墙 Token(`#0a9f6d` 等) | +| `mobile-oa-h5/src/oa/register-components.js` | 注册自研 type(与 Web 同名) | +| `mobile-oa-h5/src/oa/kitchen-sink-rule.js` | 水槽 rule(按库内实际 type) | +| `mobile-oa-h5/src/oa/components/*.vue` | 自研控件 stub / 初版(P0 可用) | +| `mobile-oa-h5/src/views/FormSink.vue` | 水槽页:form-create + 底栏校验/导出 | +| `mobile-oa-h5/.env.development` | `VITE_API_BASE` 等 | +| `mobile-h5/utils/oa-h5.js` | H5 水槽 / 发起页 URL 拼装 | +| `mobile-h5/pages/oa/form-sink/index.vue` | 整页 web-view | +| `mobile-h5/pages/index/index.vue` | 注入测试菜单 | +| `mobile-h5/pages.json` | 注册 form-sink 路由 | + +--- + +### Task 1: 固化 type 统计脚本 + +**Files:** +- Create: `scripts/oa_form_type_stats.py` + +- [ ] **Step 1: 写入统计脚本** + +```python +#!/usr/bin/env python3 +"""统计 zkxw-sc(或当前 DATABASE_URL)已发布 OA 模板 form_schema 中的 type。""" +from __future__ import annotations + +import json +import sys +from collections import Counter +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "backend")) + +from sqlalchemy import create_engine, text # noqa: E402 +from app.core.config import settings # noqa: E402 + +CUSTOM = { + "money", + "person", + "ownerPerson", + "dept", + "ownerDept", + "post", + "receiveAccount", + "relatedApproval", + "upload", + "fcUpload", + "leaveKit", + "overtimeKit", + "confirmationKit", + "resignationKit", + "tripKit", + "tripItinerary", +} + + +def walk_types(node, out: list) -> None: + if node is None: + return + if isinstance(node, dict): + t = node.get("type") + if isinstance(t, str) and t.strip(): + out.append(t.strip()) + for v in node.values(): + if isinstance(v, (dict, list)): + walk_types(v, out) + elif isinstance(node, list): + for item in node: + walk_types(item, out) + + +def main() -> None: + engine = create_engine(settings.DATABASE_URL) + sql = text( + """ + SELECT a.app_name, v.form_schema_json + FROM oa_app a + JOIN oa_app_version v ON v.app_id = a.id + WHERE a.del_flag = 0 AND v.del_flag = 0 + AND v.status = 'published' + AND (v.is_latest_published = 1 OR v.is_latest_published = '1') + ORDER BY a.app_name + """ + ) + counter: Counter[str] = Counter() + with engine.connect() as conn: + rows = conn.execute(sql).mappings().all() + print(f"published_latest_apps={len(rows)}") + for row in rows: + raw = row["form_schema_json"] or "" + try: + schema = json.loads(raw) if raw else {} + except json.JSONDecodeError: + print("BAD_JSON", row["app_name"]) + continue + types: list[str] = [] + if isinstance(schema, list): + walk_types(schema, types) + else: + walk_types(schema.get("rule", schema), types) + for t in types: + counter[t] += 1 + print("=== frequency ===") + for t, c in counter.most_common(): + kind = "CUSTOM" if t in CUSTOM else "base" + print(f"{c:4d} {kind:6s} {t}") + print("=== custom used ===") + print(sorted(set(counter) & CUSTOM)) + + +if __name__ == "__main__": + main() +``` + +- [ ] **Step 2: 运行脚本确认与设计文档一致** + +Run: + +```bash +cd /Users/wuzilong/work/00code/zkxw/backend && source venv/bin/activate && python ../scripts/oa_form_type_stats.py +``` + +Expected: `published_latest_apps=27`(或接近),`upload`/`money`/`relatedApproval`/`receiveAccount` 排在自研前列。 + +- [ ] **Step 3: Commit** + +```bash +git add scripts/oa_form_type_stats.py +git commit -m "chore: add OA published form type stats script against zkxw-sc" +``` + +--- + +### Task 2: 初始化 `mobile-oa-h5` 工程 + +**Files:** +- Create: `mobile-oa-h5/package.json` +- Create: `mobile-oa-h5/vite.config.js` +- Create: `mobile-oa-h5/index.html` +- Create: `mobile-oa-h5/src/main.js` +- Create: `mobile-oa-h5/src/App.vue` +- Create: `mobile-oa-h5/src/router.js` +- Create: `mobile-oa-h5/src/styles/tokens.css` +- Create: `mobile-oa-h5/.gitignore` +- Create: `mobile-oa-h5/.env.development` + +- [ ] **Step 1: 写入 package.json** + +```json +{ + "name": "mobile-oa-h5", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@form-create/vant": "^3.2.14", + "vant": "^4.9.21", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^6.0.7" + } +} +``` + +(若 `@form-create/vant` 版本解析失败,以 `npm view @form-create/vant version` 的 next/最新 Vue3 线为准。) + +- [ ] **Step 2: 写入 vite.config.js / index.html / 入口** + +`mobile-oa-h5/vite.config.js`: + +```js +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { fileURLToPath, URL } from 'node:url' + +export default defineConfig({ + plugins: [vue()], + base: '/oa-h5/', + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + }, + server: { + host: '0.0.0.0', + port: 5178, + proxy: { + '/api': { + target: 'http://127.0.0.1:8000', + changeOrigin: true + } + } + } +}) +``` + +`mobile-oa-h5/index.html`: + +```html + + + + + + 中科兴皖 · OA 表单 + + +
+ + + +``` + +`mobile-oa-h5/src/styles/tokens.css`: + +```css +:root { + --brand-green: #0a9f6d; + --brand-green-dark: #087f57; + --brand-green-soft: #e9f9f2; + --bg-page: #f5f8f7; + --text-primary: #1f2a26; + --text-muted: #6b7b74; + --border: #dcebe4; +} +body { + margin: 0; + background: var(--bg-page); + color: var(--text-primary); + font-family: system-ui, -apple-system, 'PingFang SC', sans-serif; +} +``` + +`mobile-oa-h5/src/App.vue`: + +```vue + +``` + +`mobile-oa-h5/src/router.js`: + +```js +import { createRouter, createWebHistory } from 'vue-router' +import FormSink from './views/FormSink.vue' + +export default createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { path: '/', redirect: '/oa-form-sink' }, + { path: '/oa-form-sink', component: FormSink }, + { + path: '/oa-start', + component: { + template: '
oa-start 占位(阶段二)
' + } + } + ] +}) +``` + +`mobile-oa-h5/src/main.js`: + +```js +import { createApp } from 'vue' +import Vant from 'vant' +import 'vant/lib/index.css' +import formCreateMobile from '@form-create/vant' +import App from './App.vue' +import router from './router' +import './styles/tokens.css' +import { registerOaComponents } from './oa/register-components' + +const app = createApp(App) +app.use(router) +app.use(Vant) +app.use(formCreateMobile) +registerOaComponents(formCreateMobile) +app.mount('#app') +``` + +`mobile-oa-h5/.gitignore`: + +``` +node_modules/ +dist/ +.DS_Store +.env.local +``` + +`mobile-oa-h5/.env.development`: + +``` +VITE_API_BASE=/api +``` + +- [ ] **Step 3: 安装依赖** + +```bash +cd /Users/wuzilong/work/00code/zkxw/mobile-oa-h5 && npm install +``` + +Expected: 无报错,存在 `node_modules/@form-create/vant` 与 `node_modules/vant`。 + +- [ ] **Step 4: Commit** + +```bash +git add mobile-oa-h5 +git commit -m "chore: scaffold mobile-oa-h5 with Vite, Vant and form-create" +``` + +--- + +### Task 3: 水槽 rule + 自研 type 注册(P0 可用,P2 stub) + +**Files:** +- Create: `mobile-oa-h5/src/oa/kitchen-sink-rule.js` +- Create: `mobile-oa-h5/src/oa/register-components.js` +- Create: `mobile-oa-h5/src/oa/components/OaStubField.vue` +- Create: `mobile-oa-h5/src/views/FormSink.vue` + +- [ ] **Step 1: Stub 通用组件** + +`mobile-oa-h5/src/oa/components/OaStubField.vue`: + +```vue + + + + + +``` + +- [ ] **Step 2: 注册表(type 名与 Web 一致)** + +`mobile-oa-h5/src/oa/register-components.js`: + +```js +import OaStubField from './components/OaStubField.vue' + +/** 与 web-admin formCreate.component 名称对齐;阶段一可用 stub */ +const CUSTOM_TYPES = [ + 'upload', + 'money', + 'relatedApproval', + 'receiveAccount', + 'dept', + 'person', + 'ownerPerson', + 'ownerDept', + 'post', + 'leaveKit', + 'overtimeKit', + 'confirmationKit', + 'resignationKit', + 'tripKit', + 'tripItinerary' +] + +export function registerOaComponents(formCreate) { + if (!formCreate || typeof formCreate.component !== 'function') return + for (const name of CUSTOM_TYPES) { + formCreate.component(name, OaStubField) + } +} +``` + +- [ ] **Step 3: kitchen-sink-rule(覆盖库内出现过的 type)** + +`mobile-oa-h5/src/oa/kitchen-sink-rule.js`: + +```js +/** 按 zkxw-sc 已发布模板实际出现的 type 构造;非设计器理论全集 */ +export const kitchenSinkRule = [ + { type: 'input', field: 'title', title: '单行文本', value: '', $required: true }, + { type: 'textarea', field: 'remark', title: '多行文本', value: '' }, + { + type: 'select', + field: 'level', + title: '下拉', + value: '', + options: [ + { label: '普通', value: 'normal' }, + { label: '紧急', value: 'urgent' } + ] + }, + { type: 'inputNumber', field: 'qty', title: '数字', value: 1 }, + { + type: 'datePicker', + field: 'bizDate', + title: '日期', + value: '', + props: { type: 'date' } + }, + { type: 'upload', field: 'files', title: '附件 upload', value: [] }, + { type: 'money', field: 'amount', title: '金额 money', value: '' }, + { + type: 'relatedApproval', + field: 'related', + title: '关联审批 relatedApproval', + value: [] + }, + { + type: 'receiveAccount', + field: 'payAccount', + title: '收款账户 receiveAccount', + value: [] + }, + { type: 'dept', field: 'deptId', title: '部门 dept', value: '' }, + { type: 'person', field: 'personIds', title: '人员 person', value: [] }, + { + type: 'ownerPerson', + field: 'ownerPerson', + title: '所属人 ownerPerson', + value: '' + }, + { + type: 'ownerDept', + field: 'ownerDept', + title: '所属部门 ownerDept', + value: '' + }, + { type: 'post', field: 'postId', title: '岗位 post', value: '' }, + { type: 'leaveKit', field: 'leaveKit', title: '请假套件 leaveKit', value: {} }, + { + type: 'overtimeKit', + field: 'overtimeKit', + title: '加班套件 overtimeKit', + value: {} + }, + { + type: 'confirmationKit', + field: 'confirmationKit', + title: '转正套件 confirmationKit', + value: {} + }, + { + type: 'resignationKit', + field: 'resignationKit', + title: '离职套件 resignationKit', + value: {} + }, + { type: 'tripKit', field: 'tripKit', title: '差旅套件 tripKit', value: {} }, + { + type: 'tripItinerary', + field: 'tripItinerary', + title: '行程 tripItinerary', + value: [] + } +] + +export const kitchenSinkOption = { + submitBtn: false, + resetBtn: false, + form: { labelPosition: 'top' } +} +``` + +- [ ] **Step 4: FormSink 页面** + +`mobile-oa-h5/src/views/FormSink.vue`: + +```vue + + + + + +``` + +- [ ] **Step 5: 本地浏览器验证** + +```bash +cd /Users/wuzilong/work/00code/zkxw/mobile-oa-h5 && npm run dev +``` + +Expected: 打开 `http://127.0.0.1:5178/oa-h5/oa-form-sink`,基础控件与自研 stub 均可见;点「校验并导出」可更新 JSON。 + +- [ ] **Step 6: Commit** + +```bash +git add mobile-oa-h5/src +git commit -m "feat(mobile-oa-h5): add kitchen-sink form page with OA type stubs" +``` + +--- + +### Task 4: 小程序测试菜单 + 整页 web-view + +**Files:** +- Create: `mobile-h5/utils/oa-h5.js` +- Create: `mobile-h5/pages/oa/form-sink/index.vue` +- Modify: `mobile-h5/pages.json` +- Modify: `mobile-h5/pages/index/index.vue` +- Modify: `mobile-h5/utils/config.js`(增加 `OA_H5_ORIGIN`) + +- [ ] **Step 1: 配置 H5 源** + +在 `mobile-h5/utils/config.js` 追加: + +```js +/** + * OA 表单 H5 源(web-view)。 + * 本地:电脑局域网 IP + Vite 端口,须与手机同网;真机/体验版改为已配置业务域名。 + * 例:http://192.168.1.8:5178/oa-h5/ + */ +export const OA_H5_ORIGIN = 'http://127.0.0.1:5178/oa-h5' +``` + +`mobile-h5/utils/oa-h5.js`: + +```js +import { OA_H5_ORIGIN } from './config.js' +import { getToken } from './auth.js' + +function trimSlash(s) { + return String(s || '').replace(/\/$/, '') +} + +/** 水槽页 URL */ +export function buildFormSinkUrl() { + const base = trimSlash(OA_H5_ORIGIN) + const token = encodeURIComponent(getToken() || '') + return `${base}/oa-form-sink?token=${token}` +} + +/** 正式发起页 URL(阶段二) */ +export function buildOaStartUrl({ appId, draftId }) { + const base = trimSlash(OA_H5_ORIGIN) + const q = new URLSearchParams() + if (appId) q.set('appId', appId) + if (draftId) q.set('draftId', draftId) + const token = getToken() + if (token) q.set('token', token) + return `${base}/oa-start?${q.toString()}` +} +``` + +- [ ] **Step 2: web-view 页** + +`mobile-h5/pages/oa/form-sink/index.vue`: + +```vue + + + +``` + +- [ ] **Step 3: 注册路由** + +在 `mobile-h5/pages.json` 的 `pages` 数组中增加: + +```json +{ + "path": "pages/oa/form-sink/index", + "style": { + "navigationBarTitleText": "表单水槽测试", + "navigationBarBackgroundColor": "#0a9f6d", + "navigationBarTextStyle": "white" + } +} +``` + +- [ ] **Step 4: 首页注入测试菜单** + +修改 `mobile-h5/pages/index/index.vue` 的 `data()`,将 `menus: []` 改为至少包含: + +```js +menus: [ + { + id: 'oa-form-sink', + title: '表单水槽测试', + icon: 'orders-o', + path: '/pages/oa/form-sink/index', + tone: '' + } +] +``` + +(`onShow` 暂不覆盖该数组;阶段二接真实菜单时再合并/替换。) + +- [ ] **Step 5: 真机 / 开发者工具验收清单** + +1. `mobile-oa-h5`:`npm run dev`,`OA_H5_ORIGIN` 改为手机可达地址(非 `127.0.0.1`)。 +2. 微信公众平台配置业务域名(体验版);开发工具可勾选不校验合法域名。 +3. 打开小程序首页 → 点「表单水槽测试」→ web-view 加载水槽。 +4. 确认:无白屏;基础字段可填;自研 stub 可见;校验按钮可用。 + +- [ ] **Step 6: Commit** + +```bash +git add mobile-h5/utils/config.js mobile-h5/utils/oa-h5.js \ + mobile-h5/pages/oa/form-sink/index.vue mobile-h5/pages.json \ + mobile-h5/pages/index/index.vue +git commit -m "feat(mobile-h5): add OA form-sink web-view entry and test menu" +``` + +--- + +### Task 5: 记录 Spike 结论(为阶段二铺路) + +**Files:** +- Create: `docs/superpowers/specs/2026-08-11-mobile-oa-form-sink-spike-notes.md`(实施当天日期可改) + +- [ ] **Step 1: 按模板写结论** + +```markdown +# OA 表单水槽 Spike 记录 + +- 日期: +- 环境:微信开发者工具 / 真机型号 / +- H5 地址: +- 结果:通过 / 阻塞 +- 问题列表: + 1. +- 下一步:开始 P0 真实控件(upload/money/relatedApproval/receiveAccount)移植计划 +``` + +- [ ] **Step 2: Commit notes** + +```bash +git add docs/superpowers/specs/2026-08-11-mobile-oa-form-sink-spike-notes.md +git commit -m "docs: record mobile OA form-sink spike results" +``` + +--- + +## 阶段二预告(另开计划,本计划不实施) + +1. P0 控件真实实现(对照 Web:`upload` / `money` / `relatedApproval` / `receiveAccount`)。 +2. H5 `/oa-start` + templates / flow-preview / drafts / instances。 +3. 小程序原生模板列表页。 +4. 移动端菜单注册 + 首页菜单 API。 +5. P1/P2 控件与套件 + 按已发布模板回归。 + +**zkxw-sc 用量优先级(2026-08-11):** +P0 `upload` > `money` > `relatedApproval` > `receiveAccount`;P1 `dept`/`person`;P2 各套件与 `owner*`/`post`。 + +--- + +## Spec coverage(自检) + +| 设计要求 | 本计划任务 | +|----------|------------| +| 独立 H5 + `@form-create/vant` | Task 2–3 | +| 水槽按 zkxw-sc 真实 type | Task 1、Task 3 rule | +| 小程序测试菜单 + 整页 web-view | Task 4 | +| 一套 JSON 渲染、不按流程单独做页 | 架构 + 阶段二 `/oa-start` 占位 | +| 正式列表/提交/菜单 | 明确 out of scope → 阶段二 | + +--- + +## 执行方式 + +Plan complete. Two execution options: + +1. **Subagent-Driven(推荐)** — 每任务新开子代理,任务间评审 +2. **Inline Execution** — 本会话按 executing-plans 连续做并设检查点 + +Which approach? diff --git a/docs/superpowers/specs/2026-08-11-mobile-oa-start-approval-design.md b/docs/superpowers/specs/2026-08-11-mobile-oa-start-approval-design.md index 7a011977..ceeb43ab 100644 --- a/docs/superpowers/specs/2026-08-11-mobile-oa-start-approval-design.md +++ b/docs/superpowers/specs/2026-08-11-mobile-oa-start-approval-design.md @@ -18,7 +18,7 @@ ### 1.2 目标 1. 在微信小程序中提供「发起审批」:模板列表(原生)+ 填写页(H5,经 web-view)。 -2. **凡 Web 可发起的已发布模板,移动端均可完整填写**(基础控件、自研组件、自研套件全覆盖)。 +2. **已在 Web 发布的模板,移动端须能完整填写**(覆盖该模板 schema 中出现的基础控件、自研组件、套件);移植与 Spike 优先按测试库真实用量,而非设计器理论全集。 3. **不按流程单独开发界面**:一个 H5 填写页根据 `appId` 拉取 JSON 动态渲染。 4. 阶段一先做「厨房水槽」真机验证,暴露 web-view + form-create/vant + 全 type 注册问题。 @@ -37,7 +37,7 @@ | 发布目标 | 微信小程序(`mobile-h5`) | | 表单承载 | 独立部署 H5 + 小程序整页 `web-view` | | 列表 vs 填写 | **A1**:模板列表用小程序原生;仅填写页进 H5 | -| 表单能力 | **全量对齐 Web**(基础 + 自研组件 + 套件) | +| 表单能力 | **已发布模板可完整发起**;Spike/移植范围以 `zkxw-sc` 真实 schema 用量为准(非设计器理论全集) | | 多流程 UI | **一套 H5 页 + JSON 渲染**,不为每个流程单独做页 | | 阶段一验证 | 首页硬编码测试菜单 → web-view → H5 水槽页(手工 kitchen-sink rule) | | 品牌色 | 三墙 `#0a9f6d` 等;禁止混用 Web `#10b981` | @@ -114,9 +114,30 @@ H5 建议独立目录(如 `mobile-oa-h5/`),Vite 构建并部署到已配 - 首页「功能菜单」本地注入测试项:`表单水槽测试` → `/pages/oa/form-sink/index`。 - 不依赖后台菜单接口(首页当前 `menus=[]`)。 -### 5.3 H5 水槽页 +### 5.3 H5 水槽页(按测试库真实用量收窄) -- 手工 `kitchen-sink-rule`:覆盖全部基础控件 + 自研组件 + 自研套件。 +不必按「设计器里理论存在的全部 type」做全量水槽。以测试库 **`zkxw-sc`** 中**已发布且最新**版本的 `oa_app_version.form_schema_json` 为准,统计客户实际用到的控件/套件,水槽 rule **只覆盖这些 type**(另加少量基础控件兜底)。 + +**2026-08-11 抽样结果(27 个已发布最新模板):** + +| 优先级 | type | 出现次数(约) | 说明 | +|--------|------|----------------|------| +| P0 | `input` / `textarea` / `datePicker`(+`date`/`datetime`) / `select` / `inputNumber`/`number` | 高 | 基础控件 | +| P0 | `upload` | 31 | 最常用自研 | +| P0 | `money` | 13 | | +| P0 | `relatedApproval` | 11 | | +| P0 | `receiveAccount` | 7 | | +| P1 | `dept` | 5 | | +| P1 | `person` | 3 | | +| P2 | `overtimeKit` | 2 | 套件 | +| P2 | `leaveKit` / `confirmationKit` / `resignationKit` / `tripKit`(+`tripItinerary`) | 各 1 | 套件 | +| P2 | `ownerPerson` / `ownerDept` / `post` | 各 1 | | + +当前已发布模板中**未出现**、可暂缓移动端移植的 type:以库内统计为准;若后续设计器新增并发布,再补注册与水槽项。 + +复测命令(本机已配 `backend/.env` → `zkxw-sc`):解析 `form_schema_json` 递归收集 `type` 字段即可;实施计划中附脚本。 + +- 手工 `kitchen-sink-rule`:覆盖上表实际用到的 type(可按 P0→P2 分批先通)。 - 底栏:校验、打印/展示 `formData`(本阶段可不接真实发起 API)。 - 审批流区:可静态占位,避免干扰表单验证。 @@ -165,31 +186,20 @@ H5 建议独立目录(如 `mobile-oa-h5/`),Vite 构建并部署到已配 --- -## 7. 自研组件与套件(全量) +## 7. 自研组件与套件(按真实用量,非理论全量) + +产品目标仍是:**已发布模板在移动端可完整发起**。实现顺序与 Spike 验证范围以 **`zkxw-sc` 已发布 schema 统计** 为准,避免为未使用的控件空转。 `type` 名称与 Web 注册保持一致。H5 侧用 Vant/移动交互重实现,不能直接复用 Element Plus 组件。 -**组件类(含但不限于):** +### 7.1 移植顺序(按 2026-08-11 库内频率) -- 上传、金额、人员、所属人、部门、所属部门、岗位、收款账户、关联审批等。 +1. **P0 高频**:`upload`、`money`、`relatedApproval`、`receiveAccount` + 基础控件 +2. **P1**:`dept`、`person` +3. **P2 套件与低频**:`overtimeKit`、`leaveKit`、`confirmationKit`、`resignationKit`、`tripKit`/`tripItinerary`、`ownerPerson`、`ownerDept`、`post` +4. 运行时细节对齐 + 按模板清单回归(付款单、日常报销、请假、出差等) -**套件类(含但不限于):** - -- 请假、加班、转正、离职、差旅/行程等。 - -**运行时:** - -- 默认值、时长/日期自动计算、手改标记与恢复、节假日等与 Web 一致的联动逻辑。 - -### 7.1 移植顺序(建议) - -1. 上传、金额、人员/部门/岗位、所属人/所属部门 -2. 收款账户、关联审批 -3. 请假 / 加班 / 转正 / 离职套件 -4. 差旅 / 行程套件 -5. 运行时细节对齐与真实模板回归 - -阶段一水槽 rule 应预先包含上述全部 type,用于尽早暴露缺口。 +发布前用同一统计脚本再跑一遍;若出现新 type,补移植后再开菜单。 ---