feat: 新增支付测试小程序与微信支付集成
Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 40s

feat(pay): 添加支付API基础结构
feat(miniapp): 创建支付测试小程序页面与配置
feat(wechatpay): 配置微信支付参数与证书
fix(guild): 修复成员列表查询条件
docs: 更新代码规范文档与需求文档
style: 统一前后端枚举显示与注释格式
refactor(admin): 重构用户奖励发放接口参数处理
test(title): 添加称号效果参数验证测试
This commit is contained in:
邹方成 2025-11-17 00:42:08 +08:00
parent 87ad4177b1
commit 6ee627139c
521 changed files with 10459 additions and 2569 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -0,0 +1,50 @@
## 概览
- 后端以 Go 实现,路由集中在 `internal/router/router.go`分5组管理端非认证(`/api/admin`登录)、管理端认证(`/api/admin`全量管理接口)、系统管理(`/api`模板兼容)、APP公开(`/api/app`)、APP认证(`/api/app`)。
- 管理端处理器分布在 `internal/api/admin/*`,覆盖活动/期次/奖励/抽奖/用户资产/称号/道具卡/优惠券/工会/系统用户角色菜单等。
- 前端后台管理位于 `web/admin`Axios 基础 `baseURL=/api`API 模块在 `web/admin/src/api/*`
## 发现与结论
- 路由与前端 URL 基本一致;系统管理模块通过 `baseURL=/api` 访问 `/api/user/list|/api/role/list|/api/v3/system/menus/simple`,与后端 `systemApiRouter` 对齐(`internal/router/router.go:59-167`)。
- 前端存在绝对 URL 前缀问题:`web/admin/src/api/reward-grant.ts:50` 使用 `url: '/api/admin/...'`,与 `baseURL=/api` 叠加为重复前缀,应改为相对 `admin/...`
- 奖励发放接口在前端有重复实现:`reward-grant.ts``player-manage.ts` 同指向 `POST /api/admin/users/{id}/rewards/grant`(后端实现见 `internal/api/admin/users_reward_admin.go:32-75`),建议合并与统一用法。
- 认证链路清晰:管理端拦截器校验 JWT、登录哈希与状态`internal/router/interceptor/admin_auth.go:18-82`APP 端简化(`internal/router/interceptor/app_auth.go:13-35`)。前端 Axios 统一加 `Authorization` 并防抖登出(`web/admin/src/utils/http/index.ts:65-76,84-92`)。
- 分层一致性:多数管理端处理器调用 service 层,但也有直接操作 DAO 的情况(如称号管理 `internal/api/admin/titles_admin.go`),建议将复杂业务(效果校验、用户称号唯一激活切换)沉入 service 保持一致性与复用。
- 性能与合理性:
- 工作台积分统计扫表累加(`internal/api/admin/dashboard_admin.go:74-84`)可改为 SQL 聚合并按有效期过滤;积分净变动已用流水 SUM`87-107`),建议两者一致走 SQL。
- 新用户概览多表聚合(`224-381`)逻辑合理但查询密集,后续可评估增加索引与批量 UNION/子查询降往返。
- API 路由一致性:系统管理接口挂在 `/api` 与管理端其余接口挂在 `/api/admin`,存在认知负担;短期保留兼容,补齐文档;中期评估统一路径策略。
## 整改实施计划
### 阶段1快速修复
- 修复前端绝对 URL 前缀问题:将 `web/admin/src/api/reward-grant.ts:50``url: '/api/admin/users/${userId}/rewards/grant'` 改为 `url: 'admin/users/${userId}/rewards/grant'`,与其余模块一致。
- 合并奖励发放 API删除/重定向 `reward-grant.ts` 的重复实现,统一在 `player-manage.ts` 暴露 `fetchGrantPlayerReward`,或反之,在 `reward-grant.ts` 统一导出并在 `player-manage.ts` 复用。
### 阶段2分层与可维护性
- 抽取称号管理核心逻辑到 service
- 将 `AssignUserTitle` 的 upsert 与“仅保留一个激活称号”的切换(`internal/api/admin/titles_admin.go:439-490`)抽到 `internal/service/title`,供管理端与未来 APP 端统一调用。
- 将效果参数校验 `validateEffectParams``titles_admin.go:296-377`)迁移为可复用的校验器,便于单元测试与前后端一致性。
- 优化工作台统计:
- 积分总额改为 SQL `SUM(points)` 并过滤过期(替换 `dashboard_admin.go:74-84` 的内存累加),减少 I/O 与CPU。
### 阶段3一致性与文档
- 路由规范化策略评审:维持 `/api`(系统管理)与 `/api/admin` 并存,新增开发文档条目明确两者用途与历史原因;中期根据前端路由与菜单加载需求评估是否迁移到 `/api/admin/system/*`
- 更新 Swagger 文档:核对 `docs/swagger.yaml`/`docs/swagger.json` 与现状路由一致,补充工作台接口、称号/效果接口、批量发放接口说明。
### 阶段4测试与验证
- 前端:为 `Axios` 层与关键 API登录、奖励发放、称号分配增加最小可运行的集成测试或契约测试校验 401 防抖与登出行为。
- 后端:为称号 service 新增单元测试效果参数校验、scope 命中、唯一激活切换),为工作台统计新增 SQL 聚合测试。
## 参考定位
- 路由总表:`internal/router/router.go:50-53,56-167,169-188,190-210,213-232`
- 绝对 URL 问题:`web/admin/src/api/reward-grant.ts:50`
- Axios baseURL`.env.development:7``web/admin/src/utils/http/index.ts:45-48`
- 奖励发放后端:`internal/api/admin/users_reward_admin.go:32-75`
- 工作台积分统计:`internal/api/admin/dashboard_admin.go:74-84,87-107`
- 称号分配逻辑:`internal/api/admin/titles_admin.go:439-490`
- 认证拦截器:`internal/router/interceptor/admin_auth.go:18-82``internal/router/interceptor/app_auth.go:13-35`
## 验收标准
- 前端所有 API 使用相对路径,无 `/api/` 重复前缀;奖励发放仅一处实现且通过。
- 工作台统计在数据量上升时仍稳定SQL 聚合后端性能可观)。
- 称号管理逻辑具备可测试的 service 层,接口行为与前端预期一致。
- Swagger 文档与实际路由对齐,前后端调用一目了然。

View File

@ -0,0 +1,25 @@
## 是否变更APP接口
- 路由与参数:不变。现有 APP 端接口保持原路径与入参。
- `POST /api/app/guilds/:guild_id/members`加入internal/api/guild/members_app.go:19-50
- `DELETE /api/app/guilds/:guild_id/members/:user_id`离开internal/api/guild/members_app.go:52-83
- `GET /api/app/guilds/:guild_id/members`成员列表internal/api/guild/members_app.go:102-141
- 响应结构:不变(仍返回 `{ message }`)。
- 行为变化:当工会 `join_mode=1`加入后处于待审状态不会立即出现在成员列表服务层变更internal/service/guild/join.go:13-48成员列表过滤正式成员internal/service/guild/members_list.go:9-29
## 客户端适配建议(保持接口不变)
- 加入后状态提示:将文案改为“申请已提交,待审核”,并在个人/工会页显示“待审核”状态。
- 功能限制:待审期间禁用仅成员可用的操作(如成员互动、贡献等)。
- 成员列表:无需变更,列表只显示已通过成员。
## 可选后端增强(增强体验,仍向后兼容)
1) 加入响应增强:在 `POST /api/app/guilds/:guild_id/members` 返回体中新增 `join_status`0待审/2已通过便于客户端无额外查询即可知状态。
2) 成员自查接口:新增 `GET /api/app/guilds/:guild_id/members/me` 返回当前用户在该工会的 `role/status/join_status`
3) 申请查询接口:新增 `GET /api/app/guilds/:guild_id/applications?user_id=xxx` 用于查询自己的待审记录(只读)。
- 以上均为增量字段与新端点,旧客户端不受影响。
## 验收
- 加入审核工会时APP 加入成功但显示“待审核”,成员列表不包含该用户;审核通过后自动展示。
- 保持现有路由与参数不变,无需强制更新客户端。
- 若启用增强:客户端通过 `join_status` 或新查询端点准确展示状态。
如确认,我将按照“保持接口不变 + 提供可选增强”的方式推进服务与可选APP接口扩展并提供前端适配示例。

View File

@ -0,0 +1,40 @@
## 范围
- API 层:补齐接口文档注释、统一 req/res 模式、限制为路由与参数控制
- Service 层:迁移业务与数据访问、补齐函数级注释、对齐 activity_* 分层命名
## 工作项
1. API 注释补全Swagger
- 为缺少注释的端点补充 `@Summary/@Description/@Tags/@Param/@Success/@Failure/@Router`
- `internal/api/activity/draw_app.go:16 ExecuteDraw`
- `internal/api/admin/draw_simulate.go:36 SimulateIssueDraw`
- `internal/api/admin/issue_random_commit.go:21/48/80 Commit/Get/History`
- `internal/api/admin/draw_receipt.go:31/77 GetDrawReceipt/GetDrawReceiptByLogID`
- `internal/api/admin/verify_draw.go:34 VerifyDrawReceipt`
2. API 限定职责
- 将 API 中直接访问数据库的逻辑迁移到 Service 层:
- `internal/api/admin/draw_receipt.go` 查询收据改为调用 `activity.Service.GetDrawReceipt/GetDrawReceiptByLogID`
- 保持 API 仅做:参数绑定、鉴权、调用 service、返回 res
3. 统一 req/res 代码风格
- 统一使用 `req := new(XxxRequest)` / `res := new(XxxResponse)`,并在所有端点输出 `ctx.Payload(res)`
- 检查并修正个别端点的 `var req` 临时写法
4. Service 注释与接口规范
- 为 `internal/service/activity/activity.go``Service` 接口所有方法添加函数级注释(功能、参数、返回值)
- 为 `internal/service/activity/*.go`(如 `draw_execute.go` 等)关键导出函数添加注释
- 保持 `internal/service/user/*.go` 已有中文注释风格的一致性
5. 分层与命名对齐
- 保持并补齐 `internal/api/activity/*``internal/service/activity/*``activity_*` 命名映射CRUD、issue、rewards、draw、logs
- 审核并调整少量不一致的文件/函数命名(如 helper 置于非 API 包或私有化)
## 验证
- 运行 Swagger 生成与本地文档预览,检查所有路由均有完整注释
- 运行单元测试/集成冒烟:抽奖、承诺、收据查询、奖励发放
- 代码静态检查:确保 API 无直接 DAO 访问、Service 注释完整
## 交付物
- 已修正的 API 与 Service 源码
- 通过的 Swagger 文档与接口列表
- 简要测试记录(通过用例与关键接口返回示例)

View File

@ -0,0 +1,40 @@
## 问题定位
- 奖品页 `web/admin/src/views/activity/rewards/index.vue` 在工具栏下方无条件渲染了批量抽奖对话框:`<BatchDrawDialog :activity="currentActivity" />`
- 该子组件内部以 `props.activity !== null` 控制 `v-model`,而当前页的 `currentActivity` 始终返回对象(非 null导致对话框初始即打开表现为点击“新增奖励”时出现“批量抽奖”。
## 修复方案
- 直接移除奖品页中的 `BatchDrawDialog` 渲染(该页仅做奖励 CRUD模拟批量抽奖逻辑属于活动管理页/模拟页)。
- 若保留(备选):使用受控显示状态,改为 `<BatchDrawDialog v-model="showBatchDrawDialog" :activity="showBatchDrawDialog ? currentActivity : null" />`,并提供单独按钮触发。默认 `showBatchDrawDialog=false`
- 采用“移除”方案,影响文件:`web/admin/src/views/activity/rewards/index.vue`(删除组件引用与相关变量)。
## 奖品列表优化
- 新增展示:
- “剩余/总量”列:`quantity / original_qty`
- “期望概率”列:基于总权重 `sumWeight` 本地计算 `weight/sumWeight*100`,保留两位小数。
- Boss 标签保持但列名更明确为“Boss”。
- 汇总信息:表头上方显示“奖品总数 / 总权重”。
- 交互优化:
- 编辑校验完善(名称、商品、权重、数量、原始数量、等级为必填/大于零)。
- 删除增加二次确认弹窗。
- 过滤与排序(前端本地):
- 过滤项名称模糊、等级S/A/B/C、是否 Boss。
- 默认排序按等级S→C+ `sort`
- 数据加载优化:商品列表一次加载并缓存,避免重复请求。
## 具体改动
- 修改文件:`web/admin/src/views/activity/rewards/index.vue`
- 删除 `<BatchDrawDialog :activity="currentActivity" />` 与相关 `currentActivity` 计算属性。
- 在 `columns` 增加 `剩余/总量``期望概率` 两列(期望概率通过本地计算得到)。
- 在顶部新增汇总区域(使用 `computed` 基于 `data` 计算)。
- 增加简单的本地过滤控件(`ElInput`/`ElSelect`),对展示数据做 `computed` 过滤与排序。
- 删除与批量抽奖相关的无用状态(如 `showSimDialog`)。
- 删除按钮文案更明确:`批量新增奖励``新增奖励`
- 不改后端接口;所有优化为前端表现层。
## 验收
- 进入“奖励”页,点击“新增奖励”只弹奖励编辑对话框,不再出现批量抽奖对话框。
- 列表显示“剩余/总量”、“期望概率”,汇总信息正确;过滤与排序生效。
- 构建通过,交互校验正常,删除有确认提示。
## 风险与回滚
- 风险低,仅前端视图改动;如需回滚,保留原组件引用并恢复初始布局即可。

View File

@ -0,0 +1,38 @@
## 问题定位
- 现象POST `/api/pay/wechat/notify` 返回 400日志提示“certificate [PUB_KEY_ID_0116104396352025041000211519001600] not found in verifier”。
- 原因:微信侧请求头 `Wechatpay-Serial``PUB_KEY_ID_*` 格式,说明该商户已启用“微信支付平台公钥”模式;当前代码仅使用“平台证书”验签(`verifiers.NewSHA256WithRSAVerifier` + `downloader`),导致找不到匹配的证书序列号而验签失败。
- 代码位置:
- 路由注册:`internal/router/router.go:252`
- 回调处理:`internal/api/pay/wechat_notify.go:40-46`(注册证书下载器+证书验签器)
- 客户端初始化:`internal/pkg/pay/client.go:32-39``WithWechatPayAutoAuthCipher` 使用平台证书模式)
## 修复方案
- 增加“平台公钥”模式配置并切换验签/签名实现:
- 在配置新增 `wechatpay.public_key_id``wechatpay.public_key_path`(商户后台下载的 `pub_key.pem` 与对应 `PUB_KEY_ID_*`)。
- 回调验签:当检测到已配置公钥,改用 `verifiers.NewSHA256WithRSAPubkeyVerifier(publicKeyID, publicKey)` 构造 `notify.Handler`;否则保持现有证书模式。
- 请求侧签名与自动加密:当检测到已配置公钥,改用 `option.WithWechatPayPublicKeyAuthCipher(mchid, serialNo, privateKey, publicKeyID, publicKey)` 初始化 `core.Client`;否则保持 `WithWechatPayAutoAuthCipher`
- 初始化时机优化:
- 将证书下载器注册/公钥加载前置至应用启动阶段,避免首次回调时的“尚未拉取证书”竞态问题。
- 兼容策略:
- 双模式自动选择:优先检测公钥配置;缺省走证书模式,确保对旧商户不破坏。
## 实施步骤
- 配置:
- 在 `configs/*.toml``[wechatpay]` 增加 `public_key_id``public_key_path` 键;同时支持环境变量覆盖(如 `WECHAT_PUBLIC_KEY_ID``WECHAT_PUBLIC_KEY_PATH`)。
- 代码改动:
- `internal/api/pay/wechat_notify.go`:根据配置分支选择 `verifiers.NewSHA256WithRSAPubkeyVerifier` 或现有证书验签器;加载公钥使用 `utils.LoadPublicKeyWithPath`
- `internal/pkg/pay/client.go`:根据配置分支选择 `option.WithWechatPayPublicKeyAuthCipher` 或现有证书选项。
- 启动流程:在应用初始化位置集中完成下载器注册/公钥加载与复用,不在每次回调时重复注册。
## 验证步骤
- 本地联调:
- 使用真实微信回调数据(包含 `Wechatpay-Serial: PUB_KEY_ID_*`)验证回调成功返回 `{"code":"SUCCESS","message":"OK"}`
- 下单→支付→回调链路验证:检查订单状态从 1→2`paidAt` 按回调 `success_time` 生效(`internal/api/pay/wechat_notify.go:67-71`)。
- 日志与安全:
- 打印初始化模式(证书/公钥)与关键配置是否完整,避免静默失败。
- 不输出密钥/私钥内容,符合安全规范。
## 额外检查关于“req 没有写”)
- 当前回调处理直接使用 `ctx.Request()` 原始请求体(`internal/api/pay/wechat_notify.go:48`),未提前读取/篡改;若上游中间件读取了 `Body`,需保证使用 `io.NopCloser` 复位请求体后再交给 `notify.Handler`。本次改造同时确认路由链路未破坏请求体传递。
请确认是否按照“平台公钥”接入(已具备 `pub_key.pem``PUB_KEY_ID_*`)。确认后我将按上述方案实现代码与配置改动,并完成联调与验证。

View File

