feat: add per-API-key weekly cost reset day/hour configuration

Allow each API key to configure its own weekly reset day (1-7, Mon-Sun)
and hour (0-23) so the relay service's weekly limit tracking can align
with upstream Claude account reset schedules instead of using a fixed
Monday 00:00 boundary for all keys.
This commit is contained in:
yptse123
2026-02-22 17:56:42 +08:00
parent d6ced986b6
commit 955b2af08e
10 changed files with 612 additions and 104 deletions

View File

@@ -246,8 +246,45 @@
type="number"
/>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
设置 Claude 模型的周费用限制周一到周日仅对 Claude 模型请求生效
设置 Claude 模型的周费用限制仅对 Claude 模型请求生效
</p>
<div
v-if="form.weeklyOpusCostLimit && Number(form.weeklyOpusCostLimit) > 0"
class="mt-2 flex gap-3"
>
<div class="flex-1">
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400"
>重置日</label
>
<select
v-model="form.weeklyResetDay"
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
>
<option value="">不修改</option>
<option :value="1">周一</option>
<option :value="2">周二</option>
<option :value="3">周三</option>
<option :value="4">周四</option>
<option :value="5">周五</option>
<option :value="6">周六</option>
<option :value="7">周日</option>
</select>
</div>
<div class="flex-1">
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400"
>重置时间 (UTC+8)</label
>
<select
v-model="form.weeklyResetHour"
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
>
<option value="">不修改</option>
<option v-for="h in 24" :key="h - 1" :value="h - 1">
{{ String(h - 1).padStart(2, '0') }}:00
</option>
</select>
</div>
</div>
</div>
<!-- 并发限制 -->
@@ -511,6 +548,8 @@ const form = reactive({
dailyCostLimit: '',
totalCostLimit: '',
weeklyOpusCostLimit: '', // 新增Claude周费用限制
weeklyResetDay: '',
weeklyResetHour: '',
permissions: '', // 空字符串表示不修改
claudeAccountId: '',
geminiAccountId: '',
@@ -737,6 +776,12 @@ const batchUpdateApiKeys = async () => {
if (form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null) {
updates.weeklyOpusCostLimit = parseFloat(form.weeklyOpusCostLimit)
}
if (form.weeklyResetDay !== '' && form.weeklyResetDay !== null) {
updates.weeklyResetDay = Number(form.weeklyResetDay)
}
if (form.weeklyResetHour !== '' && form.weeklyResetHour !== null) {
updates.weeklyResetHour = Number(form.weeklyResetHour)
}
// 权限设置
if (form.permissions !== '') {

View File

@@ -428,9 +428,43 @@
type="number"
/>
<p class="text-xs text-gray-500 dark:text-gray-400">
设置 Claude 模型的周费用限制周一到周日仅对 Claude 模型请求生效0
或留空表示无限制
设置 Claude 模型的周费用限制仅对 Claude 模型请求生效0 或留空表示无限制
</p>
<div
v-if="form.weeklyOpusCostLimit && Number(form.weeklyOpusCostLimit) > 0"
class="mt-2 flex gap-3"
>
<div class="flex-1">
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400"
>重置日</label
>
<select
v-model="form.weeklyResetDay"
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
>
<option :value="1">周一</option>
<option :value="2">周二</option>
<option :value="3">周三</option>
<option :value="4">周四</option>
<option :value="5">周五</option>
<option :value="6">周六</option>
<option :value="7">周日</option>
</select>
</div>
<div class="flex-1">
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400"
>重置时间 (UTC+8)</label
>
<select
v-model="form.weeklyResetHour"
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
>
<option v-for="h in 24" :key="h - 1" :value="h - 1">
{{ String(h - 1).padStart(2, '0') }}:00
</option>
</select>
</div>
</div>
</div>
</div>
@@ -1061,6 +1095,8 @@ const form = reactive({
dailyCostLimit: '',
totalCostLimit: '',
weeklyOpusCostLimit: '',
weeklyResetDay: 1,
weeklyResetHour: 0,
expireDuration: '',
customExpireDate: '',
expiresAt: null,
@@ -1495,6 +1531,8 @@ const createApiKey = async () => {
form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null
? parseFloat(form.weeklyOpusCostLimit)
: 0,
weeklyResetDay: form.weeklyResetDay,
weeklyResetHour: form.weeklyResetHour,
expiresAt: form.expirationMode === 'fixed' ? form.expiresAt || undefined : undefined,
expirationMode: form.expirationMode,
activationDays: form.expirationMode === 'activation' ? form.activationDays : undefined,

View File

@@ -411,9 +411,43 @@
type="number"
/>
<p class="text-xs text-gray-500 dark:text-gray-400">
设置 Claude 模型的周费用限制周一到周日仅对 Claude 模型请求生效0
或留空表示无限制
设置 Claude 模型的周费用限制仅对 Claude 模型请求生效0 或留空表示无限制
</p>
<div
v-if="form.weeklyOpusCostLimit && Number(form.weeklyOpusCostLimit) > 0"
class="mt-3 flex gap-3"
>
<div class="flex-1">
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400"
>重置日</label
>
<select
v-model="form.weeklyResetDay"
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
>
<option :value="1">周一</option>
<option :value="2">周二</option>
<option :value="3">周三</option>
<option :value="4">周四</option>
<option :value="5">周五</option>
<option :value="6">周六</option>
<option :value="7">周日</option>
</select>
</div>
<div class="flex-1">
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400"
>重置时间 (UTC+8)</label
>
<select
v-model="form.weeklyResetHour"
class="form-input w-full border-gray-300 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
>
<option v-for="h in 24" :key="h - 1" :value="h - 1">
{{ String(h - 1).padStart(2, '0') }}:00
</option>
</select>
</div>
</div>
</div>
</div>
@@ -901,6 +935,8 @@ const form = reactive({
dailyCostLimit: '',
totalCostLimit: '',
weeklyOpusCostLimit: '',
weeklyResetDay: 1,
weeklyResetHour: 0,
permissions: [], // 数组格式,空数组表示全部服务
claudeAccountId: '',
geminiAccountId: '',
@@ -1033,6 +1069,8 @@ const updateApiKey = async () => {
form.weeklyOpusCostLimit !== '' && form.weeklyOpusCostLimit !== null
? parseFloat(form.weeklyOpusCostLimit)
: 0,
weeklyResetDay: form.weeklyResetDay,
weeklyResetHour: form.weeklyResetHour,
permissions: form.permissions,
tags: form.tags
}
@@ -1355,6 +1393,8 @@ onMounted(async () => {
form.dailyCostLimit = props.apiKey.dailyCostLimit || ''
form.totalCostLimit = props.apiKey.totalCostLimit || ''
form.weeklyOpusCostLimit = props.apiKey.weeklyOpusCostLimit || ''
form.weeklyResetDay = props.apiKey.weeklyResetDay || 1
form.weeklyResetHour = props.apiKey.weeklyResetHour || 0
// 处理权限数据,兼容旧格式(字符串)和新格式(数组)
// 有效的权限值
const VALID_PERMS = ['claude', 'gemini', 'openai', 'droid']

View File

@@ -186,6 +186,17 @@
:style="{ width: getOpusWeeklyCostProgress() + '%' }"
/>
</div>
<p
v-if="statsData.limits.weeklyResetDay"
class="mt-1 text-xs text-gray-400 dark:text-gray-500"
>
{{
['', '周一', '周二', '周三', '周四', '周五', '周六', '周日'][
statsData.limits.weeklyResetDay || 1
]
}}
{{ String(statsData.limits.weeklyResetHour || 0).padStart(2, '0') }}:00 (UTC+8) 重置
</p>
</div>
<!-- 时间窗口限制 -->