Skip to content

feat(admin): 用户管理列表展示订阅信息并支持按订阅套餐筛选 - #4134

Open
ImogeneOctaviap794 wants to merge 4 commits into
QuantumNous:mainfrom
ImogeneOctaviap794:dev
Open

feat(admin): 用户管理列表展示订阅信息并支持按订阅套餐筛选#4134
ImogeneOctaviap794 wants to merge 4 commits into
QuantumNous:mainfrom
ImogeneOctaviap794:dev

Conversation

@ImogeneOctaviap794

@ImogeneOctaviap794 ImogeneOctaviap794 commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

📝 变更描述 / Description

管理员的用户管理页面新增「订阅信息」列,直观展示每个用户的活跃订阅状态,并支持按订阅套餐筛选用户。

痛点: 管理员此前无法在用户列表中快速了解用户的订阅状态,需要逐个点击进入订阅管理弹窗才能查看。

后端:

  • model/subscription.go:新增 GetActiveSubscriptionsByUserIds 批量查询方法,一次 SQL 返回多个用户的全部活跃订阅,避免 N+1 查询
  • controller/subscription.go:新增 AdminBatchActiveSubscriptions 接口,接收 user_ids 数组(上限 100),返回批量订阅数据
  • router/api-router.go:注册 POST /api/subscription/admin/users/batch_active_subscriptions
  • model/user.goSearchUsers 新增可选参数 planId,通过子查询筛选持有指定活跃订阅的用户
  • controller/user.goSearchUsers 解析 plan_id query 参数,统一返回用户友好错误消息

前端:

  • useUsersData.jsx
    • 批量接口替代 N+1 请求
    • 使用 useRef 竞态保护(用户列表 + 订阅请求均受保护)
    • 区分 null(加载中/错误)和 {}(数据已加载)状态
    • 使用 URLSearchParams 编码搜索参数
  • UsersFilters.jsx:新增「选择订阅」下拉筛选框
  • UsersColumnDefs.jsx
    • 新增「订阅信息」列,支持多订阅 Tag 展示
    • 不同套餐自动分配不同颜色,过期订阅灰色
    • Tooltip 显示完整到期时间(含时分秒)和额度使用量
    • 加载中显示 -,加载完成后无订阅显示「无订阅」

i18n:

  • 7 个语言文件新增 5 个翻译 key:选择订阅无订阅到期订阅中订阅信息

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix)
  • ✨ 新功能 (New feature)
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

N/A

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自撰写此描述,去除了 AI 原始输出的冗余。
  • 深度理解: 我已完全理解这些更改的工作原理及潜在影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过了测试或手动验证。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

image

Summary by CodeRabbit

  • New Features

    • Filter users by subscription plan and view per-user subscription details in the users table (supports multiple subscriptions, color-coded/status tags).
    • Admin batch lookup for active subscriptions to populate subscription displays.
  • UI / UX

    • Subscription selector in user filters; new “Subscription” column with tooltips, empty/expired states, and interactive tags.
  • Localization

    • Added subscription-related translations for EN, FR, JA, RU, VI, ZH‑CN, and ZH‑TW.

后端:
- 新增 GetActiveSubscriptionsByUserIds 批量查询活跃订阅
- 新增 POST /api/subscription/admin/users/batch_active_subscriptions 接口
- SearchUsers 支持 plan_id 参数筛选订阅用户

前端:
- 用户列表新增订阅信息列,支持多订阅 Tag 展示
- 不同套餐自动分配不同颜色,过期订阅灰色
- Tooltip 显示到期时间和额度使用量
- 新增选择订阅下拉筛选框
- 批量接口替代 N+1 请求,含竞态保护

i18n:
- 7 种语言新增 5 个翻译 key
- controller/user.go: 不向客户端暴露原始解析错误,合并为统一错误消息
- UsersColumnDefs.jsx: toLocaleString() 显示完整到期时间(含时分秒)
- UsersColumnDefs.jsx: 区分 null(加载中) 和 {}(无订阅) 状态
- useUsersData.jsx: 为用户列表请求添加竞态保护(latestUserRequestRef)
- useUsersData.jsx: 使用 URLSearchParams 编码搜索参数
- vi.json: 消除选择订阅/选择订阅套餐的歧义翻译
@coderabbitai

coderabbitai Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 66b59136-16c1-4366-a84b-9621b1cf1e7c

📥 Commits

