fix: 中奖名单公示页搜索修复——输入关键字时自动按大页拉全当期名单再本地过滤,修复未翻页的中奖者(如王球莲)搜索无结果的问题;接口无姓名参数,pageSize=500×N 循环实测 4 请求 2000 条,拉全失败静默降级

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jacobxu666
2026-09-29 16:58:42 +08:00
co-authored by Claude Fable 5
parent 2a6b86977c
commit 748c4563fd
2 changed files with 105 additions and 1 deletions
@@ -0,0 +1,52 @@
# 第一期中奖名单导入记录(2026-09-29)
## 背景
怀化市社保卡千万好礼促消费活动第一期开奖名单产生后,需导入 UAT 库 `dscp_business_uat.hhrs_winner` 表,供公示页 https://hh.hn12301.net/#/pages/winners/winners 展示。
## 数据来源
| 文件 | 内容 |
| --- | --- |
| `抽奖-中奖数据.xlsx`(2026-09-29 10:21 导出) | 第一期中奖名单原始文件:2000 人(姓名+脱敏身份证号) |
| `抽奖-中奖数据-匹配hhrs_lottery_pool-batch1.csv` | 与抽奖池 `hhrs_lottery_pool`(batch_no=1)匹配结果:2001 行(1 人双卡) |
## 导入内容与校验
- 导入通道:直连 UAT MySQL,事务内导入,前后计数复核通过。
- 导入行数:**2000**(batch1 CSV 2001 行按 姓名+证件号 去重;会同县「杨明 431225********4219」池中有两条卡记录,保留 pool_id 较小的一条,业务侧如需明确以哪张卡中奖需另行核实)。
- 表结构:UAT 库 `hhrs_winner` 实际为抽奖池版结构(`id, pool_id, member_name, county, member_identity_no, member_phone, created_time, updated_time`),无奖品/开奖日期列。
- 字段取值:`id` 与 `pool_id` 均取抽奖池记录 id(一一对应、可溯源);`county` 取自池表;证件/手机号为池表脱敏值(库内即脱敏,公示展示不受影响);`created_time=NOW()`(2026-09-29 14:27)。
- 同步操作:对应 2000 条池记录 `won` 置 1(与抽奖台「公布即回写」语义一致)。
- 旧数据:导入前表为空,无清理动作。
## 已知问题(导入前即存在)
dscp-uat 当前部署的后端与上表结构不匹配:
- `mp-api/hhrsWinner/dates`、`mp-api/hhrsWinner/list` 均返回 `errcode 99999`(部署代码按旧 prize 版 schema 查询,`draw_date/card_model_id` 列已不存在);
- `open-api/hhrsDraw/*`(抽奖台后台)在该服务上为 404,抽奖台实际使用的后端未部署在此环境。
因此公示页当前显示「暂无数据/接口失败」,**与本次导入的数据无关**;待与现表结构匹配的新版后端部署后,第一期名单即可展示,数据无需重导。
## 补充排查发现(2026-09-29 下午)
1. UAT 库中还存在抽奖后台配套表 `hhrs_draw_activity` / `hhrs_draw_round`(2026-09-28 建),即抽奖台新版后端的排期数据:
- **第一期官方开奖日期 = 2026-09-29**(「2026 金秋千万好礼 · 第一期(9·29 专场)」,活动 status=1 已发布);
- 排期设计:9·29 专场三场 600+700+700=2000 人,分别绑定 batch 1/2/3;全部轮次 status=0(待开奖)、seed/drawn_time 为空 → **抽奖台自动回写链路从未成功跑通**(与 dscp-uat 上 hhrsDraw 接口 404 相符);
- 实际交付的第一期名单是「batch1 一次抽 2000 人」,与排期表的三场拆分不一致——若后续场次仍按排期表取 batch2/3,不受本次导入影响(批次天然互斥)。
2. 公示页要立即显示名单,零部署方案为:`hhrs_winner` 增加旧接口所需 8 列(card_model_id/member_id/prize_*/draw_date/remark,均可空,兼容新版写入),并回填:card_model_id=747609734826757、draw_date='2026-09-29'、prize_name='怀化市社保卡千万好礼促消费千元权益包'、prize_type='权益包'、prize_amount=100000(分,即 1000 元)。**待用户批准后执行**。
3. 前端「隐藏手机号」改动(本地已完成)生效路径:HBuilderX CLI 可本地构建,产物上传 121.43.239.220:/home/hh/html 需要 SSH 权限(凭据在 Obsidian 运维手册)。
## 前端配套改动(本地已改,未发布)
`hhrs/pages/winners/winners.vue`:
1. 按业务要求移除「手机号码」列(表头、行、样式、按手机号过滤逻辑),搜索框改为仅按姓名。
2. 修复「名单未翻到的人物搜索无结果」(如 王球莲):搜索原本只在前端已加载的 10 条/页内过滤,后端列表接口无姓名检索参数;现改为输入关键字/点搜索时按 500 条大页自动拉全当期名单(实测第一期 4 次请求共 2000 条)再本地过滤,全量结果按期缓存;拉取失败静默降级为已加载部分可搜。
需 HBuilderX 构建后上传 121.43.239.220:/home/hh/html 生效(`sh h5-release.sh` → 产物 scp 原子切换)。
## 发布结果(2026-09-29 16:07 线上实测)
已发布生效:入口 `index.8ec4f995.js` → `index.e438708b.js`,修复分片 `pages-winners-winners.a875331e.js`(含 loadAll 全量搜索)公网 200。浏览器实测:不翻页直接搜「王球莲」命中 1 条(4 个 pageSize=500 请求自动拉全),姓名/身份证/手机号三列正常。四站点回归 hh/pxdy/mkh5/api/dw 全 200。发布过程偏差(目录切换替代覆盖式)已按手册补救:旧 hash 资源全量还原、`._*` AppleDouble 清理、webroot 备份 `/root/hh-html-backup-20260929-presearchfix.tar.gz`。winners.vue 源码改动尚未 commit。
+53 -1
View File
@@ -81,7 +81,11 @@ export default {
// xuchao: 页码改为按期维护(原单一 pageNo 在切期后会被沿用,导致同一页重复追加/跳页) // xuchao: 页码改为按期维护(原单一 pageNo 在切期后会被沿用,导致同一页重复追加/跳页)
pageNos: {}, pageNos: {},
pageSize: 10, pageSize: 10,
// xuchao: 搜索触发的全量拉取用大页尺寸(列表接口仅支持分页参数,无姓名检索)
pageSizeFull: 500,
totals: {}, totals: {},
fullLoaded: {},
loadingAll: {},
totalWinners: 0, totalWinners: 0,
inFlight: 0, inFlight: 0,
loadStatusMap: {}, loadStatusMap: {},
@@ -104,6 +108,15 @@ export default {
return this.loadStatusMap[this.currentTab] || 'nomore' return this.loadStatusMap[this.currentTab] || 'nomore'
} }
}, },
watch: {
// xuchao: 输入关键字即后台拉全当期名单,修复"没翻到的人物搜索无结果"
keyword(val) {
clearTimeout(this._kwTimer)
if (val && val.trim() && !this.fullLoaded[this.currentTab]) {
this._kwTimer = setTimeout(() => this.loadAll(this.currentTab), 400)
}
}
},
onLoad() { onLoad() {
this.loadDates() this.loadDates()
}, },
@@ -155,6 +168,8 @@ export default {
pageSize: this.pageSize, pageSize: this.pageSize,
drawDate: drawDate drawDate: drawDate
}).then(res => { }).then(res => {
// xuchao: 全量拉取已就绪/进行中时,旧的10条分页响应直接丢弃,避免覆盖全量列表
if (this.fullLoaded[tabIndex] || this.loadingAll[tabIndex]) return
if (res.errcode === 0 && res.data) { if (res.errcode === 0 && res.data) {
const list = res.data.list || [] const list = res.data.list || []
const prev = this.winnerData[tabIndex] || [] const prev = this.winnerData[tabIndex] || []
@@ -177,12 +192,48 @@ export default {
}, },
loadMore() { loadMore() {
const tab = this.currentTab const tab = this.currentTab
if (this.loadingAll[tab]) return
const total = this.totals[tab] || 0 const total = this.totals[tab] || 0
const len = (this.winnerData[tab] || []).length const len = (this.winnerData[tab] || []).length
if (len < total && this.inFlight === 0) { if (len < total && this.inFlight === 0) {
this.loadWinnerList(tab, (this.pageNos[tab] || 1) + 1) this.loadWinnerList(tab, (this.pageNos[tab] || 1) + 1)
} }
}, },
// xuchao: 按大页把当期名单一次拉全,搜索即可命中未翻到的人;完成后整期替换并标记 fullLoaded
loadAll(tabIndex) {
const tab = this.tabs[tabIndex]
if (!tab || this.fullLoaded[tabIndex] || this.loadingAll[tabIndex]) return
this.$set(this.loadingAll, tabIndex, true)
this.$set(this.loadStatusMap, tabIndex, 'loading')
const acc = []
const pull = (pageNo) => this.$u.api.getWinnerList({
pageNo: pageNo,
pageSize: this.pageSizeFull,
drawDate: tab.date
}).then(res => {
if (res.errcode !== 0 || !res.data) return
const list = res.data.list || []
const total = res.data.total || 0
acc.push(...list)
this.$set(this.totals, tabIndex, total)
if (list.length > 0 && acc.length < total && pageNo < 50) return pull(pageNo + 1)
})
pull(1).then(() => {
if (acc.length > 0) {
this.$set(this.winnerData, tabIndex, acc)
this.$set(this.fullLoaded, tabIndex, true)
this.$set(this.pageNos, tabIndex, Math.ceil(acc.length / this.pageSizeFull))
}
this.totalWinners = Object.values(this.totals).reduce((s, n) => s + n, 0)
}).catch(() => {
// 拉全失败静默降级为已加载部分可搜
}).finally(() => {
this.$set(this.loadingAll, tabIndex, false)
const len = (this.winnerData[tabIndex] || []).length
const total = this.totals[tabIndex] || 0
this.$set(this.loadStatusMap, tabIndex, len === 0 || len >= total ? 'nomore' : 'loadmore')
})
},
switchTab(index) { switchTab(index) {
this.currentTab = index this.currentTab = index
// xuchao: 不再重置全局 pageNo(各期游标自带);空列表补拉首页时允许并发, // xuchao: 不再重置全局 pageNo(各期游标自带);空列表补拉首页时允许并发,
@@ -204,7 +255,8 @@ export default {
} }
}, },
onSearch() { onSearch() {
// 前端过滤,无需重新请求 // xuchao: 点搜索立即确保当期全量已拉取(过滤仍在前端本地完成)
if (!this.fullLoaded[this.currentTab]) this.loadAll(this.currentTab)
} }
} }
} }