修改河南项目的管理后台ui

This commit is contained in:
2026-09-27 10:00:18 +08:00
parent ab91b0ca82
commit 266dad03e4
28 changed files with 406 additions and 1288 deletions
+34
View File
@@ -6,6 +6,40 @@
---
## 头像库「是否默认」唯一化:`tt_user_avatar` 存量数据清洗
**背景**:`tt_user_avatar.is_default` 的语义是「新用户 / 白号注册时分配的初始头像」,
**全表必须最多只有一条 `is_default='1'`**。但后台「设置 → 头像管理」的开关写成默认时,
后端只做了 `updateById`,没有把其他记录的默认标记清掉 ⇒ 库中一度累积出 **7 条默认**(id 63~69),
`id=62` 反而是「否」。后果:后台看到一排「是」容易误解(用户 2026-09-24 反馈),
且 C 端注册处(`ApiRegisterServiceImpl` / `PromoLoginServiceImpl`)用的是
`list(is_default='1')` + `RandomUtil.choice(...)` **随机挑一条**,导致默认头像实际不唯一、每次注册拿到的都不同。
**代码侧修复**(非 SQL,见 `docs/修改记录.md` 同日条目):`TtUserAvatarServiceImpl`
在 `updateUserAvatarById` / `saveAvatar` 中增加互斥逻辑 —— 任何把某条置为 `is_default='1'` 的写入,
都先把其余记录清成 `'0'`,两步同一事务。
**本次数据库变更 1 处**(2026-09-24 已在库 `henan` 执行):把多余的默认标记收敛为**只保留 id 最小的一条**(id=63)。
**变更 SQL**:
```sql
UPDATE `tt_user_avatar`
SET `is_default` = '0'
WHERE `is_default` = '1'
AND `id` <> (SELECT `min_id` FROM (
SELECT MIN(`id`) AS `min_id` FROM `tt_user_avatar` WHERE `is_default` = '1'
) AS `t`);
```
> ⚠️ MySQL 不允许在 `UPDATE` 的子查询里直接查同一张表,故此处包了一层派生表 `t` 绕过;
> 该写法的坑已记在 `.workbuddy/memory/` 当日日志里,后续同类 SQL 照此办理。
**验证口径**:执行后 `SELECT is_default, COUNT(*) FROM tt_user_avatar GROUP BY is_default;`
应当只有两行 —— `0 → N`(N = 总数-1)、`1 → 1`。
---
## 幸运升级·水位策略简化为「单一目标水位」:`sys_config` 配置结构变更
**背景**:上一版把水位策略做成了 **4 档波段表**(每档 = 水位下界/上界 + 随机数下界/上界),
+154
View File
@@ -9,6 +9,53 @@
## 2026-09-24
- **头像管理:「是否默认」可同时多条为「是」的缺陷修复(唯一性约束 + 存量数据清洗 + 开关交互)**
**起因**:用户在「网站设置 → 头像管理」截图反馈,「是否默认」那一列有 7 条都显示绿色「是」,
而按语义**全站默认头像只应该有一条**。
**排查结论(逐层验证,不是猜的)**:
| 层 | 实测 | 结论 |
|---|---|---|
| 数据库 | `SELECT is_default, COUNT(*) ... GROUP BY is_default` → `0→1`、`1→7`(id 63~69) | **数据本身就错**,界面是忠实反映 |
| 前端 | `v-model="scope.row.isDefault"` 直读接口字段 | 无显示层问题,不需要改渲染 |
| 后端 | `TtUserAvatarServiceImpl.updateUserAvatarById` 只做 `updateById` | **根因**:设为默认时不互斥清空其他记录 |
⇒ 定位为**后端写接口缺约束 + 存量脏数据**,不是 UI bug。
**二、连带发现(比表面问题更严重)**:C 端有 4 处消费「默认头像」,其中
`ApiRegisterServiceImpl`、`PromoLoginServiceImpl` 用的是
`list(is_default='1')` + `RandomUtil.choice(...)` **随机挑一条** —— 这是在「可能有多条默认」的
既成事实上打的补丁。后果是**每次注册拿到的初始头像都不一样**,默认头像形同虚设。
(`TtUserServiceImpl` 白号那边已有 `defaultAvatarList.isEmpty()` 的降级,逻辑本身没问题。)
**三、改动清单**
1. **后端互斥(核心)** `skins-service/service-admin`:
- `TtUserAvatarService` 新增 `saveAvatar(TtUserAvatar)`、`getDefaultAvatar()`;
- `TtUserAvatarServiceImpl` 新增私有方法 `clearOtherDefaultsIfNeeded(...)`:
凡是把某条写成 `is_default='1'` 的写入,**先 `UPDATE ... SET is_default='0' WHERE is_default='1' AND id<>当前id`**,
再落当前这条,两步同一事务(`@Transactional`)。只在确实要置默认时才执行 UPDATE,普通编辑不白扫全表。
- `updateUserAvatarById` / `saveAvatar` 均接入;顺手删掉原方法里无意义的 `setAvatar(getAvatar())`。
2. **控制器** `TtUserAvatarController.add` 由 `userAvatarService.save(...)` 改走 `saveAvatar(...)`,
使**新增**也受同一约束(原来新增一条默认就会叠加出第二个)。
3. **存量数据清洗**:`UPDATE tt_user_avatar SET is_default='0' WHERE is_default='1' AND id <> (最小 id 的默认)`,
实测影响 6 行,清洗后 `0→7 / 1→1`(保留 id=63)。SQL 全文见 `docs/updata_sql.md`。
4. **前端交互** `ruoyi-ui/src/views/skins/ttSetting/avatar.vue`:「是否默认」由 `updateUserStatus`
改走新的 `updateDefault(...)` —— 开启时先 `$confirm` 告知「原默认头像「xx」将自动取消默认」,
取消则回滚开关视觉并重新拉列表。关闭时直接保存。
**四、验证**:后端 `mvn -q -pl skins-service/service-admin -am install -DskipTests` 通过,
字节搜 `clearOtherDefaultsIfNeeded`/`saveAvatar`/`getDefaultAvatar` 均在 `.class` 里;
前端 `vue-cli-service build` 通过,新文案与新方法名均落在 `dist/static/js/chunk-cf2f9538.*.js`。
**五、踩坑**:MySQL **不允许 `UPDATE` 的子查询直接查同一张表**(报 1093),
解法是包一层派生表 `(SELECT MIN(id) ... ) AS t` 绕过 —— 后续同类 SQL 照此办理。
⚠️ **待办**:后端 jar 未重打包、服务未重启,互斥逻辑**尚未在运行中的服务里生效**;
前端 `dist` 已就绪但线上资源需按部署流程发布。存量数据已清洗,界面当下已正确。
- **幸运升级:水位配置从「4 档波段表」简化为「一个目标水位百分比」+ 饰品随机数区间恢复可编辑**
**起因**:上一版后台要维护一张 4 档波段表(水位下界/上界 × 随机数下界/上界 = 16 个数),
@@ -199,6 +246,113 @@
这是「水位是累计比例」的必然结果 —— 想在大水位下也能快速纠偏,要调 **`gain`**(水位每偏离 1 个点、
中心移动的幅度;现在默认 2,暂未在后台暴露),或者直接人工注水/抽水。
- **后台开关(el-switch)统一为「文字 = 当前值」口径,修掉反向文字露底色的误导**
**起因**:用户反馈「头像管理」的**状态**与**是否默认**两列看得费解。查下来是两个叠加问题:
1. **两边文字显示规则相反**。element-ui 的 `el-switch` 里 `active-text` 渲染在**右**侧、
且只在**开启时**显示;`inactive-text` 渲染在**左**侧、只在**关闭时**显示。
本页「状态」用的是 `inactive-text="启用"`(把当前值放在左侧),
「是否默认」用的是 `active-text="是"`(把当前值放在右侧)—— 同一张表里两套相反的读法。
2. **更糟的是反向文字会露出底色**。「状态」列开启时左侧露出绿色底 + 白字「启用」,
点一下滑块滑到左边、绿底被盖住,可读文字变成「停用」。两列的滑块方向可以完全相反,
同一行的绿白视觉还不一样(`is-checked` 与背景色是两套独立配色)。
⇒ **定的新口径(全后台统一):`inactive-text` = 关闭时的值,`active-text` = 开启时的值,
也就是「文字显示的就是当前值」,滑块停在哪边、哪边的文字就是现在的结果。**
颜色同步收敛为与 `active-value`/`inactive-value` 同侧:**绿 = 该项生效、红 = 该项未生效**。
**改动清单(7 个文件,15 处开关)**
| 文件 | 列 / 字段 | 调整 |
|---|---|---|
| `skins/ttSetting/avatar.vue` | 状态 `status` | 文字翻正(启用/停用),配色与 `active-value` 同侧 |
| `skins/ttSetting/avatar.vue` | 是否默认 `isDefault` | `active/inactive-value` 翻正为 `1/0`(= 与 `initData`、弹窗单选按钮一致),文字归右侧 |
| `skins/ttuser/index.vue` | 帐号状态 `status` | `active/inactive-value` 翻正为 `0/1`(原为 `1/0`,是全局唯一的反向异类) |
| `skins/ttuser/index.vue` | 提货状态 `deliveryStatus` | 同上翻正(`1` = 已禁用提货) |
| `skins/ttuser/index.vue` | 赠送状态 `giftStatus` | 同上翻正(`1` = 已禁用赠送) |
| `skins/ttBonus/index.vue` | 启用状态 `status` | 文字从「是/否」改为「启用/禁用」并翻正 |
| `skins/ttBox/boxType.vue` | 是否对战类型 `isFightType`、是否显示 `isShow` | label 归位;`isShow` 的 `active/inactive-value` 翻正为 `1/0` |
| `skins/ttredPacket/index.vue` | 是否启用 `useStatus` | `active/inactive-value` 翻正为 `1/0`,配色同侧 |
| `skins/ttUpgrade/index.vue` | 状态 `status` | `active/inactive-value` 翻正为 `0/1`(原为 `1/0`)+ 文字翻正 |
| `skins/ttBox/index.vue` | 弹窗内 是否对战宝箱 `isFight`、宝箱状态 `status`、是否首页推荐 `isHome` | 文字归位、`isFight`/`status` 的 `active-value` 翻正 |
**关键约束:以上全部只是「值 ↔ 显示」的映射调整,`active-value` 与 `inactive-value` 成对同翻,
提交给后端的字段值逐字节不变,数据库语义与接口契约零改动**(各页 `@change` 都是把整个 row/form
原样 `PUT` 出去,不做值转换)。
**语义校对依据**(改前逐个确认过,避免把含义改反):
- `tt_user_avatar.status` 与 `tt_user.status`:`UserStatus` 枚举 = `OK("0","正常")` / `DISABLE("1","停用")`,
`ApiUserDetailsServiceImpl` 对 `DISABLE` 直接拒登并踢线 ⇒ **`0` = 启用**。
- `tt_user_avatar.is_default`:`TtUserAvatar` 的 `@Excel(readConverterExp = "0=否,1=是")`,
且注册 / 白号 / 登录三处均查 `is_default = '1'` 取默认头像 ⇒ **`1` = 是**。
- `tt_user.delivery_status` / `gift_status`:实体注释 + 业务代码均为 **`1` = 已禁用**(`"1".equals(...)` 返回"已被禁用"提示)。
- 可对照的既有正确写法:`ttSetting/banner.vue`、`ttRechargeList/index.vue`、`ttuser/userPack.vue`。
⚠️ **踩坑**:用户截图里「是否默认」列显示为**红底白字「否」**容易被误读成"开着"。
那是 `active-color`/`inactive-color` 与 `active-text`/`inactive-text` 配成反的典型症状 ——
改完须确认**颜色所在的半边和文字所在的半边是同一半**。
⚠️ **未改(故意留着)**:`ttRoll/officialRollConfig.vue` 的状态开关用的是
`:value="scope.row.status === '0'"` + 无文字标注的正向布尔(绿=开),本身自洽;
只建议后续补一个文字标注。
- **管理后台:3 个页面表格右侧留白(「表格没有平铺完整」)**
**起因**:用户反馈「管理后台有些页面的表格没有平铺完整」。追问确认现象是
**表格右侧留一条空白**(不是列错位、也不是横向滚动条)。
**根因**(读 Element 源码定论):`element-ui/packages/table/src/table-layout.js:132-195`
的 `updateColumnsWidth()` ——
```
flexColumns = 所有「width 不是数字」的列(含显式 min-width 列、以及两者都没写的列)
if (flexColumns.length > 0 && fit) → 自动分配,表格撑满 bodyWidth (第 145 行)
else → bodyWidth = Σ(width || minWidth || 80)(第 182-194 行)
```
⇒ **只要有一列写了 `min-width`(或宽度两者都不写),表格就会自动撑满容器;
但当所有列都是固定 `width` 时,表格宽度恒等于列宽之和 —— 容器再宽也不拉伸,右侧就空一条。**
同一份源码里还有个易漏的点:`type="selection" / "expand" / "index"` 在未写 width 时,
由 `config.js:3-26` 的 `cellStarts` 给死 **48px 固定宽**,**不参与弹性分配** ——
所以「展开列 + 其余全固定宽」的表格同样会留白。
**扫描结论**(115 个顶层表格,1920 视口下容器可用 1584px):
- **仅 6 个**是全固定列(会留白);**109 个**含弹性列、会自行撑满。
- **0 个**会出横向滚动条(判据:`Σwidth + Σmin-width > 容器`)。
- 留白量**随屏幕变宽而增大**(2560 视口下最大 1854px)—— 这正是"没平铺完整"的典型特征。
**改动清单(3 个页面主表格,各把「内容最长的固定列」改为 `min-width`)**
| 文件 | 列 | 调整 | 表格宽 → 结果 |
|---|---|---|---|
| `skins/blankCardKey/index.vue` | 卡密编码 `cardCode` | `width="200"` → `min-width="200"` | 1000 → 撑满(原留白 584px) |
| `system/role/index.vue` | 权限字符 `roleKey` | `width="150"` → `min-width="150"` | 1075 → 撑满(原留白 509px) |
| `game/wheel/records/index.vue` | 投注符号 `betSymbols` | `width="140"` → `min-width="140"` | 1368 → 撑满(原留白 216px) |
选列理由:卡密编码是 32 位等宽字符 + 复制按钮、权限字符内容最长且**已带 `show-overflow-tooltip`**
(加宽无副作用)、投注符号是拼接字符串、长度波动最大 —— 都是"多给宽度只有好处"的列。
三处均加注释说明「为什么必须是 min-width」,防止后来人按"列宽统一用 width"的惯例改回去。
**未改(用户明确不动)**:3 个展开行内嵌明细表(`wheel/records` 下注明细、
`delivery/transferApply` 与 `transferApplyProcessed` 的饰品明细)与 1 个弹窗内倍率表
(`wheel/config`,固定 `width:640px`)—— 它们宽度受容器约束是设计意图。
其中 `transferApply` / `transferApplyProcessed` 声明了 `style="width: auto"` 却只有 2 列固定宽
(300+120=420),**在展开行里只占 420px**;若后续要铺满,改其中一列为 `min-width` 即可。
**验证**:
- 编译产物断言:`vue-template-compiler` 编译 3 个页面,✓ 全部通过,
`min-width` 各 1 个(200 / 150 / 140),其余列仍为固定 width。
- 算法级回归:复现 `updateColumnsWidth` 后,
**1280 / 1440 / 1600 / 1920 / 2560 五个视口下 3 个主表格留白全部归零**。
- dev server 起来后 `curl` 返回 200,确认改动未破坏编译。
- ⚠️ **本机无法真机截图核对**:助手拉起 Chrome 被安全策略拦(连带报 `wsl.exe` 在程序黑名单),
故本次验证口径是「读源码 + 复现算法 + 编译产物断言」,**未经浏览器实测**。
**纯前端改动,不涉及数据库与接口**;`dist` 未发布。
---
## 2026-09-23