Reviewing files that changed from the base of the PR and between 2cc1dac and d40ca16.

📒 Files selected for processing (1)
  • web/src/i18n/locales/zh-TW.json

Walkthrough

Adds a backend admin batch endpoint to fetch active subscriptions for multiple users, extends user search to filter by plan, adds frontend plan/filter UI and per-user subscription column, implements a hook to fetch plans and batch subscription data, and adds i18n entries for subscription UI labels.

Changes

Cohort / File(s) Summary
Admin subscription endpoint
controller/subscription.go, router/api-router.go
New AdminBatchActiveSubscriptionsRequest and handler AdminBatchActiveSubscriptions; route POST /api/subscription/admin/users/batch_active_subscriptions registered.
User controller
controller/user.go
SearchUsers reads optional plan_id query, validates it, and forwards to model search with/without plan filter.
Subscription model
model/subscription.go
Added GetActiveSubscriptionsByUserIds(userIds []int) (map[int][]UserSubscription, error) to query active, non-expired subscriptions and group results by user_id.
User model
model/user.go
SearchUsers signature extended to accept optional planId ...int and applies an active-subscription plan filter when provided.
Frontend — table & rendering
web/src/components/table/users/UsersColumnDefs.jsx, web/src/components/table/users/UsersTable.jsx
Added "订阅信息" column, tag color helpers, and passed userSubscriptions/planOptions into column defs.
Frontend — filters & page
web/src/components/table/users/UsersFilters.jsx, web/src/components/table/users/index.jsx
Added optional plan dropdown filter and threaded planOptions prop from page to filters.
Frontend — data hook
web/src/hooks/users/useUsersData.jsx
Added planOptions, userSubscriptions, staleness refs; searchPlan filter; fetchPlans(); loadUserSubscriptions() posts to the new admin batch endpoint.
i18n/locales
web/src/i18n/locales/{en,fr,ja,ru,vi,zh-CN,zh-TW}.json
Added subscription-related translation keys across multiple locales.

Sequence Diagram(s)

sequenceDiagram
    participant UI as User (UI)
    participant Page as UsersPage
    participant Hook as useUsersData
    participant API as Backend API
    participant DB as Database

    UI->>Page: open page / select plan filter
    Page->>Hook: init / searchUsers(start,page,plan)
    Hook->>API: GET /api/subscription/admin/plans
    API->>DB: SELECT plans
    DB-->>API: plans
    API-->>Hook: planOptions

    Hook->>API: GET /api/user/search?plan_id=X
    API->>DB: Query users (optionally filtered by active subscription plan)
    DB-->>API: users[]
    API-->>Hook: users[]

    Hook->>API: POST /api/subscription/admin/users/batch_active_subscriptions {user_ids: [...]}
    API->>DB: SELECT subscriptions WHERE user_id IN (...) AND status='active' AND end_time>now
    DB-->>API: subscriptions[]
    API-->>Hook: {userId: [subscriptions]}
    Hook-->>Page: users + planOptions + userSubscriptions
    Page-->>UI: render table with subscription tags
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • seefs001
  • creamlike1024

Poem

🐰 A hop, a fetch, a batch request so neat,
Plans and badges lined up row by row,
Tags that shimmer, colors meet,
Subscriptions found — now watch them glow! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main feature: adding a subscription info column to the user management list and enabling filtering by subscription plan, which aligns with the core changes across backend and frontend.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
web/src/i18n/locales/ru.json (1)

2555-2555: Consider a more contextually appropriate status label.

The Chinese "订阅中" is translated as "Подписан" (subscribed, masculine past participle). While grammatically acceptable, there are two concerns:

  1. Gender mismatch: Russian "подписка" (subscription) is feminine, but "Подписан" is masculine
  2. Clarity: For a status label, "Активна" (Active) or "Действует" (Valid/In effect) would be more intuitive and grammatically consistent
♻️ Suggested alternatives
-    "订阅中": "Подписан",
+    "订阅中": "Активна",

or

