feat(miniapp): migrate C-group pages to TypeScript, update contracts
This commit is contained in:
+3
-3
@@ -14,15 +14,15 @@
|
|||||||
| Phase 04 Profile、Preference 与本地资格 | 已完成 | `Phase-04-V11-Recovery-Completion-Report-2026-09-12.md`;后端专项 3/3、小程序契约通过 |
|
| Phase 04 Profile、Preference 与本地资格 | 已完成 | `Phase-04-V11-Recovery-Completion-Report-2026-09-12.md`;后端专项 3/3、小程序契约通过 |
|
||||||
| Phase 05 统一审核扩展 | 已完成 | `Phase-05-V11-Recovery-Completion-Report-2026-09-12.md`;后端专项 4/4、Admin 构建、Docker/Flyway 通过 |
|
| Phase 05 统一审核扩展 | 已完成 | `Phase-05-V11-Recovery-Completion-Report-2026-09-12.md`;后端专项 4/4、Admin 构建、Docker/Flyway 通过 |
|
||||||
| Phase 06 Match Eligibility 增量 | 已完成 | `Phase-06-V11-Recovery-Completion-Report-2026-09-12.md`;推荐专项 21/21、契约与 Docker 验证通过 |
|
| Phase 06 Match Eligibility 增量 | 已完成 | `Phase-06-V11-Recovery-Completion-Report-2026-09-12.md`;推荐专项 21/21、契约与 Docker 验证通过 |
|
||||||
| Phase 07 V1.1 首页业务与找对象 | **业务+UI 部分完成** | 后端 HomeApplicationService 已增强为真实聚合(实名查询、Capability 真实判定、Content/ServiceLocation 接入);管理端壳层+工作台通过参考图 QA;小程序 A/B 组 8 页已提交;C 组 3 页脚手架→真实 API(仍为 .js,需迁 TS);见提交 `1aea6f7`~`63cbbff` |
|
| Phase 07 V1.1 首页业务与找对象 | **业务+UI 部分完成**(原视觉已被否决,参考图驱动重设计中) | 后端 HomeApplicationService 已增强为真实聚合;管理端壳层+工作台通过参考图 QA;小程序 11 页全部 TypeScript 化 |
|
||||||
| Flyway 迁移 | V001~V035 | V028~V035(服务站 operator scope、权限补齐、软删、审计/消息/敏感字段对齐)已在工作区;此前 STATUS 只记录到 V027 为陈旧口径 |
|
| Flyway 迁移 | V001~V035 | V028~V035(服务站 operator scope、权限补齐、软删、审计/消息/敏感字段对齐)已在工作区;此前 STATUS 只记录到 V027 为陈旧口径 |
|
||||||
| Backend 全量回归 | 未全绿 | 277 tests:4 failures、8 errors、3 skipped;均登记为既有跨阶段测试债务;本地无 JDK/Maven 无法复验 |
|
| Backend 全量回归 | 未全绿 | 277 tests:4 failures、8 errors、3 skipped;均登记为既有跨阶段测试债务;本地无 JDK/Maven 无法复验 |
|
||||||
| Admin Web | **已验证** | 2026-09-14 阶段 0 门禁:`vue-tsc --noEmit` ✅ PASS、`vite build` ✅ PASS(退出码 0,仅大 chunk 警告);19 个业务 Vue 全部存在;壳层+工作台通过参考图 QA |
|
| Admin Web | **已验证** | 2026-09-14 阶段 0 门禁:`vue-tsc --noEmit` ✅ PASS、`vite build` ✅ PASS(退出码 0,仅大 chunk 警告);19 个业务 Vue 全部存在;壳层+工作台通过参考图 QA |
|
||||||
| Miniapp | **已验证** | 2026-09-14 阶段 0 门禁:`tsc --noEmit` ✅ PASS;小程序 11 个核心页代码存在(C 组 content/anti-fraud/service-station 仍为 `.js`,需阶段 1 迁 TS) |
|
| Miniapp | **已验证** | 2026-09-14 阶段 0+1 门禁:`tsc --noEmit` ✅ PASS;11 个核心页全部 TypeScript 唯一源(C 组 content/anti-fraud/service-station 已从 `.js` 迁 `.ts`) |
|
||||||
| UI 契约测试 | **13/13 通过** | 2026-09-14 阶段 0 运行:admin-reference、admin-dashboard-data、miniapp-reference、phase03/04/06/07、ui-document-baseline 等全部退出码 0;输出在 `docs/gate-output/` |
|
| UI 契约测试 | **13/13 通过** | 2026-09-14 阶段 0 运行:admin-reference、admin-dashboard-data、miniapp-reference、phase03/04/06/07、ui-document-baseline 等全部退出码 0;输出在 `docs/gate-output/` |
|
||||||
| Git 工作区 | **已清零** | 2026-09-14 阶段 0 提交 6 个 commit(`0518cda`~`fd5f4d6`);`git status --porcelain` 输出 0 行 |
|
| Git 工作区 | **已清零** | 2026-09-14 阶段 0 提交 6 个 commit(`0518cda`~`fd5f4d6`);`git status --porcelain` 输出 0 行 |
|
||||||
| Phase 08 及以后 | 已暂停 | UI 重设计未完成终验,Phase 08~26 不提前恢复 |
|
| Phase 08 及以后 | 已暂停 | UI 重设计未完成终验,Phase 08~26 不提前恢复 |
|
||||||
| UAT / RC / Production | NO-GO | 后端测试债务未收口、CapabilityService 未独立、小程序 C 组未迁 TS、双端未终验 |
|
| UAT / RC / Production | NO-GO | 后端测试债务未收口、CapabilityService 未独立、双端未终验 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
interface RiskItem {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AntiFraudPageData {
|
||||||
|
risks: RiskItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
risks: [
|
||||||
|
{ title: '投资博彩诱导', description: '刚认识便引导投资、博彩或下载陌生应用' },
|
||||||
|
{ title: '借款资金周转', description: '以生病、工程或临时周转为由反复借款' },
|
||||||
|
{ title: '索取隐私资料', description: '催促脱离平台并索要验证码、银行卡或证件信息' },
|
||||||
|
{ title: '冒充平台人员', description: '以认证、保证金或解冻账户为由要求转账' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
report() {
|
||||||
|
wx.navigateTo({ url: '/pages/report/report?type=fraud' })
|
||||||
|
},
|
||||||
|
|
||||||
|
requestHelp() {
|
||||||
|
wx.showModal({
|
||||||
|
title: '安全求助',
|
||||||
|
content: '如有财产损失或人身危险,请立即拨打 110;非紧急问题可前往平台帮助中心。',
|
||||||
|
confirmText: '拨打 110',
|
||||||
|
cancelText: '帮助中心',
|
||||||
|
success(result: WechatMiniprogram.ShowModalSuccessCallbackResult) {
|
||||||
|
if (result.confirm) wx.makePhoneCall({ phoneNumber: '110' })
|
||||||
|
else if (result.cancel) wx.navigateTo({ url: '/pages/help-center/help-center' })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import api from '../../utils/api'
|
||||||
|
|
||||||
|
interface ContentCategory {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContentArticle {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
summary?: string
|
||||||
|
categoryId?: string
|
||||||
|
categoryName?: string
|
||||||
|
coverFileId?: string
|
||||||
|
viewCount?: number
|
||||||
|
publishAt?: string
|
||||||
|
createdAt?: string
|
||||||
|
publishAtText?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContentPageData {
|
||||||
|
categories: ContentCategory[]
|
||||||
|
activeCategoryId: string
|
||||||
|
articles: ContentArticle[]
|
||||||
|
loading: boolean
|
||||||
|
loadError: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (value: string | undefined): string => {
|
||||||
|
if (!value) return ''
|
||||||
|
const date = new Date(value)
|
||||||
|
if (Number.isNaN(date.getTime())) return ''
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
return `${date.getFullYear()}-${month}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
categories: [{ id: '', name: '推荐' }],
|
||||||
|
activeCategoryId: '',
|
||||||
|
articles: [],
|
||||||
|
loading: true,
|
||||||
|
loadError: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
this.loadPage()
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadPage() {
|
||||||
|
this.setData({ loading: true, loadError: false })
|
||||||
|
try {
|
||||||
|
const [categories, articles] = await Promise.all([
|
||||||
|
api.get<ContentCategory[]>('/content/categories'),
|
||||||
|
api.get<ContentArticle[]>('/content/articles'),
|
||||||
|
])
|
||||||
|
this.setData({
|
||||||
|
categories: [{ id: '', name: '推荐' }, ...(categories || [])],
|
||||||
|
articles: this.normalizeArticles(articles || [], categories || []),
|
||||||
|
loading: false,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载婚恋课堂失败', error)
|
||||||
|
this.setData({ loading: false, loadError: true })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
normalizeArticles(articles: ContentArticle[], categories: ContentCategory[]): ContentArticle[] {
|
||||||
|
const categoryNames = new Map<string, string>()
|
||||||
|
categories.forEach((item) => categoryNames.set(item.id, item.name))
|
||||||
|
return articles.map((item) => ({
|
||||||
|
...item,
|
||||||
|
categoryName: categoryNames.get(item.categoryId || '') || '婚恋课堂',
|
||||||
|
publishAtText: formatDate(item.publishAt || item.createdAt),
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
|
async select(e: WechatMiniprogram.TouchEvent) {
|
||||||
|
const categoryId = (e.currentTarget.dataset.id as string) || ''
|
||||||
|
if (categoryId === this.data.activeCategoryId) return
|
||||||
|
this.setData({ activeCategoryId: categoryId, loading: true, loadError: false })
|
||||||
|
try {
|
||||||
|
const articles = await api.get<ContentArticle[]>('/content/articles', categoryId ? { categoryId } : undefined)
|
||||||
|
this.setData({
|
||||||
|
articles: this.normalizeArticles(articles || [], this.data.categories),
|
||||||
|
loading: false,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载课堂分类失败', error)
|
||||||
|
this.setData({ loading: false, loadError: true })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
open(e: WechatMiniprogram.TouchEvent) {
|
||||||
|
const id = e.currentTarget.dataset.id as string
|
||||||
|
if (id) wx.navigateTo({ url: `/pages/content-detail/content-detail?id=${id}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
onRetry() {
|
||||||
|
this.loadPage()
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import api from '../../utils/api'
|
||||||
|
|
||||||
|
interface ServiceLocation {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
shortName?: string
|
||||||
|
address?: string
|
||||||
|
contactPhone?: string
|
||||||
|
contactPhoneDisplay?: string
|
||||||
|
openingHoursText?: string
|
||||||
|
services?: string[]
|
||||||
|
latitude?: number | string
|
||||||
|
longitude?: number | string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ServiceStationPageData {
|
||||||
|
serviceLocation: ServiceLocation | null
|
||||||
|
loading: boolean
|
||||||
|
loadError: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
serviceLocation: null,
|
||||||
|
loading: true,
|
||||||
|
loadError: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
this.loadStation()
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadStation() {
|
||||||
|
this.setData({ loading: true, loadError: false })
|
||||||
|
try {
|
||||||
|
const home = await api.get<{ serviceLocation?: ServiceLocation }>('/home')
|
||||||
|
this.setData({ serviceLocation: home.serviceLocation || null, loading: false })
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载服务站失败', error)
|
||||||
|
this.setData({ loading: false, loadError: true })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
call() {
|
||||||
|
const station = this.data.serviceLocation
|
||||||
|
if (!station) return
|
||||||
|
const phoneNumber = station.contactPhone || station.contactPhoneDisplay || ''
|
||||||
|
const digitCount = phoneNumber.replace(/\D/g, '').length
|
||||||
|
if (!phoneNumber || phoneNumber.includes('*') || digitCount < 5) {
|
||||||
|
wx.showToast({ title: '联系电话请以预约确认为准', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wx.makePhoneCall({ phoneNumber })
|
||||||
|
},
|
||||||
|
|
||||||
|
openMap() {
|
||||||
|
const station = this.data.serviceLocation
|
||||||
|
if (!station) return
|
||||||
|
const latitude = Number(station.latitude)
|
||||||
|
const longitude = Number(station.longitude)
|
||||||
|
if (Number.isFinite(latitude) && Number.isFinite(longitude) && latitude && longitude) {
|
||||||
|
wx.openLocation({
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
name: station.name,
|
||||||
|
address: station.address || '',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (station.address) {
|
||||||
|
wx.setClipboardData({ data: station.address })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wx.showToast({ title: '暂未提供地图位置', icon: 'none' })
|
||||||
|
},
|
||||||
|
|
||||||
|
book() {
|
||||||
|
const station = this.data.serviceLocation
|
||||||
|
const id = station ? station.id : ''
|
||||||
|
const query = id ? `?serviceLocationId=${encodeURIComponent(id)}` : ''
|
||||||
|
wx.navigateTo({ url: `/pages/service-appointment/service-appointment${query}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
onRetry() {
|
||||||
|
this.loadStation()
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -104,7 +104,7 @@ assert.match(premaritalTemplate, /预约辅导/, '婚前辅导页缺少预约按
|
|||||||
assert.match(premaritalTemplate, /服务状态/, '婚前辅导页缺少服务状态')
|
assert.match(premaritalTemplate, /服务状态/, '婚前辅导页缺少服务状态')
|
||||||
|
|
||||||
const contentTemplate = read('miniapp/pages/content/content.wxml')
|
const contentTemplate = read('miniapp/pages/content/content.wxml')
|
||||||
const contentLogic = read('miniapp/pages/content/content.js')
|
const contentLogic = read('miniapp/pages/content/content.ts')
|
||||||
assert.match(contentTemplate, /content-categories/, '婚恋课堂缺少分类导航')
|
assert.match(contentTemplate, /content-categories/, '婚恋课堂缺少分类导航')
|
||||||
assert.match(contentTemplate, /content-list/, '婚恋课堂缺少内容列表')
|
assert.match(contentTemplate, /content-list/, '婚恋课堂缺少内容列表')
|
||||||
assert.match(contentTemplate, /viewCount/, '婚恋课堂缺少真实浏览量')
|
assert.match(contentTemplate, /viewCount/, '婚恋课堂缺少真实浏览量')
|
||||||
@@ -120,13 +120,13 @@ assert.match(antiFraudTemplate, /举报可疑行为/, '婚恋反诈缺少举报
|
|||||||
assert.match(antiFraudTemplate, /安全求助/, '婚恋反诈缺少求助入口')
|
assert.match(antiFraudTemplate, /安全求助/, '婚恋反诈缺少求助入口')
|
||||||
|
|
||||||
const stationTemplate = read('miniapp/pages/service-station/service-station.wxml')
|
const stationTemplate = read('miniapp/pages/service-station/service-station.wxml')
|
||||||
const stationLogic = read('miniapp/pages/service-station/service-station.js')
|
const stationLogic = read('miniapp/pages/service-station/service-station.ts')
|
||||||
for (const label of ['地址', '联系电话', '开放时间', '服务项目', '地图定位', '预约到站服务']) {
|
for (const label of ['地址', '联系电话', '开放时间', '服务项目', '地图定位', '预约到站服务']) {
|
||||||
assert.ok(stationTemplate.includes(label), `服务站缺少 ${label}`)
|
assert.ok(stationTemplate.includes(label), `服务站缺少 ${label}`)
|
||||||
}
|
}
|
||||||
assert.match(stationLogic, /wx\.makePhoneCall/, '服务站电话必须使用微信原生拨号')
|
assert.match(stationLogic, /wx\.makePhoneCall/, '服务站电话必须使用微信原生拨号')
|
||||||
assert.match(stationLogic, /wx\.openLocation/, '服务站定位必须使用微信原生地图')
|
assert.match(stationLogic, /wx\.openLocation/, '服务站定位必须使用微信原生地图')
|
||||||
assert.match(stationLogic, /api\.get\(['"]\/home['"]\)/, '服务站必须读取真实首页服务站数据')
|
assert.match(stationLogic, /api\.get.*['"]\/home['"]/, '服务站必须读取真实首页服务站数据')
|
||||||
assert.doesNotMatch(stationLogic, /00000000/, '服务站不得保留演示电话')
|
assert.doesNotMatch(stationLogic, /00000000/, '服务站不得保留演示电话')
|
||||||
|
|
||||||
for (const envFile of ['miniapp/env.js', 'miniapp/env.ts']) {
|
for (const envFile of ['miniapp/env.js', 'miniapp/env.ts']) {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const backlog = read('docs/JIRA_BACKLOG.md')
|
|||||||
const phase07 = read('docs/Phase-07-V11-Recovery-Completion-Report-2026-09-13.md')
|
const phase07 = read('docs/Phase-07-V11-Recovery-Completion-Report-2026-09-13.md')
|
||||||
const matrix = read('docs/UI_REDESIGN_PAGE_MATRIX_V1.1.md')
|
const matrix = read('docs/UI_REDESIGN_PAGE_MATRIX_V1.1.md')
|
||||||
|
|
||||||
assert.match(status, /Phase 07[^\n]*(视觉否决|视觉待重新实现|UI 待重新实现)/, 'STATUS 未标记 Phase 07 视觉失效')
|
assert.match(status, /Phase 07[^\n]*(视觉.*否决|视觉待重新实现|UI 待重新实现|重设计中)/, 'STATUS 未标记 Phase 07 视觉失效')
|
||||||
assert.match(backlog, /Phase 07[^\n]*(视觉否决|重新打开|REOPENED)/, 'JIRA 未重新打开 Phase 07 UI')
|
assert.match(backlog, /Phase 07[^\n]*(视觉否决|重新打开|REOPENED)/, 'JIRA 未重新打开 Phase 07 UI')
|
||||||
assert.match(phase07, /视觉结论已失效/, 'Phase 07 报告未保留业务证据并撤销视觉结论')
|
assert.match(phase07, /视觉结论已失效/, 'Phase 07 报告未保留业务证据并撤销视觉结论')
|
||||||
assert.match(matrix, /小程序首批 11 个核心页面/, '页面矩阵未切换到当前首批范围')
|
assert.match(matrix, /小程序首批 11 个核心页面/, '页面矩阵未切换到当前首批范围')
|
||||||
|
|||||||
Reference in New Issue
Block a user