docs: add mobile OA detail actions fold implementation plan

Break the folded footer and default-remark work into helper tests plus OaDetail.vue tasks.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-08-12 18:11:43 +08:00
1 parent 1c844398a9
commit 7da691e4c6
1 file changed
+399
@@ -0,0 +1,399 @@
# Mobile OA Detail Actions Fold 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:** Collapse approve-mode footer on mobile OA detail to `⋯` + 打回 + 同意, put other actions in an ActionSheet, and prefill default remarks for comment / add-sign / transfer.
**Architecture:** Extract pure helpers (`defaultComment`, `buildMoreActions`) for defaults and more-menu items; change only `OaDetail.vue` approve footer + sheet wiring. Non-approve footers stay as today. Visual authority: `mobile-ui/zkxw-mobile-oa-detail-actions-demo.html` scheme A.
**Tech Stack:** Vue 3, Vant 4 (`van-action-sheet`, existing `van-dialog` / `van-popup`), Vite; Node built-in `node:test` for pure helper tests (no new test deps).
**Spec:** `docs/superpowers/specs/2026-08-12-mobile-oa-detail-actions-fold-design.md`
---
## File map
| File | Responsibility |
|------|----------------|
| Create: `mobile-oa-h5/src/oa/oa-action-defaults.js` | Default remark strings + `buildMoreActions(detail)` |
| Create: `mobile-oa-h5/src/oa/oa-action-defaults.test.js` | Node tests for helpers |
| Modify: `mobile-oa-h5/src/views/OaDetail.vue` | Approve footer fold, ActionSheet, wire defaults |
| Unchanged: `mobile-ui/zkxw-mobile-oa-detail-actions-demo.html` | Visual reference only |
---
### Task 1: Pure helpers for defaults and more actions
**Files:**
- Create: `mobile-oa-h5/src/oa/oa-action-defaults.js`
- Create: `mobile-oa-h5/src/oa/oa-action-defaults.test.js`
- [ ] **Step 1: Write the failing test**
Create `mobile-oa-h5/src/oa/oa-action-defaults.test.js`:
```js
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { defaultComment, buildMoreActions } from './oa-action-defaults.js'
describe('defaultComment', () => {
it('returns fixed defaults by kind', () => {
assert.equal(defaultComment('approve'), '同意')
assert.equal(defaultComment('reject'), '拒绝')
assert.equal(defaultComment('return'), '请修改后重新提交')
assert.equal(defaultComment('addSign'), '加签')
assert.equal(defaultComment('transfer'), '转交')
})
it('returns empty string for unknown kind', () => {
assert.equal(defaultComment('x'), '')
})
})
describe('buildMoreActions', () => {
it('orders reject, addSign, transfer, print, close when all allowed', () => {
const keys = buildMoreActions({ allowAddSign: true }).map((a) => a.key)
assert.deepEqual(keys, ['reject', 'addSign', 'transfer', 'print', 'close'])
})
it('omits addSign when allowAddSign is false', () => {
const keys = buildMoreActions({ allowAddSign: false }).map((a) => a.key)
assert.deepEqual(keys, ['reject', 'transfer', 'print', 'close'])
})
it('includes addSign when allowAddSign is undefined', () => {
const keys = buildMoreActions({}).map((a) => a.key)
assert.ok(keys.includes('addSign'))
})
it('sets name and optional color on actions', () => {
const reject = buildMoreActions({}).find((a) => a.key === 'reject')
assert.equal(reject.name, '拒绝')
assert.equal(reject.color, '#ee0a24')
})
})
```
- [ ] **Step 2: Run test to verify it fails**
```bash
cd mobile-oa-h5 && node --test src/oa/oa-action-defaults.test.js
```
Expected: FAIL (module not found / cannot resolve `./oa-action-defaults.js`)
- [ ] **Step 3: Write minimal implementation**
Create `mobile-oa-h5/src/oa/oa-action-defaults.js`:
```js
const DEFAULT_COMMENTS = {
approve: '同意',
reject: '拒绝',
return: '请修改后重新提交',
addSign: '加签',
transfer: '转交'
}
/** @param {string} kind */
export function defaultComment(kind) {
return DEFAULT_COMMENTS[kind] || ''
}
/**
* More ActionSheet items for approve mode (spec order).
* @param {{ allowAddSign?: boolean } | null | undefined} detail
* @returns {{ key: string, name: string, color?: string }[]}
*/
export function buildMoreActions(detail) {
const list = [{ key: 'reject', name: '拒绝', color: '#ee0a24' }]
if (detail?.allowAddSign !== false) {
list.push({ key: 'addSign', name: '加签' })
}
list.push(
{ key: 'transfer', name: '转交' },
{ key: 'print', name: '打印' },
{ key: 'close', name: '关闭' }
)
return list
}
```
- [ ] **Step 4: Run tests to verify they pass**
```bash
cd mobile-oa-h5 && node --test src/oa/oa-action-defaults.test.js
```
Expected: PASS (all tests)
- [ ] **Step 5: Commit**
```bash
git add mobile-oa-h5/src/oa/oa-action-defaults.js mobile-oa-h5/src/oa/oa-action-defaults.test.js
git commit -m "$(cat <<'EOF'
feat(oa): add mobile detail action defaults helpers
Centralize default approval remarks and more-menu action order for the folded footer.
EOF
)"
```
---
### Task 2: Fold approve footer + ActionSheet + default remarks in OaDetail
**Files:**
- Modify: `mobile-oa-h5/src/views/OaDetail.vue`
- Test: manual checklist below + re-run helper tests
- [ ] **Step 1: Import helpers and add sheet state**
Near other imports in `<script setup>`, add:
```js
import { defaultComment, buildMoreActions } from '../oa/oa-action-defaults.js'
```
After existing refs (`transferComment` etc.), add:
```js
const moreVisible = ref(false)
const moreActions = computed(() => buildMoreActions(detail.value))
```
- [ ] **Step 2: Prefill defaults in openers**
Replace `openComment` / `openAddSign` / `openTransfer` bodies so they prefill (keep other logic):
```js
function openComment(kind) {
if (!myPendingTask.value) return
commentKind.value = kind
commentText.value = defaultComment(kind)
commentVisible.value = true
}
function openAddSign() {
if (!myPendingTask.value) return
addSignUsers.value = []
addSignComment.value = defaultComment('addSign')
addSignVisible.value = true
}
function openTransfer() {
if (!myPendingTask.value) return
transferUser.value = null
transferComment.value = defaultComment('transfer')
transferVisible.value = true
}
```
- [ ] **Step 3: Add more-sheet handlers**
```js
function openMore() {
if (acting.value || printing.value) return
moreVisible.value = true
}
function onMoreSelect(action) {
moreVisible.value = false
const key = action?.key || ''
if (key === 'reject') openComment('reject')
else if (key === 'addSign') openAddSign()
else if (key === 'transfer') openTransfer()
else if (key === 'print') void onPrint()
else if (key === 'close') onClose()
}
```
- [ ] **Step 4: Replace approve-mode footer template**
Replace the entire `<footer class="oa-detail__ft">...</footer>` block with:
```vue
<footer class="oa-detail__ft">
<template v-if="canApprove">
<van-button
class="oa-detail__btn oa-detail__btn--more"
:disabled="acting || printing"
aria-label="更多"
@click="openMore"
>
⋯
</van-button>
<van-button
v-if="detail?.allowReturn !== false"
class="oa-detail__btn oa-detail__btn--ghost"
:loading="acting"
:disabled="acting || printing"
@click="openComment('return')"
>
打回
</van-button>
<van-button
class="oa-detail__btn oa-detail__btn--primary"
type="primary"
:loading="acting"
:disabled="acting || printing"
@click="openComment('approve')"
>
同意
</van-button>
</template>
<template v-else>
<van-button class="oa-detail__btn oa-detail__btn--close" @click="onClose">
关闭
</van-button>
<van-button
class="oa-detail__btn oa-detail__btn--ghost oa-detail__btn--print"
:loading="printing"
:disabled="acting || printing"
@click="onPrint"
>
打印
</van-button>
<van-button
v-if="canWithdraw"
class="oa-detail__btn oa-detail__btn--ghost"
:loading="acting"
:disabled="acting"
@click="withdraw"
>
撤销
</van-button>
<van-button
v-if="canCopyRestart"
class="oa-detail__btn oa-detail__btn--primary"
type="primary"
:disabled="acting"
@click="goCopyRestart"
>
复制并重新发起
</van-button>
</template>
</footer>
```
- [ ] **Step 5: Add ActionSheet markup**
Place after the transfer popup (before closing `</div>` of root), still inside root template:
```vue
<van-action-sheet
v-model:show="moreVisible"
:actions="moreActions"
cancel-text="取消"
close-on-click-action
@select="onMoreSelect"
/>
```
- [ ] **Step 6: Add `.oa-detail__btn--more` styles**
In the existing `<style scoped>` next to `.oa-detail__btn--close`, add:
```css
.oa-detail__btn--more {
flex: 0 0 auto;
width: 44px;
min-width: 44px;
padding: 0 !important;
border: 1px solid #e2e8f0 !important;
background: #fff !important;
color: #334155 !important;
font-size: 18px;
font-weight: 700;
letter-spacing: 1px;
line-height: 1;
}
```
Also tighten approve body bottom padding if needed: keep `.oa-detail__body--approve { padding-bottom: 128px; }` (single-row footer is shorter; optional reduce to `96px` if content feels too padded — prefer `96px` after fold).
Change:
```css
.oa-detail__body--approve {
padding-bottom: 96px;
}
```
- [ ] **Step 7: Re-run helper tests + build**
```bash
cd mobile-oa-h5 && node --test src/oa/oa-action-defaults.test.js && npm run build
```
Expected: tests PASS; Vite build succeeds.
- [ ] **Step 8: Manual UI checklist (dev)**
```bash
cd mobile-oa-h5 && npm run dev
```
Open an approve-mode detail URL (`?instanceId=…&taskId=…&mode=approve&token=…`). Verify:
1. Footer shows only `⋯` / 打回 / 同意 (no flat 拒绝/加签/转交/打印/关闭).
2. More sheet order: 拒绝 → 加签 → 转交 → 打印 → 关闭.
3. 同意 dialog text starts as `同意`; 打回 as `请修改后重新提交`; 拒绝 as `拒绝`.
4. Clear 打回 text → confirm shows Toast「打回意见必填」, dialog stays.
5. 加签 / 转交 sheets open with description prefilled `加签` / `转交`.
6. Non-approve view still shows 关闭 / 打印 (+ 撤销 / 复制重开 when applicable).
- [ ] **Step 9: Commit**
```bash
git add mobile-oa-h5/src/views/OaDetail.vue
git commit -m "$(cat <<'EOF'
feat(oa): fold mobile detail approve actions into more sheet
Show only return and approve on the footer, move secondary actions to ActionSheet, and prefill default remarks.
EOF
)"
```
---
### Task 3: Spec self-check + graph (if required)
**Files:** none required if only `mobile-oa-h5` Vue/JS touched; graph update is optional for this leaf UI change. Per AGENTS.md, run graph update when function signatures / imports change.
- [ ] **Step 1: Confirm coverage against spec §7**
Map:
| Spec验收 | Covered by |
|----------|------------|
| 1 默认只见 ⋯/打回/同意 | Task 2 Step 4 |
| 2 更多顺序与条件 | Task 1 `buildMoreActions` + Task 2 Step 5 |
| 3 默认备注与打回必填 | Task 1 + Task 2 Step 2 + existing `onCommentBeforeClose` |
| 4 加签/转交预填 | Task 2 Step 2 |
| 5 closeToMini 不变 | No change to submit success paths |
- [ ] **Step 2: Optional graph refresh**
```bash
./scripts/update-graph.sh backend
```
Skip if script only tracks backend/web-admin; this change is mobile-oa-h5 only — **skip** unless project policy requires it.
- [ ] **Step 3: Done**
No extra commit if nothing changed.
---
## Self-review (plan vs spec)
1. **Spec coverage:** §4.1–4.3 footer/more/non-approve → Task 2; §5 defaults → Task 1+2; §7 acceptance → Task 2 Step 8 + Task 3. Demo already committed. Non-goals (mobile-h5 / API / web-admin) not in plan.
2. **Placeholders:** None; full code in steps.
3. **Consistency:** Keys `reject|addSign|transfer|print|close` match `onMoreSelect` and `buildMoreActions`; default kinds match `openComment` / add-sign / transfer.
---