mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-17 13:24:45 +08:00
feat: add platform type filter to subscription management page
Add a platform filter dropdown to the admin subscriptions view, allowing filtering subscriptions by platform (Anthropic, OpenAI, Gemini, etc.) through the group association. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -77,12 +77,13 @@ func (h *SubscriptionHandler) List(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
status := c.Query("status")
|
status := c.Query("status")
|
||||||
|
platform := c.Query("platform")
|
||||||
|
|
||||||
// Parse sorting parameters
|
// Parse sorting parameters
|
||||||
sortBy := c.DefaultQuery("sort_by", "created_at")
|
sortBy := c.DefaultQuery("sort_by", "created_at")
|
||||||
sortOrder := c.DefaultQuery("sort_order", "desc")
|
sortOrder := c.DefaultQuery("sort_order", "desc")
|
||||||
|
|
||||||
subscriptions, pagination, err := h.subscriptionService.List(c.Request.Context(), page, pageSize, userID, groupID, status, sortBy, sortOrder)
|
subscriptions, pagination, err := h.subscriptionService.List(c.Request.Context(), page, pageSize, userID, groupID, status, platform, sortBy, sortOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||||
|
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||||
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
|
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
@@ -190,7 +191,7 @@ func (r *userSubscriptionRepository) ListByGroupID(ctx context.Context, groupID
|
|||||||
return userSubscriptionEntitiesToService(subs), paginationResultFromTotal(int64(total), params), nil
|
return userSubscriptionEntitiesToService(subs), paginationResultFromTotal(int64(total), params), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *userSubscriptionRepository) List(ctx context.Context, params pagination.PaginationParams, userID, groupID *int64, status, sortBy, sortOrder string) ([]service.UserSubscription, *pagination.PaginationResult, error) {
|
func (r *userSubscriptionRepository) List(ctx context.Context, params pagination.PaginationParams, userID, groupID *int64, status, platform, sortBy, sortOrder string) ([]service.UserSubscription, *pagination.PaginationResult, error) {
|
||||||
client := clientFromContext(ctx, r.client)
|
client := clientFromContext(ctx, r.client)
|
||||||
q := client.UserSubscription.Query()
|
q := client.UserSubscription.Query()
|
||||||
if userID != nil {
|
if userID != nil {
|
||||||
@@ -199,6 +200,9 @@ func (r *userSubscriptionRepository) List(ctx context.Context, params pagination
|
|||||||
if groupID != nil {
|
if groupID != nil {
|
||||||
q = q.Where(usersubscription.GroupIDEQ(*groupID))
|
q = q.Where(usersubscription.GroupIDEQ(*groupID))
|
||||||
}
|
}
|
||||||
|
if platform != "" {
|
||||||
|
q = q.Where(usersubscription.HasGroupWith(group.PlatformEQ(platform)))
|
||||||
|
}
|
||||||
|
|
||||||
// Status filtering with real-time expiration check
|
// Status filtering with real-time expiration check
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func (userSubRepoNoop) ListActiveByUserID(context.Context, int64) ([]UserSubscri
|
|||||||
func (userSubRepoNoop) ListByGroupID(context.Context, int64, pagination.PaginationParams) ([]UserSubscription, *pagination.PaginationResult, error) {
|
func (userSubRepoNoop) ListByGroupID(context.Context, int64, pagination.PaginationParams) ([]UserSubscription, *pagination.PaginationResult, error) {
|
||||||
panic("unexpected ListByGroupID call")
|
panic("unexpected ListByGroupID call")
|
||||||
}
|
}
|
||||||
func (userSubRepoNoop) List(context.Context, pagination.PaginationParams, *int64, *int64, string, string, string) ([]UserSubscription, *pagination.PaginationResult, error) {
|
func (userSubRepoNoop) List(context.Context, pagination.PaginationParams, *int64, *int64, string, string, string, string) ([]UserSubscription, *pagination.PaginationResult, error) {
|
||||||
panic("unexpected List call")
|
panic("unexpected List call")
|
||||||
}
|
}
|
||||||
func (userSubRepoNoop) ExistsByUserIDAndGroupID(context.Context, int64, int64) (bool, error) {
|
func (userSubRepoNoop) ExistsByUserIDAndGroupID(context.Context, int64, int64) (bool, error) {
|
||||||
|
|||||||
@@ -634,9 +634,9 @@ func (s *SubscriptionService) ListGroupSubscriptions(ctx context.Context, groupI
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List 获取所有订阅(分页,支持筛选和排序)
|
// List 获取所有订阅(分页,支持筛选和排序)
|
||||||
func (s *SubscriptionService) List(ctx context.Context, page, pageSize int, userID, groupID *int64, status, sortBy, sortOrder string) ([]UserSubscription, *pagination.PaginationResult, error) {
|
func (s *SubscriptionService) List(ctx context.Context, page, pageSize int, userID, groupID *int64, status, platform, sortBy, sortOrder string) ([]UserSubscription, *pagination.PaginationResult, error) {
|
||||||
params := pagination.PaginationParams{Page: page, PageSize: pageSize}
|
params := pagination.PaginationParams{Page: page, PageSize: pageSize}
|
||||||
subs, pag, err := s.userSubRepo.List(ctx, params, userID, groupID, status, sortBy, sortOrder)
|
subs, pag, err := s.userSubRepo.List(ctx, params, userID, groupID, status, platform, sortBy, sortOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ type UserSubscriptionRepository interface {
|
|||||||
ListByUserID(ctx context.Context, userID int64) ([]UserSubscription, error)
|
ListByUserID(ctx context.Context, userID int64) ([]UserSubscription, error)
|
||||||
ListActiveByUserID(ctx context.Context, userID int64) ([]UserSubscription, error)
|
ListActiveByUserID(ctx context.Context, userID int64) ([]UserSubscription, error)
|
||||||
ListByGroupID(ctx context.Context, groupID int64, params pagination.PaginationParams) ([]UserSubscription, *pagination.PaginationResult, error)
|
ListByGroupID(ctx context.Context, groupID int64, params pagination.PaginationParams) ([]UserSubscription, *pagination.PaginationResult, error)
|
||||||
List(ctx context.Context, params pagination.PaginationParams, userID, groupID *int64, status, sortBy, sortOrder string) ([]UserSubscription, *pagination.PaginationResult, error)
|
List(ctx context.Context, params pagination.PaginationParams, userID, groupID *int64, status, platform, sortBy, sortOrder string) ([]UserSubscription, *pagination.PaginationResult, error)
|
||||||
|
|
||||||
ExistsByUserIDAndGroupID(ctx context.Context, userID, groupID int64) (bool, error)
|
ExistsByUserIDAndGroupID(ctx context.Context, userID, groupID int64) (bool, error)
|
||||||
ExtendExpiry(ctx context.Context, subscriptionID int64, newExpiresAt time.Time) error
|
ExtendExpiry(ctx context.Context, subscriptionID int64, newExpiresAt time.Time) error
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export async function list(
|
|||||||
status?: 'active' | 'expired' | 'revoked'
|
status?: 'active' | 'expired' | 'revoked'
|
||||||
user_id?: number
|
user_id?: number
|
||||||
group_id?: number
|
group_id?: number
|
||||||
|
platform?: string
|
||||||
sort_by?: string
|
sort_by?: string
|
||||||
sort_order?: 'asc' | 'desc'
|
sort_order?: 'asc' | 'desc'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1694,6 +1694,7 @@ export default {
|
|||||||
revokeSubscription: 'Revoke Subscription',
|
revokeSubscription: 'Revoke Subscription',
|
||||||
allStatus: 'All Status',
|
allStatus: 'All Status',
|
||||||
allGroups: 'All Groups',
|
allGroups: 'All Groups',
|
||||||
|
allPlatforms: 'All Platforms',
|
||||||
daily: 'Daily',
|
daily: 'Daily',
|
||||||
weekly: 'Weekly',
|
weekly: 'Weekly',
|
||||||
monthly: 'Monthly',
|
monthly: 'Monthly',
|
||||||
|
|||||||
@@ -1774,6 +1774,7 @@ export default {
|
|||||||
revokeSubscription: '撤销订阅',
|
revokeSubscription: '撤销订阅',
|
||||||
allStatus: '全部状态',
|
allStatus: '全部状态',
|
||||||
allGroups: '全部分组',
|
allGroups: '全部分组',
|
||||||
|
allPlatforms: '全部平台',
|
||||||
daily: '每日',
|
daily: '每日',
|
||||||
weekly: '每周',
|
weekly: '每周',
|
||||||
monthly: '每月',
|
monthly: '每月',
|
||||||
|
|||||||
@@ -81,6 +81,14 @@
|
|||||||
@change="applyFilters"
|
@change="applyFilters"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-full sm:w-40">
|
||||||
|
<Select
|
||||||
|
v-model="filters.platform"
|
||||||
|
:options="platformFilterOptions"
|
||||||
|
:placeholder="t('admin.subscriptions.allPlatforms')"
|
||||||
|
@change="applyFilters"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right: Actions -->
|
<!-- Right: Actions -->
|
||||||
@@ -813,6 +821,7 @@ let userSearchTimeout: ReturnType<typeof setTimeout> | null = null
|
|||||||
const filters = reactive({
|
const filters = reactive({
|
||||||
status: 'active',
|
status: 'active',
|
||||||
group_id: '',
|
group_id: '',
|
||||||
|
platform: '',
|
||||||
user_id: null as number | null
|
user_id: null as number | null
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -855,6 +864,15 @@ const groupOptions = computed(() => [
|
|||||||
...groups.value.map((g) => ({ value: g.id.toString(), label: g.name }))
|
...groups.value.map((g) => ({ value: g.id.toString(), label: g.name }))
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const platformFilterOptions = computed(() => [
|
||||||
|
{ value: '', label: t('admin.subscriptions.allPlatforms') },
|
||||||
|
{ value: 'anthropic', label: 'Anthropic' },
|
||||||
|
{ value: 'openai', label: 'OpenAI' },
|
||||||
|
{ value: 'gemini', label: 'Gemini' },
|
||||||
|
{ value: 'antigravity', label: 'Antigravity' },
|
||||||
|
{ value: 'sora', label: 'Sora' }
|
||||||
|
])
|
||||||
|
|
||||||
// Group options for assign (only subscription type groups)
|
// Group options for assign (only subscription type groups)
|
||||||
const subscriptionGroupOptions = computed(() =>
|
const subscriptionGroupOptions = computed(() =>
|
||||||
groups.value
|
groups.value
|
||||||
@@ -890,6 +908,7 @@ const loadSubscriptions = async () => {
|
|||||||
{
|
{
|
||||||
status: (filters.status as any) || undefined,
|
status: (filters.status as any) || undefined,
|
||||||
group_id: filters.group_id ? parseInt(filters.group_id) : undefined,
|
group_id: filters.group_id ? parseInt(filters.group_id) : undefined,
|
||||||
|
platform: filters.platform || undefined,
|
||||||
user_id: filters.user_id || undefined,
|
user_id: filters.user_id || undefined,
|
||||||
sort_by: sortState.sort_by,
|
sort_by: sortState.sort_by,
|
||||||
sort_order: sortState.sort_order
|
sort_order: sortState.sort_order
|
||||||
|
|||||||
Reference in New Issue
Block a user