Repository navigation
v0.0.12
·
1156 commits
to master
since this release
v0.0.11 — 在线充值(topup)全链路打通
用户可在控制台「我的余额」卡片直接发起在线充值,复用同一套微信 / 支付宝支付通道;后台新增独立的「充值」设置 Tab,管理员可配置快捷金额、自定义金额与换算比例;同步沉淀《AGENTS.md》开发规范并更新全语种 README。
中文
✨ 新增功能
后端(Go)
- 新增「在线充值」业务模块(
OrderTypeTopup=2):- 新增
model/topup.go,定义TopupPreset、CreateTopupOrderInput、TopupOrderPlanInfo等结构,提供CreateTopupOrder、ActivateTopupByOrder、ResolveTopupAmount、GetTopupSettings、SaveTopupSettings等业务函数。 - 复用
GenerateOrderNo("TP")生成充值订单号(TP 前缀),写入与套餐订单同一张orders表的type=2记录,节省一张表。 ActivateTopupByOrder幂等:订单已支付直接返回;激活时调IncreaseUserQuota加额度并RecordTopupLog,TODO标注退款时不回退 quota(待统一订单管理上线)。- 启动时通过
model.AutoMigrate自动建表,无需手写 DDL。
- 新增
- 新增充值系统设置键(
model/system_setting.go):topup.enabled(总开关)、topup.allow_custom(是否允许自定义金额)、topup.presets(JSON 数组:[{amount, bonus_quota}, ...])、topup.exchange_rate(默认1,1 元 = = 1 quota,仅作用于自定义金额)。- 分类常量
SystemSettingCategoryTopup = "topup"。
- 新增 HTTP 接口:
POST /api/topup/order(CreateTopupOrder,用户认证):参数{ amount, preset_amount, pay_method },前置校验开关 / 通道 / 金额,命中预设或自定义规则后写订单,复用controller/buildPayInfo拿 pay_url / qr_code。GET /api/setting/topup与PUT /api/setting/topup(GetTopupSettings/PutTopupSettings,Root 权限):读写上述 4 个 key。
- 支付回调按订单类型分发(
controller/payment.go::processNotify):order.Type == OrderTypeTopup→ 调ActivateTopupByOrder给用户加 quota;- 其余 → 调
ActivatePackageByOrder激活套餐。 MockPay(admin 测试接口)同步按 type 分发,提示文案分别显示「余额已到账」/「套餐已激活」。
前端(web/default-pro/)
- 新增可复用组件
components/TopupModal.vue:- props:
modelValue、settings、title;emits:update:modelValue、success、error、pay。 - 顶部「选择金额」chip 列表:依次渲染所有
settings.presets,最后一个 chip 固定为「自定义」(当settings.allow_custom=true时显示),点击后才显示金额输入框。 - 中部支付方式选择器(
pay-picker-item微信#07C160/ 支付宝#1677FF官方品牌色),复用 Plans.vue 既有样式。 - 底部「支付金额」+「确认充值」按钮;选中自定义但未填金额时禁用提交。
- props:
- 新增设置页
views/setting/TopupSetting.vue(root 可见):表单竖排三段:①总开关(enabled)②允许自定义金额(allow_custom)③自定义金额 1 元 = X quota(exchange_rate,默认1);快捷金额用<a-table>行行内编辑(amount/bonus_quota),支持新增 / 删除;提交前前端预校验金额重复(validateTopupPresets)。 - 新增路由
/setting/topup:router/index.js注册子路由,Setting.vue菜单新增「充值」项(root 可见,iconicon-subscribe-add)。 - 新增
api/topup.js:topupApi.createOrder({ amount, preset_amount, pay_method })。 - 扩展
api/setting.js:getTopup()/putTopup(data)。 - 新增
utils/topup.js纯函数工具:formatNumber/formatAmount/validateTopupPresets/calcCustomBonus,可在 Node 环境下用node:test单测。 - 仪表盘接入充值弹窗:
- 控制台
Dashboard.vue余额卡的「充值」按钮改为弹出TopupModal,点击前预拉/api/setting/topup+/api/payment/status,任一失败给出明确 toast;<a-modal>使用:visible+@update:visible(踩坑修复:曾误用:model-value,导致弹窗永远不显示)。 - 同时弹出二维码 / 转账信息
<a-modal,刷新余额(/api/user/self)。
- 控制台
- 订单管理表格新增「订单类型」列(
Orders.vue):套餐 / 充值 两种两色 chip(type-plan蓝 /type-topup橙),方便用户区分。 - 移除运营 Tab 内的「允许余额充值」占位开关(
OperationSetting.vue):该字段已迁移到新充值 Tab,UI 与后端GetPlanSettings/PutPlanSettings同步删除读写;DB 中旧plan.allow_topup行保留无害。 - 修复套餐界面硬编码套餐名白名单(
Plans.vue):删除VALID_PLAN_NAMES = ['lite','air','pro','max']过滤逻辑,改为显示后端返回的所有套餐(用户反映该过滤导致测试套餐不可见)。
🐛 问题修复
- 修复仪表盘充值按钮点击无反应:原
<TopupModal>在TopupModal.vue内部把 arco<a-modal>的可见性绑定到了:model-value,而 arco 的<a-modal>可见性属性是visible,导致v-model="topupModalVisible=true时弹窗永远不显示。已改为:visible="modelValue"+@update:visible="(v) => emit('update:modelValue', v)"。 - 修复充值按钮缓存导致的「管理员开启充值但仪表盘无反应」:原代码仅在
topupSettings.value为 null 时拉取一次,admin 刚开启充值但仪表盘缓存旧值时按钮不会再次请求。已改为每次点击都强制重新拉取/api/setting/topup,并把错误提示文案改成「请联系管理员在「设置-充值」中开启」。 - 修复
<a-spin>在 TopupSetting 设置页父容器宽度异常:补齐style="width: 100%"。 - 修复 TopupSetting 表单横排三列不对齐:改为
layout="vertical"三段竖排。
🚀 数据完整性 / 校验
- 快捷金额金额不能重复:后端
model.SaveTopupSettings用seenAmounts map检测重复金额,重复时返回「快捷金额重复:XX 元已存在」并整体拒绝保存;前端utils/topup.js::validateTopupPresets提前预校验,避免一次无效请求。
📚 文档
- 新增项目根目录
AGENTS.md(480 行):面向 Agent / 贡献者的开发规范速查,覆盖 TL;DR / 项目结构 / 开发环境 / 提交规范(Conventional Commits + 中英双语 body + 一文件一 commit 粒度)/ 版本号 / 命名(topup全局统一)/ 注释约定(文件级 4 行注释 + 导出符号注释 + TODO/XXX + 修改时如何更新)/ 后端规范(响应格式、model 不依赖 Gin、订单号前缀 TB/UP/TP)/ 前端规范(arco<a-modal>用:visible而非:model-value、<a-spin>必须style="width:100%"、工具函数用 node:test)/ 数据库 / 踩坑清单 / 工作流程(先 Plan 模式输出方案再编码)。 - 同步更新
README.md与 7 个语言版本(readme/README.{en,zh-TW,ja,ko,ar,de,ru}.md):新增「💰 在线充值(余额)」功能亮点章节;订单与支付章节补充订单类型列 +processNotify分发说明;开发计划「在线充值」「充值设置中心」从规划移入已完成,新增「充值退款闭环」为进行中。
🔧 重构 / 工程
- 移除运营 Tab 的
plan.allow_topup字段读写(controller/setting_payment.go):精简GetPlanSettings/PutPlanSettings返回结构,DB 行保留(不会主动删除)。 - 清理调试日志:移除为排查充值按钮不响应问题加在
Dashboard.vue::onRechargeClick的console.log。
⚠️ 升级注意事项
- 数据库迁移:新增 4 个
system_settings键(topup.enabled/topup.allow_custom/topup.presets/topup.exchange_rate),无需迁移脚本,AutoMigrate+ 首次调用会按需写入。 orders表复用:本次新增 type=2 的充值订单与既有 type=1 套餐订单存同一张表,无 DDL 变更。- 运营 Tab 「套餐运营」小节:UI 上不再有「允许余额充值(仅占位)」开关;如需启用充值,请进入设置 → 充值。
- 充值功能默认关闭(
topup.enabled未设置时为 false),管理员需到「设置 → 充值」手动开启并配置至少 1 个 preset 或允许自定义金额,前端充值按钮才会触发弹窗。 - 退款:本期不支持;充值订单
status=3仅标记,quota 不回退,待统一订单管理上线后补全。 - 前端构建:新增依赖
qrcode(已在 pnpm-lock.yaml),建议pnpm install && npm run build后发布。
English
✨ New Features
Go backend
- New online recharge module (
OrderTypeTopup=2):- Added
model/topup.godefiningTopupPreset,CreateTopupOrderInput,TopupOrderPlanInfo, plus business functionsCreateTopupOrder,ActivateTopupByOrder,ResolveTopupAmount,GetTopupSettings,SaveTopupSettings. - Reuses
GenerateOrderNo("TP")for recharge order numbers (prefixTP); writes them to the sameorderstable astype=2, avoiding an extra table. ActivateTopupByOrderis idempotent (paid orders return immediately); on activation it callsIncreaseUserQuotaand writesRecordTopupLog. ATODOmarks quota non-reversal on refund (awaiting unified admin order management).model.AutoMigratecreates the table at startup — no manual DDL.
- Added
- New recharge system-setting keys (
model/system_setting.go):topup.enabled(master switch),topup.allow_custom(allow custom amount),topup.presets(JSON array of[{amount, bonus_quota}, ...]),topup.exchange_rate(default1, i.e. 1 CNY = 1 quota; applies only to custom amounts).- Category constant
SystemSettingCategoryTopup = "topup".
- New HTTP endpoints:
POST /api/topup/order(CreateTopupOrder, user-auth): body{ amount, preset_amount, pay_method }; pre-validates the switch / channel / amount, then persists the order and reusescontroller/buildPayInfoforpay_url/qr_code.GET /api/setting/topup&PUT /api/setting/topup(GetTopupSettings/PutTopupSettings, root only): read/write the four keys above.
- Payment callback dispatched by order type (
controller/payment.go::processNotify):order.Type == OrderTypeTopup→ActivateTopupByOrderto credit quota.- Otherwise →
ActivatePackageByOrderto activate the plan. MockPay(admin test endpoint) dispatches the same way and surfaces "balance credited" / "plan activated" copy.
Frontend (web/default-pro/)
- New reusable component
components/TopupModal.vue:- Props:
modelValue,settings,title; emits:update:modelValue,success,error,pay. - Top "Choose amount" chip row: renders all
settings.presetsin order; the last chip is always "Custom" whensettings.allow_custom=true. The amount input is shown only after clicking Custom. - Payment-method picker (
pay-picker-item) with WeChat (#07C160) and Alipay (#1677FF) brand colors, reusing the styles fromPlans.vue. - Footer shows "Pay amount" + "Confirm recharge"; submission is disabled when Custom is selected but no amount is entered.
- Props:
- New settings view
views/setting/TopupSetting.vue(root-only): vertical layout with three sections — ① master switch (enabled), ② allow custom amount (allow_custom), ③ custom-amount 1 CNY = X quota (exchange_rate, default1); preset rows use an<a-table>with inline-editableamount/bonus_quotacolumns, plus add / delete buttons. Front-end pre-validates duplicate amounts viavalidateTopupPresetsbefore submission. - New route
/setting/topup: registered inrouter/index.js; sidebar menu entry "充值" added inSetting.vue(root-only, iconicon-subscribe-add). - New
api/topup.js:topupApi.createOrder({ amount, preset_amount, pay_method }). - Extended
api/setting.js:getTopup()/putTopup(data). - New
utils/topup.jspure-function helpers:formatNumber,formatAmount,validateTopupPresets,calcCustomBonus— usable in Node.js vianode:test. - Dashboard recharge button wired up:
Dashboard.vuenow opensTopupModalfrom the "Recharge" button on the balance card, prefetching/api/setting/topup+/api/payment/statusto give precise error copy when either is missing. The underlying<a-modal>uses:visible+@update:visible(bug fix: the previous:model-valuebinding never showed the dialog because Arco's<a-modal>prop isvisible).- Also opens a QR-code / bank-transfer modal and refreshes the balance via
/api/user/self.
- Order-management table gets an "Order type" column (
Orders.vue): two-color chips for "Plan" / "Recharge" (type-planblue,type-topuporange). - Removed the "Allow balance recharge" placeholder switch from the Operations tab (
OperationSetting.vue): that field has migrated to the new Top-up tab; the UI and backendGetPlanSettings/PutPlanSettingsno longer read or write it. The DB row is kept for backwards compatibility. - Removed the hard-coded plan-name whitelist from
Plans.vue: theVALID_PLAN_NAMES = ['lite','air','pro','max']filter was hiding test plans reported by the user; we now render whatever the backend returns.
🐛 Bug Fixes
- Dashboard "Recharge" button did nothing on click:
TopupModal.vuepreviously bound the inner<a-modal>to:model-value="modelValue", but Arco's<a-modal>visibility prop isvisible, so the dialog never showed even whentopupModalVisibleflipped totrue. Switched to:visible="modelValue"+@update:visible="(v) => emit('update:modelValue', v)". - Stale-cached settings made the recharge button appear unresponsive after admin enabled recharge: the dashboard only fetched settings when
topupSettings.valuewas null, so togglingtopup.enabledin admin did not propagate until the dashboard was hard-reloaded. Now we force-re-fetch on every click and surface clearer error copy ("contact the admin to enable it under Settings → Top-up"). <a-spin>width anomaly on TopupSetting page: added the requiredstyle="width: 100%".- Misaligned three-column form on TopupSetting: switched to
layout="vertical"so the three sections stack vertically.
🚀 Data integrity / validation
- Preset amounts cannot repeat:
model.SaveTopupSettingsuses aseenAmountsmap; duplicates return "快捷金额重复:XX 元已存在" and the whole save is rejected. The newutils/topup.js::validateTopupPresetsmirrors this check on the frontend to avoid round-tripping invalid payloads.
📚 Documentation
- New
AGENTS.mdat project root (480 lines): a single-source-of-truth guide for agents and contributors covering TL;DR / project layout / dev environment / commit conventions (Conventional Commits + bilingual body + one-file-per-commit granularity) / versioning / naming (topupis the global name) / comment conventions (4-line file header, exported-symbol comments, TODO/XXX, edit-time updates) / backend conventions (response format, model layer is HTTP-free, order-number prefixes TB/UP/TP) / frontend conventions (Arco<a-modal>uses:visiblenot:model-value,<a-spin>requiresstyle="width:100%", util tests vianode:test) / database / pitfall checklist / recommended workflow (Plan mode → per-file commit). - Synchronized
README.mdand all 7 language copies (readme/README.{en,zh-TW,ja,ko,ar,de,ru}.md): added a new "💰 Online Top-up (Quota Balance)" feature-highlight section; the existing "Orders & Real Payments" section now mentions the order-type column andprocessNotifydispatch; the "Online quota top-up" and "Top-up settings center" roadmap items moved from Planned → Done; added a new "Top-up refund loop" in-progress item awaiting unified admin order management.
🔧 Refactor / tooling
- Removed
plan.allow_topupreads/writes fromcontroller/setting_payment.go: trimmedGetPlanSettings/PutPlanSettingsresponse shape; DB row preserved. - Cleaned up debug logs from
Dashboard.vue::onRechargeClick(left over from the "recharge button does nothing" investigation).
⚠️ Upgrade Notes
- Database migration: 4 new
system_settingskeys (topup.enabled/topup.allow_custom/topup.presets/topup.exchange_rate). No migration script required —AutoMigrateplus first-write will populate them as needed. orderstable is reused: newtype=2recharge rows share the existing table withtype=1plan orders; no DDL change.- Operations tab "Plan Operations" sub-section: the placeholder switch "Allow balance recharge" has been removed from the UI. Use Settings → Top-up instead.
- Recharge is off by default (
topup.enabledisfalseuntil an admin toggles it). The dashboard button will not open the dialog until an admin enables recharge and configures at least one preset (or allows custom amounts). - Refunds are not supported in this release: recharge orders with
status=3are merely flagged; quota is not reversed. The full refund loop will land with the unified admin order-management feature. - Frontend build:
qrcodeis now a runtime dependency (already inpnpm-lock.yaml). Recommendpnpm install && npm run buildbefore publishing.