v0.0.13 — 套餐 features 字段重构 & 用户时间戳自动填充
套餐
features字段从字符串重构为array<string>(新增StringSlice类型,三种历史形态零迁移兼容),后台套餐弹窗改为动态行编辑器避免用户手敲 JSON;同时修复User模型created_at始终为 0、updated_at不刷新的历史 bug,并在用户列表新增「注册时间」列。
中文
✨ 新增功能
后端(Go)
- 套餐
features字段重构为array<string>(Issue #11):model/plan.go新增StringSlice类型,同时实现MarshalJSON/UnmarshalJSON/driver.Valuer/sql.Scanner四个接口,作为[]string在 JSON wire 与 DB text 列之间的双向桥接。- JSON wire 格式统一为
["A","B","C"];DB 仍以 JSON 字符串写入text列,AutoMigrate无 schema 变更。 UnmarshalJSON/Scan兼容历史三种形态:JSON 数组字符串 / 换行分隔纯文本 / JSON 对象,统一归一为[]string。- 新增
Plan.GetFeatures()返回拷贝后的[]string,供 controller 序列化使用,避免外部修改共享底层 slice。 - 新增
model/plan_test.go:16 个用例 覆盖 Marshal/Unmarshal/Value/Scan 往返、nil、空、空白、CRLF、非法 JSON 等边界。
GetCurrentPlan返回 features 数组形态(controller/plan.go):data["features"]从裸字符串改为Plan.GetFeatures(),与/api/plan/detail一致。前端Plans.vue::parseFeatures已Array.isArray兼容,无需联动修改。User模型时间戳自动填充(model/user.go):- 新增
User.BeforeCreate钩子:CreatedAt/UpdatedAt为 0 时自动写入helper.GetTimestamp()。 - 新增
User.BeforeUpdate钩子:任何Updates(...)路径都会刷新UpdatedAt。 - 一并覆盖
Register/CreateUser/UpdateUser/ManageUser/EmailBind/GenerateAccessToken等所有写入路径。 - 修复
/api/user/?p=0接口中created_at始终为 0、updated_at不刷新的历史 bug。
- 新增
前端(web/default-pro/)
- 后台套餐弹窗「特性说明」改为动态行编辑器(
views/setting/PlanSetting.vue):- 删除原
<a-textarea>,替换为.features-editor容器:每行<a-input allow-clear>+ 删除按钮,末尾「添加特性」虚线按钮。 - 新增
formFeaturesref 与form.features解耦,专管动态行;提供addFeature/addFeatureAfter/removeFeature/updateFeature四个操作函数。 - 某行
<a-input>按 Enter 自动新增一行;末行空且唯一时禁用删除(防误清空)。 openModal用utils/plan.featuresFromRecord回填(向后兼容旧数据形态);handleSave用sanitizeFeaturesList过滤空行后直接以数组形态POST/PUT/api/plan/,零格式歧义。- 新增
.features-editor/.features-editor-row/.features-editor-add/.features-editor-remove样式。
- 删除原
- 后台用户列表新增「注册时间」列(
views/user/User.vue):- 表头新增「注册时间」列,渲染
u.created_at;单元格显示YYYY-MM-DD短日期,hover tooltip 显示完整本地时间。 - 新增
formatDate/formatDateTime工具函数,无值时回退-。 - 调整
.list-head/.list-row的 grid 模板,新增 120px 列宽。
- 表头新增「注册时间」列,渲染
- 新增
src/utils/plan.js纯函数工具 + 14 个node:test单测:normalizeFeaturesRaw(raw):把后端可能返回的多种形态(数组 / JSON 字符串 / 换行文本 / CRLF / JSON 对象 / null / 非法 JSON / 数字)统一归一为[]string,丢弃空项。sanitizeFeaturesList(list):清洗表单输入,去空白、返回新数组不修改入参。featuresFromRecord(raw):把后端字段转成弹窗表单数组(空记录回退到 1 行空输入)。buildEmptyFeaturesForm():返回 1 个空字符串的初始表单。
🐛 问题修复
- 修复
User表created_at始终为 0、updated_at不刷新的历史 bug:通过新增User.BeforeCreate/BeforeUpdate钩子(见上)覆盖所有写入路径。 - 修复后台套餐弹窗「特性说明」易填错导致前端为空:原本要求 admin 手敲
["A","B","C"]JSON 字符串,用户格式填错前端就显示空;改为每行<a-input>动态行编辑器,提交时由sanitizeFeaturesList清洗,零格式歧义。
📚 文档
docs/API.md同步更新套餐 features 字段类型(Issue #11):- 字段说明表
features行:string→array<string>,措辞改为「功能特性列表,每项一行展示在用户端套餐卡」。 GET /api/plan/响应体示例:"features": "功能特性描述"→["API 调用 1000 次/月", "支持 GPT-4o"]。POST /api/plan/请求体示例同步为数组形式。
- 字段说明表
⚠️ 升级注意事项
- 零数据库迁移:
Plan.Features字段类型未变(仍是text列),StringSlice仅在 Go 层做 JSON ↔ DB 双向桥接;现有数据的features文本("A\nB\nC"或"{\"API\":true}"等历史形态)会在首次读取时自动归一化为数组。 - API 兼容性:
GET /api/plan/与GET /api/plan/current的features字段由字符串改为数组;前端Plans.vue::parseFeatures早已Array.isArray兼容,现有用户端无感知。 - 管理端操作:admin 进入「设置 → 套餐管理」编辑任意套餐时,特性说明将以「每行一个 input + 添加/删除」形式展示,老数据自动按行展开。
- 后端编译:新增
model/plan_test.go与model/user_test.go,纯单元测试使用glebarez/sqlite内存库,不依赖外部 DB。 - 前端构建:无新增依赖;建议
pnpm install && npm run build后发布。