Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
a8513da7
Unverified
Commit
a8513da7
authored
Jan 20, 2026
by
Wesley Liddick
Committed by
GitHub
Jan 20, 2026
Browse files
Merge pull request #335 from geminiwen/main
feat(subscription): 支持调整订阅时长(延长/缩短)
parents
e7bc6250
53534d39
Changes
7
Show whitespace changes
Inline
Side-by-side
backend/internal/handler/admin/subscription_handler.go
View file @
a8513da7
...
@@ -53,9 +53,9 @@ type BulkAssignSubscriptionRequest struct {
...
@@ -53,9 +53,9 @@ type BulkAssignSubscriptionRequest struct {
Notes
string
`json:"notes"`
Notes
string
`json:"notes"`
}
}
//
Extend
SubscriptionRequest represents
extend
subscription request
//
Adjust
SubscriptionRequest represents
adjust
subscription request
(extend or shorten)
type
Extend
SubscriptionRequest
struct
{
type
Adjust
SubscriptionRequest
struct
{
Days
int
`json:"days" binding:"required,min=
1
,max=36500"`
//
max 100 years
Days
int
`json:"days" binding:"required,min=
-36500
,max=36500"`
//
negative to shorten, positive to extend
}
}
// List handles listing all subscriptions with pagination and filters
// List handles listing all subscriptions with pagination and filters
...
@@ -180,7 +180,7 @@ func (h *SubscriptionHandler) BulkAssign(c *gin.Context) {
...
@@ -180,7 +180,7 @@ func (h *SubscriptionHandler) BulkAssign(c *gin.Context) {
response
.
Success
(
c
,
dto
.
BulkAssignResultFromService
(
result
))
response
.
Success
(
c
,
dto
.
BulkAssignResultFromService
(
result
))
}
}
// Extend handles
extend
ing a subscription
// Extend handles
adjust
ing a subscription
(extend or shorten)
// POST /api/v1/admin/subscriptions/:id/extend
// POST /api/v1/admin/subscriptions/:id/extend
func
(
h
*
SubscriptionHandler
)
Extend
(
c
*
gin
.
Context
)
{
func
(
h
*
SubscriptionHandler
)
Extend
(
c
*
gin
.
Context
)
{
subscriptionID
,
err
:=
strconv
.
ParseInt
(
c
.
Param
(
"id"
),
10
,
64
)
subscriptionID
,
err
:=
strconv
.
ParseInt
(
c
.
Param
(
"id"
),
10
,
64
)
...
@@ -189,7 +189,7 @@ func (h *SubscriptionHandler) Extend(c *gin.Context) {
...
@@ -189,7 +189,7 @@ func (h *SubscriptionHandler) Extend(c *gin.Context) {
return
return
}
}
var
req
Extend
SubscriptionRequest
var
req
Adjust
SubscriptionRequest
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
response
.
BadRequest
(
c
,
"Invalid request: "
+
err
.
Error
())
response
.
BadRequest
(
c
,
"Invalid request: "
+
err
.
Error
())
return
return
...
...
backend/internal/service/subscription_service.go
View file @
a8513da7
...
@@ -27,6 +27,7 @@ var (
...
@@ -27,6 +27,7 @@ var (
ErrWeeklyLimitExceeded
=
infraerrors
.
TooManyRequests
(
"WEEKLY_LIMIT_EXCEEDED"
,
"weekly usage limit exceeded"
)
ErrWeeklyLimitExceeded
=
infraerrors
.
TooManyRequests
(
"WEEKLY_LIMIT_EXCEEDED"
,
"weekly usage limit exceeded"
)
ErrMonthlyLimitExceeded
=
infraerrors
.
TooManyRequests
(
"MONTHLY_LIMIT_EXCEEDED"
,
"monthly usage limit exceeded"
)
ErrMonthlyLimitExceeded
=
infraerrors
.
TooManyRequests
(
"MONTHLY_LIMIT_EXCEEDED"
,
"monthly usage limit exceeded"
)
ErrSubscriptionNilInput
=
infraerrors
.
BadRequest
(
"SUBSCRIPTION_NIL_INPUT"
,
"subscription input cannot be nil"
)
ErrSubscriptionNilInput
=
infraerrors
.
BadRequest
(
"SUBSCRIPTION_NIL_INPUT"
,
"subscription input cannot be nil"
)
ErrAdjustWouldExpire
=
infraerrors
.
BadRequest
(
"ADJUST_WOULD_EXPIRE"
,
"adjustment would result in expired subscription (remaining days must be > 0)"
)
)
)
// SubscriptionService 订阅服务
// SubscriptionService 订阅服务
...
@@ -308,17 +309,20 @@ func (s *SubscriptionService) RevokeSubscription(ctx context.Context, subscripti
...
@@ -308,17 +309,20 @@ func (s *SubscriptionService) RevokeSubscription(ctx context.Context, subscripti
return
nil
return
nil
}
}
// ExtendSubscription
延长订阅
// ExtendSubscription
调整订阅时长(正数延长,负数缩短)
func
(
s
*
SubscriptionService
)
ExtendSubscription
(
ctx
context
.
Context
,
subscriptionID
int64
,
days
int
)
(
*
UserSubscription
,
error
)
{
func
(
s
*
SubscriptionService
)
ExtendSubscription
(
ctx
context
.
Context
,
subscriptionID
int64
,
days
int
)
(
*
UserSubscription
,
error
)
{
sub
,
err
:=
s
.
userSubRepo
.
GetByID
(
ctx
,
subscriptionID
)
sub
,
err
:=
s
.
userSubRepo
.
GetByID
(
ctx
,
subscriptionID
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
ErrSubscriptionNotFound
return
nil
,
ErrSubscriptionNotFound
}
}
// 限制
延长天数
// 限制
调整天数范围
if
days
>
MaxValidityDays
{
if
days
>
MaxValidityDays
{
days
=
MaxValidityDays
days
=
MaxValidityDays
}
}
if
days
<
-
MaxValidityDays
{
days
=
-
MaxValidityDays
}
// 计算新的过期时间
// 计算新的过期时间
newExpiresAt
:=
sub
.
ExpiresAt
.
AddDate
(
0
,
0
,
days
)
newExpiresAt
:=
sub
.
ExpiresAt
.
AddDate
(
0
,
0
,
days
)
...
@@ -326,6 +330,14 @@ func (s *SubscriptionService) ExtendSubscription(ctx context.Context, subscripti
...
@@ -326,6 +330,14 @@ func (s *SubscriptionService) ExtendSubscription(ctx context.Context, subscripti
newExpiresAt
=
MaxExpiresAt
newExpiresAt
=
MaxExpiresAt
}
}
// 如果是缩短(负数),检查新的过期时间必须大于当前时间
if
days
<
0
{
now
:=
time
.
Now
()
if
!
newExpiresAt
.
After
(
now
)
{
return
nil
,
ErrAdjustWouldExpire
}
}
if
err
:=
s
.
userSubRepo
.
ExtendExpiry
(
ctx
,
subscriptionID
,
newExpiresAt
);
err
!=
nil
{
if
err
:=
s
.
userSubRepo
.
ExtendExpiry
(
ctx
,
subscriptionID
,
newExpiresAt
);
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
...
...
frontend/src/components/admin/account/AccountTableActions.vue
View file @
a8513da7
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
<button
@
click=
"$emit('refresh')"
:disabled=
"loading"
class=
"btn btn-secondary"
>
<button
@
click=
"$emit('refresh')"
:disabled=
"loading"
class=
"btn btn-secondary"
>
<Icon
name=
"refresh"
size=
"md"
:class=
"[loading ? 'animate-spin' : '']"
/>
<Icon
name=
"refresh"
size=
"md"
:class=
"[loading ? 'animate-spin' : '']"
/>
</button>
</button>
<slot
name=
"after"
></slot>
<button
@
click=
"$emit('sync')"
class=
"btn btn-secondary"
>
{{
t
(
'
admin.accounts.syncFromCrs
'
)
}}
</button>
<button
@
click=
"$emit('sync')"
class=
"btn btn-secondary"
>
{{
t
(
'
admin.accounts.syncFromCrs
'
)
}}
</button>
<button
@
click=
"$emit('create')"
class=
"btn btn-primary"
>
{{
t
(
'
admin.accounts.createAccount
'
)
}}
</button>
<button
@
click=
"$emit('create')"
class=
"btn btn-primary"
>
{{
t
(
'
admin.accounts.createAccount
'
)
}}
</button>
</div>
</div>
...
...
frontend/src/i18n/locales/en.ts
View file @
a8513da7
...
@@ -950,7 +950,7 @@ export default {
...
@@ -950,7 +950,7 @@ export default {
title
:
'
Subscription Management
'
,
title
:
'
Subscription Management
'
,
description
:
'
Manage user subscriptions and quota limits
'
,
description
:
'
Manage user subscriptions and quota limits
'
,
assignSubscription
:
'
Assign Subscription
'
,
assignSubscription
:
'
Assign Subscription
'
,
extend
Subscription
:
'
Extend
Subscription
'
,
adjust
Subscription
:
'
Adjust
Subscription
'
,
revokeSubscription
:
'
Revoke Subscription
'
,
revokeSubscription
:
'
Revoke Subscription
'
,
allStatus
:
'
All Status
'
,
allStatus
:
'
All Status
'
,
allGroups
:
'
All Groups
'
,
allGroups
:
'
All Groups
'
,
...
@@ -965,6 +965,7 @@ export default {
...
@@ -965,6 +965,7 @@ export default {
resetInHoursMinutes
:
'
Resets in {hours}h {minutes}m
'
,
resetInHoursMinutes
:
'
Resets in {hours}h {minutes}m
'
,
resetInDaysHours
:
'
Resets in {days}d {hours}h
'
,
resetInDaysHours
:
'
Resets in {days}d {hours}h
'
,
daysRemaining
:
'
days remaining
'
,
daysRemaining
:
'
days remaining
'
,
remainingDays
:
'
Remaining days
'
,
noExpiration
:
'
No expiration
'
,
noExpiration
:
'
No expiration
'
,
status
:
{
status
:
{
active
:
'
Active
'
,
active
:
'
Active
'
,
...
@@ -983,28 +984,32 @@ export default {
...
@@ -983,28 +984,32 @@ export default {
user
:
'
User
'
,
user
:
'
User
'
,
group
:
'
Subscription Group
'
,
group
:
'
Subscription Group
'
,
validityDays
:
'
Validity (Days)
'
,
validityDays
:
'
Validity (Days)
'
,
extend
Days
:
'
Extend
by (Days)
'
adjust
Days
:
'
Adjust
by (Days)
'
},
},
selectUser
:
'
Select a user
'
,
selectUser
:
'
Select a user
'
,
selectGroup
:
'
Select a subscription group
'
,
selectGroup
:
'
Select a subscription group
'
,
groupHint
:
'
Only groups with subscription billing type are shown
'
,
groupHint
:
'
Only groups with subscription billing type are shown
'
,
validityHint
:
'
Number of days the subscription will be valid
'
,
validityHint
:
'
Number of days the subscription will be valid
'
,
extend
ingFor
:
'
Extend
ing subscription for
'
,
adjust
ingFor
:
'
Adjust
ing subscription for
'
,
currentExpiration
:
'
Current expiration
'
,
currentExpiration
:
'
Current expiration
'
,
adjustDaysPlaceholder
:
'
Positive to extend, negative to shorten
'
,
adjustHint
:
'
Enter positive number to extend, negative to shorten (remaining days must be > 0)
'
,
assign
:
'
Assign
'
,
assign
:
'
Assign
'
,
assigning
:
'
Assigning...
'
,
assigning
:
'
Assigning...
'
,
extend
:
'
Extend
'
,
adjust
:
'
Adjust
'
,
extending
:
'
Extend
ing...
'
,
adjusting
:
'
Adjust
ing...
'
,
revoke
:
'
Revoke
'
,
revoke
:
'
Revoke
'
,
noSubscriptionsYet
:
'
No subscriptions yet
'
,
noSubscriptionsYet
:
'
No subscriptions yet
'
,
assignFirstSubscription
:
'
Assign a subscription to get started.
'
,
assignFirstSubscription
:
'
Assign a subscription to get started.
'
,
subscriptionAssigned
:
'
Subscription assigned successfully
'
,
subscriptionAssigned
:
'
Subscription assigned successfully
'
,
subscription
Extend
ed
:
'
Subscription
extend
ed successfully
'
,
subscription
Adjust
ed
:
'
Subscription
adjust
ed successfully
'
,
subscriptionRevoked
:
'
Subscription revoked successfully
'
,
subscriptionRevoked
:
'
Subscription revoked successfully
'
,
failedToLoad
:
'
Failed to load subscriptions
'
,
failedToLoad
:
'
Failed to load subscriptions
'
,
failedToAssign
:
'
Failed to assign subscription
'
,
failedToAssign
:
'
Failed to assign subscription
'
,
failedTo
Extend
:
'
Failed to
extend
subscription
'
,
failedTo
Adjust
:
'
Failed to
adjust
subscription
'
,
failedToRevoke
:
'
Failed to revoke subscription
'
,
failedToRevoke
:
'
Failed to revoke subscription
'
,
adjustWouldExpire
:
'
Remaining days after adjustment must be greater than 0
'
,
adjustOutOfRange
:
'
Adjustment days must be between -36500 and 36500
'
,
pleaseSelectUser
:
'
Please select a user
'
,
pleaseSelectUser
:
'
Please select a user
'
,
pleaseSelectGroup
:
'
Please select a group
'
,
pleaseSelectGroup
:
'
Please select a group
'
,
validityDaysRequired
:
'
Please enter a valid number of days (at least 1)
'
,
validityDaysRequired
:
'
Please enter a valid number of days (at least 1)
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
a8513da7
...
@@ -1025,7 +1025,7 @@ export default {
...
@@ -1025,7 +1025,7 @@ export default {
title
:
'
订阅管理
'
,
title
:
'
订阅管理
'
,
description
:
'
管理用户订阅和配额限制
'
,
description
:
'
管理用户订阅和配额限制
'
,
assignSubscription
:
'
分配订阅
'
,
assignSubscription
:
'
分配订阅
'
,
extend
Subscription
:
'
延长
订阅
'
,
adjust
Subscription
:
'
调整
订阅
'
,
revokeSubscription
:
'
撤销订阅
'
,
revokeSubscription
:
'
撤销订阅
'
,
allStatus
:
'
全部状态
'
,
allStatus
:
'
全部状态
'
,
allGroups
:
'
全部分组
'
,
allGroups
:
'
全部分组
'
,
...
@@ -1040,6 +1040,7 @@ export default {
...
@@ -1040,6 +1040,7 @@ export default {
resetInHoursMinutes
:
'
{hours} 小时 {minutes} 分钟后重置
'
,
resetInHoursMinutes
:
'
{hours} 小时 {minutes} 分钟后重置
'
,
resetInDaysHours
:
'
{days} 天 {hours} 小时后重置
'
,
resetInDaysHours
:
'
{days} 天 {hours} 小时后重置
'
,
daysRemaining
:
'
天剩余
'
,
daysRemaining
:
'
天剩余
'
,
remainingDays
:
'
剩余天数
'
,
noExpiration
:
'
无过期时间
'
,
noExpiration
:
'
无过期时间
'
,
status
:
{
status
:
{
active
:
'
生效中
'
,
active
:
'
生效中
'
,
...
@@ -1058,28 +1059,32 @@ export default {
...
@@ -1058,28 +1059,32 @@ export default {
user
:
'
用户
'
,
user
:
'
用户
'
,
group
:
'
订阅分组
'
,
group
:
'
订阅分组
'
,
validityDays
:
'
有效期(天)
'
,
validityDays
:
'
有效期(天)
'
,
extend
Days
:
'
延长
天数
'
adjust
Days
:
'
调整
天数
'
},
},
selectUser
:
'
选择用户
'
,
selectUser
:
'
选择用户
'
,
selectGroup
:
'
选择订阅分组
'
,
selectGroup
:
'
选择订阅分组
'
,
groupHint
:
'
仅显示订阅计费类型的分组
'
,
groupHint
:
'
仅显示订阅计费类型的分组
'
,
validityHint
:
'
订阅的有效天数
'
,
validityHint
:
'
订阅的有效天数
'
,
extend
ingFor
:
'
为以下用户
延长
订阅
'
,
adjust
ingFor
:
'
为以下用户
调整
订阅
'
,
currentExpiration
:
'
当前到期时间
'
,
currentExpiration
:
'
当前到期时间
'
,
adjustDaysPlaceholder
:
'
正数延长,负数缩短
'
,
adjustHint
:
'
输入正数延长订阅,负数缩短订阅(缩短后剩余天数需大于0)
'
,
assign
:
'
分配
'
,
assign
:
'
分配
'
,
assigning
:
'
分配中...
'
,
assigning
:
'
分配中...
'
,
extend
:
'
延长
'
,
adjust
:
'
调整
'
,
extend
ing
:
'
延长
中...
'
,
adjust
ing
:
'
调整
中...
'
,
revoke
:
'
撤销
'
,
revoke
:
'
撤销
'
,
noSubscriptionsYet
:
'
暂无订阅
'
,
noSubscriptionsYet
:
'
暂无订阅
'
,
assignFirstSubscription
:
'
分配一个订阅以开始使用。
'
,
assignFirstSubscription
:
'
分配一个订阅以开始使用。
'
,
subscriptionAssigned
:
'
订阅分配成功
'
,
subscriptionAssigned
:
'
订阅分配成功
'
,
subscription
Extend
ed
:
'
订阅
延长
成功
'
,
subscription
Adjust
ed
:
'
订阅
调整
成功
'
,
subscriptionRevoked
:
'
订阅撤销成功
'
,
subscriptionRevoked
:
'
订阅撤销成功
'
,
failedToLoad
:
'
加载订阅列表失败
'
,
failedToLoad
:
'
加载订阅列表失败
'
,
failedToAssign
:
'
分配订阅失败
'
,
failedToAssign
:
'
分配订阅失败
'
,
failedTo
Extend
:
'
延长
订阅失败
'
,
failedTo
Adjust
:
'
调整
订阅失败
'
,
failedToRevoke
:
'
撤销订阅失败
'
,
failedToRevoke
:
'
撤销订阅失败
'
,
adjustWouldExpire
:
'
调整后剩余天数必须大于0
'
,
adjustOutOfRange
:
'
调整天数必须在 -36500 到 36500 之间
'
,
pleaseSelectUser
:
'
请选择用户
'
,
pleaseSelectUser
:
'
请选择用户
'
,
pleaseSelectGroup
:
'
请选择分组
'
,
pleaseSelectGroup
:
'
请选择分组
'
,
validityDaysRequired
:
'
请输入有效的天数(至少1天)
'
,
validityDaysRequired
:
'
请输入有效的天数(至少1天)
'
,
...
...
frontend/src/views/admin/AccountsView.vue
View file @
a8513da7
...
@@ -16,7 +16,7 @@
...
@@ -16,7 +16,7 @@
@
sync=
"showSync = true"
@
sync=
"showSync = true"
@
create=
"showCreate = true"
@
create=
"showCreate = true"
>
>
<template
#
before
>
<template
#
after
>
<!-- Column Settings Dropdown -->
<!-- Column Settings Dropdown -->
<div
class=
"relative"
ref=
"columnDropdownRef"
>
<div
class=
"relative"
ref=
"columnDropdownRef"
>
<button
<button
...
...
frontend/src/views/admin/SubscriptionsView.vue
View file @
a8513da7
...
@@ -85,6 +85,14 @@
...
@@ -85,6 +85,14 @@
<!-- Right: Actions -->
<!-- Right: Actions -->
<div
class=
"ml-auto flex flex-wrap items-center justify-end gap-3"
>
<div
class=
"ml-auto flex flex-wrap items-center justify-end gap-3"
>
<button
@
click=
"loadSubscriptions"
:disabled=
"loading"
class=
"btn btn-secondary"
:title=
"t('common.refresh')"
>
<Icon
name=
"refresh"
size=
"md"
:class=
"loading ? 'animate-spin' : ''"
/>
</button>
<!-- Column Settings Dropdown -->
<!-- Column Settings Dropdown -->
<div
class=
"relative"
ref=
"columnDropdownRef"
>
<div
class=
"relative"
ref=
"columnDropdownRef"
>
<button
<button
...
@@ -136,14 +144,6 @@
...
@@ -136,14 +144,6 @@
</div>
</div>
</div>
</div>
</div>
</div>
<button
@
click=
"loadSubscriptions"
:disabled=
"loading"
class=
"btn btn-secondary"
:title=
"t('common.refresh')"
>
<Icon
name=
"refresh"
size=
"md"
:class=
"loading ? 'animate-spin' : ''"
/>
</button>
<button
@
click=
"showAssignModal = true"
class=
"btn btn-primary"
>
<button
@
click=
"showAssignModal = true"
class=
"btn btn-primary"
>
<Icon
name=
"plus"
size=
"md"
class=
"mr-2"
/>
<Icon
name=
"plus"
size=
"md"
class=
"mr-2"
/>
{{
t
(
'
admin.subscriptions.assignSubscription
'
)
}}
{{
t
(
'
admin.subscriptions.assignSubscription
'
)
}}
...
@@ -359,10 +359,10 @@
...
@@ -359,10 +359,10 @@
<
button
<
button
v
-
if
=
"
row.status === 'active'
"
v
-
if
=
"
row.status === 'active'
"
@
click
=
"
handleExtend(row)
"
@
click
=
"
handleExtend(row)
"
class
=
"
flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-
green
-50 hover:text-
green
-600 dark:hover:bg-
green
-900/20 dark:hover:text-
green
-400
"
class
=
"
flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-
blue
-50 hover:text-
blue
-600 dark:hover:bg-
blue
-900/20 dark:hover:text-
blue
-400
"
>
>
<
Icon
name
=
"
c
lock
"
size
=
"
sm
"
/>
<
Icon
name
=
"
c
alendar
"
size
=
"
sm
"
/>
<
span
class
=
"
text-xs
"
>
{{
t
(
'
admin.subscriptions.
extend
'
)
}}
<
/span
>
<
span
class
=
"
text-xs
"
>
{{
t
(
'
admin.subscriptions.
adjust
'
)
}}
<
/span
>
<
/button
>
<
/button
>
<
button
<
button
v
-
if
=
"
row.status === 'active'
"
v
-
if
=
"
row.status === 'active'
"
...
@@ -512,10 +512,10 @@
...
@@ -512,10 +512,10 @@
<
/template
>
<
/template
>
<
/BaseDialog
>
<
/BaseDialog
>
<!--
Extend
Subscription
Modal
-->
<!--
Adjust
Subscription
Modal
-->
<
BaseDialog
<
BaseDialog
:
show
=
"
showExtendModal
"
:
show
=
"
showExtendModal
"
:
title
=
"
t('admin.subscriptions.
extend
Subscription')
"
:
title
=
"
t('admin.subscriptions.
adjust
Subscription')
"
width
=
"
narrow
"
width
=
"
narrow
"
@
close
=
"
closeExtendModal
"
@
close
=
"
closeExtendModal
"
>
>
...
@@ -527,7 +527,7 @@
...
@@ -527,7 +527,7 @@
>
>
<
div
class
=
"
rounded-lg bg-gray-50 p-4 dark:bg-dark-700
"
>
<
div
class
=
"
rounded-lg bg-gray-50 p-4 dark:bg-dark-700
"
>
<
p
class
=
"
text-sm text-gray-600 dark:text-gray-400
"
>
<
p
class
=
"
text-sm text-gray-600 dark:text-gray-400
"
>
{{
t
(
'
admin.subscriptions.
extend
ingFor
'
)
}}
{{
t
(
'
admin.subscriptions.
adjust
ingFor
'
)
}}
<
span
class
=
"
font-medium text-gray-900 dark:text-white
"
>
{{
<
span
class
=
"
font-medium text-gray-900 dark:text-white
"
>
{{
extendingSubscription
.
user
?.
email
extendingSubscription
.
user
?.
email
}}
<
/span
>
}}
<
/span
>
...
@@ -542,10 +542,25 @@
...
@@ -542,10 +542,25 @@
}}
}}
<
/span
>
<
/span
>
<
/p
>
<
/p
>
<
p
v
-
if
=
"
extendingSubscription.expires_at
"
class
=
"
mt-1 text-sm text-gray-600 dark:text-gray-400
"
>
{{
t
(
'
admin.subscriptions.remainingDays
'
)
}}
:
<
span
class
=
"
font-medium text-gray-900 dark:text-white
"
>
{{
getDaysRemaining
(
extendingSubscription
.
expires_at
)
??
0
}}
<
/span
>
<
/p
>
<
/div
>
<
/div
>
<
div
>
<
div
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.subscriptions.form.extendDays
'
)
}}
<
/label
>
<
label
class
=
"
input-label
"
>
{{
t
(
'
admin.subscriptions.form.adjustDays
'
)
}}
<
/label
>
<
input
v
-
model
.
number
=
"
extendForm.days
"
type
=
"
number
"
min
=
"
1
"
required
class
=
"
input
"
/>
<
div
class
=
"
flex items-center gap-2
"
>
<
input
v
-
model
.
number
=
"
extendForm.days
"
type
=
"
number
"
required
class
=
"
input text-center
"
:
placeholder
=
"
t('admin.subscriptions.adjustDaysPlaceholder')
"
/>
<
/div
>
<
p
class
=
"
input-hint
"
>
{{
t
(
'
admin.subscriptions.adjustHint
'
)
}}
<
/p
>
<
/div
>
<
/div
>
<
/form
>
<
/form
>
<
template
#
footer
>
<
template
#
footer
>
...
@@ -559,7 +574,7 @@
...
@@ -559,7 +574,7 @@
:
disabled
=
"
submitting
"
:
disabled
=
"
submitting
"
class
=
"
btn btn-primary
"
class
=
"
btn btn-primary
"
>
>
{{
submitting
?
t
(
'
admin.subscriptions.
extend
ing
'
)
:
t
(
'
admin.subscriptions.
extend
'
)
}}
{{
submitting
?
t
(
'
admin.subscriptions.
adjust
ing
'
)
:
t
(
'
admin.subscriptions.
adjust
'
)
}}
<
/button
>
<
/button
>
<
/div
>
<
/div
>
<
/template
>
<
/template
>
...
@@ -1000,17 +1015,27 @@ const closeExtendModal = () => {
...
@@ -1000,17 +1015,27 @@ const closeExtendModal = () => {
const
handleExtendSubscription
=
async
()
=>
{
const
handleExtendSubscription
=
async
()
=>
{
if
(
!
extendingSubscription
.
value
)
return
if
(
!
extendingSubscription
.
value
)
return
// 前端验证:调整后剩余天数必须 > 0
if
(
extendingSubscription
.
value
.
expires_at
)
{
const
currentDaysRemaining
=
getDaysRemaining
(
extendingSubscription
.
value
.
expires_at
)
??
0
const
newDaysRemaining
=
currentDaysRemaining
+
extendForm
.
days
if
(
newDaysRemaining
<=
0
)
{
appStore
.
showError
(
t
(
'
admin.subscriptions.adjustWouldExpire
'
))
return
}
}
submitting
.
value
=
true
submitting
.
value
=
true
try
{
try
{
await
adminAPI
.
subscriptions
.
extend
(
extendingSubscription
.
value
.
id
,
{
await
adminAPI
.
subscriptions
.
extend
(
extendingSubscription
.
value
.
id
,
{
days
:
extendForm
.
days
days
:
extendForm
.
days
}
)
}
)
appStore
.
showSuccess
(
t
(
'
admin.subscriptions.subscription
Extend
ed
'
))
appStore
.
showSuccess
(
t
(
'
admin.subscriptions.subscription
Adjust
ed
'
))
closeExtendModal
()
closeExtendModal
()
loadSubscriptions
()
loadSubscriptions
()
}
catch
(
error
:
any
)
{
}
catch
(
error
:
any
)
{
appStore
.
showError
(
error
.
response
?.
data
?.
detail
||
t
(
'
admin.subscriptions.failedTo
Extend
'
))
appStore
.
showError
(
error
.
response
?.
data
?.
detail
||
t
(
'
admin.subscriptions.failedTo
Adjust
'
))
console
.
error
(
'
Error
extend
ing subscription:
'
,
error
)
console
.
error
(
'
Error
adjust
ing subscription:
'
,
error
)
}
finally
{
}
finally
{
submitting
.
value
=
false
submitting
.
value
=
false
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment