修改河南项目的管理后台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` 配置结构变更 ## 幸运升级·水位策略简化为「单一目标水位」:`sys_config` 配置结构变更
**背景**:上一版把水位策略做成了 **4 档波段表**(每档 = 水位下界/上界 + 随机数下界/上界), **背景**:上一版把水位策略做成了 **4 档波段表**(每档 = 水位下界/上界 + 随机数下界/上界),
+154
View File
@@ -9,6 +9,53 @@
## 2026-09-24 ## 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 档波段表」简化为「一个目标水位百分比」+ 饰品随机数区间恢复可编辑**
**起因**:上一版后台要维护一张 4 档波段表(水位下界/上界 × 随机数下界/上界 = 16 个数), **起因**:上一版后台要维护一张 4 档波段表(水位下界/上界 × 随机数下界/上界 = 16 个数),
@@ -199,6 +246,113 @@
这是「水位是累计比例」的必然结果 —— 想在大水位下也能快速纠偏,要调 **`gain`**(水位每偏离 1 个点、 这是「水位是累计比例」的必然结果 —— 想在大水位下也能快速纠偏,要调 **`gain`**(水位每偏离 1 个点、
中心移动的幅度;现在默认 2,暂未在后台暴露),或者直接人工注水/抽水。 中心移动的幅度;现在默认 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 ## 2026-09-23
+2
View File
@@ -255,3 +255,5 @@ aside {
// 皮肤层放最后,保证覆盖 Element UI 默认样式 // 皮肤层放最后,保证覆盖 Element UI 默认样式
@import './skin.scss'; @import './skin.scss';
// 开关(状态列)已改为自研组件 components/CapsuleSwitch(自带 scoped 样式),
// 不再需要历史上那种全局 ::v-deep 覆盖 el-switch 内部结构的写法。
+3
View File
@@ -33,6 +33,8 @@ import ImageUpload from "@/components/ImageUpload"
import ImagePreview from "@/components/ImagePreview" import ImagePreview from "@/components/ImagePreview"
// 字典标签组件 // 字典标签组件
import DictTag from '@/components/DictTag' import DictTag from '@/components/DictTag'
// 胶囊开关组件(替代 el-switch)
import CapsuleSwitch from '@/components/CapsuleSwitch'
// 头部标签组件 // 头部标签组件
import VueMeta from 'vue-meta' import VueMeta from 'vue-meta'
// 字典数据组件 // 字典数据组件
@@ -58,6 +60,7 @@ Vue.component('Editor', Editor)
Vue.component('FileUpload', FileUpload) Vue.component('FileUpload', FileUpload)
Vue.component('ImageUpload', ImageUpload) Vue.component('ImageUpload', ImageUpload)
Vue.component('ImagePreview', ImagePreview) Vue.component('ImagePreview', ImagePreview)
Vue.component('CapsuleSwitch', CapsuleSwitch)
Vue.use(directive) Vue.use(directive)
Vue.use(plugins) Vue.use(plugins)
@@ -129,70 +129,6 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
#main { #main {
width: 500px; width: 500px;
height: 400px; height: 400px;
@@ -216,79 +216,6 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
#main { #main {
width: 500px; width: 500px;
height: 400px; height: 400px;
@@ -263,79 +263,6 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
#main { #main {
width: 500px; width: 500px;
height: 400px; height: 400px;
@@ -369,70 +369,6 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
#main { #main {
width: 500px; width: 500px;
height: 400px; height: 400px;
@@ -106,7 +106,10 @@
<el-tag v-if="scope.row.poolTypes" :type="poolTagType(scope.row.poolTypes)" size="mini">{{ poolLabel(scope.row.poolTypes) }}</el-tag> <el-tag v-if="scope.row.poolTypes" :type="poolTagType(scope.row.poolTypes)" size="mini">{{ poolLabel(scope.row.poolTypes) }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="betSymbols" label="投注符号" width="140" align="center" /> <!-- ★ min-width 而不是 width:展开列(48)+其余 11 列原全为固定宽,合计 1368px,
没有弹性列时表格宽度恒等于列宽之和(table-layout.js:182-194),1920 视口下右侧空 216px。
投注符号是拼接字符串、长度波动最大,交给它吃剩余宽度最合适。 -->
<el-table-column prop="betSymbols" label="投注符号" min-width="140" align="center" />
<el-table-column prop="totalBetAmount" label="总下注" width="100" align="right" /> <el-table-column prop="totalBetAmount" label="总下注" width="100" align="right" />
<el-table-column prop="userRoundPayout" label="回合实际赔付" width="120" align="right"> <el-table-column prop="userRoundPayout" label="回合实际赔付" width="120" align="right">
<template slot-scope="scope"> <template slot-scope="scope">
@@ -24,7 +24,11 @@
<!-- 卡密列表 --> <!-- 卡密列表 -->
<el-table :data="cardList" v-loading="tableLoading" border stripe style="width: 100%"> <el-table :data="cardList" v-loading="tableLoading" border stripe style="width: 100%">
<el-table-column prop="id" label="ID" width="70" align="center" /> <el-table-column prop="id" label="ID" width="70" align="center" />
<el-table-column prop="cardCode" label="卡密编码" width="200" align="center"> <!-- ★ 这一列必须是 min-width:卡密编码最长(32 位等宽字符 + 复制按钮),
且它改宽后本表就有了弹性列,才会撑满容器。
原来 8 列全是固定 width,列宽和恒为 1000px,《Element》table-layout.js:182-194
走 else 分支 ⇒ bodyWidth = 列宽之和,容器再宽也不拉伸,右侧空一条。 -->
<el-table-column prop="cardCode" label="卡密编码" min-width="200" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<span style="font-family: 'Courier New', monospace; color: #9a5c35; font-weight: 600; margin-right: 6px;">{{ scope.row.cardCode }}</span> <span style="font-family: 'Courier New', monospace; color: #9a5c35; font-weight: 600; margin-right: 6px;">{{ scope.row.cardCode }}</span>
<el-button type="text" size="mini" @click="copyText(scope.row.cardCode)">复制</el-button> <el-button type="text" size="mini" @click="copyText(scope.row.cardCode)">复制</el-button>
+6 -72
View File
@@ -50,16 +50,14 @@
<el-table-column label="奖励区间" align="center" prop="awardSection" /> <el-table-column label="奖励区间" align="center" prop="awardSection" />
<el-table-column label="启用状态" align="center" prop="status"> <el-table-column label="启用状态" align="center" prop="status">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.status" v-model="scope.row.status"
:active-value="'0'" active-value="0"
:inactive-value="'1'" inactive-value="1"
inactive-color="#a4485a" active-text="启用"
active-color="#457450" inactive-text="禁用"
inactive-text="是"
active-text="否"
@change="updateUserStatus(scope.row)" @change="updateUserStatus(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="排序" align="center" prop="sortBy" /> <el-table-column label="排序" align="center" prop="sortBy" />
@@ -427,68 +425,4 @@ export default {
border-radius: 50%; border-radius: 50%;
display: inline-block; display: inline-block;
} }
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
</style> </style>
+10 -78
View File
@@ -33,30 +33,26 @@
<!-- 仅在通用“宝箱分类”菜单展示是否对战类型;对战/经典分类已由路由锁定类型 --> <!-- 仅在通用“宝箱分类”菜单展示是否对战类型;对战/经典分类已由路由锁定类型 -->
<el-table-column v-if="$route.query.type == null" align="center" prop="isFightType" label="是否对战类型"> <el-table-column v-if="$route.query.type == null" align="center" prop="isFightType" label="是否对战类型">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.isFightType" v-model="scope.row.isFightType"
:active-value="'0'" active-value="0"
:inactive-value="'1'" inactive-value="1"
inactive-color="#a4485a" active-text="是"
active-color="#457450" inactive-text="否"
inactive-text="是"
active-text="否"
@change="updateUserStatus(scope.row)" @change="updateUserStatus(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" prop="isShow" label="是否显示"> <el-table-column align="center" prop="isShow" label="是否显示">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.isShow" v-model="scope.row.isShow"
:active-value="'0'" active-value="1"
:inactive-value="'1'" inactive-value="0"
active-color="#457450"
inactive-color="#a4485a"
active-text="显示" active-text="显示"
inactive-text="隐藏" inactive-text="隐藏"
@change="updateUserStatus(scope.row)" @change="updateUserStatus(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" prop="sort" label="宝箱分类排序"></el-table-column> <el-table-column align="center" prop="sort" label="宝箱分类排序"></el-table-column>
@@ -213,68 +209,4 @@ export default {
.home { .home {
padding: 20px; padding: 20px;
} }
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
</style> </style>
+14 -84
View File
@@ -320,16 +320,14 @@
<el-input-number v-model="form.sort" :min="1" :max="1000" label="请选择"></el-input-number> <el-input-number v-model="form.sort" :min="1" :max="1000" label="请选择"></el-input-number>
</el-form-item> </el-form-item>
<el-form-item label="是否对战宝箱" prop="isFight"> <el-form-item label="是否对战宝箱" prop="isFight">
<el-switch <capsule-switch
v-model="form.isFight" v-model="form.isFight"
:active-value="'0'" active-value="1"
:inactive-value="'1'" inactive-value="0"
active-color="#457450" active-text="是"
inactive-color="#a4485a" inactive-text="否"
active-text="否"
inactive-text="是"
:disabled="$route.query.isFight != null" :disabled="$route.query.isFight != null"
></el-switch> />
</el-form-item> </el-form-item>
<!-- 宝箱类型已隐藏(编辑时保留原值提交) <!-- 宝箱类型已隐藏(编辑时保留原值提交)
<el-form-item label="宝箱类型" prop="boxType"> <el-form-item label="宝箱类型" prop="boxType">
@@ -347,27 +345,23 @@
<el-input-number v-model="form.compAmount" :min="0" :max="1000" label="请选择"></el-input-number> <el-input-number v-model="form.compAmount" :min="0" :max="1000" label="请选择"></el-input-number>
</el-form-item> </el-form-item>
<el-form-item label="宝箱状态" prop="status"> <el-form-item label="宝箱状态" prop="status">
<el-switch <capsule-switch
v-model="form.status" v-model="form.status"
:active-value="'1'" active-value="0"
:inactive-value="'0'" inactive-value="1"
active-color="#a4485a"
inactive-color="#457450"
active-text="正常" active-text="正常"
inactive-text="停用" inactive-text="停用"
></el-switch> />
</el-form-item> </el-form-item>
<el-form-item label="是否首页推荐" prop="isHome"> <el-form-item label="是否首页推荐" prop="isHome">
<el-switch <capsule-switch
v-model="form.isHome" v-model="form.isHome"
:active-value="'1'" active-value="1"
:inactive-value="'0'" inactive-value="0"
active-color="#a4485a"
inactive-color="#457450"
active-text="是" active-text="是"
inactive-text="否" inactive-text="否"
@change="hh()" @change="hh()"
></el-switch> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
@@ -1381,70 +1375,6 @@ export default {
text-decoration-style: dashed; text-decoration-style: dashed;
text-underline-position: under; text-underline-position: under;
} }
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
.magicbox { .magicbox {
margin: 0 auto; margin: 0 auto;
margin-top: 5px; margin-top: 5px;
@@ -7,16 +7,14 @@
<el-table-column align="center" prop="productA" label="到账金额"></el-table-column> <el-table-column align="center" prop="productA" label="到账金额"></el-table-column>
<el-table-column align="center" label="状态"> <el-table-column align="center" label="状态">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.status" v-model="scope.row.status"
:active-value="'0'" active-value="0"
:inactive-value="'1'" inactive-value="1"
active-color="#457450" active-text="启用"
inactive-color="#a4485a" inactive-text="禁用"
inactive-text="启用"
active-text="禁用"
@change="handleChangeType(scope.row)" @change="handleChangeType(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="操作"> <el-table-column align="center" label="操作">
@@ -208,68 +206,4 @@ export default {
padding: 20px; padding: 20px;
} }
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
</style> </style>
+44 -79
View File
@@ -11,30 +11,26 @@
<el-table-column align="center" prop="sort" label="图片排序"></el-table-column> <el-table-column align="center" prop="sort" label="图片排序"></el-table-column>
<el-table-column align="center" prop="status" label="状态"> <el-table-column align="center" prop="status" label="状态">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.status" v-model="scope.row.status"
:active-value="'0'" active-value="0"
:inactive-value="'1'" inactive-value="1"
inactive-color="#a4485a" active-text="启用"
active-color="#457450" inactive-text="禁用"
active-text="禁用"
inactive-text="启用"
@change="updateUserStatus(scope.row)" @change="updateUserStatus(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" prop="isDefault" label="是否默认"> <el-table-column align="center" prop="isDefault" label="是否默认">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.isDefault" v-model="scope.row.isDefault"
:active-value="'0'" active-value="1"
:inactive-value="'1'" inactive-value="0"
active-color="#a4485a"
inactive-color="#457450"
active-text="是" active-text="是"
inactive-text="否" inactive-text="否"
@change="updateUserStatus(scope.row)" @change="updateDefault(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" prop="sort" label="操作"> <el-table-column align="center" prop="sort" label="操作">
@@ -137,6 +133,38 @@ export default {
this.getList(); this.getList();
}); });
}, },
/**
* 「是否默认」单独处理
* 默认头像全表只能有一条(用于新用户注册时分配初始头像),后端已做互斥:
* 置为「是」会自动把其他记录的默认标记清掉。这里只负责在开启时给出提示,
* 让运营知道「设了这条,原来那条就不默认了」,避免误以为是叠加。
*/
updateDefault(res) {
if (res.isDefault !== "1") {
// 关闭某条的默认(后端会把该条置否);若全表因此没有默认头像,提示一句
this.updateUserStatus(res);
return;
}
const prev = this.tableData.find(
row => row.id !== res.id && row.isDefault === "1"
);
const msg = prev
? `已将「${res.id}」设为默认头像,原默认头像「${prev.id}」将自动取消默认。`
: `已将「${res.id}」设为默认头像。默认头像全站只会有一个。`;
this.$confirm(msg, "设置默认头像", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
this.updateUserStatus(res);
})
.catch(() => {
// 取消 → 回滚开关视觉状态并重新拉取,保证界面与库一致
res.isDefault = "0";
this.getList();
});
},
handleSubmits() { handleSubmits() {
changheuserAvatar(this.form).then(res => { changheuserAvatar(this.form).then(res => {
this.dialogFormVisible = false; this.dialogFormVisible = false;
@@ -177,68 +205,5 @@ export default {
.home { .home {
padding: 20px; padding: 20px;
} }
::v-deep .el-switch { /* 开关统一使用全局组件 CapsuleSwitch,此处不再覆盖 el-switch 样式 */
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
</style> </style>
+7 -72
View File
@@ -35,16 +35,14 @@
</el-table-column> </el-table-column>
<el-table-column align="center" prop="status" label="状态"> <el-table-column align="center" prop="status" label="状态">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.status" v-model="scope.row.status"
:active-value="'0'" active-value="0"
:inactive-value="'1'" inactive-value="1"
active-color="#457450" active-text="启用"
inactive-color="#a4485a" inactive-text="禁用"
inactive-text="启用"
active-text="禁用"
@change="handleChangeType(scope.row)" @change="handleChangeType(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" prop="sort" label="图片排序"> <el-table-column align="center" prop="sort" label="图片排序">
@@ -238,68 +236,5 @@ export default {
text-underline-position: under; text-underline-position: under;
} }
::v-deep .el-switch { /* 开关统一使用全局组件 CapsuleSwitch,此处不再覆盖 el-switch 样式 */
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
</style> </style>
@@ -81,16 +81,14 @@
<el-table-column label="浏览量" align="center" prop="pageView" /> <el-table-column label="浏览量" align="center" prop="pageView" />
<el-table-column label="状态" align="center" prop="status"> <el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.status" v-model="scope.row.status"
:active-value="'0'" active-value="0"
:inactive-value="'1'" inactive-value="1"
inactive-color="#a4485a" active-text="启用"
active-color="#457450" inactive-text="禁用"
active-text="禁用"
inactive-text="启用"
@change="updateUserStatus(scope.row)" @change="updateUserStatus(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column width="240" label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column width="240" label="操作" align="center" class-name="small-padding fixed-width">
@@ -361,69 +359,3 @@ export default {
} }
}; };
</script> </script>
<style scoped lang="scss">
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
</style>
@@ -87,16 +87,14 @@
<el-table-column label="备注" align="center" prop="remark" /> <el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="状态" align="center" prop="status"> <el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.status" v-model="scope.row.status"
:active-value="'0'" active-value="0"
:inactive-value="'1'" inactive-value="1"
inactive-color="#a4485a" active-text="启用"
active-color="#457450" inactive-text="禁用"
active-text="禁用"
inactive-text="启用"
@change="updateUserStatus(scope.row)" @change="updateUserStatus(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="创建时间" prop="createTime"></el-table-column> <el-table-column label="创建时间" prop="createTime"></el-table-column>
@@ -335,70 +333,3 @@ export default {
} }
}; };
</script> </script>
<style lang="scss" scoped>
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
</style>
@@ -175,68 +175,4 @@ export default {
.home { .home {
padding: 20px; padding: 20px;
} }
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
</style> </style>
+4 -70
View File
@@ -244,16 +244,14 @@
</el-table-column> </el-table-column>
<el-table-column label="状态" align="center" prop="status"> <el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.status" v-model="scope.row.status"
:active-value="'1'" active-value="0"
:inactive-value="'0'" inactive-value="1"
active-color="#a4485a"
inactive-color="#457450"
active-text="启用" active-text="启用"
inactive-text="禁用" inactive-text="禁用"
@change="handleChangeType(scope.row)" @change="handleChangeType(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column width="190" label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column width="190" label="操作" align="center" class-name="small-padding fixed-width">
@@ -904,70 +902,6 @@ export default {
.wm-hint { color: #7a664e; font-size: 12px; } .wm-hint { color: #7a664e; font-size: 12px; }
.wm-link { margin-left: auto; color: #9a5c35; } .wm-link { margin-left: auto; color: #9a5c35; }
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
#main { #main {
width: 500px; width: 500px;
height: 450px; height: 450px;
@@ -879,50 +879,6 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
top: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
color: rgba(255, 255, 255, 1);
span { font-size: 13px; color: #fff; }
}
::v-deep .el-switch__label.is-active { display: none; }
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
color: rgba(255, 255, 255, 1);
span { font-size: 13px; color: #fff; }
}
::v-deep .under { ::v-deep .under {
text-decoration: underline; text-decoration: underline;
text-decoration-style: dashed; text-decoration-style: dashed;
+6 -72
View File
@@ -72,16 +72,14 @@
</el-table-column> </el-table-column>
<el-table-column align="center" prop="useStatus" label="是否启用"> <el-table-column align="center" prop="useStatus" label="是否启用">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.useStatus" v-model="scope.row.useStatus"
:active-value="'0'" active-value="1"
:inactive-value="'1'" inactive-value="0"
inactive-color="#a4485a" active-text="启用"
active-color="#457450" inactive-text="禁用"
inactive-text="启用"
active-text="禁用"
@change="updateUserStatus(scope.row)" @change="updateUserStatus(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" prop="description" label="红包描述" width="300" show-overflow-tooltip></el-table-column> <el-table-column align="center" prop="description" label="红包描述" width="300" show-overflow-tooltip></el-table-column>
@@ -573,70 +571,6 @@ export default {
justify-content: center; justify-content: center;
line-height: 1; line-height: 1;
} }
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
.kami { .kami {
padding: 15px; padding: 15px;
background-color: #d8c3b0; background-color: #d8c3b0;
+15 -85
View File
@@ -152,44 +152,38 @@
<el-table-column label="账户积分" align="center" prop="accountCredits" /> <el-table-column label="账户积分" align="center" prop="accountCredits" />
<el-table-column label="帐号状态" align="center" prop="status" class="kg"> <el-table-column label="帐号状态" align="center" prop="status" class="kg">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.status" v-model="scope.row.status"
:active-value="'1'" active-value="0"
:inactive-value="'0'" inactive-value="1"
active-color="#a4485a" active-text="启用"
inactive-color="#457450"
active-text="正常"
inactive-text="停用" inactive-text="停用"
@change="updateUserStatus(scope.row)" @change="updateUserStatus(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="提货状态" align="center" prop="deliveryStatus" class="kg"> <el-table-column label="提货状态" align="center" prop="deliveryStatus" class="kg">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.deliveryStatus" v-model="scope.row.deliveryStatus"
:active-value="'1'" active-value="0"
:inactive-value="'0'" inactive-value="1"
active-color="#a4485a"
inactive-color="#457450"
active-text="启用" active-text="启用"
inactive-text="禁用" inactive-text="停用"
@change="updateUserStatus(scope.row)" @change="updateUserStatus(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="赠送状态" align="center" prop="giftStatus" class="kg"> <el-table-column label="赠送状态" align="center" prop="giftStatus" class="kg">
<template slot-scope="scope"> <template slot-scope="scope">
<el-switch <capsule-switch
v-model="scope.row.giftStatus" v-model="scope.row.giftStatus"
:active-value="'1'" active-value="0"
:inactive-value="'0'" inactive-value="1"
active-color="#a4485a"
inactive-color="#457450"
active-text="启用" active-text="启用"
inactive-text="禁用" inactive-text="停用"
@change="updateUserStatus(scope.row)" @change="updateUserStatus(scope.row)"
></el-switch> />
</template> </template>
</el-table-column> </el-table-column>
<!-- 推广等级已按要求隐藏(列表与详情都不再展示);最后登录时间移入详情 --> <!-- 推广等级已按要求隐藏(列表与详情都不再展示);最后登录时间移入详情 -->
@@ -905,70 +899,6 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
#main { #main {
width: 500px; width: 500px;
height: 400px; height: 400px;
@@ -323,69 +323,3 @@ export default {
} }
}; };
</script> </script>
<style lang="scss" scoped>
::v-deep .el-switch {
border-radius: 0px !important;
height: 24px !important;
}
::v-deep .el-switch__core {
width: 70px !important;
height: 24px;
border-radius: 3px;
border: none;
}
::v-deep .el-switch__core::after {
width: 50%;
height: 24px;
border-radius: 0px;
top: 0px !important;
right: 0px !important;
left: 0px;
border: 1px solid #ccc;
border-radius: 3px;
}
::v-deep .el-switch.is-checked .el-switch__core::after {
margin-left: -35px;
box-sizing: border-box;
top: 0px !important;
right: 0px !important;
border: 1px solid #ccc;
border-radius: 3px;
text-align: center;
}
/*关闭时文字位置设置*/
::v-deep .el-switch__label--right {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
right: 6px;
margin-left: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
/* 激活时另一个文字消失 */
::v-deep .el-switch__label.is-active {
text-align: center;
display: none;
}
/*开启时文字位置设置*/
::v-deep .el-switch__label--left {
text-align: center;
position: absolute;
width: 40%;
z-index: 1;
left: 5px;
margin-right: 0px;
color: rgba(255, 255, 255, 1);
span {
font-size: 13px;
box-sizing: border-box;
color: #fff;
}
}
</style>
+4 -1
View File
@@ -101,7 +101,10 @@
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色编号" prop="roleId" width="120" /> <el-table-column label="角色编号" prop="roleId" width="120" />
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" /> <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
<el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="150" /> <!-- ★ min-width 而不是 width:本表 8 列原全为固定宽(合计 1075px),
没有弹性列时 Element 不会拉伸(table-layout.js:182-194),右侧会空出约 500px。
权限字符是本表最该吃剩余宽度的列(内容最长且已带 show-overflow-tooltip,加宽无副作用)。 -->
<el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" min-width="150" />
<el-table-column label="显示顺序" prop="roleSort" width="100" /> <el-table-column label="显示顺序" prop="roleSort" width="100" />
<el-table-column label="状态" align="center" width="100"> <el-table-column label="状态" align="center" width="100">
<template slot-scope="scope"> <template slot-scope="scope">
@@ -48,7 +48,8 @@ public class TtUserAvatarController extends BaseController {
public AjaxResult add(@RequestBody TtUserAvatar ttUserAvatar) { public AjaxResult add(@RequestBody TtUserAvatar ttUserAvatar) {
if (StringUtils.isEmpty(ttUserAvatar.getAvatar())) ttUserAvatar.setAvatar(""); if (StringUtils.isEmpty(ttUserAvatar.getAvatar())) ttUserAvatar.setAvatar("");
else ttUserAvatar.setAvatar(RuoYiConfig.getDomainName() + ttUserAvatar.getAvatar()); else ttUserAvatar.setAvatar(RuoYiConfig.getDomainName() + ttUserAvatar.getAvatar());
return toAjax(userAvatarService.save(ttUserAvatar)); // 走 service 的新增(内含「是否默认」互斥:置为默认时自动清掉其他记录的默认标记)
return toAjax(userAvatarService.saveAvatar(ttUserAvatar));
} }
@Log(title = "头像设置", action = "修改", businessType = BusinessType.UPDATE, @Log(title = "头像设置", action = "修改", businessType = BusinessType.UPDATE,
@@ -6,4 +6,17 @@ import com.ruoyi.domain.other.TtUserAvatar;
public interface TtUserAvatarService extends IService<TtUserAvatar> { public interface TtUserAvatarService extends IService<TtUserAvatar> {
String updateUserAvatarById(TtUserAvatar ttUserAvatar); String updateUserAvatarById(TtUserAvatar ttUserAvatar);
/**
* 新增头像(含「是否默认」互斥处理:置为默认时自动清掉其他记录的默认标记)。
*/
boolean saveAvatar(TtUserAvatar ttUserAvatar);
/**
* 取当前唯一的默认头像(is_default='1' 且 status='0'),没有则返回 null。
* <p>
* ⚠️ 修复「多默认」之前,各消费点是用 list() + RandomUtil.choice() 随机挑一个的,
* 那是在「可能有多条默认」的既成事实下打的补丁;现在约束唯一后应统一走本方法。
*/
TtUserAvatar getDefaultAvatar();
} }
@@ -1,20 +1,78 @@
package com.ruoyi.admin.service.impl; package com.ruoyi.admin.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.domain.other.TtUserAvatar; import com.ruoyi.domain.other.TtUserAvatar;
import com.ruoyi.admin.mapper.TtUserAvatarMapper; import com.ruoyi.admin.mapper.TtUserAvatarMapper;
import com.ruoyi.admin.service.TtUserAvatarService; import com.ruoyi.admin.service.TtUserAvatarService;
import com.ruoyi.common.config.RuoYiConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 用户头像库
* <p>
* 【默认头像的唯一性约束 —— 2026-09-24】
* 默认头像用于给新注册 / 白号 / 推广号分配初始头像(见 ApiRegisterServiceImpl、
* ApiLoginServiceImpl、ApiUserServiceImpl、PromoLoginServiceImpl 四处消费点),
* 语义上必须**全表最多只有一条 is_default='1'**。
* <p>
* 原实现只做 updateById,导致「把 A 设为默认」时 B/C/D 的 is_default 仍为 '1',
* 库里一度出现 7 条默认(id 63~69),并且部分消费点用的是 RandomUtil.choice(...),
* 会随机挑一条 ⇒ 默认头像实际不唯一、每次注册拿到的头像都不同。
* <p>
* ⇒ 现在把「互斥」收敛到 service 层:凡是把某条写成 is_default='1' 的写操作,
* 都先把其余记录的 is_default 清成 '0',再落当前这条;两步同一事务。
*/
@Service @Service
public class TtUserAvatarServiceImpl extends ServiceImpl<TtUserAvatarMapper, TtUserAvatar> implements TtUserAvatarService { public class TtUserAvatarServiceImpl extends ServiceImpl<TtUserAvatarMapper, TtUserAvatar> implements TtUserAvatarService {
/** 是 / 否 —— 与 TtUserAvatar#isDefault 的字典口径一致(@Excel readConverterExp = "0=否,1=是") */
private static final String YES = "1";
private static final String NO = "0";
@Override @Override
@Transactional(rollbackFor = Exception.class)
public String updateUserAvatarById(TtUserAvatar ttUserAvatar) { public String updateUserAvatarById(TtUserAvatar ttUserAvatar) {
String avatar = ttUserAvatar.getAvatar(); // 头像字段允许为空(后端会拼域名),此处不再做无意义的 setAvatar(avatar)
ttUserAvatar.setAvatar(avatar); clearOtherDefaultsIfNeeded(ttUserAvatar);
this.updateById(ttUserAvatar); this.updateById(ttUserAvatar);
return ""; return "";
} }
/**
* 新增头像:应用同一套唯一性约束。
* 原实现直接 save,新增一条 is_default='1' 就会叠加出第二个默认。
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveAvatar(TtUserAvatar ttUserAvatar) {
clearOtherDefaultsIfNeeded(ttUserAvatar);
return this.save(ttUserAvatar);
}
/**
* 若本次写入把该条置为「是默认」,则把其余记录的 is_default 全部清成「否」。
* 只在确实要置为默认时执行 UPDATE,避免每次普通编辑都白扫一次全表。
*/
private void clearOtherDefaultsIfNeeded(TtUserAvatar ttUserAvatar) {
if (ttUserAvatar == null || !YES.equals(ttUserAvatar.getIsDefault())) return;
LambdaUpdateWrapper<TtUserAvatar> wrapper = new LambdaUpdateWrapper<TtUserAvatar>()
.set(TtUserAvatar::getIsDefault, NO)
.eq(TtUserAvatar::getIsDefault, YES);
// 编辑场景排除自身,避免刚 set 完又被自己这条排除掉(新增时 id 为 null,不需要排除)
if (ttUserAvatar.getId() != null) {
wrapper.ne(TtUserAvatar::getId, ttUserAvatar.getId());
}
this.update(wrapper);
}
@Override
public TtUserAvatar getDefaultAvatar() {
return this.getOne(new LambdaQueryWrapper<TtUserAvatar>()
.eq(TtUserAvatar::getIsDefault, YES)
.eq(TtUserAvatar::getStatus, "0")
.orderByAsc(TtUserAvatar::getId)
.last("limit 1"));
}
} }