From 3b0268ab10721c417ac3556bff9be3b1d17fe2f7 Mon Sep 17 00:00:00 2001 From: xcbyy <2578898740@qq.com> Date: Tue, 13 Oct 2026 14:10:00 +0800 Subject: [PATCH] feat(bill): convert bill records modal into dedicated independent page --- pwuniapp/pages.json | 10 + pwuniapp/pages/user/bill-records.vue | 1299 +++++++++++++++++++++++++ pwuniapp/pages/user/index.vue | 29 +- server/src/db/repository.ts | 8 +- server/tests/content-security.spec.ts | 53 +- 5 files changed, 1369 insertions(+), 30 deletions(-) create mode 100644 pwuniapp/pages/user/bill-records.vue diff --git a/pwuniapp/pages.json b/pwuniapp/pages.json index 8630f65..82c0842 100644 --- a/pwuniapp/pages.json +++ b/pwuniapp/pages.json @@ -142,6 +142,16 @@ "backgroundColor": "#0A0F1D" } }, + { + "path": "pages/user/bill-records", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "账单明细", + "navigationBarBackgroundColor": "#0A0F1D", + "navigationBarTextStyle": "white", + "backgroundColor": "#0A0F1D" + } + }, { "path": "pages/user/settings", "style": { diff --git a/pwuniapp/pages/user/bill-records.vue b/pwuniapp/pages/user/bill-records.vue new file mode 100644 index 0000000..cddd760 --- /dev/null +++ b/pwuniapp/pages/user/bill-records.vue @@ -0,0 +1,1299 @@ + + + + + diff --git a/pwuniapp/pages/user/index.vue b/pwuniapp/pages/user/index.vue index 128d3f4..8ab6f8e 100644 --- a/pwuniapp/pages/user/index.vue +++ b/pwuniapp/pages/user/index.vue @@ -972,32 +972,11 @@ const ensureLoggedIn = () => { return true; }; -const viewWalletLog = async () => { +const viewWalletLog = () => { if (!ensureLoggedIn()) return; - try { - const list = await getUserFinanceRecords(user.value.id || 1001); - if (!list || list.length === 0) { - uni.showModal({ - title: '账单明细', - content: '暂无近期财务收支流水记录', - showCancel: false, - confirmText: '我知道了' - }); - return; - } - const lines = list.slice(0, 5).map(item => { - const sign = item.direction === 'IN' ? '+' : '-'; - return `${item.type}: ${sign}¥${Number(item.amount).toFixed(2)} (${item.counterparty || '系统'})`; - }); - uni.showModal({ - title: '最近收支明细', - content: lines.join('\r\n'), - showCancel: false, - confirmText: '确定' - }); - } catch (err) { - uni.showToast({ title: '账单获取异常', icon: 'none' }); - } + uni.navigateTo({ + url: '/pages/user/bill-records' + }); }; const handleRecharge = async () => { diff --git a/server/src/db/repository.ts b/server/src/db/repository.ts index c9ef239..ad75c5c 100644 --- a/server/src/db/repository.ts +++ b/server/src/db/repository.ts @@ -2183,12 +2183,12 @@ export class MysqlRepository { const [uRows]: any = await pool.query('SELECT nickname FROM users WHERE id = ?', [userId]); const nickname = uRows[0]?.nickname || ''; const [rows]: any = await pool.query(` - SELECT id, record_no as recordNo, type, amount, direction, counterparty, related_id as relatedId, created_at as createdAt + SELECT id, record_no as recordNo, user_id as userId, type, amount, direction, counterparty, related_id as relatedId, created_at as createdAt FROM finance_records - WHERE counterparty LIKE ? OR counterparty LIKE ? OR related_id = ? + WHERE user_id = ? OR counterparty LIKE ? OR counterparty LIKE ? OR related_id = ? ORDER BY created_at DESC - LIMIT 50 - `, [`%${nickname}%`, `%${userId}%`, String(userId)]); + LIMIT 100 + `, [userId, `%${nickname}%`, `%${userId}%`, String(userId)]); return rows; } diff --git a/server/tests/content-security.spec.ts b/server/tests/content-security.spec.ts index 2fcfed4..b9c87c9 100644 --- a/server/tests/content-security.spec.ts +++ b/server/tests/content-security.spec.ts @@ -130,7 +130,7 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安 const raw = fs.readFileSync(pagesJsonPath, 'utf-8'); const config = JSON.parse(raw); expect(Array.isArray(config.pages)).toBe(true); - expect(config.pages.length).toBe(20); + expect(config.pages.length).toBe(21); for (const pageItem of config.pages) { const vueFilePath = path.join(rootDir, 'pwuniapp', `${pageItem.path}.vue`); @@ -758,7 +758,58 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安 } }); }); + + describe('17. 独立专属收支账单明细页面验证 (pages/user/bill-records.vue)', () => { + it('pages.json 必须成功注册 pages/user/bill-records 且物理文件必须存在', () => { + const billPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'bill-records.vue'); + expect(fs.existsSync(billPath)).toBe(true); + const pagesJsonPath = path.join(rootDir, 'pwuniapp', 'pages.json'); + const pagesJson = fs.readFileSync(pagesJsonPath, 'utf-8'); + expect(pagesJson).toContain('pages/user/bill-records'); + expect(pagesJson).toContain('账单明细'); + }); + + it('个人中心页面 (pages/user/index.vue) 账单明细入口必须原生跳转至 /pages/user/bill-records 且杜绝弹窗', () => { + const userVuePath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue'); + const content = fs.readFileSync(userVuePath, 'utf-8'); + expect(content).toContain('/pages/user/bill-records'); + expect(content).not.toContain("title: '最近收支明细'"); + }); + + it('独立账单明细页面必须支持分类选项卡筛选、总览统计、一键复制流水号与双主题适配', () => { + const billPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'bill-records.vue'); + const content = fs.readFileSync(billPath, 'utf-8'); + expect(content).toContain('tabList'); + expect(content).toContain('filteredRecords'); + expect(content).toContain('getUserFinanceRecords'); + expect(content).toContain('handleCopyOrderNo'); + expect(content).toContain('theme-warm'); + expect(content).toContain('theme-cool'); + expect(content).toContain('userBalance'); + }); + + it('仓储层 getUserFinanceRecords 必须包含 user_id 字段选择与查询', () => { + const repoPath = path.join(rootDir, 'server', 'src', 'db', 'repository.ts'); + const content = fs.readFileSync(repoPath, 'utf-8'); + expect(content).toContain('user_id as userId'); + expect(content).toContain('user_id = ?'); + }); + + it('账单明细页面代码严禁包含任何系统 Emoji 与 showModal 4 字门禁违规', () => { + const billPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'bill-records.vue'); + const content = fs.readFileSync(billPath, 'utf-8'); + const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation}|[\u2600-\u27BF])/u; + expect(emojiRegex.test(content)).toBe(false); + + const modalMatches = content.matchAll(/(?:confirmText|cancelText):\s*['"]([^'"]+)['"]/g); + for (const match of modalMatches) { + const btnText = match[1]; + expect(btnText.length).toBeLessThanOrEqual(4); + } + }); + }); }); +