@ -0,0 +1,109 @@
## 模型目标
- 为运营提供“发货统计”独立表,脱离业务在线查询复杂度,支持导出与审计。
- 字段覆盖:产品、价格、发货数量、用户收件信息、物流、订单聚合、盈亏、来源、垫付人、时间。
## 表结构DDL
```sql
CREATE TABLE IF NOT EXISTS ops_shipping_stats (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
shipped_at DATETIME NOT NULL COMMENT '发货时间',
product_id BIGINT NULL,
product_name VARCHAR(255) NOT NULL,
product_price_cents BIGINT NOT NULL COMMENT '单位:分',
shipped_qty BIGINT NOT NULL,
user_id BIGINT NOT NULL,
user_name VARCHAR(100) NOT NULL,
user_address_text VARCHAR(512) NOT NULL,
express_code VARCHAR(64) NULL,
express_no VARCHAR(128) NULL,
order_id BIGINT NULL,
order_no VARCHAR(64) NULL,
order_qty BIGINT NULL,
order_amount_cents BIGINT NULL COMMENT '单位:分',
profit_loss_cents BIGINT NULL COMMENT '单位:分',
order_source_type INT NULL,
order_source_text VARCHAR(32) NULL,
payer VARCHAR(128) NULL COMMENT '垫付人后续录入或从remark规范解析',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_shipped_at(shipped_at),
KEY idx_product(product_id),
KEY idx_order(order_id),
KEY idx_express(express_no)
) COMMENT='运营发货统计';
```
## 明细数据入库INSERT…SELECT
```sql
/* 参数:@start_date, @end_date */
INSERT INTO ops_shipping_stats (
shipped_at, product_id, product_name, product_price_cents, shipped_qty,
user_id, user_name, user_address_text, express_code, express_no,
order_id, order_no, order_qty, order_amount_cents, profit_loss_cents,
order_source_type, order_source_text, payer
)
SELECT
sr.shipped_at,
COALESCE(sr.product_id, oi.product_id) AS product_id,
COALESCE(oi.title, p.name) AS product_name,
COALESCE(sr.price, oi.price, p.price) AS product_price_cents,
sr.quantity AS shipped_qty,
ua.user_id AS user_id,
ua.name AS user_name,
CONCAT(ua.province, ua.city, ua.district, ua.address) AS user_address_text,
sr.express_code,
sr.express_no,
o.id AS order_id,
o.order_no AS order_no,
os.total_qty AS order_qty,
o.actual_amount AS order_amount_cents,
/* 盈亏:行价格×发货数量 订单实付金额(可根据口径调整) */
COALESCE(sr.price, oi.price, p.price) * sr.quantity - o.actual_amount AS profit_loss_cents,
o.source_type AS order_source_type,
CASE o.source_type WHEN 1 THEN '商城直购' WHEN 2 THEN '抽奖票据' WHEN 3 THEN '其他' ELSE CONCAT('未知-', o.source_type) END AS order_source_text,
/* 垫付人:暂为空,后续从 remark 或新增字段填充 */
NULL AS payer
FROM shipping_records sr
LEFT JOIN order_items oi ON oi.id = sr.order_item_id
LEFT JOIN products p ON p.id = COALESCE(sr.product_id, oi.product_id)
LEFT JOIN orders o ON o.id = sr.order_id
LEFT JOIN (
SELECT order_id, SUM(quantity) AS total_qty
FROM order_items
GROUP BY order_id
) os ON os.order_id = o.id
LEFT JOIN user_addresses ua ON ua.id = COALESCE(sr.address_id, o.user_address_id)
WHERE sr.status IN (2,3) /* 已发货/已签收 */
AND sr.shipped_at BETWEEN @start_date AND @end_date;
```
## 查询(导出)
```sql
/* 查询明细(元) */
SELECT
product_name,
ROUND(product_price_cents/100, 2) AS product_price_yuan,
shipped_qty,
user_name,
user_address_text,
express_code,
express_no,
order_no,
order_qty,
ROUND(order_amount_cents/100, 2) AS order_amount_yuan,
ROUND(profit_loss_cents/100, 2) AS profit_loss_yuan,
order_source_text,
payer,
shipped_at
FROM ops_shipping_stats
WHERE shipped_at BETWEEN @start_date AND @end_date
ORDER BY shipped_at DESC;
```
## 口径与可调整项
- 盈亏口径:当前为“行价格×发货数量 订单实付金额”;如需改为“订单行应付总额(`oi.total_amount` 订单实付金额”,可替换。
- 下单数量:当前为订单维度总件数(聚合);如需改为行数量,改用 `oi.quantity`
- 垫付人:建议在 `shipping_records` 增加 `payer` 字段或在 `remark` 规范写入 `payer:xxx`,入库时解析填充。
## 后续实现建议
- 管理后台增加导出按钮,直接查询 `ops_shipping_stats` 并输出 CSV/Excel。
- 每日定时任务或发货事件触发增量写入,保证统计表实时/准实时更新。

View File

@ -0,0 +1,78 @@
**问题理解**
* 创建/编辑对话框单列过长,影响填写与可读性。
* 产品、用户应支持选择(远程搜索)而非手填。
* 订单来源限定为:淘宝/拼多多/京东/线下;来源类型与文本重复,应仅保留类型并在后端映射文本。
**前端改造**
* 表单布局(左右分布):
* 在 `web/admin/src/views/operations/shipping-stats/index.vue` 的编辑对话框使用 `el-row/el-col` 两列布局,将字段按分组排布:
* 左列:发货时间、产品选择、单价、发货数量、订单来源类型(下拉)。
* 右列:用户选择、收件人、地址、快递公司、运单号、订单号。
* 必填项:`shipped_at``product_id``user_id``shipped_qty`
* 产品选择:
* 远程搜索下拉 `el-select filterable remote` 调用 `GET 'admin/products'`(已有路由:`internal/router/router.go:120`)。
* 选中后自动填充:`product_id``product_name``product_price_cents`
* 用户选择:
* 远程搜索下拉调用 `GET 'admin/users'`(已有路由:`internal/router/router.go:129`)。
* 选中后自动填充:`user_id``user_name``user_address_text`仍支持手动输入。
* 订单来源:
* 将 `order_source_type` 改为下拉选择:`{1: 淘宝, 2: 拼多多, 3: 京东, 4: 线下}`
* 移除前端表单中的 `order_source_text` 字段,仅展示文本列(由后端返回)。
* API 类型调整:
* 在 `web/admin/src/api/shipping-stats.ts``CreateShippingStatRequest/UpdateShippingStatRequest` 中删除 `order_source_text` 字段。
* 列表表格仅显示 `order_source_text`;不显示 `order_source_type` 列(现有实现只显示文本,无需调整)。
**后端改造**
* 文段位置:`internal/api/admin/shipping_stats_admin.go`
* 文本映射策略:
* 在创建与更新接口中,根据 `OrderSourceType` 自动映射 `OrderSourceText`
* `1→"淘宝"``2→"拼多多"``3→"京东"``4→"线下"`,其他值映射为`"未知"`
* 移除对 `order_source_text` 的前端输入依赖:
* `CreateShippingStat()` 忽略请求体中的 `order_source_text`,统一用映射结果赋值。
* `ModifyShippingStat()``OrderSourceType` 变更则同步更新映射文本。
* 其余字段逻辑不变;保留列表筛选条件与分页排序。
**交互与体验**
* 远程搜索均支持关键字匹配与节流,选项标签为 `名称 (ID)`,用户便于确认。
* 表单左右分布减少滚动,分组更清晰;金额与运单等非必填项可留空。
**验收标准**
* 新建/编辑对话框为两列布局,核心字段分组合理,必填校验生效。
* 产品与用户支持远程搜索选择,选中后自动填充相关字段。
* 订单来源仅以下拉类型选择,后端返回正确文本,前端只展示文本;不再出现“来源文本/来源类型重复”问题。
* 列表/详情/增改删功能在管理端登录后完整可用。

View File

@ -0,0 +1,65 @@
**后端 API 设计**
- 路径前缀:`/api/admin/ops_shipping_stats`(管理端鉴权)。参考路由注册与鉴权模式:`internal/router/router.go` 中 Admin 组;处理方式参照 `internal/api/admin/titles_admin.go:28/81/105/135`
- 接口列表:
- `GET /api/admin/ops_shipping_stats` 列表查询(分页/筛选/排序)。
- `GET /api/admin/ops_shipping_stats/:id` 单条详情。
- `POST /api/admin/ops_shipping_stats` 新增。
- `PUT /api/admin/ops_shipping_stats/:id` 更新。
- `DELETE /api/admin/ops_shipping_stats/:id` 删除。
- 入参约定:
- 列表查询Query`page``page_size``shipped_start``shipped_end``product_id``product_name``user_id``user_name``express_code``express_no``order_id``order_no``order_source_type``payer`(字符串枚举),`keyword`(模糊匹配 `product_name/user_name/order_no/express_no`)。
- 新增/更新JSON`shipped_at`RFC3339`product_id``product_name``product_price_cents``shipped_qty``user_id``user_name``user_address_text``express_code``express_no``order_id``order_no``order_qty``order_amount_cents``profit_loss_cents``order_source_type``order_source_text``payer`
- 响应约定:
- 列表:`{ page, page_size, total, list: OpsShippingStats[] }`,默认排序 `shipped_at DESC, id DESC`
- 详情:`OpsShippingStats` 完整记录。
- 新增/更新/删除:返回受影响记录或 `{ id }`
- 校验与错误:
- 参数绑定:`ShouldBindForm`/`ShouldBindJSON`,错误码用 `internal/code``ParamBindError``DatabaseError` 等。
- 鉴权:`core.WrapAuthHandler(intc.AdminTokenAuthVerify)`
- DAO 使用:
- 读:`h.readDB.OpsShippingStats.WithContext(ctx).ReadDB().Where(...).FindByPage(...)`
- 写:`h.writeDB.OpsShippingStats.WithContext(ctx).Create/Save/Delete`
- 参考模型/DAO`internal/repository/mysql/model/ops_shipping_stats.gen.go``internal/repository/mysql/dao/ops_shipping_stats.gen.go`;全局接入点:`internal/repository/mysql/dao/gen.go`
- 过滤实现:
- 时间范围:`Where(dao.OpsShippingStats.ShippedAt.Gte(shipped_start))``Lte(shipped_end)`
- 模糊匹配:`Like` 多字段 `Or` 组合精确匹配ID 字段 `Eq`
- 分页:`FindByPage(offset, limit)` 返回 `(list, total)`
- 索引建议(后续 DBA 执行):
- 组合索引:`(shipped_at DESC, product_id)``(user_id)``(order_no)``(express_code, express_no)`,提高查询性能。
**前端对接与页面**
- 菜单与路由:
- 本地模式:在 `web/admin/src/router/modules/operations.ts``children` 增加:`{ path: 'shipping-stats', name: 'ShippingStats', component: '/operations/shipping-stats', meta: { title: '发货统计', roles: ['R_SUPER','R_ADMIN'] } }`
- 接口模式(可选):通过系统菜单表 `menus` 新增对应子菜单;后端 `GET /api/v3/system/menus/simple` 返回;参考 `internal/api/admin/system_menu.go`
- API 客户端:`web/admin/src/api/shipping-stats.ts`
- `getList(params)``GET 'admin/ops_shipping_stats'`
- `getDetail(id)``GET 'admin/ops_shipping_stats/:id'`
- `create(data)``POST 'admin/ops_shipping_stats'`
- `update(id, data)``PUT 'admin/ops_shipping_stats/:id'`
- `remove(id)``DELETE 'admin/ops_shipping_stats/:id'`
- 复用 `src/utils/http/index.ts` 封装(自动附带 `Authorization`、统一错误处理)。
- 页面视图:`web/admin/src/views/operations/shipping-stats/index.vue`
- 布局:顶部搜索(`ArtSearchBar`),表头工具(`ArtTableHeader`),数据表(`ArtTable`),分页。
- 搜索项时间范围、商品ID/名称、用户ID/名称)、快递公司/单号、订单号、来源类型、付款人、关键字。
- 列定义:`shipped_at``product_name``product_price_cents`(格式化为金额)、`shipped_qty``user_name``user_address_text`(截断+tooltip`express_code``express_no``order_no``order_qty``order_amount_cents`(金额)、`profit_loss_cents`(金额/颜色提示)、`order_source_text``payer`、操作列(查看/编辑/删除)。
- 对话框:新建/编辑(表单项与后端 JSON 契约一致)。
- 交互:列表加载用 `useTable`;增改删后按“软刷新/全量刷新”策略更新列表;统一消息提示。
- 权限:`meta.roles` 控制访问;按钮权限按需要可用 `authList`
**实施步骤**
- 后端:
- 在 `internal/api/admin` 新增 `shipping_stats_admin.go`,实现 5 个 Handler仿照 `titles_admin.go` 的风格(入参绑定/读写分离/错误与响应)。
- 在 `internal/router/router.go` 的 Admin 鉴权组注册 5 条路由。
- 前端:
- 添加菜单路由项(本地或接口模式)。
- 新建 `src/api/shipping-stats.ts`,封装 5 个方法。
- 新建 `src/views/operations/shipping-stats/index.vue` 页面:搜索区、表格列、对话框、分页与刷新逻辑。
**验收标准**
- 管理端已登录用户可访问“运营管理/发货统计”。
- 列表支持分页、筛选、排序(默认按发货时间倒序)。
- 能创建/编辑/删除记录并实时刷新;可查看详情。
- 后端输入校验与统一错误码生效;鉴权与日志记录正常。
- 查询性能在典型数据量下良好(建议索引已列出)。
请确认以上方案,我将按该设计进行代码实现与联调。

View File

@ -0,0 +1,65 @@
# 目标
- 为管理后台补齐“订单管理”全套能力:全局订单列表/详情、筛选与搜索、状态流转(取消/发货/签收/履约)、退款、导出与对账入口;与现有用户维度订单查询保持一致风格。
# 现状与缺口
- 已有:按用户查看订单、运营漏斗统计;无全局订单页与发货、退款、对账的管理操作。
- 模型完备:`orders/order_items/shipping_records/user_points_ledger`,但缺少支付域表与真实退款流程;`CreateRefund`占位未挂路由。
# 后端接口设计
- 订单列表(全局):`GET /api/admin/pay/orders`
- 筛选:`status(1/2/3/4)``source_type(1/2/3)``pay_time_range``created_range``user_id/order_no``is_consumed`
- 排序:`created_at/paid_at` 倒序
- 返回:分页、订单聚合(含金额、状态、支付时间、用户信息摘要)
- 订单详情:`GET /api/admin/pay/orders/:order_no`
- 返回:订单主信息 + `order_items` + `shipping_records` + 记账流水摘要
- 订单备注更新:`PUT /api/admin/pay/orders/:order_no/remark`
- 订单取消:`POST /api/admin/pay/orders/:order_no/cancel`
- 行为:若`status=1待支付`→置`status=3已取消`/写`cancelled_at`,幂等
- 发货单列表:`GET /api/admin/pay/shipments`
- 筛选:`status(1待发/2已发/3已签收/4异常)``order_no/product_id/express_no`、时间范围
- 创建发货:`POST /api/admin/pay/shipments`
- 入参:`order_no/order_item_id/address_id/quantity/express_code(optional)`;行为:生成`shipping_records`,将`status=1待发`
- 标记已发货:`PUT /api/admin/pay/shipments/:id/ship`
- 入参:`express_code/express_no`;行为:置`status=2已发/shipped_at`
- 标记已签收:`PUT /api/admin/pay/shipments/:id/receive`
- 行为:置`status=3已签收/received_at`
- 标记异常:`PUT /api/admin/pay/shipments/:id/abnormal`
- 履约完成(虚拟):`PUT /api/admin/pay/orders/:order_no/consume`
- 行为:`is_consumed=1`,用于虚拟资产核销;幂等
- 退款(对接支付域):
- 创建退款:`POST /api/admin/pay/refunds`保留当前占位实现后续替换为真实支付退款API
- 查询退款:`GET /api/admin/pay/refunds``GET /api/admin/pay/refunds/:refund_no`
- 导出:`GET /api/admin/pay/orders/export`按筛选条件导出CSV/XLSX
- 对账入口:`POST /api/admin/pay/bills/import`(后续结合支付域实现)
# 服务层与数据层
- 新增 `admin` 服务模块:封装订单聚合查询、详情组装、状态流转与发货操作;使用 Gorm 事务与条件更新保证幂等。
- DAO 复用:`orders/order_items/shipping_records/user_points_ledger`;索引建议:`orders.order_no`唯一、`shipping_records.express_no`索引。
# 前端管理页
- 新增“订单管理”菜单及页面:
- 订单列表页:筛选(状态、来源、时间、用户、订单号、履约)、表格列(订单号、用户、金额、状态、支付时间、来源、履约、备注)、操作(查看、取消、退款、履约、导出)
- 订单详情页:订单主信息、订单项、发货记录(创建/发货/签收/异常)、记账流水与日志
- 发货管理页:发货单列表,支持搜索与状态更新
- API 封装:在 `web/admin/src/api/` 新增 `pay-orders.ts``shipments.ts`,匹配后端接口
# 业务与校验
- 金额与状态校验:取消仅允许`status=1`;履约仅允许`status=2`;发货需校验行项目数量与地址。
- 幂等:状态流转采用条件更新;重复请求直接返回当前状态。
- 审计:请求日志已入库(`dblogger`),增加关键操作的业务日志字段。
# 验收标准
- 管理端可全局查看与筛选订单,支持详情、取消、履约、发货、签收、异常标记;
- 退款创建与查询可用(占位版本),后续升级为真实退款;
- 导出按筛选条件生成数据;
- 运营漏斗与新页面数据口径一致;编译与基础联调通过。
# 实施步骤
1. 后端:新增订单与发货相关接口与服务;将退款占位路由挂载;完善查询与分页;幂等与事务。
2. 前端新增API文件与页面组件列表/详情/发货接入菜单与路由UI按现有风格。
3. 校验:编译与接口联调、权限校验、筛选与导出验证;漏斗口径确认。
4. 留待增强:接入真实支付退款与对账流程;增加订单号唯一索引与支付幂等表。
# 风险与处理
- 真实退款未接入:先保留管理操作占位,避免资金走错;上线前需联通支付域。
- 并发与幂等:条件更新控制状态流;失败重试与告警结合现有日志。

View File

@ -0,0 +1,41 @@
**问题理解**
- 需要在发货统计中体现“采购价格”,用于与销售价格/订单金额做对比与分析。
- 当前 `ops_shipping_stats` 仅存储销售单价 `product_price_cents`,无采购单价。
**数据库变更**
- 在 `ops_shipping_stats` 表新增字段:
- `purchase_price_cents BIGINT NOT NULL DEFAULT 0 COMMENT '采购单价(分)'`
- SQLMySQL
- `ALTER TABLE ops_shipping_stats ADD COLUMN purchase_price_cents BIGINT NOT NULL DEFAULT 0 COMMENT '采购单价(分)';`
- 生成代码:执行 gormgen 以更新 Model/DAO`cmd/gormgen/main.go`,保持原有 DSN 与 `-tables` 参数)。
**后端改造**
- 控制器 `internal/api/admin/shipping_stats_admin.go`
- 创建请求体 `CreateShippingStatRequest` 增加 `purchase_price_cents`(可选)。
- 更新请求体 `UpdateShippingStatRequest` 增加 `purchase_price_cents`(可选)。
- 保存/更新时写入 `purchase_price_cents`
- 盈亏策略:保持现有“盈亏=销售单价×发货数量 订单实付金额”;
- 额外提供两个对比维度(不入库,仅响应扩展字段,便于前端展示):
- `purchase_total_cents = purchase_price_cents * shipped_qty`
- `gross_profit_cents = (product_price_cents - purchase_price_cents) * shipped_qty`
- 若你希望持久化以上值,也可扩展表结构,但默认前端计算即可。
- 列表与详情响应:保持原结构,新增返回 `purchase_price_cents`(其余对比指标前端计算)。
**前端改造**
- 创建/编辑表单(已两列布局):
- 左列补充“采购单价(分)”输入框默认可为空为空按0处理
- 列表新增列:
- 采购单价(分→元格式化)
- 采购总额(分→元,前端用 `purchase_price_cents * shipped_qty` 计算)
- 毛利(分→元,前端用 `(product_price_cents - purchase_price_cents) * shipped_qty` 计算)
- 保持“盈亏”列为后端返回值显示(颜色提示不变)。
**验收标准**
- 新建/编辑支持录入采购单价,并正确写入与返回。
- 列表能展示采购单价、采购总额和销售对比(毛利),并与现有盈亏并行显示。
- 不影响既有筛选、分页与来源类型逻辑。
**后续可选**
- 若采购价格数据来源稳定(如采购单据),可支持远程选择或自动回填;当前先手动录入以快速上线。
请确认以上方案,通过后我将进行数据库字段添加、代码生成与前后端联调实现。

View File

@ -0,0 +1,13 @@
## 目标
- 将奖品管理页Rewards操作栏的“编辑/删除”文本按钮替换为统一的图标标签按钮ArtButtonTable与活动管理、期数管理保持一致风格。
## 实施点
- 修改文件:`web/admin/src/views/activity/rewards/index.vue`
- 变更内容:
- 引入 `ArtButtonTable` 组件
- 将 `actions` 插槽中的 `ElButton` 替换为 `ArtButtonTable type="edit"``ArtButtonTable type="delete"`
- 不改动后端接口与其它逻辑(保留删除确认与编辑弹窗)。
## 验收
- 奖品管理列表操作栏显示统一的图标标签按钮,交互正常(编辑弹窗、删除确认)。
- 前端构建通过。

View File

@ -0,0 +1,46 @@
## 剩余完善项
- 积分恢复比例与幂等:部分/全额退款的积分恢复算法与重复保护
- 通知事件持久化与去重payment_notify_events 入库、processed 标记、重复回调忽略
- 退款列表与详情接口:基于 payment_refunds 的查询/分页/导出
- 对账入口与差异列表:账单导入、生成 payment_bill_diff、差异查询与导出
- 管理端详情字段完善:展示 transaction_id/refund_no/channel/支付方式、累计已退款与可退余额
- 前端金额展示统一为元(两位小数),保留分用于接口与导出
- 唯一索引与安全校验:核查并补齐关键唯一约束与条件更新
## 后端改造
1) 积分恢复
- 算法:恢复积分 = (订单积分抵扣分 × 退款分 ÷ 实付分) ÷ 100累计不超过订单抵扣积分
- 幂等:按订单维度累计已恢复积分对比目标;全额退款一次性恢复剩余积分
2) 通知事件
- 入库 payment_notify_events(notify_id,event_type,raw,processed=false);处理成功后置 processed=true重复 notify_id 直接 ACK
3) 退款查询
- GET /api/admin/pay/refunds分页筛选order_no/status/date/range
- GET /api/admin/pay/refunds/:refund_no详情refund_no/amount/status/success_time/reason/raw
4) 对账能力
- POST /api/admin/pay/bills/import上传/拉取账单入库 payment_bills
- GET /api/admin/pay/bills/diffs查询差异 payment_bill_diff缺失/金额不符/多余)
5) 订单详情聚合
- 交易:最近一笔 payment_transactionstransaction_id/success_time/amount
- 退款payment_refunds 汇总refund_no/amount/status/success_time/reason计算累计已退与可退余额
## 前端改造
- 金额格式化工具:统一元展示(两位小数),表格列与详情区全部使用
- 详情页新增字段transaction_id/refund_no/channel/支付方式;累计已退/可退余额(元)
- 退款记录表:元展示;状态中文映射
- 对账入口页面:账单导入按钮、差异列表表格、导出
## 幂等与安全
- 幂等键order_no/out_trade_no/notify_id/refund_no/transaction_id
- 唯一索引:核查 payment_* 与 orders.order_no接口层条件更新与金额校验
- 日志与告警:验签失败、金额不一致、退款异常、对账差异
## 测试与验收
- 单元:分↔积分换算、比例恢复、幂等重复、接口参数校验
- 集成:预下单→通知→主动退款→详情一致;对账导入与差异生成
- 前后端构建与联调通过;数据与展示口径一致
## 执行顺序
1. 后端:积分恢复与幂等、通知事件入库与去重
2. 后端:退款查询接口、对账入库与差异接口
3. 前端:金额格式化、详情字段与退款/对账页面
4. 测试与联调:全链路验证并修正细节

View File

@ -0,0 +1,16 @@
## 剩余任务
- 后端:
- 积分恢复比例与幂等:部分退款按比例恢复(累计不超抵扣积分)、全额一次性恢复;增加累计校验避免重复恢复
- 通知事件入库与去重payment_notify_events 入库notify_id 唯一)、处理成功置 processed=true重复直接 ACK
- 退款查询接口GET /api/admin/pay/refunds分页筛选GET /api/admin/pay/refunds/:refund_no详情
- 对账接口POST /api/admin/pay/bills/import入库账单GET /api/admin/pay/bills/diffs差异查询与导出
- 订单详情聚合:补充 transaction_id/success_time 与 payment_refunds 明细,计算累计已退与可退余额
- 前端:
- 金额统一元格式化展示(两位小数),保留分用于接口与导出
- 订单详情补充交易号/退款号/渠道/支付方式,展示累计已退与可退余额(元)
- 对账入口页面:账单导入与差异列表
- 幂等与安全:核查唯一索引与条件更新,补充异常告警
## 验收
- 覆盖单元与集成测试(换算、比例恢复、幂等、对账差异)
- 前后端构建与联调通过;数据持久化与展示口径一致

View File

@ -0,0 +1,97 @@
# 目标与范围
- 目标:落地微信小程序(JSAPI)支付,完善后台订单管理与退款闭环,支持对账与运营漏斗统计。
- 范围:预下单→前端调起→支付回调→订单状态推进→退款申请/执行→账单对账→后台运营管理。
# 背景与现状
- 已有:订单/订单项/积分流水/工作台漏斗统计与请求日志。
- 工作台漏斗接口:`internal/api/admin/dashboard_admin.go:754`;路由:`internal/router/router.go:72`
- 用户订单查询:`internal/service/user/orders_list.go:16/40`;系统发放订单与订单号生成:`internal/service/user/reward_grant.go:76/220`
- 缺失:微信支付接入(预下单、SDK、证书/签名)、支付回调与幂等、退款接口与模型、对账流程、订单唯一约束与支付幂等、防重复回调处理。
# 总体架构
- 分层Controller(HTTP) → Service(业务/状态机) → Repository(Gorm/DAO) → PayClient(微信支付SDK) → Infra(配置、证书、日志、幂等/锁)。
- 关键域:
- 订单域:`orders/order_items`维持业务订单与行项目。
- 支付域:新增`payment_preorders/payment_transactions/payment_refunds/payment_notify_events/payment_bills`
- 记账域:`user_points_ledger`扩展退款恢复落账。
# 数据模型设计
- `payment_preorders` 预下单记录
- `id``order_id``order_no`(唯一) 、`channel`(wechat_jsapi)、`prepay_id``out_trade_no`(唯一)、`amount_total`(分)、`payer_openid``status`(created/expired/paid)、`notify_url``created_at``expired_at`
- `payment_transactions` 支付成功交易
- `id``order_id``order_no`(唯一) 、`channel``transaction_id`(微信流水号唯一) 、`amount_total``success_time``raw`(JSON) 、`created_at`
- `payment_refunds` 退款记录
- `id``order_id``order_no``refund_no`(唯一) 、`channel``status`(submitted/success/abnormal/closed) 、`amount_refund`(分/支持部分退款) 、`reason``raw`(JSON) 、`created_at``success_time`
- `payment_notify_events` 回调事件
- `id``notify_id`(唯一) 、`resource_type``event_type``summary``raw`(JSON) 、`processed`(bool) 、`created_at`
- `payment_bills`/`payment_bill_diff` 对账与差异
- `bill_date``type`(tradebill/refundbill) 、`file_url``digest``imported`;差异记录`local_tx_id/wechat_tx_id/diff_type/diff_detail`
- 约束与索引:`orders.order_no`唯一索引;`payment_*`关键幂等字段唯一索引;`notify_id/out_trade_no/transaction_id/refund_no`均唯一。
# 接口设计
- App(用户态)
- `POST /api/app/pay/wechat/jsapi/preorder`:入参`order_no/openid`,出参`timeStamp/nonceStr/package(pay=prepay_id)/signType/paySign`
- `GET /api/app/orders/:order_no/payment`:查询订单支付状态与交易详情。
- Pay通知(平台回调)
- `POST /api/pay/wechat/notify`:验签/解密后入库`payment_notify_events`,幂等推进订单状态,写`payment_transactions`,更新`orders.status=2/paid_at`
- Admin(运营态)
- `GET /api/admin/pay/orders`:订单列表(筛选:状态、时间、渠道)。
- `POST /api/admin/pay/refunds`:创建退款,入参`order_no/amount/reason`;支持全/部分退款。
- `GET /api/admin/pay/refunds`/`GET /api/admin/pay/refunds/:refund_no`:查询退款状态。
- `POST /api/admin/orders/:order_no/close`:关闭未支付订单(同时调用微信`close`)。
- 漏斗统计继续沿用现有接口,支付口径改为“支付交易成功”(来源于transactions)。
# 业务流程
- 预下单(JSAPI)
- 校验订单`status=1`与金额,绑定`openid`→调用`/v3/pay/transactions/jsapi`→落库`payment_preorders`并返回客户端调起参数。参考“JSAPI预下单与签名串”[微信支付文档]。
- 前端调起
- 小程序端`wx.requestPayment`使用服务端返回的`timeStamp/nonceStr/package/signType/paySign`
- 支付回调
- 后端接收`notify`→验签(平台证书)与解密→事件幂等检查(`notify_id`)→查询交易状态或直接推进→更新`orders`为已支付,写`payment_transactions`与触发履约逻辑(虚拟商品使用`is_consumed`)。
- 失败重试
- 回调验签失败/解密失败→告警并拒收;订单未推进→后台定时查询`/v3/pay/transactions/out-trade-no/{out_trade_no}`兜底。
- 退款
- 管理端创建退款→调用`/v3/refund/domestic/refunds`→落库`payment_refunds`→回调/查询成功后更新为`success`并写`user_points_ledger(action=refund_restore)`与订单状态`4已退款`(支持部分退款:订单保持`2已支付`,以累计退款判断)。
- 对账
- 每日拉取交易/退款账单→入库`payment_bills`→与本地`payment_transactions/payment_refunds`比对→生成差异`payment_bill_diff`→运营报表与修复。
# 幂等与一致性
- 订单:`order_no`唯一;预下单多次返回同一`prepay_id`;使用`out_trade_no=order_no`作为业务幂等键。
- 回调:`notify_id`去重;状态推进使用原子事务与“当前状态→新状态”的校验。
- 退款:`refund_no`唯一;重复请求直接返回现有状态。
- 事务Service层统一开启Gorm事务对并发推进加行级乐观锁或显式`UPDATE ... WHERE status=...`
# 配置与密钥管理
- `.env`加载:`WECHAT_APPID/WECHAT_MCHID/WECHAT_SERIAL_NO/WECHAT_PRIVATE_KEY_PATH/WECHAT_API_V3_KEY/WECHAT_NOTIFY_URL`
- 证书落盘PEM私钥与平台证书SDK负责签名与应答验签。禁止将密钥提交仓库。
# SDK选型与规范
- 后端Go采用官方`/wechatpay-apiv3/wechatpay-go`(API v3支持签名/验签/回调解密)。
- 按微信文档“JSAPI签名串四行格式与RSA签名”构造客户端参数。
# 安全与合规
- 验签必做;来源白名单;请求体原样落库审计。
- 金额单位统一“分”;防止小数误差;禁止信任前端金额。
- 日志与告警:失败重试/签名失败/金额不一致必须告警。
# 测试与验收
- 单元:签名参数构造、金额边界、幂等推进、退款部分/全额。
- 集成:模拟回调事件、订单查询兜底、对账导入与差异比对。
- 验收标准:
- 小程序端正常拉起并支付成功,后台订单状态正确推进;
- 管理端可发起退款并正确落账;
- 回调/对账全链路无漏处理且有幂等保障;
- 漏斗“支付用户/完成订单”口径与交易/发货一致。
# 实施步骤
1. 引入微信支付Go SDK与配置加载建立PayClient与证书管理。
2. 建表与唯一索引:`orders.order_no``payment_*`系列关键字段唯一。
3. 实现预下单接口,返回前端调起参数并记录`payment_preorders`
4. 实现支付回调路由:验签/解密→事件入库→幂等推进订单与交易记录。
5. 实现退款接口与回调处理;联动`user_points_ledger`落账。
6. 实现对账拉取与差异比对;补充运营漏斗统计数据来源为交易表。
7. 覆盖测试与监控告警;上线灰度并观察。
# 参考文档(已核对)
- JSAPI预下单与签名串、调起参数与`prepay_id`有效期:`/websites/pay_weixin_qq_doc_v3`
- 官方Go SDK`/wechatpay-apiv3/wechatpay-go`

View File