-    "订阅中": "Подписан",
+    "订阅中": "Действует",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/ru.json` at line 2555, The translation for the key "订阅中"
in ru.json uses "Подписан" which is masculine and may be unclear as a status;
update the value to a context-appropriate, grammatically consistent Russian
status such as "Активна" or "Действует" (or another feminine/neutral form
depending on UI context) by replacing the current mapping for "订阅中" in
web/src/i18n/locales/ru.json with the chosen term so the status reads correctly
in Russian.
controller/user.go (1)

257-257: Prefer an i18n-backed error key over inline literal.

Using a hardcoded "无效的订阅套餐ID" here makes this path harder to localize and less consistent with other ApiErrorI18n responses in the same controller.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@controller/user.go` at line 257, Replace the hardcoded Chinese message call
common.ApiErrorMsg(c, "无效的订阅套餐ID") with the i18n-backed error helper (e.g.,
common.ApiErrorI18n) and an appropriate i18n key such as
"error.invalid_subscription_plan_id" (or match your existing key namespace) so
the controller uses localized messages; update the call site where the literal
appears (in controller/user.go) to call common.ApiErrorI18n(c,
"error.invalid_subscription_plan_id") and ensure the corresponding translation
entry is present in your i18n resource files.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/i18n/locales/ru.json`:
- Line 686: The translation for the Chinese key "到期" in ru.json is incorrect
(currently "Истекает"); update the Russian value for the key "到期" to the
past-tense feminine form "Истекла" so it correctly conveys "expired" for
subscriptions (replace the value associated with the "到期" key).
- Line 2556: The Russian translation for the JSON key "订阅信息" is incomplete: it
should preserve the "information" aspect; update the value for the key "订阅信息"
(currently "Подписка") to a full equivalent like "Информация о подписке" (or a
shorter UI-friendly alternative such as "Сведения о подписке") so the column
header correctly conveys "Subscription information".

In `@web/src/i18n/locales/vi.json`:
- Line 1540: The translation for the JSON key "无订阅" should be updated to clearer
Vietnamese wording for table/filter UI: replace the current value ("Không có
đăng ký") with "Chưa có đăng ký" (or the agreed alternative such as "Không có
gói đăng ký") for the key "无订阅" everywhere it appears in vi.json (including the
other occurrences noted); ensure you update all identical keys consistently,
keep valid JSON syntax (quotes/commas), and run the i18n/JSON linter to verify
no formatting errors.

---

Nitpick comments:
In `@controller/user.go`:
- Line 257: Replace the hardcoded Chinese message call common.ApiErrorMsg(c,
"无效的订阅套餐ID") with the i18n-backed error helper (e.g., common.ApiErrorI18n) and
an appropriate i18n key such as "error.invalid_subscription_plan_id" (or match
your existing key namespace) so the controller uses localized messages; update
the call site where the literal appears (in controller/user.go) to call
common.ApiErrorI18n(c, "error.invalid_subscription_plan_id") and ensure the
corresponding translation entry is present in your i18n resource files.

In `@web/src/i18n/locales/ru.json`:
- Line 2555: The translation for the key "订阅中" in ru.json uses "Подписан" which
is masculine and may be unclear as a status; update the value to a
context-appropriate, grammatically consistent Russian status such as "Активна"
or "Действует" (or another feminine/neutral form depending on UI context) by
replacing the current mapping for "订阅中" in web/src/i18n/locales/ru.json with the
chosen term so the status reads correctly in Russian.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bb0da1ec-62be-4610-8637-57ba16c2c97f

📥 Commits

Reviewing files that changed from the base of the PR and between 960bf9c and fc8d61f.

⛔ Files ignored due to path filters (1)
  • web/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • controller/subscription.go
  • controller/user.go
  • model/subscription.go
  • model/user.go
  • router/api-router.go
  • web/src/components/table/users/UsersColumnDefs.jsx
  • web/src/components/table/users/UsersFilters.jsx
  • web/src/components/table/users/UsersTable.jsx
  • web/src/components/table/users/index.jsx
  • web/src/hooks/users/useUsersData.jsx
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-CN.json
  • web/src/i18n/locales/zh-TW.json

"划转额度": "Перевести квоту",
"列出的模型将不会自动添加或移除-thinking/-nothinking 后缀": "Для этих моделей суффиксы -thinking/-nothinking не будут добавляться или удаляться автоматически.",
"列设置": "Настройки столбцов",
"到期": "Истекает",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Translation ambiguity: "到期" should indicate "expired" status, not "expiring".

The Chinese key "到期" is translated as "Истекает" (expiring, present continuous). However, according to the PR description, this label is shown for expired subscriptions (displayed in gray). The correct translation should be "Истекла" (expired, past tense, feminine form to match Russian "подписка").

📝 Suggested correction
-    "到期": "Истекает",
+    "到期": "Истекла",

This is similar to the Vietnamese translation ambiguity mentioned in the PR that was fixed in commit 2.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"到期": "Истекает",
"到期": "Истекла",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/ru.json` at line 686, The translation for the Chinese
key "到期" in ru.json is incorrect (currently "Истекает"); update the Russian
value for the key "到期" to the past-tense feminine form "Истекла" so it correctly
conveys "expired" for subscriptions (replace the value associated with the "到期"
key).

