docs(plan): add mp webview CRM contract implementation plan.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
b8e919cc5e
commit
8a57bbed30
1 file changed
+681
@@ -0,0 +1,681 @@
|
||||
# 小程序 WebView · CRM 合同 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:** 在小程序工作台开放「CRM管理 → 合同管理 / 合同审批」,通过既有 `mp-webview` 壳打开 `web-admin` `/#/m/crm/contract*`,提供管理闭环(列表/详情/增改删/提交审批/复制/作废·恢复/上传扫描件)与审批闭环(待审/已审 + 单条与批量通过驳回),不含调价函、打印/PDF、导出、执行、续签。
|
||||
|
||||
**Architecture:** 对齐报价 WebView(ticket → exchange → `MpWebviewShell`)。新建 `views/m/crm/contract/*`,样式 `mp-webview.scss`。业务 API 复用 `@/api/crm/contract`。桌面合同表单增加 `variant="page"`。列表支持多选批量。菜单 SQL 幂等写入 `MINI_CRM_CONTRACT*`,按 Web `crm:contract:list` / `crm:contract:approve` 分别补绑。
|
||||
|
||||
**Tech Stack:** Vue 3 · Vue Router hash · Element Plus · Pinia · uni-app web-view · MySQL · Node test(`pnpm exec tsx --test`)
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-10-15-mp-webview-crm-contract-design.md`
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
| 路径 | 职责 |
|
||||
|------|------|
|
||||
| `web-admin/src/utils/mp-crm-contract-card.ts` | Tab/查询/卡片纯函数 |
|
||||
| `web-admin/src/utils/mp-crm-contract-card.test.ts` | 单测 |
|
||||
| `web-admin/src/utils/mp-webview.ts` | `MP_CRM_CONTRACT_LIST_PATH`、`MP_CRM_CONTRACT_APPROVAL_PATH` |
|
||||
| `web-admin/src/views/m/crm/contract/MpContractList.vue` | 共享列表(含多选批量) |
|
||||
| `web-admin/src/views/m/crm/contract/index.vue` | 管理壳 |
|
||||
| `web-admin/src/views/m/crm/contract/approval.vue` | 审批壳 |
|
||||
| `web-admin/src/views/m/crm/contract/detail.vue` | 详情 + 动作 |
|
||||
| `web-admin/src/views/m/crm/contract/form.vue` | 表单路由壳 |
|
||||
| `web-admin/src/views/crm/contract/components/ContractFormDialog.vue` | `variant=page` |
|
||||
| `web-admin/src/router/routes/staticRoutes.ts` | `/m/crm/contract*` |
|
||||
| `miniprogram/config/workbench-menus.js` | 两项入口 |
|
||||
| `miniprogram/config/api.js` | `WORKBENCH_ROUTE_MAP` |
|
||||
| `docs/sql/migrations/2026-10-15-mini-crm-contract-webview-menus.sql` | 菜单 + 补绑 |
|
||||
| `docs/web-admin-mp-webview-modules.md` | 合同对照行 |
|
||||
|
||||
**明确不改:** mp-webview 鉴权协议、后端合同业务语义、桌面合同默认 dialog 行为、调价函。
|
||||
|
||||
**查询语义:**
|
||||
|
||||
| mode | view | 请求 |
|
||||
|------|------|------|
|
||||
| manage | `all` | `excludeStatus=invalid` |
|
||||
| manage | `approving` | `status=approving` |
|
||||
| manage | `approved` | `status=approved` |
|
||||
| approve | `submitted` | `pendingApprovalUserId=currentUserId` |
|
||||
| approve | `approved` | `approvedByUserId=currentUserId` |
|
||||
|
||||
分页:`pageNum` / `pageSize`。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 列表卡片纯函数 + 单测
|
||||
|
||||
**Files:**
|
||||
- Create: `web-admin/src/utils/mp-crm-contract-card.ts`
|
||||
- Create: `web-admin/src/utils/mp-crm-contract-card.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写失败单测**
|
||||
|
||||
```ts
|
||||
// web-admin/src/utils/mp-crm-contract-card.test.ts
|
||||
import assert from 'node:assert/strict'
|
||||
import { describe, it } from 'node:test'
|
||||
import {
|
||||
MP_CONTRACT_MANAGE_TABS,
|
||||
MP_CONTRACT_APPROVE_TABS,
|
||||
buildContractListQuery,
|
||||
buildContractCard,
|
||||
formatContractMoney
|
||||
} from './mp-crm-contract-card'
|
||||
|
||||
describe('tabs', () => {
|
||||
it('manage all/approving/approved; approve submitted/approved', () => {
|
||||
assert.deepEqual(
|
||||
MP_CONTRACT_MANAGE_TABS.map((x) => x.value),
|
||||
['all', 'approving', 'approved']
|
||||
)
|
||||
assert.deepEqual(
|
||||
MP_CONTRACT_APPROVE_TABS.map((x) => x.value),
|
||||
['submitted', 'approved']
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildContractListQuery', () => {
|
||||
it('manage all excludes invalid and trims keyword', () => {
|
||||
assert.deepEqual(
|
||||
buildContractListQuery({
|
||||
mode: 'manage',
|
||||
view: 'all',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
keyword: ' 合同 ',
|
||||
currentUserId: 'u1'
|
||||
}),
|
||||
{ pageNum: 1, pageSize: 20, keyword: '合同', excludeStatus: 'invalid' }
|
||||
)
|
||||
})
|
||||
|
||||
it('manage approving sets status only', () => {
|
||||
const q = buildContractListQuery({
|
||||
mode: 'manage',
|
||||
view: 'approving',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
keyword: '',
|
||||
currentUserId: 'u1'
|
||||
})
|
||||
assert.equal(q.status, 'approving')
|
||||
assert.equal('pendingApprovalUserId' in q, false)
|
||||
})
|
||||
|
||||
it('manage approved sets status approved', () => {
|
||||
const q = buildContractListQuery({
|
||||
mode: 'manage',
|
||||
view: 'approved',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
keyword: '',
|
||||
currentUserId: 'u1'
|
||||
})
|
||||
assert.equal(q.status, 'approved')
|
||||
assert.equal('approvedByUserId' in q, false)
|
||||
})
|
||||
|
||||
it('approve submitted uses pendingApprovalUserId', () => {
|
||||
const q = buildContractListQuery({
|
||||
mode: 'approve',
|
||||
view: 'submitted',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
keyword: '',
|
||||
currentUserId: 'u9'
|
||||
})
|
||||
assert.equal(q.pendingApprovalUserId, 'u9')
|
||||
})
|
||||
|
||||
it('approve approved uses approvedByUserId', () => {
|
||||
const q = buildContractListQuery({
|
||||
mode: 'approve',
|
||||
view: 'approved',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
keyword: 'x',
|
||||
currentUserId: 'u9'
|
||||
})
|
||||
assert.equal(q.approvedByUserId, 'u9')
|
||||
assert.equal(q.keyword, 'x')
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildContractCard', () => {
|
||||
it('maps fields', () => {
|
||||
const card = buildContractCard({
|
||||
id: 'c1',
|
||||
contractNo: 'HT001',
|
||||
contractTitle: '硫酸销售合同',
|
||||
customerName: '甲公司',
|
||||
amount: 1000,
|
||||
freight: 50,
|
||||
barrelDeposit: 20,
|
||||
status: 'draft',
|
||||
signDate: '2026-10-01'
|
||||
})
|
||||
assert.equal(card.id, 'c1')
|
||||
assert.equal(card.contractNo, 'HT001')
|
||||
assert.equal(card.amountText, '¥1,070.00')
|
||||
assert.equal(card.status, 'draft')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatContractMoney', () => {
|
||||
it('formats and handles empty', () => {
|
||||
assert.equal(formatContractMoney(1000), '¥1,000.00')
|
||||
assert.equal(formatContractMoney(undefined), '—')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑测确认失败**
|
||||
|
||||
```bash
|
||||
cd web-admin && pnpm exec tsx --test src/utils/mp-crm-contract-card.test.ts
|
||||
```
|
||||
|
||||
Expected: FAIL / module not found
|
||||
|
||||
- [ ] **Step 3: 实现纯函数**
|
||||
|
||||
```ts
|
||||
// web-admin/src/utils/mp-crm-contract-card.ts
|
||||
export type MpContractMode = 'manage' | 'approve'
|
||||
export type MpContractManageView = 'all' | 'approving' | 'approved'
|
||||
export type MpContractApproveView = 'submitted' | 'approved'
|
||||
export type MpContractView = MpContractManageView | MpContractApproveView
|
||||
|
||||
export const MP_CONTRACT_MANAGE_TABS: Array<{ value: MpContractManageView; label: string }> = [
|
||||
{ value: 'all', label: '全部' },
|
||||
{ value: 'approving', label: '待审批' },
|
||||
{ value: 'approved', label: '已审批' }
|
||||
]
|
||||
|
||||
export const MP_CONTRACT_APPROVE_TABS: Array<{ value: MpContractApproveView; label: string }> = [
|
||||
{ value: 'submitted', label: '待审批' },
|
||||
{ value: 'approved', label: '已审批' }
|
||||
]
|
||||
|
||||
export function formatContractMoney(amount?: number | null): string {
|
||||
if (amount === undefined || amount === null || Number.isNaN(Number(amount))) return '—'
|
||||
return `¥${Number(amount).toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})}`
|
||||
}
|
||||
|
||||
export function buildContractListQuery(input: {
|
||||
mode: MpContractMode
|
||||
view: MpContractView
|
||||
pageNum: number
|
||||
pageSize: number
|
||||
keyword: string
|
||||
currentUserId: string
|
||||
}): Record<string, unknown> {
|
||||
const params: Record<string, unknown> = {
|
||||
pageNum: input.pageNum,
|
||||
pageSize: input.pageSize
|
||||
}
|
||||
const kw = String(input.keyword || '').trim()
|
||||
if (kw) params.keyword = kw
|
||||
|
||||
if (input.mode === 'manage') {
|
||||
if (input.view === 'all') params.excludeStatus = 'invalid'
|
||||
else if (input.view === 'approving') params.status = 'approving'
|
||||
else params.status = 'approved'
|
||||
return params
|
||||
}
|
||||
|
||||
if (input.view === 'submitted') {
|
||||
params.pendingApprovalUserId = input.currentUserId
|
||||
} else {
|
||||
params.approvedByUserId = input.currentUserId
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
export function buildContractCard(row: {
|
||||
id: string | number
|
||||
contractNo?: string
|
||||
contractTitle?: string
|
||||
customerName?: string
|
||||
amount?: number
|
||||
freight?: number
|
||||
barrelDeposit?: number
|
||||
status?: string
|
||||
signDate?: string
|
||||
}) {
|
||||
const total =
|
||||
(Number(row.amount) || 0) + (Number(row.freight) || 0) + (Number(row.barrelDeposit) || 0)
|
||||
return {
|
||||
id: String(row.id),
|
||||
contractNo: row.contractNo || '—',
|
||||
contractTitle: row.contractTitle || '—',
|
||||
customerName: row.customerName || '—',
|
||||
amountText: formatContractMoney(total),
|
||||
status: row.status || '',
|
||||
signDate: row.signDate || '—'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
状态 Tag 在页面用 `dictStore` `crm_contract_status`。
|
||||
|
||||
- [ ] **Step 4: 跑测确认通过**
|
||||
|
||||
```bash
|
||||
cd web-admin && pnpm exec tsx --test src/utils/mp-crm-contract-card.test.ts
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/utils/mp-crm-contract-card.ts web-admin/src/utils/mp-crm-contract-card.test.ts
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(web-admin): add mp CRM contract card query helpers.
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 菜单 SQL
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/sql/migrations/2026-10-15-mini-crm-contract-webview-menus.sql`
|
||||
|
||||
- [ ] **Step 1: 写入幂等 SQL**(结构对齐报价脚本)
|
||||
|
||||
```sql
|
||||
-- 小程序:CRM管理 → 合同管理 / 合同审批(WebView)
|
||||
-- list 随 CRM3000000000000000000000000020 (crm:contract:list)
|
||||
-- approve 随 CRM3000000000000000000000000021 (crm:contract:approve)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
INSERT INTO `sys_menu` (
|
||||
`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`,
|
||||
`is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`,
|
||||
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
|
||||
)
|
||||
SELECT
|
||||
'MINI_CRM_ROOT', 'CRM管理', '1', 'M', '/mini/crm', NULL, NULL,
|
||||
1, '1', '1', 'shanxia2.0', 2, '小程序CRM管理',
|
||||
'system', '', NOW(), NULL, NOW(), 0
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `sys_menu` WHERE `id` = 'MINI_CRM_ROOT' AND `del_flag` = 0
|
||||
);
|
||||
|
||||
INSERT INTO `sys_menu` (
|
||||
`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`,
|
||||
`is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`,
|
||||
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
|
||||
)
|
||||
SELECT
|
||||
'MINI_CRM_CONTRACT', '合同管理', 'MINI_CRM_ROOT', 'C', 'mp-webview', NULL,
|
||||
'mini:crm:contract:list',
|
||||
1, '1', '1', 'shanxia2.0', 4, '小程序 WebView CRM 合同管理',
|
||||
'system', '', NOW(), NULL, NOW(), 0
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `sys_menu` WHERE `id` = 'MINI_CRM_CONTRACT' AND `del_flag` = 0
|
||||
);
|
||||
|
||||
INSERT INTO `sys_menu` (
|
||||
`id`, `menu_name`, `parent_id`, `menu_type`, `path`, `component`, `perms`,
|
||||
`is_frame`, `visible`, `status`, `tenant_no`, `order_num`, `remark`,
|
||||
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
|
||||
)
|
||||
SELECT
|
||||
'MINI_CRM_CONTRACT_APPROVAL', '合同审批', 'MINI_CRM_ROOT', 'C', 'mp-webview', NULL,
|
||||
'mini:crm:contract:approve',
|
||||
1, '1', '1', 'shanxia2.0', 5, '小程序 WebView CRM 合同审批',
|
||||
'system', '', NOW(), NULL, NOW(), 0
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `sys_menu` WHERE `id` = 'MINI_CRM_CONTRACT_APPROVAL' AND `del_flag` = 0
|
||||
);
|
||||
|
||||
INSERT INTO `sys_role_menu` (
|
||||
`id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`,
|
||||
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
|
||||
)
|
||||
SELECT
|
||||
MD5(CONCAT(r.id, '|', m.menu_id, '|mini-crm-contract-webview|v1')),
|
||||
r.id,
|
||||
m.menu_id,
|
||||
IFNULL(NULLIF(r.tenant_no, ''), 'shanxia2.0'),
|
||||
0,
|
||||
'小程序CRM合同管理:随 Web crm:contract:list 补绑',
|
||||
'system', '', NOW(), NULL, NOW(), 0
|
||||
FROM `sys_role` r
|
||||
JOIN `sys_role_menu` web
|
||||
ON web.role_id = r.id
|
||||
AND web.menu_id = 'CRM3000000000000000000000000020'
|
||||
AND web.del_flag = 0
|
||||
JOIN (
|
||||
SELECT '1' AS menu_id UNION ALL
|
||||
SELECT 'MINI_CRM_ROOT' UNION ALL
|
||||
SELECT 'MINI_CRM_CONTRACT'
|
||||
) m
|
||||
WHERE r.del_flag = 0
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `sys_role_menu` x
|
||||
WHERE x.role_id = r.id AND x.menu_id = m.menu_id AND x.del_flag = 0
|
||||
);
|
||||
|
||||
INSERT INTO `sys_role_menu` (
|
||||
`id`, `role_id`, `menu_id`, `tenant_no`, `order_num`, `remark`,
|
||||
`create_by`, `create_dept_id`, `create_time`, `update_by`, `update_time`, `del_flag`
|
||||
)
|
||||
SELECT
|
||||
MD5(CONCAT(r.id, '|', m.menu_id, '|mini-crm-contract-approval-webview|v1')),
|
||||
r.id,
|
||||
m.menu_id,
|
||||
IFNULL(NULLIF(r.tenant_no, ''), 'shanxia2.0'),
|
||||
0,
|
||||
'小程序CRM合同审批:随 Web crm:contract:approve 补绑',
|
||||
'system', '', NOW(), NULL, NOW(), 0
|
||||
FROM `sys_role` r
|
||||
JOIN `sys_role_menu` web
|
||||
ON web.role_id = r.id
|
||||
AND web.menu_id = 'CRM3000000000000000000000000021'
|
||||
AND web.del_flag = 0
|
||||
JOIN (
|
||||
SELECT '1' AS menu_id UNION ALL
|
||||
SELECT 'MINI_CRM_ROOT' UNION ALL
|
||||
SELECT 'MINI_CRM_CONTRACT_APPROVAL'
|
||||
) m
|
||||
WHERE r.del_flag = 0
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `sys_role_menu` x
|
||||
WHERE x.role_id = r.id AND x.menu_id = m.menu_id AND x.del_flag = 0
|
||||
);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit(本 Task 不连库执行)**
|
||||
|
||||
```bash
|
||||
git add docs/sql/migrations/2026-10-15-mini-crm-contract-webview-menus.sql
|
||||
git commit -m "$(cat <<'EOF'
|
||||
docs(sql): add miniprogram CRM contract webview menus and role bind.
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 静态路由 + 路径常量 + 空壳
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/utils/mp-webview.ts`
|
||||
- Modify: `web-admin/src/router/routes/staticRoutes.ts`
|
||||
- Create shells under `web-admin/src/views/m/crm/contract/`
|
||||
|
||||
- [ ] **Step 1: 常量**
|
||||
|
||||
```ts
|
||||
export const MP_CRM_CONTRACT_LIST_PATH = '/m/crm/contract'
|
||||
export const MP_CRM_CONTRACT_APPROVAL_PATH = '/m/crm/contract/approval'
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 路由(catch-all 前;顺序固定)**
|
||||
|
||||
```ts
|
||||
{
|
||||
path: '/m/crm/contract',
|
||||
name: 'MpCrmContractList',
|
||||
component: () => import('@views/m/crm/contract/index.vue'),
|
||||
meta: { title: '合同管理', isHideTab: true, isMpWebview: true }
|
||||
},
|
||||
{
|
||||
path: '/m/crm/contract/approval',
|
||||
name: 'MpCrmContractApproval',
|
||||
component: () => import('@views/m/crm/contract/approval.vue'),
|
||||
meta: { title: '合同审批', isHideTab: true, isMpWebview: true }
|
||||
},
|
||||
{
|
||||
path: '/m/crm/contract/new',
|
||||
name: 'MpCrmContractNew',
|
||||
component: () => import('@views/m/crm/contract/form.vue'),
|
||||
meta: { title: '新增合同', isHideTab: true, isMpWebview: true }
|
||||
},
|
||||
{
|
||||
path: '/m/crm/contract/:id/edit',
|
||||
name: 'MpCrmContractEdit',
|
||||
component: () => import('@views/m/crm/contract/form.vue'),
|
||||
meta: { title: '编辑合同', isHideTab: true, isMpWebview: true }
|
||||
},
|
||||
{
|
||||
path: '/m/crm/contract/:id',
|
||||
name: 'MpCrmContractDetail',
|
||||
component: () => import('@views/m/crm/contract/detail.vue'),
|
||||
meta: { title: '合同详情', isHideTab: true, isMpWebview: true }
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 最小壳 vue**(`MpWebviewShell` + title;同报价 Task 3)
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(web-admin): register /m/crm/contract static routes.
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 共享列表 + 多选批量
|
||||
|
||||
**Files:**
|
||||
- Create: `web-admin/src/views/m/crm/contract/MpContractList.vue`
|
||||
- Replace: `index.vue` / `approval.vue` 薄壳
|
||||
|
||||
对照:`views/m/crm/quote/MpQuoteList.vue`,并增加多选。
|
||||
|
||||
- [ ] **Step 1: 实现 `MpContractList.vue`**
|
||||
|
||||
1. Prop `mode: 'manage' | 'approve'`
|
||||
2. `@ready` 后 `listContract(buildContractListQuery(...))`;解析 `list|rows` + `total`
|
||||
3. `resolveCurrentUserId` 从 `MpQuoteList` / `ContractListPage` 复制
|
||||
4. Tab + keyword + 卡片(`buildContractCard` + dict 状态 Tag)
|
||||
5. **多选**:卡片左侧 checkbox;`selectedIds`;底栏「已选 N」
|
||||
6. manage:Speed Dial 新增;批量「提交审批」(打开 Sheet,仅草稿 id);可选批量作废(逐条 `invalidContract`,跳过不可作废)
|
||||
7. approve:批量通过/驳回(Sheet;驳回意见必填);对每条解析 pending approval id(对齐桌面:`listContractApproval` 找当前用户 pending),调用 `approvePass` / `approveReject`(打开 `api/crm/contract/approval.ts` 核对字段)
|
||||
8. 详情:`/m/crm/contract/${id}?from=${mode}`
|
||||
9. `goToMiniWorkbench()`;`mp-webview.scss`;403→无权限
|
||||
10. **禁止**:导出、打印、执行、续签、facet
|
||||
|
||||
批量提交 Sheet:`getContractApproverSetting` 预填 + `fetchUserOptions`;`batchSubmitApproval({ ids, approverIds, remark })`。
|
||||
|
||||
- [ ] **Step 2: 薄壳**
|
||||
|
||||
```vue
|
||||
<template><MpContractList mode="manage" /></template>
|
||||
```
|
||||
|
||||
approval:`mode="approve"`。
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(web-admin): add mp webview CRM contract list pages.
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 详情页动作
|
||||
|
||||
**Files:**
|
||||
- Create/Modify: `web-admin/src/views/m/crm/contract/detail.vue`
|
||||
|
||||
对照:`views/m/crm/quote/detail.vue` + 桌面 `ContractDetailDialog` 动作集合(排除执行/续签/打印)。
|
||||
|
||||
- [ ] **Step 1: 加载与展示**
|
||||
|
||||
`getContract` + 明细 + `listContractApproval`;分区展示;扫描件 URL 可预览。
|
||||
|
||||
- [ ] **Step 2: 管理底栏**
|
||||
|
||||
| 条件 | 按钮 |
|
||||
|------|------|
|
||||
| draft | 提交审批、编辑、删除(`canDeleteByCreator`)、作废、复制、上传扫描件 |
|
||||
| 可作废状态(draft/approving/approved/executing 等,对齐桌面) | 作废、复制、上传;可编辑则编辑 |
|
||||
| invalid | 作废恢复、复制 |
|
||||
| 永不出现 | 执行、续签、打印、审批通过驳回 |
|
||||
|
||||
动作 API:`submitApproval`、`invalidContract`、`restoreContract`、`delContract`、`uploadFile`/`uploadAttachments`(对照 `UploadDialog.vue`)、复制 → `/new?copyFrom=`。
|
||||
|
||||
- [ ] **Step 3: 审批底栏**
|
||||
|
||||
待我审 → 通过/驳回 Sheet → `approvePass`/`approveReject`。
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(web-admin): add mp webview CRM contract detail actions.
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 表单 `variant="page"`
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/crm/contract/components/ContractFormDialog.vue`(及内部 Panel 若有)
|
||||
- Create/Modify: `web-admin/src/views/m/crm/contract/form.vue`
|
||||
|
||||
对照报价 `QuoteFormDialog` page 模式与 `views/m/crm/quote/form.vue`。
|
||||
|
||||
- [ ] **Step 1: 默认 `variant=dialog`;page 模式无 el-dialog、单列布局、sticky 底栏(取消/保存)**
|
||||
- [ ] **Step 2: form.vue 支持 add / edit / copyFrom**
|
||||
- [ ] **Step 3: 验证全字段保存**
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(web-admin): add page variant for contract form in mp webview.
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: 工作台入口
|
||||
|
||||
**Files:**
|
||||
- Modify: `miniprogram/config/workbench-menus.js`
|
||||
- Modify: `miniprogram/config/api.js`
|
||||
|
||||
- [ ] **Step 1: CRM menus 增加两项**
|
||||
|
||||
```js
|
||||
{
|
||||
name: '合同管理',
|
||||
enabled: true,
|
||||
code: 'crm-contract-webview',
|
||||
icon: '📄',
|
||||
perms: 'mini:crm:contract:list'
|
||||
},
|
||||
{
|
||||
name: '合同审批',
|
||||
enabled: true,
|
||||
code: 'crm-contract-approval-webview',
|
||||
icon: '✅',
|
||||
perms: 'mini:crm:contract:approve'
|
||||
}
|
||||
```
|
||||
|
||||
`desc` 更新含「合同」。**不要**加调价函。
|
||||
|
||||
- [ ] **Step 2: WORKBENCH_ROUTE_MAP**
|
||||
|
||||
```js
|
||||
'crm-contract-webview':
|
||||
PAGE_PATHS.mpWebview + '?path=' + encodeURIComponent('/m/crm/contract'),
|
||||
'crm-contract-approval-webview':
|
||||
PAGE_PATHS.mpWebview + '?path=' + encodeURIComponent('/m/crm/contract/approval')
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(miniprogram): enable CRM contract webview workbench entries.
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: 文档 + 回归清单
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/web-admin-mp-webview-modules.md`
|
||||
|
||||
- [ ] **Step 1: 增加 CRM 合同对照**
|
||||
|
||||
```markdown
|
||||
## CRM 合同对照
|
||||
|
||||
- 管理 `/m/crm/contract`,审批 `/m/crm/contract/approval`,详情 `/:id`,表单 `/new` `/:id/edit`
|
||||
- 工作台 code `crm-contract-webview` / `crm-contract-approval-webview`
|
||||
- perms `mini:crm:contract:list` / `mini:crm:contract:approve`
|
||||
- 支持单条与批量提交/审批;含复制、作废/恢复、上传扫描件
|
||||
- 不做:调价函、打印/PDF、导出、执行、续签
|
||||
```
|
||||
|
||||
文首「已落地」补上合同。
|
||||
|
||||
- [ ] **Step 2: 回归(spec §10);测试库执行 Task 2 SQL**
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "$(cat <<'EOF'
|
||||
docs: document CRM contract as mp webview module.
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Spec 覆盖自检
|
||||
|
||||
| Spec 项 | Task |
|
||||
|---------|------|
|
||||
| 嵌套 H5 | 3–7 |
|
||||
| 管理闭环 B + 批量 | 4, 5 |
|
||||
| 全字段表单 | 6 |
|
||||
| 不做调价函/打印/导出/执行/续签 | 4–8 禁止 |
|
||||
| 菜单 + 补绑 | 2 |
|
||||
| 工作台 | 7 |
|
||||
| 文档 | 8 |
|
||||
|
||||
---
|
||||
|
||||
## 执行说明
|
||||
|
||||
当前分支 V1.5 可直接实现(与报价同模式)。每 Task 完成后 commit。后端无新接口。测试库执行菜单 SQL。
|
||||
Reference in new issue
Block a user