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:
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.
|
||||
|
||||
---
|
||||
Reference in new issue
Block a user