1. AGENTS.md 更新 - water-docs: 新增 specs/ 与 docs/design/ 生命周期规则章节 - water-backend: 更新协作引用(建设期/建成后、evidence 模块化) 2. specs/ 重复合并 - 006-reminder-event-design 合并入 003-rev006-reminder-event-design - 001-rev004-accounting 删除冗余 data-model.md + contracts/ - 002-rev005-invoice-flow 删除冗余 data-model.md + contracts/ 3. evidence 按模块归档 - 35 个 REV-004 文件归入 evidence/rev004-accounting/ - 7 个通用 bugfix 文件归入 evidence/bugfix/ 和 bugfix/frontend/ - 新建 rev005-invoice/、rev006-reminder/、rev007-statistics/ 目录 4. guides/ 清理 - 14 个 REV004_*.md 移入 evidence/rev004-accounting/ 5. 遗留文件处理 - docs/research/ 归档到 Archive/06_Migration_Plans/ - backend-check detached worktrees 清理 6. 交叉引用修复 - 006-reminder-event-design → 003-rev006-reminder-event-design - docs/guides/REV004_ → docs/evidence/rev004-accounting/REV004_ 7. DB 设计文档修正(01_Database_Design.md) - biz_invoice 明确为开票配置表,非发票记录表 - 新增 biz_invoice_record 为发票申请/结果主表 - 新增 biz_charge_invoice_rel 账单-发票关联说明 - REV-005 承接口径表名全部修正 8. 发票审计证据 - 新增 evidence/rev005-invoice/2026-06-16-invoice-document-audit.md
627 lines
18 KiB
Markdown
627 lines
18 KiB
Markdown
# 催缴登记 (Arrearage Reminder) 前端对接 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:** 将两个前端页面(欠费催缴池 + 催缴记录)从 mock 数据切换到真实后端 API,包括 API 层补齐、催缴表单对接、汇总统计、导出功能。
|
||
|
||
**Architecture:** 后端 9 个接口均已就绪 (`POST /create`, `GET /page`, `GET /pending-page`, `GET /get`, `GET /detail-list`, `POST /batch-create`, `GET /pending-summary`, `GET /pending-export`, `GET /pending-detail-export`)。前端按 "API 层 → 催缴表单 → 欠费池页面 → 催缴记录页面" 的顺序逐层对接,每层完成后可独立验证。
|
||
|
||
**Tech Stack:** Vue 3, TypeScript, Element Plus, Axios (`@/config/axios`), pnpm
|
||
|
||
---
|
||
|
||
## File Structure
|
||
|
||
### API layer
|
||
- Modify: `../water-frontend/src/api/collectionManage/arrears/index.ts`
|
||
- 新增所有请求/响应 VO 类型 + 7 个缺失的 API 方法
|
||
|
||
### Components
|
||
- Modify: `../water-frontend/src/views/collectionManage/arrears/components/RemindForm.vue`
|
||
- 对接 `batchCreate` API,接收父组件传入的选中行数据
|
||
|
||
### Views
|
||
- Modify: `../water-frontend/src/views/collectionManage/arrears/index.vue`
|
||
- 汇总统计条对接 `getPendingSummary`、导出对接 `getPendingExportList`、催缴按钮传递选中数据给 RemindForm
|
||
- Modify: `../water-frontend/src/views/collectionManage/collectionRecord/index.vue`
|
||
- 列表对接 `getPage`、详情展开对接 `getDetailList`、导出对接后端(如有对应接口)
|
||
- 查询表单字段对齐 `ArrearageReminderPageReqVO`
|
||
|
||
---
|
||
|
||
### Task 1: 补齐 API 层 — 类型定义与全部接口方法
|
||
|
||
**Files:**
|
||
- Modify: `../water-frontend/src/api/collectionManage/arrears/index.ts`
|
||
|
||
- [ ] **Step 1: 添加所有 VO 类型定义和 API 方法**
|
||
|
||
```typescript
|
||
import request from '@/config/axios'
|
||
import download from '@/utils/download'
|
||
|
||
// ========== 请求 VO ==========
|
||
|
||
export interface ArrearagePendingPageReqVO {
|
||
pageNo: number
|
||
pageSize: number
|
||
deptId?: number
|
||
code?: string
|
||
name?: string
|
||
address?: string
|
||
mobile?: string
|
||
custStates?: number[]
|
||
custType?: number
|
||
billMonthStart?: number
|
||
billMonthEnd?: number
|
||
meterReaderId?: number
|
||
bookCode?: string
|
||
arrearsCountMin?: number
|
||
arrearsCountMax?: number
|
||
arrearsAmountMin?: number
|
||
arrearsAmountMax?: number
|
||
remindFilter?: string
|
||
}
|
||
|
||
export interface ArrearageReminderPageReqVO {
|
||
pageNo: number
|
||
pageSize: number
|
||
custId?: number
|
||
reminderUser?: string
|
||
reminderType?: number
|
||
reminderReason?: number
|
||
reminderResult?: number
|
||
}
|
||
|
||
export interface ArrearageReminderCreateReqVO {
|
||
custId: number
|
||
chargeIds: number[]
|
||
reminderType: number
|
||
reminderReason: number
|
||
reminderResult: number
|
||
reminderUser: string
|
||
reminderTemplate?: number
|
||
remark?: string
|
||
}
|
||
|
||
export interface ArrearageReminderBatchCreateReqVO {
|
||
customers: {
|
||
custId: number
|
||
chargeIds: number[]
|
||
}[]
|
||
reminderType: number
|
||
reminderReason: number
|
||
reminderResult: number
|
||
reminderUser: string
|
||
reminderTemplate?: number
|
||
remark?: string
|
||
}
|
||
|
||
// ========== 响应 VO ==========
|
||
|
||
export interface ArrearagePendingPageRespVO {
|
||
custId: number
|
||
custCode: string
|
||
custName: string
|
||
custAddress: string
|
||
meterCaliber: string
|
||
waterNature: string
|
||
arrearsCount: number
|
||
waterVolume: number
|
||
totalAmount: number
|
||
penaltyAmount: number
|
||
receivableAmount: number
|
||
billAmount: number
|
||
accountMonthRange: string
|
||
custStatus: string
|
||
prestoreAmount: number
|
||
mobile: string
|
||
isRemindedThisMonth: boolean
|
||
agreementNo: string
|
||
contractNo: string
|
||
details: ArrearagePendingChargeDetailRespVO[]
|
||
}
|
||
|
||
export interface ArrearagePendingChargeDetailRespVO {
|
||
chargeId: number
|
||
billMonth: string
|
||
lastReading: number
|
||
currentReading: number
|
||
waterVolume: number
|
||
billAmount: number
|
||
penaltyAmount: number
|
||
readingDate: string
|
||
}
|
||
|
||
export interface ArrearagePendingSummaryRespVO {
|
||
custCount: number
|
||
arrearsCount: number
|
||
waterVolume: number
|
||
billAmount: number
|
||
penaltyAmount: number
|
||
totalAmount: number
|
||
receivableAmount: number
|
||
}
|
||
|
||
export interface ArrearageReminderPageRespVO {
|
||
id: number
|
||
custId: number
|
||
custCode: string
|
||
custName: string
|
||
reminderType: number
|
||
reminderReason: number
|
||
reminderUser: string
|
||
remark: string
|
||
reminderTemplate: number
|
||
completeTime: string
|
||
pushState: number
|
||
pushResults: string
|
||
reminderResult: number
|
||
batchStamp: string
|
||
totalBillWater: number
|
||
totalExtendedAmount: number
|
||
totalLateFee: number
|
||
deposit: number
|
||
mobile: string
|
||
createTime: string
|
||
updateTime: string
|
||
}
|
||
|
||
export interface ArrearageReminderRespVO extends ArrearageReminderPageRespVO {}
|
||
|
||
export interface ArrearageReminderDetailRespVO {
|
||
id: number
|
||
arrearageReminderId: number
|
||
chargeId: number
|
||
lateFee: number
|
||
createTime: string
|
||
updateTime: string
|
||
}
|
||
|
||
export interface ArrearageReminderCreateRespVO {
|
||
id: number
|
||
detailCount: number
|
||
}
|
||
|
||
export interface ArrearageReminderBatchCreateRespVO {
|
||
successCount: number
|
||
failCount: number
|
||
successItems: { custId: number; reminderId: number; detailCount: number }[]
|
||
failItems: { custId: number; reason: string }[]
|
||
}
|
||
|
||
// ========== API 方法 ==========
|
||
|
||
export const ArrearsApi = {
|
||
// 待催欠费池
|
||
getPendingPage: async (params: ArrearagePendingPageReqVO) => {
|
||
return await request.get<{ list: ArrearagePendingPageRespVO[]; total: number }>({
|
||
url: '/business/arrearage-reminder/pending-page',
|
||
params
|
||
})
|
||
},
|
||
|
||
getPendingSummary: async (params: ArrearagePendingPageReqVO) => {
|
||
return await request.get<ArrearagePendingSummaryRespVO>({
|
||
url: '/business/arrearage-reminder/pending-summary',
|
||
params
|
||
})
|
||
},
|
||
|
||
// 催缴登记 CRUD
|
||
create: async (data: ArrearageReminderCreateReqVO) => {
|
||
return await request.post<ArrearageReminderCreateRespVO>({
|
||
url: '/business/arrearage-reminder/create',
|
||
data
|
||
})
|
||
},
|
||
|
||
batchCreate: async (data: ArrearageReminderBatchCreateReqVO) => {
|
||
return await request.post<ArrearageReminderBatchCreateRespVO>({
|
||
url: '/business/arrearage-reminder/batch-create',
|
||
data
|
||
})
|
||
},
|
||
|
||
getPage: async (params: ArrearageReminderPageReqVO) => {
|
||
return await request.get<{ list: ArrearageReminderPageRespVO[]; total: number }>({
|
||
url: '/business/arrearage-reminder/page',
|
||
params
|
||
})
|
||
},
|
||
|
||
get: async (id: number) => {
|
||
return await request.get<ArrearageReminderRespVO>({
|
||
url: '/business/arrearage-reminder/get',
|
||
params: { id }
|
||
})
|
||
},
|
||
|
||
getDetailList: async (reminderId: number) => {
|
||
return await request.get<ArrearageReminderDetailRespVO[]>({
|
||
url: '/business/arrearage-reminder/detail-list',
|
||
params: { reminderId }
|
||
})
|
||
},
|
||
|
||
// 导出
|
||
exportPending: async (params: ArrearagePendingPageReqVO) => {
|
||
const res = await request.download({
|
||
url: '/business/arrearage-reminder/pending-export',
|
||
params
|
||
})
|
||
download.response(res, '待催欠费客户.xlsx')
|
||
},
|
||
|
||
exportPendingDetail: async (params: ArrearagePendingPageReqVO) => {
|
||
const res = await request.download({
|
||
url: '/business/arrearage-reminder/pending-detail-export',
|
||
params
|
||
})
|
||
download.response(res, '待催欠费明细.xlsx')
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 验证 API 层编译**
|
||
|
||
Run: `cd ../water-frontend && npx vue-tsc --noEmit src/api/collectionManage/arrears/index.ts 2>&1 | head -20`
|
||
Expected: No type errors
|
||
|
||
---
|
||
|
||
### Task 2: RemindForm 对接 batchCreate API
|
||
|
||
**Files:**
|
||
- Modify: `../water-frontend/src/views/collectionManage/arrears/components/RemindForm.vue`
|
||
|
||
- [ ] **Step 1: 重写 RemindForm 脚本,接收选中行 props 并调 batchCreate**
|
||
|
||
将 `<script setup lang="ts">` 段替换为:
|
||
|
||
```typescript
|
||
import { ArrearsApi } from '@/api/collectionManage/arrears'
|
||
|
||
const message = useMessage()
|
||
const dialogVisible = ref(false)
|
||
const formRef = ref()
|
||
|
||
const props = defineProps<{
|
||
selectedRows: any[]
|
||
}>()
|
||
|
||
// reminderType 映射: '催缴单'->1, '短信'->2, '电话'->3, '微信'->4, '其它'->5, '热线'->6
|
||
const methodMap: Record<string, number> = {
|
||
'催缴单': 1, '短信': 2, '电话': 3, '微信': 4, '其它': 5, '热线': 6
|
||
}
|
||
|
||
const formData = ref({
|
||
method: '催缴单',
|
||
operator: '',
|
||
reason: '',
|
||
remark: ''
|
||
})
|
||
|
||
const rules = {
|
||
method: [{ required: true, message: '请选择催缴方式', trigger: 'change' }],
|
||
operator: [{ required: true, message: '请选择催缴人员', trigger: 'change' }],
|
||
reason: [{ required: true, message: '请选择催缴原因', trigger: 'change' }]
|
||
}
|
||
|
||
const open = () => {
|
||
dialogVisible.value = true
|
||
formData.value = {
|
||
method: '催缴单',
|
||
operator: '',
|
||
reason: '',
|
||
remark: ''
|
||
}
|
||
}
|
||
|
||
const submitForm = async () => {
|
||
if (!formRef.value) return
|
||
await formRef.value.validate(async (valid: boolean) => {
|
||
if (!valid) return
|
||
try {
|
||
const customers = props.selectedRows.map((row: any) => ({
|
||
custId: row.custId,
|
||
chargeIds: row.details?.map((d: any) => d.chargeId) || []
|
||
}))
|
||
const res = await ArrearsApi.batchCreate({
|
||
customers,
|
||
reminderType: methodMap[formData.value.method] || 1,
|
||
reminderReason: Number(formData.value.reason),
|
||
reminderResult: 0,
|
||
reminderUser: formData.value.operator,
|
||
remark: formData.value.remark
|
||
})
|
||
if (res.failCount > 0) {
|
||
const failMsgs = res.failItems.map((f: any) => `客户${f.custId}: ${f.reason}`).join('; ')
|
||
message.warning(`成功 ${res.successCount} 条,失败 ${res.failCount} 条。${failMsgs}`)
|
||
} else {
|
||
message.success(`成功创建 ${res.successCount} 条催缴记录`)
|
||
}
|
||
dialogVisible.value = false
|
||
emit('success')
|
||
} catch (e: any) {
|
||
message.error(e?.message || '催缴失败')
|
||
}
|
||
})
|
||
}
|
||
|
||
const emit = defineEmits(['success'])
|
||
defineExpose({ open })
|
||
```
|
||
|
||
- [ ] **Step 2: 修改父组件传参 — 更新 arrears/index.vue 中 RemindForm 的使用**
|
||
|
||
在 `../water-frontend/src/views/collectionManage/arrears/index.vue` 底部模板中:
|
||
|
||
```
|
||
<!-- 修改前 -->
|
||
<RemindForm ref="remindFormRef" @success="handleSuccess" />
|
||
|
||
<!-- 修改后 -->
|
||
<RemindForm ref="remindFormRef" :selected-rows="selectedRows" @success="handleSuccess" />
|
||
```
|
||
|
||
- [ ] **Step 3: 验证编译**
|
||
|
||
Run: `cd ../water-frontend && npx vue-tsc --noEmit 2>&1 | tail -20`
|
||
Expected: No type errors (may have pre-existing errors in other files, check only arrearage-related)
|
||
|
||
---
|
||
|
||
### Task 3: 欠费催缴池页面 — 汇总统计 + 导出 + 全选催缴
|
||
|
||
**Files:**
|
||
- Modify: `../water-frontend/src/views/collectionManage/arrears/index.vue`
|
||
|
||
- [ ] **Step 1: 添加汇总统计数据获取**
|
||
|
||
在 `getList` 调用后追加 summary 请求。找到 `script setup` 中的 `getList` 函数(约 line 629),在其后添加:
|
||
|
||
```typescript
|
||
const summary = ref<{
|
||
custCount: number
|
||
arrearsCount: number
|
||
waterVolume: number
|
||
billAmount: number
|
||
penaltyAmount: number
|
||
totalAmount: number
|
||
receivableAmount: number
|
||
}>({
|
||
custCount: 0, arrearsCount: 0, waterVolume: 0,
|
||
billAmount: 0, penaltyAmount: 0, totalAmount: 0, receivableAmount: 0
|
||
})
|
||
|
||
const fetchSummary = async () => {
|
||
try {
|
||
summary.value = await ArrearsApi.getPendingSummary(buildPageParams())
|
||
} catch {
|
||
// summary stays at zero
|
||
}
|
||
}
|
||
```
|
||
|
||
修改 `handleQuery` 和 `onMounted` 中的 `getList()` 调用为并行:
|
||
|
||
```typescript
|
||
const handleQuery = () => {
|
||
queryParams.pageNo = 1
|
||
Promise.all([getList(), fetchSummary()])
|
||
}
|
||
```
|
||
|
||
并在 `onMounted` 中:
|
||
|
||
```typescript
|
||
onMounted(async () => {
|
||
await Promise.resolve(getSiteTree())
|
||
await Promise.all([getList(), fetchSummary()])
|
||
})
|
||
```
|
||
|
||
- [ ] **Step 2: 替换模板中的硬编码统计值为真实数据**
|
||
|
||
找到 `<el-descriptions>` 区域(约 line 211-226),替换其中各 `el-descriptions-item` 的值为 `summary` 响应式数据:
|
||
|
||
```html
|
||
<el-descriptions :column="5" class="mt-15px mb-8px" border label-width="120">
|
||
<el-descriptions-item label="客户数">
|
||
<span class="text-orange-500 font-bold">{{ summary.custCount }}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="欠费笔数">
|
||
<span class="text-orange-500 font-bold">{{ summary.arrearsCount }}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="用水量">
|
||
<span class="text-orange-500 font-bold">{{ summary.waterVolume }}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="合计金额">
|
||
<span class="text-orange-500 font-bold">¥{{ summary.totalAmount }}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="账单金额">
|
||
<span class="text-orange-500 font-bold">¥{{ summary.billAmount }}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="违约金">
|
||
<span class="text-orange-500 font-bold">¥{{ summary.penaltyAmount }}</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="应缴金额">
|
||
<span class="text-orange-500 font-bold">¥{{ summary.receivableAmount }}</span>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
```
|
||
|
||
- [ ] **Step 3: 导出对接真实 API**
|
||
|
||
找到 `handleExport` 函数(约 line 663),替换为:
|
||
|
||
```typescript
|
||
const handleExport = async () => {
|
||
try {
|
||
message.loading('正在导出...')
|
||
await ArrearsApi.exportPending(buildPageParams())
|
||
message.success('导出成功')
|
||
} catch {
|
||
message.error('导出失败')
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: 全部催缴传参**
|
||
|
||
找到 `handleRemindAll`(约 line 657),当前直接 `remindFormRef.value.open()`,需要传入当前列表中所有行的数据。修改为:
|
||
|
||
```typescript
|
||
const handleRemindAll = () => {
|
||
selectedRows.value = list.value
|
||
remindFormRef.value.open()
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 5: 从 API 导入汇总相关方法**
|
||
|
||
在 `arrears/index.vue` 的 import 中,`ArrearsApi` 的导入行保持不变(当前已导入),新增 `summary` ref 定义的位置放到 `selectedRows` 附近。
|
||
|
||
- [ ] **Step 6: 验证编译**
|
||
|
||
Run: `cd ../water-frontend && npx vue-tsc --noEmit 2>&1 | tail -20`
|
||
Expected: No new type errors from arrears files
|
||
|
||
---
|
||
|
||
### Task 4: 催缴记录页面 — 列表与详情对接
|
||
|
||
**Files:**
|
||
- Modify: `../water-frontend/src/views/collectionManage/collectionRecord/index.vue`
|
||
|
||
- [ ] **Step 1: 添加 API 导入**
|
||
|
||
在 script setup 顶部 import 中加入:
|
||
|
||
```typescript
|
||
import { ArrearsApi } from '@/api/collectionManage/arrears'
|
||
```
|
||
|
||
- [ ] **Step 2: 添加详情数据 ref 和获取方法**
|
||
|
||
在 `list` ref 附近(约 line 294)追加:
|
||
|
||
```typescript
|
||
const detailMap = ref<Record<number, any[]>>({})
|
||
|
||
const fetchDetailList = async (reminderId: number) => {
|
||
if (detailMap.value[reminderId]) return
|
||
try {
|
||
const details = await ArrearsApi.getDetailList(reminderId)
|
||
detailMap.value[reminderId] = details
|
||
} catch {
|
||
detailMap.value[reminderId] = []
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: 替换 getList 为真实 API 调用**
|
||
|
||
找到现有的 getList 函数(当前为 mock 数据),替换为:
|
||
|
||
```typescript
|
||
const getList = async () => {
|
||
loading.value = true
|
||
try {
|
||
const data = await ArrearsApi.getPage({
|
||
pageNo: queryParams.pageNo,
|
||
pageSize: queryParams.pageSize,
|
||
custId: undefined, // 后续可扩展
|
||
reminderUser: queryParams.remindUser || undefined,
|
||
reminderType: undefined,
|
||
reminderReason: queryParams.remindReason ? Number(queryParams.remindReason) : undefined,
|
||
reminderResult: queryParams.remindResult ? Number(queryParams.remindResult) : undefined
|
||
})
|
||
list.value = (data?.list || []).map((item: any) => ({
|
||
...item,
|
||
id: item.id,
|
||
deptName: item.deptName || '-',
|
||
custCode: item.custCode,
|
||
custName: item.custName,
|
||
custAddress: item.custAddress || '-',
|
||
remindUser: item.reminderUser,
|
||
remindMethod: item.reminderType,
|
||
remindReason: item.reminderReason,
|
||
mobile: item.mobile,
|
||
arrearsCount: 0, // reminder records don't carry count — fetched via details
|
||
arrearsWaterVolume: item.totalBillWater,
|
||
arrearsAmount: item.totalExtendedAmount,
|
||
penaltyAmount: item.totalLateFee,
|
||
prestoreBalance: item.deposit,
|
||
completeDate: item.completeTime,
|
||
registerDate: item.createTime,
|
||
remark: item.remark,
|
||
expanded: false,
|
||
pushStatus: item.pushState,
|
||
details: []
|
||
}))
|
||
total.value = data?.total || 0
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: 展开行对接详情**
|
||
|
||
找到模板中的 expand slot(当前显示 mock 数据 `detailsMock1`),替换为绑定 `detailMap`:
|
||
|
||
```html
|
||
<template #expand="scope">
|
||
<div class="p-20px bg-gray-50">
|
||
<el-table
|
||
:data="detailMap[scope.row.id] || []"
|
||
border
|
||
style="width: 100%"
|
||
v-loading="!detailMap[scope.row.id]"
|
||
>
|
||
<el-table-column prop="chargeId" label="账单ID" width="120" />
|
||
<el-table-column prop="lateFee" label="违约金" width="120" />
|
||
<el-table-column prop="createTime" label="创建时间" width="180" />
|
||
</el-table>
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
- [ ] **Step 5: 展开事件触发详情加载**
|
||
|
||
在 `handleExpandChange` 中调用 `fetchDetailList`:
|
||
|
||
```typescript
|
||
const handleExpandChange = (row: any, expandedRows: any[]) => {
|
||
row.expanded = expandedRows.includes(row)
|
||
if (row.expanded) {
|
||
fetchDetailList(row.id)
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 6: 删除 mock 数据**
|
||
|
||
删除文件中的 `detailsMock1` 等 mock 数据声明。
|
||
|
||
- [ ] **Step 7: 验证编译**
|
||
|
||
Run: `cd ../water-frontend && npx vue-tsc --noEmit 2>&1 | tail -20`
|
||
Expected: No new type errors
|
||
|
||
---
|
||
|
||
## Verification Checklist
|
||
|
||
完成所有 4 个 Task 后,执行端到端 smoke:
|
||
|
||
- [ ] `npx vue-tsc --noEmit` 编译通过
|
||
- [ ] 欠费催缴池页面:汇总统计数字不再是硬编码
|
||
- [ ] 欠费催缴池页面:点击"导出"触发 `GET /pending-export` 下载 xlsx
|
||
- [ ] 欠费催缴池页面:勾选客户 → 点击"选中催缴" → 弹窗填写 → 提交 → 调 `POST /batch-create`
|
||
- [ ] 欠费催缴池页面:点击"全部催缴"将当前列表全部行传入 RemindForm
|
||
- [ ] 催缴记录页面:列表从 `GET /page` 加载,查询条件生效
|
||
- [ ] 催缴记录页面:展开行从 `GET /detail-list` 加载账单明细
|