mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
feat: 添加账户订阅到期时间管理功能
## 新增功能 - 支持为 Claude 账户设置订阅到期时间 - 前端提供到期时间选择器(快捷选项 + 自定义日期) - 账户列表显示到期状态(已过期/即将过期/永不过期) - 新增独立的到期时间编辑弹窗组件 ## 技术变更 - 后端新增 subscriptionExpiresAt 字段存储 - 前端使用 expiresAt 字段进行交互 - 支持创建、编辑、显示完整流程 ## 包含文件 - src/routes/admin.js: POST/PUT 端点支持 expiresAt 字段 - src/services/claudeAccountService.js: 存储和返回到期时间 - web/admin-spa/src/components/accounts/AccountForm.vue: 表单添加到期时间选择 - web/admin-spa/src/views/AccountsView.vue: 列表显示和编辑功能 - web/admin-spa/src/components/accounts/AccountExpiryEditModal.vue: 新增编辑弹窗 - account_expire_feature.md: 代码评审报告和优化建议 ## 注意事项 ⚠️ 本次提交包含初步实现,详细的优化建议请查看 account_expire_feature.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -641,6 +641,49 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 到期时间 -->
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>到期时间 (可选)</label
|
||||
>
|
||||
<div
|
||||
class="rounded-lg border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<select
|
||||
v-model="form.expireDuration"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
@change="updateAccountExpireAt"
|
||||
>
|
||||
<option value="">永不过期</option>
|
||||
<option value="30d">30 天</option>
|
||||
<option value="90d">90 天</option>
|
||||
<option value="180d">180 天</option>
|
||||
<option value="365d">365 天</option>
|
||||
<option value="custom">自定义日期</option>
|
||||
</select>
|
||||
<div v-if="form.expireDuration === 'custom'" class="mt-3">
|
||||
<input
|
||||
v-model="form.customExpireDate"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
:min="minDateTime"
|
||||
type="datetime-local"
|
||||
@change="updateAccountCustomExpireAt"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="form.expiresAt" class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-calendar-alt mr-1" />
|
||||
将于 {{ formatExpireDate(form.expiresAt) }} 过期
|
||||
</p>
|
||||
<p v-else class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-infinity mr-1" />
|
||||
账户永不过期
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
设置 Claude Max/Pro 订阅的到期时间,到期后将停止调度此账户
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 分组选择器 -->
|
||||
<div v-if="form.accountType === 'group'">
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
@@ -2066,6 +2109,49 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 到期时间 -->
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
>到期时间 (可选)</label
|
||||
>
|
||||
<div
|
||||
class="rounded-lg border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800"
|
||||
>
|
||||
<select
|
||||
v-model="form.expireDuration"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
@change="updateAccountExpireAt"
|
||||
>
|
||||
<option value="">永不过期</option>
|
||||
<option value="30d">30 天</option>
|
||||
<option value="90d">90 天</option>
|
||||
<option value="180d">180 天</option>
|
||||
<option value="365d">365 天</option>
|
||||
<option value="custom">自定义日期</option>
|
||||
</select>
|
||||
<div v-if="form.expireDuration === 'custom'" class="mt-3">
|
||||
<input
|
||||
v-model="form.customExpireDate"
|
||||
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
:min="minDateTime"
|
||||
type="datetime-local"
|
||||
@change="updateAccountCustomExpireAt"
|
||||
/>
|
||||
</div>
|
||||
<p v-if="form.expiresAt" class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-calendar-alt mr-1" />
|
||||
将于 {{ formatExpireDate(form.expiresAt) }} 过期
|
||||
</p>
|
||||
<p v-else class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-infinity mr-1" />
|
||||
账户永不过期
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
设置 Claude Max/Pro 订阅的到期时间,到期后将停止调度此账户
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 分组选择器 -->
|
||||
<div v-if="form.accountType === 'group'">
|
||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||
@@ -3222,7 +3308,11 @@ const form = ref({
|
||||
// Azure OpenAI 特定字段
|
||||
azureEndpoint: props.account?.azureEndpoint || '',
|
||||
apiVersion: props.account?.apiVersion || '',
|
||||
deploymentName: props.account?.deploymentName || ''
|
||||
deploymentName: props.account?.deploymentName || '',
|
||||
// 到期时间字段
|
||||
expireDuration: '',
|
||||
customExpireDate: '',
|
||||
expiresAt: props.account?.expiresAt || null
|
||||
})
|
||||
|
||||
// 模型限制配置
|
||||
@@ -3612,6 +3702,7 @@ const handleOAuthSuccess = async (tokenInfo) => {
|
||||
accountType: form.value.accountType,
|
||||
groupId: form.value.accountType === 'group' ? form.value.groupId : undefined,
|
||||
groupIds: form.value.accountType === 'group' ? form.value.groupIds : undefined,
|
||||
expiresAt: form.value.expiresAt || undefined,
|
||||
proxy: form.value.proxy.enabled
|
||||
? {
|
||||
type: form.value.proxy.type,
|
||||
@@ -3909,6 +4000,7 @@ const createAccount = async () => {
|
||||
accountType: form.value.accountType,
|
||||
groupId: form.value.accountType === 'group' ? form.value.groupId : undefined,
|
||||
groupIds: form.value.accountType === 'group' ? form.value.groupIds : undefined,
|
||||
expiresAt: form.value.expiresAt || undefined,
|
||||
proxy: form.value.proxy.enabled
|
||||
? {
|
||||
type: form.value.proxy.type,
|
||||
@@ -4174,6 +4266,7 @@ const updateAccount = async () => {
|
||||
accountType: form.value.accountType,
|
||||
groupId: form.value.accountType === 'group' ? form.value.groupId : undefined,
|
||||
groupIds: form.value.accountType === 'group' ? form.value.groupIds : undefined,
|
||||
expiresAt: form.value.expiresAt || undefined,
|
||||
proxy: form.value.proxy.enabled
|
||||
? {
|
||||
type: form.value.proxy.type,
|
||||
@@ -4978,6 +5071,61 @@ const handleUnifiedClientIdChange = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 到期时间相关方法
|
||||
// 计算最小日期时间
|
||||
const minDateTime = computed(() => {
|
||||
const now = new Date()
|
||||
now.setMinutes(now.getMinutes() + 1)
|
||||
return now.toISOString().slice(0, 16)
|
||||
})
|
||||
|
||||
// 更新账户过期时间
|
||||
const updateAccountExpireAt = () => {
|
||||
if (!form.value.expireDuration) {
|
||||
form.value.expiresAt = null
|
||||
return
|
||||
}
|
||||
|
||||
if (form.value.expireDuration === 'custom') {
|
||||
return
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
const duration = form.value.expireDuration
|
||||
const match = duration.match(/(\d+)([d])/)
|
||||
|
||||
if (match) {
|
||||
const [, value, unit] = match
|
||||
const num = parseInt(value)
|
||||
|
||||
if (unit === 'd') {
|
||||
now.setDate(now.getDate() + num)
|
||||
}
|
||||
|
||||
form.value.expiresAt = now.toISOString()
|
||||
}
|
||||
}
|
||||
|
||||
// 更新自定义过期时间
|
||||
const updateAccountCustomExpireAt = () => {
|
||||
if (form.value.customExpireDate) {
|
||||
form.value.expiresAt = new Date(form.value.customExpireDate).toISOString()
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化过期日期
|
||||
const formatExpireDate = (dateString) => {
|
||||
if (!dateString) return ''
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// 组件挂载时获取统一 User-Agent 信息
|
||||
onMounted(() => {
|
||||
// 初始化平台分组
|
||||
|
||||
Reference in New Issue
Block a user