diff --git a/.DS_Store b/.DS_Store index 36c4879..8ea65f8 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.trae/.ignore b/.trae/.ignore new file mode 100644 index 0000000..e69de29 diff --git a/.trae/documents/修复活动创建向导步骤逻辑并统一全局风格.md b/.trae/documents/修复活动创建向导步骤逻辑并统一全局风格.md new file mode 100644 index 0000000..986e9b8 --- /dev/null +++ b/.trae/documents/修复活动创建向导步骤逻辑并统一全局风格.md @@ -0,0 +1,49 @@ +## 问题确认 +- 症状:在“创建活动”向导中点击“下一步”后,出现“批量抽奖”界面,用户认为逻辑错误。 +- 初步研判:批量抽奖来自 `BatchDrawDialog`(src/views/activity/rewards/modules/batch-draw-dialog.vue)。该组件在管理页(src/views/activity/manage/index.vue)中常驻渲染,可能因不当条件渲染或状态误触发而在向导流程中弹出。 +- 路由侧现状:列表页有 `router.push({ name: 'ActivityWizard' })`,但路由模块未注册对应路由,存在跳转异常风险。 + +## 目标 +1. 保证向导“下一步”仅在步骤间顺序前进:活动 → 期数 → 奖品,绝不触发批量抽奖。 +2. 统一界面风格为项目全局规范:颜色、圆角、阴影、按钮、表单、布局与动画。 +3. 保留现有功能,避免回归,完成自测与构建验证。 + +## 技术方案 +### 1. 逻辑修复 +- 向导页面(两处)严格限制导航: + - `src/views/activity/wizard/index.vue`:`next()` 仅在成功创建活动后 `active.value = 1`,在成功创建期数后 `active.value = 2`,移除或校验任何非最终提交阶段的 `router.push` 与其他弹窗触发。 + - `src/views/activity/manage/index.vue`:`nextWizard()` 仅递进 `wizardActive`。确保 `BatchDrawDialog` 的 `v-model` 只由 `openBatchDraw()` 显式开启。 +- 隔离批量抽奖弹窗: + - 在管理页将 `` 增加条件渲染 `v-if="!showWizard"`,确保向导弹窗期间不渲染批量抽奖组件,从根本杜绝误触发。 +- 路由补全:在 `src/router/modules/activity.ts` 注册 `ActivityWizard` 路由(path `/activity/wizard`,component `'/activity/wizard'`),避免列表页“创建活动”跳转异常导致意外页面状态。 + +### 2. 风格统一 +- 去除自定义渐变与玻璃拟态,改为全局样式: + - 主容器采用 `art-card` 与项目标准阴影、圆角(卡片 16px,按钮/输入 12px)。 + - 步骤指示器使用项目的圆形数字样式与轻量过渡;颜色使用主题色 `#5D87FF` 及状态色变量。 + - 表单与按钮风格遵循项目统一尺寸、高度与交互(36px 高度,标准阴影与过渡)。 +- 统一 CSS 变量与工具类: + - 使用项目内的 CSS 变量(颜色、圆角、间距),移除局部硬编码样式。 + - 采用现有 `art-card`、栅格与间距体系,保证一致性。 + +### 3. 验证与测试 +- 开发验证: + - 在两处向导中执行完整流程(创建活动→创建期数→配置奖品),确认“下一步”不触发 `BatchDrawDialog`。 + - 构建检查:`npm run build` 无 TS/构建错误。 +- 回归验证: + - 管理页列表的“模拟(批量抽奖)”按钮仍可正常打开 `BatchDrawDialog`。 + - 列表页“创建活动”跳转到向导路由正常。 + +## 交付物 +- 修复后的向导逻辑代码(两处),隔离批量抽奖弹窗的条件渲染。 +- 新增路由项 `ActivityWizard`。 +- 去除非统一风格的样式,替换为项目全局风格实现。 +- 构建与运行验证结果说明。 + +## 验收标准 +- 向导点击“下一步”始终进入下一步骤,不出现批量抽奖。 +- 界面风格与全局统一(颜色、圆角、阴影、按钮、表单风格一致)。 +- 列表页“创建活动”跳转向导正常;管理页“模拟”按钮打开批量抽奖正常。 +- 构建通过,无新错误。 + +如确认以上方案,我将开始实施并同步每一步的修复与验证结果。 \ No newline at end of file diff --git a/.trae/documents/删除级联逻辑优化方案.md b/.trae/documents/删除级联逻辑优化方案.md new file mode 100644 index 0000000..78129b2 --- /dev/null +++ b/.trae/documents/删除级联逻辑优化方案.md @@ -0,0 +1,86 @@ +## 背景 +- 当前不启用数据库外键约束(FK),删除由业务逻辑驱动。 +- 现有删除实现多为单表删除,未做级联:如删除活动 `internal/service/activity/activity_delete.go:9`、删除期 `internal/service/activity/issue_delete.go:9`、删除用户 `internal/service/user/batch_user.go:32`。 +- GORM 初始化未声明外键或级联:`internal/repository/mysql/mysql.go:97`。 + +## 目标 +- 在业务层实现“逻辑级联删除”,保证删除主实体时,同步清理其关联业务数据,且性能与可控性优于 FK 级联。 + +## 级联清单 +### 删除活动(Activities.ID) +- 删除活动期:`activity_issues` 按 `activity_id`(`internal/repository/mysql/model/activity_issues.gen.go:18`) +- 期下配置与承诺: + - 奖励配置 `activity_reward_settings` 按 `issue_id`(`internal/repository/mysql/model/activity_reward_settings.gen.go:18`) + - 随机承诺 `issue_random_commitments` 按 `issue_id`(`internal/repository/mysql/model/issue_random_commitments.gen.go:18`) +- 期下抽奖相关: + - 抽奖日志 `activity_draw_logs` 按 `issue_id`(`internal/repository/mysql/model/activity_draw_logs.gen.go:18`) + - 抽奖效果 `activity_draw_effects` 按 `draw_log_id`/`issue_id`(`internal/repository/mysql/model/activity_draw_effects.gen.go:17,29`) + - 抽奖凭据 `activity_draw_receipts` 按 `draw_log_id`(`internal/repository/mysql/model/activity_draw_receipts.gen.go:17`) +- 活动范围效果与道具: + - 用户道具使用记录 `user_item_cards` 按 `used_activity_id` / `used_issue_id` / `used_draw_log_id`(`internal/repository/mysql/model/user_item_cards.gen.go:24,25,23`) + - 抽奖效果快照 `activity_draw_effects` 按 `activity_id`/`issue_id`(`internal/repository/mysql/model/activity_draw_effects.gen.go:28,29`) +- 资产与日志: + - 用户资产 `user_inventory` 按 `activity_id`(`internal/repository/mysql/model/user_inventory.gen.go:21`) + - 公会贡献日志 `guild_contribute_logs` 按 `activity_id` / `issues_id`(`internal/repository/mysql/model/guild_contribute_logs.gen.go:19,20`) +- 系统模板: + - 系统道具卡 `system_item_cards` 按 `activity_id` / `issue_id`(`internal/repository/mysql/model/system_item_cards.gen.go:23,24`) + - 系统优惠券 `system_coupons` 按 `activity_id`(`internal/repository/mysql/model/system_coupons.gen.go:20`) +- 最后删除活动主表:`activities`(`internal/repository/mysql/model/activities.gen.go:15`) + +### 删除用户(Users.ID) +- 用户身份与权益: + - 头衔 `user_titles` 按 `user_id`(`internal/repository/mysql/model/user_titles.gen.go:16`) + - 领取型权益限流 `user_title_effect_claims` 按 `user_id`(`internal/repository/mysql/model/user_title_effect_claims.gen.go:16`) +- 用户账户与地址: + - 地址 `user_addresses` 按 `user_id`(`internal/repository/mysql/model/user_addresses.gen.go:18`) + - 积分余额 `user_points` 按 `user_id`(`internal/repository/mysql/model/user_points.gen.go:18`) + - 积分流水 `user_points_ledger` 按 `user_id`(`internal/repository/mysql/model/user_points_ledger.gen.go:17`) +- 用户订单与优惠: + - 订单 `orders` 按 `user_id`(`internal/repository/mysql/model/orders.gen.go:18`) + - 用户优惠券 `user_coupons` 按 `user_id`(`internal/repository/mysql/model/user_coupons.gen.go:18`) +- 用户资产与道具: + - 用户资产 `user_inventory` 按 `user_id`(`internal/repository/mysql/model/user_inventory.gen.go:18`) + - 用户道具卡 `user_item_cards` 按 `user_id`(`internal/repository/mysql/model/user_item_cards.gen.go:18`) +- 与抽奖相关: + - 抽奖效果 `activity_draw_effects` 按 `user_id` 或关联 `draw_log_id`(`internal/repository/mysql/model/activity_draw_effects.gen.go:18,17`) + - 抽奖凭据 `activity_draw_receipts` 关联 `draw_log_id`(`internal/repository/mysql/model/activity_draw_receipts.gen.go:17`) + - 抽奖日志 `activity_draw_logs` 按 `user_id`(`internal/repository/mysql/model/activity_draw_logs.gen.go:17`) +- 公会关联: + - 公会成员 `guild_members` 按 `user_id`(`internal/repository/mysql/model/guild_members.gen.go:18`) + - 公会贡献日志 `guild_contribute_logs` 按 `user_id`(`internal/repository/mysql/model/guild_contribute_logs.gen.go:18`) +- 履约/发货: + - 发货记录 `shipping_records` 按 `user_id`(`internal/repository/mysql/model/shipping_records.gen.go:18`) + - 运营发货统计 `ops_shipping_stats` 按 `user_id`(`internal/repository/mysql/model/ops_shipping_stats.gen.go:21`) +- 最后软删用户:`users.deleted_at`(`internal/repository/mysql/model/users.gen.go:20`) + +## 删除顺序(事务内) +- 统一采用“从叶到根”的顺序: + 1) 以日志/效果/凭据等子表为先(`activity_draw_effects`、`activity_draw_receipts`、`activity_draw_logs`) + 2) 再清理资产/权益/模板(`user_inventory`、`user_item_cards`、`user_titles`、`system_*`) + 3) 清理期与期下配置(`activity_issues`、`activity_reward_settings`、`issue_random_commitments`) + 4) 删除根实体(`activities` 或软删 `users`) +- 全过程包裹在单事务中,任何一步失败则回滚。 + +## 实现策略 +- 新增业务服务方法: + - `DeleteActivityCascade(ctx, activityID)`:按“删除活动”清单顺序删除 + - `DeleteUserCascade(ctx, userID)`:按“删除用户”清单顺序删除 +- 技术要点: + - 批量删除使用分批(如 5k/批)避免长事务与大锁;必要时按时间/ID 片段迭代 + - 统一 `WHERE` 条件与索引列(`user_id`/`activity_id`/`issue_id`/`draw_log_id`)确保扫描性能 + - 软删与硬删:保留用户软删(合规与审计),其余按当前表定义硬删;如需统一软删,可后续逐表补充 `gorm.DeletedAt` + - 幂等性:每个子删除操作按条件删除,无记录时直接通过;重复调用不报错 + - 审计:记录操作日志(操作者、对象ID、影响行数),便于回溯 + +## 验收标准 +- 删除活动时,任一期及其抽奖日志/效果/凭据、奖励配置、承诺、资产、贡献日志与相关模板均被清理;根活动删除成功。 +- 删除用户时,地址/订单/优惠券/积分(余额+流水)/资产/道具/抽奖相关/公会关系/发货记录及统计均被清理;根用户软删成功。 +- 全流程事务保障、失败回滚;批量删除性能稳定,无显著锁表或超时。 + +## 需要改造的现有入口(参考) +- 活动删除入口:`internal/service/activity/activity_delete.go:9` → 升级为调用 `DeleteActivityCascade` +- 期删除入口:`internal/service/activity/issue_delete.go:9` → 被 `DeleteActivityCascade` 内部调用(或保留独立级联) +- 用户删除入口:`internal/service/user/batch_user.go:32` → 升级为调用 `DeleteUserCascade` + +## 后续动作 +- 我将基于以上清单与顺序,补充两套事务级联删除实现,并为关键入口替换调用;同时补充单元测试覆盖正常/边界/异常三类用例,验证幂等与性能。 \ No newline at end of file diff --git a/.trae/documents/商品列表批量操作与活动创建优化及仪表盘修复方案.md b/.trae/documents/商品列表批量操作与活动创建优化及仪表盘修复方案.md new file mode 100644 index 0000000..3a22339 --- /dev/null +++ b/.trae/documents/商品列表批量操作与活动创建优化及仪表盘修复方案.md @@ -0,0 +1,93 @@ +## 总览 +- 覆盖 5 个问题:商品列表批量操作、活动创建奖品价格与总价、仪表盘图表重叠修复、活动创建性能、弹窗点击关闭问题。 +- 技术栈:管理端前端 Vue3 + Element Plus(路径 `web/admin`),后端 Go(路径 `internal/*`)。 +- 目标:补齐商品批量接口、完善前端交互与缓存、修正布局与加载时序、增强性能与统一弹窗行为。 + +## 关键位置 +- 商品前端:`web/admin/src/views/product/list/index.vue`,API:`web/admin/src/api/product.ts` +- 商品后端:`internal/api/admin/product_create.go`、Service:`internal/service/product/product.go` +- 活动向导:`web/admin/src/views/activity/wizard/index.vue` +- 奖励管理:`web/admin/src/views/activity/rewards/index.vue` +- 仪表盘容器:`web/admin/src/views/dashboard/console/index.vue` +- 抽奖量图表:`web/admin/src/views/dashboard/console/modules/sales-overview.vue` + `components/core/charts/art-line-chart` +- 中奖率分析:`web/admin/src/views/dashboard/console/modules/activity-prize-analysis.vue` +- 弹窗示例:`web/admin/src/views/player-manage/modules/*Dialog.vue`、`components/core/layouts/*` + +## 1. 商品列表功能优化 +- 后端批量接口 + - 新增 `PUT /api/admin/products/batch`:请求体 `{ ids: number[], stock?: number, status?: 1|2 }`,返回 `{ updated_count, failed: [ {id, reason} ] }`。 + - Controller:在 `internal/api/admin/product_create.go` 或新文件 `product_batch.go` 增加路由与校验(限制 `ids` 1–1000,`stock >= 0`)。 + - Service:`internal/service/product/product.go` 增加 `BatchUpdate(ctx, ids, stock?, status?)`,事务更新;支持“仅库存”“仅状态”“二者皆有”。 + - DAO:使用批量更新(`UPDATE products SET stock=?, status=? WHERE id IN (...)`),对无权限/不存在的 `id` 记录失败原因。 + - 文档:更新 swagger(`docs/swagger.yaml`)。 +- 前端批量操作 + - 列表顶部添加“全选本页”复选框:基于 `ElTable` 的选择事件维护 `selectedIds: number[]`,`checkAll` 同步当页数据。 + - 批量工具栏:按钮“批量改库存”“批量上架”“批量下架”。每次操作弹出确认框(库存输入/操作确认)。 + - API:在 `web/admin/src/api/product.ts` 新增 `batchUpdateProducts(payload)` 调用上游接口;失败项以通知/表格高亮反馈。 + - 交互:操作成功后刷新当页并清空选中;支持 loading 与禁用态;无选中时禁用按钮。 + +## 2. 活动创建流程优化(奖品价格与总价) +- 单价展示 + - 远程商品搜索返回 `price` 字段(已有),选择时在 `ElSelect` 的 `option` 右侧显示“¥单价”。 + - 在奖励编辑卡片/表格中追加“单价”列(从缓存读取)。 +- 总价计算 + - 公式:`总价 = Σ(price(product_id) * quantity)`;监听 `product_id/quantity` 变更实时更新。 + - 缓存:在 `wizard/index.vue` 与 `rewards/index.vue` 维护 `priceCache: Map`,首次选择命中即用,未命中时调用 `fetchProducts` 补齐并写入。 + - 同步:商品价格变更时(重新打开或重新拉取),更新缓存并触发总价重算;保留两位小数。 + - 展示:步骤3顶部显示“选中奖品总成本:¥xxx.xx”。 + +## 3. 仪表盘数据展示修复(重叠问题) +- 布局与间距 + - 在 `console/index.vue` 的 `ElRow` 增加统一 `gutter`(如 `20`),保证模块间距。 + - 调整 `ElCol` 断点:避免同一行 `ActivityPrizeAnalysis` 与其他模块在窄屏并排压缩;在 `md/sm` 断点落到 `24` 独占一行。 +- 组件容器 + - 为 `sales-overview.vue` 与 `activity-prize-analysis.vue` 外层卡片设置 `position: relative; z-index: 0`;图表内部设 `z-index: 1`,避免 canvas/svg 溢出覆盖。 + - 统一卡片最小高度,防止加载时高度为 0 导致重叠。 +- 加载时序 + - 图表组件在 `dataReady` 后再渲染;`v-if="dataReady"` 避免空容器渲染。 + - 保留/增加骨架或加载态,防止内容突变挤压。 + +## 4. 活动创建性能优化(步骤1) +- 加载指示 + - `wizard/index.vue` 步骤1添加 `v-loading` 与骨架;按钮禁用在加载中。 +- 请求超时 + - 前端 axios 层设置接口超时 `30s`,在 `web/admin/src/api/_http.ts`(或全局实例)设置,并对超时给出 Toast 提示与重试入口。 +- 慢请求日志 + - Axios 响应拦截记录 `duration > 2000ms` 的接口,打印到控制台并上报(如有日志上报端点)。 +- 初始化优化 + - 并行拉取必要数据(活动分类、默认配置、组织信息),减少串行等待;命中表格缓存的接口优先读缓存(已有 `tableCache`)。 +- 错误恢复 + - 失败时保留已填写表单,显示错误提示与“重试/刷新数据”按钮;网络恢复后自动重载。 + +## 5. 弹窗点击关闭没有反应(统一与修复) +- 审核现状 + - 业务型对话框普遍设置 `:close-on-click-modal="false"`(避免误关);全局搜索/锁屏等未禁用遮罩关闭且部分隐藏关闭按钮。 +- 统一策略 + - 业务表单:保留 `close-on-click-modal=false`,确保“关闭按钮/取消按钮/ESC”都能关闭;统一通过 `@update:model-value` 与 `@closed` 重置。 + - 全局弹窗:明确设置 `:close-on-click-modal="false"` 并显示关闭按钮或提供显式关闭入口(避免遮罩点击不生效引起困惑)。 +- 修复点 + - 核查所有 `ElDialog/ElDrawer`:确保存在 `@update:model-value` 或 `v-model` 双向绑定;为关闭图标绑定 `emit('update:visible', false)`;在有 `before-close` 时正确调用 `done()`。 + - 统一 ESC 关闭(可选):在布局层面监听 ESC 并广播关闭事件。 + +## 验收标准 +- 商品批量: + - 在商品列表勾选 N 个,批量改库存/上下架成功,返回计数与失败明细;刷新后状态一致。 +- 奖品选择: + - 选择奖品即显示单价,总价随数量/选择实时更新;切换商品价格后总价同步。 +- 仪表盘: + - “活动抽奖量”与“活动中奖率分析”在所有断点无重叠,布局稳定;数据加载时不挤压。 +- 性能与超时: + - 步骤1加载显示进度,超时 30s 有提示与重试;>2s 请求有日志可见。 +- 弹窗关闭: + - 所有弹窗可通过关闭按钮/取消按钮/ESC 关闭;遮罩行为与设计一致,不出现“点击关闭没有反应”。 + +## 交付变更清单 +- 后端:新增批量接口(Controller+Service+DAO+Swagger)。 +- 前端: + - 商品列表批量选择与工具栏;新增批量 API 调用与确认反馈。 + - 向导步骤3与奖励管理页显示单价与总价;缓存价格。 + - 仪表盘栅格与 z-index 调整;图表渲染时序保护。 + - Axios 超时与慢请求日志;向导加载骨架与错误恢复。 + - 弹窗双向绑定与关闭逻辑统一。 + +如确认本方案,我将按上述清单依次实现并提交改动,过程中为关键函数添加函数级注释与必要单元/交互测试。 \ No newline at end of file diff --git a/.trae/documents/实现回收站与软删除机制.md b/.trae/documents/实现回收站与软删除机制.md new file mode 100644 index 0000000..02a64bb --- /dev/null +++ b/.trae/documents/实现回收站与软删除机制.md @@ -0,0 +1,68 @@ +## 总体方案 +- 引入统一软删除机制:所有支持回收站的业务表增加 `deleted_at DATETIME NULL`、`deleted_by BIGINT NULL` 字段,并在 ORM 层启用 GORM Soft Delete 以默认过滤已删除记录。 +- 提供中心化回收站接口与界面:集中列出各类型的软删除数据,支持恢复与彻底删除(二次确认)。 +- 权限与菜单对齐:仅管理员可访问回收站;侧边栏在“系统管理”下新增“回收站”菜单,配置角色可见与按钮权限。 + +## 数据库改造 +- 目标表(根据现有模型与端点清单):`activities`、`activity_issues`、`activity_reward_settings`、`products`、`product_categories`、`banner`、`guild`、`system_titles`、`system_title_effects`、`system_item_cards`、`system_coupons`、`menus`、`menu_actions`、`roles`、`role_users`。 +- 变更内容: + - 添加列:`deleted_at DATETIME NULL`(建立索引以提升查询效率)、`deleted_by BIGINT NULL`。 + - 后续通过 `cmd/gormgen` 重新生成 `internal/repository/mysql/{model,dao}/*.gen.go`,使模型包含 `gorm.DeletedAt` 与 `DeletedBy` 字段。 +- 迁移策略:当前仓库未集成迁移工具,计划新增 `migrations/` 与 SQL 脚本(或集成 `goose`),在测试/生产库执行列新增与索引建立,完成后重新生成 ORM 代码。 + +## 后端改造 +- 技术栈:`gin` + `gorm`(已存在),读写库封装见 `internal/repository/mysql/mysql.go`,路由集中在 `internal/router/router.go`。 +- 删除逻辑切换为软删除: + - 将所有 `DELETE /api/...` 对应的处理器由物理删除改为 `gorm` 软删除:`db.Delete(&model)`,自动写入 `deleted_at`;同时通过上下文注入当前用户 ID 写入 `deleted_by`(更新列)。 + - 涉及端点示例(集中定义于 `internal/router/router.go` 对应 handler):活动、期数、奖励、工会、商品、分类、轮播图、称号/特效、道具卡、优惠券、系统菜单/动作、角色、角色成员等。 +- 查询默认过滤: + - 使用含 `gorm.DeletedAt` 的模型,`gorm` 默认生成 `WHERE deleted_at IS NULL`;确保服务与 DAO 层不使用 `Unscoped()`,避免误返回已删除数据。 +- 回收站接口:新增 `internal/api/admin/system_recycle.go` 与服务层 `internal/service/recycle/recycle_service.go`: + - `GET /api/admin/recycle`:参数 `type`(枚举:activity、issue、reward、product...)、分页;实现为 `Unscoped().Where("deleted_at IS NOT NULL")` 列表,返回原始字段与 `deleted_at`、`deleted_by`。 + - `POST /api/admin/recycle/restore`:参数 `type,id`;`Unscoped()` 更新目标记录 `deleted_at=NULL, deleted_by=NULL`,恢复数据。 + - `DELETE /api/admin/recycle`:参数 `type,id`;`Unscoped().Delete(&model)` 执行物理删除。 +- 关联完整性: + - 软删除不级联子表;外键仍保持,恢复后关联自然可用。 + - 若业务场景需要父子一致软删除(可选扩展),在服务层增加按类型的联动策略,但默认不做级联以降低风险。 +- 权限拦截: + - 以上回收站接口全部走管理员认证中间件 `internal/router/interceptor/admin_auth.go::AdminTokenAuthVerify`。 + - 彻底删除在服务层进行额外二次确认校验(配合前端确认对话框),后端也校验 `role` 或 `action` 权限。 + +## 前端改造(web/admin) +- 框架:`vue3` + `vue-router` + `pinia` + `element-plus`,侧边栏位于 `web/admin/src/components/core/layouts/art-menus/art-sidebar-menu`,菜单数据由路由模块/后端菜单驱动。 +- 路由与页面: + - 新增路由模块 `web/admin/src/router/modules/system/recycle.ts`,`path:'/system/recycle'`,`meta.roles:['admin']`,`meta.authList:['recycle:list','recycle:restore','recycle:forceDelete']`。 + - 新增页面 `web/admin/src/views/system/recycle/index.vue`: + - 顶部类型筛选(下拉:活动、商品、工会…)。 + - 表格列出删除数据(含关键原字段、`deleted_at`、`deleted_by`),分页。 + - 行操作:`恢复`(调用 `POST /api/admin/recycle/restore`)、`彻底删除`(`ElMessageBox.confirm` 二次确认后调用 `DELETE /api/admin/recycle`)。 +- API 封装:`web/admin/src/api/system/recycle.ts`(使用现有 Axios 封装 `web/admin/src/utils/http/index.ts`):`listDeleted(type,page,size)`、`restore(type,id)`、`forceDelete(type,id)`。 +- 菜单项: + - 后端在 `menus` 表增加“系统管理/回收站”,`path:'/system/recycle'`,并在角色分配接口 `POST /api/role/:role_id/menus` 仅赋予管理员;前端 `ListSimpleMenus` 拉取后自动渲染。 +- 权限指令与守卫: + - 按按钮权限 `meta.authList` 与角色 `meta.roles` 控制显示;普通用户不显示菜单,无法访问路由或调用接口。 + +## 权限控制细则 +- 仅管理员角色拥有“回收站”菜单与相关按钮权限;普通用户删除操作也走软删除,但无法访问回收站接口。 +- 彻底删除在前端强制二次确认,并在后端再次校验权限与保护条件(如必须管理员)。 + +## 测试方案 +- 后端(Go `testing`): + - `internal/api/admin/system_recycle_test.go`: + - 删除后记录存在于回收站接口,原查询不可见(验证 `deleted_at` 与默认过滤)。 + - 恢复后记录重新可见,`deleted_at`/`deleted_by` 为空。 + - 权限:非管理员访问回收站接口返回 `401/403`;管理员正常。 + - 关联完整性:恢复后关联查询正常(父子外键未破坏)。 +- 前端(Vitest): + - `web/admin/src/tests/system/recycle/recycle.test.ts`: + - 菜单与路由权限:管理员可见“回收站”,普通用户不可见。 + - 列表加载与分页、恢复按钮触发成功提示与刷新、彻底删除二次确认弹窗与成功流程。 + - 401/403 拦截按现有拦截器工作(参考 `src/tests/auth/401-error-handling.test.ts`)。 + +## 交付与验证 +- 执行数据库迁移并生成 ORM 代码 → 修改删除逻辑到软删除 → 新增回收站接口与前端页面 → 配置菜单与角色 → 编写并运行测试。 +- 验收标准: + - 所有默认查询不返回已删除记录。 + - 回收站能列出各类删除记录,恢复/彻底删除工作正常。 + - 权限严格生效;普通用户删除进入回收站但不可访问回收站界面。 + - 恢复后关联数据完整、功能正常。 \ No newline at end of file diff --git a/.trae/documents/彻底修复批量抽奖误弹与统一“新建活动”UI为 Element Plus 官方风格.md b/.trae/documents/彻底修复批量抽奖误弹与统一“新建活动”UI为 Element Plus 官方风格.md new file mode 100644 index 0000000..dcd8c7a --- /dev/null +++ b/.trae/documents/彻底修复批量抽奖误弹与统一“新建活动”UI为 Element Plus 官方风格.md @@ -0,0 +1,49 @@ +## 目标 + +* 在活动模块中彻底移除“批量测试/批量抽奖”入口与弹窗,避免在创建流程中出现。 + +* 保留其他模块(如“抽奖模拟盈亏”页面)的相似功能,不影响运营侧工具。 + +* 统一“新建活动”UI为 [https://www.artd.pro/docs/zh/ ](https://www.artd.pro/docs/zh/) 官方风格,保持与项目其他新建页面一致。 + +## 变更范围 + +* 移除组件引用与入口: + + * `web/admin/src/views/activity/manage/index.vue`:删除 `BatchDrawDialog` 引用与渲染、`openBatchDraw` 按钮及逻辑。 + + * `web/admin/src/views/activity/list/index.vue`:删除批量抽奖按钮与 `BatchDrawDialog` 渲染。 + +* 组件保留但不再使用: + + * `web/admin/src/views/activity/rewards/modules/batch-draw-dialog.vue` 保留于项目(供其他模块复用),但活动模块不再引用。 + +* 路由与流程: + + * 无需调整路由;保留 `Operations/LotterySimulation` 等运营侧工具。 + + * 确保“创建活动 → 期数 → 奖品”流程不再出现任何测试入口。 + +## UI统一 + +* 两处向导页面完全采用官方 EP 风格(已部分完成): + + * `ElSteps` simple 模式; + + * `ElCard` 内容区; + + * `ElForm` 默认尺寸与标准栅格; + + * 移除自定义渐变、玻璃拟态与重阴影。 + +## 验收标准 + +* 活动模块中不再出现“批量测试/批量抽奖”按钮或弹窗。 + +* 创建流程不再被任何测试功能打断。 + +* 新建活动页面的视觉与项目其他新建保持一致(EP 官方风格)。 + +* 构建与运行通过,页面与流程验证无误。 + +如确认,我将执行上述移除与统一改造,并完成构建与流程验证。 diff --git a/.trae/documents/活动创建“下一步”无响应的排查与改造方案.md b/.trae/documents/活动创建“下一步”无响应的排查与改造方案.md new file mode 100644 index 0000000..8b7791b --- /dev/null +++ b/.trae/documents/活动创建“下一步”无响应的排查与改造方案.md @@ -0,0 +1,31 @@ +## 现状确认 +- 后端接口已对接:`POST /api/admin/activities`(`internal/router/router.go:78`),控制器 `CreateActivity`(`internal/api/admin/activities_admin.go:41`)。 +- 管理端前端已调用:`createActivity()` 指向 `admin/activities`(`web/admin/src/api/adminActivities.ts:13`)。 +- 向导有两种: + - 独立向导页 `wizard/index.vue`:`下一步`仅切换步骤(`next()`),提交动作分别由页面内的`提交`按钮触发(`submitActivity/submitIssue/submitRewards`)。 + - 管理页弹窗向导 `manage/index.vue`:`下一步`会调用创建接口然后推进步骤(`nextWizard()`)。 + +## 可能原因 +- 使用了独立向导页,点击`下一步`未触发API(预期行为需用`提交`按钮)。 +- 请求未通过权限:后端要求超级管理员(`ctx.SessionUserInfo().IsSuper == 1`,`internal/api/admin/activities_admin.go:56-59`)。 +- 请求参数不合法:`name`必填、`activity_category_id`不可为0(同文件 15-18, 51-54)。 +- 环境未配置或认证头缺失:`VITE_API_URL` 未指向后端 `/api` 前缀,或 `Authorization` 未带登录后的纯 Token(不加 `Bearer`)。 + +## 排查步骤 +1) 打开浏览器 Network,在点击`下一步`或`提交`后确认是否发起 `POST {VITE_API_URL}/api/admin/activities`;查看响应码与返回体。 +2) 确认当前页面是弹窗向导还是独立向导: + - 若是独立向导,请使用每步的`提交`按钮;`下一步`只换页不提交。 +3) 验证权限与参数: + - 登录账号需 `is_super=1`;否则返回 400 `禁止操作`。 + - `name`与`activity_category_id`需填充;分类可通过 `GET /api/admin/activity_categories` 获取。 +4) 检查前端环境: + - `VITE_API_URL` 应指向后端根(例如 `http://localhost:8000`),请求工具会拼接 `admin/...` 成 `/api/admin/...`。 + - `Authorization` 头为登录获得的 Token 字符串(无 `Bearer` 前缀)。 + +## 改造建议(可选) +- 为独立向导页 `wizard/index.vue` 增强交互:在 `next()` 中根据 `active` 自动调用对应的 `submitActivity/submitIssue`,使“下一步”即提交。 +- 在按钮文案上提示“提交并进入下一步”,并在未填必填项时给出校验提示(阻止推进)。 + +## 验收标准 +- 在管理端,创建活动流程点击`提交`或(改造后)点击`下一步`能成功发起请求并进入下一步;错误时有明确提示。 +- 非超级管理员或参数缺失时,前端能正确显示后端返回的错误信息。 \ No newline at end of file diff --git a/.trae/documents/活动创建流程重构方案.md b/.trae/documents/活动创建流程重构方案.md new file mode 100644 index 0000000..e9f0d91 --- /dev/null +++ b/.trae/documents/活动创建流程重构方案.md @@ -0,0 +1,87 @@ +## 目标 +- 将活动创建流程改造成“清晰、可操作、可回退”的向导式体验,显著降低填写负担、减少出错率,并确保与现有后端接口完全对齐。 +- 统一字段与交互:活动、期、奖励配置的字段、校验与排序一致;消除重复输入与歧义(如奖励名称以选品自动回填)。 +- 杜绝常见错误:时间格式、权限校验、表单必填项、动态模块加载等问题在交互层面被提前防护。 + +## 现状与痛点 +- 后端接口已齐备(管理端): + - 创建活动:`POST /api/admin/activities`(`internal/router/router.go:78`;控制器 `internal/api/admin/activities_admin.go:41`;服务层 `internal/service/activity/activity_create.go:12-41`) + - 创建期:`POST /api/admin/activities/:activity_id/issues`(`internal/router/router.go:83`;控制器 `internal/api/admin/issues_admin.go`;服务层 `internal/service/activity/issue_create.go:13-32`) + - 创建奖励:`POST /api/admin/activities/:activity_id/issues/:issue_id/rewards` +- 前端存在两套创建入口: + - 独立向导页:`web/admin/src/views/activity/wizard/index.vue`(“下一步”仅切换步骤,提交由各步的“提交”触发) + - 管理页弹窗向导:`web/admin/src/views/activity/manage/index.vue`(“下一步”实际调用创建并推进) +- 痛点汇总: + - 两处入口的字段命名、排序不一致;奖励有的手填名称,有的选商品 + - 时间未填时易触发数据库零日期错误(`TRADITIONAL SQL` 模式下) + - “下一步”与“提交”含义不统一,用户易误操作 + - 表格行式编辑密度过高,填写多项参数不友好 + - 动态导入失败来源于 SFC 结构不合法、重复脚本块或未声明变量导致编译失败 + +## 重构设计(交互与结构) +### 向导统一(单入口) +- 入口:统一从“管理页弹窗向导”打开;支持跳转到独立页,但独立页沿用同一套逻辑(避免两套逻辑分歧) +- 步骤: + 1. 基本信息(活动):名称、分类、状态、门票价格、Boss、开始/结束时间 + 2. 期信息(活动期):期号、状态、排序 + 3. 奖励配置:按商品选择与参数设置(权重、数量、原始数量、等级、排序、Boss) + 4. 确认提交:汇总校验与最终提交 +- 导航: + - “下一步”即执行当前步的提交(成功后推进),失败则停留并有明确错误提示 + - “上一步”回退不丢数据;每步保存草稿状态在组件内存 + +### 表单布局与输入体验 +- 两列栅格布局(`ElRow/ElCol`):提升可读性与并行填写效率 +- 奖励配置交互: + - 改为“卡片列表 + 弹窗表单”,卡片上显示商品名称及关键参数;“新增奖励/编辑奖励”在弹窗中填写,分两列布局 + - 商品选择:远程搜索 `admin/products`(`web/admin/src/api/product.ts`),选择后自动回填奖励名称(传递给后端以兼容) + - 支持复制卡片、删除卡片、批量导入(CSV/JSON)作为扩展项(初版可不做批量) +- 字段统一:奖励行统一字段为 `product_id、weight、quantity、original_qty、level、sort、is_boss`;`name`由选品自动补齐,不再手填 +- 权限与提示:检测 `SessionUserInfo.IsSuper`(控制器内已做),前端在 403/400 的返回文案时专门弹出“权限不足”提示 + +### 校验与防错 +- 实时校验规则: + - 活动:名称与分类必填;开始/结束时间支持空,但若为空则前端默认以 `ISO8601` 当前时间传入或后端统一允许 `NULL` + - 期:期号必填;排序与状态为数值校验 + - 奖励:`product_id` 必填;数值项均为非负或正数;`level` 合法枚举 +- 时间防错: + - 方案优先级A:数据库将 `start_time/end_time` 改为可 `NULL` 且默认 `NULL`(推荐); + - 方案优先级B:前端在未选择时默认传当前时间的 `toISOString()`; + - 后端保留 `Omit` 逻辑(`internal/service/activity/activity_create.go:29-35`),避免强行写零日期 + +### 代码结构与可维护性 +- 组件结构:每个步骤独立子组件(`ActivityStepBasic.vue`、`ActivityStepIssue.vue`、`ActivityStepRewards.vue`)+ 容器(`ActivityWizard.vue`) +- 状态管理:使用本地状态(`ref/reactive`)即可;如需跨页保持,用 `pinia` 记录向导草稿 +- API层:沿用 `web/admin/src/api/adminActivities.ts`,奖励提交前统一映射自动补齐 `name` +- 动态导入稳定性: + - 确保每个 SFC 仅一个 `