"订阅实例": "Экземпляр подписки",
"订阅抵扣": "Списание по подписке",
"订阅中": "Подписан",
"订阅信息": "Подписка",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incomplete translation: missing "information" aspect.

The Chinese key "订阅信息" means "Subscription information", but it's translated as just "Подписка" (Subscription). For a column header showing subscription details, the full meaning should be preserved.

📝 Suggested correction
-    "订阅信息": "Подписка",
+    "订阅信息": "Информация о подписке",

If space is constrained in the UI, a shorter alternative would be:

-    "订阅信息": "Подписка",
+    "订阅信息": "Инф. о подписке",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"订阅信息": "Подписка",
"订阅信息": "Информация о подписке",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/ru.json` at line 2556, The Russian translation for the
JSON key "订阅信息" is incomplete: it should preserve the "information" aspect;
update the value for the key "订阅信息" (currently "Подписка") to a full equivalent
like "Информация о подписке" (or a shorter UI-friendly alternative such as
"Сведения о подписке") so the column header correctly conveys "Subscription
information".

"新额度:": "Hạn ngạch mới: ",
"无": "Không",
"无GPU": "No GPU",
"无订阅": "Không có đăng ký",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Refine subscription labels for clearer Vietnamese UX.

Current values are understandable, but a few labels read as actions/states in-progress rather than stable subscription status/info. Suggested wording is clearer for table/filter UI.

✍️ Suggested wording updates
-    "无订阅": "Không có đăng ký",
+    "无订阅": "Không có gói đăng ký",

-    "订阅中": "Đang đăng ký",
+    "订阅中": "Đang sử dụng gói",

-    "订阅信息": "Đăng ký",
+    "订阅信息": "Thông tin gói đăng ký",

-    "选择订阅": "Chọn đăng ký",
+    "选择订阅": "Chọn gói đăng ký",

Also applies to: 2879-2880, 3422-3422

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/vi.json` at line 1540, The translation for the JSON key
"无订阅" should be updated to clearer Vietnamese wording for table/filter UI:
replace the current value ("Không có đăng ký") with "Chưa có đăng ký" (or the
agreed alternative such as "Không có gói đăng ký") for the key "无订阅" everywhere
it appears in vi.json (including the other occurrences noted); ensure you update
all identical keys consistently, keep valid JSON syntax (quotes/commas), and run
the i18n/JSON linter to verify no formatting errors.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/i18n/locales/zh-TW.json`:
- Around line 3114-3213: The appended JSON block contains 98 keys that duplicate
earlier keys (examples: "每日签到", "签到失败", "订阅管理", "暂无订阅套餐"), which will silently
override prior translations; remove all duplicate entries in this block and
retain only genuinely new translation keys introduced by this feature, ensuring
keys like "每日签到", "签到失败", "订阅管理", "暂无订阅套餐" (and any other exact-string
duplicates) are not redefined here so earlier definitions remain authoritative.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e1a55092-ef8b-426a-ad91-93a8273c7339

📥 Commits

Reviewing files that changed from the base of the PR and between fc8d61f and 2cc1dac.

📒 Files selected for processing (6)
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-TW.json
✅ Files skipped from review due to trivial changes (4)
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/vi.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/i18n/locales/ru.json

Comment thread web/src/i18n/locales/zh-TW.json Outdated
合并官方 main 时在尾部追加的翻译块包含大量已存在的 key。
移除所有重复条目,仅保留 5 个功能新增 key(到期/无订阅/订阅中/订阅信息/选择订阅)
及官方新增的 1 个 key。
@SpeedGeeker

Copy link
Copy Markdown

希望能合并

@zailushang2008

Copy link
Copy Markdown

请求通过PR

@Calcium-Ion
Calcium-Ion force-pushed the main branch 2 times, most recently from 51fdfc5 to 2b6f1df Compare August 30, 2026 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants