Skip to content

v0.0.13

Choose a tag to compare

@github-actions github-actions released this 07 Sep 16:16
· 1146 commits to master since this release

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> + 删除按钮,末尾「添加特性」虚线按钮。
    • 新增 formFeatures ref 与 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 后发布。

English

✨ New Features

Go backend

  • Refactored the plan features field to array<string> (Issue #11):
    • Added a new StringSlice type in model/plan.go that implements MarshalJSON, UnmarshalJSON, driver.Valuer, and sql.Scanner — it bridges []string between the JSON wire format and the DB text column.
    • JSON wire format unified to ["A","B","C"]; the DB still stores a JSON string in the text column, so AutoMigrate is a no-op (zero schema migration).
    • UnmarshalJSON / Scan accept all three historical shapes (JSON array string, newline-delimited plain text, JSON object) and normalize them to []string.
    • Added Plan.GetFeatures() returning a copy-safe []string for controller serialization so external code never mutates the underlying slice.
    • Added model/plan_test.go: 16 cases covering Marshal/Unmarshal/Value/Scan round-trips plus nil, empty, whitespace, CRLF, and invalid JSON edge cases.
  • GetCurrentPlan returns features as an array (controller/plan.go): replaced data["features"] = up.Plan.Features (raw string) with data["features"] = up.Plan.GetFeatures() so it matches /api/plan/detail. The user-side Plans.vue::parseFeatures already handled Array.isArray, no coordinated front-end change required.
  • User model auto-fills timestamps (model/user.go):
    • Added User.BeforeCreate: when CreatedAt / UpdatedAt are 0 they are auto-filled via helper.GetTimestamp().
    • Added User.BeforeUpdate: every Updates(...) path now refreshes UpdatedAt.
    • Covers every write path: Register, CreateUser, UpdateUser, ManageUser, EmailBind, GenerateAccessToken, etc.
    • Fixes the historical bug where /api/user/?p=0 returned created_at=0 and never-refreshed updated_at.

Frontend (web/default-pro/)

  • Admin plan modal's "Feature description" is now a dynamic row editor (views/setting/PlanSetting.vue):
    • Replaced the <a-textarea> with a .features-editor container: one <a-input allow-clear> per row + a delete button, with a trailing dashed "Add feature" button.
    • Introduced a formFeatures ref decoupled from form.features; added addFeature / addFeatureAfter / removeFeature / updateFeature for row manipulation.
    • Pressing Enter on any row auto-appends an empty row; when the last row is empty and the only one, delete is disabled to prevent accidental emptying.
    • openModal backfills via utils/plan.featuresFromRecord (backward-compatible with legacy shapes); handleSave POSTs/PUTs /api/plan/ with a sanitizeFeaturesList-cleaned array — zero format ambiguity.
    • Added .features-editor / .features-editor-row / .features-editor-add / .features-editor-remove styles.
  • Added a "Registration time" column to the admin user list (views/user/User.vue):
    • New header column rendering u.created_at; cell shows YYYY-MM-DD, hover tooltip reveals the full local datetime.
    • Added formatDate / formatDateTime helpers; falls back to - on missing/zero timestamps.
    • Adjusted .list-head / .list-row grid template to add a 120px column.
  • New src/utils/plan.js pure-function helpers + 14 node:test unit tests:
    • normalizeFeaturesRaw(raw): normalize any of the historical shapes (array / JSON string / newline text / CRLF / JSON object / null / invalid JSON / numbers) to []string, dropping blanks.
    • sanitizeFeaturesList(list): trim, filter blanks, return a fresh array without mutating the input.
    • featuresFromRecord(raw): convert the backend field into modal form rows (empty record falls back to a single empty row).
    • buildEmptyFeaturesForm(): returns a single empty-string initial form.

🐛 Bug Fixes

  • Fixed the historical User bug where created_at was always 0 and updated_at never refreshed: addressed by the new User.BeforeCreate / BeforeUpdate hooks above, covering every write path.
  • Fixed the admin plan modal "Feature description" being easy to mistype and rendering empty on the user side: the textarea required admin to hand-type a JSON ["A","B","C"] string, and any typo produced an empty list. The new dynamic-row editor with sanitizeFeaturesList removes the format ambiguity.

📚 Documentation

  • docs/API.md updated to reflect the new features type (Issue #11):
    • Field-table row features: string → array<string>; wording updated to "list of features, one per row in the user-facing plan card".
    • GET /api/plan/ response sample: "features": "功能特性描述" → ["API 调用 1000 次/月", "支持 GPT-4o"].
    • POST /api/plan/ request sample synced to the array form.

⚠️ Upgrade Notes

  • Zero database migration: Plan.Features column type is unchanged (still text); StringSlice is purely a Go-side bridge between JSON and the DB column. Existing data — whether plain text like "A\nB\nC" or legacy JSON-object shapes — is normalized to []string automatically on first read.
  • API compatibility: GET /api/plan/ and GET /api/plan/current now return features as an array. The user-side Plans.vue::parseFeatures already handled Array.isArray, so existing user-facing code is unaffected.
  • Admin UX: when an admin opens any plan in Settings → Plan Management, the feature list now renders as one <a-input> per row with add/delete buttons; legacy data is split into rows automatically.
  • Backend build: added model/plan_test.go and model/user_test.go, both pure unit tests against glebarez/sqlite in-memory + gorm.AutoMigrate, no external DB required.
  • Frontend build: no new dependencies. Recommend pnpm install && npm run build before publishing.