docs: add implementation plan for payment card period collapse
Break down presenter periodBlocks, MarketListCard UI, CSS, and TDD steps. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
42dde4b0bc
commit
bbc24b7c73
1 file changed
+471
@@ -0,0 +1,471 @@
|
|||||||
|
# Payment List Card Period Collapse 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:** 市场【缴费信息】列表卡片按期折叠展示,默认只显示「第几期 / 应缴 / 已缴」,点击可独立展开明细。
|
||||||
|
|
||||||
|
**Architecture:** 对齐运费 `sampleBlocks`:`paymentCard` 产出 `periodBlocks` + `footerFields`(累计实缴等放在期次区之后);`MarketListCard` 渲染每期独立折叠行;样式对齐 Demo。不改后端与催缴模块。
|
||||||
|
|
||||||
|
**Tech Stack:** Vue 3, Vant 4, CSS, Node.js `node:test`
|
||||||
|
|
||||||
|
**Spec:** `docs/superpowers/specs/2026-10-14-payment-card-period-collapse-design.md`
|
||||||
|
**Demo:** `docs/demos/2026-10-14-payment-card-period-collapse-demo.html`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File map
|
||||||
|
|
||||||
|
| File | Responsibility |
|
||||||
|
|------|----------------|
|
||||||
|
| `mobile-oa-h5/src/market/list-presenter.js` | `isPaymentPeriodPresent` + `paymentPeriodBlock` + rewrite `paymentCard` |
|
||||||
|
| `mobile-oa-h5/src/market/list-presenter.test.js` | presenter 行为断言 |
|
||||||
|
| `mobile-oa-h5/src/market/components/MarketListCard.vue` | 期次折叠 UI + `footerFields` |
|
||||||
|
| `mobile-oa-h5/src/styles/market.css` | `.market-list-card__periods*` 样式 |
|
||||||
|
| `mobile-oa-h5/src/market/mobile-parity-ui.test.js` | 结构 / 默认折叠 / 44px 触控 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Presenter — failing tests for `periodBlocks`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `mobile-oa-h5/src/market/list-presenter.test.js`
|
||||||
|
- Test: `mobile-oa-h5/src/market/list-presenter.test.js`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update existing payment card test and add period-block cases**
|
||||||
|
|
||||||
|
Replace the assertions in `test('payment card formats summary totals and progress', …)` that expect flat period fields, and add a focused test. Keep the existing fixture; change expectations as follows:
|
||||||
|
|
||||||
|
```js
|
||||||
|
test('payment card formats summary totals and progress', () => {
|
||||||
|
const card = buildMarketCard('payment', 'manager', {
|
||||||
|
protocolNo: 'P-1', motherName: '周雨桐', sampleCode: 'S-1', payMode: 'fq',
|
||||||
|
contractAmount: '10000.00', paidAmount: '3000.00', remainAmount: '7000.00',
|
||||||
|
signDate: '2026-10-01', storageYears: 20, sampleTypeName: '脐带血',
|
||||||
|
discountAmount: '1000.00', receivableAmount: '9000.00', remark: '首期已缴',
|
||||||
|
periods: [{ scheduleNo: 1, dueAmount: 3000, dueDate: '2026-10-10', displayPaidAmount: 3000, payDate: '2026-10-11', arrearsAmount: 0 }],
|
||||||
|
clearedScheduleCount: 1, activeScheduleCount: 4, paymentProgress: '部分已缴',
|
||||||
|
allowedActions: ['register-transfer']
|
||||||
|
})
|
||||||
|
assert.equal(card.status, '部分已缴')
|
||||||
|
assert.equal(card.subtitle, 'S-1')
|
||||||
|
assert.ok(card.fields.some((field) => field.label === '样本编码' && field.value === 'S-1'))
|
||||||
|
assert.ok(card.fields.some((field) => field.label === '协议号' && field.value === 'P-1'))
|
||||||
|
assert.ok(card.fields.some((field) => field.label === '签署日期' && field.value === '2026-10-01'))
|
||||||
|
assert.ok(card.fields.some((field) => field.label === '存储项目' && field.value === '脐带血'))
|
||||||
|
assert.ok(!card.fields.some((field) => String(field.label).includes('第1期')))
|
||||||
|
assert.equal(card.periodBlocks.length, 1)
|
||||||
|
assert.equal(card.periodBlocks[0].scheduleNo, 1)
|
||||||
|
assert.equal(card.periodBlocks[0].dueAmount, '¥3,000.00')
|
||||||
|
assert.equal(card.periodBlocks[0].paidAmount, '¥3,000.00')
|
||||||
|
assert.ok(card.periodBlocks[0].fields.some((f) => f.label === '应缴费时间' && f.value === '2026-10-10'))
|
||||||
|
assert.ok(card.periodBlocks[0].fields.some((f) => f.label === '已缴费时间' && f.value === '2026-10-11'))
|
||||||
|
assert.ok(card.periodBlocks[0].fields.some((f) => f.label === '欠缴费金额' && f.value === '¥0.00'))
|
||||||
|
assert.ok(card.footerFields.some((field) => field.label === '待缴金额' && field.value === '¥7,000.00'))
|
||||||
|
assert.ok(card.footerFields.some((field) => field.label === '累计实缴' && field.value === '¥3,000.00'))
|
||||||
|
assert.ok(card.footerFields.some((field) => field.label === '备注' && field.value === '首期已缴'))
|
||||||
|
assert.deepEqual(card.actions, ['view-protocol', 'view-payment-detail'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('payment card periodBlocks skip empty periods and do not pad to six', () => {
|
||||||
|
const card = buildMarketCard('payment', 'specialist', {
|
||||||
|
motherName: '李婷', sampleCode: 'S-2', payMode: 'fq',
|
||||||
|
periods: [
|
||||||
|
{ scheduleNo: 1, dueAmount: 1000, dueDate: '2026-01-01', paidAmount: 1000, payDate: '2026-01-02', arrearsAmount: 0 },
|
||||||
|
{},
|
||||||
|
{ scheduleNo: 3, dueAmount: 2000, dueDate: '2026-07-01', paidAmount: 0, arrearsAmount: 2000 },
|
||||||
|
null,
|
||||||
|
undefined
|
||||||
|
]
|
||||||
|
})
|
||||||
|
assert.equal(card.periodBlocks.length, 2)
|
||||||
|
assert.equal(card.periodBlocks[0].key, 'period-1')
|
||||||
|
assert.equal(card.periodBlocks[1].scheduleNo, 3)
|
||||||
|
assert.equal(card.periodBlocks[1].dueAmount, '¥2,000.00')
|
||||||
|
assert.equal(card.periodBlocks[1].paidAmount, '¥0.00')
|
||||||
|
assert.ok(!card.fields.some((field) => /第\d+期/.test(field.label)))
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests and verify RED**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mobile-oa-h5 && node --test --test-name-pattern="payment card" src/market/list-presenter.test.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL — `periodBlocks` / `footerFields` undefined, or flat `第1期·` fields still present.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Presenter — implement `periodBlocks` + `footerFields`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `mobile-oa-h5/src/market/list-presenter.js` (`paymentCard` and helpers near it)
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add helpers and rewrite `paymentCard`**
|
||||||
|
|
||||||
|
Insert before `paymentCard` (after `chaseCard`):
|
||||||
|
|
||||||
|
```js
|
||||||
|
function hasPresentValue(value) {
|
||||||
|
if (value == null) return false
|
||||||
|
if (typeof value === 'number') return Number.isFinite(value)
|
||||||
|
return String(value).trim() !== ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPaymentPeriodPresent(period) {
|
||||||
|
if (!period || typeof period !== 'object') return false
|
||||||
|
return (
|
||||||
|
hasPresentValue(period.scheduleNo) ||
|
||||||
|
hasPresentValue(period.dueAmount) ||
|
||||||
|
hasPresentValue(period.displayPaidAmount) ||
|
||||||
|
hasPresentValue(period.paidAmount) ||
|
||||||
|
hasPresentValue(period.arrearsAmount) ||
|
||||||
|
hasPresentValue(period.dueDate) ||
|
||||||
|
hasPresentValue(period.payDate)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function paymentPeriodBlock(period, index) {
|
||||||
|
const scheduleNo = Number(period.scheduleNo) || index + 1
|
||||||
|
const paidRaw = period.displayPaidAmount ?? period.paidAmount
|
||||||
|
return {
|
||||||
|
key: String(period.id || `period-${scheduleNo}`),
|
||||||
|
scheduleNo,
|
||||||
|
dueAmount: money(period.dueAmount),
|
||||||
|
paidAmount: money(paidRaw),
|
||||||
|
fields: [
|
||||||
|
field('应缴费时间', period.dueDate),
|
||||||
|
field('已缴费时间', period.payDate),
|
||||||
|
field('欠缴费金额', money(period.arrearsAmount), { money: true })
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function paymentCard(item) {
|
||||||
|
const progress = label({}, item.paymentProgress || item.progress || (Number(item.paidAmount || 0) > 0 ? '部分已缴' : '待缴清'))
|
||||||
|
const periods = (Array.isArray(item.periods) ? item.periods : []).filter(isPaymentPeriodPresent)
|
||||||
|
return {
|
||||||
|
title: text(item.motherName),
|
||||||
|
subtitle: text(item.sampleCode),
|
||||||
|
status: progress,
|
||||||
|
fields: [
|
||||||
|
field('协议号', item.protocolNo, { wide: true }),
|
||||||
|
field('姓名', item.motherName), field('手机号', item.motherPhone),
|
||||||
|
field('样本编码', item.sampleCode, { wide: true }), field('签署日期', item.signDate),
|
||||||
|
field('存储年限', item.storageYears), field('存储项目', item.sampleTypeName, { wide: true }),
|
||||||
|
field('合同总额', money(item.contractAmount), { money: true }),
|
||||||
|
field('缴费方式', label(PAY_MODE_LABELS, item.payMode), { tag: true }),
|
||||||
|
field('优惠金额', money(item.discountAmount), { money: true }),
|
||||||
|
field('应收总额', money(item.receivableAmount), { money: true })
|
||||||
|
],
|
||||||
|
periodBlocks: periods.map(paymentPeriodBlock),
|
||||||
|
footerFields: [
|
||||||
|
field('累计实缴', money(item.paidAmount), { money: true }),
|
||||||
|
field('待缴金额', money(item.dueRemainAmount ?? item.remainAmount), { money: true }),
|
||||||
|
field('备注', item.remark, { wide: true })
|
||||||
|
],
|
||||||
|
actions: ['view-protocol', 'view-payment-detail']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove the old `Array.from({ length: 6 }, …)` + `periodFields` flatMap implementation entirely.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests and verify GREEN**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mobile-oa-h5 && node --test --test-name-pattern="payment card" src/market/list-presenter.test.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add mobile-oa-h5/src/market/list-presenter.js mobile-oa-h5/src/market/list-presenter.test.js
|
||||||
|
git commit -m "$(cat <<'EOF'
|
||||||
|
feat(mobile-oa-h5): present payment periods as collapsible periodBlocks
|
||||||
|
|
||||||
|
Stop flattening six installment field groups into list cards; emit periodBlocks and footerFields instead.
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: UI parity tests for period collapse (RED)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `mobile-oa-h5/src/market/mobile-parity-ui.test.js`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add parity test**
|
||||||
|
|
||||||
|
```js
|
||||||
|
test('payment list cards collapse period blocks by default and allow independent expand', () => {
|
||||||
|
const card = source('./components/MarketListCard.vue')
|
||||||
|
const presenter = source('./list-presenter.js')
|
||||||
|
const styles = source('../styles/market.css')
|
||||||
|
|
||||||
|
assert.match(presenter, /periodBlocks:\s*periods\.map\(paymentPeriodBlock\)/)
|
||||||
|
assert.match(presenter, /footerFields:/)
|
||||||
|
assert.match(card, /card\.periodBlocks/)
|
||||||
|
assert.match(card, /card\.footerFields/)
|
||||||
|
assert.match(card, /periodOpenKeys/)
|
||||||
|
assert.match(card, /aria-expanded/)
|
||||||
|
assert.match(card, /应缴费金额/)
|
||||||
|
assert.match(card, /已缴费金额/)
|
||||||
|
assert.match(styles, /\.market-list-card__periods\s*\{/)
|
||||||
|
assert.match(styles, /\.market-list-card__period-toggle\s*\{[\s\S]*?min-height:\s*44px;/)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run and verify RED**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mobile-oa-h5 && node --test --test-name-pattern="payment list cards collapse period" src/market/mobile-parity-ui.test.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL — template/CSS not yet present.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: MarketListCard — render periods + footerFields
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `mobile-oa-h5/src/market/components/MarketListCard.vue`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Insert period section and footer fields in template**
|
||||||
|
|
||||||
|
After the existing `card.fields` block and **before** the `sampleBlocks` section, insert:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<div v-if="card.periodBlocks?.length" class="market-list-card__periods">
|
||||||
|
<div
|
||||||
|
v-for="block in card.periodBlocks"
|
||||||
|
:key="block.key"
|
||||||
|
class="market-list-card__period"
|
||||||
|
:class="{ 'is-open': periodOpenKeys[block.key] }"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="market-list-card__period-toggle"
|
||||||
|
:aria-expanded="periodOpenKeys[block.key] ? 'true' : 'false'"
|
||||||
|
:aria-label="periodOpenKeys[block.key] ? `收起第${block.scheduleNo}期` : `展开第${block.scheduleNo}期`"
|
||||||
|
@click="togglePeriod(block.key)"
|
||||||
|
>
|
||||||
|
<span class="market-list-card__period-no">第{{ block.scheduleNo }}期</span>
|
||||||
|
<span class="market-list-card__period-col">
|
||||||
|
<span>应缴费金额</span>
|
||||||
|
<b class="is-money">{{ block.dueAmount }}</b>
|
||||||
|
</span>
|
||||||
|
<span class="market-list-card__period-col">
|
||||||
|
<span>已缴费金额</span>
|
||||||
|
<b class="is-money">{{ block.paidAmount }}</b>
|
||||||
|
</span>
|
||||||
|
<van-icon class="market-list-card__period-chevron" name="arrow-down" />
|
||||||
|
</button>
|
||||||
|
<div v-show="periodOpenKeys[block.key]" class="market-list-card__period-body">
|
||||||
|
<div class="market-list-card__fields market-list-card__fields--compact">
|
||||||
|
<div
|
||||||
|
v-for="field in block.fields"
|
||||||
|
:key="`${block.key}:${field.label}`"
|
||||||
|
class="market-list-card__field"
|
||||||
|
:class="{ 'is-wide': field.wide }"
|
||||||
|
>
|
||||||
|
<span>{{ field.label }}</span>
|
||||||
|
<b :class="{ 'is-money': field.money }">{{ field.value }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="card.footerFields?.length" class="market-list-card__fields">
|
||||||
|
<div
|
||||||
|
v-for="field in card.footerFields"
|
||||||
|
:key="`footer:${field.label}:${field.value}`"
|
||||||
|
class="market-list-card__field"
|
||||||
|
:class="{ 'is-wide': field.wide }"
|
||||||
|
>
|
||||||
|
<span>{{ field.label }}</span>
|
||||||
|
<van-tag v-if="field.tag" class="market-tag" :type="resolveStatusTone(field.value)" size="medium" round :title="field.value">{{ field.value }}</van-tag>
|
||||||
|
<b v-else :class="{ 'is-money': field.money }">{{ field.value }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add script state**
|
||||||
|
|
||||||
|
Alongside `samplesOpen`, add:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const periodOpenKeys = ref({})
|
||||||
|
|
||||||
|
function togglePeriod(key) {
|
||||||
|
periodOpenKeys.value = {
|
||||||
|
...periodOpenKeys.value,
|
||||||
|
[key]: !periodOpenKeys.value[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.item?.id || props.index,
|
||||||
|
() => {
|
||||||
|
samplesOpen.value = false
|
||||||
|
periodOpenKeys.value = {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Merge with the existing `samplesOpen` watch — keep a **single** watch that resets both `samplesOpen` and `periodOpenKeys`. Delete the old watch that only cleared `samplesOpen`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Do not run full GREEN yet** — CSS still missing; proceed to Task 5.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: CSS for period rows
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `mobile-oa-h5/src/styles/market.css`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add styles after `.market-list-card__fields--compact` block**
|
||||||
|
|
||||||
|
```css
|
||||||
|
.market-list-card__periods {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #eef4f1;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fafcfb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-list-card__period + .market-list-card__period {
|
||||||
|
border-top: 1px solid #eef4f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-list-card__period-toggle {
|
||||||
|
display: grid;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 44px;
|
||||||
|
grid-template-columns: 64px 1fr 1fr 20px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--market-text);
|
||||||
|
font: inherit;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-list-card__period-toggle:active {
|
||||||
|
background: #f0f7f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-list-card__period-no {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--market-primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-list-card__period-col {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-list-card__period-col > span {
|
||||||
|
color: var(--market-muted);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-list-card__period-col > b {
|
||||||
|
overflow: hidden;
|
||||||
|
color: #c98312;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-list-card__period-chevron {
|
||||||
|
color: var(--market-muted);
|
||||||
|
font-size: 14px;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-list-card__period.is-open .market-list-card__period-chevron {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-list-card__period-body {
|
||||||
|
padding: 0 12px 12px;
|
||||||
|
border-top: 1px dashed #eef4f1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Align with Demo; use existing token vars where already defined (`--market-text`, `--market-muted`, `--market-primary-dark`). Money color `#c98312` matches `.market-list-card__field b.is-money`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run parity + presenter tests GREEN**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mobile-oa-h5 && node --test --test-name-pattern="payment card|payment list cards collapse period" src/market/list-presenter.test.js src/market/mobile-parity-ui.test.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add mobile-oa-h5/src/market/components/MarketListCard.vue mobile-oa-h5/src/styles/market.css mobile-oa-h5/src/market/mobile-parity-ui.test.js
|
||||||
|
git commit -m "$(cat <<'EOF'
|
||||||
|
feat(mobile-oa-h5): collapse payment installment rows on list cards
|
||||||
|
|
||||||
|
Render periodBlocks as independent expandable summary rows with footer totals after the period section.
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: Full market suite + build verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Verify only
|
||||||
|
|
||||||
|
- [ ] **Step 1: Run all market tests**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mobile-oa-h5 && node --test src/market/*.test.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: zero failures.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Production build**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mobile-oa-h5 && pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: Vite exit 0.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Manual smoke (optional but recommended)**
|
||||||
|
|
||||||
|
Open市场专员 / 地区经理【缴费信息】列表:卡片默认矮;点第 N 期展开三明细;切换列表项后折叠状态重置;无数据期次不出现。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spec coverage checklist
|
||||||
|
|
||||||
|
| Spec requirement | Task |
|
||||||
|
|------------------|------|
|
||||||
|
| 折叠行:期数 / 应缴 / 已缴 | Task 4 |
|
||||||
|
| 展开:时间 + 欠缴 | Task 2 + 4 |
|
||||||
|
| 默认折叠、独立多开 | Task 4 (`periodOpenKeys`) |
|
||||||
|
| 不 pad 到 6、跳过空期 | Task 1–2 |
|
||||||
|
| `footerFields` 在期次后 | Task 2 + 4 |
|
||||||
|
| 仅 payment 列表 | Task 2(仅改 `paymentCard`) |
|
||||||
|
| 44px 触控 | Task 5 |
|
||||||
|
| 测试 | Task 1, 3, 6 |
|
||||||
Reference in new issue
Block a user