@ -0,0 +1,58 @@
## 现状与缺口
- 审核未落地:`internal/service/guild/join.go:13-48` 加入逻辑固定写 `JoinStatus=2`(同意),未按 `join_mode=1` 走待审流程;接口仅 `JoinGuild/LeaveGuild`,无审批。
- 管理端缺踢人:仅成员列表 `internal/api/admin/guild_members.go:42-73`;前端成员页无操作列 `web/admin/src/views/guild/members/index.vue:30-35`
- 角色字段未赋权:虽有 `owner/admin/member``internal/repository/mysql/model/guild_members.gen.go:19-23`),但未做权限绑定与校验。
## 目标与范围
- 增加「加入申请与审核」能力:支持待审、通过、拒绝;仅当 `join_mode=1` 生效。
- 增加「成员剔除」能力:管理端支持踢人,并记录操作日志。
- 基础优化:完善角色权限(会长/官员)、分页/检索一致性、接口与 Swagger 描述对齐。
## 后端改造
- 模型与数据流
- 新增表 `guild_join_applications`(推荐方案):字段 `id/guild_id/user_id/apply_time/status(0待审,1通过,2拒绝)/reviewer_id/review_time/remark`
- `JoinGuild` 改造:
- 当 `join_mode=1` 时,仅写一条 `applications(pending)`,不立即写 `guild_members`;返回“申请已提交”。
- 当 `join_mode=2` 时,维持现状直接入会(`JoinStatus=2`)。
- 为兼容保留 24h 离开限制与重复加入校验。
- 审批动作:
- 通过:写 `guild_members``status=1, role=member, start_time=now, join_status=2`),并更新申请为 `approved`;拒绝:仅更新申请为 `rejected`
- 踢人动作:新增服务 `KickMember(ctx, guildID, userID)`,仅将成员 `status=2` 并回写时间;禁止踢会长自身,需先转移会长。
- 服务接口(`internal/service/guild/guild.go`
- 新增:`ListApplications/ApproveApplication/RejectApplication/KickMember` 方法。
- 控制器与路由Admin
- `GET /api/admin/guilds/:guild_id/applications` 列表(分页、按状态过滤)。
- `POST /api/admin/guilds/:guild_id/applications/:id/approve``POST .../reject`
- `DELETE /api/admin/guilds/:guild_id/members/:user_id` 踢人。
- 接口均校验:`IsSuper==1`(现行规则),并记录操作日志(`log_operation`)。
- 控制器与路由App可选
- `POST /api/app/guilds/:guild_id/applications` 提交申请;`GET /api/app/guilds/:guild_id/applications/:id` 查询状态。
- 查询一致性
- 成员列表 `internal/service/guild/members_list.go:9-29` 保持 `status=1` 过滤;无需受待审数据影响。
## 管理端前端改造
- API 封装(`web/admin/src/api/guild.ts`
- 增加 `fetchGetGuildApplications``approveGuildApplication``rejectGuildApplication``kickGuildMember`
- 成员页(`web/admin/src/views/guild/members/index.vue`
- 增加操作列与「踢出」按钮;调用 `kickGuildMember`,成功后刷新当前页。
- 新增「申请审核」页
- 列表展示用户ID、申请时间、状态提供「通过/拒绝」操作;支持按状态筛选与分页。
- 路由与菜单
- 在 `web/admin/src/router/modules/guild.ts` 增加“工会申请审核”菜单。
## 权限与审计
- 权限:管理端暂按 `IsSuper==1`;后续可拓展为“会长/官员”在 App 端可审与踢人(基于 `GuildMembers.Role` 校验)。
- 审计:所有审批与踢人动作写入 `log_operation`,包含 guild_id/user_id/reviewer_id/动作/结果。
## 验收标准
- 加入审核:当 `join_mode=1` 时,提交申请后在管理端可见;可通过/拒绝;通过后出现在成员列表。
- 成员剔除:管理端成员页出现「踢出」操作,成功后该成员不再出现在成员列表;禁止踢会长。
- 权限:非超管无法访问管理端相关接口;错误提示与状态码规范。
- 文档Swagger 增补新端点与模型;接口参数与响应示例完整。
## 风险与兼容
- 数据迁移:新增 `guild_join_applications`DDL 与 gorm/gen 代码生成);对现有 `guild_members` 无破坏性更改。
- 并发与一致性:审批通过与重复申请的幂等处理;用户离开后重新申请的限制延续。
- 回滚策略:所有写操作事务化;失败回滚不影响既有功能。
请确认以上方案,确认后我将分步骤提交后端与前端改造,并补齐测试与文档。

View File

@ -0,0 +1,92 @@
## 目标
- 以微信支付 API v3 的真实流程实现:小程序 JSAPI 预下单→前端调起→支付回调验签与解密→订单推进→退款(全额/部分)→账单拉取与对账→管理端完整的订单/退款展示与操作。
- 严格按金额单位“分”、幂等与安全规范落地;所有敏感配置走环境变量与证书文件。
## 架构与分层
- Controller`internal/api/*`app 用户态、admin 运营态、pay 通知态)
- Service`internal/service/pay/*`(支付域核心:预下单、推进、退款、对账、状态机与幂等)
- Repository`internal/repository/mysql/*`(新增支付域模型与 DAO扩展索引
- PayClient`internal/pkg/pay/*`(微信官方 Go SDK client 初始化、回调验签与解密、账单下载)
- Infra配置与证书、日志、告警、幂等键与重试策略。
## 数据模型(新增表与索引)
1) `payment_preorders`
- `id` PK、`order_id``order_no`(唯一)、`channel`(`wechat_jsapi`)、`prepay_id``out_trade_no`(唯一)、`amount_total`(分)、`payer_openid``status`(created/expired/paid)、`notify_url``created_at``expired_at`
- 索引:`order_no` 唯一、`out_trade_no` 唯一
2) `payment_transactions`
- `id` PK、`order_id``order_no`(唯一)、`channel``transaction_id`(微信流水号唯一)、`amount_total``success_time``raw`(JSON)、`created_at`
- 索引:`transaction_id` 唯一、`order_no` 唯一
3) `payment_refunds`
- `id` PK、`order_id``order_no``refund_no`(唯一)、`channel``status`(submitted/success/abnormal/closed)、`amount_refund`(分)、`reason``success_time``raw`(JSON)、`created_at`
- 索引:`refund_no` 唯一、`order_no` 索引
4) `payment_notify_events`
- `id` PK、`notify_id`(唯一)、`resource_type``event_type``summary``raw`(JSON)、`processed`(bool)、`created_at`
- 索引:`notify_id` 唯一
5) `payment_bills` / `payment_bill_diff`
- 账单拉取与差异记录,支持日级对账
6) 订单表约束
- `orders.order_no` 唯一索引;基于现有字段建立缺失索引
## 配置与证书
- 环境变量:`WECHAT_APPID/WECHAT_MCHID/WECHAT_SERIAL_NO/WECHAT_PRIVATE_KEY_PATH/WECHAT_API_V3_KEY/WECHAT_NOTIFY_URL`
- 证书路径:`./configs/cert/apiclient_key.pem`(商户私钥);平台证书走 SDK 自动下载与缓存(`WithWechatPayAutoAuthCipher`),无需入库
- 不提交任何密钥与证书文件到仓库
## SDK与真实流程
1) 预下单JSAPI
- 初始化 client`option.WithWechatPayAutoAuthCipher(mchid, serial_no, private_key, api_v3_key)`
- 调用 `services/payments/jsapi.JsapiApiService.Prepay`(或 `PrepayWithRequestPayment`)生成 `prepay_id`
- 使用四行签名串 RSA-SHA256 构造小程序 `wx.requestPayment` 参数
- 落库 `payment_preorders``out_trade_no=order_no` 幂等
2) 支付回调notify
- 使用 `notify.NewNotifyHandler(api_v3_key, NewSHA256WithRSAVerifier(certificateVisitor))` 验签并解密得到 `payments.Transaction`
- 幂等:`notify_id` 去重;如已处理直接 ACK
- 状态推进:`WHERE status=1` 条件更新订单为已支付,写入 `payment_transactions`,记录交易时间与原始报文
- 失败场景兜底:按 `out_trade_no` 定时查询交易状态
3) 退款(真实):
- 管理端创建退款:生成 `refund_no` 并调用 SDK `services/refunddomestic`(具体路径以官方包为准),成功回写 `payment_refunds` 与回调推进;支持部分/全额退款
- 数据还原:积分恢复按策略记录 `user_points_ledger(action=refund_restore)`;订单全额退款置 `status=4`,部分退款保持 `status=2` 并维护累计退款
- 幂等:`refund_no` 唯一;重复请求返回已有结果
4) 对账:
- 下载交易/退款账单 `GET /v3/bill`;入库 `payment_bills`;比对本地 `payment_transactions/payment_refunds` 生成 `payment_bill_diff`;支持导出与修复流程
## 后端接口(最终版)
- App
- `POST /api/app/pay/wechat/jsapi/preorder`(真实预下单,返回调起参数)
- `GET /api/app/orders/:order_no/payment`(查询支付状态)
- Pay通知
- `POST /api/pay/wechat/notify`(真实验签与解密,推进订单与交易)
- Admin
- 订单列表/详情/备注/取消/履约(已实现基础版,依据真实交易补充支付字段)
- `POST /api/admin/pay/refunds`(真实退款)/查询退款列表与详情
- 导出与对账入口:`GET /api/admin/pay/orders/export``POST /api/admin/pay/bills/import`
## 前端管理端orders模块
- 列表与详情中文枚举显示(已完成基础);补充:展示真实 `transaction_id``refund_no``channel``支付方式`
- 退款入口:支持部分退款(金额/原因),显示可退余额;展示退款记录(来源 `payment_refunds`
- 对账入口:载入对账结果与差异列表
## 幂等与安全
- 业务幂等键:`order_no`(预下单/订单推进)、`notify_id`(回调)、`refund_no`(退款)
- 事务Service 层统一事务包裹;状态推进以条件更新控制并发
- 日志与告警:回调验签失败、金额不一致、退款异常、对账差异均告警
## 测试与验收
- 单元:签名参数构造、金额边界、幂等推进、退款全额/部分、回调解析
- 集成:模拟回调事件、订单查询兜底、账单导入与差异比对
- 验收标准:
- 小程序支付全链路通;订单与交易记录准确;
- 管理端退款成功并正确落账与还原;
- 对账拉取与差异比对可用;
- 漏斗“支付用户/完成订单”口径对齐真实交易。
## 实施阶段
1) 数据库与DAO新增 `payment_*` 表与索引生成模型与DAO
2) SDK接入统一初始化 client预下单返回参数notify 验签与解密退款API封装
3) 业务实现:订单推进、幂等、退款落账与数据还原
4) 前端完善:展示交易/退款字段、可退余额、退款记录;对账入口
5) 测试与联调:完成单元与集成测试;灰度上线观测
## 依赖
- 官方 Go SDK`github.com/wechatpay-apiv3/wechatpay-go`API v3
- 证书:商户私钥 PEM平台证书自动下载与缓存

View File

@ -0,0 +1,28 @@
## 你的现象与原因归纳
- 你只看到 `payment_preorders``payment_notify_events` 有数据,`payment_refunds` 等为空:
- 支付回调写交易记录的地方使用了“静默写入”,失败不会报错:`internal/api/pay/wechat_notify.go:92``_ = ...Create(tx)`),导致有回调事件但可能没有交易入库。
- 退款创建同样采用“静默写入”:`internal/api/admin/pay_refund_admin.go:79`即使插入失败接口仍返回“success”所以你在前端看到提示但表里没数据。
- 对账管理的数据依赖“导入账单”后才生成差异,未导入则 `payment_bills``payment_bill_diff` 为空,属正常(`internal/api/admin/pay_reconcile_admin.go:37-75,154-182`)。
## 验证与定位步骤
1. 查询订单详情聚合的退款:`GET admin/pay/orders/:order_no``internal/router/router.go:175`,实现 `internal/api/admin/pay_orders_admin.go:216-226`),确认 `refunds` 列表与 `refundable_amount` 是否合理。
2. 查询全局退款列表:`GET admin/pay/refunds?page=1&size=20``internal/router/router.go:170`,实现 `internal/api/admin/pay_refund_admin.go:154-181`)。
3. 验证 DB 表存在与权限:确认 `payment_refunds``payment_transactions` 表存在且可写;如果插入失败,当前代码不会抛错,需要看服务日志是否有“私钥/证书、权限、字段类型”等异常。
4. 核验微信支付配置与回调:`configs/fat_configs.toml:34-41``wechatpay` 配置、回调地址与路由匹配 `internal/router/router.go:252`
## 修复方案(代码层)
- 将静默写库改为显式错误处理并日志:
- 支付交易入库:`internal/api/pay/wechat_notify.go:92` 改为检查返回错误,失败则记录日志并返回错误,避免“有事件无交易”。
- 退款入库:`internal/api/admin/pay_refund_admin.go:79` 改为显式处理插入错误;同时在后续积分流水与订单状态更新前确保退款记录已成功写入。
- 接口返回语义修正:退款接口在任何一步失败时返回错误,不再统一 `success`,确保前端提示与真实入库一致。
## 对账管理的正确使用
- 在“对账管理”导入每日账单(交易/退款)后再查看差异:前端入口 `web/admin/src/views/reconcile/diff/index.vue:45-76`;后端导入与差异查询分别为 `POST admin/pay/bills/import``GET admin/pay/bills/diff`
## 交付与测试计划
- 修改错误处理后,执行一次完整流程:
- 发起支付→收到回调→检查 `payment_transactions` 有记录。
- 在订单详情发起退款→检查 `payment_refunds` 有记录,订单状态与积分流水一致。
- 导入当天账单→检查 `payment_bill_diff` 能产生差异数据(如金额不一致、缺失条目)。
我将按以上步骤先进行只读验证(接口与日志路径),随后按修复方案更新入库错误处理并回归测试,确保你在“退款管理”与“对账管理”都能看到数据。

View File

@ -0,0 +1,44 @@
## 审计结论摘要
- 金额统一“分”,积分统一“元等值整数(1元=1积分)”;现有退款登记复用积分流水记录“金额/100”语义混用。
- 退款恢复积分在多次部分退款时可能重复恢复导致超恢复;预下单未持久化;前端金额展示直用“分”,体验不佳;订单积分抵扣落账缺失。
## 整改目标
- 明确并统一金额/积分口径:分↔积分换算唯一、无混用。
- 完善退款与积分恢复逻辑:部分退款按比例恢复、全额退款一次性恢复;幂等保护。
- 持久化支付域关键数据:预下单/交易/退款/通知;订单详情展示真实字段。
- 前端金额展示统一为“元”且格式化,枚举与文案一致。
## 具体改造项
1) 数据结构与落账
- 新增“货币退款流水”独立记录(或新增表`payment_refunds`并在订单详情聚合展示),将每笔退款金额以“分”记录;保留`user_points_ledger`仅记录“积分”类动作(如`refund_restore`)。
- 在支付阶段补充“订单积分扣减流水”(action=`order_deduct`),并回填`orders.points_ledger_id`,与实际抵扣金额对应(分→积分换算)。
2) 退款恢复与幂等
- 部分退款按比例恢复积分:`恢复积分 = 订单积分抵扣(分) × 本次退款金额(分) / 实付金额(分) ÷ 100`(四舍五入),累计不超过订单抵扣积分总额。
- 全额退款一次性恢复剩余积分;为`refund_restore`增加去重幂等:唯一键`(order_no, action='refund_restore')`或累计校验。
- 订单置“已退款”的条件与部分退款展示:保持`2已支付`,在详情中清晰展示“累计已退款金额(分)”和每笔明细。
3) 预下单与交易持久化
- 引入并使用`payment_preorders/payment_transactions/payment_refunds/payment_notify_events`表:
- 预下单:落库`out_trade_no=order_no``prepay_id`,回填`orders.pay_preorder_id`
- 交易:回调落库`transaction_id/amount/success_time/raw`,订单推进。
- 退款:真实调用后落库`refund_no/amount/status/success_time/raw`,并通过回调/查询推进状态。
- 通知事件:记录`notify_id/event_type/raw/processed`作审计与去重。
4) 前端展示统一
- 列表与详情金额展示统一格式化为“元”(保留两位小数),并保留原始分用于导出或接口。
- 详情页展示:交易号`transaction_id`、退款号`refund_no`、渠道/支付方式;“可退余额(分/元)”与“累计已退款(分/元)”。
5) 安全与幂等
- 业务幂等键:`order_no`(预下单/订单推进)、`notify_id`(回调)、`refund_no`(退款)。
- 数据库唯一索引:`orders.order_no``payment_preorders.out_trade_no``payment_transactions.transaction_id``payment_refunds.refund_no``payment_notify_events.notify_id`
## 验收与测试
- 单元:分↔积分换算正确;部分/全额退款恢复积分计算;幂等重复请求不重复落账。
- 集成:真实预下单→回调验证→管理端主动退款→退款落库与恢复积分→订单详情展示一致;账单对账拉取与差异比对基础验证。
## 执行顺序
1. 数据表与DAO新增支付域表与唯一索引迁移脚本。
2. 业务与服务:预下单持久化、回调入库、退款入库与恢复积分比例算法、幂等保护。
3. 前端统一金额展示与详情字段扩展;导出口径对齐。
4. 测试与联调:场景覆盖与灰度验证,文案与枚举一致。

View File

@ -0,0 +1,19 @@
## 后端
- 通知事件:入库 payment_notify_eventsnotify_id 唯一、processed 标记)、重复回调直接 ACK
- 积分恢复:部分退款按比例恢复(累计不超订单抵扣积分)、全额退款一次性恢复;幂等累计校验
- 退款查询GET /api/admin/pay/refunds分页筛选、GET /api/admin/pay/refunds/:refund_no详情
- 对账接口POST /api/admin/pay/bills/import入库账单、GET /api/admin/pay/bills/diffs差异查询/导出)
- 详情聚合:补充 transaction_id/success_time 与 payment_refunds 明细,计算累计已退与可退余额
## 前端
- 金额统一元格式化(两位小数);保留分供接口/导出
- 详情展示transaction_id/refund_no/channel/支付方式、累计已退与可退余额(元)
- 对账入口:账单导入与差异列表页面
## 幂等与安全
- 幂等键与唯一索引核查order_no/out_trade_no/notify_id/refund_no/transaction_id
- 条件更新控制并发;异常与差异告警
## 验收
- 单元/集成:换算与比例恢复、幂等、预下单→通知→退款→详情一致、对账导入与差异生成
- 前后端构建通过,联调验证无误

View File

@ -0,0 +1,31 @@
## 目标
- 连接已生成的 payment_* 表与 Model/DAO完成真实微信支付体系预下单→通知→退款→对账统一金额/积分;管理端与前端展示一致。
## 后端改造
- 预下单JSAPI
- 在 `internal/api/user/pay_wechat_app.go` 调用 jsapi 预下单成功后,写入 `payment_preorders(order_id, order_no, out_trade_no=order_no, prepay_id, amount_total, payer_openid, notify_url, status, expired_at)` 并回填 `orders.pay_preorder_id`
- 通知验签与解密:
- 在 `internal/api/pay/wechat_notify.go` 验签解密后,写 `payment_notify_events(notify_id, event_type, raw, processed=false)` 并去重;写 `payment_transactions(order_id, order_no, transaction_id, amount_total, success_time, raw)`;条件更新 `orders.status:1→2`,写 `paid_at`
- 后台主动退款:
- 在 `internal/api/admin/pay_refund_admin.go` 调用微信退款后,写入 `payment_refunds(order_id, order_no, refund_no, amount_refund, status, success_time, raw, reason)`
- 按比例恢复积分:`恢复积分 = (订单积分抵扣分 × 退款分 ÷ 实付分) ÷ 100`,累计不超过订单抵扣积分;全额退款一次性恢复剩余;全额时置 `orders.status=4`;部分退款保留 `2已支付`
- 对账:
- 新增服务在每日任务中拉取 `tradebill/refundbill` 入库 `payment_bills`;对比 `payment_transactions/payment_refunds` 生成 `payment_bill_diff`;导出差异。
## 管理端接口
- 订单详情聚合:从 `payment_transactions/payment_refunds` 汇总展示 `transaction_id/success_time/amount_total``refund_no/amount_refund/status/success_time/reason`;计算可退余额 `actual_amount - 累计退款`
- 退款列表/详情:`GET /api/admin/pay/refunds``GET /api/admin/pay/refunds/:refund_no`
- 对账入口:`POST /api/admin/pay/bills/import``GET /api/admin/pay/bills/diffs`
## 前端改造
- 金额统一以“元”格式化展示(两位小数),接口仍用“分”;
- 订单详情增加真实字段(交易号、退款号、渠道/支付方式),展示累计已退与可退余额(元);退款记录按元显示。
## 幂等与安全
- 使用唯一索引与幂等键:`order_no/notify_id/refund_no/transaction_id/out_trade_no`
- 条件更新控制并发;异常与差异告警。
## 验收与测试
- 单元:分↔积分换算、比例恢复、幂等重复;
- 集成:预下单→通知→主动退款→详情一致;
- 对账:拉取账单并生成差异;前后端构建与联调通过。

View File

@ -0,0 +1,52 @@
## 目标
- 按真实微信支付流程完善一个完整的支付体系:持久化预下单/交易/退款/通知/对账,统一金额/积分口径,优化退款恢复与幂等,前后端一致展示。
## 数据库与模型
- 新增表(均使用“分”为金额单位):
- payment_preorders(id, order_id, order_no[UNIQUE], channel, prepay_id, out_trade_no[UNIQUE], amount_total, payer_openid, status(created/expired/paid), notify_url, created_at, expired_at)
- payment_transactions(id, order_id, order_no[UNIQUE], channel, transaction_id[UNIQUE], amount_total, success_time, raw(JSON), created_at)
- payment_refunds(id, order_id, order_no, refund_no[UNIQUE], channel, status(submitted/success/abnormal/closed), amount_refund, reason, success_time, raw(JSON), created_at)
- payment_notify_events(id, notify_id[UNIQUE], resource_type, event_type, summary, raw(JSON), processed(bool), created_at)
- payment_bills(id, bill_date, type(tradebill/refundbill), file_url, digest, imported(bool), created_at)
- payment_bill_diff(id, bill_date, diff_type(missing/amount_mismatch/extra), local_tx_id, wechat_tx_id, detail, created_at)
- 订单唯一索引orders.order_no[UNIQUE]
- 迁移:生成/应用 GORM 迁移脚本;保持向后兼容(不破坏现有数据)。
## Repository/DAO
- 生成 gorm/gen DAO 与 model 文件;在 internal/repository/mysql/dao, model 下新增 payment_* 的 gen 文件。
## Service 逻辑
- 预下单:
- 校验订单 status=1、金额与归属调用 jsapi.Prepay落库 payment_preordersout_trade_no=order_no, prepay_id回填 orders.pay_preorder_id返回调起参数RSA-SHA256 四行签名)。
- 支付通知:
- 验签解密 payments.Transaction幂等notify_id 去重)入库 payment_notify_events写入 payment_transactionstransaction_id/raw/success_timeorders 条件推进 status:1→2写 paid_at。
- 退款:
- 管理端主动退款:生成 refund_no 并调用 refunddomestic.Create入库 payment_refundsrefund_no/amount/status/raw
- 积分恢复(统一口径):
- 部分退款按比例恢复:恢复积分 = (订单积分抵扣分 × 退款分 ÷ 实付分) ÷ 100累计不超过订单抵扣积分全额退款一次性恢复剩余积分幂等校验累计已恢复积分 vs 目标)。
- 订单状态:全额退款置 4已退款部分退款维持 2已支付并维护累计已退款金额聚合 payment_refunds
- 对账:
- 每日下载交易/退款账单(依 SDK入库 payment_bills比对 payment_transactions/payment_refunds 生成 payment_bill_diff提供导出与差异修复入口。
## Admin 接口
- 订单详情:增加展示字段 transaction_id、累计已退款、每笔退款refund_no/amount/status/时间/原因);可退余额=实付-累计退款。
- 退款列表与详情GET /api/admin/pay/refunds, GET /api/admin/pay/refunds/:refund_no
- 对账POST /api/admin/pay/bills/import上传账单并入库GET /api/admin/pay/bills/diffs差异列表
## 前端
- 金额统一格式化为“元”展示(两位小数),内部接口仍用“分”;
- 订单详情页增加 transaction_id/refund_no/channel/支付方式;展示累计已退款与可退余额(元);
- 退款记录表展示 refund_no/金额(元)/原因/时间;
- 保持中文枚举一致(状态/来源/发货/抽奖)。
## 幂等与安全
- 幂等键order_no预下单/推进、notify_id回调、refund_no退款
- 唯一索引:预下单/交易/退款/通知表关键字段;
- 条件更新WHERE status=... 控制并发;
- 日志与告警:验签失败、金额不一致、退款异常、对账差异。
## 测试与验收
- 单元:分↔积分换算、部分/全额退款恢复、幂等重复;
- 集成:真实预下单→回调→主动退款→详情一致;
- 对账:拉取账单并生成差异;
- 验收:管理端全链路可用、金额与积分口径一致、数据持久化完整。

View File

@ -0,0 +1,22 @@
## 用途说明
- 退款管理:全局退款列表与详情,用于按条件检索所有订单的退款记录。前端 `web/admin/src/views/refunds/list/index.vue:21-31` 通过 `fetchAdminRefunds` 请求后端分页列表 `GET admin/pay/refunds``internal/router/router.go:169-171`;实现 `internal/api/admin/pay_refund_admin.go:154-181`)。
- 对账管理:导入微信日账单后,对本地交易/退款与微信账单进行比对并生成差异明细。入口 `web/admin/src/views/reconcile/diff/index.vue:45-76`;后端 `POST admin/pay/bills/import``GET admin/pay/bills/diff``internal/router/router.go:172-173`;实现 `internal/api/admin/pay_reconcile_admin.go:37-75,154-182`)。
## 为什么你现在看到的现象成立
- 订单管理能看到退款:订单详情直接按该订单号聚合退款记录 `internal/api/admin/pay_orders_admin.go:216-226`,来源同一表 `payment_refunds`
- 退款管理为空:通常由筛选条件或分页导致。该页面有 `order_no/status` 两个筛选项并在挂载时执行一次查询 `web/admin/src/views/refunds/list/index.vue:43-55`;若筛选为非空但不匹配当前退款,列表会显示为空。
- 对账管理为空:只有在执行“导入账单”后才会计算并产生差异;未导入则 `payment_bill_diff` 自然为空(计算逻辑 `internal/api/admin/pay_reconcile_admin.go:77-107,109-138`)。
## 快速验证步骤
1. 在“退款管理”清空筛选并点击“刷新”,或直接调用 `GET admin/pay/refunds`(默认分页 20 条)确认返回是否包含最近的退款;该接口由认证组保护,需管理员登录(`internal/router/router.go:169-171`)。
2. 如仍为空,检查是否写入了 `payment_refunds`:创建退款的写库在 `internal/api/admin/pay_refund_admin.go:70-80`,列表读取在 `154-181`
3. 在“对账管理”点击“导入账单”:选择“交易账单”或“退款账单”,粘贴当日微信账单的 JSON页面模型 `web/admin/src/views/reconcile/diff/index.vue:45-76`)。导入后查询 `GET admin/pay/bills/diff` 是否出现差异。
## 示例导入 JSON退款账单
- 格式示例:`[{"refund_no":"R202411010001","out_trade_no":"O202411010001","amount_refund":100}]`
- 交易账单示例:`[{"transaction_id":"4200001xxx","out_trade_no":"O202411010001","amount_total":100}]`
## 接下来我将执行
- 复核你当前管理端的筛选条件、分页与管理员登录态是否正常。
- 指导你导入一份当日测试账单并确认差异计算是否可见。
- 如需要,再定位具体退款记录的可见性(按 `order_no/status` 组合查询)。

View File

@ -0,0 +1,24 @@
## 问题定位
- 操作列使用 `class="flex gap-2"` 包裹多个按钮6个但项目未引入 Tailwind该类无效导致按钮按块级元素竖排堆叠视觉成“蓝色小方块纵向排布”。
- 位置:`web/admin/src/views/player-manage/index.vue:202-234`
- 其它页普遍使用 `useSlot``ElSpace` 渲染操作列,避免换行与拥挤(如:`guild/manage/index.vue:185``operations/*`)。
## 修复方案
- 将操作列容器从 `h('div', { class: 'flex gap-2' }, [...])` 改为 `h(ElSpace, { wrap: true, size: 6 }, [...])`,保证横向排列且自动换行。
- 适当增大操作列宽度,避免拥挤(由 280 调整为 320
- 如需更一致的实现,后续可将操作列改为 `useSlot: true` + `<template #operation>` 模板渲染,统一风格(本次先用 `ElSpace` 修复)。
## 具体修改点
- 文件:`web/admin/src/views/player-manage/index.vue`
- 修改 `columnsFactory()``operation` 列的 `formatter` 容器为 `ElSpace``wrap: true``size: 6`)。
- 将 `operation``width` 调整为 `320`
## 验收标准
- 操作列按钮横向排列,换行时保持等距,不再竖排堆叠。
- 表头、分页与表格不发生位移,整页无水平滚动条。
- 批量操作按钮(表头左侧)不受影响,交互正常。
## 影响范围
- 仅影响玩家管理页视图渲染;不改动接口、不影响其它模块。
确认后我将执行上述修改并重新构建前端,提供预览截图/链接以供验收。

View File

