diff --git a/src/routes/admin.js b/src/routes/admin.js
index b0aad407..f0a3035e 100644
--- a/src/routes/admin.js
+++ b/src/routes/admin.js
@@ -2266,7 +2266,8 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
autoStopOnWarning,
useUnifiedUserAgent,
useUnifiedClientId,
- unifiedClientId
+ unifiedClientId,
+ expiresAt
} = req.body
if (!name) {
@@ -2309,7 +2310,8 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
autoStopOnWarning: autoStopOnWarning === true, // 默认为false
useUnifiedUserAgent: useUnifiedUserAgent === true, // 默认为false
useUnifiedClientId: useUnifiedClientId === true, // 默认为false
- unifiedClientId: unifiedClientId || '' // 统一的客户端标识
+ unifiedClientId: unifiedClientId || '', // 统一的客户端标识
+ expiresAt: expiresAt || null // 账户订阅到期时间
})
// 如果是分组类型,将账户添加到分组
@@ -2396,7 +2398,14 @@ router.put('/claude-accounts/:accountId', authenticateAdmin, async (req, res) =>
}
}
- await claudeAccountService.updateAccount(accountId, updates)
+ // 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
+ const mappedUpdates = { ...updates }
+ if ('expiresAt' in mappedUpdates) {
+ mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
+ delete mappedUpdates.expiresAt
+ }
+
+ await claudeAccountService.updateAccount(accountId, mappedUpdates)
logger.success(`📝 Admin updated Claude account: ${accountId}`)
return res.json({ success: true, message: 'Claude account updated successfully' })
diff --git a/src/services/claudeAccountService.js b/src/services/claudeAccountService.js
index ec9e5b11..c796e7c9 100644
--- a/src/services/claudeAccountService.js
+++ b/src/services/claudeAccountService.js
@@ -73,7 +73,8 @@ class ClaudeAccountService {
autoStopOnWarning = false, // 5小时使用量接近限制时自动停止调度
useUnifiedUserAgent = false, // 是否使用统一Claude Code版本的User-Agent
useUnifiedClientId = false, // 是否使用统一的客户端标识
- unifiedClientId = '' // 统一的客户端标识
+ unifiedClientId = '', // 统一的客户端标识
+ expiresAt = null // 账户订阅到期时间
} = options
const accountId = uuidv4()
@@ -113,7 +114,9 @@ class ClaudeAccountService {
? JSON.stringify(subscriptionInfo)
: claudeAiOauth.subscriptionInfo
? JSON.stringify(claudeAiOauth.subscriptionInfo)
- : ''
+ : '',
+ // 账户订阅到期时间
+ subscriptionExpiresAt: expiresAt || ''
}
} else {
// 兼容旧格式
@@ -141,7 +144,9 @@ class ClaudeAccountService {
autoStopOnWarning: autoStopOnWarning.toString(), // 5小时使用量接近限制时自动停止调度
useUnifiedUserAgent: useUnifiedUserAgent.toString(), // 是否使用统一Claude Code版本的User-Agent
// 手动设置的订阅信息
- subscriptionInfo: subscriptionInfo ? JSON.stringify(subscriptionInfo) : ''
+ subscriptionInfo: subscriptionInfo ? JSON.stringify(subscriptionInfo) : '',
+ // 账户订阅到期时间
+ subscriptionExpiresAt: expiresAt || ''
}
}
@@ -486,7 +491,7 @@ class ClaudeAccountService {
createdAt: account.createdAt,
lastUsedAt: account.lastUsedAt,
lastRefreshAt: account.lastRefreshAt,
- expiresAt: account.expiresAt,
+ expiresAt: account.subscriptionExpiresAt || null, // 账户订阅到期时间
// 添加 scopes 字段用于判断认证方式
// 处理空字符串的情况,避免返回 ['']
scopes: account.scopes && account.scopes.trim() ? account.scopes.split(' ') : [],
@@ -618,7 +623,8 @@ class ClaudeAccountService {
'autoStopOnWarning',
'useUnifiedUserAgent',
'useUnifiedClientId',
- 'unifiedClientId'
+ 'unifiedClientId',
+ 'subscriptionExpiresAt'
]
const updatedData = { ...accountData }
let shouldClearAutoStopFields = false
@@ -637,6 +643,9 @@ class ClaudeAccountService {
} else if (field === 'subscriptionInfo') {
// 处理订阅信息更新
updatedData[field] = typeof value === 'string' ? value : JSON.stringify(value)
+ } else if (field === 'subscriptionExpiresAt') {
+ // 处理订阅到期时间,允许 null 值(永不过期)
+ updatedData[field] = value ? value.toString() : ''
} else if (field === 'claudeAiOauth') {
// 更新 Claude AI OAuth 数据
if (value) {
@@ -650,7 +659,7 @@ class ClaudeAccountService {
updatedData.lastRefreshAt = new Date().toISOString()
}
} else {
- updatedData[field] = value.toString()
+ updatedData[field] = value !== null && value !== undefined ? value.toString() : ''
}
}
}
diff --git a/web/admin-spa/src/components/accounts/AccountExpiryEditModal.vue b/web/admin-spa/src/components/accounts/AccountExpiryEditModal.vue
new file mode 100644
index 00000000..046c3332
--- /dev/null
+++ b/web/admin-spa/src/components/accounts/AccountExpiryEditModal.vue
@@ -0,0 +1,416 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
修改到期时间
+
+ 为 "{{ account.name || 'Account' }}" 设置新的到期时间
+
+
+
+
+
+
+
+
+
+
+
+
当前状态
+
+
+
+ {{ formatFullExpireDate(account.expiresAt) }}
+
+ ({{ getExpiryStatus(account.expiresAt).text }})
+
+
+
+
+
+ 永不过期
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 选择一个未来的日期和时间作为到期时间
+
+
+
+
+
+
+
+
+
+ 新的到期时间
+
+
+
+ {{ formatFullExpireDate(localForm.expiresAt) }}
+
+ ({{ getExpiryStatus(localForm.expiresAt).text }})
+
+
+
+
+ 永不过期
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/admin-spa/src/components/accounts/AccountForm.vue b/web/admin-spa/src/components/accounts/AccountForm.vue
index 8d6a1b9b..09877616 100644
--- a/web/admin-spa/src/components/accounts/AccountForm.vue
+++ b/web/admin-spa/src/components/accounts/AccountForm.vue
@@ -641,6 +641,49 @@
+
+
+
+
+
+
+
+
+
+
+ 将于 {{ formatExpireDate(form.expiresAt) }} 过期
+
+
+
+ 账户永不过期
+
+
+
+ 设置 Claude Max/Pro 订阅的到期时间,到期后将停止调度此账户
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 将于 {{ formatExpireDate(form.expiresAt) }} 过期
+
+
+
+ 账户永不过期
+
+
+
+ 设置 Claude Max/Pro 订阅的到期时间,到期后将停止调度此账户
+
+
+