From 96d5a41420063fbc3ee320479eacbfeb9238d37a Mon Sep 17 00:00:00 2001 From: Fearless Date: Tue, 25 Aug 2026 19:05:08 +0000 Subject: [PATCH 1/4] feat(user): support multi-plan display in admin backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add plan_list field to UserListItem type - User list table shows multiple plan badges when multi-plan is active - Column header renamed to 套餐列表 / Plans - Narrow-screen mobile view also renders all plan badges - Add multi_plan_enable switch to subscribe settings section - Add i18n keys for multi_plan_enable and plan_list column (zh-CN/zh-TW/en-US/ru-RU) Co-authored-by: multica-agent --- src/api/user.ts | 2 + src/locales/en-US.ts | 5 ++ src/locales/ru-RU.ts | 5 ++ src/locales/zh-CN.ts | 5 ++ src/locales/zh-TW.ts | 5 ++ .../config/sections/subscribe-section.tsx | 1 + src/pages/user/index.tsx | 49 ++++++++++++------- 7 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/api/user.ts b/src/api/user.ts index 3119d65..7018a78 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -33,6 +33,8 @@ export interface UserListItem { discount?: number | null; telegram_id?: string | null; phone?: string | null; + /** 多套餐列表(multi_plan_enable 开启时返回) */ + plan_list?: Array<{ id: number; name: string; expired_at: number | null; speed_limit?: number | null }>; [k: string]: any; } diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 5f03bf2..a20e544 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -376,6 +376,7 @@ const translations: Translations = { "online_count": "Online Devices", "status": "Status", "subscription": "Subscription", + "plan_list": "Plans", "group": "Group", "used_traffic": "Used Traffic", "total_traffic": "Total Traffic", @@ -1403,6 +1404,10 @@ const translations: Translations = { "description": "Format is threshold:bonus in major units, e.g. 100:10 means deposit 100 get 10 bonus. The highest matching tier is used.", "placeholder": "100:10" }, + "multi_plan_enable": { + "title": "Allow Multiple Subscription Plans", + "description": "When enabled, users can hold multiple active plans simultaneously. New purchases will be added alongside existing plans instead of replacing them." + }, "saving": "Saving...", "plan": { "title": "Subscription Plans", diff --git a/src/locales/ru-RU.ts b/src/locales/ru-RU.ts index 9417b63..61a79b4 100644 --- a/src/locales/ru-RU.ts +++ b/src/locales/ru-RU.ts @@ -376,6 +376,7 @@ const translations: Translations = { "online_count": "Устройства онлайн", "status": "Статус", "subscription": "Подписка", + "plan_list": "Пакеты", "group": "Группа", "used_traffic": "Использовано", "total_traffic": "Всего трафика", @@ -1403,6 +1404,10 @@ const translations: Translations = { "description": "Формат «порог:бонус» в основных единицах, например 100:10 — пополнение на 100 даёт 10 бонуса. Берётся наибольший подходящий уровень.", "placeholder": "100:10" }, + "multi_plan_enable": { + "title": "Разрешить несколько подписок", + "description": "При включении пользователи могут одновременно иметь несколько активных подписок. Новые покупки добавляются к существующим, а не заменяют их." + }, "saving": "Сохранение...", "plan": { "title": "Тарифные планы", diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 5f3bf29..59f18c0 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -376,6 +376,7 @@ const translations: Translations = { "online_count": "在线设备", "status": "状态", "subscription": "订阅", + "plan_list": "套餐列表", "group": "权限组", "used_traffic": "已用流量", "total_traffic": "总流量", @@ -1292,6 +1293,10 @@ const translations: Translations = { "description": "格式为「门槛元:赠送元」,例如 100:10 表示充值满 100 元赠送 10 元。取满足门槛的最大赠送。", "placeholder": "100:10" }, + "multi_plan_enable": { + "title": "允许用户持有多个订阅套餐", + "description": "开启后用户可同时拥有多个有效套餐,新购订单会追加到现有套餐而非覆盖。" + }, "saving": "保存中...", "plan": { "title": "订阅套餐", diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index abb5b2d..231e2f4 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -376,6 +376,7 @@ const translations: Translations = { "online_count": "在線設備", "status": "狀態", "subscription": "訂閱", + "plan_list": "套餐列表", "group": "權限組", "used_traffic": "已用流量", "total_traffic": "總流量", @@ -1292,6 +1293,10 @@ const translations: Translations = { "description": "格式為「門檻元:贈送元」,例如 100:10 表示充值滿 100 元贈送 10 元。取滿足門檻的最大贈送。", "placeholder": "100:10" }, + "multi_plan_enable": { + "title": "允許用戶持有多個訂閱套餐", + "description": "開啟後用戶可同時擁有多个有效套餐,新購訂單會追加到現有套餐而非覆蓋。" + }, "saving": "保存中...", "plan": { "title": "訂閱套餐", diff --git a/src/pages/config/sections/subscribe-section.tsx b/src/pages/config/sections/subscribe-section.tsx index 42acf00..54004cd 100644 --- a/src/pages/config/sections/subscribe-section.tsx +++ b/src/pages/config/sections/subscribe-section.tsx @@ -15,6 +15,7 @@ const switchKeys: Array<{ key: string; i18n: string }> = [ { key: "default_remind_traffic", i18n: "default_remind_traffic" }, { key: "deposit_enable", i18n: "deposit_enable" }, { key: "deposit_commission_enable", i18n: "deposit_commission_enable" }, + { key: "multi_plan_enable", i18n: "multi_plan_enable" }, ]; const baseFields: FieldDef[] = [ diff --git a/src/pages/user/index.tsx b/src/pages/user/index.tsx index 209b76e..f39002f 100644 --- a/src/pages/user/index.tsx +++ b/src/pages/user/index.tsx @@ -398,8 +398,8 @@ export function UserListPage() { {t("user.columns.status")} - - {t("user.columns.subscription")} + + {t("user.columns.plan_list")} {t("user.columns.group")} @@ -480,9 +480,13 @@ export function UserListPage() { const expire = expireMeta(u.expired_at); const online = Number(u.online_count || 0); const selectedRow = selected.includes(u.id); - const planName = u.plan_id - ? plansMap[u.plan_id] || `#${u.plan_id}` - : null; + // plan_list 优先(多套餐模式);否则回退 plan_id + const planNames: string[] = u.plan_list && u.plan_list.length > 0 + ? u.plan_list.map((p) => plansMap[p.id] || p.name || `#${p.id}`) + : u.plan_id + ? [plansMap[u.plan_id] || `#${u.plan_id}`] + : []; + const planName = planNames[0] || null; const groupName = u.group_id ? groupsMap[u.group_id] || `#${u.group_id}` : null; @@ -509,14 +513,24 @@ export function UserListPage() { ); - const planBadge = planName ? ( - - {planName} - + const planBadge = planNames.length > 0 ? ( +
+ {planNames.map((name, i) => ( + + {name} + + ))} +
) : ( ); @@ -659,15 +673,16 @@ export function UserListPage() { {/* 窄屏:在独立列出现前,把关键信息并入邮箱列 */}
{statusBadge} - {planName ? ( + {planNames.map((name, i) => ( - {planName} + {name} - ) : null} + ))}
+ + +
+ {planList.map((item, index) => ( +
+
+ + +
+ +
+ + + updatePlan(index, "expired_at", e.target.value) + } + className="mt-1" + /> +
+ +
+ + + updatePlan(index, "speed_limit", e.target.value ? Number(e.target.value) : null) + } + className="mt-1" + /> +
+ + +
+ ))} +
+ + )} +