@ -0,0 +1,34 @@
## 现状审计
- 高级表格使用:
- 活动管理:`web/admin/src/views/activity/manage/index.vue:10-17` 使用 `ArtTable`(高级表格)
- 期数管理:`web/admin/src/views/activity/issues/index.vue:6-14` 使用 `ArtTable`
- 奖励管理:`web/admin/src/views/activity/rewards/index.vue:31-37` 使用 `ArtTable`
- 操作栏一致性:
- 活动管理中“模拟”按钮使用 `ElButton``manage/index.vue:32-39`),其余使用 `ArtButtonTable`;风格不一致
- 期数管理操作栏统一使用 `ArtButtonTable``issues/index.vue:21-33`
- 表格可视宽度:多列设置了固定 `width`(如 `manage/index.vue:291-297``issues/index.vue:83-88``name` 列使用 `showOverflowTooltip``manage/index.vue:292`),导致内容显示受限;未显式启用 `tableLayout="auto"`
## 优化目标
1. 全部使用高级表格组件,启用自适应布局以显示完整内容
2. 操作栏统一为图标标签按钮(`ArtButtonTable`),视觉与交互一致
3. 表格宽度与列长度自适应:移除过度固定宽度,采用 `minWidth``tableLayout="auto"`,仅对数值列设定最小宽度;必要时保留溢出提示
## 具体改动
- 活动管理(`web/admin/src/views/activity/manage/index.vue`
- 在 `ArtTable` 上增加 `tableLayout="auto"`
- 将 `columnsFactory` 中固定 `width` 改为 `minWidth`(如 `name``minWidth: 260``categoryName` `minWidth: 140``actions` `minWidth: 240`),取消 `showOverflowTooltip` 或仅对极长文本保留
- 将“模拟抽奖”按钮统一为 `ArtButtonTable`,图标 `ri:shuffle-line`,与现有按钮风格一致(替换 `ElButton`,位置 `index.vue:32-39`
- 期数管理(`web/admin/src/views/activity/issues/index.vue`
- 在 `ArtTable` 上增加 `tableLayout="auto"`
- 将列宽改为 `minWidth`,例如:`issue_number` `minWidth: 160``status` `minWidth: 110``actions` `minWidth: 200`
- 已统一使用 `ArtButtonTable`,无需额外调整
- 奖励管理(`web/admin/src/views/activity/rewards/index.vue`
- 已是 `ArtTable` 并启用 `tableLayout="auto"`;保留前次优化的汇总与列增强(剩余/总量、期望概率)
## 验收标准
- 三个页面均使用 `ArtTable`,内容在常见分辨率下完整显示(无不必要截断)
- 操作栏按钮风格一致,皆为图标标签按钮,间距统一
- 表格宽度随容器自适应,长文本列通过 `minWidth` 与布局算法得到更好的展示;必要时保留 tooltip
## 风险与回滚
- 改动为前端表现层,低风险;如需回滚,可恢复原 `width` 配置与旧按钮类型。

View File

@ -0,0 +1,39 @@
## 实施范围
- 对接已生成的 payment_* 表与 Model/DAO落地真实支付服务逻辑与管理端展示
## 后端服务对接
- 预下单JSAPI
- 写入 `payment_preorders``order_id/order_no/out_trade_no=order_no/prepay_id/amount_total/payer_openid/notify_url/status=created/expired_at`
- 回填 `orders.pay_preorder_id`;返回四行签名参数给小程序
- 支付通知(验签与解密)
- 入库 `payment_notify_events``notify_id/event_type/raw/processed=false`,幂等去重)
- 入库 `payment_transactions``order_id/order_no/transaction_id/amount_total/success_time/raw`
- 条件更新 `orders.status:1→2`,写 `paid_at`
- 后台主动退款(全额/部分)
- 生成 `refund_no` 并调用微信退款 API入库 `payment_refunds``order_id/order_no/refund_no/amount_refund/status/success_time/raw/reason`
- 积分恢复(统一换算与幂等):
- 部分退款:`恢复积分 = (订单积分抵扣分 × 退款分 ÷ 实付分) ÷ 100`,累计不超过订单抵扣积分;幂等以“累计已恢复”校验
- 全额退款:一次性恢复剩余积分,置 `orders.status=4`
- 对账基础能力
- 拉取交易/退款账单入库 `payment_bills`;比对生成 `payment_bill_diff`;导出差异
## 管理端接口
- 订单详情聚合字段:
- 交易:`transaction_id/success_time/amount_total`
- 退款:累计已退与明细 `refund_no/amount_refund/status/success_time/reason`
- 可退余额(分):`actual_amount - 累计退款`
- 退款列表与详情:`GET /api/admin/pay/refunds``GET /api/admin/pay/refunds/:refund_no`
- 对账入口:`POST /api/admin/pay/bills/import``GET /api/admin/pay/bills/diffs`
## 前端展示改造
- 金额统一元格式化显示(两位小数),接口仍用分;
- 详情页增加展示 `transaction_id/refund_no/channel/支付方式`、累计已退与可退余额(元);退款记录按元显示
## 幂等与安全
- 幂等键:`order_no/notify_id/refund_no`;唯一索引已在表层
- 条件更新控制并发;日志与告警覆盖验签失败、金额不一致、退款异常、对账差异
## 验收与测试
- 单元:分↔积分换算、部分/全额退款恢复、幂等重复
- 集成:真实预下单→通知→主动退款→详情对齐;对账拉取与差异
- 构建与联调通过,数据持久化与展示一致

View File

@ -0,0 +1,26 @@
## 实施范围
- 支付域持久化:预下单/交易/退款/通知/账单与差异表
- 退款恢复比例与幂等:统一分↔积分换算,部分/全额退款逻辑
- 订单详情与前端金额展示:元格式化与真实字段展示
## 后端改动
- 建表与索引:`payment_preorders/payment_transactions/payment_refunds/payment_notify_events/payment_bills/payment_bill_diff`;唯一键:`order_no/out_trade_no/transaction_id/refund_no/notify_id`
- DAO与模型生成`internal/repository/mysql/dao, model`新增 payment_* gen 文件
- 预下单:落库`payment_preorders`并回填`orders.pay_preorder_id`;返回调起参数
- 通知:入库`payment_notify_events/payment_transactions``orders`从1→2并写`paid_at`
- 退款:入库`payment_refunds`;恢复积分=订单积分抵扣分×退款分/实付分÷100累计不超抵扣积分全额退款一次性恢复剩余订单状态与累计已退款更新
- 对账:拉取账单入库`payment_bills`,生成`payment_bill_diff`,接口导出差异
## 前端改动
- 金额统一元格式化展示,内部用分;
- 详情展示:`transaction_id/refund_no/channel/支付方式`、累计已退与可退余额(元);退款记录表按元显示
## 幂等与安全
- 幂等键:`order_no/notify_id/refund_no`
- 条件更新:`WHERE status=...`;回调与退款入库去重
- 日志与告警:验签失败、金额不一致、退款异常、对账差异
## 验收与测试
- 单元:换算、比例恢复、幂等
- 集成:预下单→回调→主动退款→详情一致;对账拉取与差异
- 前后端构建与联调通过

View File

@ -0,0 +1,38 @@
# 目标
- 使用你已填写的配置按“config/cert”中的证书路径完成微信支付真实接入统一下单+小程序签名+通知验签/解密。
# 当前配置状况
- 已存在 `[wechatpay]` 配置段并支持 ENV 覆盖:`mchid/serial_no/private_key_path/api_v3_key/notify_url`configs/configs.go
- 证书路径建议:将商户私钥 `apiclient_key.pem` 放在 `./configs/cert/apiclient_key.pem`,并把 `private_key_path` 配置为该相对路径。
# 具体改造点
- 证书路径接入:
- 不新增新的配置段,直接使用 `[wechatpay].private_key_path` 指向 `config/cert` 中的私钥路径。
- 若你希望本地校验平台证书(而非自动下载),增加可选字段:`wechatpay.platform_cert_path``wechatpay.platform_public_key_id`(可留空);否则走自动下载。
- 统一下单JSAPI按已封装客户端调用
- 读取 `private_key_path` → 初始化 `core.Client``WithWechatPayAutoAuthCipher`),自动管理平台证书。
- `jsapi.Prepay` 返回 `prepay_id`,之后用已有 `BuildJSAPIParams` 生成 `wx.requestPayment` 所需参数。
- 支付通知验签与解密:
- 使用 SDK 的平台证书下载管理器:在通知处理处懒加载 `downloader.MgrInstance().RegisterDownloaderWithPrivateKey(mchid, serial_no, private_key, api_v3_key)`
- 用 `notify.NewNotifyHandler(api_v3_key, RSAVerifier(certificateVisitor))` 验签;解密 `resource.ciphertext` 得到 `payments.Transaction`;按 `out_trade_no/success_time` 幂等推进订单。
- 条件更新:`WHERE status=1` 防并发重复推进;已支付直接 ACK。
# 文件与位置(只改后端)
- 设置路径:在 `configs/*_configs.toml``[wechatpay]` 写入 `private_key_path = "./configs/cert/apiclient_key.pem"`(你已填写即可)。
- 统一下单:`internal/pkg/pay/client.go` 已使用该路径初始化;无需再改。
- 通知验签:`internal/api/pay/wechat_notify.go` 切换为 SDK 验签/解密实现(现在是占位版),引入 downloader + notify.Handler。
# 验证步骤
- 编译通过:`go build ./...`
- 预下单:调用 `POST /api/app/pay/wechat/jsapi/preorder` 返回小程序参数;`wx.requestPayment` 可正常调起。
- 通知:微信推送或用 SDK 模拟请求,验签成功后订单由待支付→已支付,`paid_at` 为交易成功时间;重复通知幂等。
# 安全与合规
- 私钥路径仅用于服务端签名;不要提交证书文件到仓库。
- 金额单位统一为分;仅信任服务端订单金额。
# 我将执行的改动(收到确认后)
1. 在各环境 `*_configs.toml` 确认/写入 `wechatpay.private_key_path` 指向 `config/cert`
2. 将通知处理替换为 SDK 验签/解密版本(保留幂等与条件更新)。
3. 增加可选本地平台证书配置支持(如你偏好离线证书验证)。
4. 自测:预下单→支付→通知推进,重复通知幂等校验。

View File

@ -0,0 +1,24 @@
## 目标
- 仅支持后台管理主动发起退款(全额/部分)并记录每笔退款金额与原因
- 在订单详情展示“退款记录列表”与“可退款金额”,统一中文枚举显示
## 后端
1) 管理端退款记录补充:在 `POST /api/admin/pay/refunds` 现有逻辑基础上,新增一条 `user_points_ledger(action='refund_amount', points=退款金额, ref_table='orders', ref_id=order_no, remark=reason)` 记录每笔退款金额(不新建表)。
2) 订单详情聚合退款:在 `GET /api/admin/pay/orders/:order_no` 增加字段:
- `refunds[]`:来源于 `user_points_ledger(action='refund_amount')`;包含金额、原因、时间
- `refundable_amount``actual_amount - sum(refunds.amount)`(分)
3) 边界与幂等:
- 仅允许 `status=2已支付` 发起退款;金额需 `>0 且 <= refundable_amount`
- 全额退款后置 `status=4已退款`;部分退款维持 `status=2`
## 前端
1) 列表与详情使用中文枚举(已完成基础替换)
2) 详情页:
- 显示“可退款金额(分)”
- 新增“退款记录”表格(金额/原因/时间)
- 退款按钮支持部分退款;提交后刷新详情与可退余额
## 验收
- 管理端可主动发起退款并记录到“退款记录”
- 详情页展示可退余额与每笔退款信息
- 编译与联调通过

View File

@ -0,0 +1,73 @@
## 目标
- 列表与详情页统一显示“用户、状态、来源、发货状态”等中文枚举名称
- 在订单详情提供“退款”入口(支持全额/部分退款),并完善数据还原(积分、库存/履约、优惠券)与幂等、校验流程
- 支持用户发起退款申请→后台审核→执行退款的闭环
## 前端改造
### 枚举与显示
- 新增/复用前端常量映射:
- 订单状态:`{1: 待支付, 2: 已支付, 3: 已取消, 4: 已退款}`
- 来源类型:`{1: 商城直购, 2: 抽奖票据, 3: 其他}`
- 发货状态:`{1: 待发货, 2: 已发货, 3: 已签收, 4: 异常}`
- 抽奖结果:`is_winner(0/1)→否/是``level(1/2/3)→S/A/B`
- 列表与详情使用映射显示中文名;用户信息显示 `nickname`(无则显示 `用户ID`
### 退款入口与交互
- 详情页添加“退款”按钮:显示可退款金额(默认 `actual_amount`)、支持部分退款(输入退款金额与原因)
- 提交后刷新详情;在详情页增加“退款记录”区块,显示每笔退款状态与时间、原因
- 禁用策略:
- 已取消或已退款不可再次退款
- 货已签收或虚拟已履约需走人工审批(按钮灰显或改为“申请退款”)
## 后端接口与流程
### 管理端
- 创建退款:`POST /api/admin/pay/refunds`
- 入参:`order_no, amount, reason`
- 校验:订单存在且`status=2``amount>0 && <= 未退款余额`
- 幂等:生成`refund_no`唯一;重复请求返回既有状态
- 执行:
- 微信真实退款:调用`/v3/refund/domestic/refunds`(成功→记录到`payment_refunds`并落账)
- 数据还原:
- 积分:写 `user_points_ledger(action=refund_restore, points=points_amount)`(按原抵扣金额,部分退款按比例/策略)
- 虚拟履约:若`is_consumed=1`且可撤销,标记撤销或新建“撤销记录”;否则保留人工处理标记
- 发货:如已发/已签收不自动撤销,标记异常并提示人工流程(退货入库)
- 订单状态:
- 全额退款→置`status=4已退款`
- 部分退款→保持`status=2已支付`,记录累计退款额(在`payment_refunds`中维护)
- 查询退款:`GET /api/admin/pay/refunds``GET /api/admin/pay/refunds/:refund_no`
- 在订单详情接口增加退款列表聚合(展示退款记录)
### 用户端退款(申请→审核→执行)
- 用户申请:`POST /api/app/orders/:order_no/refund/apply`(金额与原因)
- 管理端审核:`POST /api/admin/pay/refunds/:refund_no/approve | /reject`
- 审核通过→执行退款(同管理端创建退款);拒绝→记录原因
## 数据模型建议(支付域)
- `payment_refunds``refund_no`唯一,`order_id/order_no/channel/status(submitted/success/abnormal/closed)`, `amount_refund`, `reason`, `success_time`, `raw`
- 在现有`orders`保持`order_no`唯一;在退款、通知增加幂等校验(`refund_no/notify_id`
## 校验与边界
- 金额单位统一“分”;仅信任后端计算出的可退余额
- 订单来源:抽奖类订单(`source_type=2`)需校验抽奖资产状态(`user_inventory`)是否可撤销
- 已签收/履约完成:默认不自动撤销,需人工处理或审批流;在系统中标注并可导出
## 幂等与一致性
- 退款:`refund_no`唯一;重复请求返回现有记录
- 回调解密与验签:使用 SDK `notify.Handler`;退款结果通过回调或主动查询推进记录状态
- 状态推进使用事务与条件更新(如`WHERE status=2`)保证并发安全
## 前端展示增强(详情)
- 信息区块:订单、活动、付款、订单项、发货、积分、退款记录
- 显示中文枚举;金额统一格式化(分→元)或保留分展示
- 退款弹窗:输入金额/原因;显示可退款上限与提示规则(履约/签收需审批)
## 验收标准
- 列表与详情均显示中文枚举;详情展示活动与付款信息完整
- 管理端可创建退款并正确落账(占位或真实接入);用户申请退款可走审批流程
- 幂等与边界校验完善;构建与联调通过
## 实施步骤
1. 前端:新增枚举常量并替换列表/详情的显示;详情页加退款按钮与退款记录展示
2. 后端:完善退款创建与查询接口、数据还原逻辑与幂等;补充详情接口聚合退款记录
3. 用户端:新增退款申请与后台审核接口(可按你需求分阶段)
4. 测试与联调:金额/并发/履约/签收边界;通知与查询兜底

View File

@ -0,0 +1,24 @@
## 目标
- 美化运营管理的“小程序二维码生成”页面,使布局更现代、交互更顺畅。
- 明确并保持后端与前端的 `douyin_id` 为字符串类型。
## 界面优化
- 布局:使用卡片+两栏布局,上方表单,下方预览区域居中;自适应在小屏改为单列。
- 交互:
- 增加宽度滑块 `ElSlider`,实时控制二维码预览大小
- 增加“复制链接”按钮,复制小程序路径 `pages/login/index?...`
- 表单校验与提示:必填校验,错误时显示提示消息
- 保留“下载PNG”新增“重置”按钮
- 视觉:合并间距、统一按钮风格、预览区居中显示
## 类型确认
- 后端 `miniapp_qrcode``weixin login` 请求体的 `douyin_id` 保持为 `string`,不做任何数字转换
- 前端表单的 `douyin_id` 仅用 `string` 类型
## 实施
- 修改 `web/admin/src/views/operations/miniapp-qrcode/index.vue`:加入滑块、复制链接、重置、表单校验与更优布局
- 运行构建验证
## 验收
- 表单必填校验有效,二维码预览与大小调整正常,复制链接与下载可用
- `douyin_id` 在前后端均为字符串类型

View File

@ -0,0 +1,45 @@
## 目标
- 在运营管理新增“小程序二维码生成”,输入:`invite_code``douyin_id`,生成跳转到小程序登录页的二维码。
- 在小程序登录 API 支持绑定抖音账号,仅绑定 `douyin_id`
## 后端改造
- 数据模型
- 为 `users` 表新增字段:`douyin_id`string唯一可空
- 迁移脚本添加唯一索引 `idx_users_douyin_id``gorm/gen` 重新生成模型。
- 生成二维码接口
- 新增管理端接口:`POST /api/admin/miniapp/qrcode`
- 请求:`{ invite_code: string, douyin_id: string, width?: number }`
- 响应:`{ image_base64: string }`
- 实现:使用 `internal/pkg/wechat/qrcode.go` 构造 `path`(示例:`/pages/login?invite_code=xxx&douyin_id=yyy`),校验长度 ≤1024。
- 小程序登录绑定
- 修改 `internal/api/user/login_app.go``weixinLoginRequest`:新增可选 `douyin_id`
- service 层 `LoginWeixin`
- 当请求含 `douyin_id` 且当前用户未绑定时,写入 `douyin_id`
- 若 `douyin_id` 已被其他用户绑定,返回冲突错误码;不覆盖已有绑定。
## 前端改造(运营管理)
- 新增页面:`views/operations/miniapp-qrcode/index.vue`
- 表单:`invite_code``douyin_id``width`(默认 430
- 调用 `admin/miniapp/qrcode`,预览与下载二维码。
- 小程序前端:登录页读取 query`invite_code``douyin_id``code` 一并调用 `POST /api/app/users/weixin/login`
## 路由与校验
- 路由:新增 admin 路由于 `internal/router/router.go` 管理端认证组。
- 输入校验:
- `invite_code`:非空、长度/字符集校验
- `douyin_id`:长度 150字符集限制字母数字下划线
- 唯一绑定策略:`douyin_id` 全局唯一;冲突返回明确错误码与提示。
## 配置
- 使用现有 `configs.Wechat.AppID/AppSecret`;复用 `internal/pkg/wechat/qrcode.go`
## 验收
- 运营页生成二维码成功;扫码进入并完成登录绑定,用户记录含 `douyin_id`
- 登录接口兼容无 `douyin_id` 场景;携带时正确绑定并处理冲突。
## 变更清单
- 后端:新增 `miniapp_qrcode` 管理端接口;修改登录请求体与 service数据库迁移与模型生成。
- 前端:新增运营二维码页面与 API路由注册。
## 风险与回滚
- 字段新增不影响旧流程;二维码功能为增量,回滚可移除新路由与页面。

BIN
build/.DS_Store vendored Normal file

Binary file not shown.

BIN
build/resources/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1 +1 @@
import{d as a,T as s,e as t,h as e,p as m,J as r,m as l,y as c,z as n,l as d,C as x,E as o,f as i}from"./index-ip5gDR6L.js";/* empty css */import{_ as p}from"./index-BOPvNXEP.js";import{E as u}from"./index-opP5No9I.js";const f={class:"page-content !border-0 !bg-transparent min-h-screen flex-cc"},g={class:"flex-cc max-md:!block max-md:text-center"},b={class:"ml-15 w-75 max-md:mx-auto max-md:mt-10 max-md:w-full max-md:text-center"},h={class:"text-xl leading-7 text-g-600 max-md:text-lg"},_=a({__name:"ArtException",props:{data:{}},setup(a){const _=x(),{homePath:j}=s(),v=()=>{_.push(j.value)};return(s,x)=>{const _=p,j=u,w=o("ripple");return i(),t("div",f,[e("div",g,[m(_,{src:a.data.imgUrl,size:"100%",class:"!w-100"},null,8,["src"]),e("div",b,[e("p",h,l(a.data.desc),1),r((i(),c(j,{type:"primary",size:"large",onClick:v,class:"mt-5"},{default:n(()=>[d(l(a.data.btnText),1)]),_:1})),[[w]])])])])}}});export{_};
import{d as a,T as s,e as t,h as e,p as m,J as r,m as l,y as c,z as n,l as d,C as x,E as o,f as i}from"./index-DBHnxsy6.js";/* empty css */import{_ as p}from"./index-DuC2vu0m.js";import{E as u}from"./index-r0EsCkBw.js";const f={class:"page-content !border-0 !bg-transparent min-h-screen flex-cc"},g={class:"flex-cc max-md:!block max-md:text-center"},b={class:"ml-15 w-75 max-md:mx-auto max-md:mt-10 max-md:w-full max-md:text-center"},h={class:"text-xl leading-7 text-g-600 max-md:text-lg"},_=a({__name:"ArtException",props:{data:{}},setup(a){const _=x(),{homePath:j}=s(),v=()=>{_.push(j.value)};return(s,x)=>{const _=p,j=u,w=o("ripple");return i(),t("div",f,[e("div",g,[m(_,{src:a.data.imgUrl,size:"100%",class:"!w-100"},null,8,["src"]),e("div",b,[e("p",h,l(a.data.desc),1),r((i(),c(j,{type:"primary",size:"large",onClick:v,class:"mt-5"},{default:n(()=>[d(l(a.data.btnText),1)]),_:1})),[[w]])])])])}}});export{_};

View File

@ -1 +1 @@
var e=Object.defineProperty,t=Object.defineProperties,s=Object.getOwnPropertyDescriptors,r=Object.getOwnPropertySymbols,a=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,p=(t,s,r)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[s]=r;import{_ as l}from"./index.vue_vue_type_script_setup_true_lang-CCGboj8m.js";import{d as n,e as c,f as m,p as i,h as d,i as u,m as x,j as b}from"./index-ip5gDR6L.js";const g={class:"title mt-8 text-3xl font-medium !text-g-900 max-md:mt-2.5 max-md:text-2xl"},f={class:"msg mt-5 text-base text-g-600"},_={class:"res mt-7.5 rounded bg-g-200/80 dark:bg-g-300/40 px-7.5 py-5.5 text-left max-md:px-7.5 max-md:py-2.5 [&_p]:flex [&_p]:items-center [&_p]:py-2 [&_p]:text-sm [&_p]:text-[#808695] [&_p_i]:mr-1.5"},y={class:"btn-group mt-12.5"},v=n((j=((e,t)=>{for(var s in t||(t={}))a.call(t,s)&&p(e,s,t[s]);if(r)for(var s of r(t))o.call(t,s)&&p(e,s,t[s]);return e})({},{name:"ArtResultPage"}),t(j,s({__name:"ArtResultPage",props:{type:{default:"success"},title:{default:""},message:{default:""},iconCode:{default:""}},setup:e=>(t,s)=>{const r=l;return m(),c("div",{class:u(["page-content box-border !px-20 py-3.5 text-center max-md:!px-5",e.type])},[i(r,{class:u(["icon size-22 p-2 mt-16 block rounded-full !text-white","success"===e.type?"bg-[#19BE6B]":"bg-[#ED4014]"]),icon:e.iconCode},null,8,["icon","class"]),d("h1",g,x(e.title),1),d("p",f,x(e.message),1),d("div",_,[b(t.$slots,"content")]),d("div",y,[b(t.$slots,"buttons")])],2)}}))));var j;export{v as _};
var e=Object.defineProperty,t=Object.defineProperties,s=Object.getOwnPropertyDescriptors,r=Object.getOwnPropertySymbols,a=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,p=(t,s,r)=>s in t?e(t,s,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[s]=r;import{_ as l}from"./index.vue_vue_type_script_setup_true_lang-DUyHB5jc.js";import{d as n,e as c,f as m,p as i,h as d,i as u,m as x,j as b}from"./index-DBHnxsy6.js";const g={class:"title mt-8 text-3xl font-medium !text-g-900 max-md:mt-2.5 max-md:text-2xl"},f={class:"msg mt-5 text-base text-g-600"},_={class:"res mt-7.5 rounded bg-g-200/80 dark:bg-g-300/40 px-7.5 py-5.5 text-left max-md:px-7.5 max-md:py-2.5 [&_p]:flex [&_p]:items-center [&_p]:py-2 [&_p]:text-sm [&_p]:text-[#808695] [&_p_i]:mr-1.5"},y={class:"btn-group mt-12.5"},v=n((j=((e,t)=>{for(var s in t||(t={}))a.call(t,s)&&p(e,s,t[s]);if(r)for(var s of r(t))o.call(t,s)&&p(e,s,t[s]);return e})({},{name:"ArtResultPage"}),t(j,s({__name:"ArtResultPage",props:{type:{default:"success"},title:{default:""},message:{default:""},iconCode:{default:""}},setup:e=>(t,s)=>{const r=l;return m(),c("div",{class:u(["page-content box-border !px-20 py-3.5 text-center max-md:!px-5",e.type])},[i(r,{class:u(["icon size-22 p-2 mt-16 block rounded-full !text-white","success"===e.type?"bg-[#19BE6B]":"bg-[#ED4014]"]),icon:e.iconCode},null,8,["icon","class"]),d("h1",g,x(e.title),1),d("p",f,x(e.message),1),d("div",_,[b(t.$slots,"content")]),d("div",y,[b(t.$slots,"buttons")])],2)}}))));var j;export{v as _};

View File

@ -0,0 +1 @@
.unit[data-v-47e87be5]{margin-left:8px;color:#909399}.form-tip[data-v-47e87be5]{margin-top:4px;font-size:12px;color:#909399;line-height:1.4}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.effect-manager[data-v-f2f200b4]{padding:0}.toolbar[data-v-f2f200b4]{margin-bottom:16px;display:flex;gap:12px}.params-view[data-v-f2f200b4]{display:flex;flex-wrap:wrap;gap:4px}.params-view .el-tag[data-v-f2f200b4]{margin:2px}

View File

@ -1 +1 @@
var e=Object.defineProperty,r=Object.defineProperties,a=Object.getOwnPropertyDescriptors,t=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,n=Object.prototype.propertyIsEnumerable,s=(r,a,t)=>a in r?e(r,a,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[a]=t;import{d as l,a as f,o as c,aU as i,aD as u,J as p,aV as b,u as m,e as d,h as v,f as y}from"./index-ip5gDR6L.js";const O={class:"box-border w-full h-full"},h=["src"],j=l((w=((e,r)=>{for(var a in r||(r={}))o.call(r,a)&&s(e,a,r[a]);if(t)for(var a of t(r))n.call(r,a)&&s(e,a,r[a]);return e})({},{name:"IframeView"}),r(w,a({__name:"Iframe",setup(e){const r=u(),a=f(!0),t=f(""),o=f(null);c(()=>{const e=i.getInstance().findByPath(r.path);(null==e?void 0:e.meta)&&(t.value=e.meta.link||"")});const n=()=>{a.value=!1};return(e,r)=>{const s=b;return p((y(),d("div",O,[v("iframe",{ref_key:"iframeRef",ref:o,src:m(t),frameborder:"0",class:"w-full h-full min-h-[calc(100vh-120px)] border-none",onLoad:n},null,40,h)])),[[s,m(a)]])}}}))));var w;export{j as default};
var e=Object.defineProperty,r=Object.defineProperties,a=Object.getOwnPropertyDescriptors,t=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,n=Object.prototype.propertyIsEnumerable,s=(r,a,t)=>a in r?e(r,a,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[a]=t;import{d as l,a as f,o as c,aU as i,aD as u,J as p,aV as b,u as m,e as d,h as v,f as y}from"./index-DBHnxsy6.js";const O={class:"box-border w-full h-full"},h=["src"],j=l((w=((e,r)=>{for(var a in r||(r={}))o.call(r,a)&&s(e,a,r[a]);if(t)for(var a of t(r))n.call(r,a)&&s(e,a,r[a]);return e})({},{name:"IframeView"}),r(w,a({__name:"Iframe",setup(e){const r=u(),a=f(!0),t=f(""),o=f(null);c(()=>{const e=i.getInstance().findByPath(r.path);(null==e?void 0:e.meta)&&(t.value=e.meta.link||"")});const n=()=>{a.value=!1};return(e,r)=>{const s=b;return p((y(),d("div",O,[v("iframe",{ref_key:"iframeRef",ref:o,src:m(t),frameborder:"0",class:"w-full h-full min-h-[calc(100vh-120px)] border-none",onLoad:n},null,40,h)])),[[s,m(a)]])}}}))));var w;export{j as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.json-editor[data-v-cf7dfd13]{position:relative}.json-editor .el-button[data-v-cf7dfd13]{position:absolute;right:10px;bottom:10px}

View File

@ -0,0 +1 @@
.user-assignment[data-v-dcfb44a3]{display:flex;flex-direction:column;gap:16px;max-height:70vh;overflow-y:auto}.search-card[data-v-dcfb44a3]{margin-bottom:0}.search-form[data-v-dcfb44a3]{display:flex;flex-wrap:wrap;gap:8px}.card-header[data-v-dcfb44a3]{display:flex;justify-content:space-between;align-items:center}.header-actions[data-v-dcfb44a3]{display:flex;align-items:center;gap:12px}.title-tags[data-v-dcfb44a3]{display:flex;flex-wrap:wrap;gap:4px}.title-icon[data-v-dcfb44a3]{margin-left:2px}.pagination-container[data-v-dcfb44a3]{margin-top:16px;display:flex;justify-content:center}.assignment-config-card[data-v-dcfb44a3]{background-color:#f5f7fa}.unit[data-v-dcfb44a3]{margin-left:8px;color:#909399}.dialog-footer[data-v-dcfb44a3]{display:flex;justify-content:flex-end;gap:12px}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
import{bk as n,cz as r,bi as t,b8 as u,cA as i,c7 as e,bx as o,ca as f,bm as a}from"./index-DBHnxsy6.js";import{h as c,i as s}from"./index-nZra831X.js";function v(n){return n==n&&!t(n)}function l(n,r){return function(t){return null!=t&&(t[n]===r&&(void 0!==r||n in Object(t)))}}function b(t){var i=function(n){for(var r=u(n),t=r.length;t--;){var i=r[t],e=n[i];r[t]=[i,e,v(e)]}return r}(t);return 1==i.length&&i[0][2]?l(i[0][0],i[0][1]):function(u){return u===t||function(t,u,i,e){var o=i.length,f=o;if(null==t)return!f;for(t=Object(t);o--;){var a=i[o];if(a[2]?a[1]!==t[a[0]]:!(a[0]in t))return!1}for(;++o<f;){var c=(a=i[o])[0],s=t[c],v=a[1];if(a[2]){if(void 0===s&&!(c in t))return!1}else{var l=new n;if(!r(v,s,3,e,l))return!1}}return!0}(u,0,i)}}function d(n){return i(n)?(r=e(n),function(n){return null==n?void 0:n[r]}):function(n){return function(r){return f(r,n)}}(n);var r}function j(n){return"function"==typeof n?n:null==n?s:"object"==typeof n?a(n)?(t=n[0],u=n[1],i(t)&&v(u)?l(e(t),u):function(n){var i=o(n,t);return void 0===i&&i===u?c(n,t):r(u,i,3)}):b(n):d(n);var t,u}export{j as b};

View File

@ -1 +1 @@
import{bn as r,cw as t,bs as e,cx as n,cy as o,cz as c,cA as u,cv as a,cB as f}from"./index-ip5gDR6L.js";var s=Object.create,i=function(){function t(){}return function(e){if(!r(e))return{};if(s)return s(e);t.prototype=e;var n=new t;return t.prototype=void 0,n}}();function p(r,t){var e=-1,n=r.length;for(t||(t=Array(n));++e<n;)t[e]=r[e];return t}function v(r,n,o,c){var u=!o;o||(o={});for(var a=-1,f=n.length;++a<f;){var s=n[a],i=void 0;void 0===i&&(i=r[s]),u?t(o,s,i):e(o,s,i)}return o}var l=Object.prototype.hasOwnProperty;function y(t){if(!r(t))return function(r){var t=[];if(null!=r)for(var e in Object(r))t.push(e);return t}(t);var e=n(t),o=[];for(var c in t)("constructor"!=c||!e&&l.call(t,c))&&o.push(c);return o}function b(r){return c(r)?o(r,!0):y(r)}var d=u(Object.getPrototypeOf,Object),h="object"==typeof exports&&exports&&!exports.nodeType&&exports,j=h&&"object"==typeof module&&module&&!module.nodeType&&module,w=j&&j.exports===h?a.Buffer:void 0,x=w?w.allocUnsafe:void 0;function O(r,t){if(t)return r.slice();var e=r.length,n=x?x(e):new r.constructor(e);return r.copy(n),n}function g(r){var t=new r.constructor(r.byteLength);return new f(t).set(new f(r)),t}function m(r,t){var e=t?g(r.buffer):r.buffer;return new r.constructor(e,r.byteOffset,r.length)}function A(r){return"function"!=typeof r.constructor||n(r)?{}:i(d(r))}export{g as a,m as b,v as c,O as d,p as e,d as g,A as i,b as k};
import{bi as r,ct as t,bn as e,cu as n,cv as o,cw as u,cx as c,cs as a,cy as f}from"./index-DBHnxsy6.js";var s=Object.create,i=function(){function t(){}return function(e){if(!r(e))return{};if(s)return s(e);t.prototype=e;var n=new t;return t.prototype=void 0,n}}();function p(r,t){var e=-1,n=r.length;for(t||(t=Array(n));++e<n;)t[e]=r[e];return t}function v(r,n,o,u){var c=!o;o||(o={});for(var a=-1,f=n.length;++a<f;){var s=n[a],i=void 0;void 0===i&&(i=r[s]),c?t(o,s,i):e(o,s,i)}return o}var l=Object.prototype.hasOwnProperty;function y(t){if(!r(t))return function(r){var t=[];if(null!=r)for(var e in Object(r))t.push(e);return t}(t);var e=n(t),o=[];for(var u in t)("constructor"!=u||!e&&l.call(t,u))&&o.push(u);return o}function b(r){return u(r)?o(r,!0):y(r)}var d=c(Object.getPrototypeOf,Object),h="object"==typeof exports&&exports&&!exports.nodeType&&exports,j=h&&"object"==typeof module&&module&&!module.nodeType&&module,w=j&&j.exports===h?a.Buffer:void 0,x=w?w.allocUnsafe:void 0;function O(r,t){if(t)return r.slice();var e=r.length,n=x?x(e):new r.constructor(e);return r.copy(n),n}function g(r){var t=new r.constructor(r.byteLength);return new f(t).set(new f(r)),t}function m(r,t){var e=t?g(r.buffer):r.buffer;return new r.constructor(e,r.byteOffset,r.length)}function P(r){return"function"!=typeof r.constructor||n(r)?{}:i(d(r))}export{g as a,m as b,v as c,O as d,p as e,d as g,P as i,b as k};

View File

@ -0,0 +1 @@
import{_ as e}from"./active-user.vue_vue_type_script_setup_true_lang-19Sadsu3.js";import"./index-DBHnxsy6.js";import"./useChart-MKcUOr4d.js";import"./operations-CsHdqaV1.js";/* empty css */export{e as default};

View File

@ -1 +0,0 @@
import{_ as e}from"./active-user.vue_vue_type_script_setup_true_lang-DtO48xU9.js";import"./index-ip5gDR6L.js";import"./useChart-DqpWZdD6.js";export{e as default};

View File

@ -0,0 +1 @@
var e=Object.defineProperty,t=Object.defineProperties,a=Object.getOwnPropertyDescriptors,s=Object.getOwnPropertySymbols,r=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,i=(t,a,s)=>a in t?e(t,a,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[a]=s,l=(e,t)=>{for(var a in t||(t={}))r.call(t,a)&&i(e,a,t[a]);if(s)for(var a of s(t))o.call(t,a)&&i(e,a,t[a]);return e};import{d as n,c as d,J as c,aV as u,e as p,f as m,g as h,H as y,a as f,r as g,p as x,S as b,h as v,F as w,G as A,m as L,O as S}from"./index-DBHnxsy6.js";import{u as j,a as k,g as O}from"./useChart-MKcUOr4d.js";import{f as P}from"./operations-CsHdqaV1.js";/* empty css */const B=n((W=l({},{name:"ArtBarChart"}),D={__name:"index",props:{data:{default:()=>[0,0,0,0,0,0,0]},xAxisData:{default:()=>[]},barWidth:{default:"40%"},stack:{type:Boolean,default:!1},borderRadius:{default:4},height:{default:j().chartHeight},loading:{type:Boolean,default:!1},isEmpty:{type:Boolean,default:!1},colors:{default:()=>j().colors},showAxisLabel:{type:Boolean,default:!0},showAxisLine:{type:Boolean,default:!0},showSplitLine:{type:Boolean,default:!0},showTooltip:{type:Boolean,default:!0},showLegend:{type:Boolean,default:!1},legendPosition:{default:"bottom"}},setup(e){const t=e,a=d(()=>Array.isArray(t.data)&&t.data.length>0&&"object"==typeof t.data[0]&&"name"in t.data[0]),s=(e,a)=>e||(void 0!==a?t.colors[a%t.colors.length]:new O.LinearGradient(0,0,0,1,[{offset:0,color:y("--el-color-primary-light-4")},{offset:1,color:y("--el-color-primary")}])),r=e=>new O.LinearGradient(0,0,0,1,[{offset:0,color:e},{offset:1,color:e}]),o=e=>{const a=b();return l({name:e.name,data:e.data,type:"bar",stack:e.stack,itemStyle:(s=e.color,{borderRadius:t.borderRadius,color:"string"==typeof s?r(s):s}),barWidth:e.barWidth||t.barWidth},a);var s},{chartRef:i,getAxisLineStyle:n,getAxisLabelStyle:f,getAxisTickStyle:g,getSplitLineStyle:x,getAnimationConfig:b,getTooltipStyle:v,getLegendStyle:w,getGridWithLegend:A}=k({props:t,checkEmpty:()=>{if(Array.isArray(t.data)&&"number"==typeof t.data[0]){const e=t.data;return!e.length||e.every(e=>0===e)}if(Array.isArray(t.data)&&"object"==typeof t.data[0]){const e=t.data;return!e.length||e.every(e=>{var t;return!(null==(t=e.data)?void 0:t.length)||e.data.every(e=>0===e)})}return!0},watchSources:[()=>t.data,()=>t.xAxisData,()=>t.colors],generateOptions:()=>{const e={grid:A(t.showLegend&&a.value,t.legendPosition,{top:15,right:0,left:0}),tooltip:t.showTooltip?v():void 0,xAxis:{type:"category",data:t.xAxisData,axisTick:g(),axisLine:n(t.showAxisLine),axisLabel:f(t.showAxisLabel)},yAxis:{type:"value",axisLabel:f(t.showAxisLabel),axisLine:n(t.showAxisLine),splitLine:x(t.showSplitLine)}};if(t.showLegend&&a.value&&(e.legend=w(t.legendPosition)),a.value){const a=t.data;e.series=a.map((e,a)=>{const r=s(t.colors[a],a);return o({name:e.name,data:e.data,color:r,barWidth:e.barWidth,stack:t.stack?e.stack||"total":void 0})})}else{const a=t.data,r=s();e.series=[o({data:a,color:r})]}return e}});return(e,a)=>{const s=u;return c((m(),p("div",{ref_key:"chartRef",ref:i,style:h({height:t.height})},null,4)),[[s,t.loading]])}}},t(W,a(D))));var W,D;const T={class:"art-card h-105 p-4 box-border mb-5 max-sm:mb-4"},_={class:"flex-b mt-2"},G={class:"text-2xl text-g-900"},R={class:"text-xs text-g-500"},C=n({__name:"active-user",setup(e){const t=f([]),a=f([]),s=g([{name:"总用户量",num:"0"},{name:"总访问量",num:"0"},{name:"日访问量",num:"0"},{name:"周同比",num:"+0%"}]);return(()=>{return e=this,r=null,o=function*(){try{const e=yield P("30d");t.value=e.chart.map(e=>e.date.slice(5)),a.value=e.chart.map(e=>e.value),s[0].num=String(e.metrics.totalUsers),s[1].num=String(e.metrics.totalVisits),s[2].num=String(e.metrics.dailyVisits),s[3].num=e.metrics.weeklyGrowth}catch(e){S.error("加载用户概述失败")}},new Promise((t,a)=>{var s=e=>{try{l(o.next(e))}catch(t){a(t)}},i=e=>{try{l(o.throw(e))}catch(t){a(t)}},l=e=>e.done?t(e.value):Promise.resolve(e.value).then(s,i);l((o=o.apply(e,r)).next())});var e,r,o})(),(e,r)=>{const o=B;return m(),p("div",T,[x(o,{class:"box-border p-2",barWidth:"50%",height:"13.7rem",showAxisLine:!1,data:a.value,xAxisData:t.value},null,8,["data","xAxisData"]),r[0]||(r[0]=b('<div class="ml-1"><h3 class="mt-5 text-lg font-medium">用户概述</h3><p class="mt-1 text-sm">比上周 <span class="text-success font-medium">+23%</span></p><p class="mt-1 text-sm">我们为您创建了多个选项,可将它们组合在一起并定制为像素完美的页面</p></div>',1)),v("div",_,[(m(!0),p(w,null,A(s,(e,t)=>(m(),p("div",{class:"flex-1",key:t},[v("p",G,L(e.num),1),v("p",R,L(e.name),1)]))),128))])])}}});export{C as _};

View File

@ -1 +0,0 @@
var e=Object.defineProperty,t=Object.defineProperties,a=Object.getOwnPropertyDescriptors,s=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,i=(t,a,s)=>a in t?e(t,a,{enumerable:!0,configurable:!0,writable:!0,value:s}):t[a]=s,l=(e,t)=>{for(var a in t||(t={}))o.call(t,a)&&i(e,a,t[a]);if(s)for(var a of s(t))r.call(t,a)&&i(e,a,t[a]);return e};import{d as n,c as d,J as c,aV as p,e as u,f as m,g as f,H as h,p as y,S as g,h as x,F as b,G as v,m as A}from"./index-ip5gDR6L.js";import{u as L,a as w,g as k}from"./useChart-DqpWZdD6.js";const S=n((j=l({},{name:"ArtBarChart"}),O={__name:"index",props:{data:{default:()=>[0,0,0,0,0,0,0]},xAxisData:{default:()=>[]},barWidth:{default:"40%"},stack:{type:Boolean,default:!1},borderRadius:{default:4},height:{default:L().chartHeight},loading:{type:Boolean,default:!1},isEmpty:{type:Boolean,default:!1},colors:{default:()=>L().colors},showAxisLabel:{type:Boolean,default:!0},showAxisLine:{type:Boolean,default:!0},showSplitLine:{type:Boolean,default:!0},showTooltip:{type:Boolean,default:!0},showLegend:{type:Boolean,default:!1},legendPosition:{default:"bottom"}},setup(e){const t=e,a=d(()=>Array.isArray(t.data)&&t.data.length>0&&"object"==typeof t.data[0]&&"name"in t.data[0]),s=(e,a)=>e||(void 0!==a?t.colors[a%t.colors.length]:new k.LinearGradient(0,0,0,1,[{offset:0,color:h("--el-color-primary-light-4")},{offset:1,color:h("--el-color-primary")}])),o=e=>new k.LinearGradient(0,0,0,1,[{offset:0,color:e},{offset:1,color:e}]),r=e=>{const a=b();return l({name:e.name,data:e.data,type:"bar",stack:e.stack,itemStyle:(s=e.color,{borderRadius:t.borderRadius,color:"string"==typeof s?o(s):s}),barWidth:e.barWidth||t.barWidth},a);var s},{chartRef:i,getAxisLineStyle:n,getAxisLabelStyle:y,getAxisTickStyle:g,getSplitLineStyle:x,getAnimationConfig:b,getTooltipStyle:v,getLegendStyle:A,getGridWithLegend:L}=w({props:t,checkEmpty:()=>{if(Array.isArray(t.data)&&"number"==typeof t.data[0]){const e=t.data;return!e.length||e.every(e=>0===e)}if(Array.isArray(t.data)&&"object"==typeof t.data[0]){const e=t.data;return!e.length||e.every(e=>{var t;return!(null==(t=e.data)?void 0:t.length)||e.data.every(e=>0===e)})}return!0},watchSources:[()=>t.data,()=>t.xAxisData,()=>t.colors],generateOptions:()=>{const e={grid:L(t.showLegend&&a.value,t.legendPosition,{top:15,right:0,left:0}),tooltip:t.showTooltip?v():void 0,xAxis:{type:"category",data:t.xAxisData,axisTick:g(),axisLine:n(t.showAxisLine),axisLabel:y(t.showAxisLabel)},yAxis:{type:"value",axisLabel:y(t.showAxisLabel),axisLine:n(t.showAxisLine),splitLine:x(t.showSplitLine)}};if(t.showLegend&&a.value&&(e.legend=A(t.legendPosition)),a.value){const a=t.data;e.series=a.map((e,a)=>{const o=s(t.colors[a],a);return r({name:e.name,data:e.data,color:o,barWidth:e.barWidth,stack:t.stack?e.stack||"total":void 0})})}else{const a=t.data,o=s();e.series=[r({data:a,color:o})]}return e}});return(e,a)=>{const s=p;return c((m(),u("div",{ref_key:"chartRef",ref:i,style:f({height:t.height})},null,4)),[[s,t.loading]])}}},t(j,a(O))));var j,O;const B={class:"art-card h-105 p-4 box-border mb-5 max-sm:mb-4"},P={class:"flex-b mt-2"},W={class:"text-2xl text-g-900"},_={class:"text-xs text-g-500"},D=n({__name:"active-user",setup(e){const t=["1月","2月","3月","4月","5月","6月","7月","8月","9月"],a=[160,100,150,80,190,100,175,120,160],s=[{name:"总用户量",num:"32k"},{name:"总访问量",num:"128k"},{name:"日访问量",num:"1.2k"},{name:"周同比",num:"+5%"}];return(e,o)=>{const r=S;return m(),u("div",B,[y(r,{class:"box-border p-2",barWidth:"50%",height:"13.7rem",showAxisLine:!1,data:a,xAxisData:t}),o[0]||(o[0]=g('<div class="ml-1"><h3 class="mt-5 text-lg font-medium">用户概述</h3><p class="mt-1 text-sm">比上周 <span class="text-success font-medium">+23%</span></p><p class="mt-1 text-sm">我们为您创建了多个选项,可将它们组合在一起并定制为像素完美的页面</p></div>',1)),x("div",P,[(m(),u(b,null,v(s,(e,t)=>x("div",{class:"flex-1",key:t},[x("p",W,A(e.num),1),x("p",_,A(e.name),1)])),64))])])}}});export{D as _};

View File

@ -1 +1 @@
var e=Object.defineProperty,r=Object.getOwnPropertySymbols,t=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable,o=(r,t,a)=>t in r?e(r,t,{enumerable:!0,configurable:!0,writable:!0,value:a}):r[t]=a;import{b5 as s}from"./index-ip5gDR6L.js";function n(e){return n=this,i=null,c=function*(){const n=((e,s)=>{for(var n in s||(s={}))t.call(s,n)&&o(e,n,s[n]);if(r)for(var n of r(s))a.call(s,n)&&o(e,n,s[n]);return e})({page:1,page_size:20},e||{});try{const e=yield s.get({url:"app/activities",params:n,showErrorMessage:!1});return{records:e.list.map(e=>({id:e.id,name:e.name,categoryName:e.category_name,status:e.status,priceDraw:e.price_draw,isBoss:e.is_boss})),total:e.total,current:e.page,size:e.page_size}}catch(i){return{records:[],total:0,current:n.page,size:n.page_size}}},new Promise((e,r)=>{var t=e=>{try{o(c.next(e))}catch(t){r(t)}},a=e=>{try{o(c.throw(e))}catch(t){r(t)}},o=r=>r.done?e(r.value):Promise.resolve(r.value).then(t,a);o((c=c.apply(n,i)).next())});var n,i,c}export{n as f};
var e=Object.defineProperty,r=Object.getOwnPropertySymbols,t=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable,o=(r,t,a)=>t in r?e(r,t,{enumerable:!0,configurable:!0,writable:!0,value:a}):r[t]=a;import{b4 as s}from"./index-DBHnxsy6.js";function n(e){return n=this,i=null,c=function*(){const n=((e,s)=>{for(var n in s||(s={}))t.call(s,n)&&o(e,n,s[n]);if(r)for(var n of r(s))a.call(s,n)&&o(e,n,s[n]);return e})({page:1,page_size:20},e||{});try{const e=yield s.get({url:"app/activities",params:n,showErrorMessage:!1});return{records:e.list.map(e=>({id:e.id,name:e.name,categoryName:e.category_name,status:e.status,priceDraw:e.price_draw,isBoss:e.is_boss})),total:e.total,current:e.page,size:e.page_size}}catch(i){return{records:[],total:0,current:n.page,size:n.page_size}}},new Promise((e,r)=>{var t=e=>{try{o(c.next(e))}catch(t){r(t)}},a=e=>{try{o(c.throw(e))}catch(t){r(t)}},o=r=>r.done?e(r.value):Promise.resolve(r.value).then(t,a);o((c=c.apply(n,i)).next())});var n,i,c}export{n as f};

View File

@ -1 +1 @@
import{d as t,a as e,r as l,o as a,e as s,f as r,h as o,m as n,F as i,G as c,l as d,i as g,p as x,z as v,n as f}from"./index-ip5gDR6L.js";/* empty css */import{f as p,a as u}from"./operations-Cj-RMt0B.js";import{E as m}from"./index-D1xXI5gl.js";import"./use-form-common-props-BwBs_jod.js";const b={class:"art-card h-140 p-5 mb-5 max-sm:mb-4"},h={class:"h-[calc(100%-40px)]"},y={class:"grid grid-cols-4 gap-4 mb-6"},w={class:"text-center p-3 bg-blue-50 rounded-lg"},C={class:"text-2xl font-bold text-blue-600"},R={class:"text-center p-3 bg-green-50 rounded-lg"},S={class:"text-2xl font-bold text-green-600"},j={class:"text-center p-3 bg-yellow-50 rounded-lg"},P={class:"text-2xl font-bold text-yellow-600"},$={class:"text-center p-3 bg-purple-50 rounded-lg"},k={class:"text-2xl font-bold text-purple-600"},T={class:"h-60 mb-4"},_={class:"overflow-auto"},A={class:"w-full text-sm"},F={class:"py-2"},W={class:"flex items-center"},z={class:"py-2"},E={class:"py-2"},G={class:"py-2"},L={class:"py-2"},N=t({__name:"activity-lottery",setup(t){const N=e(),B=l({totalActivities:0,totalParticipants:0,totalDraws:0,winnerCount:0,overallWinRate:0,costControl:0}),D=l([]),M=e(!1),O=t=>t.winRate>2?"success":t.winRate>1?"warning":"info",q=t=>t.winRate>2?"高中奖率":t.winRate>1?"中等中奖率":"低中奖率",H=(t,e)=>({1:`rgba(251, 191, 36, ${e})`,2:`rgba(156, 163, 175, ${e})`,3:`rgba(251, 146, 60, ${e})`,4:`rgba(96, 165, 250, ${e})`,5:`rgba(52, 211, 153, ${e})`}[t]||`rgba(156, 163, 175, ${e})`),I=()=>{return t=this,e=null,l=function*(){M.value=!0;try{const[t,e]=yield Promise.all([p("7d"),u("7d")]);Object.assign(B,t),D.splice(0,D.length,...e),f(()=>{(()=>{if(!N.value||0===D.length)return;const t=N.value,e=t.getContext("2d");if(!e)return;e.clearRect(0,0,t.width,t.height);const l=40,a=t.width-80,s=t.height-80,r=Math.max(...D.map(t=>t.winnerCount)),o=a/D.length*.6,n=a/D.length*.4;D.forEach((t,a)=>{const i=l+a*(o+n)+n/2,c=t.winnerCount/r*s,d=l+s-c,g=e.createLinearGradient(i,d+c,i,d);g.addColorStop(0,H(t.level,.8)),g.addColorStop(1,H(t.level,1)),e.fillStyle=g,e.fillRect(i,d,o,c),e.fillStyle="#333",e.font="12px sans-serif",e.textAlign="center",e.fillText(t.winnerCount.toString(),i+o/2,d-5),e.fillText(t.levelName,i+o/2,l+s+20)}),e.strokeStyle="#e0e0e0",e.lineWidth=1,e.beginPath(),e.moveTo(l,l),e.lineTo(l,l+s),e.lineTo(l+a,l+s),e.stroke()})()})}catch(t){}finally{M.value=!1}},new Promise((a,s)=>{var r=t=>{try{n(l.next(t))}catch(e){s(e)}},o=t=>{try{n(l.throw(t))}catch(e){s(e)}},n=t=>t.done?a(t.value):Promise.resolve(t.value).then(r,o);n((l=l.apply(t,e)).next())});var t,e,l};return a(()=>{I()}),(t,e)=>{const l=m;return r(),s("div",b,[e[5]||(e[5]=o("div",{class:"art-card-header"},[o("div",{class:"title"},[o("h4",null,"活动抽奖效果分析"),o("p",null,"优化中奖概率,控制活动成本")])],-1)),o("div",h,[o("div",y,[o("div",w,[o("div",C,n(B.totalActivities),1),e[0]||(e[0]=o("div",{class:"text-sm text-g-500"},"活动总数",-1))]),o("div",R,[o("div",S,n((a=B.totalParticipants,a>=1e4?(a/1e4).toFixed(1)+"w":a>=1e3?(a/1e3).toFixed(1)+"k":a.toString())),1),e[1]||(e[1]=o("div",{class:"text-sm text-g-500"},"参与人数",-1))]),o("div",j,[o("div",P,n(B.overallWinRate)+"%",1),e[2]||(e[2]=o("div",{class:"text-sm text-g-500"},"整体中奖率",-1))]),o("div",$,[o("div",k,n(B.costControl)+"%",1),e[3]||(e[3]=o("div",{class:"text-sm text-g-500"},"成本控制",-1))])]),o("div",T,[o("canvas",{ref_key:"chartRef",ref:N,width:"400",height:"240"},null,512)]),o("div",_,[o("table",A,[e[4]||(e[4]=o("thead",null,[o("tr",{class:"border-b border-g-200"},[o("th",{class:"text-left py-2"},"奖级"),o("th",{class:"text-left py-2"},"中奖人数"),o("th",{class:"text-left py-2"},"中奖率"),o("th",{class:"text-left py-2"},"成本"),o("th",{class:"text-left py-2"},"状态")])],-1)),o("tbody",null,[(r(!0),s(i,null,c(D,t=>{return r(),s("tr",{key:t.level,class:"border-b border-g-100 hover:bg-g-50"},[o("td",F,[o("div",W,[o("span",{class:g(["w-6 h-6 rounded-full flex items-center justify-center text-white text-xs font-bold mr-2",(e=t.level,{1:"bg-gradient-to-r from-yellow-400 to-yellow-300",2:"bg-gradient-to-r from-gray-400 to-gray-300",3:"bg-gradient-to-r from-orange-400 to-orange-300",4:"bg-gradient-to-r from-blue-400 to-blue-300",5:"bg-gradient-to-r from-green-400 to-green-300"}[e]||"bg-gradient-to-r from-gray-400 to-gray-300")])},n(t.level),3),d(" "+n(t.levelName),1)])]),o("td",z,n(t.winnerCount)+"人",1),o("td",E,n(t.winRate)+"%",1),o("td",G,"¥"+n(t.cost.toLocaleString()),1),o("td",L,[x(l,{type:O(t),size:"small"},{default:v(()=>[d(n(q(t)),1)]),_:2},1032,["type"])])]);var e}),128))])])])])]);var a}}});export{N as default};
import{d as t,a as e,r as l,o as a,e as s,f as r,h as o,m as n,F as i,G as c,l as d,i as g,p as x,z as v,n as f}from"./index-DBHnxsy6.js";/* empty css */import{a as p,b as u}from"./operations-CsHdqaV1.js";import{E as m}from"./index-D2YpA_om.js";import"./use-form-common-props-BOfz9xGR.js";const b={class:"art-card h-140 p-5 mb-5 max-sm:mb-4"},h={class:"h-[calc(100%-40px)]"},y={class:"grid grid-cols-4 gap-4 mb-6"},w={class:"text-center p-3 bg-blue-50 rounded-lg"},C={class:"text-2xl font-bold text-blue-600"},R={class:"text-center p-3 bg-green-50 rounded-lg"},S={class:"text-2xl font-bold text-green-600"},j={class:"text-center p-3 bg-yellow-50 rounded-lg"},P={class:"text-2xl font-bold text-yellow-600"},$={class:"text-center p-3 bg-purple-50 rounded-lg"},k={class:"text-2xl font-bold text-purple-600"},T={class:"h-60 mb-4"},_={class:"overflow-auto"},A={class:"w-full text-sm"},E={class:"py-2"},F={class:"flex items-center"},W={class:"py-2"},z={class:"py-2"},G={class:"py-2"},L={class:"py-2"},N=t({__name:"activity-lottery",setup(t){const N=e(),D=l({totalActivities:0,totalParticipants:0,totalDraws:0,winnerCount:0,overallWinRate:0,costControl:0}),M=l([]),O=e(!1),U=t=>t.winRate>2?"success":t.winRate>1?"warning":"info",q=t=>t.winRate>2?"高中奖率":t.winRate>1?"中等中奖率":"低中奖率",B=(t,e)=>({1:`rgba(251, 191, 36, ${e})`,2:`rgba(156, 163, 175, ${e})`,3:`rgba(251, 146, 60, ${e})`,4:`rgba(96, 165, 250, ${e})`,5:`rgba(52, 211, 153, ${e})`}[t]||`rgba(156, 163, 175, ${e})`),H=()=>{return t=this,e=null,l=function*(){O.value=!0;try{const[t,e]=yield Promise.all([p("7d"),u("7d")]);Object.assign(D,t),M.splice(0,M.length,...e),f(()=>{(()=>{if(!N.value||0===M.length)return;const t=N.value,e=t.getContext("2d");if(!e)return;e.clearRect(0,0,t.width,t.height);const l=40,a=t.width-80,s=t.height-80,r=Math.max(...M.map(t=>t.winnerCount)),o=a/M.length*.6,n=a/M.length*.4;M.forEach((t,a)=>{const i=l+a*(o+n)+n/2,c=t.winnerCount/r*s,d=l+s-c,g=e.createLinearGradient(i,d+c,i,d);g.addColorStop(0,B(t.level,.8)),g.addColorStop(1,B(t.level,1)),e.fillStyle=g,e.fillRect(i,d,o,c),e.fillStyle="#333",e.font="12px sans-serif",e.textAlign="center",e.fillText(t.winnerCount.toString(),i+o/2,d-5),e.fillText(t.levelName,i+o/2,l+s+20)}),e.strokeStyle="#e0e0e0",e.lineWidth=1,e.beginPath(),e.moveTo(l,l),e.lineTo(l,l+s),e.lineTo(l+a,l+s),e.stroke()})()})}catch(t){}finally{O.value=!1}},new Promise((a,s)=>{var r=t=>{try{n(l.next(t))}catch(e){s(e)}},o=t=>{try{n(l.throw(t))}catch(e){s(e)}},n=t=>t.done?a(t.value):Promise.resolve(t.value).then(r,o);n((l=l.apply(t,e)).next())});var t,e,l};return a(()=>{H()}),(t,e)=>{const l=m;return r(),s("div",b,[e[5]||(e[5]=o("div",{class:"art-card-header"},[o("div",{class:"title"},[o("h4",null,"活动抽奖效果分析"),o("p",null,"优化中奖概率,控制活动成本")])],-1)),o("div",h,[o("div",y,[o("div",w,[o("div",C,n(D.totalActivities),1),e[0]||(e[0]=o("div",{class:"text-sm text-g-500"},"活动总数",-1))]),o("div",R,[o("div",S,n((a=D.totalParticipants,a>=1e4?(a/1e4).toFixed(1)+"w":a>=1e3?(a/1e3).toFixed(1)+"k":a.toString())),1),e[1]||(e[1]=o("div",{class:"text-sm text-g-500"},"参与人数",-1))]),o("div",j,[o("div",P,n(D.overallWinRate)+"%",1),e[2]||(e[2]=o("div",{class:"text-sm text-g-500"},"整体中奖率",-1))]),o("div",$,[o("div",k,n(D.costControl)+"%",1),e[3]||(e[3]=o("div",{class:"text-sm text-g-500"},"成本控制",-1))])]),o("div",T,[o("canvas",{ref_key:"chartRef",ref:N,width:"400",height:"240"},null,512)]),o("div",_,[o("table",A,[e[4]||(e[4]=o("thead",null,[o("tr",{class:"border-b border-g-200"},[o("th",{class:"text-left py-2"},"奖级"),o("th",{class:"text-left py-2"},"中奖人数"),o("th",{class:"text-left py-2"},"中奖率"),o("th",{class:"text-left py-2"},"成本"),o("th",{class:"text-left py-2"},"状态")])],-1)),o("tbody",null,[(r(!0),s(i,null,c(M,t=>{return r(),s("tr",{key:t.level,class:"border-b border-g-100 hover:bg-g-50"},[o("td",E,[o("div",F,[o("span",{class:g(["w-6 h-6 rounded-full flex items-center justify-center text-white text-xs font-bold mr-2",(e=t.level,{1:"bg-gradient-to-r from-yellow-400 to-yellow-300",2:"bg-gradient-to-r from-gray-400 to-gray-300",3:"bg-gradient-to-r from-orange-400 to-orange-300",4:"bg-gradient-to-r from-blue-400 to-blue-300",5:"bg-gradient-to-r from-green-400 to-green-300"}[e]||"bg-gradient-to-r from-gray-400 to-gray-300")])},n(t.level),3),d(" "+n(t.levelName),1)])]),o("td",W,n(t.winnerCount)+"人",1),o("td",z,n(t.winRate)+"%",1),o("td",G,"¥"+n(t.cost.toLocaleString()),1),o("td",L,[x(l,{type:U(t),size:"small"},{default:v(()=>[d(n(q(t)),1)]),_:2},1032,["type"])])]);var e}),128))])])])])]);var a}}});export{N as default};

View File

@ -1 +0,0 @@
import{_ as i}from"./activity-prize-analysis.vue_vue_type_script_setup_true_lang-D1MExuFt.js";import"./index-ip5gDR6L.js";import"./el-progress-DSvqWOWu.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./operations-Cj-RMt0B.js";import"./index-Br3btmwp.js";import"./index-C9KXwj2c.js";import"./index-fnD_mPvE.js";import"./index-CHAf6FpU.js";import"./use-form-item-BDb3ZgPk.js";import"./use-form-common-props-BwBs_jod.js";import"./index-DbRz3Zuu.js";import"./index-D1xXI5gl.js";import"./token-DWNpOE8r.js";import"./index-D-10FtUD.js";import"./scroll-ChxXkPhM.js";import"./debounce-B9s63_mG.js";import"./index-Jz5GfFad.js";import"./vnode-6FKeug3R.js";export{i as default};

View File

@ -0,0 +1 @@
import{_ as o}from"./activity-prize-analysis.vue_vue_type_script_setup_true_lang-bThIk24K.js";import"./index-DBHnxsy6.js";import"./el-progress-DcK0UPpw.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./operations-CsHdqaV1.js";import"./index-CHZ9Ls9_.js";import"./index-B99ckkYy.js";import"./index-nZra831X.js";import"./index-MOpuiu9Z.js";import"./use-form-item-ByasanrX.js";import"./use-form-common-props-BOfz9xGR.js";import"./index-sqhzrW2O.js";import"./index-D2YpA_om.js";import"./token-DWNpOE8r.js";import"./index-DZO7eApA.js";import"./scroll-Cceui3yC.js";import"./debounce-CkazOn_9.js";import"./_baseIteratee-DRiXPtOg.js";import"./index-BC2pRl3S.js";import"./vnode-CqxKQBn0.js";export{o as default};

View File

@ -0,0 +1 @@
.search-card[data-v-82eaff85]{margin-bottom:16px}[data-v-82eaff85] .el-card__body{padding-bottom:0}

View File

@ -0,0 +1 @@
var e=Object.defineProperty,a=Object.getOwnPropertySymbols,l=Object.prototype.hasOwnProperty,t=Object.prototype.propertyIsEnumerable,o=(a,l,t)=>l in a?e(a,l,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[l]=t,s=(e,s)=>{for(var r in s||(s={}))l.call(s,r)&&o(e,r,s[r]);if(a)for(var r of a(s))t.call(s,r)&&o(e,r,s[r]);return e};import{d as r,a as i,w as p,E as u,y as d,f as m,z as n,p as c,u as f,B as j,e as v,F as _,G as b,J as y,l as x,ab as h,aW as V}from"./index-DBHnxsy6.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{a as g}from"./adminActivities-C7lwI2HP.js";import{E,a as w}from"./index-zIv9y_XZ.js";import{E as O}from"./index-BIIDnOvF.js";import{E as C}from"./index-B5f0OwDI.js";import{E as P}from"./index-qSQlj0RO.js";import{E as k,a as I}from"./index-CHZ9Ls9_.js";import{E as J}from"./index-Bi8tRqjc.js";import{E as U}from"./index-r0EsCkBw.js";import{E as z}from"./index-Bh7aAHNM.js";import{_ as B}from"./_plugin-vue_export-helper-BCo6x5W8.js";import"./use-form-common-props-BOfz9xGR.js";import"./index-DZO7eApA.js";import"./index-MOpuiu9Z.js";import"./index-nZra831X.js";import"./use-form-item-ByasanrX.js";import"./_initCloneObject-7oWMW-Ot.js";import"./index-B99ckkYy.js";import"./index-sqhzrW2O.js";import"./index-D2YpA_om.js";import"./token-DWNpOE8r.js";import"./scroll-Cceui3yC.js";import"./debounce-CkazOn_9.js";import"./_baseIteratee-DRiXPtOg.js";import"./index-BC2pRl3S.js";import"./vnode-CqxKQBn0.js";import"./index-DfWQjCuY.js";const F=B(r({__name:"activity-search",props:{modelValue:{}},emits:["update:modelValue","search","reset"],setup(e,{emit:a}){const l=e,t=a,o=i([]),r=i(s({},l.modelValue));p(()=>l.modelValue,(e,a)=>{JSON.stringify(e)!==JSON.stringify(a)&&(r.value=s({},e))},{deep:!0});let B=null;p(r,e=>{B&&clearTimeout(B),B=setTimeout(()=>{t("update:modelValue",s({},e))},100)},{deep:!0});const F=e=>{return a=this,l=null,t=function*(){if(e&&0===o.value.length)try{const e=yield g();o.value=e.list||[]}catch(a){}},new Promise((e,o)=>{var s=e=>{try{i(t.next(e))}catch(a){o(a)}},r=e=>{try{i(t.throw(e))}catch(a){o(a)}},i=a=>a.done?e(a.value):Promise.resolve(a.value).then(s,r);i((t=t.apply(a,l)).next())});var a,l,t},G=()=>{t("search",r.value)},S=()=>{r.value={name:void 0,category_id:void 0,status:void 0,is_boss:void 0},t("reset")};return(a,l)=>{const t=u("ripple");return m(),d(f(z),{class:"search-card",shadow:"never"},{default:n(()=>[c(f(E),{ref:"formRef",model:e.modelValue,"label-width":"80px"},{default:n(()=>[c(f(O),{gutter:20},{default:n(()=>[c(f(C),{span:6},{default:n(()=>[c(f(w),{label:"活动名称",prop:"name"},{default:n(()=>[c(f(P),{modelValue:r.value.name,"onUpdate:modelValue":l[0]||(l[0]=e=>r.value.name=e),placeholder:"请输入活动名称",clearable:"",onKeyup:j(G,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),c(f(C),{span:6},{default:n(()=>[c(f(w),{label:"分类",prop:"category_id"},{default:n(()=>[c(f(k),{modelValue:r.value.category_id,"onUpdate:modelValue":l[1]||(l[1]=e=>r.value.category_id=e),placeholder:"请选择分类",clearable:"",onVisibleChange:F},{default:n(()=>[(m(!0),v(_,null,b(o.value,e=>(m(),d(f(I),{key:e.id,label:e.name,value:e.id},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1})]),_:1}),c(f(C),{span:6},{default:n(()=>[c(f(w),{label:"状态",prop:"status"},{default:n(()=>[c(f(k),{modelValue:r.value.status,"onUpdate:modelValue":l[2]||(l[2]=e=>r.value.status=e),placeholder:"请选择状态",clearable:""},{default:n(()=>[c(f(I),{value:1,label:"进行中"}),c(f(I),{value:2,label:"下线"})]),_:1},8,["modelValue"])]),_:1})]),_:1}),c(f(C),{span:6},{default:n(()=>[c(f(w),{label:"Boss活动",prop:"is_boss"},{default:n(()=>[c(f(k),{modelValue:r.value.is_boss,"onUpdate:modelValue":l[3]||(l[3]=e=>r.value.is_boss=e),placeholder:"请选择",clearable:""},{default:n(()=>[c(f(I),{value:1,label:"是"}),c(f(I),{value:0,label:"否"})]),_:1},8,["modelValue"])]),_:1})]),_:1}),c(f(C),{span:4},{default:n(()=>[c(f(w),{"label-width":"0"},{default:n(()=>[c(f(J),null,{default:n(()=>[y((m(),d(f(U),{type:"primary",onClick:G},{default:n(()=>[c(f(h),{class:"mr-1"},{default:n(()=>[c(f(V))]),_:1}),l[4]||(l[4]=x(" 搜索 ",-1))]),_:1})),[[t]]),y((m(),d(f(U),{onClick:S},{default:n(()=>[...l[5]||(l[5]=[x("重置",-1)])]),_:1})),[[t]])]),_:1})]),_:1})]),_:1})]),_:1})]),_:1},8,["model"])]),_:1})}}}),[["__scopeId","data-v-82eaff85"]]);export{F as default};

View File

@ -1 +0,0 @@
.search-card[data-v-00083c50]{margin-bottom:16px}[data-v-00083c50] .el-card__body{padding-bottom:0}

View File

@ -1 +0,0 @@
var e=Object.defineProperty,a=Object.getOwnPropertySymbols,l=Object.prototype.hasOwnProperty,t=Object.prototype.propertyIsEnumerable,o=(a,l,t)=>l in a?e(a,l,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[l]=t,s=(e,s)=>{for(var r in s||(s={}))l.call(s,r)&&o(e,r,s[r]);if(a)for(var r of a(s))t.call(s,r)&&o(e,r,s[r]);return e};import{d as r,a as i,w as u,E as d,y as p,f as m,z as n,p as c,u as f,B as j,e as v,F as b,G as _,J as y,l as h,ab as x,a$ as V}from"./index-ip5gDR6L.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{a as g}from"./adminActivities-d_jfLDps.js";import{E,a as w}from"./index-CxGOZAw2.js";import{E as O}from"./index-Ca1H-sCf.js";import{E as k}from"./index-D5xppMej.js";import{E as P}from"./index-DfrOVkw6.js";import{E as C,a as U}from"./index-Br3btmwp.js";import{E as J}from"./index-BY5Lb5w1.js";import{E as S}from"./index-opP5No9I.js";import{E as $}from"./index-BBSYL4g8.js";import{_ as B}from"./_plugin-vue_export-helper-BCo6x5W8.js";import"./use-form-common-props-BwBs_jod.js";import"./index-D-10FtUD.js";import"./index-CHAf6FpU.js";import"./index-fnD_mPvE.js";import"./use-form-item-BDb3ZgPk.js";import"./_initCloneObject-DwsgJamq.js";import"./index-C9KXwj2c.js";import"./index-DbRz3Zuu.js";import"./index-D1xXI5gl.js";import"./token-DWNpOE8r.js";import"./scroll-ChxXkPhM.js";import"./debounce-B9s63_mG.js";import"./index-Jz5GfFad.js";import"./vnode-6FKeug3R.js";import"./index-D-iZIej9.js";const I=B(r({__name:"activity-search",props:{modelValue:{}},emits:["update:modelValue","search","reset"],setup(e,{emit:a}){const l=e,t=a,o=i([]),r=i(s({},l.modelValue));u(()=>l.modelValue,(e,a)=>{JSON.stringify(e)!==JSON.stringify(a)&&(r.value=s({},e))},{deep:!0});let B=null;u(r,e=>{B&&clearTimeout(B),B=setTimeout(()=>{t("update:modelValue",s({},e))},100)},{deep:!0});const I=e=>{return a=this,l=null,t=function*(){if(e&&0===o.value.length)try{const e=yield g();o.value=e.list||[]}catch(a){}},new Promise((e,o)=>{var s=e=>{try{i(t.next(e))}catch(a){o(a)}},r=e=>{try{i(t.throw(e))}catch(a){o(a)}},i=a=>a.done?e(a.value):Promise.resolve(a.value).then(s,r);i((t=t.apply(a,l)).next())});var a,l,t},K=()=>{t("search",r.value)},N=()=>{r.value={name:void 0,category_id:void 0,status:void 0,is_boss:void 0},t("reset")};return(a,l)=>{const t=d("ripple");return m(),p(f($),{class:"search-card",shadow:"never"},{default:n(()=>[c(f(E),{ref:"formRef",model:e.modelValue,"label-width":"80px"},{default:n(()=>[c(f(O),{gutter:20},{default:n(()=>[c(f(k),{span:6},{default:n(()=>[c(f(w),{label:"活动名称",prop:"name"},{default:n(()=>[c(f(P),{modelValue:r.value.name,"onUpdate:modelValue":l[0]||(l[0]=e=>r.value.name=e),placeholder:"请输入活动名称",clearable:"",onKeyup:j(K,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),c(f(k),{span:6},{default:n(()=>[c(f(w),{label:"分类",prop:"category_id"},{default:n(()=>[c(f(C),{modelValue:r.value.category_id,"onUpdate:modelValue":l[1]||(l[1]=e=>r.value.category_id=e),placeholder:"请选择分类",clearable:"",onVisibleChange:I},{default:n(()=>[(m(!0),v(b,null,_(o.value,e=>(m(),p(f(U),{key:e.id,label:e.name,value:e.id},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1})]),_:1}),c(f(k),{span:6},{default:n(()=>[c(f(w),{label:"状态",prop:"status"},{default:n(()=>[c(f(C),{modelValue:r.value.status,"onUpdate:modelValue":l[2]||(l[2]=e=>r.value.status=e),placeholder:"请选择状态",clearable:""},{default:n(()=>[c(f(U),{value:1,label:"进行中"}),c(f(U),{value:2,label:"下线"})]),_:1},8,["modelValue"])]),_:1})]),_:1}),c(f(k),{span:6},{default:n(()=>[c(f(w),{label:"Boss活动",prop:"is_boss"},{default:n(()=>[c(f(C),{modelValue:r.value.is_boss,"onUpdate:modelValue":l[3]||(l[3]=e=>r.value.is_boss=e),placeholder:"请选择",clearable:""},{default:n(()=>[c(f(U),{value:1,label:"是"}),c(f(U),{value:0,label:"否"})]),_:1},8,["modelValue"])]),_:1})]),_:1}),c(f(k),{span:4},{default:n(()=>[c(f(w),{"label-width":"0"},{default:n(()=>[c(f(J),null,{default:n(()=>[y((m(),p(f(S),{type:"primary",onClick:K},{default:n(()=>[c(f(x),{class:"mr-1"},{default:n(()=>[c(f(V))]),_:1}),l[4]||(l[4]=h(" 搜索 ",-1))]),_:1})),[[t]]),y((m(),p(f(S),{onClick:N},{default:n(()=>[...l[5]||(l[5]=[h("重置",-1)])]),_:1})),[[t]])]),_:1})]),_:1})]),_:1})]),_:1})]),_:1},8,["model"])]),_:1})}}}),[["__scopeId","data-v-00083c50"]]);export{I as default};

View File

@ -1 +0,0 @@
import{_ as o}from"./add-coupon-dialog.vue_vue_type_script_setup_true_lang-IOaUwwXz.js";import"./index-ip5gDR6L.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./coupons-DhPZnSZd.js";import"./index-CxGOZAw2.js";import"./use-form-common-props-BwBs_jod.js";import"./index-D-10FtUD.js";import"./index-CHAf6FpU.js";import"./index-fnD_mPvE.js";import"./use-form-item-BDb3ZgPk.js";import"./_initCloneObject-DwsgJamq.js";import"./index-Br3btmwp.js";import"./index-C9KXwj2c.js";import"./index-DbRz3Zuu.js";import"./index-D1xXI5gl.js";import"./token-DWNpOE8r.js";import"./scroll-ChxXkPhM.js";import"./debounce-B9s63_mG.js";import"./index-Jz5GfFad.js";import"./vnode-6FKeug3R.js";import"./index-opP5No9I.js";import"./index-D-iZIej9.js";import"./index-BpP4itD-.js";import"./index-D17cYxP7.js";import"./use-dialog-Cj8YneNA.js";import"./index-D6zsJoyl.js";import"./refs-Cw5r5QN8.js";export{o as default};

View File

@ -0,0 +1 @@
import{_ as o}from"./add-coupon-dialog.vue_vue_type_script_setup_true_lang-C0dOKfzY.js";import"./index-DBHnxsy6.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./coupons-BX1h_qVI.js";import"./index-zIv9y_XZ.js";import"./use-form-common-props-BOfz9xGR.js";import"./index-DZO7eApA.js";import"./index-MOpuiu9Z.js";import"./index-nZra831X.js";import"./use-form-item-ByasanrX.js";import"./_initCloneObject-7oWMW-Ot.js";import"./index-CHZ9Ls9_.js";import"./index-B99ckkYy.js";import"./index-sqhzrW2O.js";import"./index-D2YpA_om.js";import"./token-DWNpOE8r.js";import"./scroll-Cceui3yC.js";import"./debounce-CkazOn_9.js";import"./_baseIteratee-DRiXPtOg.js";import"./index-BC2pRl3S.js";import"./vnode-CqxKQBn0.js";import"./index-r0EsCkBw.js";import"./index-DfWQjCuY.js";import"./index-CtUPMuT5.js";import"./index-DeCbFeva.js";import"./use-dialog-yhiLZsn9.js";import"./index-DvlZWusB.js";import"./refs-Cw5r5QN8.js";export{o as default};

View File

@ -1 +1 @@
var e=(e,a,l)=>new Promise((o,t)=>{var i=e=>{try{r(l.next(e))}catch(a){t(a)}},s=e=>{try{r(l.throw(e))}catch(a){t(a)}},r=e=>e.done?o(e.value):Promise.resolve(e.value).then(i,s);r((l=l.apply(e,a)).next())});import{d as a,a as l,r as o,w as t,o as i,y as s,f as r,z as d,p as u,u as n,e as p,F as m,G as c,l as v}from"./index-ip5gDR6L.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{c as f}from"./coupons-DhPZnSZd.js";import{E as y,a as b}from"./index-CxGOZAw2.js";import{E as j,a as h}from"./index-Br3btmwp.js";import{E as g}from"./index-opP5No9I.js";import{E as _}from"./index-BpP4itD-.js";const x=a({__name:"add-coupon-dialog",props:{visible:{type:Boolean}},emits:["update:visible","submit"],setup(a,{emit:x}){const I=a,w=x,k=l(),V=l(!1),E=l([]),C=o({couponId:null});t(()=>I.visible,e=>{});const $={couponId:[{required:!0,message:"请选择优惠券",trigger:"change"}]},z=()=>e(this,null,function*(){var e;try{yield null==(e=k.value)?void 0:e.validate(),V.value=!0,w("submit",{coupon_id:C.couponId})}catch(a){}finally{V.value=!1}}),A=()=>{w("update:visible",!1)},M=()=>{var e;C.couponId=null,null==(e=k.value)||e.clearValidate()};return i(()=>e(this,null,function*(){try{const e=yield f.getList({status:1,page:1,page_size:100});E.value=Array.isArray(e.list)?e.list.map(e=>({id:e.id,name:e.name})):[]}catch(e){E.value=[]}})),(e,l)=>(r(),s(n(_),{"model-value":a.visible,title:"发放优惠券",width:"400px","close-on-click-modal":!1,"onUpdate:modelValue":l[1]||(l[1]=e=>w("update:visible",e)),onClosed:M},{footer:d(()=>[u(n(g),{onClick:A},{default:d(()=>[...l[2]||(l[2]=[v("取消",-1)])]),_:1}),u(n(g),{type:"primary",loading:V.value,onClick:z},{default:d(()=>[...l[3]||(l[3]=[v(" 确定 ",-1)])]),_:1},8,["loading"])]),default:d(()=>[u(n(y),{ref_key:"formRef",ref:k,model:C,rules:$,"label-width":"80px"},{default:d(()=>[u(n(b),{label:"优惠券",prop:"couponId"},{default:d(()=>[u(n(j),{modelValue:C.couponId,"onUpdate:modelValue":l[0]||(l[0]=e=>C.couponId=e),placeholder:"请选择优惠券",filterable:"",style:{width:"100%"}},{default:d(()=>[(r(!0),p(m,null,c(E.value,e=>(r(),s(n(h),{key:e.id,label:`${e.name}ID: ${e.id}`,value:e.id},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["model-value"]))}});export{x as _};
var e=(e,a,l)=>new Promise((o,t)=>{var i=e=>{try{r(l.next(e))}catch(a){t(a)}},s=e=>{try{r(l.throw(e))}catch(a){t(a)}},r=e=>e.done?o(e.value):Promise.resolve(e.value).then(i,s);r((l=l.apply(e,a)).next())});import{d as a,a as l,r as o,w as t,o as i,y as s,f as r,z as d,p as u,u as n,e as p,F as m,G as c,l as v}from"./index-DBHnxsy6.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{c as f}from"./coupons-BX1h_qVI.js";import{E as y,a as b}from"./index-zIv9y_XZ.js";import{E as j,a as g}from"./index-CHZ9Ls9_.js";import{E as h}from"./index-r0EsCkBw.js";import{E as _}from"./index-CtUPMuT5.js";const x=a({__name:"add-coupon-dialog",props:{visible:{type:Boolean}},emits:["update:visible","submit"],setup(a,{emit:x}){const I=a,w=x,k=l(),V=l(!1),C=l([]),E=o({couponId:null});t(()=>I.visible,e=>{});const A={couponId:[{required:!0,message:"请选择优惠券",trigger:"change"}]},z=()=>e(this,null,function*(){var e;try{yield null==(e=k.value)?void 0:e.validate(),V.value=!0,w("submit",{coupon_id:E.couponId})}catch(a){}finally{V.value=!1}}),B=()=>{w("update:visible",!1)},P=()=>{var e;E.couponId=null,null==(e=k.value)||e.clearValidate()};return i(()=>e(this,null,function*(){try{const e=yield f.getList({status:1,page:1,page_size:100});C.value=Array.isArray(e.list)?e.list.map(e=>({id:e.id,name:e.name})):[]}catch(e){C.value=[]}})),(e,l)=>(r(),s(n(_),{"model-value":a.visible,title:"发放优惠券",width:"400px","close-on-click-modal":!1,"onUpdate:modelValue":l[1]||(l[1]=e=>w("update:visible",e)),onClosed:P},{footer:d(()=>[u(n(h),{onClick:B},{default:d(()=>[...l[2]||(l[2]=[v("取消",-1)])]),_:1}),u(n(h),{type:"primary",loading:V.value,onClick:z},{default:d(()=>[...l[3]||(l[3]=[v(" 确定 ",-1)])]),_:1},8,["loading"])]),default:d(()=>[u(n(y),{ref_key:"formRef",ref:k,model:E,rules:A,"label-width":"80px"},{default:d(()=>[u(n(b),{label:"优惠券",prop:"couponId"},{default:d(()=>[u(n(j),{modelValue:E.couponId,"onUpdate:modelValue":l[0]||(l[0]=e=>E.couponId=e),placeholder:"请选择优惠券",filterable:"",style:{width:"100%"}},{default:d(()=>[(r(!0),p(m,null,c(C.value,e=>(r(),s(n(g),{key:e.id,label:`${e.name}ID: ${e.id}`,value:e.id},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["model-value"]))}});export{x as _};

View File

@ -0,0 +1 @@
import{_ as i}from"./add-item-card-dialog.vue_vue_type_script_setup_true_lang-r-95eO1C.js";import"./index-DBHnxsy6.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./itemCards-BHUYJ5ez.js";import"./index-zIv9y_XZ.js";import"./use-form-common-props-BOfz9xGR.js";import"./index-DZO7eApA.js";import"./index-MOpuiu9Z.js";import"./index-nZra831X.js";import"./use-form-item-ByasanrX.js";import"./_initCloneObject-7oWMW-Ot.js";import"./index-CHZ9Ls9_.js";import"./index-B99ckkYy.js";import"./index-sqhzrW2O.js";import"./index-D2YpA_om.js";import"./token-DWNpOE8r.js";import"./scroll-Cceui3yC.js";import"./debounce-CkazOn_9.js";import"./_baseIteratee-DRiXPtOg.js";import"./index-BC2pRl3S.js";import"./vnode-CqxKQBn0.js";import"./index-B4jq9Ja2.js";import"./index-qSQlj0RO.js";import"./index-CuWUnlSZ.js";import"./index-r0EsCkBw.js";import"./index-DfWQjCuY.js";import"./index-CtUPMuT5.js";import"./index-DeCbFeva.js";import"./use-dialog-yhiLZsn9.js";import"./index-DvlZWusB.js";import"./refs-Cw5r5QN8.js";export{i as default};

View File

@ -1 +0,0 @@
import{_ as i}from"./add-item-card-dialog.vue_vue_type_script_setup_true_lang-BbAo8kLg.js";import"./index-ip5gDR6L.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./itemCards-C_dE3q5e.js";import"./index-CxGOZAw2.js";import"./use-form-common-props-BwBs_jod.js";import"./index-D-10FtUD.js";import"./index-CHAf6FpU.js";import"./index-fnD_mPvE.js";import"./use-form-item-BDb3ZgPk.js";import"./_initCloneObject-DwsgJamq.js";import"./index-Br3btmwp.js";import"./index-C9KXwj2c.js";import"./index-DbRz3Zuu.js";import"./index-D1xXI5gl.js";import"./token-DWNpOE8r.js";import"./scroll-ChxXkPhM.js";import"./debounce-B9s63_mG.js";import"./index-Jz5GfFad.js";import"./vnode-6FKeug3R.js";import"./index-HtXsSizM.js";import"./index-DfrOVkw6.js";import"./index-BbcTNOqF.js";import"./index-opP5No9I.js";import"./index-D-iZIej9.js";import"./index-BpP4itD-.js";import"./index-D17cYxP7.js";import"./use-dialog-Cj8YneNA.js";import"./index-D6zsJoyl.js";import"./refs-Cw5r5QN8.js";export{i as default};

View File

@ -1 +0,0 @@
var e=(e,a,l)=>new Promise((t,i)=>{var r=e=>{try{s(l.next(e))}catch(a){i(a)}},o=e=>{try{s(l.throw(e))}catch(a){i(a)}},s=e=>e.done?t(e.value):Promise.resolve(e.value).then(r,o);s((l=l.apply(e,a)).next())});import{d as a,a as l,r as t,w as i,o as r,y as o,f as s,z as d,p as u,u as n,e as m,F as p,G as c,l as v}from"./index-ip5gDR6L.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{i as y}from"./itemCards-C_dE3q5e.js";import{E as f,a as b}from"./index-CxGOZAw2.js";import{E as j,a as g}from"./index-Br3btmwp.js";import{E as h}from"./index-HtXsSizM.js";import{E as _}from"./index-opP5No9I.js";import{E as x}from"./index-BpP4itD-.js";const q=a({__name:"add-item-card-dialog",props:{visible:{type:Boolean}},emits:["update:visible","submit"],setup(a,{emit:q}){const I=a,V=q,w=l(),k=l(!1),E=l([]),C=t({cardId:null,quantity:1});i(()=>I.visible,e=>{});const U={cardId:[{required:!0,message:"请选择道具卡",trigger:"change"}],quantity:[{required:!0,message:"请输入数量",trigger:"change"}]},$=()=>e(this,null,function*(){var e;try{yield null==(e=w.value)?void 0:e.validate(),k.value=!0,V("submit",{card_id:C.cardId,quantity:C.quantity})}catch(a){}finally{k.value=!1}}),z=()=>{V("update:visible",!1)},A=()=>{var e;C.cardId=null,C.quantity=1,null==(e=w.value)||e.clearValidate()};return r(()=>e(this,null,function*(){try{const e=yield y.getList({page:1,page_size:100});E.value=Array.isArray(e.list)?e.list.map(e=>({id:e.id,name:e.name})):[]}catch(e){E.value=[]}})),(e,l)=>(s(),o(n(x),{"model-value":a.visible,title:"分配道具卡",width:"420px","close-on-click-modal":!1,"onUpdate:modelValue":l[2]||(l[2]=e=>V("update:visible",e)),onClosed:A},{footer:d(()=>[u(n(_),{onClick:z},{default:d(()=>[...l[3]||(l[3]=[v("取消",-1)])]),_:1}),u(n(_),{type:"primary",loading:k.value,onClick:$},{default:d(()=>[...l[4]||(l[4]=[v("确定",-1)])]),_:1},8,["loading"])]),default:d(()=>[u(n(f),{ref_key:"formRef",ref:w,model:C,rules:U,"label-width":"80px"},{default:d(()=>[u(n(b),{label:"道具卡",prop:"cardId"},{default:d(()=>[u(n(j),{modelValue:C.cardId,"onUpdate:modelValue":l[0]||(l[0]=e=>C.cardId=e),placeholder:"请选择道具卡",filterable:"",style:{width:"100%"}},{default:d(()=>[(s(!0),m(p,null,c(E.value,e=>(s(),o(n(g),{key:e.id,label:`${e.name}ID: ${e.id}`,value:e.id},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1}),u(n(b),{label:"数量",prop:"quantity"},{default:d(()=>[u(n(h),{modelValue:C.quantity,"onUpdate:modelValue":l[1]||(l[1]=e=>C.quantity=e),min:1,max:100},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["model-value"]))}});export{q as _};

View File

@ -0,0 +1 @@
var e=(e,a,l)=>new Promise((t,i)=>{var r=e=>{try{s(l.next(e))}catch(a){i(a)}},o=e=>{try{s(l.throw(e))}catch(a){i(a)}},s=e=>e.done?t(e.value):Promise.resolve(e.value).then(r,o);s((l=l.apply(e,a)).next())});import{d as a,a as l,r as t,w as i,o as r,y as o,f as s,z as d,p as u,u as n,e as m,F as p,G as c,l as v}from"./index-DBHnxsy6.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{i as y}from"./itemCards-BHUYJ5ez.js";import{E as f,a as b}from"./index-zIv9y_XZ.js";import{E as g,a as j}from"./index-CHZ9Ls9_.js";import{E as h}from"./index-B4jq9Ja2.js";import{E as _}from"./index-r0EsCkBw.js";import{E as x}from"./index-CtUPMuT5.js";const q=a({__name:"add-item-card-dialog",props:{visible:{type:Boolean}},emits:["update:visible","submit"],setup(a,{emit:q}){const I=a,V=q,w=l(),k=l(!1),C=l([]),E=t({cardId:null,quantity:1});i(()=>I.visible,e=>{});const A={cardId:[{required:!0,message:"请选择道具卡",trigger:"change"}],quantity:[{required:!0,message:"请输入数量",trigger:"change"}]},U=()=>e(this,null,function*(){var e;try{yield null==(e=w.value)?void 0:e.validate(),k.value=!0,V("submit",{card_id:E.cardId,quantity:E.quantity})}catch(a){}finally{k.value=!1}}),$=()=>{V("update:visible",!1)},z=()=>{var e;E.cardId=null,E.quantity=1,null==(e=w.value)||e.clearValidate()};return r(()=>e(this,null,function*(){try{const e=yield y.getList({page:1,page_size:100});C.value=Array.isArray(e.list)?e.list.map(e=>({id:e.id,name:e.name})):[]}catch(e){C.value=[]}})),(e,l)=>(s(),o(n(x),{"model-value":a.visible,title:"分配道具卡",width:"420px","close-on-click-modal":!1,"onUpdate:modelValue":l[2]||(l[2]=e=>V("update:visible",e)),onClosed:z},{footer:d(()=>[u(n(_),{onClick:$},{default:d(()=>[...l[3]||(l[3]=[v("取消",-1)])]),_:1}),u(n(_),{type:"primary",loading:k.value,onClick:U},{default:d(()=>[...l[4]||(l[4]=[v("确定",-1)])]),_:1},8,["loading"])]),default:d(()=>[u(n(f),{ref_key:"formRef",ref:w,model:E,rules:A,"label-width":"80px"},{default:d(()=>[u(n(b),{label:"道具卡",prop:"cardId"},{default:d(()=>[u(n(g),{modelValue:E.cardId,"onUpdate:modelValue":l[0]||(l[0]=e=>E.cardId=e),placeholder:"请选择道具卡",filterable:"",style:{width:"100%"}},{default:d(()=>[(s(!0),m(p,null,c(C.value,e=>(s(),o(n(j),{key:e.id,label:`${e.name}ID: ${e.id}`,value:e.id},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1}),u(n(b),{label:"数量",prop:"quantity"},{default:d(()=>[u(n(h),{modelValue:E.quantity,"onUpdate:modelValue":l[1]||(l[1]=e=>E.quantity=e),min:1,max:100},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["model-value"]))}});export{q as _};

View File

@ -0,0 +1 @@
import{_ as i}from"./add-points-dialog.vue_vue_type_script_setup_true_lang-B99Uz4mM.js";import"./index-DBHnxsy6.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./index-zIv9y_XZ.js";import"./use-form-common-props-BOfz9xGR.js";import"./index-DZO7eApA.js";import"./index-MOpuiu9Z.js";import"./index-nZra831X.js";import"./use-form-item-ByasanrX.js";import"./_initCloneObject-7oWMW-Ot.js";import"./index-B4jq9Ja2.js";import"./index-qSQlj0RO.js";import"./index-CuWUnlSZ.js";import"./index-CHZ9Ls9_.js";import"./index-B99ckkYy.js";import"./index-sqhzrW2O.js";import"./index-D2YpA_om.js";import"./token-DWNpOE8r.js";import"./scroll-Cceui3yC.js";import"./debounce-CkazOn_9.js";import"./_baseIteratee-DRiXPtOg.js";import"./index-BC2pRl3S.js";import"./vnode-CqxKQBn0.js";import"./index-r0EsCkBw.js";import"./index-DfWQjCuY.js";import"./index-CtUPMuT5.js";import"./index-DeCbFeva.js";import"./use-dialog-yhiLZsn9.js";import"./index-DvlZWusB.js";import"./refs-Cw5r5QN8.js";export{i as default};

View File

@ -1 +0,0 @@
import{_ as i}from"./add-points-dialog.vue_vue_type_script_setup_true_lang-BG76LgaZ.js";import"./index-ip5gDR6L.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./index-CxGOZAw2.js";import"./use-form-common-props-BwBs_jod.js";import"./index-D-10FtUD.js";import"./index-CHAf6FpU.js";import"./index-fnD_mPvE.js";import"./use-form-item-BDb3ZgPk.js";import"./_initCloneObject-DwsgJamq.js";import"./index-HtXsSizM.js";import"./index-DfrOVkw6.js";import"./index-BbcTNOqF.js";import"./index-Br3btmwp.js";import"./index-C9KXwj2c.js";import"./index-DbRz3Zuu.js";import"./index-D1xXI5gl.js";import"./token-DWNpOE8r.js";import"./scroll-ChxXkPhM.js";import"./debounce-B9s63_mG.js";import"./index-Jz5GfFad.js";import"./vnode-6FKeug3R.js";import"./index-opP5No9I.js";import"./index-D-iZIej9.js";import"./index-BpP4itD-.js";import"./index-D17cYxP7.js";import"./use-dialog-Cj8YneNA.js";import"./index-D6zsJoyl.js";import"./refs-Cw5r5QN8.js";export{i as default};

View File

@ -1 +1 @@
import{d as e,a,r as l,w as i,y as r,f as o,z as t,p as d,u as s,l as n}from"./index-ip5gDR6L.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{E as m,a as p}from"./index-CxGOZAw2.js";import{E as u}from"./index-HtXsSizM.js";import{E as v,a as c}from"./index-Br3btmwp.js";import{E as f}from"./index-DfrOVkw6.js";import{E as b}from"./index-opP5No9I.js";import{E as k}from"./index-BpP4itD-.js";const _=e({__name:"add-points-dialog",props:{visible:{type:Boolean}},emits:["update:visible","submit"],setup(e,{emit:_}){const j=e,y=_,h=a(),g=a(!1),x=l({points:null,kind:"admin_add",remark:""});i(()=>j.visible,e=>{});const V={points:[{required:!0,message:"请输入积分数量",trigger:"blur"}],kind:[{required:!0,message:"请选择积分类型",trigger:"change"}]},w=()=>{return e=this,a=null,l=function*(){var e;try{yield null==(e=h.value)?void 0:e.validate(),g.value=!0,y("submit",{points:x.points,kind:x.kind,remark:x.remark})}catch(a){}finally{g.value=!1}},new Promise((i,r)=>{var o=e=>{try{d(l.next(e))}catch(a){r(a)}},t=e=>{try{d(l.throw(e))}catch(a){r(a)}},d=e=>e.done?i(e.value):Promise.resolve(e.value).then(o,t);d((l=l.apply(e,a)).next())});var e,a,l},E=()=>{y("update:visible",!1)},U=()=>{var e;x.points=null,x.kind="admin_add",x.remark="",null==(e=h.value)||e.clearValidate()};return(a,l)=>(o(),r(s(k),{"model-value":e.visible,title:"增加积分",width:"400px","close-on-click-modal":!1,"onUpdate:modelValue":l[3]||(l[3]=e=>y("update:visible",e)),onClosed:U},{footer:t(()=>[d(s(b),{onClick:E},{default:t(()=>[...l[4]||(l[4]=[n("取消",-1)])]),_:1}),d(s(b),{type:"primary",loading:g.value,onClick:w},{default:t(()=>[...l[5]||(l[5]=[n(" 确定 ",-1)])]),_:1},8,["loading"])]),default:t(()=>[d(s(m),{ref_key:"formRef",ref:h,model:x,rules:V,"label-width":"80px"},{default:t(()=>[d(s(p),{label:"积分数量",prop:"points"},{default:t(()=>[d(s(u),{modelValue:x.points,"onUpdate:modelValue":l[0]||(l[0]=e=>x.points=e),placeholder:"请输入积分数量",min:1,precision:0,style:{width:"100%"}},null,8,["modelValue"])]),_:1}),d(s(p),{label:"积分类型",prop:"kind"},{default:t(()=>[d(s(v),{modelValue:x.kind,"onUpdate:modelValue":l[1]||(l[1]=e=>x.kind=e),placeholder:"请选择积分类型",style:{width:"100%"}},{default:t(()=>[d(s(c),{label:"管理员增加",value:"admin_add"}),d(s(c),{label:"活动奖励",value:"activity_reward"}),d(s(c),{label:"签到奖励",value:"sign_reward"}),d(s(c),{label:"消费返还",value:"consume_return"})]),_:1},8,["modelValue"])]),_:1}),d(s(p),{label:"备注",prop:"remark"},{default:t(()=>[d(s(f),{modelValue:x.remark,"onUpdate:modelValue":l[2]||(l[2]=e=>x.remark=e),type:"textarea",placeholder:"请输入备注",rows:3},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["model-value"]))}});export{_};
import{d as e,a,r as l,w as i,y as r,f as o,z as t,p as d,u as s,l as n}from"./index-DBHnxsy6.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{E as m,a as p}from"./index-zIv9y_XZ.js";import{E as u}from"./index-B4jq9Ja2.js";import{E as v,a as c}from"./index-CHZ9Ls9_.js";import{E as f}from"./index-qSQlj0RO.js";import{E as b}from"./index-r0EsCkBw.js";import{E as k}from"./index-CtUPMuT5.js";const _=e({__name:"add-points-dialog",props:{visible:{type:Boolean}},emits:["update:visible","submit"],setup(e,{emit:_}){const j=e,y=_,g=a(),h=a(!1),x=l({points:null,kind:"admin_add",remark:""});i(()=>j.visible,e=>{});const V={points:[{required:!0,message:"请输入积分数量",trigger:"blur"}],kind:[{required:!0,message:"请选择积分类型",trigger:"change"}]},w=()=>{return e=this,a=null,l=function*(){var e;try{yield null==(e=g.value)?void 0:e.validate(),h.value=!0,y("submit",{points:x.points,kind:x.kind,remark:x.remark})}catch(a){}finally{h.value=!1}},new Promise((i,r)=>{var o=e=>{try{d(l.next(e))}catch(a){r(a)}},t=e=>{try{d(l.throw(e))}catch(a){r(a)}},d=e=>e.done?i(e.value):Promise.resolve(e.value).then(o,t);d((l=l.apply(e,a)).next())});var e,a,l},E=()=>{y("update:visible",!1)},C=()=>{var e;x.points=null,x.kind="admin_add",x.remark="",null==(e=g.value)||e.clearValidate()};return(a,l)=>(o(),r(s(k),{"model-value":e.visible,title:"增加积分",width:"400px","close-on-click-modal":!1,"onUpdate:modelValue":l[3]||(l[3]=e=>y("update:visible",e)),onClosed:C},{footer:t(()=>[d(s(b),{onClick:E},{default:t(()=>[...l[4]||(l[4]=[n("取消",-1)])]),_:1}),d(s(b),{type:"primary",loading:h.value,onClick:w},{default:t(()=>[...l[5]||(l[5]=[n(" 确定 ",-1)])]),_:1},8,["loading"])]),default:t(()=>[d(s(m),{ref_key:"formRef",ref:g,model:x,rules:V,"label-width":"80px"},{default:t(()=>[d(s(p),{label:"积分数量",prop:"points"},{default:t(()=>[d(s(u),{modelValue:x.points,"onUpdate:modelValue":l[0]||(l[0]=e=>x.points=e),placeholder:"请输入积分数量",min:1,precision:0,style:{width:"100%"}},null,8,["modelValue"])]),_:1}),d(s(p),{label:"积分类型",prop:"kind"},{default:t(()=>[d(s(v),{modelValue:x.kind,"onUpdate:modelValue":l[1]||(l[1]=e=>x.kind=e),placeholder:"请选择积分类型",style:{width:"100%"}},{default:t(()=>[d(s(c),{label:"管理员增加",value:"admin_add"}),d(s(c),{label:"活动奖励",value:"activity_reward"}),d(s(c),{label:"签到奖励",value:"sign_reward"}),d(s(c),{label:"消费返还",value:"consume_return"})]),_:1},8,["modelValue"])]),_:1}),d(s(p),{label:"备注",prop:"remark"},{default:t(()=>[d(s(f),{modelValue:x.remark,"onUpdate:modelValue":l[2]||(l[2]=e=>x.remark=e),type:"textarea",placeholder:"请输入备注",rows:3},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["model-value"]))}});export{_};

View File

@ -0,0 +1 @@
import{b4 as i}from"./index-DBHnxsy6.js";function s(s){return i.post({url:"admin/activities",params:s})}function t(s,t){return i.put({url:`admin/activities/${s}`,params:t})}function r(s){return i.del({url:`admin/activities/${s}`})}function a(s){return i.get({url:`admin/activities/${s}`})}function n(s,t=1,r=20){return i.get({url:`admin/activities/${s}/issues`,params:{page:t,page_size:r}})}function e(s,t){return i.post({url:`admin/activities/${s}/issues`,params:t})}function u(s,t,r){return i.put({url:`admin/activities/${s}/issues/${t}`,params:r})}function c(s,t){return i.del({url:`admin/activities/${s}/issues/${t}`})}function m(s,t){return i.get({url:`admin/activities/${s}/issues/${t}/rewards`})}function o(s,t,r){return i.post({url:`admin/activities/${s}/issues/${t}/rewards`,params:{rewards:r}})}function d(s,t,r,a){return i.put({url:`admin/activities/${s}/issues/${t}/rewards/${r}`,params:a})}function $(s,t,r){return i.del({url:`admin/activities/${s}/issues/${t}/rewards/${r}`})}function l(){return i.get({url:"admin/activity_categories"})}function p(s,t){return i.post({url:`admin/activities/${s}/issues/${t}/commit_random`})}function f(s,t){return i.get({url:`admin/activities/${s}/issues/${t}/commit_random`,showErrorMessage:!1})}function v(s,t){return i.get({url:`admin/activities/${s}/issues/${t}/commit_random/history`})}function g(s,t,r){return i.post({url:`admin/activities/${s}/issues/${t}/simulate_draw`,params:r,timeout:6e4})}function w(s,t,r){return i.post({url:`admin/activities/${s}/issues/${t}/batch_draw`,params:r,timeout:6e4})}function _(s,t,r){return i.post({url:`admin/activities/${s}/issues/${t}/verify_draw`,params:r})}function h(s){return i.get({url:`admin/draw_receipts/${s}`})}function b(s){return i.get({url:`admin/draw_receipts/log/${s}`})}function y(s){return i.get({url:"admin/users",params:s})}export{l as a,t as b,e as c,c as d,s as e,r as f,a as g,o as h,f as i,v as j,p as k,n as l,d as m,m as n,$ as o,y as p,w as q,h as r,g as s,b as t,u,_ as v};

View File

@ -1 +0,0 @@
import{b5 as i}from"./index-ip5gDR6L.js";function s(s){return i.post({url:"admin/activities",params:s})}function t(s,t){return i.put({url:`admin/activities/${s}`,params:t})}function a(s){return i.del({url:`admin/activities/${s}`})}function r(s){return i.get({url:`admin/activities/${s}`})}function e(s,t=1,a=20){return i.get({url:`admin/activities/${s}/issues`,params:{page:t,page_size:a}})}function n(s,t){return i.post({url:`admin/activities/${s}/issues`,params:t})}function u(s,t,a){return i.put({url:`admin/activities/${s}/issues/${t}`,params:a})}function c(s,t){return i.del({url:`admin/activities/${s}/issues/${t}`})}function d(s,t){return i.get({url:`admin/activities/${s}/issues/${t}/rewards`})}function m(s,t,a){return i.post({url:`admin/activities/${s}/issues/${t}/rewards`,params:{rewards:a}})}function o(s,t,a,r){return i.put({url:`admin/activities/${s}/issues/${t}/rewards/${a}`,params:r})}function $(s,t,a){return i.del({url:`admin/activities/${s}/issues/${t}/rewards/${a}`})}function l(){return i.get({url:"admin/activity_categories"})}export{l as a,t as b,n as c,c as d,s as e,a as f,r as g,m as h,o as i,d as j,$ as k,e as l,u};

View File

@ -0,0 +1 @@
import{_ as i}from"./assign-title-dialog.vue_vue_type_script_setup_true_lang-ChRt4Uun.js";import"./index-DBHnxsy6.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./titles-Bs2uXvDP.js";import"./index-zIv9y_XZ.js";import"./use-form-common-props-BOfz9xGR.js";import"./index-DZO7eApA.js";import"./index-MOpuiu9Z.js";import"./index-nZra831X.js";import"./use-form-item-ByasanrX.js";import"./_initCloneObject-7oWMW-Ot.js";import"./index-CHZ9Ls9_.js";import"./index-B99ckkYy.js";import"./index-sqhzrW2O.js";import"./index-D2YpA_om.js";import"./token-DWNpOE8r.js";import"./scroll-Cceui3yC.js";import"./debounce-CkazOn_9.js";import"./_baseIteratee-DRiXPtOg.js";import"./index-BC2pRl3S.js";import"./vnode-CqxKQBn0.js";import"./index-CS6jHv1e.js";import"./index-r0EsCkBw.js";import"./index-DfWQjCuY.js";import"./index-qSQlj0RO.js";import"./index-CuWUnlSZ.js";import"./index-CtUPMuT5.js";import"./index-DeCbFeva.js";import"./use-dialog-yhiLZsn9.js";import"./index-DvlZWusB.js";import"./refs-Cw5r5QN8.js";export{i as default};

View File

@ -0,0 +1 @@
var e=(e,l,a)=>new Promise((t,i)=>{var r=e=>{try{s(a.next(e))}catch(l){i(l)}},o=e=>{try{s(a.throw(e))}catch(l){i(l)}},s=e=>e.done?t(e.value):Promise.resolve(e.value).then(r,o);s((a=a.apply(e,l)).next())});import{d as l,a,r as t,o as i,y as r,f as o,z as s,p as d,u as m,e as p,F as u,G as n,l as f}from"./index-DBHnxsy6.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{titlesApi as v}from"./titles-Bs2uXvDP.js";import{E as c,a as x}from"./index-zIv9y_XZ.js";import{E as y,a as j}from"./index-CHZ9Ls9_.js";import{E as b}from"./index-CS6jHv1e.js";import{E as h}from"./index-qSQlj0RO.js";import{E as _}from"./index-r0EsCkBw.js";import{E as g}from"./index-CtUPMuT5.js";const k=l({__name:"assign-title-dialog",props:{visible:{type:Boolean}},emits:["update:visible","submit"],setup(l,{emit:k}){const V=k,A=a(),I=a(!1),w=a([]),E=t({titleId:null,expiresAt:"",remark:""}),C={titleId:[{required:!0,message:"请选择称号",trigger:"change"}]},U=()=>e(this,null,function*(){var e;try{yield null==(e=A.value)?void 0:e.validate(),I.value=!0;const l={title_id:E.titleId};E.expiresAt&&(l.expires_at=E.expiresAt),E.remark&&(l.remark=E.remark),V("submit",l)}catch(l){}finally{I.value=!1}}),Y=()=>{V("update:visible",!1)},D=()=>{var e;E.titleId=null,E.expiresAt="",E.remark="",null==(e=A.value)||e.clearValidate()};return i(()=>e(this,null,function*(){try{const e=yield v.getList({page:1,page_size:100});w.value=Array.isArray(e.list)?e.list.map(e=>({id:e.id,name:e.name})):[]}catch(e){w.value=[]}})),(e,a)=>(o(),r(m(g),{"model-value":l.visible,title:"分配称号",width:"460px","close-on-click-modal":!1,"onUpdate:modelValue":a[3]||(a[3]=e=>V("update:visible",e)),onClosed:D},{footer:s(()=>[d(m(_),{onClick:Y},{default:s(()=>[...a[4]||(a[4]=[f("取消",-1)])]),_:1}),d(m(_),{type:"primary",loading:I.value,onClick:U},{default:s(()=>[...a[5]||(a[5]=[f("确定",-1)])]),_:1},8,["loading"])]),default:s(()=>[d(m(c),{ref_key:"formRef",ref:A,model:E,rules:C,"label-width":"90px"},{default:s(()=>[d(m(x),{label:"称号",prop:"titleId"},{default:s(()=>[d(m(y),{modelValue:E.titleId,"onUpdate:modelValue":a[0]||(a[0]=e=>E.titleId=e),placeholder:"请选择称号",filterable:"",style:{width:"100%"}},{default:s(()=>[(o(!0),p(u,null,n(w.value,e=>(o(),r(m(j),{key:e.id,label:`${e.name}ID: ${e.id}`,value:e.id},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1}),d(m(x),{label:"过期时间",prop:"expiresAt"},{default:s(()=>[d(m(b),{modelValue:E.expiresAt,"onUpdate:modelValue":a[1]||(a[1]=e=>E.expiresAt=e),type:"datetime","value-format":"YYYY-MM-DDTHH:mm:ssZ",placeholder:"可选,默认为永久",style:{width:"100%"}},null,8,["modelValue"])]),_:1}),d(m(x),{label:"备注",prop:"remark"},{default:s(()=>[d(m(h),{modelValue:E.remark,"onUpdate:modelValue":a[2]||(a[2]=e=>E.remark=e),placeholder:"可选备注",maxlength:"100"},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["model-value"]))}});export{k as _};

View File

@ -0,0 +1 @@
import{_ as i}from"./batch-draw-dialog.vue_vue_type_script_setup_true_lang-D6SWKrJK.js";import"./index-DBHnxsy6.js";/* empty css *//* empty css */import"./el-pagination-CkwA8__y.js";import"./index-CHZ9Ls9_.js";import"./index-B99ckkYy.js";import"./index-nZra831X.js";import"./index-MOpuiu9Z.js";import"./use-form-item-ByasanrX.js";import"./use-form-common-props-BOfz9xGR.js";import"./index-sqhzrW2O.js";import"./index-D2YpA_om.js";import"./token-DWNpOE8r.js";import"./index-DZO7eApA.js";import"./scroll-Cceui3yC.js";import"./debounce-CkazOn_9.js";import"./_baseIteratee-DRiXPtOg.js";import"./index-BC2pRl3S.js";import"./vnode-CqxKQBn0.js";import"./index-qSQlj0RO.js";import"./index-DfWQjCuY.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import"./el-table-column-DWU1JSNJ.js";import"./_initCloneObject-7oWMW-Ot.js";import"./isArrayLikeObject-BJDhNHaT.js";import"./raf-CXqF1NB1.js";import"./index-DKaQAdbW.js";/* empty css */import"./el-tooltip-l0sNRNKZ.js";/* empty css *//* empty css *//* empty css *//* empty css */import"./el-step-DZviJjCE.js";import"./index-y_oew012.js";/* empty css */import"./adminActivities-C7lwI2HP.js";import"./index-zIv9y_XZ.js";import"./index-BIIDnOvF.js";import"./index-B5f0OwDI.js";import"./index-r0EsCkBw.js";import"./index-CtUPMuT5.js";import"./index-DeCbFeva.js";import"./use-dialog-yhiLZsn9.js";import"./index-DvlZWusB.js";import"./refs-Cw5r5QN8.js";export{i as default};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.el-card{--el-card-border-color: var(--el-border-color-light);--el-card-border-radius: 4px;--el-card-padding: 20px;--el-card-bg-color: var(--el-fill-color-blank)}.el-card{border-radius:var(--el-card-border-radius);border:1px solid var(--el-card-border-color);background-color:var(--el-card-bg-color);overflow:hidden;color:var(--el-text-color-primary);transition:var(--el-transition-duration)}.el-card.is-always-shadow{box-shadow:var(--el-box-shadow-light)}.el-card.is-hover-shadow:hover,.el-card.is-hover-shadow:focus{box-shadow:var(--el-box-shadow-light)}.el-card__header{padding:calc(var(--el-card-padding) - 2px) var(--el-card-padding);border-bottom:1px solid var(--el-card-border-color);box-sizing:border-box}.el-card__body{padding:var(--el-card-padding)}.el-card__footer{padding:calc(var(--el-card-padding) - 2px) var(--el-card-padding);border-top:1px solid var(--el-card-border-color);box-sizing:border-box}

View File

@ -1 +0,0 @@
import{_ as e}from"./card-list.vue_vue_type_script_setup_true_lang-Dm1x1mF4.js";import"./index-ip5gDR6L.js";/* empty css *//* empty css */import"./index.vue_vue_type_script_setup_true_lang-CCGboj8m.js";/* empty css */import"./dashboard-C-Wq9kTO.js";import"./index-D5xppMej.js";import"./index-Ca1H-sCf.js";export{e as default};

View File

@ -0,0 +1 @@
import{_ as t}from"./card-list.vue_vue_type_script_setup_true_lang-CibUUuFD.js";import"./index-DBHnxsy6.js";/* empty css *//* empty css */import"./index.vue_vue_type_script_setup_true_lang-DUyHB5jc.js";/* empty css */import"./dashboard-efoygTEr.js";import"./index-B5f0OwDI.js";import"./index-BIIDnOvF.js";export{t as default};

View File

@ -1 +1 @@
import{d as e,c as a,b6 as t,aC as s,b7 as u,w as r,aH as n,e as l,f as i,m as o,i as d,n as c,r as m,y as v,z as p,F as g,G as f,h as x,p as h,O as b}from"./index-ip5gDR6L.js";/* empty css *//* empty css */import{_}from"./index.vue_vue_type_script_setup_true_lang-CCGboj8m.js";/* empty css */import{f as y}from"./dashboard-C-Wq9kTO.js";import{E as j}from"./index-D5xppMej.js";import{E as w}from"./index-Ca1H-sCf.js";const C="easeOutExpo",S=e({__name:"index",props:{target:{default:0},duration:{default:2e3},autoStart:{type:Boolean,default:!0},decimals:{default:0},decimal:{default:"."},separator:{default:""},prefix:{default:""},suffix:{default:""},easing:{default:C},disabled:{type:Boolean,default:!1}},emits:["started","finished","paused","reset"],setup(e,{expose:m,emit:v}){const p=Number.EPSILON,g=e,f=v,x=(e,a,t)=>Number.isFinite(e)?e:t,h=(e,a,t)=>Math.max(a,Math.min(e,t)),b=a(()=>x(g.target,0,0)),_=a(()=>h(x(g.duration,0,2e3),100,6e4)),y=a(()=>h(x(g.decimals,0,0),0,10)),j=a(()=>{const e=g.easing;return e in t?e:C}),w=s(0),S=s(b.value),F=s(!1),N=s(!1),V=s(0),M=u(w,{duration:_,transition:a(()=>t[j.value]),onStarted:()=>{F.value=!0,N.value=!1,f("started",S.value)},onFinished:()=>{F.value=!1,N.value=!1,f("finished",S.value)}}),P=a(()=>{const e=N.value?V.value:M.value;if(!Number.isFinite(e))return`${g.prefix}0${g.suffix}`;const a=((e,a,t,s)=>{let u=a>0?e.toFixed(a):Math.floor(e).toString();if("."!==t&&u.includes(".")&&(u=u.replace(".",t)),s){const e=u.split(t);e[0]=e[0].replace(/\B(?=(\d{3})+(?!\d))/g,s),u=e.join(t)}return u})(e,y.value,g.decimal,g.separator);return`${g.prefix}${a}${g.suffix}`}),$=()=>{N.value=!1,V.value=0},E=e=>{if(g.disabled)return;const a=void 0!==e?e:S.value;Number.isFinite(a)&&(S.value=a,(e=>{const a=N.value?V.value:M.value;return Math.abs(a-e)<p})(a)||(N.value&&(w.value=V.value,$()),c(()=>{w.value=a})))},O=()=>{(F.value||N.value)&&(w.value=0,$(),f("paused",0))};return r(b,e=>{g.autoStart&&!g.disabled?E(e):S.value=e},{immediate:g.autoStart&&!g.disabled}),r(()=>g.disabled,e=>{e&&F.value&&O()}),n(()=>{F.value&&O()}),m({start:E,pause:()=>{F.value&&!N.value&&(N.value=!0,V.value=M.value,w.value=V.value,f("paused",V.value))},reset:(e=0)=>{const a=x(e,0,0);w.value=a,S.value=a,$(),f("reset")},stop:O,setTarget:e=>{Number.isFinite(e)&&(S.value=e,!F.value&&!g.autoStart||g.disabled||E(e))},get isRunning(){return F.value},get isPaused(){return N.value},get currentValue(){return N.value?V.value:M.value},get targetValue(){return S.value},get progress(){const e=N.value?V.value:M.value,a=S.value;return 0===a?0===e?1:0:Math.abs(e/a)}}),(e,a)=>(i(),l("span",{class:d(["text-g-900 tabular-nums",F.value?"transition-opacity duration-300 ease-in-out":""])},o(P.value),3))}}),F={class:"art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4"},N={class:"text-g-700 text-sm"},V={class:"flex-c mt-1"},M={class:"absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"},P=e({__name:"card-list",props:{range:{}},setup(e){const a=e,t=m([{des:"道具卡销量",icon:"ri:shopping-bag-3-line",startVal:0,duration:1e3,num:0,change:"+0%"},{des:"活动抽奖次数",icon:"ri:fire-line",startVal:0,duration:1e3,num:0,change:"+0%"},{des:"新用户注册数",icon:"ri:user-add-line",startVal:0,duration:1e3,num:0,change:"+0%"},{des:"用户总积分",icon:"ri:coin-line",startVal:0,duration:1e3,num:0,change:"+0%"}]);return r(()=>a.range,()=>{return e=this,s=null,u=function*(){try{const e=yield y(a.range);t[0].num=e.itemCardSales,t[0].change=e.itemCardChange,t[1].num=e.drawCount,t[1].change=e.drawChange,t[2].num=e.newUsers,t[2].change=e.newUserChange,t[3].num=e.totalPoints,t[3].change=e.pointsChange}catch(e){b.error("获取卡片数据失败")}},new Promise((a,t)=>{var r=e=>{try{l(u.next(e))}catch(a){t(a)}},n=e=>{try{l(u.throw(e))}catch(a){t(a)}},l=e=>e.done?a(e.value):Promise.resolve(e.value).then(r,n);l((u=u.apply(e,s)).next())});var e,s,u},{immediate:!0}),(e,a)=>{const s=S,u=_,r=j,n=w;return i(),v(n,{gutter:20,class:"flex"},{default:p(()=>[(i(!0),l(g,null,f(t,(e,t)=>(i(),v(r,{key:t,sm:12,md:6,lg:6},{default:p(()=>[x("div",F,[x("span",N,o(e.des),1),h(s,{class:"text-[26px] font-medium mt-2",target:e.num,duration:1300},null,8,["target"]),x("div",V,[a[0]||(a[0]=x("span",{class:"text-xs text-g-600"},"较上周",-1)),x("span",{class:d(["ml-1 text-xs font-semibold",[-1===e.change.indexOf("+")?"text-danger":"text-success"]])},o(e.change),3)]),x("div",M,[h(u,{icon:e.icon,class:"text-xl text-theme"},null,8,["icon"])])])]),_:2},1024))),128))]),_:1})}}});export{P as _};
import{d as e,c as a,b1 as t,aC as s,b2 as u,w as r,aH as n,e as l,f as i,m as o,i as d,n as c,r as m,y as v,z as p,F as g,G as f,h as x,p as h,O as b}from"./index-DBHnxsy6.js";/* empty css *//* empty css */import{_}from"./index.vue_vue_type_script_setup_true_lang-DUyHB5jc.js";/* empty css */import{f as y}from"./dashboard-efoygTEr.js";import{E as j}from"./index-B5f0OwDI.js";import{E as S}from"./index-BIIDnOvF.js";const w="easeOutExpo",C=e({__name:"index",props:{target:{default:0},duration:{default:2e3},autoStart:{type:Boolean,default:!0},decimals:{default:0},decimal:{default:"."},separator:{default:""},prefix:{default:""},suffix:{default:""},easing:{default:w},disabled:{type:Boolean,default:!1}},emits:["started","finished","paused","reset"],setup(e,{expose:m,emit:v}){const p=Number.EPSILON,g=e,f=v,x=(e,a,t)=>Number.isFinite(e)?e:t,h=(e,a,t)=>Math.max(a,Math.min(e,t)),b=a(()=>x(g.target,0,0)),_=a(()=>h(x(g.duration,0,2e3),100,6e4)),y=a(()=>h(x(g.decimals,0,0),0,10)),j=a(()=>{const e=g.easing;return e in t?e:w}),S=s(0),C=s(b.value),F=s(!1),N=s(!1),V=s(0),M=u(S,{duration:_,transition:a(()=>t[j.value]),onStarted:()=>{F.value=!0,N.value=!1,f("started",C.value)},onFinished:()=>{F.value=!1,N.value=!1,f("finished",C.value)}}),P=a(()=>{const e=N.value?V.value:M.value;if(!Number.isFinite(e))return`${g.prefix}0${g.suffix}`;const a=((e,a,t,s)=>{let u=a>0?e.toFixed(a):Math.floor(e).toString();if("."!==t&&u.includes(".")&&(u=u.replace(".",t)),s){const e=u.split(t);e[0]=e[0].replace(/\B(?=(\d{3})+(?!\d))/g,s),u=e.join(t)}return u})(e,y.value,g.decimal,g.separator);return`${g.prefix}${a}${g.suffix}`}),$=()=>{N.value=!1,V.value=0},E=e=>{if(g.disabled)return;const a=void 0!==e?e:C.value;Number.isFinite(a)&&(C.value=a,(e=>{const a=N.value?V.value:M.value;return Math.abs(a-e)<p})(a)||(N.value&&(S.value=V.value,$()),c(()=>{S.value=a})))},O=()=>{(F.value||N.value)&&(S.value=0,$(),f("paused",0))};return r(b,e=>{g.autoStart&&!g.disabled?E(e):C.value=e},{immediate:g.autoStart&&!g.disabled}),r(()=>g.disabled,e=>{e&&F.value&&O()}),n(()=>{F.value&&O()}),m({start:E,pause:()=>{F.value&&!N.value&&(N.value=!0,V.value=M.value,S.value=V.value,f("paused",V.value))},reset:(e=0)=>{const a=x(e,0,0);S.value=a,C.value=a,$(),f("reset")},stop:O,setTarget:e=>{Number.isFinite(e)&&(C.value=e,!F.value&&!g.autoStart||g.disabled||E(e))},get isRunning(){return F.value},get isPaused(){return N.value},get currentValue(){return N.value?V.value:M.value},get targetValue(){return C.value},get progress(){const e=N.value?V.value:M.value,a=C.value;return 0===a?0===e?1:0:Math.abs(e/a)}}),(e,a)=>(i(),l("span",{class:d(["text-g-900 tabular-nums",F.value?"transition-opacity duration-300 ease-in-out":""])},o(P.value),3))}}),F={class:"art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4"},N={class:"text-g-700 text-sm"},V={class:"flex-c mt-1"},M={class:"absolute top-0 bottom-0 right-5 m-auto size-12.5 rounded-xl flex-cc bg-theme/10"},P=e({__name:"card-list",props:{range:{}},setup(e){const a=e,t=m([{des:"道具卡销量",icon:"ri:shopping-bag-3-line",startVal:0,duration:1e3,num:0,change:"+0%"},{des:"活动抽奖次数",icon:"ri:fire-line",startVal:0,duration:1e3,num:0,change:"+0%"},{des:"新用户注册数",icon:"ri:user-add-line",startVal:0,duration:1e3,num:0,change:"+0%"},{des:"用户总积分",icon:"ri:coin-line",startVal:0,duration:1e3,num:0,change:"+0%"}]);return r(()=>a.range,()=>{return e=this,s=null,u=function*(){try{const e=yield y(a.range);t[0].num=e.itemCardSales,t[0].change=e.itemCardChange,t[1].num=e.drawCount,t[1].change=e.drawChange,t[2].num=e.newUsers,t[2].change=e.newUserChange,t[3].num=e.totalPoints,t[3].change=e.pointsChange}catch(e){b.error("获取卡片数据失败")}},new Promise((a,t)=>{var r=e=>{try{l(u.next(e))}catch(a){t(a)}},n=e=>{try{l(u.throw(e))}catch(a){t(a)}},l=e=>e.done?a(e.value):Promise.resolve(e.value).then(r,n);l((u=u.apply(e,s)).next())});var e,s,u},{immediate:!0}),(e,a)=>{const s=C,u=_,r=j,n=S;return i(),v(n,{gutter:20,class:"flex"},{default:p(()=>[(i(!0),l(g,null,f(t,(e,t)=>(i(),v(r,{key:t,sm:12,md:6,lg:6},{default:p(()=>[x("div",F,[x("span",N,o(e.des),1),h(s,{class:"text-[26px] font-medium mt-2",target:e.num,duration:1300},null,8,["target"]),x("div",V,[a[0]||(a[0]=x("span",{class:"text-xs text-g-600"},"较上周",-1)),x("span",{class:d(["ml-1 text-xs font-semibold",[-1===e.change.indexOf("+")?"text-danger":"text-success"]])},o(e.change),3)]),x("div",M,[h(u,{icon:e.icon,class:"text-xl text-theme"},null,8,["icon"])])])]),_:2},1024))),128))]),_:1})}}});export{P as _};

View File

@ -1 +0,0 @@
var e=Object.defineProperty,a=Object.getOwnPropertySymbols,t=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,r=(a,t,o)=>t in a?e(a,t,{enumerable:!0,configurable:!0,writable:!0,value:o}):a[t]=o,s=(e,s)=>{for(var l in s||(s={}))t.call(s,l)&&r(e,l,s[l]);if(a)for(var l of a(s))o.call(s,l)&&r(e,l,s[l]);return e};import{d as l,a as p,w as i,E as m,y as d,f as u,z as n,p as f,u as j,B as c,J as b,l as _,ab as v,a$ as x}from"./index-ip5gDR6L.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{E as y,a as h}from"./index-CxGOZAw2.js";import{E as V}from"./index-Ca1H-sCf.js";import{E as g}from"./index-D5xppMej.js";import{E}from"./index-DfrOVkw6.js";import{E as O,a as w}from"./index-Br3btmwp.js";import{E as k}from"./index-BY5Lb5w1.js";import{E as C}from"./index-opP5No9I.js";import{E as J}from"./index-BBSYL4g8.js";import{_ as P}from"./_plugin-vue_export-helper-BCo6x5W8.js";import"./use-form-common-props-BwBs_jod.js";import"./index-D-10FtUD.js";import"./index-CHAf6FpU.js";import"./index-fnD_mPvE.js";import"./use-form-item-BDb3ZgPk.js";import"./_initCloneObject-DwsgJamq.js";import"./index-C9KXwj2c.js";import"./index-DbRz3Zuu.js";import"./index-D1xXI5gl.js";import"./token-DWNpOE8r.js";import"./scroll-ChxXkPhM.js";import"./debounce-B9s63_mG.js";import"./index-Jz5GfFad.js";import"./vnode-6FKeug3R.js";import"./index-D-iZIej9.js";const S=P(l({__name:"category-search",props:{modelValue:{}},emits:["update:modelValue","search","reset"],setup(e,{emit:a}){const t=e,o=a,r=p(s({},t.modelValue));i(()=>t.modelValue,(e,a)=>{JSON.stringify(e)!==JSON.stringify(a)&&(r.value=s({},e))},{deep:!0});let l=null;i(r,e=>{l&&clearTimeout(l),l=setTimeout(()=>{o("update:modelValue",s({},e))},100)},{deep:!0});const P=()=>{o("search",r.value)},S=()=>{r.value={name:void 0,status:void 0},o("reset")};return(a,t)=>{const o=m("ripple");return u(),d(j(J),{class:"search-card",shadow:"never"},{default:n(()=>[f(j(y),{ref:"formRef",model:e.modelValue,"label-width":"80px"},{default:n(()=>[f(j(V),{gutter:20},{default:n(()=>[f(j(g),{span:8},{default:n(()=>[f(j(h),{label:"分类名称",prop:"name"},{default:n(()=>[f(j(E),{modelValue:r.value.name,"onUpdate:modelValue":t[0]||(t[0]=e=>r.value.name=e),placeholder:"请输入分类名称",clearable:"",onKeyup:c(P,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),f(j(g),{span:6},{default:n(()=>[f(j(h),{label:"状态",prop:"status"},{default:n(()=>[f(j(O),{modelValue:r.value.status,"onUpdate:modelValue":t[1]||(t[1]=e=>r.value.status=e),placeholder:"请选择状态",clearable:""},{default:n(()=>[f(j(w),{value:1,label:"启用"}),f(j(w),{value:2,label:"禁用"})]),_:1},8,["modelValue"])]),_:1})]),_:1}),f(j(g),{span:6},{default:n(()=>[f(j(h),{"label-width":"0"},{default:n(()=>[f(j(k),null,{default:n(()=>[b((u(),d(j(C),{type:"primary",onClick:P},{default:n(()=>[f(j(v),{class:"mr-1"},{default:n(()=>[f(j(x))]),_:1}),t[2]||(t[2]=_(" 搜索 ",-1))]),_:1})),[[o]]),b((u(),d(j(C),{onClick:S},{default:n(()=>[...t[3]||(t[3]=[_("重置",-1)])]),_:1})),[[o]])]),_:1})]),_:1})]),_:1})]),_:1})]),_:1},8,["model"])]),_:1})}}}),[["__scopeId","data-v-7c3709d1"]]);export{S as default};

View File

@ -0,0 +1 @@
.search-card[data-v-9b5715ff]{margin-bottom:16px}[data-v-9b5715ff] .el-card__body{padding-bottom:0}

View File

@ -0,0 +1 @@
var e=Object.defineProperty,a=Object.getOwnPropertySymbols,t=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,r=(a,t,o)=>t in a?e(a,t,{enumerable:!0,configurable:!0,writable:!0,value:o}):a[t]=o,s=(e,s)=>{for(var l in s||(s={}))t.call(s,l)&&r(e,l,s[l]);if(a)for(var l of a(s))o.call(s,l)&&r(e,l,s[l]);return e};import{d as l,a as p,w as i,E as m,y as d,f as u,z as n,p as f,u as j,B as c,J as b,l as _,ab as x,aW as v}from"./index-DBHnxsy6.js";/* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css *//* empty css */import{E as y,a as E}from"./index-zIv9y_XZ.js";import{E as V}from"./index-BIIDnOvF.js";import{E as h}from"./index-B5f0OwDI.js";import{E as g}from"./index-qSQlj0RO.js";import{E as O,a as w}from"./index-CHZ9Ls9_.js";import{E as C}from"./index-Bi8tRqjc.js";import{E as I}from"./index-r0EsCkBw.js";import{E as J}from"./index-Bh7aAHNM.js";import{_ as k}from"./_plugin-vue_export-helper-BCo6x5W8.js";import"./use-form-common-props-BOfz9xGR.js";import"./index-DZO7eApA.js";import"./index-MOpuiu9Z.js";import"./index-nZra831X.js";import"./use-form-item-ByasanrX.js";import"./_initCloneObject-7oWMW-Ot.js";import"./index-B99ckkYy.js";import"./index-sqhzrW2O.js";import"./index-D2YpA_om.js";import"./token-DWNpOE8r.js";import"./scroll-Cceui3yC.js";import"./debounce-CkazOn_9.js";import"./_baseIteratee-DRiXPtOg.js";import"./index-BC2pRl3S.js";import"./vnode-CqxKQBn0.js";import"./index-DfWQjCuY.js";const z=k(l({__name:"category-search",props:{modelValue:{}},emits:["update:modelValue","search","reset"],setup(e,{emit:a}){const t=e,o=a,r=p(s({},t.modelValue));i(()=>t.modelValue,(e,a)=>{JSON.stringify(e)!==JSON.stringify(a)&&(r.value=s({},e))},{deep:!0});let l=null;i(r,e=>{l&&clearTimeout(l),l=setTimeout(()=>{o("update:modelValue",s({},e))},100)},{deep:!0});const k=()=>{o("search",r.value)},z=()=>{r.value={name:void 0,status:void 0},o("reset")};return(a,t)=>{const o=m("ripple");return u(),d(j(J),{class:"search-card",shadow:"never"},{default:n(()=>[f(j(y),{ref:"formRef",model:e.modelValue,"label-width":"80px"},{default:n(()=>[f(j(V),{gutter:20},{default:n(()=>[f(j(h),{span:8},{default:n(()=>[f(j(E),{label:"分类名称",prop:"name"},{default:n(()=>[f(j(g),{modelValue:r.value.name,"onUpdate:modelValue":t[0]||(t[0]=e=>r.value.name=e),placeholder:"请输入分类名称",clearable:"",onKeyup:c(k,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),f(j(h),{span:6},{default:n(()=>[f(j(E),{label:"状态",prop:"status"},{default:n(()=>[f(j(O),{modelValue:r.value.status,"onUpdate:modelValue":t[1]||(t[1]=e=>r.value.status=e),placeholder:"请选择状态",clearable:""},{default:n(()=>[f(j(w),{value:1,label:"启用"}),f(j(w),{value:2,label:"禁用"})]),_:1},8,["modelValue"])]),_:1})]),_:1}),f(j(h),{span:6},{default:n(()=>[f(j(E),{"label-width":"0"},{default:n(()=>[f(j(C),null,{default:n(()=>[b((u(),d(j(I),{type:"primary",onClick:k},{default:n(()=>[f(j(x),{class:"mr-1"},{default:n(()=>[f(j(v))]),_:1}),t[2]||(t[2]=_(" 搜索 ",-1))]),_:1})),[[o]]),b((u(),d(j(I),{onClick:z},{default:n(()=>[...t[3]||(t[3]=[_("重置",-1)])]),_:1})),[[o]])]),_:1})]),_:1})]),_:1})]),_:1})]),_:1},8,["model"])]),_:1})}}}),[["__scopeId","data-v-9b5715ff"]]);export{z as default};

View File

@ -1 +0,0 @@
.search-card[data-v-7c3709d1]{margin-bottom:16px}[data-v-7c3709d1] .el-card__body{padding-bottom:0}

View File

@ -1 +0,0 @@
import{b as r}from"./index-CxGOZAw2.js";function n(n){return r(n,5)}export{n as c};

View File

@ -0,0 +1 @@
import{b as n}from"./index-zIv9y_XZ.js";function r(r){return n(r,5)}export{r as c};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.form-tip[data-v-d9eb48ea]{margin-left:8px;color:#909399;font-size:12px}

Some files were not shown because too many files have changed in this diff Show More