修改河南项目的管理后台ui和删除之前不需要的游戏
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
# 数据库变更台账
|
||||
|
||||
> **数据库改动只写这里**:中文标题 + 完整可执行 SQL。不再单独建 `docs/*.sql`(全量基线除外)。
|
||||
> 每条都要能回得去 —— **回滚方案与验证步骤必写**。
|
||||
|
||||
---
|
||||
|
||||
## 删除「奥林匹斯 / 荒野争锋 / 极速糖果」三个玩法的后台菜单
|
||||
|
||||
**背景**:这三个玩法不再需要,要求从后台左侧菜单移除。
|
||||
|
||||
**为什么先动数据库**:RuoYi 的左侧菜单是**数据库驱动**的(`sys_menu` → `/getRouters` 接口 → 前端 `sidebarRouters`),
|
||||
代码里没有菜单清单。所以顺序是:**先删数据,再删前端页面文件**。
|
||||
|
||||
**影响面**(实查本机库 `henan`,非估算):
|
||||
|
||||
| 表 | 行数 | 说明 |
|
||||
|---|---|---|
|
||||
| `sys_menu` | **22** | 3 个顶级菜单(`menu_type='M'`)+ 19 个子页面(`'C'`);**无** `'F'` 按钮权限行 |
|
||||
| `sys_role_menu` | **22** | 全部属于 `role_id = 103` 这一个角色 |
|
||||
|
||||
| 顶级 menu_id | 菜单 | path | 子菜单 | 子菜单 menu_id | 前端 component |
|
||||
|---|---|---|---|---|---|
|
||||
| 3130 | 奥林匹斯 | `olympus` | 7 个 | 3131~3137 | `game/olympus/*` |
|
||||
| 2105 | 荒野争锋 | `bullet` | 6 个 | 2106~2110、2112 | `game/bullet/*` |
|
||||
| 2125 | 极速糖果 | `sweet` | 6 个 | 2126~2131 | `game/sweet/*` |
|
||||
|
||||
⚠️ **`2111`(宝箱管理→临时倍率)不在删除范围内** —— 它虽然编号紧邻 `2110`,但父级是 `2018`(宝箱管理),
|
||||
且其 component 是 `game/box/mock/index`(被 `3141` 经典开箱的「临时爆率」也在用),**不要动**。
|
||||
|
||||
**变更 SQL**(2026-09-22 已在库 `henan` 执行):
|
||||
|
||||
```sql
|
||||
-- 1) 先清角色-菜单授权,避免留下孤儿行
|
||||
DELETE FROM sys_role_menu
|
||||
WHERE menu_id IN (3130,3131,3132,3133,3134,3135,3136,3137,2105,2106,2107,2108,2109,2110,2112,2125,2126,2127,2128,2129,2130,2131);
|
||||
|
||||
-- 2) 再删菜单本身
|
||||
DELETE FROM sys_menu
|
||||
WHERE menu_id IN (3130,3131,3132,3133,3134,3135,3136,3137,2105,2106,2107,2108,2109,2110,2112,2125,2126,2127,2128,2129,2130,2131);
|
||||
```
|
||||
|
||||
**验证**(执行后实测):
|
||||
|
||||
```sql
|
||||
-- 两个都必须是 0
|
||||
SELECT COUNT(*) FROM sys_menu
|
||||
WHERE menu_id IN (3130,3131,3132,3133,3134,3135,3136,3137,2105,2106,2107,2108,2109,2110,2112,2125,2126,2127,2128,2129,2130,2131)
|
||||
OR parent_id IN (3130, 2105, 2125); -- 实测 0
|
||||
|
||||
SELECT COUNT(*) FROM sys_role_menu
|
||||
WHERE menu_id IN (3130,3131,3132,3133,3134,3135,3136,3137,2105,2106,2107,2108,2109,2110,2112,2125,2126,2127,2128,2129,2130,2131); -- 实测 0
|
||||
```
|
||||
|
||||
一级菜单复查(`SELECT menu_id, menu_name, path FROM sys_menu WHERE parent_id = 0 ORDER BY order_num, menu_id`)
|
||||
实测剩余 20 项,已无「奥林匹斯 / 荒野争锋 / 极速糖果」。
|
||||
|
||||
**回滚 SQL**(22 条 `sys_menu` + 22 条 `sys_role_menu`,由 `mysqldump --no-create-info --complete-insert` 导出):
|
||||
|
||||
```sql
|
||||
-- 事务包裹更安全;menu_id 是显式指定,不需要自增
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2105,'荒野争锋',0,7,'bullet',NULL,NULL,1,0,'M','0','0','','excel','admin','2026-05-14 14:36:15','admin','2026-08-01 13:37:13','');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2106,'饰品列表',2105,1,'ornament','game/bullet/ornament/index',NULL,1,0,'C','0','0','game:bullet:ornament:list','button','admin','2026-05-14 14:49:48','admin','2026-05-14 14:55:16','');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2107,'箱子管理',2105,2,'box','game/bullet/box/index',NULL,1,0,'C','0','0','game:bullet:box:list','bug','admin','2026-05-14 14:56:11','',NULL,'');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2108,'回合管理',2105,3,'round','game/bullet/round/index',NULL,1,0,'C','0','0','game:bullet:round:list','build','admin','2026-05-14 14:56:49','',NULL,'');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2109,'玩家记录',2105,4,'record','game/bullet/record/index',NULL,1,0,'C','0','0','game:bullet:record:list','button','admin','2026-05-14 14:57:29','',NULL,'');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2110,'每日统计',2105,5,'dailywin','game/bullet/dailywin/index',NULL,1,0,'C','0','0','game:bullet:dailywin:list','chart','admin','2026-05-14 14:58:07','',NULL,'');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2112,'奖品管理',2105,6,'prize','game/bullet/prize/index',NULL,1,0,'C','0','0','game:bullet:prize:list','component','admin','2026-05-26 04:47:02','admin','2026-05-26 05:34:40','');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2125,'极速糖果',0,7,'sweet',NULL,NULL,1,0,'M','0','0','','star','admin','2026-06-18 10:15:36','admin','2026-08-01 13:29:08','');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2126,'箱子管理',2125,2,' box','game/sweet/box/index',NULL,1,0,'C','0','0','game:sweet:box:list','404','admin','2026-06-18 10:17:18','admin','2026-06-18 14:34:47','');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2127,'饰品列表',2125,1,'ornament','game/sweet/ornament/index',NULL,1,0,'C','0','0','game:sweet:ornament:list','bug','admin','2026-06-18 10:18:01','admin','2026-06-18 14:34:29','');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2128,'奖品管理',2125,6,'prize','game/sweet/prize/index',NULL,1,0,'C','0','0','game:sweet:prize:list','build','admin','2026-06-18 10:18:41','admin','2026-06-18 14:35:55','');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2129,'每日统计',2125,5,'dailywin','game/sweet/dailywin/index',NULL,1,0,'C','0','0','game:sweet:dailywin:list','button','admin','2026-06-18 10:19:39','admin','2026-06-18 14:35:39','');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2130,'回合管理',2125,3,'round','game/sweet/round/index',NULL,1,0,'C','0','0','game:sweet:round:list','cascader','admin','2026-06-18 10:20:13','admin','2026-06-18 14:35:03','');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2131,'玩家记录',2125,4,'record','game/sweet/record/index',NULL,1,0,'C','0','0','game:sweet:record:list','cascader','admin','2026-06-18 10:21:20','admin','2026-06-18 14:35:17','');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3130,'奥林匹斯',0,5,'olympus','',NULL,1,0,'M','0','0','','star','admin','2026-08-01 13:41:53','',NULL,'奥林匹斯电子游戏管理');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3131,'箱子配置',3130,1,'box','game/olympus/box/index',NULL,1,0,'C','0','0','game:olympus:box','shopping','admin','2026-08-01 13:41:53','',NULL,'奥林匹斯箱子配置与身份桶分布');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3132,'饰品列表',3130,2,'ornament','game/olympus/ornament/index',NULL,1,0,'C','0','0','game:olympus:ornament','star','admin','2026-08-01 13:41:53','',NULL,'奥林匹斯 17 档饰品桶定义');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3133,'回合管理',3130,3,'round','game/olympus/round/index',NULL,1,0,'C','0','0','game:olympus:round','documentation','admin','2026-08-01 13:41:53','',NULL,'奥林匹斯预生成回合查询');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3134,'奖池水位',3130,4,'rewardPool','game/olympus/rewardPool/index',NULL,1,0,'C','0','0','game:olympus:pool','money','admin','2026-08-01 13:41:53','',NULL,'奥林匹斯按箱奖池水位(注水/抽水/批量/重置)');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3135,'玩家记录',3130,5,'record','game/olympus/record/index',NULL,1,0,'C','0','0','game:olympus:record','list','admin','2026-08-01 13:41:53','',NULL,'奥林匹斯游戏记录');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3136,'每日统计',3130,6,'dailywin','game/olympus/dailywin/index',NULL,1,0,'C','0','0','game:olympus:win','chart','admin','2026-08-01 13:41:53','',NULL,'奥林匹斯每日盈亏统计');
|
||||
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3137,'奖品管理',3130,7,'prize','game/olympus/prize/index',NULL,1,0,'C','0','0','game:olympus:prize','guide','admin','2026-08-01 13:41:53','admin','2026-08-04 13:46:18','奥林匹斯返奖饰品与区间配置');
|
||||
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2105);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2106);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2107);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2108);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2109);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2110);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2112);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2125);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2126);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2127);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2128);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2129);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2130);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,2131);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,3130);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,3131);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,3132);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,3133);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,3134);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,3135);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,3136);
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (103,3137);
|
||||
```
|
||||
|
||||
**配套的前端文件删除**(38 个文件,全部在 git 里,`git checkout` 可恢复):
|
||||
|
||||
| 目录 | 文件数 |
|
||||
|---|---|
|
||||
| `ruoyi-ui/src/views/game/olympus/` | 7 |
|
||||
| `ruoyi-ui/src/views/game/bullet/` | 6 |
|
||||
| `ruoyi-ui/src/views/game/sweet/` | 6 |
|
||||
| `ruoyi-ui/src/api/game/olympus/` | 7 |
|
||||
| `ruoyi-ui/src/api/game/bullet/` | 6 |
|
||||
| `ruoyi-ui/src/api/game/sweet/` | 6 |
|
||||
|
||||
**刻意没动**:
|
||||
|
||||
- **后端 Java 代码**。`olympus / bullet / sweet / sugar` 在后端出现在**跨模块共享**的位置 ——
|
||||
`TtAccountRecordSource`(流水来源码:60=奥林匹斯…)、`TtboxRecordSource`、`RedisConstants`、
|
||||
`MessageCategoryEnum`、`PromoTurnover*`、`ApiUserPackSackServiceImpl`、`WebsiteSetupServiceImpl` 等。
|
||||
删这些会波及**其他玩法与推广/发货链路**,不在「删页面」范围内。
|
||||
- **数据库业务表**(`game_olympus_*`、`game_sweet_*` 等)原样保留,未 TRUNCATE、未 DROP。
|
||||
- `ruoyi-ui/src/views/game/sugar/`(5 个 api + 5 个页面)—— **没有任何菜单引用它**:
|
||||
菜单里的「极速糖果」指向的是 `game/sweet/*`,而 `game_sugar_*` 表在 `docs/cleanup_records.sql` 里被标注为「极速糖果」。
|
||||
即 `sugar` 与 `sweet` 是两套并存的东西,`sugar` 未被任何菜单挂载(后端却存在 `GameSugarWin/User/StepInfo/Spin` 实体)。
|
||||
**疑似遗留或未接入的开发分支,未确认前不删。**
|
||||
|
||||
@@ -30,3 +30,32 @@
|
||||
**上线前必做两件事**:① 卷是 `external: true`,**必须先 `docker volume create henan_mysql_data`**,否则报 `external volume not found` 直接起不来(`deploy.sh up -d` 会自动建,手敲 `docker-compose up` 不会);② 新卷首次初始化会建出**空**的 `henan` 库 —— `MYSQL_DATABASE` / `MYSQL_USER` / `MYSQL_PASSWORD` **只在数据卷首次初始化时生效**,原有数据需另行导入,否则应用连上的是空库。
|
||||
|
||||
**遗留待定**:网络没固定 `name:`,实际名会随执行目录变(在 `docker/compose` 下执行时是 `compose_henan_app_network`)。卷已用 `external` + 固定 `name` 规避「换目录丢数据」,网络是否同样钉死成 `henan_app_network` 待定。
|
||||
|
||||
- **下架三个玩法:删除「奥林匹斯 / 荒野争锋 / 极速糖果」的后台菜单与页面**
|
||||
|
||||
**起因**:这三个玩法不再需要,要求从后台左侧菜单移除。
|
||||
|
||||
**关键前提**:RuoYi 的左侧菜单是**数据库驱动**的(`sys_menu` → `/getRouters` → 前端 `sidebarRouters`),**代码里没有菜单清单**,所以必须"先删数据、再删页面文件",只删前端文件的话菜单依然在。
|
||||
|
||||
**改动清单**(完整 SQL 与回滚语句见 `docs/updata_sql.md`):
|
||||
|
||||
1. **数据库**(库 `henan`,已执行):`sys_menu` 删 **22 行**(3 个顶级 M + 19 个子页面 C);`sys_role_menu` 删 **22 行**授权(全属 `role_id=103`)。**无 `'F'` 按钮权限行**。
|
||||
- 3130 奥林匹斯 `olympus` → 子菜单 3131~3137
|
||||
- 2105 荒野争锋 `bullet` → 子菜单 2106~2110、2112
|
||||
- 2125 极速糖果 `sweet` → 子菜单 2126~2131
|
||||
2. **前端文件**(38 个,删前已确认全部被 git 跟踪):`ruoyi-ui/src/views/game/{olympus,bullet,sweet}/`(7+6+6)与 `ruoyi-ui/src/api/game/{olympus,bullet,sweet}/`(7+6+6)。
|
||||
3. **文档**:本条 + `docs/updata_sql.md` 新条目(含 44 条回滚 INSERT)。
|
||||
|
||||
**验证**:
|
||||
|
||||
- 删除后 `sys_menu` / `sys_role_menu` 命中行数**均为 0**(`printf` 实测);`SELECT ... WHERE parent_id = 0` 复查剩余 20 个一级菜单,三个玩法已消失。
|
||||
- 全量搜索 `game/olympus|game/bullet|game/sweet|api/game/...` 残留引用 **= 0**。
|
||||
- `ruoyi-ui` 生产构建 **`exitcode=0` / `DONE Build complete`**(删掉页面后仍能编译,说明没有别处静态引用它们)。
|
||||
|
||||
**刻意没动**:
|
||||
|
||||
1. **后端 Java 代码**。这些玩法名在后端出现在**跨模块共享**的文件里 —— `TtAccountRecordSource`(流水来源码 60=奥林匹斯)、`TtboxRecordSource`、`RedisConstants`、`MessageCategoryEnum`、`PromoTurnover*`、`ApiUserPackSackServiceImpl`、`WebsiteSetupServiceImpl`。删它们会波及**其他玩法与推广/发货链路**,不在「删页面」范围内。
|
||||
2. **数据库业务表**:`game_olympus_*` / `game_sweet_*` 等原样保留,未 TRUNCATE、未 DROP。数据还在,随时可回。
|
||||
3. **`ruoyi-ui/src/views/game/sugar/`**(5 api + 5 页面)—— **没有任何菜单引用**。菜单「极速糖果」指向的是 `game/sweet/*`,而 `docs/cleanup_records.sql` 又把 `game_sugar_*` 标注为「极速糖果」,两套并存;后端还存在 `GameSugarWin/User/StepInfo/Spin` 实体。**疑似遗留或未接入的开发分支,未确认前不删。**
|
||||
|
||||
**遗留待定**:① 仪表盘「实时 RTP」卡片与「流水统计」折线里仍有 **玫瑰女郎(=olympus)** 一条 —— 那是后端 `/getRtpData` 返回的玩法列表,**与菜单无关**,删菜单不会让它消失;若要一并去掉得动后端。② `game/sugar/*` 是否清理待定。③ 是否需要把 `game_olympus_*` / `game_sweet_*` 业务表也清掉(现在只是入口没了,数据仍在库里占空间)。
|
||||
|
||||
@@ -177,7 +177,7 @@ const clear = () => {
|
||||
<ColorRadioPicker
|
||||
v-model="systemTheme"
|
||||
:schema="[
|
||||
'#409eff',
|
||||
'#9a5c35',
|
||||
'#009688',
|
||||
'#536dfe',
|
||||
'#ff5c93',
|
||||
@@ -212,7 +212,7 @@ const clear = () => {
|
||||
v-model="menuTheme"
|
||||
:schema="[
|
||||
'#fff',
|
||||
'#001529',
|
||||
'#faf6f0',
|
||||
'#212121',
|
||||
'#273352',
|
||||
'#191b24',
|
||||
|
||||
@@ -64,34 +64,38 @@ export const useAppStore = defineStore('app', {
|
||||
isDark: false, // 是否是暗黑模式
|
||||
currentSize: 'default', // 组件尺寸
|
||||
theme: {
|
||||
// 主题色
|
||||
elColorPrimary: '#409eff',
|
||||
/* Cottagecore(田园核风)
|
||||
⚠️ 本 store 是 persist: true,老用户 localStorage 里的 'app' 会覆盖这里的默认值。
|
||||
换肤后如果看到旧配色,清掉 localStorage 的 'app' 键(或在设置抽屉里重新选一次色)即可。
|
||||
色值与 src/styles/var.css 保持一致,两处要一起改。 */
|
||||
// 主题色(陶土棕 · 与 success 草绿拉开色相,避免新增/修改按钮同色)
|
||||
elColorPrimary: '#9a5c35',
|
||||
// 左侧菜单边框颜色
|
||||
leftMenuBorderColor: 'inherit',
|
||||
// 左侧菜单背景颜色
|
||||
leftMenuBgColor: '#001529',
|
||||
// 左侧菜单背景颜色(奶油)
|
||||
leftMenuBgColor: '#faf6f0',
|
||||
// 左侧菜单浅色背景颜色
|
||||
leftMenuBgLightColor: '#0f2438',
|
||||
// 左侧菜单选中背景颜色
|
||||
leftMenuBgActiveColor: 'var(--el-color-primary)',
|
||||
leftMenuBgLightColor: '#f6efe3',
|
||||
// 左侧菜单选中背景颜色(陶土浅色,不用实心,免得整栏太重)
|
||||
leftMenuBgActiveColor: 'rgba(154, 92, 53, 0.16)',
|
||||
// 左侧菜单收起选中背景颜色
|
||||
leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',
|
||||
leftMenuCollapseBgActiveColor: 'rgba(154, 92, 53, 0.16)',
|
||||
// 左侧菜单字体颜色
|
||||
leftMenuTextColor: '#bfcbd9',
|
||||
leftMenuTextColor: '#5f4f3d',
|
||||
// 左侧菜单选中字体颜色
|
||||
leftMenuTextActiveColor: '#fff',
|
||||
leftMenuTextActiveColor: '#9a5c35',
|
||||
// logo字体颜色
|
||||
logoTitleTextColor: '#fff',
|
||||
logoTitleTextColor: '#4f4130',
|
||||
// logo边框颜色
|
||||
logoBorderColor: 'inherit',
|
||||
// 头部背景颜色
|
||||
topHeaderBgColor: '#fff',
|
||||
topHeaderBgColor: '#faf6f0',
|
||||
// 头部字体颜色
|
||||
topHeaderTextColor: 'inherit',
|
||||
// 头部悬停颜色
|
||||
topHeaderHoverColor: '#f6f6f6',
|
||||
topHeaderHoverColor: '#f6efe3',
|
||||
// 头部边框颜色
|
||||
topToolBorderColor: '#eee'
|
||||
topToolBorderColor: '#eadfd0'
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -279,7 +283,7 @@ export const useAppStore = defineStore('app', {
|
||||
const isDarkColor = colorIsDark(color)
|
||||
const theme: Recordable = {
|
||||
// 左侧菜单边框颜色
|
||||
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
|
||||
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eadfd0',
|
||||
// 左侧菜单背景颜色
|
||||
leftMenuBgColor: color,
|
||||
// 左侧菜单浅色背景颜色
|
||||
@@ -293,13 +297,13 @@ export const useAppStore = defineStore('app', {
|
||||
? 'var(--el-color-primary)'
|
||||
: hexToRGB(unref(primaryColor) as string, 0.1),
|
||||
// 左侧菜单字体颜色
|
||||
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
|
||||
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#5f4f3d',
|
||||
// 左侧菜单选中字体颜色
|
||||
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
|
||||
// logo字体颜色
|
||||
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
|
||||
// logo边框颜色
|
||||
logoBorderColor: isDarkColor ? color : '#eee'
|
||||
logoBorderColor: isDarkColor ? color : '#eadfd0'
|
||||
}
|
||||
this.setTheme(theme)
|
||||
this.setCssVarTheme()
|
||||
@@ -308,7 +312,7 @@ export const useAppStore = defineStore('app', {
|
||||
const isDarkColor = colorIsDark(color)
|
||||
const textColor = isDarkColor ? '#fff' : 'inherit'
|
||||
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
|
||||
const topToolBorderColor = isDarkColor ? color : '#eee'
|
||||
const topToolBorderColor = isDarkColor ? color : '#eadfd0'
|
||||
setCssVar('--top-header-bg-color', color)
|
||||
setCssVar('--top-header-text-color', textColor)
|
||||
setCssVar('--top-header-hover-color', textHoverColor)
|
||||
|
||||
@@ -1,36 +1,39 @@
|
||||
:root {
|
||||
--login-bg-color: #293146;
|
||||
/* Cottagecore(田园核风)· 奶油底 + 暖棕字,去掉深蓝冷调 */
|
||||
--login-bg-color: #f3ece0;
|
||||
|
||||
/* left menu start */
|
||||
--left-menu-max-width: 200px;
|
||||
|
||||
--left-menu-min-width: 64px;
|
||||
|
||||
--left-menu-bg-color: #001529;
|
||||
--left-menu-bg-color: #faf6f0;
|
||||
|
||||
--left-menu-bg-light-color: #0f2438;
|
||||
--left-menu-bg-light-color: #f6efe3;
|
||||
|
||||
--left-menu-bg-active-color: var(--el-color-primary);
|
||||
--left-menu-bg-active-color: rgba(154, 92, 53, 0.16);
|
||||
|
||||
--left-menu-text-color: #bfcbd9;
|
||||
--left-menu-text-color: #5f4f3d;
|
||||
|
||||
--left-menu-text-active-color: #fff;
|
||||
--left-menu-text-active-color: #9a5c35;
|
||||
|
||||
--left-menu-collapse-bg-active-color: var(--el-color-primary);
|
||||
--left-menu-collapse-bg-active-color: rgba(154, 92, 53, 0.16);
|
||||
/* left menu end */
|
||||
|
||||
/* logo start */
|
||||
--logo-height: 50px;
|
||||
|
||||
--logo-title-text-color: #fff;
|
||||
--logo-title-text-color: #4f4130;
|
||||
/* logo end */
|
||||
|
||||
/* header start */
|
||||
--top-header-bg-color: '#fff';
|
||||
/* header start
|
||||
注意:原文件这几个值是带引号的字符串('#fff' / 'inherit'),引号会被当成值的一部分,
|
||||
已去掉。 */
|
||||
--top-header-bg-color: #faf6f0;
|
||||
|
||||
--top-header-text-color: 'inherit';
|
||||
--top-header-text-color: inherit;
|
||||
|
||||
--top-header-hover-color: #f6f6f6;
|
||||
--top-header-hover-color: #f6efe3;
|
||||
|
||||
--top-tool-height: var(--logo-height);
|
||||
|
||||
@@ -49,13 +52,65 @@
|
||||
|
||||
--app-content-padding: 20px;
|
||||
|
||||
--app-content-bg-color: #f5f7f9;
|
||||
--app-content-bg-color: #f3ece0;
|
||||
|
||||
--app-footer-height: 50px;
|
||||
|
||||
--transition-time-02: 0.2s;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Element Plus 设计令牌 · Cottagecore
|
||||
用 html:root:not(.dark) 把特异性抬到 (0,2,1):
|
||||
1) 确保压得住后加载的 element-plus 默认 :root;
|
||||
2) 只在浅色模式生效,`.dark` 下交还给 element-plus 的 dark/css-vars,
|
||||
否则暗色模式会出现「奶油底 + 浅色字」不可读。
|
||||
========================================================================== */
|
||||
html:root:not(.dark) {
|
||||
--el-color-primary: #9a5c35; /* 陶土棕 · 白字 5.31:1 */
|
||||
--el-color-success: #457450; /* 草绿 */
|
||||
--el-color-warning: #8f6418; /* 深雏菊金 */
|
||||
--el-color-danger: #a4485a; /* 莓红 */
|
||||
--el-color-info: #6b6152; /* 暖褐灰 */
|
||||
|
||||
--el-text-color-primary: #4f4130;
|
||||
--el-text-color-regular: #5f4f3d;
|
||||
--el-text-color-secondary: #7a664e;
|
||||
--el-text-color-placeholder: #9a886e;
|
||||
--el-text-color-disabled: #9a886e;
|
||||
|
||||
--el-border-color: #d8c3b0;
|
||||
--el-border-color-light: #e2d3c2;
|
||||
--el-border-color-lighter: #eadfd0;
|
||||
--el-border-color-extra-light: #f0e8dc;
|
||||
--el-border-color-hover: #c08a8a;
|
||||
|
||||
--el-fill-color: #f6efe3;
|
||||
--el-fill-color-light: #f6efe3;
|
||||
--el-fill-color-lighter: #fbf5ea;
|
||||
--el-fill-color-blank: #faf6f0;
|
||||
--el-bg-color: #faf6f0;
|
||||
--el-bg-color-page: #f3ece0;
|
||||
--el-bg-color-overlay: #fffdfa;
|
||||
|
||||
--el-border-radius-base: 12px;
|
||||
--el-border-radius-small: 10px;
|
||||
--el-border-radius-round: 999px;
|
||||
|
||||
--el-box-shadow-light: 0 2px 10px rgba(139, 115, 85, 0.12);
|
||||
--el-box-shadow: 0 6px 18px rgba(139, 115, 85, 0.13);
|
||||
--el-box-shadow-dark: 0 12px 32px rgba(139, 115, 85, 0.17);
|
||||
}
|
||||
|
||||
/* 标题类走衬线(只在大号字上),正文/表单/表格保持无衬线,
|
||||
理由同 ruoyi-ui:13px 宋体在密集表格里可读性会掉。 */
|
||||
.el-dialog__title,
|
||||
.el-message-box__title,
|
||||
.el-drawer__title {
|
||||
font-family: 'Songti SC', 'Source Han Serif SC', 'Noto Serif SC', STSong, SimSun, Georgia, 'Times New Roman', serif;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--app-content-bg-color: var(--el-bg-color);
|
||||
}
|
||||
|
||||
@@ -392,7 +392,7 @@ const copyText = (text: string) => {
|
||||
.card-code {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-weight: 600;
|
||||
color: #409eff;
|
||||
color: #9a5c35;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="招商收益" width="120">
|
||||
<template #default="{ row }">
|
||||
<span style="color: #409eff; font-weight: 600">{{ formatNum(row.totalCommission) }}</span>
|
||||
<span style="color: #9a5c35; font-weight: 600">{{ formatNum(row.totalCommission) }}</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
@@ -415,7 +415,7 @@ onMounted(() => {
|
||||
}
|
||||
.summary-card {
|
||||
background: #fff;
|
||||
border: 1px solid #409eff;
|
||||
border: 1px solid #9a5c35;
|
||||
border-radius: 4px;
|
||||
padding: 12px 24px;
|
||||
color: #303133;
|
||||
@@ -455,7 +455,7 @@ onMounted(() => {
|
||||
}
|
||||
.daily-card {
|
||||
background: #fff;
|
||||
border: 1px solid #409eff;
|
||||
border: 1px solid #9a5c35;
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
display: flex;
|
||||
@@ -475,7 +475,7 @@ onMounted(() => {
|
||||
}
|
||||
.weekly-total-card {
|
||||
background: #fff;
|
||||
border: 1px solid #409eff;
|
||||
border: 1px solid #9a5c35;
|
||||
border-radius: 6px;
|
||||
padding: 12px 24px;
|
||||
color: #303133;
|
||||
|
||||
@@ -372,10 +372,10 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.rate-card:hover {
|
||||
background-color: #ecf5ff;
|
||||
border-color: #409eff;
|
||||
background-color: #fbf5ea;
|
||||
border-color: #9a5c35;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 2px 12px rgba(64, 158, 255, 0.1);
|
||||
box-shadow: 0 2px 12px rgba(154, 92, 53, 0.10);
|
||||
}
|
||||
|
||||
.rate-card-title {
|
||||
|
||||
@@ -53,16 +53,20 @@ const inviteCode = computed(() => {
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
|
||||
background: radial-gradient(circle at 72% 18%, rgba(92, 86, 176, 0.15), transparent 30%),
|
||||
linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
|
||||
/* 原为 #0f0c29 → #302b63 → #24243e 的紫蓝渐变(规范明令禁止),
|
||||
改成亚麻→奶油暖渐变 + 雏菊黄/草绿柔光斑 */
|
||||
background: radial-gradient(circle at 72% 18%, rgba(245, 215, 95, 0.24), transparent 34%),
|
||||
radial-gradient(circle at 12% 88%, rgba(90, 143, 90, 0.14), transparent 36%),
|
||||
linear-gradient(135deg, #fdfaf5 0%, #f7f0e4 52%, #f3ece0 100%);
|
||||
}
|
||||
|
||||
.grid-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
background-image: linear-gradient(rgba(255, 255, 255, 0.028) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.028) 1px, transparent 1px);
|
||||
/* 亚麻织纹:暖棕细线,不再是白色科技网格 */
|
||||
background-image: linear-gradient(rgba(139, 115, 85, 0.055) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(139, 115, 85, 0.055) 1px, transparent 1px);
|
||||
|
||||
background-size: 50px 50px;
|
||||
}
|
||||
@@ -89,23 +93,21 @@ const inviteCode = computed(() => {
|
||||
② 受光放在右上角,与背景光斑方向一致
|
||||
③ 投影反方向往左下,棱线只亮右上
|
||||
*/
|
||||
background-color: rgba(126, 132, 240, 0.24);
|
||||
/* 原来是半透明靛蓝玻璃卡 + backdrop-filter;
|
||||
Cottagecore 不把玻璃态当默认风格,改成不透明奶油卡 + 花粉细边 + 暖棕柔影 */
|
||||
background-color: #fffdfa;
|
||||
|
||||
background-image: radial-gradient(
|
||||
130% 110% at 100% 0%,
|
||||
rgba(152, 158, 255, 0.22),
|
||||
rgba(245, 215, 95, 0.20),
|
||||
transparent 62%
|
||||
);
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border: 1px solid rgba(212, 160, 160, 0.45);
|
||||
|
||||
box-shadow:
|
||||
-20px 24px 56px -20px rgba(5, 4, 24, 0.68),
|
||||
inset -1px 1px 0 rgba(255, 255, 255, 0.2),
|
||||
inset 1px -1px 0 rgba(255, 255, 255, 0.02);
|
||||
|
||||
backdrop-filter: blur(24px) saturate(150%);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(150%);
|
||||
0 18px 48px -18px rgba(139, 115, 85, 0.34),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
||||
|
||||
/* =========================
|
||||
标题
|
||||
@@ -124,10 +126,11 @@ const inviteCode = computed(() => {
|
||||
h2 {
|
||||
margin: 0 16px;
|
||||
|
||||
font-family: 'Songti SC', 'Source Han Serif SC', 'Noto Serif SC', STSong, SimSun, Georgia, serif;
|
||||
font-size: 24px;
|
||||
font-weight: 650;
|
||||
|
||||
color: #f7f8ff;
|
||||
color: #4f4130;
|
||||
}
|
||||
|
||||
.title-decoration {
|
||||
@@ -136,10 +139,10 @@ const inviteCode = computed(() => {
|
||||
|
||||
border-radius: 2px;
|
||||
|
||||
background: linear-gradient(90deg, transparent, rgba(184, 194, 232, 0.55));
|
||||
background: linear-gradient(90deg, transparent, rgba(192, 138, 138, 0.8));
|
||||
|
||||
&:last-child {
|
||||
background: linear-gradient(90deg, rgba(184, 194, 232, 0.55), transparent);
|
||||
background: linear-gradient(90deg, rgba(192, 138, 138, 0.8), transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,13 +155,13 @@ const inviteCode = computed(() => {
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
color: #5f4f3d;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
输入框
|
||||
玻璃上的凹槽:比卡片暗,形成内嵌感
|
||||
奶油底上的凹槽:比卡片略深,形成内嵌感
|
||||
不再改内部 Row / Col 结构
|
||||
========================= */
|
||||
:deep(.el-input__wrapper) {
|
||||
@@ -168,11 +171,11 @@ const inviteCode = computed(() => {
|
||||
|
||||
border-radius: 12px;
|
||||
|
||||
background: rgba(10, 8, 34, 0.34);
|
||||
background: #faf6f0;
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(212, 160, 160, 0.45);
|
||||
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.22);
|
||||
box-shadow: inset 0 1px 2px rgba(139, 115, 85, 0.08);
|
||||
|
||||
transition:
|
||||
border-color 0.22s ease,
|
||||
@@ -180,34 +183,34 @@ const inviteCode = computed(() => {
|
||||
box-shadow 0.22s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
background: rgba(10, 8, 34, 0.26);
|
||||
border-color: #c08a8a;
|
||||
background: #fdfaf5;
|
||||
}
|
||||
|
||||
&.is-focus {
|
||||
border-color: rgba(150, 158, 255, 0.62);
|
||||
background: rgba(10, 8, 34, 0.22);
|
||||
border-color: rgba(154, 92, 53, 0.55);
|
||||
background: #fffdfa;
|
||||
box-shadow:
|
||||
inset 0 1px 2px rgba(0, 0, 0, 0.22),
|
||||
0 0 0 3px rgba(120, 128, 255, 0.16);
|
||||
inset 0 1px 2px rgba(139, 115, 85, 0.08),
|
||||
0 0 0 3px rgba(154, 92, 53, 0.14);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
height: 46px;
|
||||
|
||||
color: #fff;
|
||||
color: #4f4130;
|
||||
|
||||
font-size: 14px;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
color: #9a886e;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-input__prefix),
|
||||
:deep(.el-input__suffix) {
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
color: #7a664e;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
@@ -232,16 +235,16 @@ const inviteCode = computed(() => {
|
||||
|
||||
border-radius: 12px;
|
||||
|
||||
border: 1px solid rgba(133, 150, 255, 0.38);
|
||||
border: 1px solid rgba(212, 160, 160, 0.6);
|
||||
|
||||
background: linear-gradient(135deg, rgba(112, 131, 255, 0.28), rgba(121, 91, 200, 0.24));
|
||||
background: rgba(245, 215, 95, 0.22);
|
||||
|
||||
color: #e3e7ff;
|
||||
color: #9a5c35;
|
||||
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
box-shadow: none;
|
||||
|
||||
transition:
|
||||
background 0.2s ease,
|
||||
@@ -250,24 +253,24 @@ const inviteCode = computed(() => {
|
||||
transform 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
color: #7d4a2a;
|
||||
|
||||
border-color: rgba(151, 164, 255, 0.62);
|
||||
border-color: #c08a8a;
|
||||
|
||||
background: linear-gradient(135deg, rgba(112, 131, 255, 0.4), rgba(121, 91, 200, 0.34));
|
||||
background: rgba(245, 215, 95, 0.34);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
&.is-disabled,
|
||||
&:disabled {
|
||||
color: rgba(255, 255, 255, 0.34);
|
||||
color: #9a886e;
|
||||
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
border-color: #eadfd0;
|
||||
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: #f6efe3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,11 +285,11 @@ const inviteCode = computed(() => {
|
||||
|
||||
border-radius: 12px;
|
||||
|
||||
border: 1px solid rgba(133, 150, 255, 0.38);
|
||||
border: 1px solid rgba(212, 160, 160, 0.6);
|
||||
|
||||
background: linear-gradient(135deg, rgba(112, 131, 255, 0.28), rgba(121, 91, 200, 0.24));
|
||||
background: rgba(245, 215, 95, 0.22);
|
||||
|
||||
color: #e3e7ff;
|
||||
color: #9a5c35;
|
||||
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
@@ -321,19 +324,21 @@ const inviteCode = computed(() => {
|
||||
|
||||
color: #fff;
|
||||
|
||||
background: linear-gradient(100deg, #667eea 0%, #7075dc 48%, #875bbb 100%);
|
||||
/* 原为 #667eea → #7075dc → #875bbb 的紫蓝渐变(规范明令禁止),改陶土;
|
||||
渐变两端白字对比度 5.31:1 / 7.28:1,均过 AA */
|
||||
background: linear-gradient(100deg, #9a5c35 0%, #8a5230 48%, #7d4a2a 100%);
|
||||
|
||||
box-shadow: 0 7px 20px rgba(91, 98, 220, 0.27);
|
||||
box-shadow: 0 7px 20px rgba(154, 92, 53, 0.24);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
filter: brightness(1.06);
|
||||
/* 去掉原来的 filter: brightness(硬闪),改规范要求的微风感:轻微旋转 + 小幅放大 */
|
||||
transform: rotate(-0.6deg) scale(1.02);
|
||||
|
||||
box-shadow: 0 10px 24px rgba(91, 98, 220, 0.36);
|
||||
box-shadow: 0 10px 24px rgba(154, 92, 53, 0.32);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,44 +349,45 @@ const inviteCode = computed(() => {
|
||||
:deep(.el-select__wrapper) {
|
||||
min-height: 48px;
|
||||
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
background: #faf6f0;
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(212, 160, 160, 0.45);
|
||||
|
||||
border-radius: 12px;
|
||||
|
||||
box-shadow: none;
|
||||
|
||||
color: #fff;
|
||||
color: #4f4130;
|
||||
}
|
||||
|
||||
:deep(.el-select__placeholder) {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
color: #9a886e;
|
||||
}
|
||||
|
||||
:deep(.el-checkbox__label) {
|
||||
color: rgba(255, 255, 255, 0.72);
|
||||
color: #5f4f3d;
|
||||
}
|
||||
|
||||
:deep(.el-checkbox__inner) {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
background: #faf6f0;
|
||||
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
border-color: #d8c3b0;
|
||||
}
|
||||
|
||||
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
|
||||
background: #667eea;
|
||||
border-color: #667eea;
|
||||
background: #9a5c35;
|
||||
border-color: #9a5c35;
|
||||
}
|
||||
|
||||
/* 密码强度条 */
|
||||
/* 密码强度条:弱→强 用 莓红 → 雏菊金 → 草绿 → 深草绿,色相递进而不是明度递进,
|
||||
保证色盲用户也能分辨(不靠单一颜色维度传递状态) */
|
||||
:deep(.@{adminNamespace}-input-password__bar) {
|
||||
height: 4px;
|
||||
|
||||
margin-top: 8px;
|
||||
margin-bottom: 0;
|
||||
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
background-color: #eadfd0;
|
||||
|
||||
border-radius: 999px;
|
||||
|
||||
@@ -398,19 +404,19 @@ const inviteCode = computed(() => {
|
||||
|
||||
&[data-score='0'],
|
||||
&[data-score='1'] {
|
||||
background-color: #ff6b81;
|
||||
background-color: #a4485a;
|
||||
}
|
||||
|
||||
&[data-score='2'] {
|
||||
background-color: #ffc069;
|
||||
background-color: #8f6418;
|
||||
}
|
||||
|
||||
&[data-score='3'] {
|
||||
background-color: #7c8cff;
|
||||
background-color: #457450;
|
||||
}
|
||||
|
||||
&[data-score='4'] {
|
||||
background: linear-gradient(90deg, #667eea, #4facfe);
|
||||
background: linear-gradient(90deg, #457450, #3f6b45);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,14 +425,14 @@ const inviteCode = computed(() => {
|
||||
|
||||
background: transparent;
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.24);
|
||||
border: 1px solid #d8c3b0;
|
||||
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
color: #5f4f3d;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-color: rgba(255, 255, 255, 0.42);
|
||||
color: #fff;
|
||||
background: #f6efe3;
|
||||
border-color: #c08a8a;
|
||||
color: #9a5c35;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -275,14 +275,14 @@ const handleClearAll = () => {
|
||||
}
|
||||
|
||||
.role-card:hover {
|
||||
border-color: #b3d8ff;
|
||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
|
||||
border-color: #c08a8a;
|
||||
box-shadow: 0 2px 8px rgba(154, 92, 53, 0.10);
|
||||
}
|
||||
|
||||
.role-card.active {
|
||||
border-color: #409eff;
|
||||
background: linear-gradient(135deg, #ecf5ff 0%, #f0f9ff 100%);
|
||||
box-shadow: 0 2px 12px rgba(64, 158, 255, 0.15);
|
||||
border-color: #9a5c35;
|
||||
background: linear-gradient(135deg, #fbf5ea 0%, #f6efe3 100%);
|
||||
box-shadow: 0 2px 12px rgba(154, 92, 53, 0.14);
|
||||
}
|
||||
|
||||
.role-icon {
|
||||
@@ -344,8 +344,8 @@ const handleClearAll = () => {
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
border-color: #c6e2ff;
|
||||
box-shadow: 0 1px 4px rgba(64, 158, 255, 0.08);
|
||||
border-color: #d8c3b0;
|
||||
box-shadow: 0 1px 4px rgba(154, 92, 53, 0.08);
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
|
||||
@@ -890,7 +890,7 @@ const handleSaveTemplate = async () => {
|
||||
.ornament-grid { display: flex; flex-wrap: wrap; gap: 10px; max-height: 240px; overflow-y: auto; padding: 4px; }
|
||||
.ornament-card { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border: 1px solid #e4e7ed;
|
||||
border-radius: 6px; cursor: pointer; transition: all 0.2s; width: 250px; }
|
||||
.ornament-card:hover { border-color: #409eff; background: #f0f9ff; }
|
||||
.ornament-card:hover { border-color: #9a5c35; background: #fbf5ea; }
|
||||
.ornament-img { width: 50px; height: 38px; object-fit: cover; border-radius: 4px; flex-shrink: 0; background: #f5f5f5; }
|
||||
.ornament-info { flex: 1; min-width: 0; }
|
||||
.ornament-name { font-size: 12px; line-height: 1.3; word-break: break-all; }
|
||||
|
||||
+10
-9
@@ -1,16 +1,22 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view />
|
||||
<theme-picker />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ThemePicker from "@/components/ThemePicker";
|
||||
|
||||
/**
|
||||
* 说明:这里曾经挂着一个隐藏的 <theme-picker />(用 #app .theme-picker{display:none} 藏起来)。
|
||||
* 它虽然不可见,组件照样会 mount,ThemePicker.created() 里只要 localStorage 的 theme 不等于
|
||||
* ThemePicker 自己那个 ORIGINAL_THEME 常量,就会:
|
||||
* 1) XHR 去 https://unpkg.com/element-ui@<version>/lib/theme-chalk/index.css 拉整包默认主题(外网依赖)
|
||||
* 2) 把拉回来的 CSS 注入成 <style id="chalk-style"> 追加到 head 末尾
|
||||
* 3) 用正则把页面上所有含旧色的 <style> 标签逐个做字符串替换
|
||||
* 第 2 步注入的默认主题与本项目皮肤层 skin.scss 同权重但更靠后,会把圆角/配色改回企业蓝方角,
|
||||
* 导致换肤被部分覆盖。故移除。如需恢复主题色选择器,请连带处理上述覆盖问题。
|
||||
*/
|
||||
export default {
|
||||
name: "App",
|
||||
components: { ThemePicker },
|
||||
metaInfo() {
|
||||
return {
|
||||
title:
|
||||
@@ -25,8 +31,3 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
#app .theme-picker {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 箱子列表 (支持分页+筛选)
|
||||
export function boxList(params) {
|
||||
return request({
|
||||
url: '/admin/bullet/box/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 新增/编辑箱子
|
||||
export function boxSave(data) {
|
||||
return request({
|
||||
url: '/admin/bullet/box/save',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除箱子
|
||||
export function boxDelete(boxId) {
|
||||
return request({
|
||||
url: '/admin/bullet/box/delete',
|
||||
method: 'post',
|
||||
params: { boxId }
|
||||
})
|
||||
}
|
||||
|
||||
// 查看箱子饰品配置
|
||||
export function boxOrnaments(boxId) {
|
||||
return request({
|
||||
url: '/admin/bullet/box/ornaments',
|
||||
method: 'get',
|
||||
params: { boxId }
|
||||
})
|
||||
}
|
||||
|
||||
// 一键重算全部箱子饰品分布
|
||||
export function recalcAll() {
|
||||
return request({ url: '/admin/bullet/box/recalc_all', method: 'post' })
|
||||
}
|
||||
|
||||
// 新增/编辑饰品数量 (upsert)
|
||||
export function saveOrnaments(boxId, data) {
|
||||
return request({
|
||||
url: '/admin/bullet/box/ornaments/save',
|
||||
method: 'post',
|
||||
params: { boxId },
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 版本管理
|
||||
export function getBoxVersions(boxId) {
|
||||
return request({ url: '/admin/bullet/box/versions', method: 'get', params: { boxId } })
|
||||
}
|
||||
export function publishVersion(boxId, version) {
|
||||
return request({ url: '/admin/bullet/box/publish', method: 'post', params: { boxId, version } })
|
||||
}
|
||||
export function rollbackVersion(boxId, version) {
|
||||
return request({ url: '/admin/bullet/box/rollback', method: 'post', params: { boxId, version } })
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function winStats(params) {
|
||||
return request({ url: '/admin/bullet/winstats', method: 'get', params })
|
||||
}
|
||||
|
||||
export function rewardPoolReset() {
|
||||
return request({ url: '/admin/bullet/rewardpool/reset', method: 'post' })
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function ornamentList() {
|
||||
return request({ url: '/admin/bullet/ornament/list', method: 'get' })
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function prizeList() {
|
||||
return request({ url: '/admin/bullet/prize/list', method: 'get' })
|
||||
}
|
||||
|
||||
export function prizeSave(data) {
|
||||
return request({ url: '/admin/bullet/prize/save', method: 'post', data })
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function recordList(params) {
|
||||
return request({ url: '/admin/bullet/records', method: 'get', params })
|
||||
}
|
||||
|
||||
export function deleteRecord(recordId) {
|
||||
return request({ url: '/admin/bullet/records/delete', method: 'post', params: { recordId } })
|
||||
}
|
||||
|
||||
export function clearRecords() {
|
||||
return request({ url: '/admin/bullet/records/clear', method: 'post' })
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function roundList(params) {
|
||||
return request({ url: '/admin/bullet/round/list', method: 'get', params })
|
||||
}
|
||||
|
||||
export function roundByOrnament(params) {
|
||||
return request({ url: '/admin/bullet/round/byOrnament', method: 'get', params })
|
||||
}
|
||||
|
||||
export function roundDelete(roundId) {
|
||||
return request({ url: '/admin/bullet/round/delete', method: 'post', params: { roundId } })
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 箱子列表(分页 + 按模式筛选)
|
||||
export function getBoxList(params) {
|
||||
return request({ url: '/admin/olympus/box/list', method: 'get', params })
|
||||
}
|
||||
|
||||
// 新增/编辑箱子
|
||||
export function saveBox(data) {
|
||||
return request({ url: '/admin/olympus/box/save', method: 'post', data })
|
||||
}
|
||||
|
||||
// 某箱某身份的饰品桶分布(含 convergeCount / fixedRounds / convergeRounds)
|
||||
export function getBoxOrnaments(boxId, audience) {
|
||||
return request({ url: `/admin/olympus/box/ornaments/${boxId}`, method: 'get', params: { audience }})
|
||||
}
|
||||
|
||||
// 保存某箱某身份饰品回合分布
|
||||
export function saveBoxOrnaments(data) {
|
||||
return request({ url: '/admin/olympus/box/ornaments/save', method: 'post', data })
|
||||
}
|
||||
|
||||
// 按 RTP 重算某箱某身份分布
|
||||
export function calcDistribution(data) {
|
||||
return request({ url: '/admin/olympus/box/ornaments/calc', method: 'post', data })
|
||||
}
|
||||
|
||||
// 模拟 payoutCap 约束下的预估实际 RTP
|
||||
export function simulateRtp(data) {
|
||||
return request({ url: '/admin/olympus/box/ornaments/simulate-rtp', method: 'post', data })
|
||||
}
|
||||
|
||||
// ==================== 分布模板(按身份) ====================
|
||||
|
||||
export function getBoxTemplates(boxId, audience) {
|
||||
return request({ url: `/admin/olympus/box/templates/${boxId}`, method: 'get', params: { audience }})
|
||||
}
|
||||
export function saveBoxTemplate(data) {
|
||||
return request({ url: '/admin/olympus/box/template/save', method: 'post', data })
|
||||
}
|
||||
export function deleteBoxTemplate(id) {
|
||||
return request({ url: `/admin/olympus/box/template/${id}`, method: 'delete' })
|
||||
}
|
||||
export function applyBoxTemplate(data) {
|
||||
return request({ url: '/admin/olympus/box/template/apply', method: 'post', data })
|
||||
}
|
||||
|
||||
// 一键重算所有箱子 × 所有身份(按各箱现有配置)
|
||||
export function recalcAll() {
|
||||
return request({ url: '/admin/olympus/box/recalc_all', method: 'post' })
|
||||
}
|
||||
|
||||
// 重算分布:按参数批量重算(boxType 可选=全部模式;audience 可选=全部身份;rtp/totalRounds/convergeRatio 必传统一设置)
|
||||
export function recalcBatch(data) {
|
||||
return request({ url: '/admin/olympus/box/recalcBatch', method: 'post', data })
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 每日统计列表(分页)
|
||||
export function getWinList(params) {
|
||||
return request({ url: '/admin/olympus/win/list', method: 'get', params })
|
||||
}
|
||||
|
||||
// 汇总(按日期区间)
|
||||
export function getWinSummary(params) {
|
||||
return request({ url: '/admin/olympus/win/summary', method: 'get', params })
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 饰品桶定义列表(17 桶)
|
||||
export function getOrnamentList() {
|
||||
return request({ url: '/admin/olympus/ornament/list', method: 'get' })
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 返奖饰品列表(含返奖区间)
|
||||
export function prizeList() {
|
||||
return request({ url: '/admin/olympus/prize/list', method: 'get' })
|
||||
}
|
||||
|
||||
// 新增/编辑返奖饰品(含区间)
|
||||
export function prizeSave(data) {
|
||||
return request({ url: '/admin/olympus/prize/save', method: 'post', data })
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 游戏记录列表(分页 + 按玩家ID/身份筛选;identity=common/anchor/robot/white,空=全部身份)
|
||||
export function getRecordList(params) {
|
||||
return request({ url: '/admin/olympus/record/list', method: 'get', params })
|
||||
}
|
||||
|
||||
// 返奖汇总(按日期区间)
|
||||
export function getWinSummary(params) {
|
||||
return request({ url: '/admin/olympus/win/summary', method: 'get', params })
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 奖池配置快照(总池 + 各箱子池),按身份 audience=common/anchor/robot/white
|
||||
export function getPoolConfig(params) {
|
||||
return request({ url: '/admin/olympus/rewardPool/config', method: 'get', params })
|
||||
}
|
||||
|
||||
// 逐箱注水/抽水(type=1 注水 / 2 抽水,含 audience、remark)
|
||||
export function adjustPool(data) {
|
||||
return request({ url: '/admin/olympus/rewardPool/adjust', method: 'post', data })
|
||||
}
|
||||
|
||||
// 批量按一轮花费比例注水/抽水:{ audience, type, ratio, remark },每箱金额=一轮总花费(底注×模式倍数×总回合数)×ratio%
|
||||
export function adjustPoolBatch(data) {
|
||||
return request({ url: '/admin/olympus/rewardPool/adjustBatch', method: 'post', data })
|
||||
}
|
||||
|
||||
// 重置人工调整:该身份所有箱子注水/抽水累计归零,只保留真实水位(下注−返奖):{ audience, remark }
|
||||
export function adjustPoolReset(data) {
|
||||
return request({ url: '/admin/olympus/rewardPool/adjustReset', method: 'post', data })
|
||||
}
|
||||
|
||||
// 奖池调整历史(分页,params 含 audience)
|
||||
export function getPoolLogs(params) {
|
||||
return request({ url: '/admin/olympus/rewardPool/adjustLog', method: 'get', params })
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 预生成回合列表(分页 + 按回合ID/模式筛选)
|
||||
export function getRoundList(params) {
|
||||
return request({ url: '/admin/olympus/round/list', method: 'get', params })
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getBoxList(params) {
|
||||
return request({
|
||||
url: '/admin/sweet/box/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function saveBox(data) {
|
||||
return request({
|
||||
url: '/admin/sweet/box/save',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function getBoxOrnaments(boxId) {
|
||||
return request({
|
||||
url: `/admin/sweet/box/ornaments/${boxId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function saveBoxOrnaments(data) {
|
||||
return request({
|
||||
url: '/admin/sweet/box/ornaments/save',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function calcDistribution(data) {
|
||||
return request({
|
||||
url: '/admin/sweet/box/ornaments/calc',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 模拟 payoutCap 约束下的预估实际 RTP
|
||||
export function simulateRtp(data) {
|
||||
return request({
|
||||
url: '/admin/sweet/box/ornaments/simulate-rtp',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 箱子饰品分布模板(后端持久化) ====================
|
||||
|
||||
// 查询某箱子的模板列表
|
||||
export function getBoxTemplates(boxId) {
|
||||
return request({
|
||||
url: `/admin/sweet/box/templates/${boxId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 保存当前分布为模板
|
||||
export function saveBoxTemplate(data) {
|
||||
return request({
|
||||
url: '/admin/sweet/box/template/save',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除模板
|
||||
export function deleteBoxTemplate(id) {
|
||||
return request({
|
||||
url: `/admin/sweet/box/template/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 应用模板(写入分布 + 标记为生效模板)
|
||||
export function applyBoxTemplate(data) {
|
||||
return request({
|
||||
url: '/admin/sweet/box/template/apply',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 奖池水位 ====================
|
||||
|
||||
export function getPoolStatus() {
|
||||
return request({ url: '/admin/sweet/pool/status', method: 'get' })
|
||||
}
|
||||
export function addPoolBet(data) {
|
||||
return request({ url: '/admin/sweet/pool/addBet', method: 'post', data })
|
||||
}
|
||||
export function addPoolWin(data) {
|
||||
return request({ url: '/admin/sweet/pool/addWin', method: 'post', data })
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getWinList(params) {
|
||||
return request({
|
||||
url: '/admin/sweet/win/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getWinSummary(params) {
|
||||
return request({
|
||||
url: '/admin/sweet/win/summary',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getOrnamentList() {
|
||||
return request({
|
||||
url: '/admin/sweet/ornament/list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function prizeList() {
|
||||
return request({
|
||||
url: '/admin/sweet/prize/list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function prizeSave(data) {
|
||||
return request({
|
||||
url: '/admin/sweet/prize/save',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getRecordList(params) {
|
||||
return request({
|
||||
url: '/admin/sweet/record/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getRoundList(params) {
|
||||
return request({
|
||||
url: '/admin/sweet/round/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function roundDelete(ids) {
|
||||
return request({
|
||||
url: '/admin/sweet/round/delete',
|
||||
method: 'delete',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/* ==========================================================================
|
||||
Cottagecore(田园核风)色板 —— 全站唯一色源
|
||||
改色只改这里;skin.scss / sidebar.scss / variables.scss / element-variables.scss
|
||||
全部引用本文件的变量,不再各自写死十六进制。
|
||||
|
||||
★ 无障碍(已按 WCAG 2.1 AA 实算,承载文字的组合均 ≥ 4.5:1)
|
||||
- 规范原值 #8b7355 作正文只有 4.17:1 ⇒ 正文用 $cc-ink-2
|
||||
- 规范原值 #5a8f5a 作浅底文字只有 3.56:1、白字只有 3.81:1
|
||||
⇒ 它只做装饰与大面积色块,绿色文字一律用 $cc-grass-text
|
||||
========================================================================== */
|
||||
|
||||
/* ---- 底色 ---- */
|
||||
$cc-cream: #faf6f0; // 卡片底(规范「背景主色」)
|
||||
$cc-cream-hi: #fffdfa; // 卡片悬停/更亮
|
||||
$cc-linen: #f3ece0; // 页面底(比卡片深一档,卡片才浮得起来)
|
||||
$cc-linen-2: #ece2d3; // 表头 / 浅槽 / 斑马纹
|
||||
|
||||
/* ---- 绿:草绿只做装饰,绿色文字用深值 ---- */
|
||||
$cc-grass: #5a8f5a; // 装饰、大色块
|
||||
$cc-grass-deep: #457450; // 绿底填充(白字 5.43:1)
|
||||
$cc-grass-text: #3f6b45; // 浅底上的绿色文字(5.73:1)
|
||||
|
||||
/* ---- 雏菊黄 ---- */
|
||||
$cc-daisy: #f5d75f; // 强调块 / 区块底(不承载小字)
|
||||
$cc-daisy-deep: #8f6418; // 深雏菊金 · 警告(白字 5.24:1)
|
||||
|
||||
/* ---- 大地色 ---- */
|
||||
$cc-earth: #8b7355; // 土棕 · 仅大号字与装饰
|
||||
$cc-terra: #9a5c35; // 陶土棕 · 主色 primary(白字 5.31:1)
|
||||
$cc-rose: #a4485a; // 莓红 · 危险(白字 5.75:1)
|
||||
$cc-ash: #6b6152; // 暖褐灰 · 信息(白字 6.08:1)
|
||||
$cc-blossom: #d4a0a0; // 花粉 · 描边 / 徽章
|
||||
$cc-blossom-deep: #c08a8a; // 花粉加深 · 悬停描边
|
||||
|
||||
/* ---- 墨色(文字)---- */
|
||||
$cc-ink: #4f4130; // 主文字(奶油底 9.16:1)
|
||||
$cc-ink-2: #5f4f3d; // 正文(6.37:1)
|
||||
$cc-ink-3: #7a664e; // 次级 / 弱化(5.08:1)
|
||||
$cc-ink-4: #8b755a; // 占位符 / 禁用(原 #9a886e 太淡,看不出还有内容)
|
||||
|
||||
/* ---- 线 ---- */
|
||||
$cc-line: rgba(212, 160, 160, 0.55); // 花粉细边(原 .42 的卡片描边几乎看不见)
|
||||
$cc-line-2: rgba(139, 115, 85, 0.20); // 暖棕分隔线
|
||||
|
||||
/* ---- 柔影:暖棕扩散,不用灰黑硬影 ---- */
|
||||
$cc-shadow-sm: 0 2px 8px rgba(139, 115, 85, 0.10);
|
||||
$cc-shadow-md: 0 6px 18px rgba(139, 115, 85, 0.13);
|
||||
$cc-shadow-lg: 0 12px 32px rgba(139, 115, 85, 0.17);
|
||||
|
||||
/* ---- 字体 ----
|
||||
Cottagecore 要求衬线,但后台是拿表格干活的:13px 宋体在密集表格里可读性明显下降。
|
||||
因此只在「标题类」(≥ 16px 的页面标题 / 弹窗标题)用衬线,
|
||||
正文、表单、表格一律保持系统无衬线,数字用 tabular-nums 保证列对齐(不是等宽字体)。 */
|
||||
$cc-serif: 'Songti SC', 'Source Han Serif SC', 'Noto Serif SC', STSong, SimSun, Georgia, 'Times New Roman', serif;
|
||||
$cc-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
||||
|
||||
@@ -52,13 +52,15 @@
|
||||
left: 0;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
border-radius: 12px;
|
||||
border-radius: 18px;
|
||||
border: 1px solid $cc-line;
|
||||
box-shadow: $cc-shadow-lg;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 优化遮罩层
|
||||
// 优化遮罩层:暖棕调,不再是纯黑
|
||||
.v-modal {
|
||||
background: rgba(0, 0, 0, 0.4) !important;
|
||||
background: rgba(79, 65, 48, 0.42) !important;
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
}
|
||||
@@ -73,25 +75,25 @@
|
||||
// 对话框头部
|
||||
.el-dialog__header {
|
||||
padding: 20px 24px 16px;
|
||||
border-bottom: 1px solid rgba(115, 133, 166, 0.12);
|
||||
border-bottom: 1px solid $cc-line-2;
|
||||
}
|
||||
|
||||
.el-dialog__title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #263654;
|
||||
color: $cc-ink;
|
||||
}
|
||||
|
||||
// 对话框内容
|
||||
.el-dialog__body {
|
||||
padding: 24px;
|
||||
color: #5a6478;
|
||||
color: $cc-ink-2;
|
||||
}
|
||||
|
||||
// 对话框底部
|
||||
.el-dialog__footer {
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid rgba(115, 133, 166, 0.12);
|
||||
border-top: 1px solid $cc-line-2;
|
||||
}
|
||||
|
||||
// refine element ui upload
|
||||
@@ -129,4 +131,4 @@
|
||||
> .el-submenu__title
|
||||
.el-submenu__icon-arrow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,62 @@
|
||||
/**
|
||||
* I think element-ui's default theme color is too light for long-term use.
|
||||
* So I modified the default color and you can modify it to your liking.
|
||||
**/
|
||||
* Element UI 主题变量(Cottagecore / 田园核风)
|
||||
*
|
||||
* 这里覆盖的变量会在 @import element-ui 主题源码之前生效,
|
||||
* 因此整包 theme-chalk 是「从源码编译」出来的新主题,而不是事后打补丁。
|
||||
* 色值全部来自 cottagecore.scss(唯一色源),本文件只做「语义 → Element」的映射。
|
||||
*
|
||||
* ★ 主色用陶土棕而不是草绿:Element 的 success 已经被工具栏的「修改」按钮占用为绿色,
|
||||
* 若 primary 也用绿色,「新增 / 修改」两个按钮就变成同色系分不清。
|
||||
* 想改回草绿主色 → 把下面 $--color-primary 换成 $cc-grass-deep 即可(链接文字同样达标)。
|
||||
**/
|
||||
|
||||
@import './cottagecore.scss';
|
||||
|
||||
/* theme color */
|
||||
$--color-primary: #1890ff;
|
||||
$--color-success: #13ce66;
|
||||
$--color-warning: #ffba00;
|
||||
$--color-danger: #ff4949;
|
||||
// $--color-info: #1E1E1E;
|
||||
$--color-primary: $cc-terra; // 陶土棕 · 白字 5.31:1 / 奶油底文字 4.93:1
|
||||
$--color-success: $cc-grass-deep; // 草绿 · 白字 5.43:1 / 奶油底文字 5.05:1
|
||||
$--color-warning: $cc-daisy-deep; // 深雏菊金 · 白字 5.24:1
|
||||
$--color-danger: $cc-rose; // 莓红 · 白字 5.75:1
|
||||
$--color-info: $cc-ash; // 暖褐灰 · 白字 6.08:1
|
||||
|
||||
$--button-font-weight: 400;
|
||||
$--button-font-weight: 500;
|
||||
|
||||
// $--color-text-regular: #1f2d3d;
|
||||
/* 文字层级:一律暖棕,不用冷灰 */
|
||||
$--color-text-primary: $cc-ink;
|
||||
$--color-text-regular: $cc-ink-2;
|
||||
$--color-text-secondary: $cc-ink-3;
|
||||
$--color-text-placeholder: $cc-ink-4;
|
||||
|
||||
$--border-color-light: #dfe4ed;
|
||||
$--border-color-lighter: #e6ebf5;
|
||||
/* 描边:花粉细边,禁止纯黑硬线 */
|
||||
$--border-color-base: #d8c3b0;
|
||||
$--border-color-light: #e2d3c2;
|
||||
$--border-color-lighter: #eadfd0;
|
||||
$--border-color-extra-light: #f0e8dc;
|
||||
|
||||
$--table-border: 1px solid #dfe6ec;
|
||||
/* 底色 */
|
||||
$--background-color-base: $cc-linen;
|
||||
$--disabled-fill-base: $cc-linen;
|
||||
$--disabled-color-base: $cc-ink-4;
|
||||
$--disabled-border-base: #e2d3c2;
|
||||
|
||||
/* 圆润:Cottagecore 的圆角一律大于 Element 默认的 4px */
|
||||
$--border-radius-base: 12px;
|
||||
$--border-radius-small: 10px;
|
||||
$--border-radius-circle: 100%;
|
||||
|
||||
/* 柔影:暖棕扩散 */
|
||||
$--box-shadow-base: $cc-shadow-md;
|
||||
$--box-shadow-dark: $cc-shadow-lg;
|
||||
$--box-shadow-light: $cc-shadow-sm;
|
||||
|
||||
/* 表格:柔和暖线,禁用 #dfe6ec 冷灰 */
|
||||
$--table-border: 1px solid #eadfd0;
|
||||
$--table-border-color: #eadfd0;
|
||||
$--table-header-background-color: $cc-linen;
|
||||
$--table-header-font-color: $cc-ink-3;
|
||||
$--table-row-hover-background-color: #fbf5ea;
|
||||
$--table-current-row-background-color: $cc-linen;
|
||||
$--table-font-color: $cc-ink-2;
|
||||
|
||||
/* icon font path, required */
|
||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import './cottagecore.scss';
|
||||
@import './variables.scss';
|
||||
@import './mixin.scss';
|
||||
@import './transition.scss';
|
||||
@@ -11,7 +12,8 @@ body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
background: #f3f7ff;
|
||||
background: $cc-linen;
|
||||
color: $cc-ink-2;
|
||||
}
|
||||
|
||||
label {
|
||||
@@ -21,7 +23,7 @@ label {
|
||||
html {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
background: #f3f7ff;
|
||||
background: $cc-linen;
|
||||
}
|
||||
|
||||
#app {
|
||||
@@ -99,24 +101,24 @@ div:focus {
|
||||
}
|
||||
|
||||
aside {
|
||||
background: #eef1f6;
|
||||
background: $cc-linen-2;
|
||||
padding: 8px 24px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 2px;
|
||||
border-radius: 12px;
|
||||
display: block;
|
||||
line-height: 32px;
|
||||
font-size: 16px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
color: #2c3e50;
|
||||
color: $cc-ink;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
a {
|
||||
color: #337ab7;
|
||||
color: $cc-grass-text;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: rgb(32, 160, 255);
|
||||
color: $cc-grass-deep;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,12 +127,15 @@ aside {
|
||||
// 注意:这两张卡片都不能加 backdrop-filter / filter / transform,
|
||||
// 它们会创建层叠上下文,把 position:fixed 的 el-dialog 困在卡片里,
|
||||
// 而 Element UI 的 .v-modal 遮罩挂在 body 上(z-index:2000) → 弹窗被遮罩盖住(整页变黑、点一下才恢复)
|
||||
// Cottagecore 皮肤下也严格只用 background / border / box-shadow 三样做卡片。
|
||||
// 另:整页卡片刻意「弱化」——去掉描边、底色贴近页面底、阴影收薄。
|
||||
// 否则「页卡 > 区块卡 > 小卡」三层卡片套在一起,画面很碎(规范也禁止卡片嵌套)。
|
||||
.app-container {
|
||||
padding: 24px;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
border: 1px solid rgba(255, 255, 255, 0.9);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 24px rgba(72, 105, 166, 0.09), inset 0 1px 0 rgba(255, 255, 255, 0.95);
|
||||
background: #fdfaf5;
|
||||
border: 0;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 2px 10px rgba(139, 115, 85, 0.07);
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
@@ -140,37 +145,34 @@ aside {
|
||||
position: relative;
|
||||
margin: 6px 10px 6px 0;
|
||||
padding: 10px 24px 24px;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
border: 1px solid rgba(255, 255, 255, 0.9);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 24px rgba(72, 105, 166, 0.09), inset 0 1px 0 rgba(255, 255, 255, 0.95);
|
||||
background: #fdfaf5;
|
||||
border: 0;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 2px 10px rgba(139, 115, 85, 0.07);
|
||||
min-height: calc(100vh - 150px);
|
||||
}
|
||||
|
||||
/* 只有「固定顶栏」时顶栏才脱离文档流,卡片才需要给它让位;
|
||||
fixedHeader=false(本项目默认)时顶栏自己占位置,再加边距就会多出一条空白 */
|
||||
fixedHeader=false(本项目默认)时顶栏自己占位置,再加边距就会多出一条空白。
|
||||
注意:标签页已并入顶栏,有/无标签页都只有一条 58px 的栏,不再区分 76 / 126px。 */
|
||||
.fixed-header + .page-card {
|
||||
margin-top: 76px;
|
||||
}
|
||||
|
||||
.hasTagsView .fixed-header + .page-card {
|
||||
margin-top: 126px;
|
||||
}
|
||||
|
||||
// 页面标题通用样式
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid rgba(115, 133, 166, 0.12);
|
||||
border-bottom: 1px solid $cc-line-2;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #263654;
|
||||
color: $cc-ink;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
@@ -178,19 +180,19 @@ aside {
|
||||
margin-left: 12px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #7385a6;
|
||||
|
||||
color: $cc-ink-3;
|
||||
|
||||
&::before {
|
||||
content: '·';
|
||||
margin-right: 8px;
|
||||
color: #b0bace;
|
||||
color: $cc-ink-4;
|
||||
}
|
||||
}
|
||||
|
||||
.page-description {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
color: #7385a6;
|
||||
color: $cc-ink-3;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@@ -215,7 +217,7 @@ aside {
|
||||
text-align: right;
|
||||
padding-right: 20px;
|
||||
transition: 600ms ease position;
|
||||
background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
|
||||
background: linear-gradient(90deg, $cc-grass-deep 0%, $cc-grass-deep 0%, $cc-grass 100%, $cc-grass 100%);
|
||||
|
||||
.subtitle {
|
||||
font-size: 20px;
|
||||
@@ -223,21 +225,21 @@ aside {
|
||||
}
|
||||
|
||||
&.draft {
|
||||
background: #d0d0d0;
|
||||
background: $cc-ink-4;
|
||||
}
|
||||
|
||||
&.deleted {
|
||||
background: #d0d0d0;
|
||||
background: $cc-ink-4;
|
||||
}
|
||||
}
|
||||
|
||||
.link-type,
|
||||
.link-type:focus {
|
||||
color: #337ab7;
|
||||
color: $cc-terra;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: rgb(32, 160, 255);
|
||||
color: $cc-grass-text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,46 +1,60 @@
|
||||
/* ==========================================================================
|
||||
悬浮卡片侧栏(主题核心文件)
|
||||
悬浮卡片侧栏(主题核心文件)· Cottagecore / 田园核风
|
||||
- 侧栏容器完全透明:页面上只有一张张独立卡片
|
||||
- 一级菜单:48px 毛玻璃卡片 + 28px 彩色图标块
|
||||
- 一级菜单:48px 奶油卡片 + 28px 暖色图标块
|
||||
- 二/三级:原地向下展开,用竖线 + 圆点区分层级
|
||||
- 收起态:54px 窄栏只留图标,悬停向右弹出子菜单
|
||||
- 移动端:整栏抽屉
|
||||
|
||||
改动说明(相对上一版蓝色皮肤):
|
||||
1. 去掉毛玻璃(backdrop-filter)—— 规范明确不把玻璃态当默认风格,
|
||||
且不透明奶油卡在亚麻底上层次更清楚,也少一层层叠上下文隐患。
|
||||
2. 8 色图标渐变换成大地/花卉色,不再有一个蓝。
|
||||
3. 所有选中/悬停色由陶土棕 $cc-terra + 雏菊黄 $cc-daisy 派生。
|
||||
========================================================================== */
|
||||
|
||||
$sidebar-width: $base-sidebar-width;
|
||||
$menu-h: 48px;
|
||||
$submenu-h: 36px;
|
||||
$card-radius: 13px;
|
||||
$brand: #2f6bff;
|
||||
$text-main: #263654;
|
||||
$text-sub: #66738b;
|
||||
$text-muted: #98a3b7;
|
||||
$card-bg: rgba(255, 255, 255, .84);
|
||||
$card-border: rgba(255, 255, 255, .88);
|
||||
$card-shadow: 0 7px 20px rgba(72, 105, 166, .11), inset 0 1px 0 rgba(255, 255, 255, .92);
|
||||
@import './cottagecore.scss';
|
||||
|
||||
/* 一级菜单图标色板:8 色循环(浅色→深色),避免整栏一个蓝 */
|
||||
$icon-palette:
|
||||
(#4e9bff, #2f6bff), // 1 蓝
|
||||
(#ffb648, #f97316), // 2 橙
|
||||
(#4ade80, #16a34a), // 3 绿
|
||||
(#fb7185, #ef4444), // 4 红
|
||||
(#a78bfa, #7c3aed), // 5 紫
|
||||
(#38bdf8, #0ea5e9), // 6 青
|
||||
(#f472b6, #db2777), // 7 粉
|
||||
(#fcd34d, #f59e0b); // 8 黄
|
||||
$sidebar-width: $base-sidebar-width;
|
||||
/* 菜单密度
|
||||
★ 注意:用户说「拥挤」指的是视觉憋闷,不是占得高。上一轮缩成 42px/3px 反而更挤了。
|
||||
正确做法是「加大项高 + 加大间距」:现在每项 54px + 上下 7px = 68px。
|
||||
17 个一级菜单会超一屏 → 走 el-scrollbar 滚动,这是可接受的代价。 */
|
||||
$menu-h: 54px;
|
||||
$submenu-h: 38px;
|
||||
$card-radius: 14px;
|
||||
$brand: $cc-terra;
|
||||
$text-main: $cc-ink;
|
||||
$text-sub: $cc-ink-2;
|
||||
$text-muted: $cc-ink-3;
|
||||
$card-bg: $cc-cream-hi;
|
||||
$card-border: $cc-line;
|
||||
$card-shadow: $cc-shadow-sm;
|
||||
|
||||
/* 一级菜单图标色板:只留 4 个田园色循环(陶土 / 草绿 / 雏菊金 / 玫瑰粉)
|
||||
原来是 8 色高饱和渐变,15 个菜单项齐上像调色盘;
|
||||
现在与仪表盘统计卡共用同一套 4 色,整站配色口径统一。 */
|
||||
$icon-hues:
|
||||
#9a5c35, // 1 陶土
|
||||
#457450, // 2 草绿
|
||||
#a8791f, // 3 雏菊金
|
||||
#b57474; // 4 玫瑰粉
|
||||
$icon-hue-count: length($icon-hues);
|
||||
|
||||
html,
|
||||
body {
|
||||
background: #f3f7ff;
|
||||
background: $cc-linen;
|
||||
}
|
||||
|
||||
#app {
|
||||
min-height: 100%;
|
||||
/* 亚麻底整体加深一档(原来是 #fdfaf5→#f3ece0,顶部近白,
|
||||
和奶油卡片几乎同色,卡片边界会糊掉)。现在卡片是亮面、页面是麻面,层次清楚。 */
|
||||
background:
|
||||
radial-gradient(760px 520px at 4% 100%, rgba(78, 155, 255, .18), transparent 62%),
|
||||
radial-gradient(760px 520px at 96% 0%, rgba(124, 92, 252, .12), transparent 62%),
|
||||
linear-gradient(155deg, #f5f8ff 0%, #fbfdff 46%, #edf4ff 100%);
|
||||
radial-gradient(760px 520px at 4% 100%, rgba(90, 143, 90, .11), transparent 62%),
|
||||
radial-gradient(760px 520px at 96% 0%, rgba(212, 160, 160, .13), transparent 62%),
|
||||
radial-gradient(520px 420px at 78% 88%, rgba(245, 215, 95, .14), transparent 60%),
|
||||
linear-gradient(155deg, #f7f1e6 0%, #f3ece0 48%, #ece2d3 100%);
|
||||
background-attachment: fixed;
|
||||
|
||||
.main-container {
|
||||
@@ -83,9 +97,9 @@ body {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* Logo 卡:10 + 64 + 6 = 80px */
|
||||
/* Logo 卡:10 + 60 + 8 = 78px */
|
||||
&.has-logo .el-scrollbar {
|
||||
height: calc(100% - 80px);
|
||||
height: calc(100% - 78px);
|
||||
}
|
||||
|
||||
.scrollbar-wrapper {
|
||||
@@ -98,7 +112,7 @@ body {
|
||||
}
|
||||
|
||||
.el-scrollbar__thumb {
|
||||
background: rgba(123, 144, 181, .28);
|
||||
background: rgba(139, 115, 85, .26);
|
||||
}
|
||||
|
||||
.is-horizontal {
|
||||
@@ -140,19 +154,17 @@ body {
|
||||
min-width: 0 !important;
|
||||
height: $menu-h;
|
||||
line-height: $menu-h;
|
||||
margin: 6px 10px !important;
|
||||
margin: 7px 10px !important;
|
||||
padding: 0 14px !important;
|
||||
box-sizing: border-box;
|
||||
|
||||
border: 1px solid $card-border;
|
||||
border-radius: $card-radius;
|
||||
background: $card-bg !important;
|
||||
-webkit-backdrop-filter: blur(14px) saturate(155%);
|
||||
backdrop-filter: blur(14px) saturate(155%);
|
||||
box-shadow: $card-shadow;
|
||||
|
||||
color: $text-main !important;
|
||||
font-size: 14px;
|
||||
font-size: 14.5px;
|
||||
font-weight: 600;
|
||||
|
||||
transition:
|
||||
@@ -164,34 +176,34 @@ body {
|
||||
|
||||
.el-menu:not(.el-menu--inline) > div > a > .el-menu-item:hover,
|
||||
.el-menu:not(.el-menu--inline) > div > .el-submenu > .el-submenu__title:hover {
|
||||
background: rgba(255, 255, 255, .95) !important;
|
||||
border-color: rgba(150, 179, 235, .38);
|
||||
box-shadow: 0 9px 24px rgba(57, 94, 159, .14), inset 0 1px 0 #fff;
|
||||
background: $cc-cream !important;
|
||||
border-color: $cc-blossom-deep;
|
||||
box-shadow: $cc-shadow-md;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* 展开中的一级菜单 */
|
||||
.el-menu:not(.el-menu--inline) > div > .el-submenu.is-opened > .el-submenu__title,
|
||||
.el-menu:not(.el-menu--inline) > div > .el-submenu.is-active > .el-submenu__title {
|
||||
color: #173f94 !important;
|
||||
border-color: rgba(67, 126, 255, .28);
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, .96), rgba(235, 242, 255, .91)) !important;
|
||||
box-shadow: 0 8px 22px rgba(47, 107, 255, .13), inset 3px 0 0 $brand;
|
||||
color: $cc-grass-text !important;
|
||||
border-color: $cc-blossom;
|
||||
background: linear-gradient(135deg, $cc-cream-hi, rgba(245, 215, 95, .18)) !important;
|
||||
box-shadow: $cc-shadow-md, inset 3px 0 0 $brand;
|
||||
}
|
||||
|
||||
/* 一级叶子菜单选中态 */
|
||||
.el-menu:not(.el-menu--inline) > div > a > .el-menu-item.is-active {
|
||||
color: $brand !important;
|
||||
border-color: rgba(67, 126, 255, .30);
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, .98), rgba(231, 239, 255, .92)) !important;
|
||||
box-shadow: 0 8px 22px rgba(47, 107, 255, .14), inset 3px 0 0 $brand;
|
||||
border-color: $cc-blossom;
|
||||
background: linear-gradient(135deg, $cc-cream-hi, rgba(212, 160, 160, .22)) !important;
|
||||
box-shadow: $cc-shadow-md, inset 3px 0 0 $brand;
|
||||
}
|
||||
|
||||
/* 展开箭头 */
|
||||
.el-menu:not(.el-menu--inline) > div > .el-submenu > .el-submenu__title .el-submenu__icon-arrow {
|
||||
right: 14px;
|
||||
margin-top: -5px;
|
||||
color: #7385a6;
|
||||
color: $cc-ink-3;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -206,27 +218,29 @@ body {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin: 0 11px 0 0 !important;
|
||||
padding: 5px;
|
||||
border-radius: 9px;
|
||||
color: #fff !important;
|
||||
fill: #fff;
|
||||
background: linear-gradient(135deg, #4e9bff, #2f6bff);
|
||||
box-shadow: 0 5px 12px rgba(47, 107, 255, .25);
|
||||
padding: 6px;
|
||||
border-radius: 11px;
|
||||
/* 浅底 + 同色深图标。底色不能太淡,否则小图标「看不见」(用户反馈对比度不够),
|
||||
所以 tint 用 .22 并加 1px 内描边。 */
|
||||
color: #9a5c35 !important;
|
||||
fill: currentColor;
|
||||
background: rgba(154, 92, 53, .22);
|
||||
box-shadow: inset 0 0 0 1px rgba(154, 92, 53, .32);
|
||||
}
|
||||
|
||||
/* 按菜单顺序轮换图标配色:第 1~8 个一组,之后循环 */
|
||||
@for $i from 1 through 8 {
|
||||
$pair: nth($icon-palette, $i);
|
||||
$from: nth($pair, 1);
|
||||
$to: nth($pair, 2);
|
||||
/* 按菜单顺序在这 4 个色里轮换 */
|
||||
@for $i from 1 through $icon-hue-count {
|
||||
$hue: nth($icon-hues, $i);
|
||||
|
||||
.el-menu:not(.el-menu--inline) > div:nth-child(8n + #{$i}) > a > .el-menu-item .svg-icon,
|
||||
.el-menu:not(.el-menu--inline) > div:nth-child(8n + #{$i}) > .el-submenu > .el-submenu__title .svg-icon {
|
||||
background: linear-gradient(135deg, $from, $to);
|
||||
box-shadow: 0 5px 12px rgba($to, .28);
|
||||
.el-menu:not(.el-menu--inline) > div:nth-child(#{$icon-hue-count}n + #{$i}) > a > .el-menu-item .svg-icon,
|
||||
.el-menu:not(.el-menu--inline) > div:nth-child(#{$icon-hue-count}n + #{$i}) > .el-submenu > .el-submenu__title .svg-icon {
|
||||
background: rgba($hue, .22);
|
||||
box-shadow: inset 0 0 0 1px rgba($hue, .32);
|
||||
color: $hue !important;
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,8 +250,8 @@ body {
|
||||
.el-menu--inline {
|
||||
position: relative;
|
||||
width: calc(100% - 30px) !important;
|
||||
margin: -1px 10px 5px 20px;
|
||||
padding: 4px 0 5px 17px;
|
||||
margin: -2px 10px 6px 18px;
|
||||
padding: 4px 0 6px 17px;
|
||||
box-sizing: border-box;
|
||||
background: transparent !important;
|
||||
border: 0 !important;
|
||||
@@ -253,7 +267,7 @@ body {
|
||||
bottom: 7px;
|
||||
width: 2px;
|
||||
border-radius: 1px;
|
||||
background: linear-gradient(180deg, rgba(47, 107, 255, .40), rgba(115, 133, 166, .22));
|
||||
background: linear-gradient(180deg, rgba(154, 92, 53, .38), rgba(212, 160, 160, .22));
|
||||
}
|
||||
|
||||
.el-menu--inline .el-menu-item,
|
||||
@@ -265,12 +279,12 @@ body {
|
||||
width: 100%;
|
||||
height: $submenu-h;
|
||||
line-height: $submenu-h;
|
||||
margin: 1px 0 !important;
|
||||
padding: 0 12px 0 24px !important;
|
||||
margin: 2px 0 !important;
|
||||
padding: 0 12px 0 26px !important;
|
||||
box-sizing: border-box;
|
||||
|
||||
border: 0 !important;
|
||||
border-radius: 9px;
|
||||
border-radius: 10px;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
@@ -293,8 +307,8 @@ body {
|
||||
margin-top: -4px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #7385a6;
|
||||
background: #fff;
|
||||
border: 2px solid $cc-ink-4;
|
||||
background: $cc-cream;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
@@ -306,20 +320,20 @@ body {
|
||||
|
||||
.el-menu--inline .el-menu-item:hover,
|
||||
.el-menu--inline .el-submenu > .el-submenu__title:hover {
|
||||
color: #2f6bff !important;
|
||||
background: rgba(234, 241, 255, .72) !important;
|
||||
color: $brand !important;
|
||||
background: rgba(245, 215, 95, .20) !important;
|
||||
transform: none !important;
|
||||
|
||||
&::before {
|
||||
border-color: #2f6bff;
|
||||
background: rgba(47, 107, 255, .15);
|
||||
border-color: $brand;
|
||||
background: rgba(154, 92, 53, .16);
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu--inline .el-menu-item.is-active {
|
||||
color: $brand !important;
|
||||
font-weight: 650;
|
||||
background: linear-gradient(90deg, rgba(222, 234, 255, .92), rgba(239, 245, 255, .72)) !important;
|
||||
background: linear-gradient(90deg, rgba(212, 160, 160, .30), rgba(245, 215, 95, .16)) !important;
|
||||
}
|
||||
|
||||
.el-menu--inline .el-menu-item.is-active::before {
|
||||
@@ -328,7 +342,7 @@ body {
|
||||
margin-top: -5px;
|
||||
border-color: $brand;
|
||||
background: $brand;
|
||||
box-shadow: 0 0 0 4px rgba(47, 107, 255, .15);
|
||||
box-shadow: 0 0 0 4px rgba(154, 92, 53, .16);
|
||||
}
|
||||
|
||||
/* 三级:在二级基础上小步缩进,不把内容推到很右边 */
|
||||
@@ -345,16 +359,16 @@ body {
|
||||
|
||||
.el-menu--inline .el-menu--inline .el-menu-item,
|
||||
.el-menu--inline .el-menu--inline .el-submenu > .el-submenu__title {
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
padding-left: 22px !important;
|
||||
font-size: 12.5px;
|
||||
color: #7c879a !important;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
padding-left: 24px !important;
|
||||
font-size: 13px;
|
||||
color: $cc-ink-3 !important;
|
||||
}
|
||||
|
||||
.el-menu--inline .el-submenu__icon-arrow {
|
||||
right: 10px;
|
||||
color: #9aa6bb;
|
||||
color: $cc-ink-4;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
@@ -438,12 +452,10 @@ body {
|
||||
.el-menu--vertical {
|
||||
> .el-menu--popup {
|
||||
padding: 8px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, .96);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(155%);
|
||||
backdrop-filter: blur(14px) saturate(155%);
|
||||
border: 1px solid rgba(255, 255, 255, .9);
|
||||
box-shadow: 0 12px 32px rgba(72, 105, 166, .18);
|
||||
border-radius: 14px;
|
||||
background: $cc-cream-hi;
|
||||
border: 1px solid $cc-line;
|
||||
box-shadow: $cc-shadow-lg;
|
||||
|
||||
.el-menu-item {
|
||||
height: 36px;
|
||||
@@ -451,21 +463,21 @@ body {
|
||||
min-width: 150px;
|
||||
margin: 2px 0;
|
||||
padding: 0 14px !important;
|
||||
border-radius: 8px;
|
||||
border-radius: 10px;
|
||||
color: $text-sub !important;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
background: transparent !important;
|
||||
|
||||
&:hover {
|
||||
background: rgba(234, 241, 255, .85) !important;
|
||||
background: rgba(245, 215, 95, .24) !important;
|
||||
color: $brand !important;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
color: $brand !important;
|
||||
font-weight: 650;
|
||||
background: linear-gradient(90deg, rgba(222, 234, 255, .95), rgba(239, 245, 255, .8)) !important;
|
||||
background: linear-gradient(90deg, rgba(212, 160, 160, .32), rgba(245, 215, 95, .18)) !important;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
@@ -487,7 +499,7 @@ body {
|
||||
bottom: 7px;
|
||||
width: 2px;
|
||||
border-radius: 1px;
|
||||
background: linear-gradient(180deg, rgba(47, 107, 255, .35), rgba(115, 133, 166, .2));
|
||||
background: linear-gradient(180deg, rgba(154, 92, 53, .34), rgba(212, 160, 160, .2));
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
@@ -497,7 +509,7 @@ body {
|
||||
line-height: 34px;
|
||||
padding-left: 20px !important;
|
||||
font-size: 12.5px;
|
||||
color: #7c879a !important;
|
||||
color: $cc-ink-3 !important;
|
||||
background: transparent !important;
|
||||
|
||||
&::before {
|
||||
@@ -510,13 +522,13 @@ body {
|
||||
margin-top: -4px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #9aa6bb;
|
||||
background: #fff;
|
||||
border: 2px solid $cc-ink-4;
|
||||
background: $cc-cream;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $brand !important;
|
||||
background: rgba(234, 241, 255, .85) !important;
|
||||
background: rgba(245, 215, 95, .24) !important;
|
||||
|
||||
&::before {
|
||||
border-color: $brand;
|
||||
@@ -526,7 +538,7 @@ body {
|
||||
&.is-active {
|
||||
color: $brand !important;
|
||||
font-weight: 650;
|
||||
background: rgba(234, 241, 255, .9) !important;
|
||||
background: rgba(212, 160, 160, .26) !important;
|
||||
|
||||
&::before {
|
||||
border-color: $brand;
|
||||
|
||||
@@ -1,28 +1,48 @@
|
||||
/* ==========================================================================
|
||||
皮肤层:按钮 / 表单 / 表格 / 分页 / 页头 / 水印 / 暗色
|
||||
皮肤层:按钮 / 表单 / 表格 / 分页 / 页头 / 弹层 · Cottagecore / 田园核风
|
||||
只覆盖 Element UI 默认样式,不改组件源码;需要整体回滚时删掉本文件的 import 即可
|
||||
(index.scss 最后一行 @import './skin.scss')
|
||||
|
||||
交互节奏按规范走「Gentle / Watercolor」:过渡 .4s ease-in-out,
|
||||
悬停做轻微旋转(≤0.6deg)与小幅放大,按下柔和回弹 scale(.97),
|
||||
不使用 bounce / elastic,也不用 filter: brightness 那种硬闪。
|
||||
========================================================================== */
|
||||
|
||||
$brand: #2f6bff;
|
||||
$ink: #263654;
|
||||
$ink-2: #5b6880;
|
||||
$ink-3: #98a3b7;
|
||||
$line: #eef2f9;
|
||||
@import './cottagecore.scss';
|
||||
|
||||
$brand: $cc-terra;
|
||||
$ink: $cc-ink;
|
||||
$ink-2: $cc-ink-2;
|
||||
$ink-3: $cc-ink-3;
|
||||
$line: #eadfd0;
|
||||
|
||||
/* ------------------------------- 按钮 ------------------------------- */
|
||||
.el-button {
|
||||
border-radius: 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
transition: filter .16s ease, box-shadow .16s ease;
|
||||
border-color: $cc-line;
|
||||
background: $cc-cream-hi;
|
||||
color: $ink-2;
|
||||
transition:
|
||||
transform .4s ease-in-out,
|
||||
box-shadow .4s ease-in-out,
|
||||
background-color .4s ease-in-out,
|
||||
border-color .4s ease-in-out;
|
||||
|
||||
> i {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
filter: brightness(1.06);
|
||||
&:hover:not(.is-disabled) {
|
||||
transform: rotate(-0.4deg) scale(1.02);
|
||||
border-color: $cc-blossom-deep;
|
||||
box-shadow: $cc-shadow-sm;
|
||||
}
|
||||
|
||||
&:active:not(.is-disabled) {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,109 +51,115 @@ $line: #eef2f9;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 工具栏按钮:实心胶囊(新增蓝 / 修改绿 / 删除红 / 导入青 / 导出橙) */
|
||||
/* 工具栏按钮:实心胶囊(新增陶土 / 修改草绿 / 删除莓红 / 导入褐灰 / 导出雏菊金)
|
||||
—— 五种色相刻意拉开,避免「新增 / 修改」变成同色系分不清 */
|
||||
.el-button--primary.is-plain {
|
||||
background: #2f6bff;
|
||||
border-color: #2f6bff;
|
||||
background: $cc-terra;
|
||||
border-color: $cc-terra;
|
||||
color: #fff;
|
||||
|
||||
&:hover, &:focus {
|
||||
background: #1f57e0;
|
||||
border-color: #1f57e0;
|
||||
background: #854c2a;
|
||||
border-color: #854c2a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.is-disabled, &.is-disabled:hover, &.is-disabled:focus {
|
||||
background: #a9c1ff;
|
||||
border-color: #a9c1ff;
|
||||
background: #cfa98f;
|
||||
border-color: #cfa98f;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button--success.is-plain {
|
||||
background: #22c55e;
|
||||
border-color: #22c55e;
|
||||
background: $cc-grass-deep;
|
||||
border-color: $cc-grass-deep;
|
||||
color: #fff;
|
||||
|
||||
&:hover, &:focus {
|
||||
background: #16a34a;
|
||||
border-color: #16a34a;
|
||||
background: #3a6243;
|
||||
border-color: #3a6243;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.is-disabled, &.is-disabled:hover, &.is-disabled:focus {
|
||||
background: #a5e5bf;
|
||||
border-color: #a5e5bf;
|
||||
background: #a9c9ae;
|
||||
border-color: #a9c9ae;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button--danger.is-plain {
|
||||
background: #ef4444;
|
||||
border-color: #ef4444;
|
||||
background: $cc-rose;
|
||||
border-color: $cc-rose;
|
||||
color: #fff;
|
||||
|
||||
&:hover, &:focus {
|
||||
background: #dc2626;
|
||||
border-color: #dc2626;
|
||||
background: #8b3c4c;
|
||||
border-color: #8b3c4c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.is-disabled, &.is-disabled:hover, &.is-disabled:focus {
|
||||
background: #f7b0b0;
|
||||
border-color: #f7b0b0;
|
||||
background: #d9a9b2;
|
||||
border-color: #d9a9b2;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button--info.is-plain {
|
||||
background: #06b6d4;
|
||||
border-color: #06b6d4;
|
||||
background: $cc-ash;
|
||||
border-color: $cc-ash;
|
||||
color: #fff;
|
||||
|
||||
&:hover, &:focus {
|
||||
background: #0891b2;
|
||||
border-color: #0891b2;
|
||||
background: #5b5244;
|
||||
border-color: #5b5244;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.is-disabled, &.is-disabled:hover, &.is-disabled:focus {
|
||||
background: #9adcea;
|
||||
border-color: #9adcea;
|
||||
background: #b8b0a3;
|
||||
border-color: #b8b0a3;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button--warning.is-plain {
|
||||
background: #f59e0b;
|
||||
border-color: #f59e0b;
|
||||
background: $cc-daisy-deep;
|
||||
border-color: $cc-daisy-deep;
|
||||
color: #fff;
|
||||
|
||||
&:hover, &:focus {
|
||||
background: #d97706;
|
||||
border-color: #d97706;
|
||||
background: #7a5512;
|
||||
border-color: #7a5512;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.is-disabled, &.is-disabled:hover, &.is-disabled:focus {
|
||||
background: #fbd08a;
|
||||
border-color: #fbd08a;
|
||||
background: #d6bb86;
|
||||
border-color: #d6bb86;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格行内文字按钮:编辑蓝、删除红 */
|
||||
/* 表格行内文字按钮:编辑陶土、删除莓红 */
|
||||
.el-table .el-button--text {
|
||||
padding: 4px 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: $brand;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: $cc-grass-text;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table .el-button--text.text-danger {
|
||||
color: #ef4444;
|
||||
color: $cc-rose;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #dc2626;
|
||||
color: #8b3c4c;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,21 +167,27 @@ $line: #eef2f9;
|
||||
.el-table .more-dot {
|
||||
padding: 4px 10px;
|
||||
font-size: 16px;
|
||||
color: $ink-3;
|
||||
color: $cc-ink-4;
|
||||
}
|
||||
|
||||
/* ------------------------------- 表单 ------------------------------- */
|
||||
.el-input__inner,
|
||||
.el-textarea__inner {
|
||||
border-radius: 10px;
|
||||
border-color: #e6ecf6;
|
||||
border-radius: 12px;
|
||||
border-color: $cc-line;
|
||||
background: $cc-cream-hi;
|
||||
color: $ink;
|
||||
}
|
||||
|
||||
.el-input__inner::placeholder,
|
||||
.el-textarea__inner::placeholder {
|
||||
color: $cc-ink-4;
|
||||
}
|
||||
|
||||
.el-input__inner:focus,
|
||||
.el-textarea__inner:focus {
|
||||
border-color: rgba(47, 107, 255, .55);
|
||||
box-shadow: 0 0 0 3px rgba(47, 107, 255, .1);
|
||||
border-color: rgba(154, 92, 53, .55);
|
||||
box-shadow: 0 0 0 3px rgba(154, 92, 53, .12);
|
||||
}
|
||||
|
||||
/* 搜索条不再显示 label,用 placeholder 表达(与设计图一致) */
|
||||
@@ -168,10 +200,20 @@ $line: #eef2f9;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* 下拉/日期浮层的输入框跟着圆 */
|
||||
.el-select-dropdown__item.selected,
|
||||
.el-select-dropdown__item.hover,
|
||||
.el-select-dropdown__item:hover {
|
||||
background: rgba(245, 215, 95, .26);
|
||||
}
|
||||
|
||||
/* ------------------------------- 表格 ------------------------------- */
|
||||
.el-table {
|
||||
font-size: 13px;
|
||||
color: #2b3a55;
|
||||
color: $ink-2;
|
||||
background: transparent;
|
||||
/* 数字列右对齐时天然对齐,不用等宽字体(等宽被规范列为禁止项) */
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
@@ -179,8 +221,8 @@ $line: #eef2f9;
|
||||
|
||||
th.el-table__cell {
|
||||
height: 42px;
|
||||
background: #f7f9fe;
|
||||
color: $ink-2;
|
||||
background: $cc-linen;
|
||||
color: $cc-ink-3;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -191,7 +233,7 @@ $line: #eef2f9;
|
||||
}
|
||||
|
||||
.el-table__row:hover > td.el-table__cell {
|
||||
background: #f8fbff;
|
||||
background: #fbf5ea;
|
||||
}
|
||||
|
||||
.el-button--text {
|
||||
@@ -212,7 +254,7 @@ $line: #eef2f9;
|
||||
.app-container .pagination-container {
|
||||
margin-top: 6px;
|
||||
padding: 8px 16px 6px;
|
||||
border-radius: 12px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.el-pagination {
|
||||
@@ -227,13 +269,20 @@ $line: #eef2f9;
|
||||
height: 32px;
|
||||
line-height: 30px;
|
||||
margin: 0 3px;
|
||||
border: 1px solid #e9eff8;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
border: 1px solid $line;
|
||||
border-radius: 10px;
|
||||
background: $cc-cream-hi;
|
||||
color: $ink-2;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-prev:hover,
|
||||
.btn-next:hover,
|
||||
.el-pager li:hover {
|
||||
border-color: $cc-blossom-deep;
|
||||
color: $brand;
|
||||
}
|
||||
|
||||
.el-pager li.active {
|
||||
background: $brand;
|
||||
border-color: $brand;
|
||||
@@ -242,7 +291,7 @@ $line: #eef2f9;
|
||||
|
||||
.el-pagination__sizes .el-input__inner,
|
||||
.el-pagination__jump .el-input__inner {
|
||||
border-radius: 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,6 +305,14 @@ $line: #eef2f9;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
/* 标题类走衬线:只在 ≥16px 的大号字上用,制造田园感而不牺牲表格可读性 */
|
||||
.page-title,
|
||||
.el-dialog__title,
|
||||
.el-message-box__title {
|
||||
font-family: $cc-serif;
|
||||
letter-spacing: .2px;
|
||||
}
|
||||
|
||||
/* ------------------------------- 弹层 -------------------------------
|
||||
$confirm / $alert(el-message-box)之前完全没被皮肤层覆盖,还是 Element
|
||||
默认的 4px 方角 + 15px 内边距,和 12px 的 el-dialog、10px 的按钮/输入框
|
||||
@@ -263,14 +320,14 @@ $line: #eef2f9;
|
||||
------------------------------------------------------------------- */
|
||||
.el-message-box {
|
||||
padding-bottom: 0;
|
||||
border: 1px solid rgba(255, 255, 255, 0.9);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 12px 32px rgba(72, 105, 166, 0.18);
|
||||
border: 1px solid $cc-line;
|
||||
border-radius: 18px;
|
||||
box-shadow: $cc-shadow-lg;
|
||||
}
|
||||
|
||||
.el-message-box__header {
|
||||
padding: 20px 24px 16px;
|
||||
border-bottom: 1px solid rgba(115, 133, 166, 0.12);
|
||||
border-bottom: 1px solid $cc-line-2;
|
||||
}
|
||||
|
||||
.el-message-box__title {
|
||||
@@ -292,13 +349,13 @@ $line: #eef2f9;
|
||||
|
||||
.el-message-box__status {
|
||||
&.el-icon-warning {
|
||||
color: #f59e0b;
|
||||
color: $cc-daisy-deep;
|
||||
}
|
||||
&.el-icon-success {
|
||||
color: #22c55e;
|
||||
color: $cc-grass-deep;
|
||||
}
|
||||
&.el-icon-error {
|
||||
color: #ef4444;
|
||||
color: $cc-rose;
|
||||
}
|
||||
&.el-icon-info {
|
||||
color: $brand;
|
||||
@@ -307,7 +364,7 @@ $line: #eef2f9;
|
||||
|
||||
.el-message-box__btns {
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid rgba(115, 133, 166, 0.12);
|
||||
border-top: 1px solid $cc-line-2;
|
||||
|
||||
.el-button {
|
||||
min-width: 76px;
|
||||
@@ -322,12 +379,61 @@ $line: #eef2f9;
|
||||
.el-picker-panel,
|
||||
.el-message,
|
||||
.el-notification {
|
||||
border-color: rgba(115, 133, 166, 0.14);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 24px rgba(72, 105, 166, 0.12);
|
||||
border-color: $cc-line-2;
|
||||
border-radius: 14px;
|
||||
box-shadow: $cc-shadow-lg;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.el-message {
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
/* 标签:圆润小球形,配色跟随语义色 */
|
||||
.el-tag {
|
||||
border-radius: 999px;
|
||||
padding: 0 12px;
|
||||
border-color: $cc-line;
|
||||
}
|
||||
|
||||
/* 复选框/单选框/开关:跟随陶土主色 */
|
||||
.el-checkbox__input.is-checked .el-checkbox__inner,
|
||||
.el-checkbox__input.is-indeterminate .el-checkbox__inner,
|
||||
.el-radio__input.is-checked .el-radio__inner {
|
||||
background-color: $brand;
|
||||
border-color: $brand;
|
||||
}
|
||||
|
||||
.el-checkbox__input.is-checked + .el-checkbox__label,
|
||||
.el-radio__input.is-checked + .el-radio__label {
|
||||
color: $brand;
|
||||
}
|
||||
|
||||
/* 卡片 / 折叠面板 / 描述列表:统一的奶油卡 */
|
||||
.el-card,
|
||||
.el-collapse,
|
||||
.el-collapse-item__header,
|
||||
.el-collapse-item__wrap,
|
||||
.el-descriptions__body {
|
||||
border-color: $cc-line;
|
||||
border-radius: 16px;
|
||||
background: $cc-cream;
|
||||
}
|
||||
|
||||
.el-collapse-item__header,
|
||||
.el-collapse-item__wrap {
|
||||
border-radius: 0;
|
||||
border-bottom-color: $cc-line-2;
|
||||
}
|
||||
|
||||
/* ------------------------------- 无障碍 -------------------------------
|
||||
规范要求动效提供 reduced-motion 备选:关掉悬停位移/旋转,只留颜色变化
|
||||
------------------------------------------------------------------- */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.el-button,
|
||||
.el-button:hover,
|
||||
.el-button:active {
|
||||
transform: none !important;
|
||||
transition: background-color .01ms linear, border-color .01ms linear !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,29 @@
|
||||
// base color
|
||||
$blue:#324157;
|
||||
$light-blue:#3A71A8;
|
||||
$red:#C03639;
|
||||
$pink: #E65D6E;
|
||||
$green: #30B08F;
|
||||
$tiffany: #4AB7BD;
|
||||
$yellow:#FEC171;
|
||||
$panGreen: #30B08F;
|
||||
@import './cottagecore.scss';
|
||||
|
||||
// 默认菜单主题风格
|
||||
$base-menu-color:#bfcbd9;
|
||||
$base-menu-color-active:#f4f4f5;
|
||||
$base-menu-background:#304156;
|
||||
// base color(沿用 cottagecore 色板,避免这里的 $blue/$green 与主题脱节)
|
||||
$blue: $cc-earth;
|
||||
$light-blue: $cc-grass;
|
||||
$red: $cc-rose;
|
||||
$pink: $cc-blossom;
|
||||
$green: $cc-grass-deep;
|
||||
$tiffany: $cc-grass;
|
||||
$yellow: $cc-daisy;
|
||||
$panGreen: $cc-grass-deep;
|
||||
|
||||
// 深色菜单主题风格(sideTheme: 'theme-dark' 时用;Cottagecore 皮肤默认走下面的浅色)
|
||||
$base-menu-color: #bfcbd9;
|
||||
$base-menu-color-active: #f4f4f5;
|
||||
$base-menu-background: #304156;
|
||||
$base-logo-title-color: #ffffff;
|
||||
|
||||
$base-menu-light-color:rgba(0,0,0,.70);
|
||||
$base-menu-light-background:#ffffff;
|
||||
$base-logo-light-title-color: #001529;
|
||||
// 浅色菜单主题风格(当前使用:奶油底 + 暖棕文字)
|
||||
$base-menu-light-color: $cc-ink-2;
|
||||
$base-menu-light-background: $cc-cream;
|
||||
$base-logo-light-title-color: $cc-ink;
|
||||
|
||||
$base-sub-menu-background:#1f2d3d;
|
||||
$base-sub-menu-hover:#001528;
|
||||
|
||||
// 自定义暗色菜单风格
|
||||
/**
|
||||
$base-menu-color:hsla(0,0%,100%,.65);
|
||||
$base-menu-color-active:#fff;
|
||||
$base-menu-background:#001529;
|
||||
$base-logo-title-color: #ffffff;
|
||||
|
||||
$base-menu-light-color:rgba(0,0,0,.70);
|
||||
$base-menu-light-background:#ffffff;
|
||||
$base-logo-light-title-color: #001529;
|
||||
|
||||
$base-sub-menu-background:#000c17;
|
||||
$base-sub-menu-hover:#001528;
|
||||
*/
|
||||
// 深色主题的二级菜单底色(未使用,留作回退)
|
||||
$base-sub-menu-background: #1f2d3d;
|
||||
$base-sub-menu-hover: #001528;
|
||||
|
||||
$base-sidebar-width: 240px;
|
||||
|
||||
|
||||
@@ -65,48 +65,48 @@ export default {
|
||||
font-size: 14px;
|
||||
line-height: 58px;
|
||||
margin-left: 4px;
|
||||
color: #7385a6;
|
||||
color: #7a664e;
|
||||
|
||||
::v-deep .el-breadcrumb__separator {
|
||||
color: #b0bace;
|
||||
color: #9a886e;
|
||||
margin: 0 6px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
::v-deep .el-breadcrumb__item {
|
||||
.el-breadcrumb__inner {
|
||||
color: #5a6478;
|
||||
color: #5f4f3d;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #2f6bff;
|
||||
color: #9a5c35;
|
||||
}
|
||||
|
||||
&.is-link {
|
||||
color: #7385a6;
|
||||
color: #7a664e;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child .el-breadcrumb__inner {
|
||||
color: #2f6bff;
|
||||
color: #9a5c35;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.no-redirect {
|
||||
color: #2f6bff;
|
||||
color: #9a5c35;
|
||||
cursor: text;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #7385a6;
|
||||
color: #7a664e;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
color: #2f6bff;
|
||||
color: #9a5c35;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
<!-- 上传提示 -->
|
||||
<div class="el-upload__tip" slot="tip" v-if="showTip">
|
||||
请上传
|
||||
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
|
||||
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
|
||||
<template v-if="fileSize"> 大小不超过 <b style="color: #a4485a">{{ fileSize }}MB</b> </template>
|
||||
<template v-if="fileType"> 格式为 <b style="color: #a4485a">{{ fileType.join("/") }}</b> </template>
|
||||
的文件
|
||||
</div>
|
||||
</el-upload>
|
||||
|
||||
@@ -83,7 +83,7 @@ export default {
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #909399;
|
||||
color: #7a664e;
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
<!-- 上传提示 -->
|
||||
<div class="el-upload__tip" slot="tip" v-if="showTip">
|
||||
请上传
|
||||
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
|
||||
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
|
||||
<template v-if="fileSize"> 大小不超过 <b style="color: #a4485a">{{ fileSize }}MB</b> </template>
|
||||
<template v-if="fileType"> 格式为 <b style="color: #a4485a">{{ fileType.join("/") }}</b> </template>
|
||||
的文件
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-color-picker
|
||||
v-model="theme"
|
||||
:predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
|
||||
:predefine="['#9a5c35', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
|
||||
class="theme-picker"
|
||||
popper-class="theme-picker-dropdown"
|
||||
/>
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<script>
|
||||
const version = require('element-ui/package.json').version // element-ui version from node_modules
|
||||
const ORIGINAL_THEME = '#409EFF' // default color
|
||||
const ORIGINAL_THEME = '#9a5c35' // default color
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
||||
@@ -41,17 +41,19 @@ export default {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
/* 全局滚动条:原来是冷白蓝轨 + 石板蓝滑块,悬停还是蓝色
|
||||
(rgba(47,107,255))—— 换肤时容易漏掉,因为它不在任何卡片里 */
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: rgba(248, 250, 254, 0.6);
|
||||
background-color: rgba(236, 226, 211, 0.6);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(180deg, rgba(123, 144, 181, 0.4), rgba(123, 144, 181, 0.3));
|
||||
background: linear-gradient(180deg, rgba(139, 115, 85, 0.34), rgba(139, 115, 85, 0.24));
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(180deg, rgba(47, 107, 255, 0.5), rgba(47, 107, 255, 0.4));
|
||||
background: linear-gradient(180deg, rgba(154, 92, 53, 0.5), rgba(154, 92, 53, 0.4));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div class="navbar">
|
||||
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/>
|
||||
<top-nav id="topmenu-container" class="topmenu-container" v-if="topNav"/>
|
||||
<!-- 顶栏与标签页合并成一条
|
||||
原来「面包屑 + 标签页」是两条叠在一起的栏,而且都在显示当前位置
|
||||
(面包屑显示"首页",紧挨着的标签页也显示"首页")= 重复,还白占 108px 竖向空间。
|
||||
现在只保留标签页(多标签导航更实用),面包屑去掉。 -->
|
||||
<tags-view v-if="showTagsView" class="navbar-tags-view" />
|
||||
|
||||
<div class="right-menu">
|
||||
<!-- 任意页面都能一键看"当前这个页面"的操作记录 -->
|
||||
@@ -35,23 +38,20 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import Breadcrumb from '@/components/Breadcrumb'
|
||||
import TopNav from '@/components/TopNav'
|
||||
import TagsView from './TagsView'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Breadcrumb,
|
||||
TopNav
|
||||
TagsView
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'avatar',
|
||||
'name'
|
||||
]),
|
||||
topNav: {
|
||||
get() {
|
||||
return this.$store.state.settings.topNav
|
||||
}
|
||||
/** 标签页已并入顶栏,是否显示跟随布局设置 */
|
||||
showTagsView() {
|
||||
return this.$store.state.settings.tagsView
|
||||
},
|
||||
/** 已经在操作日志页时不显示入口 */
|
||||
showOperLog() {
|
||||
@@ -84,26 +84,25 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
height: 58px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
backdrop-filter: blur(14px) saturate(145%);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(145%);
|
||||
box-shadow: 0 6px 22px rgba(47, 107, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.72);
|
||||
/* 原来是白色毛玻璃 + 蓝影(rgba(47,107,255,.08)),
|
||||
与奶油菜单卡不是一套;统一成不透明奶油卡 + 花粉细边 + 暖棕柔影,并去掉 backdrop-filter */
|
||||
background: #fffdfa;
|
||||
box-shadow: 0 2px 8px rgba(139, 115, 85, 0.10);
|
||||
border: 1px solid rgba(212, 160, 160, 0.55);
|
||||
border-radius: 14px;
|
||||
margin: 6px 10px 6px 0;
|
||||
padding-left: 16px;
|
||||
padding: 0 10px 0 8px;
|
||||
|
||||
/* 顶栏不再放置收起按钮/装饰图标,面包屑直接从左侧留白开始 */
|
||||
.breadcrumb-container {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.topmenu-container {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
/* 面包屑与 top-nav 模式不再使用(当前位置由标签页表达) */
|
||||
.navbar-tags-view {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.errLog-container {
|
||||
@@ -112,22 +111,24 @@ export default {
|
||||
}
|
||||
|
||||
.right-menu {
|
||||
float: right;
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 100%;
|
||||
line-height: 58px;
|
||||
padding-right: 18px;
|
||||
padding-right: 4px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.right-menu-item {
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
height: 100%;
|
||||
font-size: 18px;
|
||||
color: #5a6478;
|
||||
vertical-align: text-bottom;
|
||||
color: #5f4f3d;
|
||||
}
|
||||
|
||||
/* 操作日志入口:文案按钮,比图标更好找 */
|
||||
@@ -141,7 +142,7 @@ export default {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #2f6bff;
|
||||
color: #9a5c35;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +152,7 @@ export default {
|
||||
.avatar-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 58px;
|
||||
height: 100%;
|
||||
|
||||
.user-avatar {
|
||||
width: 36px;
|
||||
@@ -164,7 +165,7 @@ export default {
|
||||
margin: 0 6px 0 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #263654;
|
||||
color: #4f4130;
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -173,7 +174,7 @@ export default {
|
||||
|
||||
.el-icon-caret-bottom {
|
||||
font-size: 12px;
|
||||
color: #98a3b7;
|
||||
color: #9a886e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ export default {
|
||||
height: 100%;
|
||||
padding-top: 15px;
|
||||
padding-left: 24px;
|
||||
color: #1890ff;
|
||||
color: #9a5c35;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@@ -28,18 +28,18 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 悬浮 Logo 卡片:与一级菜单使用同一套毛玻璃语言 */
|
||||
/* 悬浮 Logo 卡片:与一级菜单同一套语言(不透明奶油卡 + 花粉细边 + 暖棕柔影)
|
||||
原来还是旧的蓝色毛玻璃:rgba(255,255,255,.86) + blur(14px) + rgba(72,105,166,.11) 蓝影,
|
||||
换肤时漏了这个文件,结果 Logo 卡和下面的菜单卡不是一套。 */
|
||||
.sidebar-logo-container {
|
||||
position: relative;
|
||||
width: calc(100% - 20px);
|
||||
height: 64px;
|
||||
margin: 10px 10px 6px;
|
||||
background: rgba(255, 255, 255, .86);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(155%);
|
||||
backdrop-filter: blur(14px) saturate(155%);
|
||||
border: 1px solid rgba(255, 255, 255, .88);
|
||||
height: 60px;
|
||||
margin: 10px 10px 8px;
|
||||
background: #fffdfa;
|
||||
border: 1px solid rgba(212, 160, 160, 0.55);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 7px 20px rgba(72, 105, 166, .11), inset 0 1px 0 rgba(255, 255, 255, .92);
|
||||
box-shadow: 0 2px 8px rgba(139, 115, 85, 0.10);
|
||||
|
||||
.sidebar-logo-link {
|
||||
display: flex;
|
||||
@@ -63,7 +63,7 @@ export default {
|
||||
.sidebar-title {
|
||||
flex: 0 1 auto;
|
||||
margin: 0;
|
||||
color: #263654;
|
||||
color: #4f4130;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 22px;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<span v-if="!isAffix(tag)" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
|
||||
</router-link>
|
||||
</scroll-pane>
|
||||
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
|
||||
<ul v-show="visible" ref="menu" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
|
||||
<li @click="refreshSelectedTag(selectedTag)"><i class="el-icon-refresh-right"></i> 刷新页面</li>
|
||||
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><i class="el-icon-close"></i> 关闭当前</li>
|
||||
<li @click="closeOthersTags"><i class="el-icon-circle-close"></i> 关闭其他</li>
|
||||
@@ -218,21 +218,34 @@ export default {
|
||||
}
|
||||
},
|
||||
openMenu(tag, e) {
|
||||
const menuMinWidth = 105
|
||||
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
|
||||
const offsetWidth = this.$el.offsetWidth // container width
|
||||
const maxLeft = offsetWidth - menuMinWidth // left boundary
|
||||
const left = e.clientX - offsetLeft + 15 // 15: margin right
|
||||
|
||||
if (left > maxLeft) {
|
||||
this.left = maxLeft
|
||||
} else {
|
||||
this.left = left
|
||||
}
|
||||
|
||||
this.top = e.clientY
|
||||
/* 菜单是 position: fixed(见 <style>),坐标用光标的视口坐标即可。
|
||||
原实现是 absolute + (clientX - 容器 left),按「相对定位祖先」算:
|
||||
顶栏合并后祖先变成 .navbar(overflow: hidden),于是菜单既被裁成一条、又整体偏位。
|
||||
改成 fixed 后就跳出裁切(.navbar 没有 transform / filter,不会给 fixed 造包含块)。 */
|
||||
this.left = e.clientX + 6
|
||||
this.top = e.clientY + 6
|
||||
this.visible = true
|
||||
this.selectedTag = tag
|
||||
this.clampMenu()
|
||||
},
|
||||
/** 贴边时回收/上翻,保证整个菜单都在视口内(原来 maxLeft 用的是容器宽度,算不对) */
|
||||
clampMenu() {
|
||||
this.$nextTick(() => {
|
||||
const el = this.$refs.menu
|
||||
if (!el) return
|
||||
const rect = el.getBoundingClientRect()
|
||||
const pad = 8
|
||||
let left = this.left
|
||||
let top = this.top
|
||||
if (rect.right > window.innerWidth - pad) {
|
||||
left = window.innerWidth - rect.width - pad
|
||||
}
|
||||
if (rect.bottom > window.innerHeight - pad) {
|
||||
top = this.top - rect.height - 12
|
||||
}
|
||||
this.left = left < pad ? pad : left
|
||||
this.top = top < pad ? pad : top
|
||||
})
|
||||
},
|
||||
closeMenu() {
|
||||
this.visible = false
|
||||
@@ -246,16 +259,18 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tags-view-container {
|
||||
height: 50px;
|
||||
margin: 0 10px 0 0;
|
||||
padding: 9px 12px 9px 14px;
|
||||
/* 已并入顶栏(Navbar):不再自己做一张卡片,只负责横向占位,
|
||||
否则会变成「顶栏卡片里再套一张标签卡片」 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.86);
|
||||
backdrop-filter: blur(14px) saturate(145%);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(145%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.72);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 6px 22px rgba(47, 107, 255, 0.08);
|
||||
background: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
@@ -265,15 +280,15 @@ export default {
|
||||
cursor: pointer;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border: 1px solid rgba(214, 225, 241, 0.9);
|
||||
color: #5a6478;
|
||||
background: rgba(238, 244, 253, 0.95);
|
||||
border: 1px solid #eadfd0;
|
||||
color: #5f4f3d;
|
||||
background: #f6efe3;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
margin-left: 6px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 10px;
|
||||
transition: all .4s ease-in-out;
|
||||
|
||||
&:first-of-type {
|
||||
margin-left: 0;
|
||||
@@ -283,16 +298,17 @@ export default {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(228, 238, 253, 1);
|
||||
border-color: rgba(47, 107, 255, 0.25);
|
||||
color: #2f6bff;
|
||||
background: #fbf5ea;
|
||||
border-color: #c08a8a;
|
||||
color: #9a5c35;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(135deg, #2f6bff, #4e9bff);
|
||||
/* 渐变两端都保证白字 ≥4.5:1(#9a5c35 为 5.31:1,#7d4a2a 为 7.28:1) */
|
||||
background: linear-gradient(135deg, #9a5c35, #7d4a2a);
|
||||
color: #fff;
|
||||
border-color: transparent;
|
||||
box-shadow: 0 4px 12px rgba(47, 107, 255, 0.28);
|
||||
box-shadow: 0 4px 12px rgba(139, 115, 85, 0.22);
|
||||
font-weight: 600;
|
||||
|
||||
&::before {
|
||||
@@ -304,38 +320,39 @@ export default {
|
||||
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
background: #fffdfa;
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
/* 必须是 fixed:顶栏(.navbar)有 overflow: hidden,
|
||||
absolute 会被裁成一条、且坐标基准是 .navbar 而不是视口 */
|
||||
position: fixed;
|
||||
list-style-type: none;
|
||||
padding: 6px;
|
||||
border-radius: 10px;
|
||||
min-width: 136px;
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
box-shadow: 0 8px 24px rgba(47, 107, 255, 0.12);
|
||||
color: #4f4130;
|
||||
border: 1px solid rgba(212, 160, 160, 0.55);
|
||||
box-shadow: 0 8px 24px rgba(139, 115, 85, 0.16);
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 8px;
|
||||
transition: all .4s ease-in-out;
|
||||
|
||||
i {
|
||||
margin-right: 6px;
|
||||
color: #7385a6;
|
||||
color: #7a664e;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(47, 107, 255, 0.08);
|
||||
color: #2f6bff;
|
||||
background: rgba(154, 92, 53, 0.10);
|
||||
color: #9a5c35;
|
||||
|
||||
i {
|
||||
color: #2f6bff;
|
||||
color: #9a5c35;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container"/>
|
||||
<div :class="{hasTagsView:needTagsView,sidebarHide:sidebar.hide}" class="main-container">
|
||||
<div :class="{'fixed-header':fixedHeader}">
|
||||
<!-- 标签页已并入 <navbar/>(原来「面包屑 + 标签页」两条栏重复显示当前位置) -->
|
||||
<navbar/>
|
||||
<tags-view v-if="needTagsView"/>
|
||||
</div>
|
||||
<div class="page-card">
|
||||
<app-main/>
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script>
|
||||
import RightPanel from '@/components/RightPanel'
|
||||
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
|
||||
import { AppMain, Navbar, Settings, Sidebar } from './components'
|
||||
import ResizeMixin from './mixin/ResizeHandler'
|
||||
import { mapState } from 'vuex'
|
||||
import variables from '@/assets/styles/variables.scss'
|
||||
@@ -32,8 +32,7 @@ export default {
|
||||
Navbar,
|
||||
RightPanel,
|
||||
Settings,
|
||||
Sidebar,
|
||||
TagsView
|
||||
Sidebar
|
||||
},
|
||||
mixins: [ResizeMixin],
|
||||
data() {
|
||||
|
||||
@@ -73,7 +73,7 @@ export default {
|
||||
lock: true,
|
||||
text: content,
|
||||
spinner: "el-icon-loading",
|
||||
background: "rgba(0, 0, 0, 0.7)",
|
||||
background: "rgba(79, 65, 48, 0.7)",
|
||||
})
|
||||
},
|
||||
// 关闭遮罩层
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
module.exports = {
|
||||
/**
|
||||
* 侧边栏主题 深色主题theme-dark,浅色主题theme-light
|
||||
* Cottagecore 皮肤下侧栏是奶油浅色卡片,故用 theme-light(配合 variables.scss 的 $base-menu-light-*)
|
||||
*/
|
||||
sideTheme: 'theme-dark',
|
||||
sideTheme: 'theme-light',
|
||||
|
||||
/**
|
||||
* 是否系统布局配置
|
||||
|
||||
@@ -5,7 +5,7 @@ const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dyn
|
||||
const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || ''
|
||||
const state = {
|
||||
title: '',
|
||||
theme: storageSetting.theme || '#409EFF',
|
||||
theme: storageSetting.theme || '#9a5c35',
|
||||
sideTheme: storageSetting.sideTheme || sideTheme,
|
||||
showSettings: showSettings,
|
||||
topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav,
|
||||
|
||||
@@ -156,7 +156,7 @@ service.interceptors.response.use(res => {
|
||||
|
||||
// 通用下载方法
|
||||
export function download(url, params, filename, config) {
|
||||
downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", })
|
||||
downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(79, 65, 48, 0.7)", })
|
||||
return service.post(url, params, {
|
||||
transformRequest: [(params) => { return tansParams(params) }],
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="10" style="margin-bottom: 16px;">
|
||||
<el-select v-model="filter.boxType" placeholder="按分类筛选" clearable @change="getList" style="width:180px;margin-right:10px">
|
||||
<el-option v-for="t in boxTypes" :key="t" :label="boxTypeLabels[t]" :value="t" />
|
||||
</el-select>
|
||||
<el-input v-model="filter.name" placeholder="按名称搜索" clearable @keyup.enter.native="getList" @clear="getList" style="width:200px">
|
||||
<el-button slot="append" icon="el-icon-search" @click="getList" />
|
||||
</el-input>
|
||||
<el-button type="danger" icon="el-icon-refresh" @click="handleRecalc" :loading="recalcLoading" style="float:right">一键重算分布</el-button>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="boxList" border>
|
||||
<el-table-column prop="box_id" label="ID" min-width="50" />
|
||||
<el-table-column prop="name" label="名称" min-width="150" />
|
||||
<el-table-column label="箱子类型" min-width="120">
|
||||
<template slot-scope="scope">{{ boxTypeLabels[scope.row.box_type] || scope.row.box_type }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="price" label="价格(€)" min-width="90" />
|
||||
<el-table-column prop="total_ornaments" label="饰品总数" min-width="90" />
|
||||
<el-table-column prop="rtp" label="RTP" min-width="80">
|
||||
<template slot-scope="scope">{{ (scope.row.rtp * 100).toFixed(1) }}%</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" min-width="60">
|
||||
<template slot-scope="scope">{{ scope.row.status == '1' ? '启用' : '禁用' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="260">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="info" @click="handleOrnaments(scope.row)">饰品配置</el-button>
|
||||
<el-button size="mini" type="warning" @click="handleVersions(scope.row)">版本</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
style="margin-top:16px;text-align:right"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="total"
|
||||
:page-size="filter.size"
|
||||
:current-page="filter.page"
|
||||
:page-sizes="[5,10,20,50]"
|
||||
@size-change="s=>{filter.size=s;getList()}"
|
||||
@current-change="p=>{filter.page=p;getList()}"
|
||||
/>
|
||||
|
||||
<el-dialog title="编辑箱子" :visible.sync="dialogVisible" width="480px">
|
||||
<el-form ref="form" :model="form" label-width="110px">
|
||||
<el-form-item label="箱子类型" prop="boxType">
|
||||
<el-select v-model="form.boxType" disabled>
|
||||
<el-option v-for="t in boxTypes" :key="t" :label="boxTypeLabels[t]" :value="t" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="下注额" prop="bet">
|
||||
<el-input-number v-model="form.bet" :disabled="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格 (自动)">
|
||||
<el-input-number v-model="form.price" :disabled="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="饰品总数" prop="totalOrnaments">
|
||||
<el-input-number v-model="form.totalOrnaments" :min="0" :step="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="RTP" prop="rtp">
|
||||
<el-input-number v-model="form.rtp" :min="0" :max="1" :precision="3" :step="0.05" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SCATTER权重" prop="scatterWeight" v-if="form.boxType === 'buy1_bonushunt'">
|
||||
<el-input-number v-model="form.scatterWeight" :min="1" :max="20" :precision="1" :step="0.5" />
|
||||
<span style="margin-left:8px;color:#999;font-size:12px">幸运加成, 默认2.0</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status" v-if="isEdit">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio label="1">启用</el-radio>
|
||||
<el-radio label="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :title="'饰品配置 - ' + (boxTypeLabels[currentBox.box_type] || currentBox.box_type) + ' (€' + currentBox.price + ')'":visible.sync="ornamentVisible" width="650px">
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:10px">
|
||||
修改数量后保存即可。<br/>
|
||||
当前总回拨: {{ ornamentTotal }} 计算RTP: {{ ornamentRTP }}% 目标RTP: {{ (currentBox.rtp * 100).toFixed(1) }}%
|
||||
<br/>
|
||||
未分配饰品: <span :style="{ color: ornamentRemaining !== 0 ? '#F56C6C' : '#67C23A', fontWeight: 'bold' }">{{ ornamentRemaining }}</span>
|
||||
<span v-if="ornamentRemaining > 0" style="color:#909399"> (还需分配)</span>
|
||||
<span v-else-if="ornamentRemaining === 0" style="color:#67C23A"> (已分配完毕)</span>
|
||||
<span v-else style="color:#F56C6C"> (超出 {{ Math.abs(ornamentRemaining) }} 个)</span>
|
||||
</el-alert>
|
||||
<el-table :data="ornamentList" border>
|
||||
<el-table-column label="饰品ID" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="!scope.row.isNew">{{ getOrnamentName(scope.row.ornamentId) }}</span>
|
||||
<el-select v-else v-model="scope.row.ornamentId" placeholder="选择饰品" size="small" style="width:130px">
|
||||
<el-option v-for="o in availableOrnaments" :key="o.id" :label="o.label" :value="o.id" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="饰品名称" min-width="120">
|
||||
<template slot-scope="scope">{{ getOrnamentName(scope.row.ornamentId) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-input-number v-model="scope.row.count" :min="0" :step="10" size="small" controls-position="right" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剩余回合" min-width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{ color: (scope.row.remaining || 0) < 100 ? '#F56C6C' : '#67C23A', fontWeight: 'bold' }">
|
||||
{{ scope.row.remaining || 0 }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div slot="footer">
|
||||
<el-button type="primary" @click="saveOrnaments">保存配置</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :title="'版本管理 - ' + (boxTypeLabels[currentBox.box_type] || currentBox.box_type) + ' (€' + currentBox.price + ')'" :visible.sync="versionVisible" width="650px">
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:10px">
|
||||
当前活跃版本: <b>v{{ currentBox.active_version || 1 }}</b> |
|
||||
SCATTER权重: <b>{{ currentBox.scatter_weight || '2.00' }}</b>
|
||||
</el-alert>
|
||||
<el-table :data="versionList" border>
|
||||
<el-table-column label="版本" prop="version" width="80">
|
||||
<template slot-scope="scope"><b>v{{ scope.row.version }}</b></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="SCATTER权重" prop="scatterWeight" width="130" />
|
||||
<el-table-column label="状态" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === 'active' ? 'success' : scope.row.status === 'draft' ? 'warning' : 'info'">
|
||||
{{ scope.row.status === 'active' ? '当前' : scope.row.status === 'draft' ? '草稿' : '归档' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" prop="createTime" width="170" />
|
||||
<el-table-column label="操作" width="140">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="scope.row.status === 'draft'" size="mini" type="primary" @click="handlePublish(scope.row)">发布</el-button>
|
||||
<el-button v-if="scope.row.status === 'archived'" size="mini" type="warning" @click="handleRollback(scope.row)">回退</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div slot="footer">
|
||||
<el-button @click="versionVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { boxList, boxSave, boxOrnaments, saveOrnaments, recalcAll, getBoxVersions, publishVersion, rollbackVersion } from "@/api/game/bullet/box/api";
|
||||
|
||||
export default {
|
||||
name: "BulletBox",
|
||||
data() {
|
||||
return {
|
||||
loading: false, recalcLoading: false, boxList: [], total: 0,
|
||||
filter: { page: 1, size: 10, boxType: "", name: "" },
|
||||
dialogVisible: false, ornamentVisible: false, isEdit: false, currentBox: {},
|
||||
form: { boxType: "", bet: 1, price: 1, totalOrnaments: 500, rtp: 0.8, scatterWeight: 2.0 },
|
||||
boxTypes: ["normal", "buy1_bonushunt", "buy2_bad_blood", "buy3_draw_or_die", "buy4_true_grit", "buy5_four_shots", "buy6_go_ahead"],
|
||||
boxTypeLabels: { "normal":"普通", "buy1_bonushunt":"幸运加成", "buy2_bad_blood":"匪徒高发", "buy3_draw_or_die":"赏金频现", "buy4_true_grit":"幸运常驻", "buy5_four_shots":"欧皇旅途", "buy6_go_ahead":"终极之路" },
|
||||
boxTypeCM: { "normal":1, "buy1_bonushunt":5, "buy2_bad_blood":60, "buy3_draw_or_die":60, "buy4_true_grit":80, "buy5_four_shots":200, "buy6_go_ahead":400 },
|
||||
ornamentList: [], _ornamentAll: [], _ornamentNames: {},
|
||||
versionVisible: false, versionList: [], activeVersion: null, pollTimer: null
|
||||
};
|
||||
},
|
||||
created() { this.getList(); },
|
||||
computed: {
|
||||
availableOrnaments() {
|
||||
const usedIds = this.ornamentList.filter(o => o.ornamentId > 0).map(o => o.ornamentId);
|
||||
return this._ornamentAll.filter(o => !usedIds.includes(o.id));
|
||||
},
|
||||
ornamentTotal() {
|
||||
return this.ornamentList.reduce((s, o) => s + (o.count || 0), 0);
|
||||
},
|
||||
ornamentRemaining() {
|
||||
const total = this.currentBox.total_ornaments || 0;
|
||||
return total - this.ornamentTotal;
|
||||
},
|
||||
ornamentRTP() {
|
||||
const total = this.ornamentTotal;
|
||||
if (!total || !this.currentBox.bet) return '0.0';
|
||||
let contrib = 0;
|
||||
this.ornamentList.forEach(o => {
|
||||
const mul = this._ornamentExpects && this._ornamentExpects[o.ornamentId];
|
||||
if (mul != null) contrib += (o.count || 0) * mul;
|
||||
});
|
||||
const cm = this.boxTypeCM[this.currentBox.box_type] || 1;
|
||||
return (contrib / total / cm * 100).toFixed(1);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
ornamentVisible(val) {
|
||||
if (val) this.startPolling();
|
||||
else this.stopPolling();
|
||||
}
|
||||
},
|
||||
beforeDestroy() { this.stopPolling(); },
|
||||
methods: {
|
||||
handleRecalc() {
|
||||
this.$modal.confirm('确认重算所有箱子的饰品分布并重置Redis预热回合吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', closeOnClickModal: false }).then(() => {
|
||||
this.recalcLoading = true;
|
||||
recalcAll().then(() => { this.$message.success('重算完成'); this.getList(); }).finally(() => { this.recalcLoading = false; });
|
||||
}).catch(() => {});
|
||||
return;
|
||||
},
|
||||
_handleRecalcOld() {
|
||||
this.$modal.confirm('确认重算所有箱子的饰品分布并重置Redis预热回合吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
|
||||
.then(() => {
|
||||
this.recalcLoading = true;
|
||||
recalcAll().then(() => { this.$message.success('重算完成'); this.getList(); }).finally(() => { this.recalcLoading = false; });
|
||||
}).catch(() => {});
|
||||
},
|
||||
getList() {
|
||||
this.loading = true;
|
||||
boxList({ page: this.filter.page, size: this.filter.size, boxType: this.filter.boxType || undefined, name: this.filter.name || undefined })
|
||||
.then(r => { this.boxList = r.data.rows || []; this.total = r.data.total || 0; this.loading = false; });
|
||||
},
|
||||
calcPrice() {
|
||||
const cm = this.boxTypeCM[this.form.boxType] || 1;
|
||||
this.form.price = +(this.form.bet || 0) * cm;
|
||||
},
|
||||
handleEdit(row) { this.isEdit = true; this.form = { boxId: row.box_id, boxType: row.box_type, bet: row.bet, price: row.price, totalOrnaments: row.total_ornaments, rtp: row.rtp, scatterWeight: row.scatter_weight != null ? row.scatter_weight : 2.0, gameType: "bullet", status: row.status }; this.dialogVisible = true; },
|
||||
submitForm() {
|
||||
const data = { gameType: "bullet", ...this.form }; if (this.isEdit) data.boxId = this.form.boxId;
|
||||
boxSave(data).then(r => { if (r.code !== 200) { this.$modal.msgError(r.msg); return; } this.$message.success("保存成功"); this.dialogVisible = false; this.getList(); });
|
||||
},
|
||||
handleOrnaments(row) { this.currentBox = row; boxOrnaments(row.box_id).then(r => { this.ornamentList = (r.data || []).map(o => ({ ornamentId: Number(o.ornamentId), count: o.count, remaining: o.remaining || 0, isNew: false })); this.loadOrnamentNames().then(() => { this.ornamentList = [...this.ornamentList]; this.ornamentVisible = true; }); }); },
|
||||
loadOrnamentNames() { return import("@/api/game/bullet/ornament/api").then(api => api.ornamentList().then(r => { this._ornamentNames = {}; this._ornamentExpects = {}; this._ornamentAll = []; (r.data || []).forEach(o => { this._ornamentNames[o.id] = o.expect_mul + '倍'; this._ornamentExpects[o.id] = Number(o.expect_mul); this._ornamentAll.push({id: o.id, label: 'ID' + o.id + ' ' + o.expect_mul + '倍'}); }); })); },
|
||||
getOrnamentName(id) { if (!id || id <= 0) return '-'; return (this._ornamentNames && this._ornamentNames[id]) || ('ID:' + id); },
|
||||
addOrnamentRow() { this.ornamentList.push({ ornamentId: null, count: 0, isNew: true }); },
|
||||
startPolling() { this.stopPolling(); this.pollRemaining(); this.pollTimer = setInterval(() => { this.pollRemaining(); }, 3000); },
|
||||
stopPolling() { if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; } },
|
||||
pollRemaining() {
|
||||
if (!this.currentBox || !this.currentBox.box_id) return;
|
||||
boxOrnaments(this.currentBox.box_id).then(r => {
|
||||
const latest = r.data || [];
|
||||
this.ornamentList.forEach(row => {
|
||||
const match = latest.find(o => Number(o.ornamentId) === row.ornamentId);
|
||||
if (match) row.remaining = match.remaining || 0;
|
||||
});
|
||||
}).catch(() => {});
|
||||
},
|
||||
saveOrnaments() { const data = this.ornamentList.filter(o => o.ornamentId > 0).map(o => ({ ornamentId: Number(o.ornamentId), count: o.count })); if (data.length === 0) { this.$message.warning("请至少添加一个饰品"); return; } saveOrnaments(this.currentBox.box_id, data).then(() => { this.$message.success("保存成功"); this.ornamentVisible = false; this.getList(); }); },
|
||||
handleVersions(row) {
|
||||
this.currentBox = row;
|
||||
getBoxVersions(row.box_id).then(r => {
|
||||
this.versionList = r.data || [];
|
||||
this.versionVisible = true;
|
||||
});
|
||||
},
|
||||
handlePublish(row) {
|
||||
this.$modal.confirm(`确认发布 v${row.version} 吗?发布后玩家将使用该版本数据。`, '提示', { confirmButtonText: '确定', type: 'warning' })
|
||||
.then(() => {
|
||||
publishVersion(this.currentBox.box_id, row.version).then(() => {
|
||||
this.$message.success('发布成功');
|
||||
this.versionVisible = false;
|
||||
this.getList();
|
||||
});
|
||||
}).catch(() => {});
|
||||
},
|
||||
handleRollback(row) {
|
||||
this.$modal.confirm(`确认回退到 v${row.version} 吗?当前版本将变为归档。`, '提示', { confirmButtonText: '确定', type: 'warning' })
|
||||
.then(() => {
|
||||
rollbackVersion(this.currentBox.box_id, row.version).then(() => {
|
||||
this.$message.success('回退成功');
|
||||
this.versionVisible = false;
|
||||
this.getList();
|
||||
});
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="6"><el-card shadow="hover"><div style="text-align:center"><small>累计投注</small><h3>{{ summary.totalBet }}</h3></div></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><div style="text-align:center"><small>累计赢奖</small><h3>{{ summary.totalWin }}</h3></div></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><div style="text-align:center"><small>整体RTP</small><h3 :style="{color: summary.rtp > 100 ? 'red' : 'green'}">{{ summary.rtp }}%</h3></div></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><div style="text-align:center"><small>总局数</small><h3>{{ summary.totalCount }}</h3></div></el-card></el-col>
|
||||
</el-row>
|
||||
<div style="margin-bottom: 16px;">
|
||||
<el-date-picker v-model="queryParams.dateStart" type="date" placeholder="开始日期" value-format="yyyy-MM-dd" style="width:150px;margin-right:8px" />
|
||||
<span style="margin:0 4px">至</span>
|
||||
<el-date-picker v-model="queryParams.dateEnd" type="date" placeholder="结束日期" value-format="yyyy-MM-dd" style="width:150px;margin-right:8px" />
|
||||
<el-button type="primary" icon="el-icon-search" @click="getList">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="winList" border>
|
||||
<el-table-column label="日期" min-width="120">
|
||||
<template slot-scope="scope">{{ formatDate(scope.row.date) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="total_bet" label="总投注" min-width="130" />
|
||||
<el-table-column prop="total_win" label="总赢奖" min-width="130" />
|
||||
<el-table-column prop="count" label="总局数" min-width="80" />
|
||||
<el-table-column prop="bonus_count" label="Bonus购买" min-width="100" />
|
||||
<el-table-column label="RTP" min-width="90">
|
||||
<template slot-scope="scope"><span :style="{color: scope.row.total_bet > 0 && scope.row.total_win/scope.row.total_bet > 1 ? 'red' : 'green'}">{{ scope.row.total_bet > 0 ? (scope.row.total_win / scope.row.total_bet * 100).toFixed(1) + '%' : '-' }}</span></template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-show="total>0" :total="total" :page.sync="queryParams.page" :limit.sync="queryParams.size" @pagination="getList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { winStats } from "@/api/game/bullet/dailywin/api";
|
||||
import { parseTime } from "@/utils/ruoyi";
|
||||
export default {
|
||||
name: "BulletDailyWin",
|
||||
data() { return { loading: false, winList: [], total: 0, summary: { totalBet: "0", totalWin: "0", totalCount: 0, rtp: "0.0" }, queryParams: { page: 1, size: 10, dateStart: null, dateEnd: null } }; },
|
||||
created() { this.getList(); },
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true;
|
||||
winStats({ page: this.queryParams.page, size: this.queryParams.size, dateStart: this.queryParams.dateStart, dateEnd: this.queryParams.dateEnd }).then(r => {
|
||||
const d = r.data;
|
||||
this.winList = (d && d.rows) ? d.rows : (d || []);
|
||||
this.total = (d && d.total) || 0;
|
||||
if (d) {
|
||||
this.summary = {
|
||||
totalBet: Number(d.totalBet || 0).toFixed(2),
|
||||
totalWin: Number(d.totalWin || 0).toFixed(2),
|
||||
totalCount: Number(d.totalCount || 0),
|
||||
rtp: Number(d.totalBet) > 0 ? (Number(d.totalWin) / Number(d.totalBet) * 100).toFixed(1) : "0.0"
|
||||
};
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleReset() { this.queryParams = { page: 1, size: 10, dateStart: null, dateEnd: null }; this.getList(); },
|
||||
formatDate(v) { if (!v) return ''; const d = new Date(v); return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0'); }
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,23 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:10px">
|
||||
饰品即倍率桶,由 Python 模拟器生成。每个桶的 expect_mul 对应一个预生成的回合数据池。
|
||||
</el-alert>
|
||||
<el-table v-loading="loading" :data="list" border>
|
||||
<el-table-column prop="id" label="ID" min-width="60" />
|
||||
<el-table-column prop="name" label="名称" min-width="150" />
|
||||
<el-table-column prop="expect_mul" label="期望倍率" min-width="100" />
|
||||
<el-table-column prop="game_type" label="游戏" min-width="80" />
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ornamentList } from "@/api/game/bullet/ornament/api";
|
||||
export default {
|
||||
name: "BulletOrnament",
|
||||
data() { return { loading: false, list: [] }; },
|
||||
created() { this.getList(); },
|
||||
methods: { getList() { this.loading = true; ornamentList().then(r => { this.list = r.data || []; this.loading = false; }); } }
|
||||
};
|
||||
</script>
|
||||
@@ -1,74 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-table v-loading="loading" :data="list" border>
|
||||
<el-table-column prop="id" label="ID" min-width="100" />
|
||||
<el-table-column prop="name" label="名称" min-width="400" show-overflow-tooltip align="center" />
|
||||
<el-table-column prop="grade" label="品阶" min-width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.grade === 1" type="warning" size="small">金</el-tag>
|
||||
<el-tag v-else-if="scope.row.grade === 2" type="danger" size="small">紫</el-tag>
|
||||
<el-tag v-else-if="scope.row.grade === 3" size="small">红</el-tag>
|
||||
<el-tag v-else-if="scope.row.grade === 4" type="success" size="small">绿</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="饰品图片" min-width="250" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-image v-if="scope.row.image_url" :src="getImageUrl(scope.row.image_url)" style="width:48px;height:48px" fit="cover" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="exterior_name" label="品质" min-width="120" align="center" />
|
||||
<el-table-column label="等阶图片" min-width="250" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-image v-if="scope.row.level_image_url" :src="getImageUrl(scope.row.level_image_url)" style="width:48px;height:48px" fit="cover" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="创建时间" min-width="250" />
|
||||
<el-table-column label="操作" min-width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog title="编辑奖品" :visible.sync="dialogVisible" min-width="480px">
|
||||
<el-form ref="form" :model="form" label-min-width="80px">
|
||||
<el-form-item label="名称"><el-input v-model="form.name" /></el-form-item>
|
||||
<el-form-item label="品阶">
|
||||
<el-select v-model="form.grade" style="width:100%">
|
||||
<el-option label="无品阶" :value="null" />
|
||||
<el-option label="1 - 金品" :value="1" />
|
||||
<el-option label="2 - 紫品" :value="2" />
|
||||
<el-option label="3 - 红品" :value="3" />
|
||||
<el-option label="4 - 绿品" :value="4" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="品质"><el-input v-model="form.exterior_name" placeholder="崭新出厂/战痕累累等" /></el-form-item>
|
||||
<el-form-item label="图片URL"><el-input v-model="form.image_url" /></el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { prizeList, prizeSave } from "@/api/game/bullet/prize/api";
|
||||
export default {
|
||||
name: "BulletPrize",
|
||||
data() {
|
||||
return { loading: false, list: [], dialogVisible: false, form: {} };
|
||||
},
|
||||
created() { this.getList(); },
|
||||
methods: {
|
||||
getList() { this.loading = true; prizeList().then(r => { this.list = r.data || []; this.loading = false; }); },
|
||||
getImageUrl(url) { if (!url) return ''; return url.startsWith('http') ? url : (process.env.VUE_APP_BASE_API || '') + url; },
|
||||
handleEdit(row) { this.form = { ...row }; this.dialogVisible = true; },
|
||||
submitForm() { prizeSave(this.form).then(() => { this.$message.success("保存成功"); this.dialogVisible = false; this.getList(); }); }
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,148 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 汇总卡片 -->
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover"><div style="text-align:center"><small>总投注</small><h3>{{ summary.totalBet }}</h3></div></el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover"><div style="text-align:center"><small>总赢奖</small><h3>{{ summary.totalWin }}</h3></div></el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover"><div style="text-align:center"><small>总局数</small><h3>{{ summary.totalCount }}</h3></div></el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover"><div style="text-align:center"><small>RTP</small><h3 :style="{color: summary.rtp > 100 ? 'red' : 'green'}">{{ summary.rtp }}%</h3></div></el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form :inline="true" :model="queryParams">
|
||||
<el-form-item label="用户ID">
|
||||
<el-input v-model="queryParams.userId" placeholder="用户ID" clearable style="width:120px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="箱子类型">
|
||||
<el-select v-model="queryParams.boxType" placeholder="全部" clearable style="width:180px">
|
||||
<el-option v-for="t in boxTypes" :key="t" :label="boxTypeLabels[t] || t" :value="t" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="日期范围">
|
||||
<el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始" end-placeholder="结束" value-format="yyyy-MM-dd" style="width:260px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="getList">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
<el-button type="danger" plain @click="handleClear">清空所有数据</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" :data="recordList" border>
|
||||
<el-table-column prop="user_id" label="用户ID" min-width="80" />
|
||||
<el-table-column prop="nick_name" label="昵称" min-width="110" />
|
||||
<el-table-column prop="bet" label="投注" min-width="90" />
|
||||
<el-table-column label="赢奖" min-width="90">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{color: Number(scope.row.win) > Number(scope.row.bet) ? 'green' : (Number(scope.row.win) > 0 ? '#333' : '#999')}">
|
||||
{{ scope.row.win }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="倍率" min-width="70">
|
||||
<template slot-scope="scope">{{ scope.row.multiplier }}x</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="box_type" label="箱子类型" min-width="150">
|
||||
<template slot-scope="scope">{{ boxTypeLabels[scope.row.box_type] || scope.row.box_type }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ornament_name" label="饰品名称" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.ornament_name || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="饰品图片" min-width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-image v-if="scope.row.image_url" :src="getImageUrl(scope.row.image_url)" style="width:40px;height:40px" fit="cover" :preview-src-list="[getImageUrl(scope.row.image_url)]" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" min-width="70">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status == '1' ? 'success' : 'info'" size="small">{{ scope.row.status == '1' ? '完成' : '进行' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="时间" min-width="150" />
|
||||
<el-table-column label="操作" min-width="60">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.page" :limit.sync="queryParams.size" @pagination="getList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { recordList, deleteRecord, clearRecords } from "@/api/game/bullet/record/api";
|
||||
|
||||
export default {
|
||||
name: "BulletRecord",
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
recordList: [],
|
||||
total: 0,
|
||||
dateRange: [],
|
||||
summary: { totalBet: 0, totalWin: 0, totalCount: 0, rtp: 0 },
|
||||
boxTypes: ["normal", "buy1_bonushunt", "buy2_bad_blood", "buy3_draw_or_die", "buy4_true_grit", "buy5_four_shots", "buy6_go_ahead"],
|
||||
boxTypeLabels: { "normal":"普通", "buy1_bonushunt":"幸运加成", "buy2_bad_blood":"匪徒高发", "buy3_draw_or_die":"赏金频现", "buy4_true_grit":"幸运常驻", "buy5_four_shots":"欧皇旅途", "buy6_go_ahead":"终极之路" },
|
||||
queryParams: { page: 1, size: 10, userId: "", boxType: "" }
|
||||
};
|
||||
},
|
||||
created() { this.getList(); },
|
||||
methods: {
|
||||
getImageUrl(url) {
|
||||
if (!url) return '';
|
||||
if (url.startsWith('http')) return url;
|
||||
return (process.env.VUE_APP_BASE_API || '') + url;
|
||||
},
|
||||
getList() {
|
||||
this.loading = true;
|
||||
const params = { page: this.queryParams.page, size: this.queryParams.size };
|
||||
if (this.queryParams.userId) params.userId = this.queryParams.userId;
|
||||
if (this.queryParams.boxType) params.boxType = this.queryParams.boxType;
|
||||
recordList(params).then(r => {
|
||||
const d = r.data;
|
||||
const rows = d.rows || d || [];
|
||||
this.total = d.total || 0;
|
||||
this.summary = {
|
||||
totalBet: Number(d.totalBet || 0).toFixed(2),
|
||||
totalWin: Number(d.totalWin || 0).toFixed(2),
|
||||
totalCount: Number(d.totalCount || 0),
|
||||
rtp: Number(d.totalBet) > 0 ? (Number(d.totalWin) / Number(d.totalBet) * 100).toFixed(1) : "0.0"
|
||||
};
|
||||
if (this.dateRange && this.dateRange.length === 2) {
|
||||
const [start, end] = this.dateRange;
|
||||
this.recordList = rows.filter(r2 => r2.create_time && r2.create_time >= start && r2.create_time <= end + " 23:59:59");
|
||||
} else {
|
||||
this.recordList = rows;
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
resetQuery() {
|
||||
this.queryParams = { page: 1, size: 10, userId: "", boxType: "" };
|
||||
this.dateRange = [];
|
||||
this.getList();
|
||||
},
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确定删除该记录吗?`, "警告", { type: "warning" }).then(() => {
|
||||
deleteRecord(row.id).then(() => { this.$message.success("删除成功"); this.getList(); });
|
||||
});
|
||||
},
|
||||
handleClear() {
|
||||
this.$confirm("确定清空所有玩家记录吗?此操作不可恢复!", "严重警告", { type: "warning", confirmButtonText: "确定清空", confirmButtonClass: "el-button--danger" }).then(() => {
|
||||
clearRecords().then(() => { this.$message.success("已清空全部记录"); this.getList(); });
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert type="warning" :closable="false" style="margin-bottom:10px">
|
||||
删除回合会级联删除关联的 game_step。回合由 Python 模拟器生成,删除后需重新运行模拟器补充。
|
||||
为保证不误删已去掉删除功能,可执行 Python 模拟器进行覆盖。
|
||||
</el-alert>
|
||||
|
||||
<el-row :gutter="12" style="margin-bottom:16px">
|
||||
<el-col :span="4"><el-card shadow="hover"><div style="text-align:center"><small>总回合</small><h3>{{ stats.total }}</h3></div></el-card></el-col>
|
||||
<el-col :span="4" v-for="s in stats.breakdown" :key="s.key"><el-card shadow="hover"><div style="text-align:center"><small>{{ typeLabels[s.key] || s.key }}</small><h3>{{ s.count }}</h3></div></el-card></el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form :inline="true" :model="queryParams">
|
||||
<el-form-item label="回合ID"><el-input-number v-model="queryParams.roundId" :min="0" style="width:130px" /></el-form-item>
|
||||
<el-form-item label="Bonus类型">
|
||||
<el-select v-model="queryParams.bonusType" style="width:150px">
|
||||
<el-option v-for="b in bonusTypes" :key="b" :label="bonusLabels[b] || b" :value="b" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item><el-button type="primary" icon="el-icon-search" @click="getList">查询</el-button></el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" :data="roundList" border>
|
||||
<el-table-column type="expand">
|
||||
<template slot-scope="scope"><div style="padding:10px 20px"><p> ornament={{ scope.row.ornament_id }} | bonus_type={{ scope.row.bonus_type || '普通' }} | scatters={{ scope.row.scatters }} | 总步数={{ scope.row.total_steps }} | gamble={{ scope.row.gamble_result || '无' }} | actual_mul={{ scope.row.actual_mul }}</p></div></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="round_id" label="ID" min-width="70" />
|
||||
<el-table-column prop="name" label="饰品" min-width="100" />
|
||||
<el-table-column prop="expect_mul" label="期望倍率" min-width="80" />
|
||||
<el-table-column prop="actual_mul" label="实际倍率" min-width="80" />
|
||||
<el-table-column label="偏差" min-width="70">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{color: Math.abs(scope.row.actual_mul - scope.row.expect_mul) > scope.row.expect_mul * 0.5 ? 'red' : '#333'}">
|
||||
{{ scope.row.expect_mul > 0 ? ((scope.row.actual_mul / scope.row.expect_mul * 100 - 100).toFixed(0) + '%') : '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="box_type" label="Bonus" min-width="130"><template slot-scope="scope">{{ typeLabels[scope.row.box_type] || scope.row.box_type || '普通' }}</template></el-table-column>
|
||||
<el-table-column prop="scatters" label="FS" min-width="50" />
|
||||
<el-table-column prop="total_steps" label="步数" min-width="50" />
|
||||
<el-table-column prop="is_buy" label="模式" min-width="60"><template slot-scope="scope"><el-tag :type="scope.row.is_buy ? 'warning' : 'info'" size="small">{{ scope.row.is_buy ? '购买' : '普通' }}</el-tag></template></el-table-column>
|
||||
<el-table-column prop="create_time" label="创建时间" min-width="160" />
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.page" :limit.sync="queryParams.size" @pagination="getList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { roundList } from "@/api/game/bullet/round/api";
|
||||
export default {
|
||||
name: "BulletRound",
|
||||
data() { return {
|
||||
loading: false, roundList: [], total: 0,
|
||||
stats: { total: 0, breakdown: [] },
|
||||
bonusTypes: ["ALL","normal","buy1_bonushunt","buy2_bad_blood","buy3_draw_or_die","buy4_true_grit","buy5_four_shots","buy6_go_ahead"],
|
||||
bonusLabels: { "ALL":"全部", "normal":"普通", "buy1_bonushunt":"幸运加成", "buy2_bad_blood":"匪徒高发", "buy3_draw_or_die":"赏金频现", "buy4_true_grit":"幸运常驻", "buy5_four_shots":"欧皇旅途", "buy6_go_ahead":"终极之路" },
|
||||
typeLabels: { "normal":"普通", "buy1_bonushunt":"幸运加成", "buy2_bad_blood":"匪徒高发", "buy3_draw_or_die":"赏金频现", "buy4_true_grit":"幸运常驻", "buy5_four_shots":"欧皇旅途", "buy6_go_ahead":"终极之路", "BONUSHUNT":"幸运加成", "BAD_BLOOD":"匪徒高发", "DRAW_OR_DIE":"赏金频现", "TRUE_GRIT":"幸运常驻", "FOUR_SHOTS":"欧皇旅途", "GO_AHEAD":"终极之路" },
|
||||
queryParams: { page: 1, size: 20, roundId: 0, bonusType: "ALL" }
|
||||
}; },
|
||||
created() { this.getList(); },
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true;
|
||||
const params = { page: this.queryParams.page, size: this.queryParams.size };
|
||||
if (this.queryParams.roundId > 0) params.roundId = this.queryParams.roundId;
|
||||
if (this.queryParams.bonusType) params.bonusType = this.queryParams.bonusType;
|
||||
roundList(params).then(r => {
|
||||
const d = r.data;
|
||||
this.roundList = (d && d.rows) ? d.rows : (d || []);
|
||||
this.total = (d && d.total) || 0;
|
||||
if (d && d.stats) {
|
||||
this.stats = { total: this.total, breakdown: d.stats.map(s => ({ key: s.name, count: s.cnt })) };
|
||||
}
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,451 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="10" style="margin-bottom:16px">
|
||||
<el-select v-model="queryParams.boxType" placeholder="按模式筛选" clearable style="width:180px;margin-right:10px" @change="loadData">
|
||||
<el-option v-for="t in boxTypes" :key="t" :label="boxTypeLabels[t]" :value="t" />
|
||||
</el-select>
|
||||
<el-input v-model="queryParams.name" placeholder="按名称搜索" clearable style="width:200px" @clear="loadData" @keyup.enter.native="loadData">
|
||||
<el-button slot="append" icon="el-icon-search" @click="loadData" />
|
||||
</el-input>
|
||||
<span style="margin-left:10px;margin-right:4px;color:#606266">身份:</span>
|
||||
<el-radio-group v-model="audienceFilter" size="small" @change="loadData">
|
||||
<el-radio-button v-for="a in audiences" :key="a.key" :label="a.key">{{ a.name }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button icon="el-icon-refresh" style="margin-left:10px" @click="loadData">刷新</el-button>
|
||||
<el-button type="danger" icon="el-icon-refresh" :loading="recalcLoading" style="float:right" @click="openRecalcDialog">一键重算分布</el-button>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="boxList" border>
|
||||
<el-table-column prop="boxId" label="ID" min-width="50" align="center" />
|
||||
<el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="模式" min-width="110" align="center">
|
||||
<template slot-scope="scope">{{ boxTypeLabels[scope.row.boxType] || scope.row.boxType }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="bet" label="赌注" min-width="70" align="center" />
|
||||
<el-table-column prop="price" label="价格" min-width="80" align="center" />
|
||||
<el-table-column label="总回合" min-width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="totalRoundsOf(scope.row) != null">{{ totalRoundsOf(scope.row) }}</span>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="目标RTP" min-width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<!-- 显示 1 位小数:配比是"整数条回合 × 桶内实际均值"分出来的,颗粒度(≈0.5 倍)比 2 位小数还粗,
|
||||
真实值只会在 79.97~80.03 之间抖;精确值留在 title 里,鼠标悬停可看 -->
|
||||
<span v-if="targetRtp(scope.row) != null" :title="targetRtp(scope.row) + '%'">{{ Number(targetRtp(scope.row)).toFixed(1) + '%' }}</span>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实时RTP" min-width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="actualRtp(scope.row) != null" :style="{color: actualRtp(scope.row) > (targetRtp(scope.row) != null ? targetRtp(scope.row) : 0) ? '#f56c6c' : '#67c23a'}">{{ actualRtp(scope.row) }}%</span>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" min-width="60" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === '1' ? 'success' : 'danger'" size="small">{{ scope.row.status === '1' ? '启用' : '禁用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="270" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="info" @click="handleOrnaments(scope.row)">饰品配置</el-button>
|
||||
<el-button size="mini" type="success" @click="openTemplateManager(scope.row)">模板管理</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
style="margin-top:16px;text-align:right"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="total"
|
||||
:page-size.sync="queryParams.pageSize"
|
||||
:current-page.sync="queryParams.pageNum"
|
||||
:page-sizes="[10,20,50,100]"
|
||||
@size-change="loadData"
|
||||
@current-change="loadData"
|
||||
/>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<el-dialog :title="'编辑箱子 - ' + (form.name || '')" :visible.sync="dialogVisible" width="480px" append-to-body> <el-form ref="form" :model="form" label-width="110px">
|
||||
<el-form-item label="名称"><el-input v-model="form.name" disabled /></el-form-item>
|
||||
<el-form-item label="模式类型"><el-input :value="boxTypeLabels[form.boxType] || form.boxType" disabled /></el-form-item>
|
||||
<el-form-item label="基本赌注"><el-input-number v-model="form.bet" disabled style="width:100%" /></el-form-item>
|
||||
<el-form-item label="价格(自动)">
|
||||
<el-input-number v-model="form.price" disabled style="width:100%" />
|
||||
<span style="color:#909399;font-size:12px">价格 = 赌注 × 倍率系数({{ boxTypeCM[form.boxType] }}x)</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="总回合数">
|
||||
<el-input-number v-model="form.totalRounds" :min="0" :step="50" style="width:100%" />
|
||||
<span style="color:#909399;font-size:12px">身份级参数,仅作用于身份【{{ audienceLabel(audienceFilter) }}】</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标RTP">
|
||||
<el-input-number v-model="form.rtp" :min="0" :max="1" :precision="3" :step="0.05" style="width:100%" />
|
||||
<span style="color:#909399;font-size:12px">身份级参数,仅作用于身份【{{ audienceLabel(audienceFilter) }}】</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="固定/收敛回合%">
|
||||
<el-input-number v-model="form.fixedPct" :min="0" :max="100" :step="5" style="width:38%" @change="onFixedPctChange" />
|
||||
<span style="margin-left:4px">%</span>
|
||||
<span style="margin:0 4%">/</span>
|
||||
<el-input-number v-model="form.convergePct" :min="0" :max="100" :step="5" style="width:38%" @change="onConvergePctChange" />
|
||||
<span style="margin-left:4px">%</span>
|
||||
<div style="color:#909399;font-size:12px">固定% + 收敛% = 100;收敛回合用于一轮内把实时RTP动态拉回目标,随保存作用于当前身份</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio label="1">启用</el-radio>
|
||||
<el-radio label="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" :loading="saveLoading" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 重算分布弹窗 -->
|
||||
<el-dialog title="重算分布" :visible.sync="recalcDialogVisible" width="480px" append-to-body>
|
||||
<el-form label-width="110px">
|
||||
<el-form-item label="身份范围">
|
||||
<el-radio-group v-model="recalcForm.onlyCurrent">
|
||||
<el-radio :label="false">全部身份</el-radio>
|
||||
<el-radio :label="true">仅当前身份【{{ audienceLabel(audienceFilter) }}】</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="模式范围">
|
||||
<el-select v-model="recalcForm.boxType" clearable placeholder="全部模式" style="width:100%">
|
||||
<el-option v-for="t in boxTypes" :key="t" :label="boxTypeLabels[t]" :value="t" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标RTP">
|
||||
<el-input-number v-model="recalcForm.rtp" :min="0" :max="1" :precision="3" :step="0.05" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="总回合">
|
||||
<el-input-number v-model="recalcForm.totalRounds" :min="50" :step="50" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="固定/收敛回合%">
|
||||
<el-input-number v-model="recalcForm.fixedPct" :min="0" :max="100" :step="5" style="width:40%" @change="onRecalcFixedPctChange" />
|
||||
<span style="margin-left:4px">%</span>
|
||||
<span style="margin:0 4%">/</span>
|
||||
<el-input-number v-model="recalcForm.convergePct" :min="0" :max="100" :step="5" style="width:40%" @change="onRecalcConvergePctChange" />
|
||||
<span style="margin-left:4px">%</span>
|
||||
<div style="color:#909399;font-size:12px">固定% + 收敛% = 100;收敛回合用于一轮内把实时RTP动态拉回目标</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="recalcDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" :loading="recalcLoading" @click="submitRecalc">确认重算</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 饰品配置弹窗(跟随列表页当前身份) -->
|
||||
<el-dialog :title="'饰品配置 - ' + (boxTypeLabels[currentBox.boxType] || currentBox.boxType) + ' (ID:' + currentBox.boxId + ')【' + audienceLabel(audience) + '】'" :visible.sync="ornamentVisible" width="720px" append-to-body>
|
||||
<div style="display:flex;align-items:center;margin-bottom:10px">
|
||||
<el-alert type="info" :closable="false" style="flex:1">
|
||||
身份【{{ audienceLabel(audience) }}】 计算回合(固定): <b>{{ ornamentTotal }}</b> |
|
||||
收敛回合: <b>{{ convergeRoundsDerived }}</b> | 总回合: <b>{{ totalRoundsDerived }}</b> |
|
||||
计算RTP(池内实际均值): <b>{{ ornamentRTP }}%</b> | 目标RTP: <b>{{ currentOrnamentTargetRtp }}%</b>
|
||||
</el-alert>
|
||||
<el-button type="warning" size="small" icon="el-icon-refresh" style="margin-left:10px;flex-shrink:0" @click="handleOrnamentRecalc">重算分布</el-button>
|
||||
</div>
|
||||
<el-table v-loading="ornamentLoading" :data="ornamentList" border max-height="420">
|
||||
<el-table-column prop="id" label="桶ID" width="60" align="center" />
|
||||
<el-table-column label="饰品" min-width="110" align="center">
|
||||
<template slot-scope="scope">{{ getOrnName(scope.row.name) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="倍率范围" min-width="120" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.rangeLow }} - {{ scope.row.rangeHigh }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回合数" min-width="140" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-input-number v-model="scope.row.count" :min="0" :step="10" size="small" controls-position="right" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剩余" min-width="70" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{color: (scope.row.remaining != null ? scope.row.remaining : scope.row.count) <= 0 ? '#f56c6c' : '#67c23a'}">{{ scope.row.remaining != null ? scope.row.remaining : scope.row.count }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="动态收敛回合" min-width="100" align="center">
|
||||
<template slot-scope="scope"><span style="color:#e6a23c">{{ scope.row.convergeCount || 0 }}</span></template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div slot="footer">
|
||||
<el-button @click="ornamentVisible = false">取 消</el-button>
|
||||
<el-button type="success" icon="el-icon-document-add" @click="handleSaveAsTemplate">保存为模板</el-button>
|
||||
<el-button type="primary" @click="saveOrnaments">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 模板管理弹窗(跟随列表页当前身份) -->
|
||||
<el-dialog :title="'模板管理 - ' + (boxTypeLabels[templateBox.boxType] || templateBox.boxType || '') + ' (ID:' + templateBox.boxId + ')【' + audienceLabel(templateAudience) + '】'" :visible.sync="templateDialogVisible" width="720px" append-to-body>
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:12px">
|
||||
当前为身份【{{ audienceLabel(templateAudience) }}】的模板。模板保存了该箱子该身份的饰品回合分布。选择后点【应用】可将该身份饰品数量恢复为模板值,并设为抽干补桶依据。
|
||||
</el-alert>
|
||||
<el-table v-if="boxTemplates.length > 0" :data="boxTemplates" border highlight-current-row @current-change="onTemplateRowChange">
|
||||
<el-table-column label="模板名称" min-width="150">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.name }}
|
||||
<el-tag v-if="scope.row.isActive === 1" type="success" size="mini" style="margin-left:6px">生效中</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="饰品数" width="80" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.items ? scope.row.items.length : 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="固定回合" width="90" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.totalRounds }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="保存时间" width="170" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.createTime }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="danger" @click="handleDeleteTemplate(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-else description="该身份暂无模板,请在饰品配置中修改数量后点【保存为模板】。" />
|
||||
<div slot="footer">
|
||||
<el-button @click="templateDialogVisible = false">关 闭</el-button>
|
||||
<el-button type="primary" :disabled="!selectedTemplateRow" @click="handleApplyTemplate">应用选中模板</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getBoxList, saveBox, getBoxOrnaments, saveBoxOrnaments, calcDistribution, getBoxTemplates, saveBoxTemplate, deleteBoxTemplate, applyBoxTemplate, recalcBatch } from '@/api/game/olympus/box/api'
|
||||
import { getOrnamentList } from '@/api/game/olympus/ornament/api'
|
||||
|
||||
export default {
|
||||
name: 'OlympusBox',
|
||||
data() {
|
||||
return {
|
||||
loading: false, recalcLoading: false, saveLoading: false, boxList: [], total: 0,
|
||||
queryParams: { pageNum: 1, pageSize: 10, name: null, boxType: null },
|
||||
audienceFilter: 'common',
|
||||
dialogVisible: false, ornamentVisible: false, currentBox: {},
|
||||
form: { boxId: null, name: '', bet: 1, boxType: 'normal', rtp: 0.8, status: '1', price: 0, totalRounds: 0, fixedPct: 85, convergePct: 15 },
|
||||
// 重算分布弹窗
|
||||
recalcDialogVisible: false,
|
||||
recalcForm: { onlyCurrent: false, boxType: '', rtp: 0.8, totalRounds: 200, fixedPct: 85, convergePct: 15 },
|
||||
boxTypes: ['normal', 'ante', 'buy_fs'],
|
||||
boxTypeLabels: { normal: '普通旋转', ante: '底注', buy_fs: '购买免费旋转' },
|
||||
boxTypeCM: { normal: 1, ante: 2, buy_fs: 50 },
|
||||
modeOrnRange: { normal: [1, 13], ante: [1, 14], buy_fs: [7, 17] },
|
||||
audiences: [{ key: 'common', name: '普通' }, { key: 'anchor', name: '主播' }, { key: 'robot', name: '机器人' }, { key: 'white', name: '白号' }],
|
||||
audience: 'common', ornamentLoading: false, ornamentList: [], ornamentAll: [], ornamentExpects: {},
|
||||
// 模板
|
||||
templateDialogVisible: false, templateBox: {}, templateAudience: 'common', boxTemplates: [], selectedTemplateRow: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ornamentTotal() { return this.ornamentList.reduce((s, o) => s + (o.count || 0), 0) },
|
||||
ornamentRTP() {
|
||||
const total = this.ornamentTotal
|
||||
if (!total || !this.currentBox.bet) return '0.0'
|
||||
let contrib = 0
|
||||
this.ornamentList.forEach(o => { const mul = this.ornamentExpects[o.id]; if (mul != null) contrib += (o.count || 0) * mul })
|
||||
// 扣费倍率优先用后端返回的 costMultiplier,避免这里的表跟后端脱节(曾漏改 buy_fs 20→50,算出 200%)
|
||||
const cm = Number(this.currentBox.costMultiplier) || this.boxTypeCM[this.currentBox.boxType] || 1
|
||||
return (contrib / total / cm * 100).toFixed(1)
|
||||
},
|
||||
convergeRatioVal() {
|
||||
let cr = this.currentBox.rtpConvergeRatio != null ? Number(this.currentBox.rtpConvergeRatio) : 0.15
|
||||
if (!(cr >= 0)) cr = 0.15
|
||||
return Math.min(0.9, Math.max(0, cr))
|
||||
},
|
||||
totalRoundsDerived() { const fixed = this.ornamentTotal; return fixed > 0 ? Math.round(fixed / (1 - this.convergeRatioVal)) : 0 },
|
||||
convergeRoundsDerived() { return Math.max(0, this.totalRoundsDerived - this.ornamentTotal) },
|
||||
// 当前身份目标RTP(%):优先取该身份分布反推值,无则回退箱子统一值
|
||||
// 统一按 1 位小数显示(后端回的是 2 位小数,例如 80.01),这样弹窗里"计算RTP"和"目标RTP"
|
||||
// 不会因为一个 1 位一个 2 位而看起来不一致
|
||||
currentOrnamentTargetRtp() {
|
||||
const m = this.currentBox.targetRtpByAudience
|
||||
const v = m && m[this.audience] != null ? m[this.audience] : null
|
||||
if (v != null) return Number(v).toFixed(1)
|
||||
return this.currentBox.rtp != null ? (this.currentBox.rtp * 100).toFixed(1) : '-'
|
||||
}
|
||||
},
|
||||
created() { this.loadData() },
|
||||
methods: {
|
||||
loadData() {
|
||||
this.loading = true
|
||||
const params = { pageNum: this.queryParams.pageNum, pageSize: this.queryParams.pageSize }
|
||||
if (this.queryParams.name) params.name = this.queryParams.name
|
||||
if (this.queryParams.boxType) params.boxType = this.queryParams.boxType
|
||||
getBoxList(params).then(res => {
|
||||
this.boxList = (res.data && res.data.rows) || []
|
||||
this.total = (res.data && res.data.total) || 0
|
||||
this.loading = false
|
||||
}).catch(() => { this.loading = false })
|
||||
},
|
||||
openRecalcDialog() {
|
||||
// 默认参数取当前列表第一行的当前身份反推值(无数据时用默认)
|
||||
const row = this.boxList[0] || {}
|
||||
let rtp = 0.8, totalRounds = 200, convergePct = 15
|
||||
const t = row.targetRtpByAudience && row.targetRtpByAudience[this.audienceFilter]
|
||||
if (t != null) rtp = Number(t) / 100
|
||||
const tr = row.totalRoundsByAudience && row.totalRoundsByAudience[this.audienceFilter]
|
||||
if (tr != null) totalRounds = Number(tr)
|
||||
if (row.rtpConvergeRatio != null) convergePct = Math.round(Number(row.rtpConvergeRatio) * 100)
|
||||
this.recalcForm = { onlyCurrent: false, boxType: '', rtp, totalRounds, fixedPct: 100 - convergePct, convergePct }
|
||||
this.recalcDialogVisible = true
|
||||
},
|
||||
onRecalcFixedPctChange(v) { const val = Math.min(100, Math.max(0, v || 0)); this.recalcForm.fixedPct = val; this.recalcForm.convergePct = 100 - val },
|
||||
onRecalcConvergePctChange(v) { const val = Math.min(100, Math.max(0, v || 0)); this.recalcForm.convergePct = val; this.recalcForm.fixedPct = 100 - val },
|
||||
submitRecalc() {
|
||||
const f = this.recalcForm
|
||||
const params = {
|
||||
boxType: f.boxType || null,
|
||||
audience: f.onlyCurrent ? this.audienceFilter : null,
|
||||
rtp: f.rtp,
|
||||
totalRounds: f.totalRounds,
|
||||
convergeRatio: f.convergePct / 100
|
||||
}
|
||||
this.recalcLoading = true
|
||||
recalcBatch(params).then(() => {
|
||||
this.$message.success('重算完成')
|
||||
this.recalcDialogVisible = false
|
||||
this.loadData()
|
||||
}).catch(e => this.$message.error('重算失败: ' + (e.message || e)))
|
||||
.finally(() => { this.recalcLoading = false })
|
||||
},
|
||||
handleEdit(row) {
|
||||
const convergePct = row.rtpConvergeRatio != null ? Math.round(row.rtpConvergeRatio * 100) : 15
|
||||
// 目标RTP 初始值 = 当前身份分布反推的目标(百分点/100),避免显示成其他身份的值
|
||||
let rtp = row.rtp != null ? row.rtp : 0.8
|
||||
const t = row.targetRtpByAudience && row.targetRtpByAudience[this.audienceFilter]
|
||||
if (t != null) rtp = Number(t) / 100
|
||||
// 总回合 初始值 = 当前身份分布反推的一轮总回合(身份级独立)
|
||||
let totalRounds = row.totalOrnaments || 0
|
||||
const tr = row.totalRoundsByAudience && row.totalRoundsByAudience[this.audienceFilter]
|
||||
if (tr != null) totalRounds = Number(tr)
|
||||
this.form = { ...row, totalRounds, rtp, status: row.status || '1', convergePct, fixedPct: 100 - convergePct }
|
||||
this.dialogVisible = true
|
||||
},
|
||||
onFixedPctChange(v) { const val = Math.min(100, Math.max(0, v || 0)); this.form.fixedPct = val; this.form.convergePct = 100 - val },
|
||||
onConvergePctChange(v) { const val = Math.min(100, Math.max(0, v || 0)); this.form.convergePct = val; this.form.fixedPct = 100 - val },
|
||||
submitForm() {
|
||||
const convergeRatio = (this.form.convergePct != null ? this.form.convergePct : 15) / 100
|
||||
const data = { boxId: this.form.boxId, name: this.form.name, bet: this.form.bet, boxType: this.form.boxType, rtp: this.form.rtp, status: this.form.status, rtpConvergeRatio: convergeRatio, totalOrnaments: this.form.totalRounds }
|
||||
this.saveLoading = true
|
||||
saveBox(data).then(res => {
|
||||
const boxId = this.form.boxId || (res.data && res.data.boxId)
|
||||
if (!boxId) { this.saveLoading = false; this.$message.success('保存成功'); this.dialogVisible = false; this.loadData(); return }
|
||||
// 保存后只重算当前身份
|
||||
calcDistribution({ boxId, audience: this.audienceFilter, totalRounds: this.form.totalRounds, rtp: this.form.rtp, convergeRatio })
|
||||
.then(() => { this.$message.success('修改成功,已重算【' + this.audienceLabel(this.audienceFilter) + '】身份分布') })
|
||||
.catch(() => { this.$message.success('修改成功(分布计算失败,可稍后用"重算分布"重试)') })
|
||||
.finally(() => { this.saveLoading = false; this.dialogVisible = false; this.loadData() })
|
||||
}).catch(() => { this.saveLoading = false })
|
||||
},
|
||||
handleOrnaments(row) {
|
||||
this.currentBox = row
|
||||
this.audience = this.audienceFilter // 跟随列表页当前身份
|
||||
getOrnamentList().then(ornRes => {
|
||||
this.ornamentAll = (ornRes.data && ornRes.data.list) ? ornRes.data.list : []
|
||||
this.ornamentExpects = {}
|
||||
this.ornamentAll.forEach(o => { this.ornamentExpects[o.id] = Number(o.expectMul) })
|
||||
this.ornamentVisible = true
|
||||
this._refreshOrnamentList()
|
||||
})
|
||||
},
|
||||
_refreshOrnamentList() {
|
||||
this.ornamentLoading = true
|
||||
getBoxOrnaments(this.currentBox.boxId, this.audience).then(res => {
|
||||
const items = (res.data && res.data.list) ? res.data.list : []
|
||||
// 「计算RTP」必须与后台「目标RTP」同一口径 = 该桶**池内实际均值**(AVG(actual_mul)),
|
||||
// 否则会拿桶定义的名义 expect_mul(区间中点)来算,两个数必然对不上(单桶能差 -18%~+11%)。
|
||||
// 池子里还没有该桶的回合时,后端会回退名义值,这里保持同样回退。
|
||||
const pe = (res.data && res.data.poolExpect) || {}
|
||||
const expects = {}
|
||||
this.ornamentAll.forEach(o => {
|
||||
expects[o.id] = pe[o.id] != null ? Number(pe[o.id]) : Number(o.expectMul)
|
||||
})
|
||||
this.ornamentExpects = expects
|
||||
const map = {}
|
||||
items.forEach(it => { map[it.ornamentId] = { count: it.count, remaining: it.remaining != null ? it.remaining : (it.count - (it.used || 0)), convergeCount: it.convergeCount } })
|
||||
const [lo, hi] = this.modeOrnRange[this.currentBox.boxType] || [1, 17]
|
||||
this.ornamentList = this.ornamentAll.filter(o => o.id >= lo && o.id <= hi)
|
||||
.map(o => ({ ...o, count: (map[o.id] && map[o.id].count) || 0, remaining: map[o.id] ? map[o.id].remaining : 0, convergeCount: map[o.id] ? map[o.id].convergeCount : 0 }))
|
||||
this.ornamentLoading = false
|
||||
}).catch(() => { this.ornamentLoading = false })
|
||||
},
|
||||
getOrnName(name) { return name ? name.replace('x', '倍饰品') : name },
|
||||
handleOrnamentRecalc() {
|
||||
const box = this.currentBox
|
||||
const cr = box.rtpConvergeRatio != null ? box.rtpConvergeRatio : 0.15
|
||||
// 目标RTP 优先用当前身份分布反推值,与"按身份编辑"语义一致
|
||||
const t = box.targetRtpByAudience && box.targetRtpByAudience[this.audience] != null ? Number(box.targetRtpByAudience[this.audience]) / 100 : (box.rtp || 0.8)
|
||||
// 总回合 用当前身份反推值(身份级独立)
|
||||
const tr = box.totalRoundsByAudience && box.totalRoundsByAudience[this.audience] != null ? Number(box.totalRoundsByAudience[this.audience]) : (box.totalOrnaments || 200)
|
||||
calcDistribution({ boxId: box.boxId, audience: this.audience, totalRounds: tr, rtp: t, convergeRatio: cr }).then(() => {
|
||||
this.$message.success('分布已重算并保存')
|
||||
this._refreshOrnamentList()
|
||||
this.loadData()
|
||||
}).catch(() => { this.$message.error('重算失败') })
|
||||
},
|
||||
saveOrnaments() {
|
||||
const items = this.ornamentList.filter(o => o.count > 0).map(o => ({ ornamentId: o.id, count: o.count }))
|
||||
saveBoxOrnaments({ boxId: this.currentBox.boxId, audience: this.audience, items }).then(() => {
|
||||
this.$message.success('保存成功')
|
||||
this.ornamentVisible = false
|
||||
this.loadData()
|
||||
})
|
||||
},
|
||||
handleSaveAsTemplate() {
|
||||
const items = this.ornamentList.filter(o => o.count > 0).map(o => ({ ornamentId: o.id, count: o.count }))
|
||||
if (items.length === 0) return this.$message.warning('当前没有配置任何饰品数量,无法保存模板')
|
||||
this.$prompt('请输入模板名称(同一箱子+身份内不可重复)', '保存为模板【' + this.audienceLabel(this.audience) + '】', { inputPattern: /\S+/, inputErrorMessage: '名称不能为空' })
|
||||
.then(({ value }) => {
|
||||
saveBoxTemplate({ boxId: this.currentBox.boxId, audience: this.audience, name: value.trim(), items })
|
||||
.then(() => this.$message.success('模板【' + value.trim() + '】已保存'))
|
||||
.catch(e => this.$message.error('保存失败: ' + (e.message || e)))
|
||||
}).catch(() => {})
|
||||
},
|
||||
// 模板管理
|
||||
openTemplateManager(row) { this.templateBox = row; this.templateAudience = this.audienceFilter; this.selectedTemplateRow = null; this.templateDialogVisible = true; this._loadTemplates() },
|
||||
_loadTemplates() {
|
||||
getBoxTemplates(this.templateBox.boxId, this.templateAudience).then(res => { this.boxTemplates = (res.data && res.data.list) || [] }).catch(() => { this.boxTemplates = [] })
|
||||
},
|
||||
onTemplateRowChange(row) { this.selectedTemplateRow = row },
|
||||
handleDeleteTemplate(tpl) {
|
||||
this.$confirm('确定删除模板【' + tpl.name + '】吗?', '提示', { type: 'warning' }).then(() => {
|
||||
deleteBoxTemplate(tpl.id).then(() => { this.selectedTemplateRow = null; this.$message.success('已删除'); this._loadTemplates() })
|
||||
}).catch(() => {})
|
||||
},
|
||||
handleApplyTemplate() {
|
||||
if (!this.selectedTemplateRow) return
|
||||
const tpl = this.selectedTemplateRow
|
||||
this.$confirm('将模板【' + tpl.name + '】应用到身份【' + this.audienceLabel(this.templateAudience) + '】,并设为抽干补桶依据,确认?', '应用模板', { type: 'warning' }).then(() => {
|
||||
applyBoxTemplate({ templateId: tpl.id }).then(() => {
|
||||
this.$message.success('模板已应用,饰品分布已更新')
|
||||
this.templateDialogVisible = false
|
||||
if (this.ornamentVisible && this.currentBox.boxId === this.templateBox.boxId && this.audience === this.templateAudience) this._refreshOrnamentList()
|
||||
this.loadData()
|
||||
}).catch(e => this.$message.error('应用失败: ' + (e.message || e)))
|
||||
}).catch(() => {})
|
||||
},
|
||||
audienceLabel(key) { const a = this.audiences.find(x => x.key === key); return a ? a.name : key },
|
||||
// 按当前身份筛选取值(后端一次返回4身份)
|
||||
targetRtp(row) {
|
||||
const m = row.targetRtpByAudience
|
||||
return m && m[this.audienceFilter] != null ? m[this.audienceFilter] : null
|
||||
},
|
||||
actualRtp(row) {
|
||||
const m = row.actualRtpByAudience
|
||||
return m && m[this.audienceFilter] != null ? m[this.audienceFilter] : null
|
||||
},
|
||||
// 当前身份一轮总回合(身份级独立)
|
||||
totalRoundsOf(row) {
|
||||
const m = row.totalRoundsByAudience
|
||||
return m && m[this.audienceFilter] != null ? m[this.audienceFilter] : (row.totalOrnaments != null ? row.totalOrnaments : null)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,92 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="6"><el-card shadow="hover"><div class="stat"><small>累计投注</small><h3>{{ summary.totalCost || 0 }}</h3></div></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><div class="stat"><small>累计赢奖</small><h3>{{ summary.totalWin || 0 }}</h3></div></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><div class="stat"><small>整体RTP</small><h3 :style="{color: rtp > 100 ? 'red' : 'green'}">{{ rtp }}%</h3></div></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><div class="stat"><small>总局数</small><h3>{{ summary.totalSpins || 0 }}</h3></div></el-card></el-col>
|
||||
</el-row>
|
||||
|
||||
<div style="margin-bottom:16px">
|
||||
<el-date-picker v-model="queryParams.dateStart" type="date" placeholder="开始日期" value-format="yyyy-MM-dd" style="width:150px;margin-right:8px" />
|
||||
<span style="margin:0 4px">至</span>
|
||||
<el-date-picker v-model="queryParams.dateEnd" type="date" placeholder="结束日期" value-format="yyyy-MM-dd" style="width:150px;margin-right:8px" />
|
||||
<el-button type="primary" icon="el-icon-search" @click="loadAll">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="list" border>
|
||||
<el-table-column prop="date" label="日期" min-width="120" align="center" />
|
||||
<el-table-column prop="totalCost" label="总投注" min-width="120" align="center" />
|
||||
<el-table-column prop="totalWin" label="总赢奖" min-width="120" align="center" />
|
||||
<el-table-column prop="totalSpins" label="总局数" min-width="90" align="center" />
|
||||
<el-table-column prop="playerCount" label="玩家数" min-width="90" align="center" />
|
||||
<el-table-column label="RTP" min-width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{color: scope.row.totalCost > 0 && scope.row.totalWin / scope.row.totalCost > 1 ? 'red' : 'green'}">
|
||||
{{ scope.row.totalCost > 0 ? (Math.round(scope.row.totalWin / scope.row.totalCost * 10000) / 100).toFixed(2) + '%' : '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
style="margin-top:16px;text-align:right"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="total"
|
||||
:page-size.sync="queryParams.pageSize"
|
||||
:current-page.sync="queryParams.pageNum"
|
||||
:page-sizes="[10,20,50]"
|
||||
@size-change="loadData"
|
||||
@current-change="loadData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getWinList, getWinSummary } from '@/api/game/olympus/dailywin/api'
|
||||
|
||||
export default {
|
||||
name: 'OlympusDailyWin',
|
||||
data() {
|
||||
return {
|
||||
loading: false, list: [], total: 0, summary: {},
|
||||
queryParams: { pageNum: 1, pageSize: 10, dateStart: new Date().toISOString().slice(0, 10), dateEnd: null }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rtp() {
|
||||
const c = Number(this.summary.totalCost) || 0
|
||||
const w = Number(this.summary.totalWin) || 0
|
||||
return c > 0 ? (Math.round(w / c * 10000) / 100).toFixed(2) : '0.00'
|
||||
}
|
||||
},
|
||||
created() { this.loadAll() },
|
||||
methods: {
|
||||
loadAll() { this.loadSummary(); this.loadData() },
|
||||
loadSummary() {
|
||||
getWinSummary({ dateStart: this.queryParams.dateStart, dateEnd: this.queryParams.dateEnd }).then(res => {
|
||||
this.summary = res.data || {}
|
||||
})
|
||||
},
|
||||
loadData() {
|
||||
this.loading = true
|
||||
getWinList(this.queryParams).then(res => {
|
||||
this.list = (res.data && res.data.rows) || []
|
||||
this.total = (res.data && res.data.total) || 0
|
||||
this.loading = false
|
||||
}).catch(() => { this.loading = false })
|
||||
},
|
||||
handleReset() {
|
||||
this.queryParams = { pageNum: 1, pageSize: 10, dateStart: new Date().toISOString().slice(0, 10), dateEnd: null }
|
||||
this.loadAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stat { text-align: center; }
|
||||
.stat small { color: #909399; }
|
||||
</style>
|
||||
@@ -1,32 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:16px">
|
||||
17 档饰品桶(0x~1000x)的定义与落桶区间。分布由 Python 预生成落库,此处仅供参考查看。
|
||||
</el-alert>
|
||||
<el-table v-loading="loading" :data="list" border>
|
||||
<el-table-column prop="id" label="桶ID" width="80" align="center" />
|
||||
<el-table-column label="名称" min-width="140" align="center">
|
||||
<template slot-scope="scope">{{ (scope.row.name || '').replace('x', '倍饰品') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="expectMul" label="期望倍率" min-width="120" align="center" />
|
||||
<el-table-column prop="rangeLow" label="落桶下限" min-width="120" align="center" />
|
||||
<el-table-column prop="rangeHigh" label="落桶上限" min-width="120" align="center" />
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getOrnamentList } from '@/api/game/olympus/ornament/api'
|
||||
|
||||
export default {
|
||||
name: 'OlympusOrnament',
|
||||
data() { return { loading: false, list: [] } },
|
||||
created() {
|
||||
this.loading = true
|
||||
getOrnamentList().then(res => {
|
||||
this.list = (res.data && res.data.list) || []
|
||||
this.loading = false
|
||||
}).catch(() => { this.loading = false })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,121 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:12px">
|
||||
返奖饰品按"结算赢额"匹配返奖区间展示。区间需连续覆盖;未命中时用兜底饰品。
|
||||
</el-alert>
|
||||
|
||||
<el-table v-loading="loading" :data="list" border>
|
||||
<el-table-column prop="id" label="ID" width="60" align="center" />
|
||||
<el-table-column prop="name" label="名称" min-width="160" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="品质" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.grade" size="small" :type="gradeType(scope.row.grade)" :style="gradeStyle(scope.row.grade)">{{ gradeLabel(scope.row.grade) }}</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="饰品图片" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-image v-if="scope.row.imageUrl" :src="getImg(scope.row.imageUrl)" style="width:40px;height:40px" fit="cover" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="品质图片" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-image v-if="scope.row.levelImg" :src="getImg(scope.row.levelImg)" style="width:40px;height:40px" fit="contain" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="磨损" width="110" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.exteriorName || '崭新出厂' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="兜底" width="70" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.isFallback === 1" type="danger" size="small">是</el-tag>
|
||||
<el-tag v-else type="info" size="small">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="返奖区间" min-width="160" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.winLow != null">{{ scope.row.winLow }} ~ {{ scope.row.winHigh }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog title="编辑奖品" :visible.sync="dialogVisible" width="480px" append-to-body>
|
||||
<el-form ref="form" :model="form" label-width="80px">
|
||||
<el-form-item label="名称"><el-input v-model="form.name" /></el-form-item>
|
||||
<el-form-item label="品质">
|
||||
<el-select v-model="form.grade" style="width:100%">
|
||||
<el-option label="1 - 金" :value="1" />
|
||||
<el-option label="2 - 红" :value="2" />
|
||||
<el-option label="3 - 紫" :value="3" />
|
||||
<el-option label="4 - 蓝" :value="4" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="饰品图片">
|
||||
<ImageUpload v-model="form.imageUrl" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="磨损">
|
||||
<el-select v-model="form.exteriorName" placeholder="请选择磨损度" clearable style="width:100%">
|
||||
<el-option label="崭新出厂" value="崭新出厂" />
|
||||
<el-option label="略有磨损" value="略有磨损" />
|
||||
<el-option label="久经沙场" value="久经沙场" />
|
||||
<el-option label="破损不堪" value="破损不堪" />
|
||||
<el-option label="战痕累累" value="战痕累累" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="兜底饰品">
|
||||
<el-switch v-model="form.isFallback" :active-value="1" :inactive-value="0" active-text="是" inactive-text="否" />
|
||||
<div style="color:#909399;font-size:12px;margin-top:4px">未命中区间时随机选择一个兜底饰品返奖</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="返奖区间">
|
||||
<div style="display:flex;align-items:center;gap:8px">
|
||||
<el-input-number v-model="form.winLow" :min="0" :precision="2" :step="0.01" controls-position="right" placeholder="最小值" style="flex:1" />
|
||||
<span>~</span>
|
||||
<el-input-number v-model="form.winHigh" :min="0" :precision="2" :step="0.01" controls-position="right" placeholder="最大值" style="flex:1" />
|
||||
</div>
|
||||
<div style="color:#909399;font-size:12px;margin-top:4px">玩家结算赢额落在该区间即获得此饰品</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { prizeList, prizeSave } from '@/api/game/olympus/prize/api'
|
||||
import ImageUpload from '@/components/ImageUpload'
|
||||
|
||||
export default {
|
||||
name: 'OlympusPrize',
|
||||
components: { ImageUpload },
|
||||
data() {
|
||||
return { loading: false, list: [], dialogVisible: false, form: {}}
|
||||
},
|
||||
created() { this.getList() },
|
||||
methods: {
|
||||
getList() {
|
||||
this.loading = true
|
||||
prizeList().then(res => { this.list = (res.data && res.data.list) || []; this.loading = false }).catch(() => { this.loading = false })
|
||||
},
|
||||
getImg(u) { return u ? (u.startsWith('http') ? u : (process.env.VUE_APP_BASE_API || '') + u) : '' },
|
||||
// 品质配色与饰品品质一致:1金/2红/3紫/4蓝
|
||||
gradeType(g) { const m = { 1: 'warning', 2: 'danger', 3: '', 4: '' }; return m[g] === undefined ? 'info' : m[g] },
|
||||
gradeStyle(g) { return g === 3 ? { color: '#a16ee8', background: '#f7f1fe', borderColor: '#e4d5f9' } : null },
|
||||
gradeLabel(g) { return ({ 1: '金', 2: '红', 3: '紫', 4: '蓝' })[g] || ('品质' + g) },
|
||||
handleEdit(row) { this.form = { ...row }; this.dialogVisible = true },
|
||||
submitForm() {
|
||||
if (!this.form.name || !this.form.name.trim()) return this.$message.warning('名称不能为空')
|
||||
prizeSave(this.form).then(() => { this.$message.success('保存成功'); this.dialogVisible = false; this.getList() })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,108 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :inline="true" class="demo-form-inline">
|
||||
<el-form-item label="玩家ID">
|
||||
<el-input v-model="queryParams.userId" placeholder="输入玩家ID" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户类型">
|
||||
<el-select v-model="queryParams.audience" placeholder="全部" clearable style="width:130px" @change="handleQuery">
|
||||
<el-option v-for="a in audiences" :key="a.key" :label="a.name" :value="a.key" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" :data="list" border>
|
||||
<el-table-column prop="id" label="ID" width="80" align="center" />
|
||||
<el-table-column prop="userId" label="玩家ID" width="90" align="center" />
|
||||
<el-table-column label="用户类型" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="audienceTagType(scope.row.audience)" size="small">{{ audienceLabel(scope.row.audience) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="boxId" label="箱子" width="70" align="center" />
|
||||
<el-table-column label="模式" width="120" align="center">
|
||||
<template slot-scope="scope">{{ boxTypeLabels[scope.row.modeType] || scope.row.modeType }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="prizeName" label="饰品名称" min-width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="饰品图片" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-image v-if="scope.row.prizeImageUrl" :src="getImg(scope.row.prizeImageUrl)" style="width:40px;height:40px" fit="cover" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="cost" label="花费" width="90" align="center" />
|
||||
<el-table-column prop="settledWin" label="结算赢额" width="100" align="center" />
|
||||
<el-table-column label="播放状态" width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip :content="settleTips[scope.row.settleSource] || '历史值'" placement="top">
|
||||
<el-tag :type="scope.row.settleSource === 'start' ? 'warning' : 'success'" size="small">
|
||||
{{ settleLabels[scope.row.settleSource] || scope.row.settleSource || '—' }}
|
||||
</el-tag>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="时间" width="160" align="center" />
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
style="margin-top:16px;text-align:right"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="total"
|
||||
:page-size.sync="queryParams.pageSize"
|
||||
:current-page.sync="queryParams.pageNum"
|
||||
:page-sizes="[10,20,50]"
|
||||
@size-change="loadData"
|
||||
@current-change="loadData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getRecordList } from '@/api/game/olympus/record/api'
|
||||
|
||||
export default {
|
||||
name: 'OlympusRecord',
|
||||
data() {
|
||||
return {
|
||||
loading: false, list: [], total: 0,
|
||||
queryParams: { pageNum: 1, pageSize: 10, userId: null, audience: null },
|
||||
boxTypeLabels: { normal: '普通旋转', ante: '底注', buy_fs: '购买免费旋转' },
|
||||
audiences: [{ key: 'common', name: '普通' }, { key: 'anchor', name: '主播' }, { key: 'robot', name: '机器人' }, { key: 'white', name: '白号' }],
|
||||
// 播放状态:start=开局即结算(奖已入背包)但动画没播完;与"结算赢额"不矛盾
|
||||
settleLabels: { start: '未播完', finish: '已播完', reconnect: '重连补结', scheduled: '定时兜底', force_settle: '强制结算' },
|
||||
settleTips: {
|
||||
start: '开局就已结算:钱和饰品当场定下并直接进背包(防断网丢奖)。只是整局动画没播到末帧(玩家中途退出/断网),客户端战绩暂不展示,超 30 分钟兜底放行',
|
||||
finish: '整局已播到末帧,客户端战绩正常展示(站内信也是此刻才发)'
|
||||
}
|
||||
}
|
||||
},
|
||||
created() { this.loadData() },
|
||||
methods: {
|
||||
audienceLabel(key) {
|
||||
const a = this.audiences.find(x => x.key === key)
|
||||
return a ? a.name : (key || '—')
|
||||
},
|
||||
audienceTagType(key) {
|
||||
return { common: '', anchor: 'success', robot: 'info', white: 'warning' }[key] || 'info'
|
||||
},
|
||||
loadData() {
|
||||
this.loading = true
|
||||
const params = { pageNum: this.queryParams.pageNum, pageSize: this.queryParams.pageSize }
|
||||
if (this.queryParams.userId) params.userId = this.queryParams.userId
|
||||
if (this.queryParams.audience) params.audience = this.queryParams.audience
|
||||
getRecordList(params).then(res => {
|
||||
this.list = (res.data && res.data.rows) || []
|
||||
this.total = (res.data && res.data.total) || 0
|
||||
this.loading = false
|
||||
}).catch(() => { this.loading = false })
|
||||
},
|
||||
handleQuery() { this.queryParams.pageNum = 1; this.loadData() },
|
||||
resetQuery() { this.queryParams = { pageNum: 1, pageSize: 10, userId: null, audience: null }; this.loadData() },
|
||||
getImg(u) { return u ? (u.startsWith('http') ? u : (process.env.VUE_APP_BASE_API || '') + u) : '' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,451 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container reward-pool-page">
|
||||
<div class="page-head">
|
||||
<div class="page-head-row">
|
||||
<el-button type="text" size="mini" :icon="showGuide ? 'el-icon-arrow-up' : 'el-icon-question'" @click="showGuide = !showGuide">
|
||||
{{ showGuide ? '收起说明' : '配置说明' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<p class="head-tip">
|
||||
奖池按【箱子 × 身份】独立:每箱每身份一套水位 = 该身份箱内下注 − 箱内返奖 + 该箱该身份人工调整;开局按「(本箱该身份水位 + 本局花费) × 目标RTP」算赔付上限,只有预期赔付不超上限的饰品才可被抽中。白号水位仅统计与人工调整,不受上限约束(纯概率)。
|
||||
</p>
|
||||
<div class="audience-row">
|
||||
<span class="aud-label">身份:</span>
|
||||
<el-radio-group v-model="audience" size="small" @change="switchAudience">
|
||||
<el-radio-button v-for="a in audiences" :key="a.key" :label="a.key">{{ a.name }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
<span class="aud-tip">四个身份各自独立水位、独立注水/抽水、独立调整历史;白号不参与其他身份池。</span>
|
||||
</div>
|
||||
<div v-show="showGuide" class="guide-panel">
|
||||
<div class="guide-col">
|
||||
<h5><i class="el-icon-info" /> 机制说明</h5>
|
||||
<ul>
|
||||
<li>每箱每身份独立水位 = 该身份箱内累计下注 − 累计返奖 + 人工调整;四个身份互不串池。</li>
|
||||
<li>白号(audience=white)水位独立统计,但按设计不受水位约束,白号出奖=配比即爆率,注水/抽水只影响记录。</li>
|
||||
<li>抽饰品两阶段:固定阶段按分布抽取(确定性);轮末收敛阶段按实时RTP偏差定向选桶纠偏。</li>
|
||||
<li>水位随下注升高、随返奖降低;顶部总池为当前身份全站汇总,仅展示不可调。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="guide-col">
|
||||
<h5><i class="el-icon-setting" /> 如何配置</h5>
|
||||
<ul>
|
||||
<li>新箱上线或水位偏低:建议批量注水一轮花费的 60%~80%(接近目标RTP),相当于提前垫付整轮返奖预算,保证高倍饰品全程可正常抽出。</li>
|
||||
<li>日常运营小幅补水:比例 5%~20% 即可;某箱长期不出高倍时检查该箱水位,接近 0 或为负则单箱注水。</li>
|
||||
<li>返奖偏高想收紧:批量或单箱抽水;水位为负时只会出最低倍,请谨慎大额抽水。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="guide-col">
|
||||
<h5><i class="el-icon-s-operation" /> 批量调整规则</h5>
|
||||
<ul>
|
||||
<li>基数 = 该箱抽干一轮的总花费 = 底注 × 模式倍数 × 总回合数;总回合数在箱子管理【编辑】中配置,修改后基数自动跟随。</li>
|
||||
<li>每箱调整金额 = 基数 × 比例。例:普通旋转底注 1、1000 回合 → 基数 1000,注水 10% = +100。</li>
|
||||
<li>比例支持 0~1000%;基数为 0 的箱子自动跳过;批量操作记一条汇总历史可审计(调整原因必填,会写进调整历史)。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="guide-col">
|
||||
<h5><i class="el-icon-data-line" /> 两阶段RTP</h5>
|
||||
<ul>
|
||||
<li>本轮实时RTP / 已抽数反映当前轮收敛进度;一轮抽满 N 回合后自动重置账本开新轮。</li>
|
||||
<li>收敛占比越高,轮末纠偏能力越强(购买免费旋转类建议更高占比)。</li>
|
||||
<li>总回合数 N 与收敛占比在「箱子配置 → 编辑」中配置。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16" class="stat-row">
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="sc-icon sc-blue"><i class="el-icon-coin" /></div>
|
||||
<div class="sc-body"><small>{{ audienceLabel(audience) }}·总下注额</small><h3>{{ fmt(pool.totalBet) }}</h3></div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="sc-icon sc-orange"><i class="el-icon-present" /></div>
|
||||
<div class="sc-body"><small>{{ audienceLabel(audience) }}·总返奖额</small><h3>{{ fmt(pool.totalWin) }}</h3></div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="sc-icon sc-cyan"><i class="el-icon-wallet" /></div>
|
||||
<div class="sc-body"><small>{{ audienceLabel(audience) }}·总池余额</small><h3 :style="{color: Number(pool.balance) < 0 ? '#f56c6c' : '#409eff'}">{{ fmt(pool.balance) }}</h3></div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="sc-icon sc-green"><i class="el-icon-data-line" /></div>
|
||||
<div class="sc-body"><small>{{ audienceLabel(audience) }}·实时RTP</small><h3 :style="{color: rtpPct(pool.rtp) > 100 ? '#f56c6c' : '#67c23a'}">{{ rtpPct(pool.rtp).toFixed(1) }}%</h3></div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-card shadow="never" class="batch-card">
|
||||
<div slot="header">
|
||||
<b>批量调整(按一轮花费比例)</b>
|
||||
<span class="sub-tip">身份【{{ audienceLabel(audience) }}】:基数 = 底注 × 模式倍数 × 总回合数(随箱子配置动态变化)</span>
|
||||
</div>
|
||||
<el-form :inline="true" @submit.native.prevent>
|
||||
<!-- 注:skin.scss 全局隐藏了 inline 表单的 label,所以字段名写成 span,别用 label 属性 -->
|
||||
<el-form-item>
|
||||
<span class="fld-label">调整比例</span>
|
||||
<el-input-number v-model="batchForm.ratio" :min="0.01" :max="1000" :precision="2" :step="5" :controls="false" style="width:120px" placeholder="如 10" />
|
||||
<span style="margin-left:4px">%</span>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<span class="fld-label">调整原因<span class="req">*</span></span>
|
||||
<el-input v-model="batchForm.remark" maxlength="80" placeholder="必填,如:活动补水" style="width:200px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-popconfirm :title="'按【' + audienceLabel(audience) + '】各箱一轮花费 ' + (batchForm.ratio || 0) + '% 注水,预估共 +' + fmt(estBatchTotal) + ',确定?'" @confirm="submitBatch(1)">
|
||||
<el-button slot="reference" type="success" plain icon="el-icon-top" :loading="batchLoading" :disabled="!batchForm.ratio">批量注水</el-button>
|
||||
</el-popconfirm>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-popconfirm :title="'按【' + audienceLabel(audience) + '】各箱一轮花费 ' + (batchForm.ratio || 0) + '% 抽水,预估共 -' + fmt(estBatchTotal) + ',确定?'" @confirm="submitBatch(2)">
|
||||
<el-button slot="reference" type="danger" plain icon="el-icon-bottom" :loading="batchLoading" :disabled="!batchForm.ratio">批量抽水</el-button>
|
||||
</el-popconfirm>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-popconfirm :title="'重置将把身份【' + audienceLabel(audience) + '】所有箱子的注水/抽水累计值归 0,只保留真实水位(下注−返奖),确定?'" @confirm="submitReset">
|
||||
<el-button slot="reference" type="warning" plain icon="el-icon-refresh-left" :loading="resetLoading">重置调整</el-button>
|
||||
</el-popconfirm>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="batchForm.ratio">
|
||||
<span class="est-text">预估总额:{{ fmt(estBatchTotal) }}({{ boxes.length }} 个箱子)</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div class="section-head">
|
||||
<h4 style="margin:0">箱子奖池 · 身份【{{ audienceLabel(audience) }}】</h4>
|
||||
<el-button size="mini" type="text" icon="el-icon-refresh" @click="loadConfig">刷新</el-button>
|
||||
</div>
|
||||
<el-table v-loading="boxLoading" :data="pagedBoxes" size="small" border stripe>
|
||||
<el-table-column prop="boxId" label="ID" width="60" align="center" />
|
||||
<el-table-column prop="name" label="箱子" min-width="140" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="boxType" label="模式" min-width="110" align="center">
|
||||
<template slot-scope="scope">{{ boxTypeLabels[scope.row.boxType] || scope.row.boxType }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="底注" width="70" align="center">
|
||||
<template slot-scope="scope">{{ fmt(scope.row.bet) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="一轮花费(基数)" width="130" align="center">
|
||||
<template slot-scope="scope">
|
||||
<div>{{ fmt(scope.row.cycleCost) }}</div>
|
||||
<div style="font-size:11px;color:#909399">{{ fmt(scope.row.costPerRound) }} × {{ scope.row.cycleRounds || '—' }}回合</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="箱内下注" min-width="100" align="center">
|
||||
<template slot-scope="scope">{{ fmt(scope.row.totalBet) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="箱内返奖" min-width="100" align="center">
|
||||
<template slot-scope="scope">{{ fmt(scope.row.totalWin) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="人工调整" min-width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{color: signColor(scope.row.adjust)}">{{ fmt(scope.row.adjust) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="水位余额" min-width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<b :style="{color: Number(scope.row.balance) < 0 ? '#f56c6c' : '#409eff'}">{{ fmt(scope.row.balance) }}</b>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="累计RTP" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{color: rtpPct(scope.row.rtp) > 100 ? '#f56c6c' : '#67c23a'}">{{ scope.row.rtp == null ? '—' : rtpPct(scope.row.rtp).toFixed(1) + '%' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="本轮RTP/进度" min-width="120" align="center">
|
||||
<template slot-scope="scope">
|
||||
<div :style="{color: rtpPct(scope.row.cycleRtp) > 100 ? '#f56c6c' : '#67c23a'}">{{ scope.row.cycleRtp == null ? '—' : rtpPct(scope.row.cycleRtp).toFixed(1) + '%' }}</div>
|
||||
<div style="font-size:11px;color:#909399">{{ scope.row.cycleDrawn || 0 }} / {{ scope.row.totalRounds || '—' }} 回合</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收敛占比" width="80" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.convergeRatio == null ? '—' : (Number(scope.row.convergeRatio) * 100).toFixed(0) + '%' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip content="注水(增加水位)" placement="top">
|
||||
<el-button circle size="mini" type="success" plain icon="el-icon-top" @click="openAdjust(scope.row, 1)" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="抽水(降低水位)" placement="top">
|
||||
<el-button circle size="mini" type="danger" plain icon="el-icon-bottom" @click="openAdjust(scope.row, 2)" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
style="margin-top:12px;text-align:right"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="boxes.length"
|
||||
:page-size.sync="boxSize"
|
||||
:current-page.sync="boxPage"
|
||||
:page-sizes="[10,20,30,50]"
|
||||
/>
|
||||
|
||||
<el-dialog :title="'调整水位【' + audienceLabel(audience) + '】- ' + (form.boxName || '')" :visible.sync="dialogVisible" width="440px" append-to-body>
|
||||
<el-form label-width="90px" @submit.native.prevent>
|
||||
<el-form-item label="调整类型">
|
||||
<el-radio-group v-model="form.type">
|
||||
<el-radio-button :label="1">注水(增加)</el-radio-button>
|
||||
<el-radio-button :label="2">抽水(减少)</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="当前水位">
|
||||
<b :style="{color: Number(form.balance) < 0 ? '#f56c6c' : '#409eff'}">{{ fmt(form.balance) }}</b>
|
||||
</el-form-item>
|
||||
<el-form-item label="调整金额">
|
||||
<el-input-number v-model="form.amount" :min="0.01" :precision="2" :step="100" :controls="false" style="width:100%" placeholder="请输入金额" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.amount" label="调整后水位">
|
||||
<b :style="{color: afterBalance < 0 ? '#f56c6c' : '#67c23a'}">{{ fmt(afterBalance) }}</b>
|
||||
</el-form-item>
|
||||
<el-form-item label="调整原因" required>
|
||||
<el-input v-model="form.remark" maxlength="100" placeholder="必填,如:活动补水" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button :type="form.type === 1 ? 'success' : 'danger'" :loading="submitting" @click="submit">确定{{ form.type === 1 ? '注水' : '抽水' }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<div class="section-head">
|
||||
<h4 style="margin:0">调整历史 · 身份【{{ audienceLabel(audience) }}】</h4>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="list" border size="small">
|
||||
<el-table-column prop="createTime" label="时间" min-width="150" align="center" />
|
||||
<el-table-column label="身份" width="80" align="center">
|
||||
<template slot-scope="scope"><el-tag size="mini">{{ audienceLabel(scope.row.audience) }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="boxName" label="箱子" min-width="130" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="类型" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.type === 1 ? 'success' : (scope.row.type === 2 ? 'danger' : 'warning')" size="mini">
|
||||
{{ scope.row.type === 1 ? '注水' : (scope.row.type === 2 ? '抽水' : '重置') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" min-width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{color: scope.row.type === 1 ? '#67c23a' : (scope.row.type === 2 ? '#f56c6c' : '#e6a23c')}">
|
||||
{{ scope.row.type === 1 ? '+' : (scope.row.type === 2 ? '-' : '') }}{{ fmt(scope.row.amount) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="调整前余额" min-width="110" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.balanceBefore == null ? '—' : fmt(scope.row.balanceBefore) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="调整后余额" min-width="110" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.balanceAfter == null ? '—' : fmt(scope.row.balanceAfter) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总池余额" min-width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<b :style="{color: Number(scope.row.poolBalance) < 0 ? '#f56c6c' : '#409eff'}">{{ scope.row.poolBalance == null ? '—' : fmt(scope.row.poolBalance) }}</b>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operator" label="操作人" min-width="90" align="center" />
|
||||
<el-table-column label="备注" min-width="160" align="center" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span :style="{color: scope.row.remark ? '#303133' : '#c0c4cc'}">{{ scope.row.remark || '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
style="margin-top:16px;text-align:right"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="total"
|
||||
:page-size.sync="queryParams.size"
|
||||
:current-page.sync="queryParams.page"
|
||||
:page-sizes="[10,20,50]"
|
||||
@size-change="loadLogs"
|
||||
@current-change="loadLogs"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getPoolConfig, adjustPool, adjustPoolBatch, adjustPoolReset, getPoolLogs } from '@/api/game/olympus/rewardPool/api'
|
||||
|
||||
export default {
|
||||
name: 'OlympusRewardPool',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
boxLoading: false,
|
||||
submitting: false,
|
||||
batchLoading: false,
|
||||
resetLoading: false,
|
||||
dialogVisible: false,
|
||||
showGuide: false,
|
||||
audience: 'common',
|
||||
audiences: [{ key: 'common', name: '普通' }, { key: 'anchor', name: '主播' }, { key: 'robot', name: '机器人' }, { key: 'white', name: '白号' }],
|
||||
pool: { totalBet: 0, totalWin: 0, adjust: 0, balance: 0, rtp: 0 },
|
||||
boxes: [],
|
||||
list: [],
|
||||
total: 0,
|
||||
boxPage: 1,
|
||||
boxSize: 10,
|
||||
boxTypeLabels: { normal: '普通旋转', ante: '底注', buy_fs: '购买免费旋转' },
|
||||
form: { boxId: undefined, boxName: '', balance: 0, type: 1, amount: undefined, remark: '' },
|
||||
batchForm: { ratio: undefined, remark: '' },
|
||||
queryParams: { page: 1, size: 10, audience: 'common' }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pagedBoxes() {
|
||||
const start = (this.boxPage - 1) * this.boxSize
|
||||
return this.boxes.slice(start, start + this.boxSize)
|
||||
},
|
||||
estBatchTotal() {
|
||||
const r = Number(this.batchForm.ratio) || 0
|
||||
if (r <= 0) return 0
|
||||
return this.boxes.reduce((s, b) => s + (Number(b.cycleCost) > 0 ? Number(b.cycleCost) * r / 100 : 0), 0)
|
||||
},
|
||||
afterBalance() {
|
||||
const amt = Number(this.form.amount) || 0
|
||||
return Number(this.form.balance) + (this.form.type === 2 ? -amt : amt)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadConfig()
|
||||
this.loadLogs()
|
||||
},
|
||||
methods: {
|
||||
fmt(v) {
|
||||
const n = Number(v)
|
||||
return isNaN(n) ? '0.00' : n.toFixed(2)
|
||||
},
|
||||
rtpPct(v) {
|
||||
const n = Number(v)
|
||||
return isNaN(n) ? 0 : n * 100
|
||||
},
|
||||
signColor(v) {
|
||||
const n = Number(v)
|
||||
return n < 0 ? '#f56c6c' : (n > 0 ? '#67c23a' : '#303133')
|
||||
},
|
||||
applyConfig(data) {
|
||||
if (!data) return
|
||||
this.pool = data.total || this.pool
|
||||
this.boxes = data.boxes || []
|
||||
},
|
||||
audienceLabel(key) {
|
||||
const a = this.audiences.find(x => x.key === key)
|
||||
return a ? a.name : (key || '普通')
|
||||
},
|
||||
requiredRemark(v) {
|
||||
if (v && v.trim()) return true
|
||||
this.$message.warning('请填写调整原因(备注)')
|
||||
return false
|
||||
},
|
||||
switchAudience() {
|
||||
this.queryParams.audience = this.audience
|
||||
this.queryParams.page = 1
|
||||
this.loadConfig()
|
||||
this.loadLogs()
|
||||
},
|
||||
loadConfig() {
|
||||
this.boxLoading = true
|
||||
getPoolConfig({ audience: this.audience }).then(res => {
|
||||
this.applyConfig(res.data)
|
||||
this.boxLoading = false
|
||||
}).catch(() => { this.boxLoading = false })
|
||||
},
|
||||
loadLogs() {
|
||||
this.loading = true
|
||||
this.queryParams.audience = this.audience
|
||||
getPoolLogs(this.queryParams).then(res => {
|
||||
this.list = (res.data && res.data.rows) || []
|
||||
this.total = (res.data && res.data.total) || 0
|
||||
this.loading = false
|
||||
}).catch(() => { this.loading = false })
|
||||
},
|
||||
openAdjust(row, type) {
|
||||
this.form = { boxId: row.boxId, boxName: row.name, audience: this.audience, balance: row.balance, type, amount: undefined, remark: '' }
|
||||
this.dialogVisible = true
|
||||
},
|
||||
submit() {
|
||||
if (!this.form.amount || this.form.amount <= 0) {
|
||||
this.$message.warning('请输入大于 0 的调整金额')
|
||||
return
|
||||
}
|
||||
if (!this.requiredRemark(this.form.remark)) return
|
||||
this.submitting = true
|
||||
adjustPool({ boxId: this.form.boxId, audience: this.audience, type: this.form.type, amount: this.form.amount, remark: this.form.remark.trim() }).then(res => {
|
||||
this.applyConfig(res.data)
|
||||
this.$message.success((this.form.type === 1 ? '注水' : '抽水') + '成功')
|
||||
this.dialogVisible = false
|
||||
this.queryParams.page = 1
|
||||
this.loadLogs()
|
||||
this.submitting = false
|
||||
}).catch(() => { this.submitting = false })
|
||||
},
|
||||
submitBatch(type) {
|
||||
if (!this.batchForm.ratio || this.batchForm.ratio <= 0) {
|
||||
this.$message.warning('请输入大于 0 的调整比例')
|
||||
return
|
||||
}
|
||||
if (!this.requiredRemark(this.batchForm.remark)) return
|
||||
this.batchLoading = true
|
||||
adjustPoolBatch({ audience: this.audience, type, ratio: this.batchForm.ratio, remark: this.batchForm.remark.trim() }).then(res => {
|
||||
this.applyConfig(res.data)
|
||||
this.$message.success(`批量${type === 1 ? '注水' : '抽水'}完成:${res.data.adjusted} 个箱子,共 ${this.fmt(res.data.totalAmount)}`)
|
||||
this.batchForm.ratio = undefined
|
||||
this.batchForm.remark = ''
|
||||
this.queryParams.page = 1
|
||||
this.loadLogs()
|
||||
this.batchLoading = false
|
||||
}).catch(() => { this.batchLoading = false })
|
||||
},
|
||||
submitReset() {
|
||||
if (!this.requiredRemark(this.batchForm.remark)) return
|
||||
this.resetLoading = true
|
||||
adjustPoolReset({ audience: this.audience, remark: this.batchForm.remark.trim() }).then(res => {
|
||||
this.applyConfig(res.data)
|
||||
this.$message.success(`重置完成:${res.data.cleared} 个箱子的人工调整已归零(清除净调整 ${this.fmt(res.data.totalCleared)})`)
|
||||
this.batchForm.remark = ''
|
||||
this.queryParams.page = 1
|
||||
this.loadLogs()
|
||||
this.resetLoading = false
|
||||
}).catch(() => { this.resetLoading = false })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-head { margin-bottom: 14px; }
|
||||
.page-head-row { display: flex; align-items: center; justify-content: space-between; }
|
||||
.head-tip { color: #909399; font-size: 12px; margin: 6px 0 0; }
|
||||
.audience-row { display: flex; align-items: center; margin-top: 10px; }
|
||||
.aud-label { color: #606266; font-size: 13px; margin-right: 6px; }
|
||||
.aud-tip { color: #909399; font-size: 12px; margin-left: 10px; }
|
||||
.guide-panel { display: flex; gap: 16px; background: #f7f9fc; border: 1px solid #e4e9f2; border-radius: 8px; padding: 14px 16px; margin-top: 10px; }
|
||||
.guide-col { flex: 1; min-width: 0; }
|
||||
.guide-col h5 { margin: 0 0 8px; font-size: 13px; color: #303133; }
|
||||
.guide-col h5 i { color: #409eff; margin-right: 4px; }
|
||||
.guide-col ul { margin: 0; padding-left: 16px; }
|
||||
.guide-col li { font-size: 12px; color: #606266; line-height: 1.9; }
|
||||
.stat-row { margin-bottom: 16px; }
|
||||
.stat-card { display: flex; align-items: center; gap: 12px; background: #fff; border: 1px solid #ebeef5; border-radius: 8px; padding: 14px 16px; box-shadow: 0 1px 4px rgba(0,0,0,.04); }
|
||||
.sc-icon { width: 42px; height: 42px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
|
||||
.sc-blue { background: #ecf5ff; color: #409eff; }
|
||||
.sc-orange { background: #fdf6ec; color: #e6a23c; }
|
||||
.sc-cyan { background: #e8f7f7; color: #13a8a8; }
|
||||
.sc-green { background: #f0f9eb; color: #67c23a; }
|
||||
.sc-body small { color: #909399; display: block; }
|
||||
.sc-body h3 { margin: 2px 0 0; font-size: 18px; }
|
||||
.batch-card { margin-bottom: 16px; border-radius: 8px; }
|
||||
.batch-card .sub-tip { color: #909399; font-size: 12px; margin-left: 10px; }
|
||||
.fld-label { color: #606266; font-size: 13px; margin-right: 6px; }
|
||||
.fld-label .req { color: #f56c6c; margin-left: 2px; }
|
||||
.batch-card >>> .el-form-item { margin-bottom: 0; }
|
||||
.est-text { color: #e6a23c; font-size: 13px; }
|
||||
.section-head { display: flex; align-items: center; justify-content: space-between; margin: 16px 0 10px; }
|
||||
</style>
|
||||
@@ -1,82 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||
<el-form-item label="回合ID">
|
||||
<el-input v-model="queryParams.roundId" placeholder="输入回合ID" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="模式类型">
|
||||
<el-select v-model="queryParams.boxType" placeholder="全部" clearable>
|
||||
<el-option v-for="m in modeTypes" :key="m.code" :label="m.name" :value="m.code" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" :data="roundList" border style="width:100%">
|
||||
<el-table-column prop="roundId" label="回合ID" width="100" align="center" />
|
||||
<el-table-column prop="boxId" label="箱子ID" width="80" align="center" />
|
||||
<el-table-column label="模式" min-width="120" align="center">
|
||||
<template slot-scope="scope">{{ getModeName(scope.row.boxType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ornamentId" label="饰品桶" width="80" align="center" />
|
||||
<el-table-column prop="actualMul" label="返奖倍率" width="100" align="center" />
|
||||
<el-table-column prop="freeSpinCount" label="免费旋转" width="90" align="center" />
|
||||
<el-table-column prop="totalCascades" label="翻转步数" width="90" align="center" />
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
style="margin-top:16px;text-align:right"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="total"
|
||||
:page-size.sync="queryParams.pageSize"
|
||||
:current-page.sync="queryParams.pageNum"
|
||||
:page-sizes="[10,20,50,100]"
|
||||
@size-change="loadData"
|
||||
@current-change="loadData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getRoundList } from '@/api/game/olympus/round/api'
|
||||
|
||||
export default {
|
||||
name: 'OlympusRound',
|
||||
data() {
|
||||
return {
|
||||
loading: false, roundList: [], total: 0,
|
||||
queryParams: { pageNum: 1, pageSize: 10, roundId: null, boxType: null },
|
||||
modeTypes: [
|
||||
{ code: 'normal', name: '普通旋转' },
|
||||
{ code: 'ante', name: '底注' },
|
||||
{ code: 'buy_fs', name: '购买免费旋转' }
|
||||
]
|
||||
}
|
||||
},
|
||||
created() { this.loadData() },
|
||||
methods: {
|
||||
getModeName(boxType) {
|
||||
if (!boxType) return ''
|
||||
const m = this.modeTypes.find(t => t.code === boxType)
|
||||
return m ? m.name : boxType
|
||||
},
|
||||
loadData() {
|
||||
this.loading = true
|
||||
const params = { pageNum: this.queryParams.pageNum, pageSize: this.queryParams.pageSize }
|
||||
if (this.queryParams.roundId) params.roundId = this.queryParams.roundId
|
||||
if (this.queryParams.boxType) params.boxType = this.queryParams.boxType
|
||||
getRoundList(params).then(res => {
|
||||
this.roundList = (res.data && res.data.rows) || []
|
||||
this.total = (res.data && res.data.total) || 0
|
||||
this.loading = false
|
||||
}).catch(() => { this.loading = false })
|
||||
},
|
||||
handleQuery() { this.queryParams.pageNum = 1; this.loadData() },
|
||||
resetQuery() { this.queryParams = { pageNum: 1, pageSize: 10, roundId: null, boxType: null }; this.loadData() }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,578 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="10" style="margin-bottom:16px">
|
||||
<el-select v-model="queryParams.boxType" placeholder="按模式筛选" clearable @change="loadData" style="width:180px;margin-right:10px">
|
||||
<el-option v-for="t in boxTypes" :key="t" :label="boxTypeLabels[t]" :value="t" />
|
||||
</el-select>
|
||||
<el-input v-model="queryParams.name" placeholder="按名称搜索" clearable @clear="loadData" @keyup.enter.native="loadData" style="width:200px">
|
||||
<el-button slot="append" icon="el-icon-search" @click="loadData" />
|
||||
</el-input>
|
||||
<el-button icon="el-icon-refresh" @click="loadData">刷新</el-button>
|
||||
<el-button type="danger" icon="el-icon-refresh" @click="handleRecalc" :loading="recalcLoading" style="float:right">一键重算分布</el-button>
|
||||
</el-row>
|
||||
|
||||
<!-- 奖池水位控制 -->
|
||||
<el-card shadow="never" style="margin-bottom:16px">
|
||||
<div slot="header"><i class="el-icon-coin" /> 奖池水位控制</div>
|
||||
<div style="color:#909399;font-size:12px;line-height:1.7;margin-bottom:10px">
|
||||
【水位】= 这个游戏当前可用来赔大奖的“资金池”(≈ 真实玩家累计下注 − 累计赔奖,白号不算)。水越多越容易开出大奖,水越少越压着不出。<br>
|
||||
【注水】= 手动把水位调高(更容易出大奖);【抽水】= 手动调低(压制大奖)。注/抽水只动这个内部资金池,不是真给玩家发钱,也不会改动首页/每日RTP 等报表数字。<br>
|
||||
提醒:水位被抽得太低时,大奖会被压住,实时RTP 也可能到不了目标。
|
||||
</div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<div class="pool-stat"><span>累计投注</span><b>{{ pool.totalBet || 0 }}</b></div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="pool-stat"><span>累计返奖</span><b>{{ pool.totalWin || 0 }}</b></div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="pool-stat"><span>当前水位</span><b :style="{color: pool.balance < 0 ? '#f56c6c' : '#67c23a'}">{{ pool.balance || 0 }}</b></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" style="margin-top:12px">
|
||||
<el-col :span="8">
|
||||
<el-input v-model="addBetAmount" type="number" placeholder="金额" style="width:120px;margin-right:8px" />
|
||||
<el-button type="success" size="small" icon="el-icon-bottom" @click="addBet" :loading="poolLoading">注水</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-input v-model="addWinAmount" type="number" placeholder="金额" style="width:120px;margin-right:8px" />
|
||||
<el-button type="warning" size="small" icon="el-icon-top" @click="addWin" :loading="poolLoading">抽水</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-button size="small" icon="el-icon-refresh" @click="loadPool(); loadData()" :loading="poolLoading">刷新</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-table v-loading="loading" :data="boxList" border>
|
||||
<el-table-column prop="boxId" label="ID" min-width="50" align="center" />
|
||||
<el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="模式" min-width="120" align="center">
|
||||
<template slot-scope="scope">{{ boxTypeLabels[scope.row.boxType] || scope.row.boxType }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="bet" label="赌注" min-width="70" align="center" />
|
||||
<el-table-column prop="price" label="价格" min-width="80" align="center" />
|
||||
<el-table-column prop="totalOrnaments" label="总回合" min-width="80" align="center" />
|
||||
<el-table-column label="目标RTP" min-width="70" align="center">
|
||||
<template slot-scope="scope">{{ (scope.row.rtp * 100).toFixed(1) }}%</template>
|
||||
</el-table-column>
|
||||
<!-- 实时RTP=本轮口径(Redis轮次账本 mulSum/drawn/cm,抽满一轮/重算分布自动重置);
|
||||
如需改回全时段累计口径,把 cycleRtp 换回 actualRtp(>100飘红)即可 -->
|
||||
<el-table-column label="实时RTP" min-width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.cycleRtp != null" :style="{color: scope.row.cycleRtp > (scope.row.rtp * 100) ? '#f56c6c' : '#67c23a'}">
|
||||
{{ scope.row.cycleRtp }}%
|
||||
</span>
|
||||
<span v-else style="color:#c0c4cc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" min-width="60" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === '1' ? 'success' : 'danger'" size="small">
|
||||
{{ scope.row.status === '1' ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="260" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button size="mini" type="info" @click="handleOrnaments(scope.row)">饰品配置</el-button>
|
||||
<el-button size="mini" type="success" @click="openTemplateManager(scope.row)">模板管理</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
style="margin-top:16px;text-align:right"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="total"
|
||||
:page-size.sync="queryParams.pageSize"
|
||||
:current-page.sync="queryParams.pageNum"
|
||||
:page-sizes="[10,20,50,100]"
|
||||
@size-change="loadData"
|
||||
@current-change="loadData"
|
||||
/>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<el-dialog :title="'编辑箱子 - ' + form.name" :visible.sync="dialogVisible" width="480px" append-to-body>
|
||||
<el-form ref="form" :model="form" label-width="100px">
|
||||
<el-form-item label="模式类型">
|
||||
<el-input :value="boxTypeLabels[form.boxType] || form.boxType" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="基本赌注">
|
||||
<el-input-number v-model="form.bet" disabled style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格(自动)">
|
||||
<el-input-number v-model="form.price" disabled style="width:100%" />
|
||||
<span style="color:#909399;font-size:12px">价格 = 赌注 × 倍率系数</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="总回合数">
|
||||
<el-input-number v-model="form.totalRounds" :min="0" :step="50" style="width:100%" />
|
||||
<span style="color:#909399;font-size:12px">修改后将重新计算饰品分布</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="RTP">
|
||||
<el-input-number v-model="form.rtp" :min="0" :max="1" :precision="3" :step="0.05" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="固定/收敛回合%">
|
||||
<el-input-number v-model="form.fixedPct" :min="0" :max="100" :step="5" @change="onFixedPctChange" style="width:46%" />
|
||||
<span style="margin:0 2%">/</span>
|
||||
<el-input-number v-model="form.convergePct" :min="0" :max="100" :step="5" @change="onConvergePctChange" style="width:46%" />
|
||||
<div style="color:#909399;font-size:12px">固定% + 收敛% = 100;收敛回合用于一轮内把实时RTP动态拉回目标</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio label="1">启用</el-radio>
|
||||
<el-radio label="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 饰品配置弹窗 -->
|
||||
<el-dialog :title="'饰品配置 - ' + (boxTypeLabels[currentBox.boxType] || currentBox.boxType) + ' (' + currentBox.price + ')'" :visible.sync="ornamentVisible" width="650px" append-to-body>
|
||||
<div style="display:flex;align-items:center;margin-bottom:10px">
|
||||
<el-alert type="info" :closable="false" style="flex:1">
|
||||
计算回合(固定): <b>{{ ornamentTotal }}</b> |
|
||||
收敛回合: <b>{{ convergeRoundsDerived }}</b> |
|
||||
总回合: <b>{{ totalRoundsDerived }}</b> |
|
||||
计算RTP: <b>{{ ornamentRTP }}%</b> |
|
||||
目标RTP: <b>{{ (currentBox.rtp * 100).toFixed(1) }}%</b>
|
||||
</el-alert>
|
||||
<el-button type="warning" size="small" icon="el-icon-refresh" @click="handleOrnamentRecalc" style="margin-left:10px;flex-shrink:0">重算分布</el-button>
|
||||
</div>
|
||||
<el-table :data="ornamentList" border max-height="400">
|
||||
<el-table-column prop="id" label="ID" width="50" align="center" />
|
||||
<el-table-column label="饰品" min-width="100" align="center">
|
||||
<template slot-scope="scope">{{ getOrnName(scope.row.name) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="倍率范围" min-width="110" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.rangeLow }} - {{ scope.row.rangeHigh }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回合数" min-width="130" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-input-number v-model="scope.row.count" :min="0" :step="10" size="small" controls-position="right" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剩余" min-width="70" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{color: (scope.row.remaining != null ? scope.row.remaining : scope.row.count) <= 0 ? '#f56c6c' : '#67c23a'}">{{ scope.row.remaining != null ? scope.row.remaining : scope.row.count }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="动态收敛回合" min-width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span style="color:#e6a23c">{{ scope.row.convergeCount || 0 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div slot="footer">
|
||||
<el-button @click="ornamentVisible = false">取 消</el-button>
|
||||
<el-button type="success" icon="el-icon-document-add" @click="handleSaveAsTemplate">保存为模板</el-button>
|
||||
<el-button type="primary" @click="saveOrnaments">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 模板管理对话框(按箱子隔离) -->
|
||||
<el-dialog :title="'模板管理 - ' + (boxTypeLabels[templateBox.boxType] || templateBox.boxType || '') + ' (ID:' + templateBox.boxId + ')'" :visible.sync="templateDialogVisible" width="680px" append-to-body>
|
||||
<el-alert type="info" :closable="false" style="margin-bottom:12px">
|
||||
模板保存了该箱子的饰品回合分布。选择模板后点击【应用】可将饰品数量恢复为模板保存时的值。
|
||||
</el-alert>
|
||||
<el-table :data="boxTemplates" border v-if="boxTemplates.length > 0" highlight-current-row @current-change="onTemplateRowChange">
|
||||
<el-table-column label="模板名称" min-width="160">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.name }}
|
||||
<el-tag v-if="scope.row.isActive === 1" type="success" size="mini" style="margin-left:6px">生效中</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="饰品数" width="80" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.items ? scope.row.items.length : 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="固定回合" width="80" align="center">
|
||||
<template slot-scope="scope">{{ templateTotalRounds(scope.row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总回合" width="90" align="center">
|
||||
<template slot-scope="scope">{{ templateDerivedTotal(scope.row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="保存时间" width="170" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.createTime }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="danger" @click="handleDeleteTemplate(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-else description="该箱子暂无模板,请在饰品配置中修改数量后点击【保存为模板】。" />
|
||||
<div slot="footer">
|
||||
<el-button @click="templateDialogVisible = false">关 闭</el-button>
|
||||
<el-button type="primary" :disabled="!selectedTemplateRow" @click="handleApplyTemplate">应用选中模板</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getBoxList, saveBox, getBoxOrnaments, saveBoxOrnaments, calcDistribution, simulateRtp, getBoxTemplates, saveBoxTemplate, deleteBoxTemplate, applyBoxTemplate, getPoolStatus, addPoolBet, addPoolWin } from '@/api/game/sweet/box/api'
|
||||
import { getOrnamentList } from '@/api/game/sweet/ornament/api'
|
||||
|
||||
export default {
|
||||
name: 'SweetBox',
|
||||
data() {
|
||||
return {
|
||||
loading: false, recalcLoading: false, boxList: [], total: 0,
|
||||
queryParams: { pageNum: 1, pageSize: 10, name: null, boxType: null },
|
||||
dialogVisible: false, ornamentVisible: false, isEdit: false, currentBox: {}, origTotalRounds: 0,
|
||||
fixedRounds: 0, convergeRounds: 0,
|
||||
form: { boxId: null, name: '', bet: 1, boxType: 'normal', rtp: 0.8, status: '1', price: 0, totalRounds: 0, fixedPct: 85, convergePct: 15 },
|
||||
boxTypes: ['normal','ante','buy_fs','buy_super_fs'],
|
||||
boxTypeLabels: {
|
||||
'normal':'普通旋转', 'ante':'底注',
|
||||
'buy_fs':'购买免费旋转', 'buy_super_fs':'购买超级免费旋转'
|
||||
},
|
||||
boxTypeCM: { 'normal':1, 'ante':2, 'buy_fs':50, 'buy_super_fs':250 },
|
||||
modeOrnRange: {
|
||||
'normal': [1,15], 'ante': [1,17], 'buy_fs': [5,24], 'buy_super_fs': [6,31]
|
||||
},
|
||||
ornamentList: [], _ornamentAll: [], _ornamentExpects: {},
|
||||
estimatedRTP: '--', simRtpLoading: false,
|
||||
pool: { totalBet: 0, totalWin: 0, balance: 0, histRtp: '0%' },
|
||||
poolLoading: false, addBetAmount: null, addWinAmount: null,
|
||||
// 模板管理
|
||||
templateDialogVisible: false,
|
||||
templateBox: {},
|
||||
boxTemplates: [],
|
||||
selectedTemplateRow: null
|
||||
}
|
||||
},
|
||||
created() { this.loadData(); this.loadPool() },
|
||||
computed: {
|
||||
ornamentTotal() { return this.ornamentList.reduce((s, o) => s + (o.count || 0), 0) },
|
||||
ornamentRTP() {
|
||||
const total = this.ornamentTotal
|
||||
if (!total || !this.currentBox.bet) return '0.0'
|
||||
let contrib = 0
|
||||
this.ornamentList.forEach(o => {
|
||||
const mul = this._ornamentExpects[o.id]
|
||||
if (mul != null) contrib += (o.count || 0) * mul
|
||||
})
|
||||
const cm = this.boxTypeCM[this.currentBox.boxType] || 1
|
||||
return (contrib / total / cm * 100).toFixed(1)
|
||||
},
|
||||
// 收敛占比(当前箱子,空回退 0.15,兑底 [0,0.9])
|
||||
convergeRatioVal() {
|
||||
let cr = this.currentBox.rtpConvergeRatio != null ? Number(this.currentBox.rtpConvergeRatio) : 0.15
|
||||
if (!(cr >= 0)) cr = 0.15
|
||||
return Math.min(0.9, Math.max(0, cr))
|
||||
},
|
||||
// 由固定回合(计算回合)之和反推总回合 N = round(fixed/(1-cr))
|
||||
totalRoundsDerived() {
|
||||
const fixed = this.ornamentTotal
|
||||
return fixed > 0 ? Math.round(fixed / (1 - this.convergeRatioVal)) : 0
|
||||
},
|
||||
// 收敛回合 = 总回合 - 固定回合
|
||||
convergeRoundsDerived() {
|
||||
return Math.max(0, this.totalRoundsDerived - this.ornamentTotal)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// ==================== 模板加载(后端持久化) ====================
|
||||
_loadBoxTemplates(boxId) {
|
||||
getBoxTemplates(boxId).then(res => {
|
||||
this.boxTemplates = (res.data && res.data.list) || []
|
||||
}).catch(() => { this.boxTemplates = [] })
|
||||
},
|
||||
|
||||
// ==================== 保存为模板(在饰品配置弹窗中) ====================
|
||||
async handleSaveAsTemplate() {
|
||||
const items = this.ornamentList.filter(o => o.count > 0).map(o => ({
|
||||
ornamentId: o.id,
|
||||
count: o.count
|
||||
}))
|
||||
if (items.length === 0) {
|
||||
return this.$message.warning('当前没有配置任何饰品数量,无法保存模板')
|
||||
}
|
||||
const boxId = this.currentBox.boxId
|
||||
// 拉取已有模板名,用于不区分大小写的去重校验
|
||||
let existingNames = new Set()
|
||||
try {
|
||||
const res = await getBoxTemplates(boxId)
|
||||
const list = (res.data && res.data.list) || []
|
||||
existingNames = new Set(list.map(t => (t.name || '').toLowerCase().trim()))
|
||||
} catch { /* ignore */ }
|
||||
|
||||
this.$prompt('请输入模板名称(同一箱子内名称不可重复)', '保存为模板', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /\S+/,
|
||||
inputErrorMessage: '名称不能为空',
|
||||
inputValidator: (value) => {
|
||||
if (!value || !value.trim()) return '名称不能为空'
|
||||
if (existingNames.has(value.trim().toLowerCase())) {
|
||||
return '模板名称已存在,请使用不同的名称'
|
||||
}
|
||||
return true
|
||||
}
|
||||
}).then(async ({ value }) => {
|
||||
const trimmedName = value.trim()
|
||||
try {
|
||||
await saveBoxTemplate({ boxId, name: trimmedName, items })
|
||||
this.$message.success('模板【' + trimmedName + '】已保存')
|
||||
} catch (e) {
|
||||
this.$message.error('保存失败: ' + (e.message || e))
|
||||
}
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
// ==================== 模板管理对话框 ====================
|
||||
openTemplateManager(row) {
|
||||
this.templateBox = row
|
||||
this.boxTemplates = []
|
||||
this.selectedTemplateRow = null
|
||||
this.templateDialogVisible = true
|
||||
this._loadBoxTemplates(row.boxId)
|
||||
},
|
||||
onTemplateRowChange(row) {
|
||||
this.selectedTemplateRow = row
|
||||
},
|
||||
templateTotalRounds(tpl) {
|
||||
if (!tpl.items) return 0
|
||||
return tpl.items.reduce((s, i) => s + (i.count || 0), 0)
|
||||
},
|
||||
// 模板存的是固定池分布(固定回合),按当前箱子收敛占比反推总回合 N = round(fixed/(1-cr))
|
||||
templateDerivedTotal(tpl) {
|
||||
const fixed = this.templateTotalRounds(tpl)
|
||||
let cr = this.templateBox.rtpConvergeRatio != null ? Number(this.templateBox.rtpConvergeRatio) : 0.15
|
||||
if (!(cr >= 0)) cr = 0.15
|
||||
cr = Math.min(0.9, Math.max(0, cr))
|
||||
return fixed > 0 ? Math.round(fixed / (1 - cr)) : 0
|
||||
},
|
||||
handleDeleteTemplate(tpl) {
|
||||
this.$confirm('确定删除模板【' + tpl.name + '】吗?', '提示', { type: 'warning' }).then(async () => {
|
||||
try {
|
||||
await deleteBoxTemplate(tpl.id)
|
||||
this.selectedTemplateRow = null
|
||||
this.$message.success('已删除')
|
||||
this._loadBoxTemplates(this.templateBox.boxId)
|
||||
} catch (e) {
|
||||
this.$message.error('删除失败: ' + (e.message || e))
|
||||
}
|
||||
}).catch(() => {})
|
||||
},
|
||||
async handleApplyTemplate() {
|
||||
if (!this.selectedTemplateRow) return
|
||||
const tpl = this.selectedTemplateRow
|
||||
const boxId = this.templateBox.boxId
|
||||
try {
|
||||
await this.$confirm('将模板【' + tpl.name + '】的饰品分布应用到该箱子,并设为抽干补桶依据,确认?', '应用模板', { type: 'warning' })
|
||||
} catch { return }
|
||||
// 1. 应用模板(写入分布 + 标记为该箱子生效模板,作为抽干补桶依据)
|
||||
try {
|
||||
await applyBoxTemplate({ templateId: tpl.id })
|
||||
// 立即更新本地表格行中的总回合数(模板存固定回合,按收敛占比反推 N)
|
||||
const newTotal = this.templateDerivedTotal(tpl)
|
||||
this._updateBoxTotalOrnaments(boxId, newTotal)
|
||||
this.$message.success('模板已应用,饰品分布已更新')
|
||||
} catch (e) {
|
||||
this.$message.error('应用失败: ' + (e.message || e))
|
||||
return
|
||||
}
|
||||
// 2. 关闭模板对话框
|
||||
this.templateDialogVisible = false
|
||||
// 3. 如果饰品配置弹窗正打开着同一个箱子,立即刷新饰品列表
|
||||
if (this.ornamentVisible && this.currentBox.boxId === boxId) {
|
||||
this._refreshOrnamentList(boxId)
|
||||
}
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// ==================== 原有功能 ====================
|
||||
async handleRecalc() {
|
||||
this.$confirm('确认重算所有箱子的饰品分布吗?将根据当前 RTP 和总回合数重新分配。', '提示', { confirmButtonText: '确定', type: 'warning' })
|
||||
.then(async () => {
|
||||
this.recalcLoading = true
|
||||
try {
|
||||
const res = await getBoxList({ pageNum: 1, pageSize: 100 })
|
||||
const allBoxes = res.data && res.data.rows ? res.data.rows : []
|
||||
for (const box of allBoxes) {
|
||||
const rtp = box.rtp || 0.8
|
||||
const rounds = box.totalOrnaments || 200
|
||||
const convergeRatio = box.rtpConvergeRatio != null ? box.rtpConvergeRatio : 0.15
|
||||
await calcDistribution({ boxId: box.boxId, totalRounds: rounds, rtp, convergeRatio })
|
||||
}
|
||||
this.$message.success('全部重算完成')
|
||||
this.loadData()
|
||||
} catch (e) { /* ignore */ }
|
||||
this.recalcLoading = false
|
||||
}).catch(() => {})
|
||||
},
|
||||
loadData() {
|
||||
this.loading = true
|
||||
const params = { pageNum: this.queryParams.pageNum, pageSize: this.queryParams.pageSize }
|
||||
if (this.queryParams.name) params.name = this.queryParams.name
|
||||
if (this.queryParams.boxType) params.boxType = this.queryParams.boxType
|
||||
getBoxList(params).then(res => {
|
||||
this.boxList = res.data.rows || []
|
||||
this.total = res.data.total || 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
async loadPool() {
|
||||
this.poolLoading = true
|
||||
try {
|
||||
const res = await getPoolStatus()
|
||||
if (res.data) this.pool = res.data
|
||||
} catch (e) { /* ignore */ }
|
||||
this.poolLoading = false
|
||||
},
|
||||
async addBet() {
|
||||
if (!this.addBetAmount || this.addBetAmount <= 0) return this.$message.warning('请输入金额')
|
||||
this.poolLoading = true
|
||||
try {
|
||||
await addPoolBet({ amount: this.addBetAmount })
|
||||
this.$message.success('注水成功')
|
||||
this.addBetAmount = null
|
||||
await this.loadPool()
|
||||
} catch (e) { this.$message.error('操作失败') }
|
||||
this.poolLoading = false
|
||||
},
|
||||
async addWin() {
|
||||
if (!this.addWinAmount || this.addWinAmount <= 0) return this.$message.warning('请输入金额')
|
||||
this.poolLoading = true
|
||||
try {
|
||||
await addPoolWin({ amount: this.addWinAmount })
|
||||
this.$message.success('抽水成功')
|
||||
this.addWinAmount = null
|
||||
await this.loadPool()
|
||||
} catch (e) { this.$message.error('操作失败') }
|
||||
this.poolLoading = false
|
||||
},
|
||||
handleEdit(row) {
|
||||
this.isEdit = true
|
||||
this.origTotalRounds = row.totalOrnaments || 0
|
||||
const convergePct = row.rtpConvergeRatio != null ? Math.round(row.rtpConvergeRatio * 100) : 15
|
||||
this.form = { ...row, totalRounds: row.totalOrnaments || 0, rtp: row.rtp || 0.8, convergePct, fixedPct: 100 - convergePct }
|
||||
this.dialogVisible = true
|
||||
},
|
||||
onFixedPctChange(v) {
|
||||
const val = Math.min(100, Math.max(0, v || 0))
|
||||
this.form.fixedPct = val
|
||||
this.form.convergePct = 100 - val
|
||||
},
|
||||
onConvergePctChange(v) {
|
||||
const val = Math.min(100, Math.max(0, v || 0))
|
||||
this.form.convergePct = val
|
||||
this.form.fixedPct = 100 - val
|
||||
},
|
||||
submitForm() {
|
||||
const convergeRatio = (this.form.convergePct != null ? this.form.convergePct : 15) / 100
|
||||
const params = { boxId: this.form.boxId, name: this.form.name, bet: this.form.bet, boxType: this.form.boxType, rtp: this.form.rtp, status: this.form.status, rtpConvergeRatio: convergeRatio }
|
||||
saveBox(params).then(res => {
|
||||
const boxId = this.form.boxId || (res.data && res.data.boxId)
|
||||
if (boxId) {
|
||||
calcDistribution({ boxId, totalRounds: this.form.totalRounds, rtp: this.form.rtp, convergeRatio }).then(() => {
|
||||
this.$message.success('修改成功,饰品分布已更新'); this.dialogVisible = false; this.loadData()
|
||||
}).catch(() => { this.$message.success('修改成功(分布计算失败)'); this.dialogVisible = false; this.loadData() })
|
||||
}
|
||||
})
|
||||
},
|
||||
handleOrnaments(row) {
|
||||
this.currentBox = row
|
||||
getOrnamentList().then(ornRes => {
|
||||
this._ornamentAll = (ornRes.data && ornRes.data.list) ? ornRes.data.list : []
|
||||
this._ornamentExpects = {}
|
||||
this._ornamentAll.forEach(o => { this._ornamentExpects[o.id] = Number(o.expectMul) })
|
||||
this._refreshOrnamentList(row.boxId)
|
||||
})
|
||||
},
|
||||
_refreshOrnamentList(boxId) {
|
||||
getBoxOrnaments(boxId).then(res => {
|
||||
const items = res.data && res.data.list ? res.data.list : []
|
||||
this.fixedRounds = (res.data && res.data.fixedRounds) || 0
|
||||
this.convergeRounds = (res.data && res.data.convergeRounds) || 0
|
||||
const map = {}
|
||||
items.forEach(it => { map[it.ornamentId] = { count: it.count, remaining: it.remaining, convergeCount: it.convergeCount } })
|
||||
const [lo, hi] = this.modeOrnRange[this.currentBox.boxType] || [1, 17]
|
||||
this.ornamentList = this._ornamentAll
|
||||
.filter(o => o.id >= lo && o.id <= hi)
|
||||
.map(o => ({ ...o, count: (map[o.id] && map[o.id].count) || 0, remaining: map[o.id] ? map[o.id].remaining : 0, convergeCount: map[o.id] ? map[o.id].convergeCount : 0 }))
|
||||
if (!this.ornamentVisible) this.ornamentVisible = true
|
||||
this.fetchEstimatedRtp()
|
||||
})
|
||||
},
|
||||
/** 调用后端模拟API获取预估实际RTP */
|
||||
fetchEstimatedRtp() {
|
||||
const items = this.ornamentList.filter(o => o.count > 0).map(o => ({
|
||||
ornamentId: o.id,
|
||||
count: o.count
|
||||
}))
|
||||
if (items.length === 0) {
|
||||
this.estimatedRTP = '--'
|
||||
return
|
||||
}
|
||||
this.simRtpLoading = true
|
||||
simulateRtp({
|
||||
items: items,
|
||||
boxType: this.currentBox.boxType,
|
||||
rtp: this.currentBox.rtp || 0.8
|
||||
}).then(res => {
|
||||
this.estimatedRTP = (res.data && res.data.estimatedRtp) ? res.data.estimatedRtp.replace('%', '') : '--'
|
||||
}).catch(() => {
|
||||
this.estimatedRTP = '--'
|
||||
}).finally(() => {
|
||||
this.simRtpLoading = false
|
||||
})
|
||||
},
|
||||
getOrnName(name) { return name ? name.replace('x', '倍饰品') : name },
|
||||
handleOrnamentRecalc() {
|
||||
const box = this.currentBox
|
||||
const convergeRatio = box.rtpConvergeRatio != null ? box.rtpConvergeRatio : 0.15
|
||||
calcDistribution({ boxId: box.boxId, totalRounds: box.totalOrnaments || 200, rtp: box.rtp || 0.8, convergeRatio }).then(r => {
|
||||
if (r.data && r.data.items) {
|
||||
const map = {}
|
||||
r.data.items.forEach(it => { map[it.ornamentId] = it.count })
|
||||
const [lo, hi] = this.modeOrnRange[box.boxType] || [1, 17]
|
||||
this.ornamentList = this._ornamentAll
|
||||
.filter(o => o.id >= lo && o.id <= hi)
|
||||
.map(o => ({ ...o, count: map[o.id] || 0, remaining: map[o.id] || 0 }))
|
||||
// 同步更新表格行的总回合数(后端已按收敛占比反推 N,优先用返回的 boxTotal)
|
||||
const fixed = this.ornamentList.reduce((s, o) => s + (o.count || 0), 0)
|
||||
const newTotal = (r.data && r.data.boxTotal) || (fixed > 0 ? Math.round(fixed / (1 - this.convergeRatioVal)) : 0)
|
||||
this._updateBoxTotalOrnaments(box.boxId, newTotal)
|
||||
this.$message.success('分布已重算并保存')
|
||||
this.fetchEstimatedRtp()
|
||||
}
|
||||
}).catch(() => { this.$message.error('重算失败') })
|
||||
},
|
||||
saveOrnaments() {
|
||||
const fixed = this.ornamentList.reduce((s, o) => s + (o.count || 0), 0)
|
||||
const newTotal = fixed > 0 ? Math.round(fixed / (1 - this.convergeRatioVal)) : 0
|
||||
saveBoxOrnaments({
|
||||
boxId: this.currentBox.boxId,
|
||||
items: this.ornamentList.filter(o => o.count > 0).map(o => ({ ornamentId: o.id, count: o.count }))
|
||||
}).then(() => {
|
||||
this.$message.success('保存成功')
|
||||
// 立即更新本地表格行中的总回合数,避免显示旧值
|
||||
this._updateBoxTotalOrnaments(this.currentBox.boxId, newTotal)
|
||||
this.ornamentVisible = false
|
||||
this.loadData()
|
||||
})
|
||||
},
|
||||
/** 更新 boxList 中指定箱子的 totalOrnaments(本地立即生效,不等后端返回) */
|
||||
_updateBoxTotalOrnaments(boxId, newTotal) {
|
||||
const box = this.boxList.find(b => b.boxId === boxId)
|
||||
if (box) {
|
||||
this.$set(box, 'totalOrnaments', newTotal)
|
||||
}
|
||||
// 同步更新 currentBox 引用
|
||||
if (this.currentBox.boxId === boxId) {
|
||||
this.$set(this.currentBox, 'totalOrnaments', newTotal)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="16" style="margin-bottom:16px">
|
||||
<el-col :span="6"><el-card shadow="hover"><div style="text-align:center"><small>累计投注</small><h3>{{ summary.totalCost }}</h3></div></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><div style="text-align:center"><small>累计赢奖</small><h3>{{ summary.totalWin }}</h3></div></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><div style="text-align:center"><small>整体RTP</small><h3 :style="{color: rtp > 100 ? 'red' : 'green'}">{{ rtp }}%</h3></div></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="hover"><div style="text-align:center"><small>总局数</small><h3>{{ summary.totalSpins }}</h3></div></el-card></el-col>
|
||||
</el-row>
|
||||
|
||||
<div style="margin-bottom:16px">
|
||||
<el-date-picker v-model="queryParams.dateStart" type="date" placeholder="开始日期" value-format="yyyy-MM-dd" style="width:150px;margin-right:8px" />
|
||||
<span style="margin:0 4px">至</span>
|
||||
<el-date-picker v-model="queryParams.dateEnd" type="date" placeholder="结束日期" value-format="yyyy-MM-dd" style="width:150px;margin-right:8px" />
|
||||
<el-button type="primary" icon="el-icon-search" @click="loadAll">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="list" border>
|
||||
<el-table-column prop="date" label="日期" min-width="120" align="center" />
|
||||
<el-table-column prop="totalCost" label="总投注" min-width="120" align="center" />
|
||||
<el-table-column prop="totalWin" label="总赢奖" min-width="120" align="center" />
|
||||
<el-table-column prop="totalSpins" label="总局数" min-width="80" align="center" />
|
||||
<el-table-column prop="playerCount" label="玩家数" min-width="80" align="center" />
|
||||
<el-table-column label="RTP" min-width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{color: scope.row.totalCost > 0 && scope.row.totalWin / scope.row.totalCost > 1 ? 'red' : 'green'}">
|
||||
{{ scope.row.totalCost > 0 ? (Math.round(scope.row.totalWin / scope.row.totalCost * 10000) / 100).toFixed(2) + '%' : '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
style="margin-top:16px;text-align:right"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="total"
|
||||
:page-size.sync="queryParams.pageSize"
|
||||
:current-page.sync="queryParams.pageNum"
|
||||
:page-sizes="[10,20,50]"
|
||||
@size-change="loadData"
|
||||
@current-change="loadData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getWinList } from '@/api/game/sweet/dailywin/api'
|
||||
import request from '@/utils/request'
|
||||
|
||||
export default {
|
||||
name: 'SweetDailyWin',
|
||||
data() {
|
||||
return {
|
||||
loading: false, list: [], total: 0, summary: {},
|
||||
queryParams: { pageNum: 1, pageSize: 10, dateStart: new Date().toISOString().slice(0, 10), dateEnd: null }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rtp() {
|
||||
const c = Number(this.summary.totalCost) || 0, w = Number(this.summary.totalWin) || 0
|
||||
return c > 0 ? (Math.round(w / c * 10000) / 100).toFixed(2) : '0.00'
|
||||
}
|
||||
},
|
||||
created() { this.loadAll() },
|
||||
methods: {
|
||||
loadAll() { this.loadSummary(); this.loadData() },
|
||||
loadSummary() {
|
||||
request({ url: '/admin/sweet/win/summary', method: 'get', params: { dateStart: this.queryParams.dateStart, dateEnd: this.queryParams.dateEnd } }).then(res => {
|
||||
this.summary = res.data || {}
|
||||
})
|
||||
},
|
||||
loadData() {
|
||||
this.loading = true
|
||||
getWinList(this.queryParams).then(res => { this.list = res.data.rows || []; this.total = res.data.total || 0; this.loading = false })
|
||||
},
|
||||
handleReset() { this.queryParams = { pageNum: 1, pageSize: 10, dateStart: new Date().toISOString().slice(0, 10), dateEnd: null }; this.loadAll() }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,22 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-table v-loading="loading" :data="list" border>
|
||||
<el-table-column prop="id" label="ID" width="60" />
|
||||
<el-table-column label="饰品名称">
|
||||
<template slot-scope="scope">{{ scope.row.name.replace('x', '倍饰品') }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="expectMul" label="预期倍率" />
|
||||
<el-table-column prop="rangeLow" label="倍率下限" />
|
||||
<el-table-column prop="rangeHigh" label="倍率上限" />
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getOrnamentList } from '@/api/game/sweet/ornament/api'
|
||||
export default {
|
||||
name: 'SweetOrnament',
|
||||
data() { return { loading: false, list: [] } },
|
||||
created() { this.loading = true; getOrnamentList().then(res => { this.list = res.data.list; this.loading = false }) }
|
||||
}
|
||||
</script>
|
||||
@@ -1,125 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-table v-loading="loading" :data="list" border>
|
||||
<el-table-column prop="id" label="ID" width="60" />
|
||||
<el-table-column label="名称" min-width="180" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.id !== editingId" class="editable-name" @click="startEdit(scope.row)">{{ scope.row.name }}</span>
|
||||
<el-input v-else v-model="editName" size="mini" @blur="finishEdit(scope.row)" @keyup.enter.native="finishEdit(scope.row)" ref="nameInput" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="grade" label="品质" width="70" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.grade === 1" type="warning" size="small">金</el-tag>
|
||||
<el-tag v-else-if="scope.row.grade === 2" type="danger" size="small">红</el-tag>
|
||||
<el-tag v-else-if="scope.row.grade === 3" size="small">紫</el-tag>
|
||||
<el-tag v-else-if="scope.row.grade === 4" type="success" size="small">绿</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="饰品图片" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-image v-if="scope.row.imageUrl" :src="getImg(scope.row.imageUrl)" style="width:40px;height:40px" fit="cover" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="品质图片" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-image v-if="scope.row.levelImg" :src="getImg(scope.row.levelImg)" style="width:40px;height:40px" fit="cover" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="兜底" width="70" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.isFallback === 1" type="danger" size="small">是</el-tag>
|
||||
<el-tag v-else type="info" size="small">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="磨损" width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.exteriorName || '崭新出厂' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="返奖区间" width="180" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.winLow != null">{{ scope.row.winLow }} ~ {{ scope.row.winHigh }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog title="编辑奖品" :visible.sync="dialogVisible" width="480px">
|
||||
<el-form ref="form" :model="form" label-width="80px">
|
||||
<el-form-item label="名称"><el-input v-model="form.name" /></el-form-item>
|
||||
<el-form-item label="品质">
|
||||
<el-select v-model="form.grade" style="width:100%">
|
||||
<el-option label="1 - 金" :value="1" />
|
||||
<el-option label="2 - 红" :value="2" />
|
||||
<el-option label="3 - 紫" :value="3" />
|
||||
<el-option label="4 - 绿" :value="4" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="饰品图片">
|
||||
<ImageUpload v-model="form.imageUrl" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="磨损">
|
||||
<el-select v-model="form.exteriorName" placeholder="请选择磨损度" clearable style="width:100%">
|
||||
<el-option label="崭新出厂" value="崭新出厂" />
|
||||
<el-option label="略有磨损" value="略有磨损" />
|
||||
<el-option label="久经沙场" value="久经沙场" />
|
||||
<el-option label="破损不堪" value="破损不堪" />
|
||||
<el-option label="战痕累累" value="战痕累累" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="兜底饰品">
|
||||
<el-switch v-model="form.isFallback" :active-value="1" :inactive-value="0" active-text="是" inactive-text="否" />
|
||||
<div style="color:#909399;font-size:12px;margin-top:4px">可设置多个兜底饰品,未命中区间时随机选择一个兜底返奖</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="返奖区间">
|
||||
<div style="display:flex;align-items:center;gap:8px">
|
||||
<el-input-number v-model="form.winLow" :min="0" :max="form.winHigh" :precision="2" :step="0.01" controls-position="right" placeholder="最小值" style="flex:1" />
|
||||
<span>~</span>
|
||||
<el-input-number v-model="form.winHigh" :min="form.winLow" :precision="2" :step="0.01" controls-position="right" placeholder="最大值" style="flex:1" />
|
||||
</div>
|
||||
<div style="color:#909399;font-size:12px;margin-top:4px">玩家返奖金额落在该区间即获得此饰品</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { prizeList, prizeSave } from "@/api/game/sweet/prize/api";
|
||||
import ImageUpload from "@/components/ImageUpload";
|
||||
|
||||
export default {
|
||||
name: "SweetPrize",
|
||||
components: { ImageUpload },
|
||||
data() {
|
||||
return { loading: false, list: [], dialogVisible: false, form: {}, editingId: null, editName: '', rangeEditId: null, rangeLow: 0, rangeHigh: 0 };
|
||||
},
|
||||
computed: { fallbackExists() { return this.list.some(o => o.isFallback === 1); } },
|
||||
created() { this.getList(); },
|
||||
methods: {
|
||||
getList() { this.loading = true; prizeList().then(r => { this.list = r.data.list || []; this.loading = false; }); },
|
||||
getImg(u) { return u ? (u.startsWith('http') ? u : (process.env.VUE_APP_BASE_API || '') + u) : ''; },
|
||||
startEdit(row) { this.editingId = row.id; this.editName = row.name; this.$nextTick(() => { this.$refs.nameInput && this.$refs.nameInput.focus(); }); },
|
||||
finishEdit(row) { if (!this.editName || !this.editName.trim()) { this.$message.warning("名称不能为空"); this.editingId = null; return; } if (this.editName !== row.name) { row.name = this.editName.trim(); prizeSave(row).then(() => this.$message.success("已更新")); } this.editingId = null; },
|
||||
startRangeEdit(row) { this.rangeEditId = row.id; this.rangeLow = row.winLow; this.rangeHigh = row.winHigh; },
|
||||
finishRangeEdit(row) { if (this.rangeLow !== row.winLow || this.rangeHigh !== row.winHigh) { row.winLow = this.rangeLow; row.winHigh = this.rangeHigh; prizeSave(row).then(() => this.$message.success("区间已更新")); } this.rangeEditId = null; },
|
||||
handleEdit(row) { this.form = { ...row }; this.dialogVisible = true; },
|
||||
submitForm() { prizeSave(this.form).then(() => { this.$message.success("保存成功"); this.dialogVisible = false; this.getList(); }); },
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.editable-name { color: #409eff; cursor: pointer; border-bottom: 1px dashed #409eff; }
|
||||
.editable-name:hover { color: #66b1ff; }
|
||||
</style>
|
||||
@@ -1,101 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :inline="true" class="demo-form-inline">
|
||||
<el-form-item label="模式类型">
|
||||
<el-select v-model="queryParams.boxType" placeholder="全部" clearable>
|
||||
<el-option v-for="m in modeList" :key="m.key" :label="m.name" :value="m.key" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="玩家ID">
|
||||
<el-input v-model="queryParams.userId" placeholder="输入玩家ID" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="日期">
|
||||
<el-date-picker v-model="dateRange" type="daterange" range-separator="~" start-placeholder="开始" end-placeholder="结束" value-format="yyyy-MM-dd" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
<el-button type="danger" @click="clearAll">清除所有数据</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" :data="list" border>
|
||||
<el-table-column prop="id" label="ID" width="80" align="center" />
|
||||
<el-table-column prop="userId" label="用户ID" align="center" />
|
||||
<el-table-column prop="nickName" label="昵称" align="center" />
|
||||
<el-table-column label="模式" width="140" align="center">
|
||||
<template slot-scope="scope">{{ getModeName(scope.row.modeType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="prizeName" label="饰品名称" width="160" align="center" />
|
||||
<el-table-column label="饰品图片" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-image v-if="scope.row.prizeImage" :src="getImg(scope.row.prizeImage)" style="width:40px;height:40px" fit="cover" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="cost" label="花费" width="90" align="center" />
|
||||
<el-table-column prop="totalWin" label="赢取" width="90" align="center" />
|
||||
<el-table-column prop="createTime" label="时间" width="155" align="center" />
|
||||
<el-table-column label="操作" width="70" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
style="margin-top:16px;text-align:right"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="total"
|
||||
:page-size.sync="queryParams.pageSize"
|
||||
:current-page.sync="queryParams.pageNum"
|
||||
:page-sizes="[10,20,50]"
|
||||
@size-change="loadData"
|
||||
@current-change="loadData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getRecordList } from '@/api/game/sweet/record/api'
|
||||
import request from '@/utils/request'
|
||||
|
||||
export default {
|
||||
name: 'SweetRecord',
|
||||
data() {
|
||||
return {
|
||||
loading: false, list: [], total: 0,
|
||||
queryParams: { pageNum: 1, pageSize: 10, userId: null, boxType: null }, dateRange: null,
|
||||
modeList: [{key:'normal',name:'普通'},{key:'ante',name:'底注'},{key:'buy_fs',name:'购买免费旋转'},{key:'buy_super_fs',name:'购买超级免费旋转'}],
|
||||
// 卡片顺序:普通 → 底注 → 购FS → 购SuperFS
|
||||
cardOrder: [
|
||||
{ key: 'normal', name: '普通' },
|
||||
{ key: 'ante', name: '底注' },
|
||||
{ key: 'buy_fs', name: '购买免费旋转' },
|
||||
{ key: 'buy_super_fs', name: '购买超级免费旋转' }
|
||||
]
|
||||
}
|
||||
},
|
||||
created() { this.loadData() },
|
||||
methods: {
|
||||
getModeName(type) { const card = this.cardOrder.find(c => c.key === type); return card ? card.name : type },
|
||||
loadData() {
|
||||
this.loading = true
|
||||
const params = { pageNum: this.queryParams.pageNum, pageSize: this.queryParams.pageSize }
|
||||
if (this.queryParams.userId) params.userId = this.queryParams.userId
|
||||
if (this.queryParams.boxType) params.boxType = this.queryParams.boxType
|
||||
if (this.dateRange) { params.dateStart = this.dateRange[0]; params.dateEnd = this.dateRange[1] }
|
||||
getRecordList(params).then(res => { this.list = res.data.rows; this.total = res.data.total; this.loading = false })
|
||||
},
|
||||
resetQuery() { this.queryParams = { pageNum: 1, pageSize: 10, userId: null, boxType: null }; this.dateRange = null; this.loadData(); },
|
||||
handleDelete(row) { this.$confirm('确认删除此记录?').then(() => { request({ url: '/admin/sweet/record/' + row.id, method: 'delete' }).then(() => { this.$message.success('已删除'); this.loadData(); }) }) },
|
||||
clearAll() { this.$confirm('确认清除所有数据?此操作不可恢复!', '危险操作', { type: 'warning' }).then(() => { request({ url: '/admin/sweet/record/clear', method: 'delete' }).then(() => { this.$message.success('已清空'); this.loadData(); }) }) },
|
||||
handleSizeChange(v) { this.queryParams.pageSize = v; this.loadData() },
|
||||
handleCurrentChange(v) { this.queryParams.pageNum = v; this.loadData() },
|
||||
getImg(u) { return u ? (u.startsWith('http') ? u : (process.env.VUE_APP_BASE_API || '') + u) : '' }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.active-card { box-shadow: 0 0 0 2px #409eff; }
|
||||
</style>
|
||||
@@ -1,85 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||
<el-form-item label="回合ID">
|
||||
<el-input v-model="queryParams.roundId" placeholder="输入回合ID" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="模式类型">
|
||||
<el-select v-model="queryParams.boxType" placeholder="全部" clearable>
|
||||
<el-option v-for="m in modeTypes" :key="m.code" :label="m.name" :value="m.code" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="loadData">查询</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" :data="roundList" border style="width:100%">
|
||||
<el-table-column prop="roundId" label="回合ID" width="90" align="center" />
|
||||
<el-table-column prop="boxId" label="箱子ID" width="70" align="center" />
|
||||
<el-table-column label="模式" min-width="120" align="center">
|
||||
<template slot-scope="scope">{{ getModeName(scope.row.boxType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="ornamentId" label="饰品ID" width="70" align="center" />
|
||||
<el-table-column prop="actualMul" label="返奖倍率" width="90" align="center" />
|
||||
<el-table-column prop="freeSpinCount" label="免费旋转" width="80" align="center" />
|
||||
<el-table-column prop="totalCascades" label="翻转次数" width="80" align="center" />
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
style="margin-top:16px;text-align:right"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="total"
|
||||
:page-size.sync="queryParams.pageSize"
|
||||
:current-page.sync="queryParams.pageNum"
|
||||
:page-sizes="[10,20,50,100]"
|
||||
@size-change="loadData"
|
||||
@current-change="loadData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getRoundList } from '@/api/game/sweet/round/api'
|
||||
|
||||
export default {
|
||||
name: 'SweetRound',
|
||||
data() {
|
||||
return {
|
||||
loading: false, roundList: [], total: 0,
|
||||
queryParams: { pageNum: 1, pageSize: 10, roundId: null, boxType: null },
|
||||
modeTypes: [
|
||||
{ code: 'normal', name: '普通旋转' }, { code: 'ante', name: '底注' },
|
||||
{ code: 'buy_fs', name: '购买免费旋转' }, { code: 'buy_super_fs', name: '购买超级免费旋转' }
|
||||
],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
getModeName(boxType) {
|
||||
if (!boxType) return ''
|
||||
const m = this.modeTypes.find(t => t.code === boxType)
|
||||
return m ? m.name : boxType
|
||||
},
|
||||
loadData() {
|
||||
this.loading = true
|
||||
const params = { ...this.queryParams }
|
||||
if (!params.roundId) delete params.roundId
|
||||
if (!params.boxType) delete params.boxType
|
||||
getRoundList(params).then(res => {
|
||||
this.roundList = res.data.rows || []
|
||||
this.total = res.data.total || 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
resetQuery() {
|
||||
this.queryParams = { pageNum: 1, pageSize: 10, roundId: null, boxType: null }
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -5,7 +5,7 @@
|
||||
<div slot="header">
|
||||
<span>奖池统计</span>
|
||||
<el-button style="float: right" type="text" icon="el-icon-refresh" @click="refreshAll">刷新</el-button>
|
||||
<el-button style="float: right; margin-right: 16px; color: #F56C6C" type="text" @click="handleResetPool">重置奖池</el-button>
|
||||
<el-button style="float: right; margin-right: 16px; color: #a4485a" type="text" @click="handleResetPool">重置奖池</el-button>
|
||||
</div>
|
||||
<el-table :data="poolStatsList" border size="small">
|
||||
<el-table-column prop="pool" label="决策池" width="120" align="center">
|
||||
@@ -18,19 +18,19 @@
|
||||
<el-table-column prop="profit" label="系统盈利(毛口径)" align="right" min-width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip content="毛口径:累计下注 − 累计返奖(按饰品标价记账,含每注 0.01 安慰奖),不含人工调整;真实成本请看奖池余额" placement="top">
|
||||
<span :style="{ color: Number(scope.row.profit) > 0 ? '#67C23A' : '#F56C6C' }">{{ scope.row.profit }}</span>
|
||||
<span :style="{ color: Number(scope.row.profit) > 0 ? '#457450' : '#a4485a' }">{{ scope.row.profit }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="adjustment" label="人工调整" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{ color: Number(scope.row.adjustment) < 0 ? '#F56C6C' : '' }">{{ scope.row.adjustment }}</span>
|
||||
<span :style="{ color: Number(scope.row.adjustment) < 0 ? '#a4485a' : '' }">{{ scope.row.adjustment }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="poolBalance" label="奖池余额" align="right" />
|
||||
<el-table-column prop="rtp" label="RTP" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{ color: scope.row.rtp > 0.85 ? '#F56C6C' : '' }">{{ (scope.row.rtp * 100).toFixed(2) }}%</span>
|
||||
<span :style="{ color: scope.row.rtp > 0.85 ? '#a4485a' : '' }">{{ (scope.row.rtp * 100).toFixed(2) }}%</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="当前奖池余额">
|
||||
<span style="font-size: 16px; font-weight: bold; color: #409EFF;">{{ poolBalance }}</span> 元
|
||||
<span style="font-size: 16px; font-weight: bold; color: #9a5c35;">{{ poolBalance }}</span> 元
|
||||
<el-button type="text" size="mini" icon="el-icon-refresh" @click="fetchPoolBalance" style="margin-left: 8px">刷新</el-button>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="说明">
|
||||
@@ -67,7 +67,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="调整金额" prop="amount">
|
||||
<el-input-number v-model="adjustForm.amount" :min="0.01" :precision="2" :step="100" style="width: 100%;" />
|
||||
<div style="color: #909399; font-size: 12px; margin-top: 4px;">
|
||||
<div style="color: #7a664e; font-size: 12px; margin-top: 4px;">
|
||||
当前余额:{{ poolBalance }} 元
|
||||
</div>
|
||||
</el-form-item>
|
||||
@@ -92,7 +92,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="adjustmentAmount" label="调整金额" width="120" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{ color: Number(scope.row.adjustmentAmount) > 0 ? '#67C23A' : '#F56C6C', fontWeight: 'bold' }">
|
||||
<span :style="{ color: Number(scope.row.adjustmentAmount) > 0 ? '#457450' : '#a4485a', fontWeight: 'bold' }">
|
||||
{{ Number(scope.row.adjustmentAmount) > 0 ? '+' : '' }}{{ scope.row.adjustmentAmount }}
|
||||
</span>
|
||||
</template>
|
||||
@@ -117,13 +117,13 @@
|
||||
|
||||
<el-form-item label="游戏关闭">
|
||||
<el-switch v-model="config.switchOff" />
|
||||
<span style="margin-left: 8px; color: #E6A23C">切换后<strong>必须点下方「保存」才生效</strong>(只拨开关不会保存)</span>
|
||||
<span style="margin-left: 8px; color: #8f6418">切换后<strong>必须点下方「保存」才生效</strong>(只拨开关不会保存)</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="下注额">
|
||||
<div>
|
||||
<el-tag v-for="b in [5,10,20,50,100,200,500,1000,2000,5000]" :key="b" style="margin-right: 6px; margin-bottom: 4px;">{{ b }} 元</el-tag>
|
||||
<span style="color: #909399; font-size: 12px; margin-left: 8px">固定10档,对应奖励矩阵120条</span>
|
||||
<span style="color: #7a664e; font-size: 12px; margin-left: 8px">固定10档,对应奖励矩阵120条</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
@@ -131,12 +131,12 @@
|
||||
|
||||
<el-form-item label="转盘总格数">
|
||||
<el-input-number v-model="config.segmentCount" :min="8" :max="48" disabled />
|
||||
<span style="margin-left: 8px; color: #909399">内外圈各 N 格,索引一一对应</span>
|
||||
<span style="margin-left: 8px; color: #7a664e">内外圈各 N 格,索引一一对应</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="外圈符号布局">
|
||||
<el-input v-model="config.outerSymbols" placeholder="24个字符,由 A/B/C/D 组成" style="width: 500px" @change="onLayoutChanged" />
|
||||
<span style="margin-left: 8px; color: #909399">{{ (config.outerSymbols || '').length }} 格</span>
|
||||
<span style="margin-left: 8px; color: #7a664e">{{ (config.outerSymbols || '').length }} 格</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="内圈金色格数">
|
||||
@@ -147,14 +147,14 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="内圈紫色格数">
|
||||
<el-input-number v-model="config.innerPurpleCount" :min="0" :max="48" @change="onLayoutChanged" />
|
||||
<span v-if="innerSumError" style="color: #F56C6C; margin-left: 8px">三色合计必须 = {{ config.segmentCount }},当前 {{ innerSum }}</span>
|
||||
<span v-if="innerSumError" style="color: #a4485a; margin-left: 8px">三色合计必须 = {{ config.segmentCount }},当前 {{ innerSum }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">倍率矩阵(内圈3色 × 外圈4符号 = 12个组合)</el-divider>
|
||||
|
||||
<el-form-item label="倍率矩阵">
|
||||
<div style="width: 640px; margin-bottom: 8px; line-height: 20px;">
|
||||
<span style="color: #909399; font-size: 12px">
|
||||
<span style="color: #7a664e; font-size: 12px">
|
||||
这 12 格由下方「目标RTP(累计口径)」= <strong>{{ config.rtp }}</strong> 自动生成:倍率 = 目标 × {{ cellSquare }} ÷ (色格数 × 符号格数);改目标 → 12 格全变,手改某一格 → 目标RTP 跟着变成新的均衡返奖率;保存时一起写入 | 当前均衡返奖率 <strong>{{ matrixRtp }}%</strong><br>
|
||||
注意:「累计上限」此时等于倍率期望,上限会去削高倍格(实测押 38 倍那格只剩 ~65%)。需要让上限只当保险(比期望高 5 个点)就说一声,我加个固定余量
|
||||
</span>
|
||||
@@ -189,12 +189,12 @@
|
||||
|
||||
<el-form-item label="单局奖池投放比例">
|
||||
<el-input-number v-model="config.bufferRatio" :min="0" :max="1" :step="0.05" :precision="2" style="width: 200px" />
|
||||
<span style="margin-left: 8px; color: #909399">0 ~ 1,一次旋转最多可动用奖池余额的比例(填 0.6 = 最多拿出 60%)</span>
|
||||
<span style="margin-left: 8px; color: #7a664e">0 ~ 1,一次旋转最多可动用奖池余额的比例(填 0.6 = 最多拿出 60%)</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="目标RTP(累计口径)">
|
||||
<el-input-number v-model="config.rtp" :min="0" :max="1" :step="0.05" :precision="2" style="width: 200px" @change="applyRecalc" />
|
||||
<span style="margin-left: 8px; color: #909399">0 ~ 1,累计返奖 ≤ 累计投注 × 该值;本局可发额度 = 累计投注×该值 − 累计返奖;改动后上方倍率矩阵会自动重算。<strong>注意这是"上限不是目标"</strong>:实际返奖率通常低于它,详见下方说明</span>
|
||||
<span style="margin-left: 8px; color: #7a664e">0 ~ 1,累计返奖 ≤ 累计投注 × 该值;本局可发额度 = 累计投注×该值 − 累计返奖;改动后上方倍率矩阵会自动重算。<strong>注意这是"上限不是目标"</strong>:实际返奖率通常低于它,详见下方说明</span>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 盈利配置说明 -->
|
||||
@@ -203,7 +203,7 @@
|
||||
<div style="margin-bottom: 8px; font-weight: 600;">💡 配置说明(下一局立即生效)</div>
|
||||
|
||||
<div style="margin-bottom: 6px;">
|
||||
<strong>单局奖池投放比例 (bufferRatio = {{ config.bufferRatio }})</strong>:一局最多从奖池拿出 <span style="color: #E6A23C;">{{ bufferRatioPct }} 的余额</span> 来发奖
|
||||
<strong>单局奖池投放比例 (bufferRatio = {{ config.bufferRatio }})</strong>:一局最多从奖池拿出 <span style="color: #8f6418;">{{ bufferRatioPct }} 的余额</span> 来发奖
|
||||
</div>
|
||||
<div style="padding-left: 16px; color: #606266; font-size: 12px; margin-bottom: 8px;">
|
||||
• 例:奖池余额 10000 元 → 本项上限 10000 × {{ config.bufferRatio }} = <strong>{{ poolCapDemo }} 元</strong><br>
|
||||
@@ -212,19 +212,19 @@
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 6px;">
|
||||
<strong>目标RTP (rtp = {{ config.rtp }},累计口径)</strong>:按"总共该发多少 − 已经发了多少"算出<span style="color: #E6A23C;">还能发多少</span>,这就是本局的返奖预算(是发奖上限,不是平台欠玩家钱)
|
||||
<strong>目标RTP (rtp = {{ config.rtp }},累计口径)</strong>:按"总共该发多少 − 已经发了多少"算出<span style="color: #8f6418;">还能发多少</span>,这就是本局的返奖预算(是发奖上限,不是平台欠玩家钱)
|
||||
</div>
|
||||
<div style="padding-left: 16px; color: #606266; font-size: 12px; margin-bottom: 8px;">
|
||||
• 本局可发额度 = 累计真实投注 × {{ rtpPct }} − 累计真实返奖;<br>
|
||||
• 例:累计投注 10 万、累计返奖 7 万 → 该发 {{ rtpDemo.should }} 万、已发 7 万 → 本局额度 <strong>{{ rtpDemo.room }} 万</strong><br>
|
||||
• 它只是"这局最多能发多少":实际发多少仍由转盘概率决定,没发完的额度继续攒到以后(所以池子厚了能一局爆大奖)。本局投注在封盘时已计入累计,历史刚好发到 {{ rtpPct }} 时,额度正好 = 本局投注 × {{ rtpPct }}<br>
|
||||
• <strong style="color: #F56C6C;">它是"上限",不是"恒温器"</strong>:只保证长期返奖率<strong>不超过</strong> {{ rtpPct }},不保证一定发到 {{ rtpPct }}。只要还有组合没被真实玩家押上,系统就优先开"没人押的那一格"(全体只拿安慰奖),实际返奖率会明显低于该值<br>
|
||||
• <strong style="color: #a4485a;">它是"上限",不是"恒温器"</strong>:只保证长期返奖率<strong>不超过</strong> {{ rtpPct }},不保证一定发到 {{ rtpPct }}。只要还有组合没被真实玩家押上,系统就优先开"没人押的那一格"(全体只拿安慰奖),实际返奖率会明显低于该值<br>
|
||||
• 实际返奖率请以首页「实时RTP」为准,不要拿本配置值当预期
|
||||
</div>
|
||||
|
||||
<div style="padding: 8px; background: #F4F4F5; border-radius: 4px; margin-top: 8px;">
|
||||
<strong style="color: #303133;">协同机制</strong>:发奖上限 = <span style="color: #409EFF;">min(奖池余额×{{ config.bufferRatio }}, 本局可发额度)</span>,两条都要满足<br>
|
||||
<span style="color: #909399; font-size: 12px;">
|
||||
<strong style="color: #303133;">协同机制</strong>:发奖上限 = <span style="color: #9a5c35;">min(奖池余额×{{ config.bufferRatio }}, 本局可发额度)</span>,两条都要满足<br>
|
||||
<span style="color: #7a664e; font-size: 12px;">
|
||||
• 谁小听谁的:池子不够厚 → 按奖池的 {{ bufferRatioPct }} 卡;池子够厚 → 按返奖额度卡<br>
|
||||
• 额度够时,单局返奖可以远超本局投注("池子厚就能爆大奖")<br>
|
||||
• 后台注水/抽水只改奖池余额,只抬高上面第一条线,不会凭空增加返奖额度<br>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<el-table v-loading="loading" :data="betAmountList" border>
|
||||
<el-table-column prop="betAmount" label="下注额" width="120" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span style="font-size: 16px; font-weight: bold; color: #409eff">{{ scope.row.betAmount }} 元</span>
|
||||
<span style="font-size: 16px; font-weight: bold; color: #9a5c35">{{ scope.row.betAmount }} 元</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="倍率预览">
|
||||
@@ -50,7 +50,7 @@
|
||||
<el-card shadow="hover" :body-style="{ padding: '14px' }">
|
||||
<div slot="header" style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span style="font-weight: bold; font-size: 15px;">符号 {{ item.symbol }}</span>
|
||||
<span style="color: #67C23A; font-weight: bold; font-size: 14px;">{{ item.prizeValue }}</span>
|
||||
<span style="color: #457450; font-weight: bold; font-size: 14px;">{{ item.prizeValue }}</span>
|
||||
</div>
|
||||
<el-form label-width="56px" size="mini" label-position="left">
|
||||
<el-form-item label="倍率">
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<el-tag v-if="scope.row.result != null" type="primary" size="medium" style="font-size: 16px; font-weight: bold;">
|
||||
{{ scope.row.result }}
|
||||
</el-tag>
|
||||
<span v-if="scope.row.result != null && scope.row.symbols && scope.row.innerColors" style="margin-left: 8px; font-size: 16px; font-weight: bold; color: #409EFF;">
|
||||
<span v-if="scope.row.result != null && scope.row.symbols && scope.row.innerColors" style="margin-left: 8px; font-size: 16px; font-weight: bold; color: #9a5c35;">
|
||||
({{ getResultLabel(scope.row.result, scope.row.symbols, scope.row.innerColors) }})
|
||||
</span>
|
||||
</div>
|
||||
@@ -44,11 +44,11 @@
|
||||
textAlign: 'center',
|
||||
fontWeight: idx == scope.row.result ? 'bold' : 'normal',
|
||||
fontSize: idx == scope.row.result ? '16px' : '14px',
|
||||
color: idx == scope.row.result ? '#E6A23C' : '#909399',
|
||||
color: idx == scope.row.result ? '#8f6418' : '#7a664e',
|
||||
backgroundColor: idx == scope.row.result ? '#FDF6EC' : 'transparent',
|
||||
padding: idx == scope.row.result ? '3px 0' : '0',
|
||||
borderRadius: '3px',
|
||||
border: idx == scope.row.result ? '2px solid #E6A23C' : 'none',
|
||||
border: idx == scope.row.result ? '2px solid #8f6418' : 'none',
|
||||
marginRight: '2px'
|
||||
}"
|
||||
>{{ sym }}</span>
|
||||
@@ -90,7 +90,7 @@
|
||||
<el-table-column prop="bet" label="下注金额" width="100" align="right" />
|
||||
<el-table-column prop="win" label="单注赢得" width="100" align="right">
|
||||
<template slot-scope="inner">
|
||||
<span :style="{ color: Number(inner.row.win) > 0 ? '#67C23A' : '', fontWeight: Number(inner.row.win) > 0.01 ? 'bold' : 'normal' }">{{ inner.row.win }}</span>
|
||||
<span :style="{ color: Number(inner.row.win) > 0 ? '#457450' : '', fontWeight: Number(inner.row.win) > 0.01 ? 'bold' : 'normal' }">{{ inner.row.win }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -110,7 +110,7 @@
|
||||
<el-table-column prop="totalBetAmount" label="总下注" width="100" align="right" />
|
||||
<el-table-column prop="userRoundPayout" label="回合实际赔付" width="120" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{ color: scope.row.userRoundPayout > 0 ? '#E6A23C' : '' }">{{ scope.row.userRoundPayout }}</span>
|
||||
<span :style="{ color: scope.row.userRoundPayout > 0 ? '#8f6418' : '' }">{{ scope.row.userRoundPayout }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="settled" label="结算状态" width="90" align="center">
|
||||
@@ -217,13 +217,13 @@ export default {
|
||||
},
|
||||
/** 高亮颜色(用于中奖格文字) */
|
||||
getHighlightColor(color) {
|
||||
const map = { '金': '#E6A23C', '红': '#F56C6C', '紫': '#909399' };
|
||||
return map[color] || '#409EFF';
|
||||
const map = { '金': '#8f6418', '红': '#a4485a', '紫': '#7a664e' };
|
||||
return map[color] || '#9a5c35';
|
||||
},
|
||||
/** 高亮背景色(用于中奖格背景) */
|
||||
getHighlightBgColor(color) {
|
||||
const map = { '金': '#FDF6EC', '红': '#FEF0F0', '紫': '#F4F4F5' };
|
||||
return map[color] || '#ECF5FF';
|
||||
return map[color] || '#fbf5ea';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+147
-101
@@ -343,34 +343,41 @@ export default {
|
||||
this.lineChartInstance.dispose();
|
||||
}
|
||||
this.lineChartInstance = echarts.init(chartDom);
|
||||
/* 6 条线的颜色:按「色相均匀分布」取(约每 60° 一个),并统一压低饱和度。
|
||||
★ 这里刻意放宽了 Cottagecore 的配色纪律(出现了灰青 #4a7f8c、灰紫 #7a6a9c):
|
||||
上一轮为了收敛配色把 6 条线压成「两个绿 + 两个棕」,结果用户反馈"区分不明显"。
|
||||
数据可视化里「6 条线能分辨」是功能需求,优先于风格约束;饱和度已压低,不会显得刺眼。
|
||||
若哪天要严格贴风格,正解是拆成两张图(每张 ≤3 条线),而不是继续挤这 6 个色。 */
|
||||
var seriesConfig = [
|
||||
{ name: "充值", data: rechargeList, color: "#3b82f6" },
|
||||
{ name: "开箱流水", data: boxOpenList, color: "#10b981" },
|
||||
{ name: "对战开箱流水", data: fightBoxOpenList, color: "#f59e0b" },
|
||||
{ name: "幸运升级流水", data: luckyUpgradeList, color: "#8b5cf6" },
|
||||
{ name: "疯狂转盘流水", data: wheelList, color: "#14b8a6" },
|
||||
{ name: "玫瑰女郎流水", data: roseladyList, color: "#ec4899" }
|
||||
{ name: "充值", data: rechargeList, color: "#b05730" },
|
||||
{ name: "开箱流水", data: boxOpenList, color: "#9aa334" },
|
||||
{ name: "对战开箱流水", data: fightBoxOpenList, color: "#4a9163" },
|
||||
{ name: "幸运升级流水", data: luckyUpgradeList, color: "#4a7f8c" },
|
||||
{ name: "疯狂转盘流水", data: wheelList, color: "#7a6a9c" },
|
||||
{ name: "玫瑰女郎流水", data: roseladyList, color: "#a35790" }
|
||||
];
|
||||
var option = {
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.96)",
|
||||
borderColor: "#e5e7eb",
|
||||
backgroundColor: "rgba(255, 253, 250, 0.97)",
|
||||
borderColor: "#eadfd0",
|
||||
borderWidth: 1,
|
||||
textStyle: { color: "#374151", fontSize: 12 },
|
||||
textStyle: { color: "#5f4f3d", fontSize: 12 },
|
||||
axisPointer: {
|
||||
type: "line",
|
||||
lineStyle: { color: "#9ca3af", type: "dashed" }
|
||||
lineStyle: { color: "#7a664e", type: "dashed" }
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: seriesConfig.map(s => s.name),
|
||||
top: 0,
|
||||
icon: "roundRect",
|
||||
itemWidth: 12,
|
||||
itemHeight: 4,
|
||||
itemGap: 18,
|
||||
textStyle: { color: "#6b7280", fontSize: 12 },
|
||||
/* 原来色块只有 12x4px,像一条细线,6 个色块挤在一起根本看不出差别;
|
||||
加宽加厚成 16x8 的小色条 */
|
||||
itemWidth: 16,
|
||||
itemHeight: 8,
|
||||
itemGap: 16,
|
||||
textStyle: { color: "#7a664e", fontSize: 12 },
|
||||
selectedMode: true
|
||||
},
|
||||
grid: {
|
||||
@@ -385,17 +392,17 @@ export default {
|
||||
type: "category",
|
||||
boundaryGap: false,
|
||||
data: dateList,
|
||||
axisLine: { lineStyle: { color: "#e5e7eb" } },
|
||||
axisLine: { lineStyle: { color: "#eadfd0" } },
|
||||
axisTick: { show: false },
|
||||
axisLabel: {
|
||||
color: "#9ca3af",
|
||||
color: "#7a664e",
|
||||
rotate: dateList.length > 12 ? 45 : 0
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
axisLabel: {
|
||||
color: "#9ca3af",
|
||||
color: "#7a664e",
|
||||
formatter: function(value) {
|
||||
if (value >= 10000) return (value / 10000).toFixed(1) + '万';
|
||||
if (value >= 1000) return (value / 1000).toFixed(1) + 'k';
|
||||
@@ -403,7 +410,7 @@ export default {
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: { type: "dashed", color: "#f0f1f5" }
|
||||
lineStyle: { type: "dashed", color: "#eadfd0" }
|
||||
}
|
||||
},
|
||||
series: seriesConfig.map(s => ({
|
||||
@@ -412,9 +419,10 @@ export default {
|
||||
smooth: true,
|
||||
data: s.data,
|
||||
itemStyle: { color: s.color },
|
||||
lineStyle: { width: 2.5 },
|
||||
/* 线加粗到 3:6 条线叠在同一张图上时,细线更容易糊成一片 */
|
||||
lineStyle: { width: 3 },
|
||||
symbol: "circle",
|
||||
symbolSize: 6,
|
||||
symbolSize: 5,
|
||||
showSymbol: false,
|
||||
emphasis: { focus: "series" },
|
||||
areaStyle: {
|
||||
@@ -456,13 +464,13 @@ export default {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
color: #4f4130;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
p {
|
||||
margin: 4px 0 0;
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
color: #7a664e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,20 +497,25 @@ export default {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
background: #fffdfa;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 3px rgba(17, 24, 39, 0.06);
|
||||
border: 1px solid rgba(229, 231, 235, 0.7);
|
||||
transition: all 0.25s ease;
|
||||
box-shadow: 0 4px 14px rgba(139, 115, 85, 0.10);
|
||||
border: 1px solid rgba(212, 160, 160, 0.55);
|
||||
transition:
|
||||
transform 0.5s ease-in-out,
|
||||
box-shadow 0.5s ease-in-out,
|
||||
border-color 0.5s ease-in-out;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 10px 24px rgba(17, 24, 39, 0.1);
|
||||
/* 规范:悬停用「小幅放大 + ≤1deg 微旋」,避免机械式直上直下 */
|
||||
transform: scale(1.015) rotate(-0.3deg);
|
||||
border-color: #c08a8a;
|
||||
box-shadow: 0 10px 26px rgba(139, 115, 85, 0.16);
|
||||
|
||||
.stat-icon {
|
||||
transform: scale(1.08) rotate(-4deg);
|
||||
transform: rotate(-1deg) scale(1.04);
|
||||
}
|
||||
.stat-arrow {
|
||||
opacity: 1;
|
||||
@@ -519,16 +532,19 @@ export default {
|
||||
flex-shrink: 0;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 14px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
transition: transform 0.3s ease;
|
||||
/* 底色与图标色由 .theme-* 提供:浅底 + 同色深图标(水彩感),
|
||||
不再是一格一色的高饱和实心块 */
|
||||
background: rgba(154, 92, 53, 0.22);
|
||||
transition: transform 0.5s ease-in-out;
|
||||
|
||||
i {
|
||||
font-size: 26px;
|
||||
color: #fff;
|
||||
color: #9a5c35;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,7 +554,7 @@ export default {
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
color: #4f4130;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@@ -547,11 +563,11 @@ export default {
|
||||
.stat-label {
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
color: #7a664e;
|
||||
|
||||
.stat-note-icon {
|
||||
margin-left: 4px;
|
||||
color: #c0c4cc;
|
||||
color: #7a664e;
|
||||
cursor: help;
|
||||
}
|
||||
}
|
||||
@@ -563,32 +579,56 @@ export default {
|
||||
top: 50%;
|
||||
margin-top: -7px;
|
||||
font-size: 14px;
|
||||
color: #c0c4cc;
|
||||
color: #7a664e;
|
||||
opacity: 0;
|
||||
transform: translateX(-6px);
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
/* 图标渐变主题 */
|
||||
.theme-blue { background: linear-gradient(135deg, #4e9bff, #2f6bff); box-shadow: 0 6px 14px rgba(47, 107, 255, 0.3); }
|
||||
.theme-orange { background: linear-gradient(135deg, #ffb64d, #ff8a00); box-shadow: 0 6px 14px rgba(255, 138, 0, 0.3); }
|
||||
.theme-green { background: linear-gradient(135deg, #3ddc84, #14b866); box-shadow: 0 6px 14px rgba(20, 184, 102, 0.3); }
|
||||
.theme-red { background: linear-gradient(135deg, #ff7a6e, #f5424e); box-shadow: 0 6px 14px rgba(245, 66, 78, 0.3); }
|
||||
.theme-purple { background: linear-gradient(135deg, #a78bfa, #7c5cfc); box-shadow: 0 6px 14px rgba(124, 92, 252, 0.3); }
|
||||
.theme-cyan { background: linear-gradient(135deg, #38d1e0, #00a8c6); box-shadow: 0 6px 14px rgba(0, 168, 198, 0.3); }
|
||||
.theme-pink { background: linear-gradient(135deg, #ff85b3, #f43f7f); box-shadow: 0 6px 14px rgba(244, 63, 127, 0.3); }
|
||||
.theme-amber { background: linear-gradient(135deg, #ffd166, #f4a300); box-shadow: 0 6px 14px rgba(244, 163, 0, 0.3); }
|
||||
.theme-indigo { background: linear-gradient(135deg, #7f9cf5, #4c62e0); box-shadow: 0 6px 14px rgba(76, 98, 224, 0.3); }
|
||||
.theme-teal { background: linear-gradient(135deg, #45e0c0, #0fb894); box-shadow: 0 6px 14px rgba(15, 184, 148, 0.3); }
|
||||
.theme-sky { background: linear-gradient(135deg, #6ec1ff, #3c8ce7); box-shadow: 0 6px 14px rgba(60, 140, 231, 0.3); }
|
||||
/* 统计卡图标配色
|
||||
11 个卡片只用 5 个田园色(陶土 / 草绿 / 苔绿 / 雏菊金 / 玫瑰粉),循环使用。
|
||||
浅底 + 同色深图标。
|
||||
tint 从 .14~.18 提到 .22~.26:原来太淡,小图标几乎"看不见"(用户反馈对比度不够)。
|
||||
类名(blue/orange/...)只是 card.theme 的旧键名,沿用不改,免得动模板。 */
|
||||
.theme-blue,
|
||||
.theme-indigo {
|
||||
background: rgba(154, 92, 53, 0.22) !important;
|
||||
box-shadow: inset 0 0 0 1px rgba(154, 92, 53, 0.28);
|
||||
i { color: #8a4f2b !important; }
|
||||
}
|
||||
.theme-green,
|
||||
.theme-teal {
|
||||
background: rgba(69, 116, 80, 0.22) !important;
|
||||
box-shadow: inset 0 0 0 1px rgba(69, 116, 80, 0.28);
|
||||
i { color: #3a6342 !important; }
|
||||
}
|
||||
.theme-purple,
|
||||
.theme-cyan {
|
||||
background: rgba(127, 154, 95, 0.26) !important;
|
||||
box-shadow: inset 0 0 0 1px rgba(127, 154, 95, 0.32);
|
||||
i { color: #56682f !important; }
|
||||
}
|
||||
.theme-orange,
|
||||
.theme-amber {
|
||||
background: rgba(168, 121, 31, 0.24) !important;
|
||||
box-shadow: inset 0 0 0 1px rgba(168, 121, 31, 0.30);
|
||||
i { color: #7d5512 !important; }
|
||||
}
|
||||
.theme-red,
|
||||
.theme-pink,
|
||||
.theme-sky {
|
||||
background: rgba(181, 116, 116, 0.24) !important;
|
||||
box-shadow: inset 0 0 0 1px rgba(181, 116, 116, 0.30);
|
||||
i { color: #8f3f50 !important; }
|
||||
}
|
||||
|
||||
/* ========== 区块卡片(RTP / 图表) ========== */
|
||||
.section-card {
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
background: #faf6f0;
|
||||
border-radius: 16px;
|
||||
padding: 20px 24px;
|
||||
box-shadow: 0 1px 3px rgba(17, 24, 39, 0.06);
|
||||
border: 1px solid rgba(229, 231, 235, 0.7);
|
||||
box-shadow: 0 6px 18px rgba(139, 115, 85, 0.11);
|
||||
border: 1px solid rgba(212, 160, 160, 0.55);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -604,26 +644,27 @@ export default {
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
color: #4f4130;
|
||||
|
||||
.title-bar {
|
||||
/* 原为 4px 竖细条(单侧色带式装饰,规范禁止),改成草绿「莓果点」 */
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
border-radius: 2px;
|
||||
background: linear-gradient(180deg, #4e9bff, #2f6bff);
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: #5a8f5a;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-sub {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
color: #7a664e;
|
||||
|
||||
&.chart-sub {
|
||||
margin-left: 12px;
|
||||
font-weight: 400;
|
||||
background: #f3f4f6;
|
||||
background: #f3ece0;
|
||||
padding: 2px 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
@@ -647,16 +688,19 @@ export default {
|
||||
}
|
||||
|
||||
.rtp-card {
|
||||
border-radius: 12px;
|
||||
border-radius: 14px;
|
||||
padding: 16px 18px;
|
||||
background: #fafbfd;
|
||||
border: 1px solid #eef0f4;
|
||||
transition: all 0.25s ease;
|
||||
background: #f6efe3;
|
||||
border: 1px solid rgba(212, 160, 160, 0.48);
|
||||
transition:
|
||||
transform 0.5s ease-in-out,
|
||||
box-shadow 0.5s ease-in-out,
|
||||
background-color 0.5s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 18px rgba(17, 24, 39, 0.08);
|
||||
background: #fff;
|
||||
transform: scale(1.015) rotate(-0.3deg);
|
||||
box-shadow: 0 8px 20px rgba(139, 115, 85, 0.14);
|
||||
background: #fbf5ea;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -665,12 +709,13 @@ export default {
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #4b5563;
|
||||
color: #5f4f3d;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.rtp-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
/* 8px 在奶油底上偏小,色相看不出差别;放到 10px */
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
@@ -681,12 +726,14 @@ export default {
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 10px;
|
||||
/* 数值统一用正文墨色:颜色交给圆点与进度条,避免 5 个百分比各染一色 */
|
||||
color: #4f4130;
|
||||
}
|
||||
|
||||
.rtp-progress {
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: #eef0f4;
|
||||
background: #eadfd0;
|
||||
overflow: hidden;
|
||||
margin-bottom: 12px;
|
||||
|
||||
@@ -702,11 +749,11 @@ export default {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
color: #7a664e;
|
||||
line-height: 1.9;
|
||||
|
||||
span:last-child {
|
||||
color: #4b5563;
|
||||
color: #5f4f3d;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
@@ -715,42 +762,41 @@ export default {
|
||||
.rtp-empty {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
color: #c0c4cc;
|
||||
color: #7a664e;
|
||||
font-size: 13px;
|
||||
padding: 30px 0;
|
||||
}
|
||||
|
||||
/* RTP 主题色 */
|
||||
.rtp-theme-0 {
|
||||
.rtp-dot { background: #ec4899; }
|
||||
.rtp-value { color: #ec4899; }
|
||||
.rtp-progress-inner { background: linear-gradient(90deg, #ff85b3, #ec4899); }
|
||||
/* RTP 主题色(只给圆点 + 进度条上色)
|
||||
★ 与上方折线图的颜色一一对应:同一游戏在哪儿都是同一个色,
|
||||
这样「颜色」本身就带信息,不用每次重新认。
|
||||
(原来是品红 #ec4899 / 青 #14b8a6 / 紫 #8b5cf6 / 翠绿 #10b981 / 琥珀 #f59e0b,
|
||||
属于规范禁止的高饱和荧光色,已换掉。) */
|
||||
.rtp-theme-0 { /* 疯狂转盘 */
|
||||
.rtp-dot { background: #7a6a9c; }
|
||||
.rtp-progress-inner { background: linear-gradient(90deg, #a89cc0, #7a6a9c); }
|
||||
}
|
||||
.rtp-theme-1 {
|
||||
.rtp-dot { background: #14b8a6; }
|
||||
.rtp-value { color: #14b8a6; }
|
||||
.rtp-progress-inner { background: linear-gradient(90deg, #45e0c0, #14b8a6); }
|
||||
.rtp-theme-1 { /* 幸运升级 */
|
||||
.rtp-dot { background: #4a7f8c; }
|
||||
.rtp-progress-inner { background: linear-gradient(90deg, #8fb0b8, #4a7f8c); }
|
||||
}
|
||||
.rtp-theme-2 {
|
||||
.rtp-dot { background: #8b5cf6; }
|
||||
.rtp-value { color: #8b5cf6; }
|
||||
.rtp-progress-inner { background: linear-gradient(90deg, #a78bfa, #8b5cf6); }
|
||||
.rtp-theme-2 { /* 经典开箱 */
|
||||
.rtp-dot { background: #9aa334; }
|
||||
.rtp-progress-inner { background: linear-gradient(90deg, #c0c677, #9aa334); }
|
||||
}
|
||||
.rtp-theme-3 {
|
||||
.rtp-dot { background: #10b981; }
|
||||
.rtp-value { color: #10b981; }
|
||||
.rtp-progress-inner { background: linear-gradient(90deg, #3ddc84, #10b981); }
|
||||
.rtp-theme-3 { /* 对战 */
|
||||
.rtp-dot { background: #4a9163; }
|
||||
.rtp-progress-inner { background: linear-gradient(90deg, #8fb8a0, #4a9163); }
|
||||
}
|
||||
.rtp-theme-4 {
|
||||
.rtp-dot { background: #f59e0b; }
|
||||
.rtp-value { color: #f59e0b; }
|
||||
.rtp-progress-inner { background: linear-gradient(90deg, #ffd166, #f59e0b); }
|
||||
.rtp-theme-4 { /* 玫瑰女郎 */
|
||||
.rtp-dot { background: #a35790; }
|
||||
.rtp-progress-inner { background: linear-gradient(90deg, #c69bb8, #a35790); }
|
||||
}
|
||||
|
||||
/* ========== 图表切换标签 ========== */
|
||||
.chart-tabs {
|
||||
display: flex;
|
||||
background: #f3f4f6;
|
||||
background: #f3ece0;
|
||||
border-radius: 10px;
|
||||
padding: 3px;
|
||||
|
||||
@@ -758,19 +804,19 @@ export default {
|
||||
padding: 5px 18px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
color: #7a664e;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
color: #2f6bff;
|
||||
color: #9a5c35;
|
||||
}
|
||||
&.active {
|
||||
background: #fff;
|
||||
color: #2f6bff;
|
||||
background: #fffdfa;
|
||||
color: #9a5c35;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 1px 4px rgba(17, 24, 39, 0.1);
|
||||
box-shadow: 0 2px 8px rgba(139, 115, 85, 0.14);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
<el-table-column label="操作内容" align="left" min-width="320" :show-overflow-tooltip="true">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ actionOf(scope.row) }}</span>
|
||||
<span v-if="briefOf(scope.row)" style="color:#909399;"> {{ briefOf(scope.row) }}</span>
|
||||
<span v-if="briefOf(scope.row)" style="color:#7a664e;"> {{ briefOf(scope.row) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" align="center" prop="businessType" width="90">
|
||||
@@ -171,7 +171,7 @@
|
||||
<el-descriptions-item label="结果">
|
||||
<el-tag v-if="form.status === 0" type="success" size="mini">成功</el-tag>
|
||||
<el-tag v-else type="danger" size="mini">失败</el-tag>
|
||||
<span style="color:#909399;margin-left:6px;">{{ form.costTime }}毫秒</span>
|
||||
<span style="color:#7a664e;margin-left:6px;">{{ form.costTime }}毫秒</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="操作内容" :span="2">
|
||||
<b>{{ actionOf(form) }}</b>
|
||||
@@ -192,12 +192,12 @@
|
||||
<el-table-column label="项目" prop="label" width="160" />
|
||||
<el-table-column label="改之前" prop="before" :show-overflow-tooltip="true">
|
||||
<template slot-scope="scope">
|
||||
<span style="color:#F56C6C;">{{ scope.row.before }}</span>
|
||||
<span style="color:#a4485a;">{{ scope.row.before }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="改之后" prop="after" :show-overflow-tooltip="true">
|
||||
<template slot-scope="scope">
|
||||
<span style="color:#67C23A;">{{ scope.row.after }}</span>
|
||||
<span style="color:#457450;">{{ scope.row.after }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openBlankDialog">生成白号用户</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<span style="color: #909399; font-size: 12px; line-height: 32px;">
|
||||
<span style="color: #7a664e; font-size: 12px; line-height: 32px;">
|
||||
<i class="el-icon-info"></i> 提示:如需删除白号,请前往“用户管理 / 用户列表”操作。
|
||||
</span>
|
||||
</el-col>
|
||||
@@ -91,7 +91,7 @@
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="请选择数量:" style="margin-bottom: 12px;">
|
||||
<el-input-number size="mini" v-model="newNumber" :min="1" :max="10" @change="onBlankNumberChange"></el-input-number>
|
||||
<span style="font-size:12px;color:#909399;margin-left:10px;">
|
||||
<span style="font-size:12px;color:#7a664e;margin-left:10px;">
|
||||
<i class="el-icon-info"></i> 可生成 1-10 个白号用户
|
||||
</span>
|
||||
</el-form-item>
|
||||
@@ -108,7 +108,7 @@
|
||||
placeholder="留空则自动生成随机手机号"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div style="font-size:12px;color:#909399;padding-left:90px;">
|
||||
<div style="font-size:12px;color:#7a664e;padding-left:90px;">
|
||||
<i class="el-icon-info"></i> 手机号选填:填写后作为登录账号,留空则系统随机生成
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<el-table-column prop="id" label="ID" width="70" align="center" />
|
||||
<el-table-column prop="cardCode" label="卡密编码" width="200" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span style="font-family: 'Courier New', monospace; color: #409eff; 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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -154,7 +154,7 @@ export default {
|
||||
methods: {
|
||||
getPriceColor(row) {
|
||||
if (!row.cs340Price) return '#606266'; // 无三方价格时默认色
|
||||
return row.ornamentsPrice > row.cs340Price ? '#F56C6C' : '#67C23A'; // 溢价红色,未溢价绿色
|
||||
return row.ornamentsPrice > row.cs340Price ? '#a4485a' : '#457450'; // 溢价红色,未溢价绿色
|
||||
},
|
||||
handleSubmit(formName) {
|
||||
this.$refs[formName].validate(valid => {
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
已用:{{ scope.row.dailyUsedCount }}<br/>
|
||||
审核中:{{ scope.row.dailyPendingCount }}
|
||||
</div>
|
||||
<i class="el-icon-question" style="color: #909399; margin-left: 4px;"></i>
|
||||
<i class="el-icon-question" style="color: #7a664e; margin-left: 4px;"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
@@ -122,8 +122,8 @@
|
||||
<el-table-column align="center" class-name="operation-column" label="操作" width="120">
|
||||
<template slot-scope="scope">
|
||||
<div v-if="scope.row.status === 0" style="display: flex; justify-content: center; align-items: center;">
|
||||
<el-button type="text" style="color: #67C23A; margin: 0 5px;" @click="handleApprove(scope.row)">同意</el-button>
|
||||
<el-button type="text" style="color: #F56C6C; margin: 0 5px;" @click="handleReject(scope.row)">驳回</el-button>
|
||||
<el-button type="text" style="color: #457450; margin: 0 5px;" @click="handleApprove(scope.row)">同意</el-button>
|
||||
<el-button type="text" style="color: #a4485a; margin: 0 5px;" @click="handleReject(scope.row)">驳回</el-button>
|
||||
</div>
|
||||
<div v-else style="text-align: center; color: #999;">已处理</div>
|
||||
</template>
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
已用:{{ scope.row.dailyUsedCount }}<br/>
|
||||
审核中:{{ scope.row.dailyPendingCount }}
|
||||
</div>
|
||||
<i class="el-icon-question" style="color: #909399; margin-left: 4px;"></i>
|
||||
<i class="el-icon-question" style="color: #7a664e; margin-left: 4px;"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
@@ -265,7 +265,7 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
&:hover {
|
||||
background-color: #409eff;
|
||||
background-color: #9a5c35;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,7 +414,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="text-align:center; color:#909399; padding:20px;">暂无饰品</div>
|
||||
<div v-else style="text-align:center; color:#7a664e; padding:20px;">暂无饰品</div>
|
||||
<!-- 分页:默认每页 10 条 -->
|
||||
<div style="margin-top: 8px; display: flex; justify-content: flex-end;" v-if="orTotal > 0">
|
||||
<el-pagination
|
||||
@@ -1105,7 +1105,7 @@ export default {
|
||||
padding: 2px 5px;
|
||||
line-height: 32px;
|
||||
&:hover {
|
||||
background-color: #409eff;
|
||||
background-color: #9a5c35;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
@@ -1137,8 +1137,8 @@ export default {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.ornament-card:hover {
|
||||
border-color: #409eff;
|
||||
background: #f0f9ff;
|
||||
border-color: #9a5c35;
|
||||
background: #f6efe3;
|
||||
}
|
||||
.ornament-img {
|
||||
width: 50px;
|
||||
@@ -1164,7 +1164,7 @@ export default {
|
||||
}
|
||||
.ornament-price {
|
||||
font-size: 12px;
|
||||
color: #f56c6c;
|
||||
color: #a4485a;
|
||||
font-weight: bold;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
@@ -917,7 +917,7 @@ export default {
|
||||
"普通": "#b0c3d9",
|
||||
"普通级": "#b0c3d9"
|
||||
};
|
||||
return map[rarityName] || "#909399";
|
||||
return map[rarityName] || "#7a664e";
|
||||
},
|
||||
// 图表展示当前 tab 奖池当日的出货金额/开启次数/利润
|
||||
initEcharts(day, pool) {
|
||||
@@ -1455,7 +1455,7 @@ export default {
|
||||
padding: 2px 5px;
|
||||
line-height: 32px;
|
||||
&:hover {
|
||||
background-color: #409eff;
|
||||
background-color: #9a5c35;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<el-table-column align="center" prop="phoneNumber" label="手机号" width="140" />
|
||||
<el-table-column align="center" label="卡商余额" width="140">
|
||||
<template slot-scope="scope">
|
||||
<span style="color: #E6A23C; font-weight: bold; font-size: 16px;">{{ scope.row.merchantBalance }}</span>
|
||||
<span style="color: #8f6418; font-weight: bold; font-size: 16px;">{{ scope.row.merchantBalance }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="变动金额" width="140">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{ color: scope.row.credits > 0 ? '#67C23A' : '#F56C6C', fontWeight: 'bold' }">
|
||||
<span :style="{ color: scope.row.credits > 0 ? '#457450' : '#a4485a', fontWeight: 'bold' }">
|
||||
{{ scope.row.credits > 0 ? '+' : '' }}{{ scope.row.credits }}
|
||||
</span>
|
||||
</template>
|
||||
@@ -99,7 +99,7 @@
|
||||
<el-tag type="info" size="medium">{{ currentMerchant.nickName }}(ID: {{ currentMerchant.userId }})</el-tag>
|
||||
<br />
|
||||
<span style="margin-top: 8px; display: inline-block;">
|
||||
当前卡商余额:<strong style="color: #E6A23C; font-size: 18px;">{{ currentMerchant.merchantBalance }}</strong>
|
||||
当前卡商余额:<strong style="color: #8f6418; font-size: 18px;">{{ currentMerchant.merchantBalance }}</strong>
|
||||
</span>
|
||||
</div>
|
||||
<el-form :model="rechargeForm" :rules="rechargeRules" ref="rechargeForm" label-width="100px">
|
||||
@@ -134,7 +134,7 @@
|
||||
<el-tag type="info" size="medium">{{ currentMerchant.nickName }}(ID: {{ currentMerchant.userId }})</el-tag>
|
||||
<br />
|
||||
<span style="margin-top: 8px; display: inline-block;">
|
||||
当前卡商余额:<strong style="color: #E6A23C; font-size: 18px;">{{ currentMerchant.merchantBalance }}</strong>
|
||||
当前卡商余额:<strong style="color: #8f6418; font-size: 18px;">{{ currentMerchant.merchantBalance }}</strong>
|
||||
</span>
|
||||
</div>
|
||||
<el-form :model="editBalanceForm" :rules="editBalanceRules" ref="editBalanceForm" label-width="100px">
|
||||
@@ -149,10 +149,10 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="变动金额">
|
||||
<span :style="{ color: balanceDelta > 0 ? '#67C23A' : (balanceDelta < 0 ? '#F56C6C' : '#909399'), fontWeight: 'bold' }">
|
||||
<span :style="{ color: balanceDelta > 0 ? '#457450' : (balanceDelta < 0 ? '#a4485a' : '#7a664e'), fontWeight: 'bold' }">
|
||||
{{ balanceDelta > 0 ? '+' : '' }}{{ balanceDelta }}
|
||||
</span>
|
||||
<span style="color: #909399; margin-left: 8px;">(按差额记入充值 / 消费记录)</span>
|
||||
<span style="color: #7a664e; margin-left: 8px;">(按差额记入充值 / 消费记录)</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
@@ -419,12 +419,15 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 原来是 border-left: 4px solid #9a5c35 的单侧粗边框(规范列为禁止项),
|
||||
换成雏菊黄浅底的圆角标题条,仍是整块标题但不靠一侧色带表达层级 */
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid #409EFF;
|
||||
color: #303133;
|
||||
padding: 6px 14px;
|
||||
border-radius: 12px;
|
||||
background: rgba(245, 215, 95, 0.22);
|
||||
color: #4f4130;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -325,7 +325,7 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
&:hover {
|
||||
background-color: #409eff;
|
||||
background-color: #9a5c35;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,7 +393,7 @@ export default {
|
||||
padding: 2px 5px;
|
||||
line-height: 32px;
|
||||
&:hover {
|
||||
background-color: #409eff;
|
||||
background-color: #9a5c35;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
>发放饰品</el-button>
|
||||
<el-button icon="el-icon-edit" size="mini" type="text" @click="handleUpdate(scope.row)">编辑</el-button>
|
||||
<el-button icon="el-icon-date" size="mini" type="text" @click="handleDetail(scope.row)">详情</el-button>
|
||||
<el-button icon="el-icon-delete" size="mini" type="text" style="color: #f56c6c;" @click="handleDelete(scope.row)">删除</el-button>
|
||||
<el-button icon="el-icon-delete" size="mini" type="text" style="color: #a4485a;" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -545,7 +545,7 @@ export default {
|
||||
"普通": "#b0c3d9",
|
||||
"普通级": "#b0c3d9"
|
||||
};
|
||||
return map[rarityName] || "#909399";
|
||||
return map[rarityName] || "#7a664e";
|
||||
},
|
||||
// 饰品品质颜色映射:1黄/2红/3紫/4蓝/5蓝(4与5均为蓝色)
|
||||
getQualityColor(quality) {
|
||||
@@ -553,10 +553,10 @@ export default {
|
||||
"1": "#ffd700",
|
||||
"2": "#ff4d4f",
|
||||
"3": "#a16ee8",
|
||||
"4": "#409eff",
|
||||
"5": "#409eff"
|
||||
"4": "#9a5c35",
|
||||
"5": "#9a5c35"
|
||||
};
|
||||
return map[quality] || "#909399";
|
||||
return map[quality] || "#7a664e";
|
||||
},
|
||||
getQualityName(quality) {
|
||||
if (quality == '4' || quality == '5') return '蓝色';
|
||||
@@ -792,7 +792,7 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
&:hover {
|
||||
background-color: #409eff;
|
||||
background-color: #9a5c35;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
&:hover {
|
||||
background-color: #409eff;
|
||||
background-color: #9a5c35;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
&:hover {
|
||||
background-color: #409eff;
|
||||
background-color: #9a5c35;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,10 +172,10 @@
|
||||
></el-option>
|
||||
</el-select>
|
||||
<div v-if="form.rollType == 1 && form.userId && form.jackpotId" style="font-size: 13px; line-height: 1.6; margin-top: 4px;">
|
||||
当前金币余额:<span :style="{ color: balanceInsufficient ? '#F56C6C' : '#67C23A' }">{{ anchorBalance }}</span> 金币
|
||||
| 奖池总价值:<span style="color:#E6A23C;">{{ selectedJackpotTotal }}</span> 金币
|
||||
<span v-if="balanceInsufficient" style="color:#F56C6C; font-weight:bold;">(余额不足)</span>
|
||||
<span v-if="selectedJackpotTotal > 0 && selectedJackpotTotal < 200" style="color:#F56C6C; font-weight:bold;">(主播房奖池总价值不能低于200金币)</span>
|
||||
当前金币余额:<span :style="{ color: balanceInsufficient ? '#a4485a' : '#457450' }">{{ anchorBalance }}</span> 金币
|
||||
| 奖池总价值:<span style="color:#8f6418;">{{ selectedJackpotTotal }}</span> 金币
|
||||
<span v-if="balanceInsufficient" style="color:#a4485a; font-weight:bold;">(余额不足)</span>
|
||||
<span v-if="selectedJackpotTotal > 0 && selectedJackpotTotal < 200" style="color:#a4485a; font-weight:bold;">(主播房奖池总价值不能低于200金币)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="开奖时间" prop="endTime">
|
||||
@@ -225,7 +225,7 @@
|
||||
</el-dialog>
|
||||
<!-- 中奖详情 -->
|
||||
<el-dialog title="中奖详情" :visible.sync="winningVisible" width="700px">
|
||||
<div style="margin-bottom: 10px; color: #909399; font-size: 13px;">
|
||||
<div style="margin-bottom: 10px; color: #7a664e; font-size: 13px;">
|
||||
共 <b>{{ winningList.length }}</b> 件奖品,总价值 <b>{{ winningTotalPrice }}</b> 金币
|
||||
</div>
|
||||
<el-table v-loading="winningLoading" :data="winningPagedList" border stripe max-height="400">
|
||||
|
||||
@@ -412,7 +412,7 @@ export default {
|
||||
"普通": "#b0c3d9",
|
||||
"普通级": "#b0c3d9"
|
||||
};
|
||||
return map[rarityName] || "#909399";
|
||||
return map[rarityName] || "#7a664e";
|
||||
},
|
||||
serchprop() {
|
||||
this.loadings = true;
|
||||
@@ -620,7 +620,7 @@ export default {
|
||||
padding: 2px 5px;
|
||||
line-height: 32px;
|
||||
&:hover {
|
||||
background-color: #409eff;
|
||||
background-color: #9a5c35;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,7 +434,7 @@ export default {
|
||||
padding: 2px 5px;
|
||||
line-height: 32px;
|
||||
&:hover {
|
||||
background-color: #409eff;
|
||||
background-color: #9a5c35;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,7 +522,7 @@ export default {
|
||||
"普通": "#b0c3d9",
|
||||
"普通级": "#b0c3d9"
|
||||
};
|
||||
return map[rarityName] || "#909399";
|
||||
return map[rarityName] || "#7a664e";
|
||||
},
|
||||
initEcharts(res) {
|
||||
var chartDom = document.getElementById("main");
|
||||
|
||||
@@ -331,7 +331,7 @@ export default {
|
||||
"普通": "#b0c3d9",
|
||||
"普通级": "#b0c3d9"
|
||||
};
|
||||
return map[rarityName] || "#909399";
|
||||
return map[rarityName] || "#7a664e";
|
||||
},
|
||||
handleR() {
|
||||
document.body.click();
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div style="padding-left: 18px; color: #606266;">
|
||||
玩家选的概率 ≤ 75% 时,真实计算金额 = 投入金额;<br>
|
||||
玩家选的概率 > 75% 时,最高只按 75% 计算。<br>
|
||||
<span style="color: #909399;">例如:饰品价格 1000,选 90% 概率,实际扣 900,但真实计算金额只算 750。</span>
|
||||
<span style="color: #7a664e;">例如:饰品价格 1000,选 90% 概率,实际扣 900,但真实计算金额只算 750。</span>
|
||||
</div>
|
||||
<div><strong>3. 起始金额:</strong>本轮抽奖之前,真实计算口径下已经累计了多少钱。如果上一轮多投了,多出的部分会带到这里。</div>
|
||||
<div><strong>4. 本轮目标金额:</strong>= 饰品价格 × 幸运区间随机值 ÷ 100。幸运区间是后台配置的(如 [100,160]),每轮开始时在区间内随机取一个值,算出本轮累计到多少钱就会出货。</div>
|
||||
|
||||
@@ -336,7 +336,7 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
&:hover {
|
||||
background-color: #409eff;
|
||||
background-color: #9a5c35;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,7 +501,7 @@ export default {
|
||||
"普通": "#b0c3d9",
|
||||
"普通级": "#b0c3d9"
|
||||
};
|
||||
return map[rarityName] || "#909399";
|
||||
return map[rarityName] || "#7a664e";
|
||||
},
|
||||
getList() {
|
||||
this.loading = true;
|
||||
@@ -938,7 +938,7 @@ export default {
|
||||
padding: 2px 5px;
|
||||
line-height: 32px;
|
||||
&:hover {
|
||||
background-color: #409eff;
|
||||
background-color: #9a5c35;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
controls-position="right"
|
||||
style="width:150px"
|
||||
/>
|
||||
<span style="margin-left:8px; color:#909399;">
|
||||
<span style="margin-left:8px; color:#7a664e;">
|
||||
元 返水超过该金额不自动发放,需在下方「操作」列点「发放CDK」人工发放;填 <b>0</b> 表示不设上限
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user