diff --git a/frontend/src/components/developer-sidebar/index.vue b/frontend/src/components/admin-sidebar/index.vue similarity index 97% rename from frontend/src/components/developer-sidebar/index.vue rename to frontend/src/components/admin-sidebar/index.vue index 74d3173..00aac83 100644 --- a/frontend/src/components/developer-sidebar/index.vue +++ b/frontend/src/components/admin-sidebar/index.vue @@ -4,7 +4,7 @@ import { onMounted, reactive } from 'vue' import NavTeam from '@/components/app-sidebar/nav-team.vue' import TeamSwitcher from '@/components/app-sidebar/team-switcher.vue' -import NavFooter from '@/components/developer-sidebar/nav-footer.vue' +import NavFooter from '@/components/admin-sidebar/nav-footer.vue' const user = reactive({ name: '管理员', @@ -33,7 +33,7 @@ const teams = [ { name: '管理后台', logo: Code, - plan: 'Developer', + plan: 'Admin', }, ] diff --git a/frontend/src/components/developer-sidebar/nav-footer.vue b/frontend/src/components/admin-sidebar/nav-footer.vue similarity index 100% rename from frontend/src/components/developer-sidebar/nav-footer.vue rename to frontend/src/components/admin-sidebar/nav-footer.vue diff --git a/frontend/src/components/marketing-layout/the-header.vue b/frontend/src/components/marketing-layout/the-header.vue index a911b0f..fa47b2e 100644 --- a/frontend/src/components/marketing-layout/the-header.vue +++ b/frontend/src/components/marketing-layout/the-header.vue @@ -62,7 +62,7 @@ const logoSrc = computed(() => { } }) -const developerMenus = computed(() => ({ +const adminMenus = computed(() => ({ main: { title: t('nav.mainFeatures'), items: [ @@ -204,15 +204,15 @@ watch(route, () => { - {{ developerMenus.main.title }} + {{ adminMenus.main.title }} { - {{ developerMenus.business.title }} + {{ adminMenus.business.title }} { - {{ developerMenus.advanced.title }} + {{ adminMenus.advanced.title }} { - {{ t('nav.developerDashboard') }} + {{ t('nav.adminDashboard') }} @@ -397,10 +397,10 @@ watch(route, () => { - {{ developerMenus.main.title }} + {{ adminMenus.main.title }} { - {{ developerMenus.business.title }} + {{ adminMenus.business.title }} { - {{ developerMenus.advanced.title }} + {{ adminMenus.advanced.title }} -import { Icon } from '@iconify/vue' -import { onMounted, ref } from 'vue' -import { useRoute, useRouter } from 'vue-router' -import { toast } from 'vue-sonner' - -import { BasicPage } from '@/components/global-layout' - -interface CardType { - id: number - name: string - billing_type: string - price: number -} - -interface CardTypePermission { - card_type_id: number - name: string - billing_type: string - price: number - can_generate: boolean -} - -const route = useRoute() -const router = useRouter() -const API_BASE = 'http://localhost:8080/api/v1' - -const loading = ref(true) -const saving = ref(false) -const agentAppName = ref('') -const cardTypeForm = ref([]) - -function getBillingTypeText(type: string) { - const map: Record = { - subscription: '订阅', - time: '计时', - point: '点卡', - } - return map[type] || type -} - -async function fetchData() { - loading.value = true - try { - const token = localStorage.getItem('token') - - const [agentAppRes, cardTypesRes] = await Promise.all([ - fetch(`${API_BASE}/dev/agent-apps/${route.params.id}`, { - headers: { Authorization: `Bearer ${token}` }, - }), - fetch(`${API_BASE}/dev/card-types`, { - headers: { Authorization: `Bearer ${token}` }, - }), - ]) - - const [agentAppData, cardTypesData] = await Promise.all([ - agentAppRes.json(), - cardTypesRes.json(), - ]) - - if (agentAppData.code === 200 && agentAppData.agent_app) { - agentAppName.value = `${agentAppData.agent_app.agent_name} - ${agentAppData.agent_app.app_name}` - const existingPerms = agentAppData.agent_app.card_types || [] - - const allCardTypes = cardTypesData.card_types || [] - cardTypeForm.value = allCardTypes.map((ct: CardType) => { - const existing = existingPerms.find((p: any) => p.card_type_id === ct.id) - return { - card_type_id: ct.id, - name: ct.name, - billing_type: ct.billing_type, - price: ct.price, - can_generate: existing ? existing.can_generate : false, - } - }) - } - else { - toast.error('获取授权信息失败') - router.push('/admin/agent-apps') - } - } - catch (error) { - console.error('获取数据失败:', error) - toast.error('获取数据失败') - router.push('/admin/agent-apps') - } - finally { - loading.value = false - } -} - -function selectAll() { - cardTypeForm.value.forEach(ct => ct.can_generate = true) -} - -function clearAll() { - cardTypeForm.value.forEach(ct => ct.can_generate = false) -} - -async function handleSubmit() { - saving.value = true - try { - const token = localStorage.getItem('token') - const response = await fetch(`${API_BASE}/dev/agent-apps/${route.params.id}/card-types`, { - method: 'PUT', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - card_types: cardTypeForm.value.map(ct => ({ - card_type_id: ct.card_type_id, - can_generate: ct.can_generate, - })), - }), - }) - - const data = await response.json() - if (data.code === 200) { - toast.success('保存成功') - router.push('/admin/agent-apps') - } - else { - toast.error(data.message || '保存失败') - } - } - catch (error) { - console.error('保存失败:', error) - toast.error('保存失败') - } - finally { - saving.value = false - } -} - -function goBack() { - router.push('/admin/agent-apps') -} - -onMounted(() => { - fetchData() -}) - - - - - - - - 返回 - - - - - - - - - - - 授权信息 - - {{ agentAppName }} - - - - - - - 全选 - - - - 清空 - - - - - - - - - - - - 卡类名称 - 计费类型 - 价格 - - - - - - - - - {{ cardType.name }} - - - - {{ getBillingTypeText(cardType.billing_type) }} - - - - ¥{{ cardType.price }} - - - - - - - - 取消 - - - - 保存 - - - - - - diff --git a/frontend/src/pages/developer/agent-apps/[id]/edit.vue b/frontend/src/pages/developer/agent-apps/[id]/edit.vue deleted file mode 100644 index ae6530e..0000000 --- a/frontend/src/pages/developer/agent-apps/[id]/edit.vue +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - - 返回 - - - - - - - - - - - 授权信息 - - 代理商: {{ agentApp.agent_name }} | 应用: {{ agentApp.app_name }} - - - - - - 状态 - - - - - - - - 已授权 - - - 已禁用 - - - - - - - - 折扣 - - - - - {{ (form.discount * 10).toFixed(1) }}折 - - - - 代理商购买卡密的折扣,范围0-1 - - - - - 当前余额 - - ¥{{ agentApp.balance.toFixed(2) }} - - - - 充值余额 - - - - - - 取消 - - - - 保存 - - - - - - diff --git a/frontend/src/pages/developer/agent-apps/[id]/recharge.vue b/frontend/src/pages/developer/agent-apps/[id]/recharge.vue deleted file mode 100644 index aa250e8..0000000 --- a/frontend/src/pages/developer/agent-apps/[id]/recharge.vue +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - - - 返回 - - - - - - - - - - - 充值信息 - - {{ agentAppName }} - - - - - 当前余额 - - ¥{{ currentBalance.toFixed(2) }} - - - - - - 充值金额 - - - - - - - ¥{{ amount }} - - - - - - 当前余额 - ¥{{ currentBalance.toFixed(2) }} - - - 充值金额 - +¥{{ rechargeAmount.toFixed(2) }} - - - - 充值后余额 - ¥{{ (currentBalance + rechargeAmount).toFixed(2) }} - - - - - - 取消 - - - - 确认充值 - - - - - - diff --git a/frontend/src/pages/developer/agent-apps/components/columns.ts b/frontend/src/pages/developer/agent-apps/components/columns.ts deleted file mode 100644 index 27c540d..0000000 --- a/frontend/src/pages/developer/agent-apps/components/columns.ts +++ /dev/null @@ -1,228 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' - -import { h } from 'vue' -import { useRouter } from 'vue-router' - -import type { CombinedItem } from '@/pages/admin/agent-apps/data/schema' - -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getStatusVariant(status: string, hasAuthorization: boolean) { - if (hasAuthorization) { - return status === 'active' ? 'default' : 'secondary' - } - if (status === 'pending') - return 'outline' - if (status === 'approved') - return 'default' - return 'destructive' -} - -export function getStatusText(status: string, hasAuthorization: boolean) { - if (hasAuthorization) { - return status === 'active' ? '已授权' : '已禁用' - } - const map: Record = { - pending: '待处理', - approved: '已通过', - rejected: '已拒绝', - } - return map[status] || status -} - -export function getItemTypeText(itemType: string) { - const map: Record = { - authorization: '授权记录', - request: '收到的申请', - my_request: '发出的申请', - } - return map[itemType] || itemType -} - -export function getColumns(actions: { - onApprove: (row: CombinedItem) => void - onReject: (row: CombinedItem) => void - onRemove: (row: CombinedItem) => void -}): ColumnDef[] { - return [ - { - accessorKey: 'itemType', - header: '类型', - cell: ({ row }) => { - const itemType = row.getValue('itemType') as string - const variants: Record = { - authorization: 'default', - request: 'outline', - my_request: 'secondary', - } - return h(Badge, { variant: variants[itemType] || 'default' }, () => getItemTypeText(itemType)) - }, - }, - { - accessorKey: 'agent_name', - header: '代理商/管理员', - cell: ({ row }) => { - const agentName = row.getValue('agent_name') as string - const itemType = row.original.itemType - const label = itemType === 'my_request' ? '管理员' : '代理商' - return h('div', {}, [ - h('div', { class: 'font-medium' }, agentName || '-'), - h('div', { class: 'text-xs text-muted-foreground' }, label), - ]) - }, - }, - { - accessorKey: 'app_name', - header: '应用', - cell: ({ row }) => { - const appName = row.getValue('app_name') as string - return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-' - }, - }, - { - accessorKey: 'status', - header: '状态', - cell: ({ row }) => { - const status = row.getValue('status') as string - const hasAuthorization = row.original.hasAuthorization - const variant = getStatusVariant(status, hasAuthorization) - const text = getStatusText(status, hasAuthorization) - return h(Badge, { variant }, () => text) - }, - }, - { - accessorKey: 'discount', - header: '折扣', - cell: ({ row }) => { - const hasAuthorization = row.original.hasAuthorization - if (!hasAuthorization) - return '-' - const discount = row.getValue('discount') as number - return `${(discount * 10).toFixed(1)}折` - }, - }, - { - accessorKey: 'balance', - header: '余额', - cell: ({ row }) => { - const hasAuthorization = row.original.hasAuthorization - if (!hasAuthorization) - return '-' - const balance = row.getValue('balance') as number - return h('span', { class: 'font-medium' }, `¥${balance.toFixed(2)}`) - }, - }, - { - accessorKey: 'created_at', - header: '创建时间', - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, '操作'), - cell: ({ row }) => { - const item = row.original - const router = useRouter() - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => - h('span', { class: 'sr-only' }, '打开菜单'))), - h( - DropdownMenuContent, - { align: 'end' }, - () => { - const items = [] - - if (item.itemType === 'request' && item.status === 'pending') { - items.push( - h(DropdownMenuItem, { onClick: () => actions.onApprove(item) }, () => [ - h('span', { class: 'i-lucide-check mr-2 h-4 w-4' }), - '批准', - ]), - h(DropdownMenuItem, { onClick: () => actions.onReject(item) }, () => [ - h('span', { class: 'i-lucide-x mr-2 h-4 w-4' }), - '拒绝', - ]), - ) - } - - if (item.hasAuthorization) { - items.push( - h(DropdownMenuItem, { onClick: () => router.push(`/admin/agent-apps/${item.authorizationId}/edit`) }, () => [ - h('span', { class: 'i-lucide-edit mr-2 h-4 w-4' }), - '编辑', - ]), - h(DropdownMenuItem, { onClick: () => router.push(`/admin/agent-apps/${item.authorizationId}/card-types`) }, () => [ - h('span', { class: 'i-lucide-key mr-2 h-4 w-4' }), - '卡密权限', - ]), - h(DropdownMenuItem, { onClick: () => router.push(`/admin/agent-apps/${item.authorizationId}/recharge`) }, () => [ - h('span', { class: 'i-lucide-wallet mr-2 h-4 w-4' }), - '充值余额', - ]), - ) - } - - if (item.itemType === 'my_request' && item.status === 'pending') { - items.push( - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onRemove(item) }, () => [ - h('span', { class: 'i-lucide-trash-2 mr-2 h-4 w-4' }), - '撤回申请', - ]), - ) - } - else if (item.hasAuthorization) { - items.push( - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onRemove(item) }, () => [ - h('span', { class: 'i-lucide-trash-2 mr-2 h-4 w-4' }), - '移除授权', - ]), - ) - } - - return items - }, - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/agent-apps/components/data-table-toolbar.vue b/frontend/src/pages/developer/agent-apps/components/data-table-toolbar.vue deleted file mode 100644 index 5a6bcd0..0000000 --- a/frontend/src/pages/developer/agent-apps/components/data-table-toolbar.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - 重置 - - - - diff --git a/frontend/src/pages/developer/agent-apps/components/data-table.vue b/frontend/src/pages/developer/agent-apps/components/data-table.vue deleted file mode 100644 index 8b6d924..0000000 --- a/frontend/src/pages/developer/agent-apps/components/data-table.vue +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/agent-apps/data/data.ts b/frontend/src/pages/developer/agent-apps/data/data.ts deleted file mode 100644 index 6b1f9c9..0000000 --- a/frontend/src/pages/developer/agent-apps/data/data.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const agentAppStatuses = [ - { label: '已授权', value: 'active' }, - { label: '已禁用', value: 'inactive' }, - { label: '待处理', value: 'pending' }, - { label: '已拒绝', value: 'rejected' }, -] - -export const itemTypes = [ - { label: '授权记录', value: 'authorization' }, - { label: '收到的申请', value: 'request' }, - { label: '发出的申请', value: 'my_request' }, -] diff --git a/frontend/src/pages/developer/agent-apps/data/schema.ts b/frontend/src/pages/developer/agent-apps/data/schema.ts deleted file mode 100644 index 0f21257..0000000 --- a/frontend/src/pages/developer/agent-apps/data/schema.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { z } from 'zod' - -export const agentAppStatusSchema = z.enum(['active', 'inactive', 'pending', 'approved', 'rejected']) - -export const agentAppSchema = z.object({ - id: z.number(), - agent_id: z.number(), - agent_name: z.string(), - application_id: z.number(), - app_name: z.string(), - status: agentAppStatusSchema, - discount: z.number(), - balance: z.number(), - card_types: z.array(z.object({ - card_type_id: z.number(), - can_generate: z.boolean(), - })).optional(), - created_at: z.string(), - updated_at: z.string().optional(), - is_received: z.boolean().optional(), -}) - -export const agentRequestSchema = z.object({ - id: z.number(), - developer_id: z.number(), - developer_name: z.string(), - agent_id: z.number(), - agent_name: z.string(), - application_id: z.number(), - app_name: z.string(), - status: z.enum(['pending', 'approved', 'rejected']), - message: z.string().optional(), - created_at: z.string(), -}) - -export type AgentApp = z.infer -export type AgentRequest = z.infer - -export interface CombinedItem { - id: number - agent_id: number - agent_name: string - application_id: number - app_name: string - status: string - discount: number - balance: number - card_types?: Array<{ card_type_id: number, can_generate: boolean }> - created_at: string - itemType: 'authorization' | 'request' | 'my_request' - hasAuthorization: boolean - authorizationId: number | null - is_received?: boolean -} diff --git a/frontend/src/pages/developer/agent-apps/index.vue b/frontend/src/pages/developer/agent-apps/index.vue deleted file mode 100644 index 76563ec..0000000 --- a/frontend/src/pages/developer/agent-apps/index.vue +++ /dev/null @@ -1,389 +0,0 @@ - - - - - - - - {{ t('admin.agentApps.requestAuth') }} - - - - {{ t('admin.agentApps.inviteAuth') }} - - - - - - - - - {{ t('admin.agentApps.totalAuth') }} - - - - - - {{ agentApps.length }} - - - - - - - - {{ t('admin.agentApps.activeAuth') }} - - - - - - {{ activeCount }} - - - - - - - - {{ t('admin.agentApps.pendingRequests') }} - - - - - - {{ pendingCount }} - - - - - - - - {{ t('admin.agentApps.agentBalance') }} - - - - - - ¥{{ totalBalance.toFixed(2) }} - - - - - - - - - - - - - - r.original.id))" - > - - {{ t('admin.agentApps.batchApprove') }} - - r.original.id))" - > - - {{ t('admin.agentApps.batchReject') }} - - r.original.id))" - > - - {{ t('admin.agentApps.batchRemove') }} - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/agent-apps/invite.vue b/frontend/src/pages/developer/agent-apps/invite.vue deleted file mode 100644 index deffbf9..0000000 --- a/frontend/src/pages/developer/agent-apps/invite.vue +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - 返回 - - - - - - - 邀请信息 - - 填写以下信息邀请代理商成为授权方 - - - - - - 代理商ID - - - - 请向代理商索取其ID - - - - - - 选择应用 - - - - - - - - {{ app.name }} - - - - - - - - 邀请说明 - - - - - - - 取消 - - - - 发送邀请 - - - - - - diff --git a/frontend/src/pages/developer/agent-apps/request.vue b/frontend/src/pages/developer/agent-apps/request.vue deleted file mode 100644 index afb1f10..0000000 --- a/frontend/src/pages/developer/agent-apps/request.vue +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - 返回 - - - - - - - 申请信息 - - 填写以下信息向管理员申请应用授权 - - - - - - 管理员ID - - - - 请向管理员索取其ID - - - - - - 选择应用 - - - - - - - - {{ app.name }} - - - - - - - - 申请说明 - - - - - - - 取消 - - - - 提交申请 - - - - - - diff --git a/frontend/src/pages/developer/agents/[id].vue b/frontend/src/pages/developer/agents/[id].vue deleted file mode 100644 index 254523a..0000000 --- a/frontend/src/pages/developer/agents/[id].vue +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - - {{ t('admin.agents.addAgent') }} - - - - - - - - - {{ t('admin.agents.totalAgents') }} - - - - - - {{ totalAgents }} - - - - - - - - {{ t('admin.agents.status.active') }} - - - - - - {{ activeCount }} - - - - - - - - {{ t('admin.agents.status.inactive') }} - - - - - - {{ inactiveCount }} - - - - - - - - {{ t('admin.agents.status.banned') }} - - - - - - {{ bannedCount }} - - - - - - - - - - - - - - - - - - - {{ t('admin.agents.deleteAgent') }} - - - {{ t('admin.agents.deleteAgentConfirm', { username: deleteTarget?.username }) }} - - - - diff --git a/frontend/src/pages/developer/agents/components/columns.ts b/frontend/src/pages/developer/agents/components/columns.ts deleted file mode 100644 index 2406ca0..0000000 --- a/frontend/src/pages/developer/agents/components/columns.ts +++ /dev/null @@ -1,186 +0,0 @@ -import type { ColumnDef, Row } from '@tanstack/vue-table' -import type { Composer } from 'vue-i18n' - -import { Ban, Check, MoreHorizontal, Pencil, Trash2, X } from 'lucide-vue-next' -import { h } from 'vue' - -import type { Agent } from '../data/schema' - -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -function getIndentStyle(row: Row): string { - const depth = row.depth - if (depth === 0) return '' - return `padding-left: ${depth * 24}px;` -} - -export function getColumns(actions: { - onToggleStatus: (row: Agent) => void - onEdit: (row: Agent) => void - onDelete: (row: Agent) => void -}, t: Composer['t']): ColumnDef[] { - return [ - { - accessorKey: 'username', - header: () => t('admin.agents.columns.username'), - cell: ({ row }) => { - const agent = row.original - const indentStyle = getIndentStyle(row) - return h('div', { class: 'flex items-center space-x-3', style: indentStyle }, [ - h(Avatar, { class: 'h-8 w-8' }, () => [ - h(AvatarImage, { src: agent.avatar, alt: agent.username }), - h(AvatarFallback, {}, () => agent.username?.charAt(0).toUpperCase() || 'U'), - ]), - h('div', { class: 'flex flex-col' }, [ - h('span', { class: 'font-medium' }, agent.username), - agent.email ? h('span', { class: 'text-xs text-muted-foreground' }, agent.email) : null, - ]), - ]) - }, - }, - { - accessorKey: 'status', - header: () => t('admin.agents.columns.status'), - cell: ({ row }) => { - const status = row.getValue('status') as string - const statusMap: Record = { - active: { text: t('admin.agents.status.active'), variant: 'default' }, - inactive: { text: t('admin.agents.status.inactive'), variant: 'secondary' }, - banned: { text: t('admin.agents.status.banned'), variant: 'destructive' }, - } - const { text, variant } = statusMap[status] || { text: status, variant: 'default' } - return h(Badge, { variant }, () => text) - }, - }, - { - accessorKey: 'can_create_agent', - header: () => t('admin.agents.columns.canCreateAgent'), - cell: ({ row }) => { - const canCreate = row.getValue('can_create_agent') as boolean - return h('div', { class: 'flex items-center justify-center' }, [ - canCreate - ? h(Check, { class: 'h-4 w-4 text-green-500' }) - : h(X, { class: 'h-4 w-4 text-red-500' }), - ]) - }, - }, - { - accessorKey: 'cards_count', - header: () => t('admin.agents.columns.cardsCount'), - cell: ({ row }) => { - const count = row.getValue('cards_count') as number - return h(Badge, { variant: 'outline' }, () => String(count)) - }, - }, - { - accessorKey: 'child_agents_count', - header: () => t('admin.agents.columns.childAgentsCount'), - cell: ({ row }) => { - const count = row.getValue('child_agents_count') as number - return h(Badge, { variant: 'secondary' }, () => String(count)) - }, - }, - { - accessorKey: 'balance', - header: () => t('admin.agents.columns.balance'), - cell: ({ row }) => { - const balance = row.getValue('balance') as number - return `¥${balance.toFixed(2)}` - }, - }, - { - accessorKey: 'created_at', - header: () => t('admin.agents.columns.createdAt'), - cell: ({ row }) => { - const createdAt = row.getValue('created_at') as string - if (!createdAt) return '-' - try { - const date = new Date(createdAt) - if (Number.isNaN(date.getTime())) return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } catch { - return '-' - } - }, - }, - { - accessorKey: 'last_login_at', - header: () => t('admin.agents.columns.lastLoginAt'), - cell: ({ row }) => { - const lastLoginAt = row.getValue('last_login_at') as string - if (!lastLoginAt) return '-' - try { - const date = new Date(lastLoginAt) - if (Number.isNaN(date.getTime())) return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, t('common.actions')), - cell: ({ row }) => { - const agent = row.original - const isBanned = agent.status === 'banned' - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, t('common.openMenu')), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => [ - h(DropdownMenuItem, { onClick: () => actions.onEdit(agent) }, () => [ - h(Pencil, { class: 'mr-2 h-4 w-4' }), - t('common.edit'), - ]), - h(DropdownMenuItem, { onClick: () => actions.onToggleStatus(agent) }, () => [ - h(Ban, { class: 'mr-2 h-4 w-4' }), - isBanned ? t('admin.agents.enable') : t('admin.agents.disable'), - ]), - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(agent) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - t('common.delete'), - ]), - ], - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/agents/components/data-table.vue b/frontend/src/pages/developer/agents/components/data-table.vue deleted file mode 100644 index 6282a18..0000000 --- a/frontend/src/pages/developer/agents/components/data-table.vue +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - {{ viewMode === 'tree' ? t('admin.agents.listView') : t('admin.agents.treeView') }} - - - - - - - - diff --git a/frontend/src/pages/developer/agents/create.vue b/frontend/src/pages/developer/agents/create.vue deleted file mode 100644 index 4254d09..0000000 --- a/frontend/src/pages/developer/agents/create.vue +++ /dev/null @@ -1,295 +0,0 @@ - - - - - - - - - - - - {{ t('admin.agents.create.basicInfo') }} - - {{ t('admin.agents.create.basicInfoDesc') }} - - - - - {{ t('admin.agents.form.username') }} - - - - - - - {{ t('admin.agents.form.email') }} - - - - - - - {{ t('admin.agents.form.password') }} - - - - {{ t('admin.agents.create.passwordHint') }} - - - - - - - - - - {{ t('admin.agents.create.agentConfig') }} - - {{ t('admin.agents.create.agentConfigDesc') }} - - - - - {{ t('admin.agents.form.parentAgent') }} - - - - - - - - {{ t('admin.agents.form.noParent') }} - - - {{ agent.username }} - - - - - {{ t('admin.agents.create.parentAgentHint') }} - - - - - - {{ t('admin.agents.form.balance') }} - - - - - - - - - - ¥ - - - {{ t('admin.agents.form.balancePlaceholder') }} - - - - - - {{ t('admin.agents.form.canCreateAgent') }} - - {{ t('admin.agents.create.canCreateAgentHint') }} - - - - - - - - - - - - - - - {{ t('admin.agents.create.preview') }} - - - - - - {{ t('admin.agents.form.username') }} - {{ form.username || '-' }} - - - {{ t('admin.agents.form.email') }} - {{ form.email || '-' }} - - - {{ t('admin.agents.form.canCreateAgent') }} - {{ form.can_create_agent ? t('common.yes') : t('common.no') }} - - - {{ t('admin.agents.form.parentAgent') }} - {{ selectedParentAgent?.username || '-' }} - - - {{ t('admin.agents.form.balance') }} - ¥{{ form.balance.toFixed(2) }} - - - - - - - - - - - - {{ t('admin.agents.form.create') }} - - - {{ t('admin.agents.create.cancel') }} - - - - - - - - - diff --git a/frontend/src/pages/developer/agents/data/schema.ts b/frontend/src/pages/developer/agents/data/schema.ts deleted file mode 100644 index 019255a..0000000 --- a/frontend/src/pages/developer/agents/data/schema.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface Agent { - id: number - username: string - email: string - avatar: string - status: string - role: string - parent_agent_id: number | null - parent_agent_name: string - created_at: string - last_login_at: string - balance: number - can_create_agent: boolean - cards_count: number - child_agents_count: number - children?: Agent[] -} diff --git a/frontend/src/pages/developer/agents/index.vue b/frontend/src/pages/developer/agents/index.vue deleted file mode 100644 index 254523a..0000000 --- a/frontend/src/pages/developer/agents/index.vue +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - - {{ t('admin.agents.addAgent') }} - - - - - - - - - {{ t('admin.agents.totalAgents') }} - - - - - - {{ totalAgents }} - - - - - - - - {{ t('admin.agents.status.active') }} - - - - - - {{ activeCount }} - - - - - - - - {{ t('admin.agents.status.inactive') }} - - - - - - {{ inactiveCount }} - - - - - - - - {{ t('admin.agents.status.banned') }} - - - - - - {{ bannedCount }} - - - - - - - - - - - - - - - - - - - {{ t('admin.agents.deleteAgent') }} - - - {{ t('admin.agents.deleteAgentConfirm', { username: deleteTarget?.username }) }} - - - - diff --git a/frontend/src/pages/developer/announcements/[id].vue b/frontend/src/pages/developer/announcements/[id].vue deleted file mode 100644 index d9a2cc7..0000000 --- a/frontend/src/pages/developer/announcements/[id].vue +++ /dev/null @@ -1,354 +0,0 @@ - - - - - - - - {{ t('admin.announcements.create') }} - - - - - - - - - {{ t('admin.announcements.totalAnnouncements') }} - - - - - - {{ filteredAnnouncements.length }} - - - - - - - - {{ t('admin.announcements.activeCount') }} - - - - - - {{ activeCount }} - - - - - - - - {{ t('admin.announcements.inactiveCount') }} - - - - - - {{ inactiveCount }} - - - - - - - - {{ t('admin.announcements.topCount') }} - - - - - - {{ topCount }} - - - - - - - - r.original.id) || [])" - @batch-export="handleBatchExport(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - > - - - - - - - - - - - - - {{ t('admin.announcements.deleteAnnouncement') }} - - - {{ t('admin.announcements.deleteConfirm', { title: deleteTarget?.title }) }} - - - - - - {{ t('admin.announcements.batchDelete') }} - - - {{ t('admin.announcements.batchDeleteConfirm', { count: batchDeleteIds.length }) }} - - - - diff --git a/frontend/src/pages/developer/announcements/components/columns.ts b/frontend/src/pages/developer/announcements/components/columns.ts deleted file mode 100644 index 1a33da2..0000000 --- a/frontend/src/pages/developer/announcements/components/columns.ts +++ /dev/null @@ -1,117 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' - -import { Icon } from '@iconify/vue' -import { h } from 'vue' - -import type { Announcement } from '@/pages/admin/announcements/data/schema' - -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' - -interface ColumnOptions { - onToggleTop: (row: Announcement) => void - onEdit: (row: Announcement) => void - onDelete: (row: Announcement) => void -} - -export function getColumns(options: ColumnOptions, t: (key: string) => string): ColumnDef[] { - return [ - { - accessorKey: 'title', - header: () => t('admin.announcements.columns.title'), - cell: ({ row }) => { - return h('div', { class: 'space-y-1' }, [ - h('div', { class: 'font-medium' }, row.original.title), - h('div', { class: 'text-xs text-muted-foreground truncate max-w-xs' }, row.original.content), - ]) - }, - }, - { - accessorKey: 'application_name', - header: () => t('admin.announcements.columns.application'), - cell: ({ row }) => { - const appName = row.original.application_name - return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-' - }, - }, - { - accessorKey: 'type', - header: () => t('admin.announcements.columns.type'), - cell: ({ row }) => { - const typeLabels: Record = { - info: t('admin.announcements.types.info'), - warning: t('admin.announcements.types.warning'), - error: t('admin.announcements.types.error'), - success: t('admin.announcements.types.success'), - } - const typeClasses: Record = { - info: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400', - warning: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400', - error: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400', - success: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400', - } - return h(Badge, { class: typeClasses[row.original.type] }, () => typeLabels[row.original.type]) - }, - }, - { - accessorKey: 'status', - header: () => t('admin.announcements.columns.status'), - cell: ({ row }) => { - const statusLabels: Record = { - active: t('admin.announcements.statuses.active'), - inactive: t('admin.announcements.statuses.inactive'), - } - const statusClasses: Record = { - active: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400', - inactive: 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400', - } - return h(Badge, { class: statusClasses[row.original.status] }, () => statusLabels[row.original.status]) - }, - }, - { - accessorKey: 'is_top', - header: () => t('admin.announcements.columns.isTop'), - cell: ({ row }) => { - return h(Button, { - variant: 'ghost', - size: 'sm', - onClick: () => options.onToggleTop(row.original), - }, () => [ - h(Icon, { - icon: row.original.is_top ? 'lucide:pin' : 'lucide:pin-off', - class: `h-4 w-4 ${row.original.is_top ? 'text-primary' : 'text-muted-foreground'}`, - }), - ]) - }, - }, - { - accessorKey: 'created_at', - header: () => t('admin.announcements.columns.createdAt'), - cell: ({ row }) => { - return h('span', { class: 'text-sm text-muted-foreground' }, new Date(row.original.created_at).toLocaleString('zh-CN')) - }, - }, - { - id: 'actions', - header: () => t('admin.announcements.columns.actions'), - cell: ({ row }) => { - return h('div', { class: 'flex items-center justify-end gap-1' }, [ - h(Button, { - variant: 'ghost', - size: 'sm', - onClick: () => options.onEdit(row.original), - }, () => [ - h(Icon, { icon: 'lucide:edit', class: 'h-4 w-4' }), - ]), - h(Button, { - variant: 'ghost', - size: 'sm', - onClick: () => options.onDelete(row.original), - }, () => [ - h(Icon, { icon: 'lucide:trash-2', class: 'h-4 w-4 text-destructive' }), - ]), - ]) - }, - }, - ] -} diff --git a/frontend/src/pages/developer/announcements/components/data-table-toolbar.vue b/frontend/src/pages/developer/announcements/components/data-table-toolbar.vue deleted file mode 100644 index d3bfe64..0000000 --- a/frontend/src/pages/developer/announcements/components/data-table-toolbar.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - {{ t('admin.announcements.reset') }} - - - - diff --git a/frontend/src/pages/developer/announcements/components/data-table.vue b/frontend/src/pages/developer/announcements/components/data-table.vue deleted file mode 100644 index cbbe87f..0000000 --- a/frontend/src/pages/developer/announcements/components/data-table.vue +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - {{ t('admin.announcements.batchExportBtn') }} - - - {{ t('admin.announcements.batchDeleteBtn') }} - - - - - - - - - - {{ t('admin.announcements.exportBtn') }} - - - - - - - - diff --git a/frontend/src/pages/developer/announcements/create.vue b/frontend/src/pages/developer/announcements/create.vue deleted file mode 100644 index ccd6c90..0000000 --- a/frontend/src/pages/developer/announcements/create.vue +++ /dev/null @@ -1,292 +0,0 @@ - - - - - - - - - - - - 公告配置 - - 填写公告的基本信息和内容 - - - - 选择应用 * - - - - - - - {{ app.name }} - - - - - - - - 公告标题 * - - - - - - - 公告内容 * - - - - - - 公告类型 - - - - - - - - - {{ type.label }} - - - - - - - - - - 发布状态 - - - 启用后公告将对用户可见 - - - - - - - - - 置顶公告 - - - 置顶的公告将在应用内优先显示 - - - - - - - - - - - - - - 预览 - - - - - - 所属应用 - {{ selectedApplication?.name || '-' }} - - - 公告标题 - {{ formData.title || '-' }} - - - 公告类型 - - - {{ selectedType?.label || '-' }} - - - - 发布状态 - {{ formData.status === 'active' ? '启用' : '禁用' }} - - - 是否置顶 - {{ formData.is_top ? '是' : '否' }} - - - - - - 内容预览 - - - - {{ formData.content || '暂无内容' }} - - - - - - - - - - - - - 发布公告 - - - 取消 - - - - - - - - - diff --git a/frontend/src/pages/developer/announcements/data/schema.ts b/frontend/src/pages/developer/announcements/data/schema.ts deleted file mode 100644 index cee2a80..0000000 --- a/frontend/src/pages/developer/announcements/data/schema.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { z } from 'zod' - -export const announcementStatusSchema = z.enum(['active', 'inactive']) -export const announcementTypeSchema = z.enum(['info', 'warning', 'error', 'success']) - -export const announcementSchema = z.object({ - id: z.number(), - application_id: z.number(), - application_name: z.string(), - title: z.string(), - content: z.string(), - type: announcementTypeSchema, - status: announcementStatusSchema, - is_top: z.boolean(), - created_at: z.string(), - updated_at: z.string(), -}) - -export type Announcement = z.infer diff --git a/frontend/src/pages/developer/announcements/index.vue b/frontend/src/pages/developer/announcements/index.vue deleted file mode 100644 index d9a2cc7..0000000 --- a/frontend/src/pages/developer/announcements/index.vue +++ /dev/null @@ -1,354 +0,0 @@ - - - - - - - - {{ t('admin.announcements.create') }} - - - - - - - - - {{ t('admin.announcements.totalAnnouncements') }} - - - - - - {{ filteredAnnouncements.length }} - - - - - - - - {{ t('admin.announcements.activeCount') }} - - - - - - {{ activeCount }} - - - - - - - - {{ t('admin.announcements.inactiveCount') }} - - - - - - {{ inactiveCount }} - - - - - - - - {{ t('admin.announcements.topCount') }} - - - - - - {{ topCount }} - - - - - - - - r.original.id) || [])" - @batch-export="handleBatchExport(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - > - - - - - - - - - - - - - {{ t('admin.announcements.deleteAnnouncement') }} - - - {{ t('admin.announcements.deleteConfirm', { title: deleteTarget?.title }) }} - - - - - - {{ t('admin.announcements.batchDelete') }} - - - {{ t('admin.announcements.batchDeleteConfirm', { count: batchDeleteIds.length }) }} - - - - diff --git a/frontend/src/pages/developer/applications.vue b/frontend/src/pages/developer/applications.vue deleted file mode 100644 index f8488d8..0000000 --- a/frontend/src/pages/developer/applications.vue +++ /dev/null @@ -1,248 +0,0 @@ - - - - - - - - {{ t('admin.applications.createApp') }} - - - - - - - - - {{ t('admin.applications.totalApps') }} - - - - - - {{ apps.length }} - - - - - - - - {{ t('admin.applications.activeApps') }} - - - - - - {{ activeAppsCount }} - - - - - - - - {{ t('admin.applications.totalUsers') }} - - - - - - {{ totalUsers }} - - - - - - - - {{ t('admin.applications.totalVerifyCount') }} - - - - - - {{ totalVerifyCount }} - - - - - - - - - - - - - - - {{ t('admin.applications.deleteApp') }} - - - {{ t('admin.applications.deleteAppConfirm', { name: deleteTarget?.name }) }} - - - - diff --git a/frontend/src/pages/developer/applications/[id]/email.vue b/frontend/src/pages/developer/applications/[id]/email.vue deleted file mode 100644 index 25cdd9a..0000000 --- a/frontend/src/pages/developer/applications/[id]/email.vue +++ /dev/null @@ -1,484 +0,0 @@ - - - - - - - - - - - - 邮箱验证设置 - 配置应用的邮箱验证功能 - - - - - 启用邮箱验证 - - 允许用户使用邮箱注册和验证 - - - - - - - - 强制邮箱验证 - - 注册时必须验证邮箱才能完成注册 - - - - - - - - 启用密码重置 - - 允许用户通过邮箱验证码重置密码 - - - - - - - - - - SMTP 配置 - 配置邮件发送服务器 - - - - - - SMTP 服务器 - - - - - - - 端口 - - - - - - - 用户名 - - - - - - - 密码/授权码 - - - - - - - 发件人名称 - - - - - - - 发件人邮箱 - - - - - - - - 使用 SSL - - 推荐开启,端口 465 通常需要 SSL - - - - - - - - - 测试邮箱 - - - - - - 发送测试邮件 - - - - - - - - 发送验证码测试 - 测试验证码发送功能 - - - - - - 邮箱地址 - - - - - - - 用途 - - - - - - - - 注册验证 - - - 重置密码 - - - 更换邮箱 - - - - - - - - - 发送验证码 - - - - - - - - - - - 邮件模板 - 自定义邮件模板 - - - - 新建模板 - - - - - - - 暂无自定义模板,将使用系统默认模板 - - - - - - {{ template.name }} - - - {{ template.subject }} - - - - - 默认 - - - - - - - - - - - - - - 保存设置 - - - - - diff --git a/frontend/src/pages/developer/applications/[id]/security.vue b/frontend/src/pages/developer/applications/[id]/security.vue deleted file mode 100644 index 29ba14b..0000000 --- a/frontend/src/pages/developer/applications/[id]/security.vue +++ /dev/null @@ -1,557 +0,0 @@ - - - - - - - - - - - - 加密设置 - 配置数据传输加密方式 - - - - 加密方式 - - - - - - 不加密 - - - 数据明文传输 - - - - - - - - - AES 加密 - - - 对称加密,速度快 - - - - - - - - - RSA 加密 - - - 非对称加密,更安全 - - - - - - - - - - 加密密钥 - - - - 自动生成 - - - - - 请妥善保管密钥,丢失后无法恢复 - - - - - - - - 绑定策略 - 配置应用的绑定策略 - - - - 绑定模式 - - - - - - 不绑定 - - - 不限制设备或IP - - - - - - - - - 绑定设备 - - - 绑定用户设备硬件信息 - - - - - - - - - 绑定IP - - - 绑定用户IP地址 - - - - - - - - - 混合绑定 - - - 同时绑定设备和IP - - - - - - - - - - - 最大设备数 - - - - 用户最多可绑定的设备数量 - - - - - 更换次数限制 - - - - 允许更换绑定的次数 - - - - - 更换间隔(天) - - - - 两次更换之间的最小间隔 - - - - - - 超出更换次数处理 - - - - - - 拒绝更换 - - - 超出次数后禁止更换绑定 - - - - - - - - - 扣除时长 - - - 超出次数后扣除使用时长 - - - - - - - - - 扣除点数 - - - 超出次数后扣除点数 - - - - - - - - {{ form.change_exceed_action === 'deduct_time' ? '扣除时长(天)' : '扣除点数' }} - - - - {{ form.change_exceed_action === 'deduct_time' ? '每次超出更换将扣除的时长' : '每次超出更换将扣除的点数' }} - - - - - - - - - - 多开控制 - 配置应用的多开限制 - - - - 多开模式 - - - - - - 禁止多开 - - - 只允许运行一个实例 - - - - - - - - - 有限多开 - - - 限制同时运行的实例数 - - - - - - - - - 无限多开 - - - 不限制实例数量 - - - - - - - - - 最大实例数 - - - - 同一账号最多可同时运行的实例数量 - - - - - - 允许多开模式将不限制应用实例数量,请谨慎使用此选项。 - - - - - - 禁止多开模式将只允许应用运行一个实例,尝试启动第二个实例将被阻止。 - - - - - - - - 心跳设置 - 配置客户端心跳检测参数 - - - - - - 心跳间隔(秒) - - - - 客户端发送心跳的间隔时间 - - - - - 心跳超时(秒) - - - - 超过此时间未收到心跳则判定离线 - - - - - - - - - 安全防护 - 配置登录安全策略 - - - - - - 最大尝试次数 - - - - 连续登录失败超过此次数将锁定 - - - - - 锁定时长(分钟) - - - - 账号被锁定后的解锁时间 - - - - - - - - - - 保存设置 - - - - - diff --git a/frontend/src/pages/developer/applications/[id]/settings.vue b/frontend/src/pages/developer/applications/[id]/settings.vue deleted file mode 100644 index 9b537eb..0000000 --- a/frontend/src/pages/developer/applications/[id]/settings.vue +++ /dev/null @@ -1,881 +0,0 @@ - - - - - - - - - - - - 基本信息 - 修改应用的基本信息 - - - - - 应用名称 - - - - - - 运营状态 - - - - - - 正常 - - - 应用正常运行 - - - - - - - - - 维护 - - - 应用维护中 - - - - - - - - - 停止 - - - 应用已停止 - - - - - - - - 应用图标 - - - - - - - - - - {{ form.iconPreview ? '更换图标' : '上传图标' }} - - - - - 支持 JPG、PNG、GIF 格式,建议尺寸 512x512 - - - - - - 应用描述 - - - - - - - - - 运营模式 - 选择应用的计费方式 - - - - - - - - 免费模式 - - - 用户无需付费即可使用所有功能 - - - - - - - - - 订阅模式 - - - 用户按周期付费订阅使用 - - - - - - - - - 余额模式 - - - 用户按使用量计费,从余额扣除 - - - - - - - - 扣费方式 - - - - - - ⚡ 自动扣费 - - - 系统自动触发扣费 - - - - - - - - - 🔧 手动扣费 - - - 通过 API 自行控制扣费 - - - - - - - - - 自动扣费类型 - - - - - - 登录扣费 - - - 每次登录验证时扣费 - - - - - - - - - 计时扣费 - - - 按设定的时间间隔自动扣费 - - - - - - - - 扣费间隔 - - 每 - - - - - - - - - - - - - - 分钟 - - - 小时 - - - 天 - - - - 扣费一次 - - - - - 每次扣费金额 - - - - - - - - - 每次扣费时扣除的余额数量 - - - - - - - - - - 手动扣费说明 - - - 选择手动扣费后,系统不会自动扣除用户余额。您需要通过 API 接口自行控制扣费时机和金额。 - - - POST /api/v1/dev/applications/:id/deduct - - - - - - - - - - - 登录策略 - 设置用户登录验证策略 - - - - - - - - 宽松模式 - - - 用户到期后仍可登录,由客户端控制功能 - - - - - - - - - 严格模式 - - - 用户到期后无法登录,必须续费 - - - - - - - - - - 注册设置 - 配置用户注册方式和权限 - - - - - - 允许注册 - - - 是否允许新用户注册账号 - - - - - - - - 注册方式 - - 选择允许用户使用的注册方式 - - - - { - if (checked) { - form.register_methods.push('username') - } - else { - form.register_methods = form.register_methods.filter(m => m !== 'username') - } - }" - /> - - 用户名注册 - - - - { - if (checked) { - form.register_methods.push('email') - } - else { - form.register_methods = form.register_methods.filter(m => m !== 'email') - } - }" - /> - - 邮箱注册 - - - - { - if (checked) { - form.register_methods.push('phone') - } - else { - form.register_methods = form.register_methods.filter(m => m !== 'phone') - } - }" - /> - - 手机号注册 - - - - - 至少选择一种注册方式 - - - - - - - - - 试用功能 - 为新用户提供试用体验 - - - - - - 启用试用 - - - 新用户注册后自动获得试用资格 - - - - - - - - 试用余额 - - - - - - - - - 新用户注册后自动获得的余额数量 - - - - - 试用天数 - - - - - - - - - 新用户注册后自动获得的试用天数 - - - - - - - - - 免费时段 - 在指定时间段内所有用户免费使用 - - - - - - 启用免费时段 - - - 在设定的时间段内,所有用户可免费使用 - - - - - - - - 免费时段类型 - - - - - - 日期范围 - - - 指定开始和结束日期 - - - - - - - - - 每周重复 - - - 每周指定日期和时间 - - - - - - - - - 开始时间 - - - - 结束时间 - - - - - - - 选择日期 - - - 周{{ day }} - - - - - - - 开始时间 - - - - 结束时间 - - - - - - - - - - - - 保存设置 - - - - - diff --git a/frontend/src/pages/developer/applications/components/columns.ts b/frontend/src/pages/developer/applications/components/columns.ts deleted file mode 100644 index 544d15f..0000000 --- a/frontend/src/pages/developer/applications/components/columns.ts +++ /dev/null @@ -1,206 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' -import type { Composer } from 'vue-i18n' - -import { - Ban, - CheckCircle, - Mail, - MoreHorizontal, - Settings, - Shield, - Trash2, -} from 'lucide-vue-next' -import { h } from 'vue' - -import type { App } from '../data/schema' - -import { Copy } from '@/components/sva-ui/copy' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getColumns(actions: { - onGoToSettings: (app: App) => void - onGoToSecurity: (app: App) => void - onGoToEmail: (app: App) => void - onToggleStatus: (app: App) => void - onDelete: (app: App) => void -}, t: Composer['t']): ColumnDef[] { - const billingTypeMap: Record = { - free: { label: '免费', class: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300' }, - subscription: { label: '订阅', class: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300' }, - time: { label: '计时', class: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300' }, - count: { label: '计次', class: 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300' }, - } - - const loginPolicyMap: Record = { - loose: { label: '宽松', class: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300' }, - strict: { label: '严格', class: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300' }, - hybrid: { label: '混合', class: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300' }, - } - - const statusMap: Record = { - active: { label: '启用', class: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300' }, - inactive: { label: '禁用', class: 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300' }, - } - - return [ - { - accessorKey: 'name', - header: () => '应用名称', - cell: ({ row }) => h('div', { class: 'flex items-center space-x-3' }, [ - h('div', { class: 'h-8 w-8 rounded bg-primary/10 flex items-center justify-center flex-shrink-0 overflow-hidden' }, [ - row.original.icon_url - ? h('img', { - src: `http://localhost:8080${row.original.icon_url}`, - alt: row.getValue('name') as string, - class: 'w-full h-full object-cover', - }) - : h('span', { class: 'text-primary font-bold text-sm' }, (row.getValue('name') as string).charAt(0).toUpperCase()), - ]), - h('div', { class: 'flex flex-col' }, [ - h('p', { class: 'font-medium' }, row.getValue('name') as string), - h('p', { class: 'text-xs text-muted-foreground truncate max-w-[200px]' }, row.original.description), - ]), - ]), - }, - - { - accessorKey: 'app_key', - header: () => 'App Key', - cell: ({ row }) => { - const appKey = row.getValue('app_key') as string - return h('div', { class: 'flex items-center space-x-2' }, [ - h('code', { class: 'text-xs bg-muted px-2 py-1 rounded font-mono' }, appKey || '-'), - appKey && h(Copy, { class: 'h-4 w-4', size: 'sm', content: appKey }), - ]) - }, - }, - - { - accessorKey: 'billing_type', - header: () => '运营模式', - cell: ({ row }) => { - const billingType = row.getValue('billing_type') as string - const info = billingTypeMap[billingType] - if (!info) - return h('span', {}, billingType) - return h('span', { class: `px-2 py-1 rounded text-xs ${info.class}` }, info.label) - }, - }, - - { - accessorKey: 'login_policy', - header: () => '登录策略', - cell: ({ row }) => { - const loginPolicy = row.getValue('login_policy') as string - const info = loginPolicyMap[loginPolicy] - if (!info) - return h('span', {}, loginPolicy) - return h('span', { class: `px-2 py-1 rounded text-xs ${info.class}` }, info.label) - }, - }, - - { - accessorKey: 'enable_trial', - header: () => '试用', - cell: ({ row }) => { - const enableTrial = row.getValue('enable_trial') as boolean - return h('span', { - class: `px-2 py-1 rounded text-xs ${enableTrial ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300' : 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300'}`, - }, enableTrial ? `${row.original.trial_balance}余额` : '无') - }, - }, - - { - accessorKey: 'status', - header: () => '状态', - cell: ({ row }) => { - const status = row.getValue('status') as string - const info = statusMap[status] - if (!info) - return h('span', {}, status) - return h('span', { class: `px-2 py-1 rounded text-xs ${info.class}` }, info.label) - }, - }, - - { - accessorKey: 'created_at', - header: () => '创建时间', - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleDateString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, t('common.actions')), - cell: ({ row }) => { - const app = row.original - const isActive = app.status === 'active' - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, t('common.openMenu')), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => [ - h(DropdownMenuItem, { onClick: () => actions.onGoToSettings(app) }, () => [ - h(Settings, { class: 'mr-2 h-4 w-4' }), - '基本设置', - ]), - h(DropdownMenuItem, { onClick: () => actions.onGoToSecurity(app) }, () => [ - h(Shield, { class: 'mr-2 h-4 w-4' }), - '安全设置', - ]), - h(DropdownMenuItem, { onClick: () => actions.onGoToEmail(app) }, () => [ - h(Mail, { class: 'mr-2 h-4 w-4' }), - '邮箱设置', - ]), - h(DropdownMenuSeparator), - h(DropdownMenuItem, { onClick: () => actions.onToggleStatus(app) }, () => [ - isActive ? h(Ban, { class: 'mr-2 h-4 w-4' }) : h(CheckCircle, { class: 'mr-2 h-4 w-4' }), - isActive ? '禁用' : '启用', - ]), - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(app) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - t('admin.applications.delete'), - ]), - ], - ), - ], - }, - ) - }, - }, - ] -} diff --git a/frontend/src/pages/developer/applications/components/data-table.vue b/frontend/src/pages/developer/applications/components/data-table.vue deleted file mode 100644 index 32fbb39..0000000 --- a/frontend/src/pages/developer/applications/components/data-table.vue +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/applications/create.vue b/frontend/src/pages/developer/applications/create.vue deleted file mode 100644 index c1ce131..0000000 --- a/frontend/src/pages/developer/applications/create.vue +++ /dev/null @@ -1,652 +0,0 @@ - - - - - - - - {{ t('admin.applications.basicInfo') }} - {{ t('admin.applications.basicInfoDesc') }} - - - - - - {{ t('admin.applications.appName') }} * - - - - - - {{ t('admin.applications.appIcon') }} - - - - - - - - - - {{ formData.iconPreview ? t('admin.applications.change') : t('admin.applications.upload') }} - - - - - - - - - {{ t('admin.applications.appDescription') }} - - - - - - - - - {{ t('admin.applications.operationMode') }} - {{ t('admin.applications.operationModeDesc') }} - - - - - - - - {{ t('admin.applications.freeMode') }} - - - {{ t('admin.applications.freeModeDesc') }} - - - - - - - - - {{ t('admin.applications.subscriptionMode') }} - - - {{ t('admin.applications.subscriptionModeDesc') }} - - - - - - - - - {{ t('admin.applications.timeMode') }} - - - {{ t('admin.applications.timeModeDesc') }} - - - - - - - - - {{ t('admin.applications.countMode') }} - - - {{ t('admin.applications.countModeDesc') }} - - - - - - - - - {{ t('admin.applications.deductionCycle') }} - - - - - - - {{ t('admin.applications.perUse') }} - - - {{ t('admin.applications.perMinute') }} - - - {{ t('admin.applications.perHour') }} - - - {{ t('admin.applications.perDay') }} - - - - - - - {{ t('admin.applications.deductionAmount') }} - - - - - - - - - - - - - - - - {{ t('admin.applications.loginPolicy') }} - {{ t('admin.applications.loginPolicyDesc') }} - - - - - - - - {{ t('admin.applications.looseMode') }} - - - {{ t('admin.applications.looseModeDesc') }} - - - - - - - - - {{ t('admin.applications.strictMode') }} - - - {{ t('admin.applications.strictModeDesc') }} - - - - - - - - - {{ t('admin.applications.hybridMode') }} - - - {{ t('admin.applications.hybridModeDesc') }} - - - - - - - - - - {{ t('admin.applications.trialSettings') }} - {{ t('admin.applications.trialSettings') }} - - - - - - {{ t('admin.applications.enableTrial') }} - - - 新用户注册后自动获得试用权益 - - - - - - - - {{ t('admin.applications.trialBalance') }} - - - - - - - - - 新用户注册后获得的余额 - - - - - - - - - {{ t('admin.applications.freePeriodSettings') }} - {{ t('admin.applications.freePeriodSettings') }} - - - - - - {{ t('admin.applications.enableFreePeriod') }} - - - 在设定的时间段内,所有用户可免费使用 - - - - - - - - {{ t('admin.applications.periodType') }} - - - - - - {{ t('admin.applications.dateRange') }} - - - 指定开始和结束日期时间 - - - - - - - - - {{ t('admin.applications.weekly') }} - - - 每周固定日期和时间段 - - - - - - - - - {{ t('admin.applications.startTime') }} - - - - {{ t('admin.applications.endTime') }} - - - - - - - {{ t('admin.applications.weekdays') }} - - - - {{ day }} - - - - - 选择每周哪些日期免费 - - - - - - {{ t('admin.applications.startTime') }} - - - 每天免费开始时间 - - - - {{ t('admin.applications.endTime') }} - - - 每天免费结束时间 - - - - - - - - - - - {{ t('admin.applications.securitySettings') }} - {{ t('admin.applications.securitySettingsDesc') }} - - - - - {{ t('admin.applications.encryptionType') }} - - - - - - - {{ t('admin.applications.noEncryption') }} - - - {{ t('admin.applications.aesEncryption') }} - - - {{ t('admin.applications.rsaEncryption') }} - - - {{ t('admin.applications.rc4Encryption') }} - - - - - 数据明文传输,仅用于测试环境 - 使用AES-GCM算法加密,安全性高 - 非对称加密,安全性最高 - 使用RC4流加密,性能较好 - - - - - {{ t('admin.applications.secretKey') }} - - - - - - - - 用于加密通信数据的密钥,留空则自动生成 - - - - - - - - - {{ t('admin.applications.deviceBind') }} - 控制用户登录时是否需要绑定设备 - - - - - - {{ t('admin.applications.deviceBind') }} - - - 启用后,用户登录时需要绑定设备,可限制同时登录的设备数量 - - - formData.bind_type = val ? 'device' : 'none'" - /> - - - - {{ t('admin.applications.maxDevices') }} - - - - - - - - - 每个账号最多绑定的设备数量 - - - - - - - {{ t('admin.applications.allowMultiOpen') }} - - - 允许同一账号在同一设备上同时运行多个实例 - - - - - - - - - - 取消 - - - - {{ t('admin.applications.createApp') }} - - - - - diff --git a/frontend/src/pages/developer/applications/data/schema.ts b/frontend/src/pages/developer/applications/data/schema.ts deleted file mode 100644 index c573bae..0000000 --- a/frontend/src/pages/developer/applications/data/schema.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { z } from 'zod' - -export const appStatusSchema = z.enum(['active', 'inactive']) -export type AppStatus = z.infer - -export const billingMethodSchema = z.enum(['subscription', 'time', 'count']) -export type BillingMethod = z.infer - -export const operationModeSchema = z.enum(['free', 'paid']) -export type OperationMode = z.infer - -export const deductionCycleSchema = z.enum(['per_use', 'per_minute', 'per_hour', 'per_day']) -export type DeductionCycle = z.infer - -export const loginPolicySchema = z.enum(['loose', 'strict', 'hybrid']) -export type LoginPolicy = z.infer - -export const appSchema = z.object({ - id: z.union([z.string(), z.number()]), - name: z.string(), - description: z.string(), - app_key: z.string(), - billing_type: z.enum(['free', 'subscription', 'time', 'count']), - login_policy: loginPolicySchema.default('loose'), - encrypt_type: z.enum(['none', 'aes', 'rsa', 'rc4']), - secret_key: z.string(), - bind_type: z.enum(['device', 'account', 'none']), - max_devices: z.number().default(1), - change_limit: z.number().optional(), - change_interval: z.number().optional(), - multi_open: z.boolean().default(false), - max_instances: z.number().optional(), - enable_trial: z.boolean().default(false), - trial_balance: z.number().default(0), - enable_free_period: z.boolean().default(false), - free_period_start: z.string().optional(), - free_period_end: z.string().optional(), - deduction_cycle: deductionCycleSchema.optional(), - deduction_amount: z.number().optional(), - heartbeat_interval: z.number().default(60), - heartbeat_timeout: z.number().default(300), - max_attempts: z.number().default(5), - lock_duration: z.number().default(30), - status: appStatusSchema, - disabled_by_package: z.boolean().default(false), - icon_url: z.string().optional(), - users: z.number().optional(), - verify_count: z.number().optional(), - created_at: z.string(), - updated_at: z.string().optional(), -}) -export type App = z.infer - -export const appListSchema = z.array(appSchema) diff --git a/frontend/src/pages/developer/card-types/[id].vue b/frontend/src/pages/developer/card-types/[id].vue deleted file mode 100644 index 010d249..0000000 --- a/frontend/src/pages/developer/card-types/[id].vue +++ /dev/null @@ -1,409 +0,0 @@ - - - - - - - - {{ t('admin.cardTypes.createCardType') }} - - - - - - - - - {{ t('admin.cardTypes.totalTypes') }} - - - - - - {{ filteredCardTypes.length }} - - - - - - - - {{ t('admin.cardTypes.activeTypes') }} - - - - - - {{ activeTypesCount }} - - - - - - - - {{ t('admin.cardTypes.generatedCards') }} - - - - - - {{ totalGeneratedCount }} - - - - - - - - {{ t('admin.cardTypes.totalValue') }} - - - - - - ¥{{ totalValue }} - - - - - - - - r.original.status === 'inactive').map((r: any) => r.original.id) || [], 'active')" - @batch-disable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'active').map((r: any) => r.original.id) || [], 'inactive')" - @batch-delete="confirmBatchDelete(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - > - - - - - - - - - - - - {{ t('admin.cardTypes.delete') }} - - - {{ t('admin.cardTypes.deleteConfirm', { name: deleteTarget?.name }) }} - - - - - - {{ t('admin.cardTypes.batchDelete') }} - - - {{ t('admin.cardTypes.batchDeleteConfirm', { count: batchDeleteIds.length }) }} - - - - diff --git a/frontend/src/pages/developer/card-types/components/columns.ts b/frontend/src/pages/developer/card-types/components/columns.ts deleted file mode 100644 index cf297b9..0000000 --- a/frontend/src/pages/developer/card-types/components/columns.ts +++ /dev/null @@ -1,176 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' -import type { Composer } from 'vue-i18n' - -import { Ban, MoreHorizontal, Pencil, Trash2 } from 'lucide-vue-next' -import { h } from 'vue' - -import type { CardType } from '@/pages/admin/card-types/data/schema' - -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getColumns(actions: { - onEdit: (row: CardType) => void - onToggleStatus: (row: CardType) => void - onDelete: (row: CardType) => void -}, t: Composer['t']): ColumnDef[] { - return [ - { - accessorKey: 'name', - header: () => t('admin.cardTypes.columns.name'), - cell: ({ row }) => { - return row.getValue('name') as string - }, - }, - { - accessorKey: 'description', - header: () => t('admin.cardTypes.columns.description'), - cell: ({ row }) => { - const description = row.getValue('description') as string - return description || '-' - }, - }, - { - accessorKey: 'application.name', - header: () => t('admin.cardTypes.columns.application'), - cell: ({ row }) => { - const appName = row.original.application?.name - return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-' - }, - }, - { - accessorKey: 'recharge_type', - header: () => '充值类型', - cell: ({ row }) => { - const rechargeType = row.original.recharge_type - const isBalance = rechargeType === 'balance' - return h(Badge, { variant: isBalance ? 'outline' : 'default' }, () => isBalance ? '余额充值' : '订阅充值') - }, - }, - { - accessorKey: 'price', - header: () => t('admin.cardTypes.columns.price'), - cell: ({ row }) => { - const price = row.getValue('price') as number - return `¥${price}` - }, - }, - { - accessorKey: 'value', - header: () => t('admin.cardTypes.columns.value'), - cell: ({ row }) => { - const value = row.getValue('value') as number - const rechargeType = row.original.recharge_type - const valueUnit = row.original.value_unit - - if (value === -1) { - return h(Badge, { variant: 'default' }, () => t('admin.cardTypes.units.permanent')) - } - - if (rechargeType === 'subscription' && valueUnit) { - const unitMap: Record = { - minute: '分钟', - hour: '小时', - day: '天', - month: '月', - year: '年', - } - return `${value} ${unitMap[valueUnit] || valueUnit}` - } - - return `${value}` - }, - }, - { - accessorKey: 'generatedCount', - header: () => t('admin.cardTypes.columns.generatedCount'), - cell: ({ row }) => { - const count = row.original.generatedCount ?? 0 - return `${count} ${t('admin.cardTypes.units.cards')}` - }, - }, - { - accessorKey: 'status', - header: () => t('admin.cardTypes.columns.status'), - cell: ({ row }) => { - const status = row.getValue('status') as string - const isActive = status === 'active' - return h(Badge, { variant: isActive ? 'default' : 'destructive' }, () => t(`admin.cardTypes.statusOptions.${status}`)) - }, - }, - { - accessorKey: 'created_at', - header: () => t('admin.cardTypes.columns.createdAt'), - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, t('common.actions')), - cell: ({ row }) => { - const cardType = row.original - const isActive = cardType.status === 'active' - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, t('common.openMenu')), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => [ - h(DropdownMenuItem, { onClick: () => actions.onEdit(cardType) }, () => [ - h(Pencil, { class: 'mr-2 h-4 w-4' }), - t('admin.cardTypes.edit'), - ]), - h(DropdownMenuItem, { onClick: () => actions.onToggleStatus(cardType) }, () => [ - h(Ban, { class: 'mr-2 h-4 w-4' }), - isActive ? t('admin.cardTypes.disable') : t('admin.cardTypes.enable'), - ]), - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(cardType) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - t('admin.cardTypes.delete'), - ]), - ], - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/card-types/components/data-table-toolbar.vue b/frontend/src/pages/developer/card-types/components/data-table-toolbar.vue deleted file mode 100644 index 703066d..0000000 --- a/frontend/src/pages/developer/card-types/components/data-table-toolbar.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - {{ t('admin.cardTypes.reset') }} - - - - diff --git a/frontend/src/pages/developer/card-types/components/data-table.vue b/frontend/src/pages/developer/card-types/components/data-table.vue deleted file mode 100644 index b648310..0000000 --- a/frontend/src/pages/developer/card-types/components/data-table.vue +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - - - - - {{ t('admin.cardTypes.batchEnableBtn') }} - - - {{ t('admin.cardTypes.batchDisableBtn') }} - - - {{ t('admin.cardTypes.batchDeleteBtn') }} - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/card-types/create.vue b/frontend/src/pages/developer/card-types/create.vue deleted file mode 100644 index 87f072c..0000000 --- a/frontend/src/pages/developer/card-types/create.vue +++ /dev/null @@ -1,372 +0,0 @@ - - - - - - - - - - - - {{ t('admin.cardTypes.create.basicInfo') }} - - {{ t('admin.cardTypes.create.basicInfoDesc') }} - - - - - {{ t('admin.cardTypes.create.name') }} * - - - - - - - {{ t('admin.cardTypes.create.description') }} - - - - - - - {{ t('admin.cardTypes.create.application') }} * - - - - - - - - {{ app.name }} - - - - - - - - - - - - 充值配置 - - 设置卡密的充值类型和金额 - - - - 充值类型 - - - - - - 余额充值 - - - 充值账户余额,适用于计时/计次模式 - - - - - - - - - 订阅充值 - - - 充值会员时长,适用于订阅模式 - - - - - - - - - {{ t('admin.cardTypes.create.price') }} - - - - - - - - - - {{ t('admin.cardTypes.create.priceUnit') }} - - - - - - - 永久会员 - - - 开启后,使用此卡密的用户将成为永久会员 - - - - - - - - 充值金额 - - - - - - - - - 用户充值后获得的余额数量 - - - - - - - 充值时长 - - - - - - - - - - - 时长单位 - - - - - - - 分钟 - - - 小时 - - - 天 - - - 月 - - - 年 - - - - - - - - - - - - - - - - - {{ t('admin.cardTypes.create.preview') }} - - - - - - {{ t('admin.cardTypes.create.previewName') }} - {{ form.name || '-' }} - - - {{ t('admin.cardTypes.create.previewApplication') }} - {{ selectedApplication?.name || '-' }} - - - 充值类型 - {{ form.recharge_type === 'balance' ? '余额充值' : '订阅充值' }} - - - {{ t('admin.cardTypes.create.previewPrice') }} - ¥{{ form.price.toFixed(2) }} - - - {{ t('admin.cardTypes.create.previewValue') }} - {{ displayValue }} - - - - - - - - - - - - {{ t('admin.cardTypes.create.createNow') }} - - - {{ t('admin.cardTypes.create.cancel') }} - - - - - - - - - diff --git a/frontend/src/pages/developer/card-types/data/data.ts b/frontend/src/pages/developer/card-types/data/data.ts deleted file mode 100644 index d4b4ce8..0000000 --- a/frontend/src/pages/developer/card-types/data/data.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { Composer } from 'vue-i18n' - -import type { CardTypeStatus } from './schema' - -export function getCardTypeStatuses(t: Composer['t']): { label: string, value: CardTypeStatus }[] { - return [ - { label: t('admin.cardTypes.statusOptions.active'), value: 'active' }, - { label: t('admin.cardTypes.statusOptions.inactive'), value: 'inactive' }, - ] -} - -export function getValueLabel(): string { - return '充值金额' -} - -export function getValueUnit(): string { - return '' -} - -export function formatValue(value: number): string { - if (value === -1) { - return '永久' - } - return `${value}` -} diff --git a/frontend/src/pages/developer/card-types/data/schema.ts b/frontend/src/pages/developer/card-types/data/schema.ts deleted file mode 100644 index d8ebf01..0000000 --- a/frontend/src/pages/developer/card-types/data/schema.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { z } from 'zod' - -export const cardTypeStatusSchema = z.enum(['active', 'inactive']) - -export const rechargeTypeSchema = z.enum(['balance', 'subscription']) - -export const cardTypeSchema = z.object({ - id: z.union([z.string(), z.number()]), - user_id: z.union([z.string(), z.number()]), - application_id: z.union([z.string(), z.number()]).nullable(), - name: z.string(), - description: z.string(), - recharge_type: rechargeTypeSchema.default('balance'), - value: z.number(), - value_unit: z.string().optional(), - price: z.number(), - generatedCount: z.number().default(0), - status: cardTypeStatusSchema, - created_at: z.string(), - updated_at: z.string().optional(), - application: z.object({ - id: z.union([z.string(), z.number()]), - name: z.string(), - }).optional().nullable(), -}) - -export type CardType = z.infer -export type CardTypeStatus = z.infer -export type RechargeType = z.infer diff --git a/frontend/src/pages/developer/card-types/index.vue b/frontend/src/pages/developer/card-types/index.vue deleted file mode 100644 index 010d249..0000000 --- a/frontend/src/pages/developer/card-types/index.vue +++ /dev/null @@ -1,409 +0,0 @@ - - - - - - - - {{ t('admin.cardTypes.createCardType') }} - - - - - - - - - {{ t('admin.cardTypes.totalTypes') }} - - - - - - {{ filteredCardTypes.length }} - - - - - - - - {{ t('admin.cardTypes.activeTypes') }} - - - - - - {{ activeTypesCount }} - - - - - - - - {{ t('admin.cardTypes.generatedCards') }} - - - - - - {{ totalGeneratedCount }} - - - - - - - - {{ t('admin.cardTypes.totalValue') }} - - - - - - ¥{{ totalValue }} - - - - - - - - r.original.status === 'inactive').map((r: any) => r.original.id) || [], 'active')" - @batch-disable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'active').map((r: any) => r.original.id) || [], 'inactive')" - @batch-delete="confirmBatchDelete(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - > - - - - - - - - - - - - {{ t('admin.cardTypes.delete') }} - - - {{ t('admin.cardTypes.deleteConfirm', { name: deleteTarget?.name }) }} - - - - - - {{ t('admin.cardTypes.batchDelete') }} - - - {{ t('admin.cardTypes.batchDeleteConfirm', { count: batchDeleteIds.length }) }} - - - - diff --git a/frontend/src/pages/developer/cards/components/columns.ts b/frontend/src/pages/developer/cards/components/columns.ts deleted file mode 100644 index cd52588..0000000 --- a/frontend/src/pages/developer/cards/components/columns.ts +++ /dev/null @@ -1,202 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' -import type { Composer } from 'vue-i18n' - -import { Ban, MoreHorizontal, Trash2 } from 'lucide-vue-next' -import { h } from 'vue' - -import type { Card } from '@/pages/admin/cards/data/schema' - -import { Copy } from '@/components/sva-ui/copy' -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getColumns(actions: { - onToggleStatus: (row: Card) => void - onDelete: (row: Card) => void -}, t: Composer['t']): ColumnDef[] { - return [ - { - accessorKey: 'card_key', - header: () => t('admin.cards.columns.cardKey'), - cell: ({ row }) => { - const cardKey = row.getValue('card_key') as string - if (!cardKey) - return '-' - return h('div', { class: 'flex items-center space-x-2' }, [ - h('code', { class: 'text-xs bg-muted px-2 py-1 rounded font-mono' }, cardKey), - h(Copy, { class: 'h-4 w-4', size: 'sm', content: cardKey }), - ]) - }, - }, - { - accessorKey: 'application.name', - header: () => t('admin.cards.columns.application'), - cell: ({ row }) => { - const appName = row.original.application?.name - return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-' - }, - }, - { - accessorKey: 'card_type.name', - header: () => t('admin.cards.columns.type'), - cell: ({ row }) => { - const typeName = row.original.card_type?.name - return typeName ? h(Badge, { variant: 'outline' }, () => typeName) : '-' - }, - }, - { - accessorKey: 'card_type.price', - header: () => t('admin.cards.columns.faceValue'), - cell: ({ row }) => { - const price = row.original.card_type?.price - return price != null ? `¥${price}` : '-' - }, - }, - { - accessorKey: 'card_type.value', - header: () => t('admin.cards.columns.duration'), - cell: ({ row }) => { - const value = row.original.card_type?.value - const billingType = row.original.card_type?.billing_type - if (value == null) - return '-' - if (value === -1) - return t('admin.cards.columns.permanent') - if (billingType === 'subscription') - return `${value}${t('admin.cards.columns.hours')}` - if (billingType === 'timer') - return `${value}${t('admin.cards.columns.minutes')}` - if (billingType === 'points') - return `${value}${t('admin.cards.columns.points')}` - return String(value) - }, - }, - { - accessorKey: 'status', - header: () => t('admin.cards.columns.status'), - cell: ({ row }) => { - const status = row.getValue('status') as string - if (!status || status === '') { - return h(Badge, { variant: 'default' }, () => t('admin.cards.unused')) - } - const statusMap: Record = { - unused: { text: t('admin.cards.unused'), variant: 'default' }, - used: { text: t('admin.cards.used'), variant: 'secondary' }, - banned: { text: t('admin.cards.banned'), variant: 'destructive' }, - } - const { text, variant } = statusMap[status] || { text: status, variant: 'default' } - return h(Badge, { variant }, () => text) - }, - }, - { - accessorKey: 'app_user.username', - header: () => t('admin.cards.columns.usedUser'), - cell: ({ row }) => { - const username = row.original.app_user?.username - return username || '-' - }, - }, - { - accessorKey: 'used_at', - header: () => t('admin.cards.columns.usedTime'), - cell: ({ row }) => { - const usedAt = row.getValue('used_at') - if (!usedAt) - return '-' - try { - const date = new Date(usedAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - accessorKey: 'creator.username', - header: () => t('admin.cards.columns.creator'), - cell: ({ row }) => { - const username = row.original.creator?.username - return username || '-' - }, - }, - { - accessorKey: 'created_at', - header: () => t('admin.cards.columns.createdAt'), - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, t('common.actions')), - cell: ({ row }) => { - const card = row.original - const isBanned = card.status === 'banned' - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, t('common.openMenu')), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => [ - h(DropdownMenuItem, { onClick: () => actions.onToggleStatus(card) }, () => [ - h(Ban, { class: 'mr-2 h-4 w-4' }), - isBanned ? t('admin.cards.enable') : t('admin.cards.disable'), - ]), - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(card) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - t('admin.cards.delete'), - ]), - ], - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/cards/components/data-table-toolbar.vue b/frontend/src/pages/developer/cards/components/data-table-toolbar.vue deleted file mode 100644 index 75c0ce4..0000000 --- a/frontend/src/pages/developer/cards/components/data-table-toolbar.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - {{ t('admin.cards.reset') }} - - - - diff --git a/frontend/src/pages/developer/cards/components/data-table.vue b/frontend/src/pages/developer/cards/components/data-table.vue deleted file mode 100644 index d9e1a4e..0000000 --- a/frontend/src/pages/developer/cards/components/data-table.vue +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - {{ t('admin.cards.batchEnableBtn') }} - - - {{ t('admin.cards.batchDisableBtn') }} - - - {{ t('admin.cards.batchExportBtn') }} - - - {{ t('admin.cards.batchDeleteBtn') }} - - - - - - - - - - {{ t('admin.cards.exportBtn') }} - - - - - - - - diff --git a/frontend/src/pages/developer/cards/create.vue b/frontend/src/pages/developer/cards/create.vue deleted file mode 100644 index 10d80ee..0000000 --- a/frontend/src/pages/developer/cards/create.vue +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - - - - - {{ t('admin.cards.create.config') }} - - {{ t('admin.cards.create.configDesc') }} - - - - - {{ t('admin.cards.create.application') }} - - - - - - - - {{ app.name }} - - - - - - - - {{ t('admin.cards.create.cardType') }} - - - - - - - - {{ type.name }} - - - - - {{ t('admin.cards.create.noCardType') }} - - - - - - {{ t('admin.cards.create.count') }} - - - - - - - - - - - - 10 - - - 50 - - - 100 - - - 500 - - - - - {{ t('admin.cards.create.maxCount') }} - - - - - - - - - - - - {{ t('admin.cards.create.preview') }} - - - - - - {{ t('admin.cards.create.app') }} - {{ applications.find(a => String(a.id) === form.application_id)?.name || '-' }} - - - {{ t('admin.cards.create.type') }} - {{ selectedCardType?.name || '-' }} - - - {{ t('admin.cards.create.unitPrice') }} - ¥{{ selectedCardType?.price?.toFixed(2) || '0.00' }} - - - {{ t('admin.cards.create.quantity') }} - {{ form.count }} {{ t('admin.cards.create.units') }} - - - - {{ t('admin.cards.create.totalAmount') }} - ¥{{ totalCost.toFixed(2) }} - - - - - - - - - - - - - {{ t('admin.cards.create.generateNow') }} - - - {{ t('admin.cards.create.cancel') }} - - - - - - - - - diff --git a/frontend/src/pages/developer/cards/data/data.ts b/frontend/src/pages/developer/cards/data/data.ts deleted file mode 100644 index 085f7a4..0000000 --- a/frontend/src/pages/developer/cards/data/data.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Composer } from 'vue-i18n' - -import { Ban, CheckCircle2, Users } from 'lucide-vue-next' -import { h } from 'vue' - -import type { FacetedFilterOption } from '@/components/data-table/types' - -export function getCardStatuses(t: Composer['t']): (FacetedFilterOption & { style: string, variant?: string })[] { - return [ - { - label: t('admin.cards.unused'), - value: 'unused', - icon: h(CheckCircle2), - style: 'bg-green-500/10 text-green-500', - variant: 'default', - }, - { - label: t('admin.cards.used'), - value: 'used', - icon: h(Users), - style: 'bg-blue-500/10 text-blue-500', - variant: 'secondary', - }, - { - label: t('admin.cards.banned'), - value: 'banned', - icon: h(Ban), - style: 'bg-gray-500/10 text-gray-500', - variant: 'destructive', - }, - ] -} diff --git a/frontend/src/pages/developer/cards/data/schema.ts b/frontend/src/pages/developer/cards/data/schema.ts deleted file mode 100644 index f8529e2..0000000 --- a/frontend/src/pages/developer/cards/data/schema.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { z } from 'zod' - -export const cardStatusSchema = z.enum(['unused', 'used', 'expired', 'banned']) - -export const cardSchema = z.object({ - id: z.union([z.string(), z.number()]), - card_key: z.string(), - application_id: z.union([z.string(), z.number()]), - card_type_id: z.union([z.string(), z.number()]), - status: cardStatusSchema, - used_at: z.string().optional().nullable(), - expire_at: z.string().optional().nullable(), - created_at: z.string(), - updated_at: z.string().optional(), - application: z.object({ - id: z.union([z.string(), z.number()]), - name: z.string(), - }).optional(), - card_type: z.object({ - id: z.union([z.string(), z.number()]), - name: z.string(), - price: z.number(), - value: z.number(), - billing_type: z.string(), - }).optional(), - creator: z.object({ - id: z.union([z.string(), z.number()]), - username: z.string(), - }).optional(), - app_user: z.object({ - id: z.union([z.string(), z.number()]), - username: z.string(), - }).optional().nullable(), -}) - -export type Card = z.infer diff --git a/frontend/src/pages/developer/cards/index.vue b/frontend/src/pages/developer/cards/index.vue deleted file mode 100644 index e4eb646..0000000 --- a/frontend/src/pages/developer/cards/index.vue +++ /dev/null @@ -1,423 +0,0 @@ - - - - - - - - {{ t('admin.cards.generateCard') }} - - - - - - - - - {{ t('admin.cards.totalCards') }} - - - - - - {{ filteredCards.length }} - - - - - - - - {{ t('admin.cards.unused') }} - - - - - - {{ unusedCount }} - - - - - - - - {{ t('admin.cards.used') }} - - - - - - {{ usedCount }} - - - - - - - - {{ t('admin.cards.banned') }} - - - - - - {{ bannedCount }} - - - - - - - - r.original.status === 'unused').map((r: any) => r.original.id) || [], 'banned')" - @batch-enable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'banned').map((r: any) => r.original.id) || [], 'unused')" - @batch-delete="confirmBatchDelete(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - @batch-export="handleBatchExport(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - @export="handleExport" - @update:search-filter="searchFilter = $event" - > - - - - - - - - - - - - - - {{ t('admin.cards.deleteCard') }} - - - {{ t('admin.cards.deleteCardConfirm', { cardKey: deleteTarget?.card_key }) }} - - - - - - {{ t('admin.cards.batchDeleteCards') }} - - - {{ t('admin.cards.batchDeleteConfirm', { count: batchDeleteIds.length }) }} - - - - diff --git a/frontend/src/pages/developer/cloud-constants/[id].vue b/frontend/src/pages/developer/cloud-constants/[id].vue deleted file mode 100644 index 7b45556..0000000 --- a/frontend/src/pages/developer/cloud-constants/[id].vue +++ /dev/null @@ -1,337 +0,0 @@ - - - - - - - - {{ t('admin.cloudConstants.addConstant') }} - - - - - - - - - {{ t('admin.cloudConstants.totalConstants') }} - - - - - - {{ filteredConstants.length }} - - - - - - - - {{ t('admin.cloudConstants.linkedApps') }} - - - - - - {{ linkedCount }} - - - - - - - - {{ t('admin.cloudConstants.unlinked') }} - - - - - - {{ unlinkedCount }} - - - - - - - - {{ t('admin.cloudConstants.appCount') }} - - - - - - {{ applications.length }} - - - - - - - - - - - - - - - - - - - - {{ t('admin.cloudConstants.deleteConstant') }} - - - {{ t('admin.cloudConstants.deleteConfirm', { key: deleteTarget?.key }) }} - - - - - - {{ batchAction === 'delete' ? t('admin.cloudConstants.batchDelete') : batchAction === 'enable' ? t('admin.cloudConstants.batchEnable') : t('admin.cloudConstants.batchDisable') }} - - - {{ batchAction === 'delete' ? t('admin.cloudConstants.batchDeleteConfirm', { count: selectedRows.length }) : batchAction === 'enable' ? t('admin.cloudConstants.batchEnableConfirm', { count: selectedRows.length }) : t('admin.cloudConstants.batchDisableConfirm', { count: selectedRows.length }) }} - - - - diff --git a/frontend/src/pages/developer/cloud-constants/components/columns.ts b/frontend/src/pages/developer/cloud-constants/components/columns.ts deleted file mode 100644 index fb1fbe2..0000000 --- a/frontend/src/pages/developer/cloud-constants/components/columns.ts +++ /dev/null @@ -1,185 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' -import type { Composer } from 'vue-i18n' - -import { Download, File, MoreHorizontal, Pencil, Power, PowerOff, Trash2 } from 'lucide-vue-next' -import { h } from 'vue' - -import type { CloudConstant } from '@/pages/admin/cloud-constants/data/schema' - -import { Copy } from '@/components/sva-ui/copy' -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getColumns(actions: { - onEdit: (row: CloudConstant) => void - onDelete: (row: CloudConstant) => void - onToggleStatus: (row: CloudConstant) => void - onDownload: (row: CloudConstant) => void -}, t: Composer['t']): ColumnDef[] { - return [ - { - accessorKey: 'key', - header: () => t('admin.cloudConstants.columns.key'), - cell: ({ row }) => { - const key = row.getValue('key') as string - if (!key) - return '-' - return h('div', { class: 'flex items-center space-x-2' }, [ - h('code', { class: 'text-xs bg-muted px-2 py-1 rounded font-mono' }, key), - h(Copy, { class: 'h-4 w-4', size: 'sm', content: key }), - ]) - }, - }, - { - accessorKey: 'value', - header: () => t('admin.cloudConstants.columns.value'), - cell: ({ row }) => { - const value = row.getValue('value') as string - const varType = row.original.var_type - if (!value) - return '-' - if (varType === 'binary') { - const originalName = row.original.original_name || value - return h('div', { class: 'flex items-center gap-2' }, [ - h(File, { class: 'h-4 w-4 text-muted-foreground' }), - h('span', { class: 'max-w-xs truncate', title: originalName }, originalName), - ]) - } - return h('div', { class: 'max-w-xs truncate', title: value }, value) - }, - }, - { - accessorKey: 'var_type', - header: () => t('admin.cloudConstants.columns.type'), - cell: ({ row }) => { - const type = row.getValue('var_type') as string - const typeMap: Record = { - integer: t('admin.cloudConstants.types.integer'), - decimal: t('admin.cloudConstants.types.decimal'), - string: t('admin.cloudConstants.types.string'), - binary: t('admin.cloudConstants.types.binary'), - } - return h(Badge, { variant: 'secondary' }, () => typeMap[type || 'string'] || type || 'string') - }, - }, - { - accessorKey: 'description', - header: () => t('admin.cloudConstants.columns.description'), - cell: ({ row }) => { - const description = row.getValue('description') as string - if (!description) - return '-' - return h('div', { class: 'max-w-xs truncate', title: description }, description) - }, - }, - { - accessorKey: 'application_name', - header: () => t('admin.cloudConstants.columns.application'), - cell: ({ row }) => { - const appName = row.getValue('application_name') as string - const appId = row.original.app_id - if (!appId) - return h(Badge, { variant: 'outline' }, () => t('admin.cloudConstants.notLinked')) - if (!appName) - return '-' - return h(Badge, { variant: 'outline' }, () => appName) - }, - }, - { - accessorKey: 'status', - header: () => t('admin.cloudConstants.columns.status'), - cell: ({ row }) => { - const status = row.getValue('status') as string - const isActive = status === 'active' - return h(Badge, { variant: isActive ? 'default' : 'destructive' }, () => isActive ? t('admin.cloudConstants.statusActive') : t('admin.cloudConstants.statusInactive')) - }, - }, - { - accessorKey: 'created_at', - header: () => t('admin.cloudConstants.columns.createdAt'), - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, t('common.actions')), - cell: ({ row }) => { - const constant = row.original - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, t('common.openMenu')), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => { - const items = [ - h(DropdownMenuItem, { onClick: () => actions.onEdit(constant) }, () => [ - h(Pencil, { class: 'mr-2 h-4 w-4' }), - t('admin.cloudConstants.edit'), - ]), - ] - - if (constant.var_type === 'binary') { - items.push( - h(DropdownMenuItem, { onClick: () => actions.onDownload(constant) }, () => [ - h(Download, { class: 'mr-2 h-4 w-4' }), - '下载文件', - ]), - ) - } - - items.push( - h(DropdownMenuItem, { onClick: () => actions.onToggleStatus(constant) }, () => [ - constant.status === 'active' ? h(PowerOff, { class: 'mr-2 h-4 w-4' }) : h(Power, { class: 'mr-2 h-4 w-4' }), - constant.status === 'active' ? t('admin.cloudConstants.disable') : t('admin.cloudConstants.enable'), - ]), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(constant) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - t('admin.cloudConstants.delete'), - ]), - ) - - return items - }, - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/cloud-constants/components/data-table-row-actions.vue b/frontend/src/pages/developer/cloud-constants/components/data-table-row-actions.vue deleted file mode 100644 index 563e589..0000000 --- a/frontend/src/pages/developer/cloud-constants/components/data-table-row-actions.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - 打开菜单 - - - - - - 编辑 - - - - - 删除 - - - - diff --git a/frontend/src/pages/developer/cloud-constants/components/data-table-toolbar.vue b/frontend/src/pages/developer/cloud-constants/components/data-table-toolbar.vue deleted file mode 100644 index 5b6a9af..0000000 --- a/frontend/src/pages/developer/cloud-constants/components/data-table-toolbar.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - {{ t('common.reset') }} - - - - diff --git a/frontend/src/pages/developer/cloud-constants/components/data-table.vue b/frontend/src/pages/developer/cloud-constants/components/data-table.vue deleted file mode 100644 index 4c4c086..0000000 --- a/frontend/src/pages/developer/cloud-constants/components/data-table.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - {{ t('admin.cloudConstants.batchEnableBtn') }} - - - {{ t('admin.cloudConstants.batchDisableBtn') }} - - - {{ t('admin.cloudConstants.batchDeleteBtn') }} - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/cloud-constants/create.vue b/frontend/src/pages/developer/cloud-constants/create.vue deleted file mode 100644 index b9dec18..0000000 --- a/frontend/src/pages/developer/cloud-constants/create.vue +++ /dev/null @@ -1,354 +0,0 @@ - - - - - - - - - - - - {{ t('admin.cloudConstants.create.config') }} - - {{ t('admin.cloudConstants.create.configDesc') }} - - - - - {{ t('admin.cloudConstants.create.application') }} - - - - - - - - {{ app.name }} - - - - - - - - {{ t('admin.cloudConstants.create.type') }} - - - - - - - - {{ t('admin.cloudConstants.types.integer') }} - - - {{ t('admin.cloudConstants.types.decimal') }} - - - {{ t('admin.cloudConstants.types.string') }} - - - 二进制 - - - - - - - - {{ t('admin.cloudConstants.create.key') }} - - - - {{ t('admin.cloudConstants.create.keyHint') }} - - - - - 选择文件 - - - - - 拖拽文件到此处或点击选择 - - - 选择文件 - - - - - - - - - {{ selectedFile.name }} - - - {{ formatFileSize(selectedFile.size) }} - - - - - - - - - - - - - {{ t('admin.cloudConstants.create.value') }} - - - - - - - {{ t('admin.cloudConstants.create.description') }} - - - - - - - - - - - - - {{ t('admin.cloudConstants.create.preview') }} - - - - - - {{ t('admin.cloudConstants.create.application') }} - {{ selectedApplication?.name || '-' }} - - - {{ t('admin.cloudConstants.create.type') }} - {{ t(`admin.cloudConstants.types.${form.var_type}`) }} - - - {{ t('admin.cloudConstants.create.key') }} - {{ form.key || '-' }} - - - {{ t('admin.cloudConstants.create.status') }} - - - - - - {{ t('admin.cloudConstants.create.value') }} - - - {{ selectedFile ? `${selectedFile.name} (${formatFileSize(selectedFile.size)})` : '未选择文件' }} - - - {{ form.value || '-' }} - - - - - - - - - - - - - - {{ uploading ? '上传中...' : t('admin.cloudConstants.create.submit') }} - - - {{ t('admin.cloudConstants.create.cancel') }} - - - - - - - - - diff --git a/frontend/src/pages/developer/cloud-constants/data/data.ts b/frontend/src/pages/developer/cloud-constants/data/data.ts deleted file mode 100644 index 792b83c..0000000 --- a/frontend/src/pages/developer/cloud-constants/data/data.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { CloudConstant } from './schema' - -export const constantAppOptions: { label: string, value: string }[] = [] - -export function filterConstantsByApp(constants: CloudConstant[], appId: string): CloudConstant[] { - if (!appId || appId === 'all') - return constants - return constants.filter(c => String(c.app_id) === appId) -} diff --git a/frontend/src/pages/developer/cloud-constants/data/schema.ts b/frontend/src/pages/developer/cloud-constants/data/schema.ts deleted file mode 100644 index 00405ae..0000000 --- a/frontend/src/pages/developer/cloud-constants/data/schema.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { z } from 'zod' - -export const cloudConstantSchema = z.object({ - id: z.union([z.string(), z.number()]), - user_id: z.union([z.string(), z.number()]), - app_id: z.union([z.string(), z.number()]).nullable().optional(), - application_name: z.string().optional().nullable(), - key: z.string(), - value: z.string(), - var_type: z.string().optional().nullable(), - file_path: z.string().optional().nullable(), - file_size: z.union([z.string(), z.number()]).optional().nullable(), - mime_type: z.string().optional().nullable(), - original_name: z.string().optional().nullable(), - description: z.string().optional().nullable(), - status: z.string().optional().nullable(), - created_at: z.string(), - updated_at: z.string().optional().nullable(), -}) - -export type CloudConstant = z.infer diff --git a/frontend/src/pages/developer/cloud-constants/index.vue b/frontend/src/pages/developer/cloud-constants/index.vue deleted file mode 100644 index 7b45556..0000000 --- a/frontend/src/pages/developer/cloud-constants/index.vue +++ /dev/null @@ -1,337 +0,0 @@ - - - - - - - - {{ t('admin.cloudConstants.addConstant') }} - - - - - - - - - {{ t('admin.cloudConstants.totalConstants') }} - - - - - - {{ filteredConstants.length }} - - - - - - - - {{ t('admin.cloudConstants.linkedApps') }} - - - - - - {{ linkedCount }} - - - - - - - - {{ t('admin.cloudConstants.unlinked') }} - - - - - - {{ unlinkedCount }} - - - - - - - - {{ t('admin.cloudConstants.appCount') }} - - - - - - {{ applications.length }} - - - - - - - - - - - - - - - - - - - - {{ t('admin.cloudConstants.deleteConstant') }} - - - {{ t('admin.cloudConstants.deleteConfirm', { key: deleteTarget?.key }) }} - - - - - - {{ batchAction === 'delete' ? t('admin.cloudConstants.batchDelete') : batchAction === 'enable' ? t('admin.cloudConstants.batchEnable') : t('admin.cloudConstants.batchDisable') }} - - - {{ batchAction === 'delete' ? t('admin.cloudConstants.batchDeleteConfirm', { count: selectedRows.length }) : batchAction === 'enable' ? t('admin.cloudConstants.batchEnableConfirm', { count: selectedRows.length }) : t('admin.cloudConstants.batchDisableConfirm', { count: selectedRows.length }) }} - - - - diff --git a/frontend/src/pages/developer/cloud-variables/[id].vue b/frontend/src/pages/developer/cloud-variables/[id].vue deleted file mode 100644 index f39438f..0000000 --- a/frontend/src/pages/developer/cloud-variables/[id].vue +++ /dev/null @@ -1,361 +0,0 @@ - - - - - - - - {{ t('admin.cloudVariables.addVariable') }} - - - - - - - - - {{ t('admin.cloudVariables.totalVariables') }} - - - - - - {{ filteredVariables.length }} - - - - - - - - {{ t('admin.cloudVariables.appScopeVariables') }} - - - - - - {{ appScopeCount }} - - - - - - - - {{ t('admin.cloudVariables.userScopeVariables') }} - - - - - - {{ userScopeCount }} - - - - - - - - {{ t('admin.cloudVariables.appCount') }} - - - - - - {{ applications.length }} - - - - - - - - - - - - - - - - - - - - - {{ t('admin.cloudVariables.deleteVariable') }} - - - {{ t('admin.cloudVariables.deleteConfirm', { key: deleteTarget?.key }) }} - - - - - - {{ batchAction === 'delete' ? t('admin.cloudVariables.batchDelete') : batchAction === 'enable' ? t('admin.cloudVariables.batchEnable') : t('admin.cloudVariables.batchDisable') }} - - - {{ batchAction === 'delete' ? t('admin.cloudVariables.batchDeleteConfirm', { count: selectedRows.length }) : batchAction === 'enable' ? t('admin.cloudVariables.batchEnableConfirm', { count: selectedRows.length }) : t('admin.cloudVariables.batchDisableConfirm', { count: selectedRows.length }) }} - - - - diff --git a/frontend/src/pages/developer/cloud-variables/[id]/records.vue b/frontend/src/pages/developer/cloud-variables/[id]/records.vue deleted file mode 100644 index aa77024..0000000 --- a/frontend/src/pages/developer/cloud-variables/[id]/records.vue +++ /dev/null @@ -1,274 +0,0 @@ - - - - - - - - - 开始日期 - - - - 结束日期 - - - - - 查询 - - - - - - 删除选中 ({{ selectedIds.length }}) - - - - - - - - - - - - - - ID - - - 用户 - - - 数据 - - - 创建时间 - - - - - - - - - - {{ record.id }} - - - {{ record.user.username }} - - - - - {{ formatData(record.data) }} - - - {{ formatDate(record.created_at) }} - - - - - 暂无记录 - - - - - - - - - - 共 {{ total }} 条记录 - - - - 上一页 - - 第 {{ page }} 页 - - 下一页 - - - - - - diff --git a/frontend/src/pages/developer/cloud-variables/components/columns.ts b/frontend/src/pages/developer/cloud-variables/components/columns.ts deleted file mode 100644 index 4557e77..0000000 --- a/frontend/src/pages/developer/cloud-variables/components/columns.ts +++ /dev/null @@ -1,221 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' -import type { Composer } from 'vue-i18n' - -import { Download, File, List, MoreHorizontal, Pencil, Power, PowerOff, Trash2 } from 'lucide-vue-next' -import { h } from 'vue' - -import type { CloudVariable } from '@/pages/admin/cloud-variables/data/schema' - -import { Copy } from '@/components/sva-ui/copy' -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getColumns(actions: { - onEdit: (row: CloudVariable) => void - onDelete: (row: CloudVariable) => void - onToggleStatus: (row: CloudVariable) => void - onDownload: (row: CloudVariable) => void - onViewRecords: (row: CloudVariable) => void -}, t: Composer['t']): ColumnDef[] { - return [ - { - accessorKey: 'key', - header: () => t('admin.cloudVariables.columns.key'), - cell: ({ row }) => { - const key = row.getValue('key') as string - if (!key) - return '-' - return h('div', { class: 'flex items-center space-x-2' }, [ - h('code', { class: 'text-xs bg-muted px-2 py-1 rounded font-mono' }, key), - h(Copy, { class: 'h-4 w-4', size: 'sm', content: key }), - ]) - }, - }, - { - accessorKey: 'default_value', - header: () => t('admin.cloudVariables.columns.defaultValue'), - cell: ({ row }) => { - const value = row.getValue('default_value') as string - const varType = row.original.var_type - if (!value) - return '-' - if (varType === 'binary') { - const originalName = row.original.original_name || value - return h('div', { class: 'flex items-center gap-2' }, [ - h(File, { class: 'h-4 w-4 text-muted-foreground' }), - h('span', { class: 'max-w-xs truncate', title: originalName }, originalName), - ]) - } - return h('div', { class: 'max-w-xs truncate', title: value }, value) - }, - }, - { - accessorKey: 'scope', - header: () => t('admin.cloudVariables.columns.scope'), - cell: ({ row }) => { - const scope = row.getValue('scope') as string - if (scope === 'app') { - return h(Badge, { variant: 'default' }, () => t('admin.cloudVariables.appScope')) - } - return h(Badge, { variant: 'secondary' }, () => t('admin.cloudVariables.userScope')) - }, - }, - { - accessorKey: 'write_permission', - header: () => t('admin.cloudVariables.create.writePermission'), - cell: ({ row }) => { - const permission = row.getValue('write_permission') as string - if (permission === 'user') { - return h(Badge, { variant: 'outline', class: 'bg-green-50 text-green-700 dark:bg-green-950 dark:text-green-300' }, () => t('admin.cloudVariables.create.userWritable')) - } - return h(Badge, { variant: 'outline' }, () => t('admin.cloudVariables.create.developerOnly')) - }, - }, - { - accessorKey: 'var_type', - header: () => t('admin.cloudVariables.columns.type'), - cell: ({ row }) => { - const type = row.getValue('var_type') as string - const typeMap: Record = { - integer: t('admin.cloudVariables.types.integer'), - decimal: t('admin.cloudVariables.types.decimal'), - string: t('admin.cloudVariables.types.string'), - binary: t('admin.cloudVariables.types.binary'), - stream: '记录', - } - return h(Badge, { variant: 'outline' }, () => typeMap[type || 'string'] || type || 'string') - }, - }, - { - accessorKey: 'description', - header: () => t('admin.cloudVariables.columns.description'), - cell: ({ row }) => { - const description = row.getValue('description') as string - if (!description) - return '-' - return h('div', { class: 'max-w-xs truncate', title: description }, description) - }, - }, - { - accessorKey: 'application_name', - header: () => t('admin.cloudVariables.columns.application'), - cell: ({ row }) => { - const appName = row.getValue('application_name') as string - if (!appName) { - const appId = row.original.app_id - if (!appId) - return h(Badge, { variant: 'outline' }, () => t('admin.cloudVariables.notLinked')) - return '-' - } - return h(Badge, { variant: 'outline' }, () => appName) - }, - }, - { - accessorKey: 'status', - header: () => t('admin.cloudVariables.columns.status'), - cell: ({ row }) => { - const status = row.getValue('status') as string - const isActive = status === 'active' - return h(Badge, { variant: isActive ? 'default' : 'destructive' }, () => isActive ? t('admin.cloudVariables.statusActive') : t('admin.cloudVariables.statusInactive')) - }, - }, - { - accessorKey: 'created_at', - header: () => t('admin.cloudVariables.columns.createdAt'), - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, t('common.actions')), - cell: ({ row }) => { - const variable = row.original - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, t('common.openMenu')), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => { - const items = [] - - if (variable.var_type === 'stream') { - items.push( - h(DropdownMenuItem, { onClick: () => actions.onViewRecords(variable) }, () => [ - h(List, { class: 'mr-2 h-4 w-4' }), - '查看记录', - ]), - ) - } - - items.push( - h(DropdownMenuItem, { onClick: () => actions.onEdit(variable) }, () => [ - h(Pencil, { class: 'mr-2 h-4 w-4' }), - t('admin.cloudVariables.edit'), - ]), - ) - - if (variable.var_type === 'binary') { - items.push( - h(DropdownMenuItem, { onClick: () => actions.onDownload(variable) }, () => [ - h(Download, { class: 'mr-2 h-4 w-4' }), - '下载文件', - ]), - ) - } - - items.push( - h(DropdownMenuItem, { onClick: () => actions.onToggleStatus(variable) }, () => [ - variable.status === 'active' ? h(PowerOff, { class: 'mr-2 h-4 w-4' }) : h(Power, { class: 'mr-2 h-4 w-4' }), - variable.status === 'active' ? t('admin.cloudVariables.disable') : t('admin.cloudVariables.enable'), - ]), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(variable) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - t('admin.cloudVariables.delete'), - ]), - ) - - return items - }, - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/cloud-variables/components/data-table-row-actions.vue b/frontend/src/pages/developer/cloud-variables/components/data-table-row-actions.vue deleted file mode 100644 index d643893..0000000 --- a/frontend/src/pages/developer/cloud-variables/components/data-table-row-actions.vue +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - 打开菜单 - - - - - - 查看记录 - - - - 编辑 - - - - - 删除 - - - - diff --git a/frontend/src/pages/developer/cloud-variables/components/data-table-toolbar.vue b/frontend/src/pages/developer/cloud-variables/components/data-table-toolbar.vue deleted file mode 100644 index c5ca9df..0000000 --- a/frontend/src/pages/developer/cloud-variables/components/data-table-toolbar.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - {{ t('common.reset') }} - - - - diff --git a/frontend/src/pages/developer/cloud-variables/components/data-table.vue b/frontend/src/pages/developer/cloud-variables/components/data-table.vue deleted file mode 100644 index d074b31..0000000 --- a/frontend/src/pages/developer/cloud-variables/components/data-table.vue +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - {{ t('admin.cloudVariables.batchEnableBtn') }} - - - {{ t('admin.cloudVariables.batchDisableBtn') }} - - - {{ t('admin.cloudVariables.batchDeleteBtn') }} - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/cloud-variables/create.vue b/frontend/src/pages/developer/cloud-variables/create.vue deleted file mode 100644 index 3a6b0b1..0000000 --- a/frontend/src/pages/developer/cloud-variables/create.vue +++ /dev/null @@ -1,481 +0,0 @@ - - - - - - - - - - - - {{ t('admin.cloudVariables.create.config') }} - - {{ t('admin.cloudVariables.create.configDesc') }} - - - - - {{ t('admin.cloudVariables.create.application') }} - - - - - - - - {{ app.name }} - - - - - - - - {{ t('admin.cloudVariables.create.type') }} - - - - - - - - {{ t('admin.cloudVariables.types.integer') }} - - - {{ t('admin.cloudVariables.types.decimal') }} - - - {{ t('admin.cloudVariables.types.string') }} - - - 二进制 - - - 记录 - - - - - - - - 最大记录数 - - - - 超过限制时自动删除最旧的记录,0表示不限制 - - - - - - {{ t('admin.cloudVariables.create.key') }} - - - - {{ t('admin.cloudVariables.create.keyHint') }} - - - - - 选择文件 - - - - - 拖拽文件到此处或点击选择 - - - 选择文件 - - - - - - - - - {{ selectedFile.name }} - - - {{ formatFileSize(selectedFile.size) }} - - - - - - - - - - - - - {{ t('admin.cloudVariables.create.defaultValue') }} - - - - - - - {{ t('admin.cloudVariables.create.description') }} - - - - - - - - - {{ t('admin.cloudVariables.create.scopeTitle') }} - {{ t('admin.cloudVariables.create.scopeDesc') }} - - - - - - - - - - {{ t('admin.cloudVariables.create.appScope') }} - - - - {{ t('admin.cloudVariables.create.appScopeDesc') }} - - - - - - - - - - - {{ t('admin.cloudVariables.create.userScope') }} - - - - {{ t('admin.cloudVariables.create.userScopeDesc') }} - - - - - - - - - - {{ t('admin.cloudVariables.create.permissionTitle') }} - {{ t('admin.cloudVariables.create.permissionDesc') }} - - - - - - - - - - {{ t('admin.cloudVariables.create.developerOnly') }} - - - - {{ form.scope === 'app' ? t('admin.cloudVariables.create.developerOnlyAppDesc') : t('admin.cloudVariables.create.developerOnlyDesc') }} - - - - - - - - - - - {{ t('admin.cloudVariables.create.userWritable') }} - - - - {{ form.scope === 'app' ? t('admin.cloudVariables.create.userWritableAppDesc') : t('admin.cloudVariables.create.userWritableDesc') }} - - - - - - - - - - - - - - {{ t('admin.cloudVariables.create.preview') }} - - - - - - {{ t('admin.cloudVariables.create.application') }} - {{ selectedApplication?.name || '-' }} - - - {{ t('admin.cloudVariables.create.type') }} - {{ t(`admin.cloudVariables.types.${form.var_type}`) }} - - - {{ t('admin.cloudVariables.create.key') }} - {{ form.key || '-' }} - - - {{ t('admin.cloudVariables.create.scope') }} - - {{ form.scope === 'app' ? t('admin.cloudVariables.appScope') : t('admin.cloudVariables.userScope') }} - - - - {{ t('admin.cloudVariables.create.writePermission') }} - - {{ form.write_permission === 'admin' ? t('admin.cloudVariables.create.developerOnly') : t('admin.cloudVariables.create.userWritable') }} - - - - {{ t('admin.cloudVariables.create.status') }} - - - - - - {{ t('admin.cloudVariables.create.defaultValue') }} - - - {{ form.default_value || '-' }} - - - - - - - - - - - - - - {{ uploading ? '上传中...' : t('admin.cloudVariables.create.submit') }} - - - {{ t('admin.cloudVariables.create.cancel') }} - - - - - - - - - diff --git a/frontend/src/pages/developer/cloud-variables/data/data.ts b/frontend/src/pages/developer/cloud-variables/data/data.ts deleted file mode 100644 index def5331..0000000 --- a/frontend/src/pages/developer/cloud-variables/data/data.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { CloudVariable } from './schema' - -export const variableAppOptions: { label: string, value: string }[] = [] - -export function filterVariablesByApp(variables: CloudVariable[], appId: string): CloudVariable[] { - if (!appId || appId === 'all') - return variables - return variables.filter(v => String(v.app_id) === appId) -} diff --git a/frontend/src/pages/developer/cloud-variables/data/schema.ts b/frontend/src/pages/developer/cloud-variables/data/schema.ts deleted file mode 100644 index 16b2088..0000000 --- a/frontend/src/pages/developer/cloud-variables/data/schema.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { z } from 'zod' - -export const cloudVariableSchema = z.object({ - id: z.union([z.string(), z.number()]), - user_id: z.union([z.string(), z.number()]), - app_id: z.union([z.string(), z.number()]).nullable().optional(), - application_name: z.string().optional().nullable(), - key: z.string(), - default_value: z.string().optional().nullable(), - var_type: z.string().optional().nullable(), - max_records: z.union([z.string(), z.number()]).optional().nullable(), - file_path: z.string().optional().nullable(), - file_size: z.union([z.string(), z.number()]).optional().nullable(), - mime_type: z.string().optional().nullable(), - original_name: z.string().optional().nullable(), - scope: z.string().optional().nullable(), - write_permission: z.string().optional().nullable(), - description: z.string().optional().nullable(), - status: z.string().optional().nullable(), - created_at: z.string(), - updated_at: z.string().optional().nullable(), -}) - -export type CloudVariable = z.infer diff --git a/frontend/src/pages/developer/cloud-variables/index.vue b/frontend/src/pages/developer/cloud-variables/index.vue deleted file mode 100644 index f39438f..0000000 --- a/frontend/src/pages/developer/cloud-variables/index.vue +++ /dev/null @@ -1,361 +0,0 @@ - - - - - - - - {{ t('admin.cloudVariables.addVariable') }} - - - - - - - - - {{ t('admin.cloudVariables.totalVariables') }} - - - - - - {{ filteredVariables.length }} - - - - - - - - {{ t('admin.cloudVariables.appScopeVariables') }} - - - - - - {{ appScopeCount }} - - - - - - - - {{ t('admin.cloudVariables.userScopeVariables') }} - - - - - - {{ userScopeCount }} - - - - - - - - {{ t('admin.cloudVariables.appCount') }} - - - - - - {{ applications.length }} - - - - - - - - - - - - - - - - - - - - - {{ t('admin.cloudVariables.deleteVariable') }} - - - {{ t('admin.cloudVariables.deleteConfirm', { key: deleteTarget?.key }) }} - - - - - - {{ batchAction === 'delete' ? t('admin.cloudVariables.batchDelete') : batchAction === 'enable' ? t('admin.cloudVariables.batchEnable') : t('admin.cloudVariables.batchDisable') }} - - - {{ batchAction === 'delete' ? t('admin.cloudVariables.batchDeleteConfirm', { count: selectedRows.length }) : batchAction === 'enable' ? t('admin.cloudVariables.batchEnableConfirm', { count: selectedRows.length }) : t('admin.cloudVariables.batchDisableConfirm', { count: selectedRows.length }) }} - - - - diff --git a/frontend/src/pages/developer/devices/components/columns.ts b/frontend/src/pages/developer/devices/components/columns.ts deleted file mode 100644 index ce76ad1..0000000 --- a/frontend/src/pages/developer/devices/components/columns.ts +++ /dev/null @@ -1,179 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' - -import { Ban, Eye, LogOut, MoreHorizontal, Trash2 } from 'lucide-vue-next' -import { h } from 'vue' -import { useRouter } from 'vue-router' - -import type { Device } from '@/pages/admin/devices/data/schema' - -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getColumns(actions: { - onToggleStatus: (row: Device) => void - onDelete: (row: Device) => void - onForceOffline: (row: Device) => void -}): ColumnDef[] { - const router = useRouter() - - return [ - { - accessorKey: 'device_id', - header: '设备指纹', - cell: ({ row }) => { - const deviceId = row.getValue('device_id') as string - if (!deviceId) - return '-' - const displayText = deviceId.length > 16 - ? `${deviceId.substring(0, 8)}...${deviceId.substring(deviceId.length - 4)}` - : deviceId - return h('span', { class: 'font-mono text-xs' }, displayText) - }, - }, - { - accessorKey: 'device_name', - header: '设备名称', - cell: ({ row }) => { - const deviceName = row.getValue('device_name') as string - return deviceName || '-' - }, - }, - { - accessorKey: 'device_type', - header: '设备类型', - cell: ({ row }) => { - const deviceType = row.getValue('device_type') as string - return deviceType || '-' - }, - }, - { - accessorKey: 'user.username', - header: '所属用户', - cell: ({ row }) => { - const username = row.original.user?.username - return username || '-' - }, - }, - { - accessorKey: 'application.name', - header: '所属应用', - cell: ({ row }) => { - const appName = row.original.application?.name - return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-' - }, - }, - { - id: 'online_status', - header: '在线状态', - cell: ({ row }) => { - const onlineSessions = row.original.online_sessions || 0 - const isOnline = onlineSessions > 0 - return h(Badge, { variant: isOnline ? 'default' : 'secondary' }, () => isOnline ? '在线' : '离线') - }, - }, - { - accessorKey: 'online_sessions', - header: '在线实例', - cell: ({ row }) => { - const onlineSessions = row.getValue('online_sessions') as number - const count = onlineSessions || 0 - if (count === 0) { - return h('span', { class: 'text-muted-foreground' }, '0') - } - return h(Badge, { variant: 'default', class: 'cursor-pointer' }, () => `${count} 个`) - }, - }, - { - accessorKey: 'status', - header: '状态', - cell: ({ row }) => { - const status = (row.getValue('status') as string) || 'active' - const isBanned = status === 'banned' - return h(Badge, { variant: isBanned ? 'destructive' : 'default' }, () => isBanned ? '已禁用' : '正常') - }, - }, - { - accessorKey: 'created_at', - header: '绑定时间', - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, '操作'), - cell: ({ row }) => { - const device = row.original - const isBanned = device.status === 'banned' - const hasOnlineSessions = (device.online_sessions || 0) > 0 - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, '打开菜单'), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => [ - hasOnlineSessions - ? h(DropdownMenuItem, { onClick: () => router.push(`/admin/sessions?device_fingerprint=${device.device_id}`) }, () => [ - h(Eye, { class: 'mr-2 h-4 w-4' }), - '查看实例', - ]) - : null, - hasOnlineSessions - ? h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onForceOffline(device) }, () => [ - h(LogOut, { class: 'mr-2 h-4 w-4' }), - '强制离线', - ]) - : null, - h(DropdownMenuItem, { onClick: () => actions.onToggleStatus(device) }, () => [ - h(Ban, { class: 'mr-2 h-4 w-4' }), - isBanned ? '启用' : '禁用', - ]), - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(device) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - '解绑', - ]), - ].filter(Boolean), - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/devices/components/data-table-toolbar.vue b/frontend/src/pages/developer/devices/components/data-table-toolbar.vue deleted file mode 100644 index 213fff1..0000000 --- a/frontend/src/pages/developer/devices/components/data-table-toolbar.vue +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - {{ t('admin.devices.reset') }} - - - - - - - - 刷新 - - - - - {{ t('admin.devices.selected', { count: selectedCount }) }} - - - - {{ t('admin.devices.batchUnban') }} - - - - {{ t('admin.devices.batchBan') }} - - - - {{ t('admin.devices.batchUnbind') }} - - - - diff --git a/frontend/src/pages/developer/devices/components/data-table.vue b/frontend/src/pages/developer/devices/components/data-table.vue deleted file mode 100644 index b37a30f..0000000 --- a/frontend/src/pages/developer/devices/components/data-table.vue +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - {{ t('admin.devices.batchUnban') }} - - - {{ t('admin.devices.batchBan') }} - - - {{ t('admin.devices.batchUnbind') }} - - - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/devices/data/data.ts b/frontend/src/pages/developer/devices/data/data.ts deleted file mode 100644 index 45bb24b..0000000 --- a/frontend/src/pages/developer/devices/data/data.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { DeviceStatus } from './schema' - -export function getDeviceStatusLabel(status: DeviceStatus): string { - const labels: Record = { - active: '在线', - inactive: '离线', - banned: '已禁用', - } - return labels[status] || status -} - -export function getDeviceStatusVariant(status: DeviceStatus): 'default' | 'secondary' | 'destructive' | 'outline' { - const variants: Record = { - active: 'default', - inactive: 'secondary', - banned: 'destructive', - } - return variants[status] || 'secondary' -} diff --git a/frontend/src/pages/developer/devices/data/schema.ts b/frontend/src/pages/developer/devices/data/schema.ts deleted file mode 100644 index b546232..0000000 --- a/frontend/src/pages/developer/devices/data/schema.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { z } from 'zod' - -export const deviceStatusSchema = z.enum(['active', 'inactive', 'banned']) - -export const deviceSchema = z.object({ - id: z.union([z.string(), z.number()]), - user_id: z.union([z.string(), z.number()]), - application_id: z.union([z.string(), z.number()]), - device_id: z.string(), - device_name: z.string().optional().nullable(), - device_type: z.string().optional().nullable(), - status: deviceStatusSchema, - online_sessions: z.number().optional(), - created_at: z.string(), - updated_at: z.string().optional(), - user: z.object({ - id: z.union([z.string(), z.number()]), - username: z.string(), - }).optional().nullable(), - application: z.object({ - id: z.union([z.string(), z.number()]), - name: z.string(), - }).optional().nullable(), -}) - -export type Device = z.infer -export type DeviceStatus = z.infer diff --git a/frontend/src/pages/developer/devices/index.vue b/frontend/src/pages/developer/devices/index.vue deleted file mode 100644 index e0d2da1..0000000 --- a/frontend/src/pages/developer/devices/index.vue +++ /dev/null @@ -1,386 +0,0 @@ - - - - - - - - - - {{ t('admin.devices.totalDevices') }} - - - - - - {{ totalDevices }} - - - - - - - - {{ t('admin.devices.onlineSessions') }} - - - - - - {{ onlineSessionCount }} - - - - - - - - {{ t('admin.devices.normalDevices') }} - - - - - - {{ totalDevices - bannedCount }} - - - - - - - - {{ t('admin.devices.banned') }} - - - - - - {{ bannedCount }} - - - - - - - - r.original.id) || [])" - @batch-ban="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status !== 'banned').map((r: any) => r.original.id) || [], 'banned')" - @batch-unban="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'banned').map((r: any) => r.original.id) || [], 'active')" - > - - - - - - - - - - - - - {{ t('admin.devices.unbindDevice') }} - - - {{ t('admin.devices.unbindConfirm', { deviceId: deleteTarget?.device_id }) }} - - - - - - {{ t('admin.devices.batchUnbind') }} - - - {{ t('admin.devices.batchUnbindConfirm', { count: batchDeleteIds.length }) }} - - - - - - 强制离线 - - - 确定要将设备 "{{ forceOfflineTarget?.device_id }}" 的所有在线实例强制离线吗?该设备将需要重新登录。 - - - - diff --git a/frontend/src/pages/developer/dynamic-code.vue b/frontend/src/pages/developer/dynamic-code.vue deleted file mode 100644 index 5924d6d..0000000 --- a/frontend/src/pages/developer/dynamic-code.vue +++ /dev/null @@ -1,195 +0,0 @@ - - - - - - - - - - - 加载中... - - - - - - 暂无云端函数 - - - - - - - - - - - - {{ code.name }} - - - {{ code.appName }} - - - - - - {{ code.enabled ? '已启用' : '已禁用' }} - - - {{ code.enabled ? '禁用' : '启用' }} - - - - 查看代码 - - - - - - {{ code.description }} - - - - 创建时间: {{ formatDate(code.createdAt) }} - 更新时间: {{ formatDate(code.updatedAt) }} - - - - - - - - - - - {{ selectedCode?.name }} - 云端函数详情 - - - - {{ selectedCode?.code }} - - - - - 关闭 - - - - - - diff --git a/frontend/src/pages/developer/dynamic-code/[id].vue b/frontend/src/pages/developer/dynamic-code/[id].vue deleted file mode 100644 index 59f8b26..0000000 --- a/frontend/src/pages/developer/dynamic-code/[id].vue +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - {{ t('admin.cloudFunction.createBtn') }} - - - - - - - - - {{ t('admin.cloudFunction.totalCodes') }} - - - - - - {{ filteredCodes.length }} - - - - - - - - {{ t('admin.cloudFunction.enabled') }} - - - - - - {{ enabledCount }} - - - - - - - - {{ t('admin.cloudFunction.disabled') }} - - - - - - {{ disabledCount }} - - - - - - - - {{ t('admin.cloudFunction.linkedApps') }} - - - - - - {{ linkedAppsCount }} - - - - - - - - r.original.id) || [], true)" - @batch-disable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [], false)" - @batch-delete="confirmBatchDelete(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - > - - - - - - - - - - - - - - {{ t('admin.cloudFunction.deleteCode') }} - - - {{ t('admin.cloudFunction.deleteCodeConfirm', { codeName: deleteTarget?.name }) }} - - - - - - {{ t('admin.cloudFunction.batchDelete') }} - - - {{ t('admin.cloudFunction.batchDeleteConfirm', { count: batchDeleteIds.length }) }} - - - - diff --git a/frontend/src/pages/developer/dynamic-code/components/columns.ts b/frontend/src/pages/developer/dynamic-code/components/columns.ts deleted file mode 100644 index 987897d..0000000 --- a/frontend/src/pages/developer/dynamic-code/components/columns.ts +++ /dev/null @@ -1,157 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' -import type { Composer } from 'vue-i18n' - -import { Ban, MoreHorizontal, Pencil, Trash2 } from 'lucide-vue-next' -import { h } from 'vue' - -import type { DynamicCode } from '@/pages/admin/dynamic-code/data/schema' - -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getColumns(actions: { - onToggleStatus: (row: DynamicCode) => void - onEdit: (row: DynamicCode) => void - onDelete: (row: DynamicCode) => void -}, t: Composer['t']): ColumnDef[] { - return [ - { - accessorKey: 'name', - header: () => t('admin.cloudFunction.columns.name'), - cell: ({ row }) => { - const name = row.getValue('name') as string - return name || '-' - }, - }, - { - accessorKey: 'application_name', - header: () => t('admin.cloudFunction.columns.application'), - cell: ({ row }) => { - const appName = row.getValue('application_name') as string - return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-' - }, - }, - { - accessorKey: 'description', - header: () => t('admin.cloudFunction.columns.description'), - cell: ({ row }) => { - const description = row.getValue('description') as string - return description || '-' - }, - }, - { - accessorKey: 'enabled', - header: () => t('admin.cloudFunction.columns.status'), - cell: ({ row }) => { - const enabled = row.getValue('enabled') as boolean - return h(Badge, { variant: enabled ? 'default' : 'destructive' }, () => enabled ? t('admin.cloudFunction.enabled') : t('admin.cloudFunction.disabled')) - }, - }, - { - accessorKey: 'creator.username', - header: () => t('admin.cloudFunction.columns.creator'), - cell: ({ row }) => { - const username = row.original.creator?.username - return username || '-' - }, - }, - { - accessorKey: 'created_at', - header: () => t('admin.cloudFunction.columns.createdAt'), - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - accessorKey: 'updated_at', - header: () => t('admin.cloudFunction.columns.updatedAt'), - cell: ({ row }) => { - const updatedAt = row.getValue('updated_at') - if (!updatedAt) - return '-' - try { - const date = new Date(updatedAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, t('common.actions')), - cell: ({ row }) => { - const code = row.original - const isDisabled = !code.enabled - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, t('common.openMenu')), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => [ - h(DropdownMenuItem, { onClick: () => actions.onEdit(code) }, () => [ - h(Pencil, { class: 'mr-2 h-4 w-4' }), - t('admin.cloudFunction.edit.title'), - ]), - h(DropdownMenuItem, { onClick: () => actions.onToggleStatus(code) }, () => [ - h(Ban, { class: 'mr-2 h-4 w-4' }), - isDisabled ? t('admin.cloudFunction.enable') : t('admin.cloudFunction.disable'), - ]), - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(code) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - t('admin.cloudFunction.delete'), - ]), - ], - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/dynamic-code/components/data-table-toolbar.vue b/frontend/src/pages/developer/dynamic-code/components/data-table-toolbar.vue deleted file mode 100644 index 41d630c..0000000 --- a/frontend/src/pages/developer/dynamic-code/components/data-table-toolbar.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - {{ t('admin.cloudFunction.reset') }} - - - - diff --git a/frontend/src/pages/developer/dynamic-code/components/data-table.vue b/frontend/src/pages/developer/dynamic-code/components/data-table.vue deleted file mode 100644 index b4d4123..0000000 --- a/frontend/src/pages/developer/dynamic-code/components/data-table.vue +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - {{ t('admin.cloudFunction.batchEnableBtn') }} - - - {{ t('admin.cloudFunction.batchDisableBtn') }} - - - {{ t('admin.cloudFunction.batchDeleteBtn') }} - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/dynamic-code/create.vue b/frontend/src/pages/developer/dynamic-code/create.vue deleted file mode 100644 index 29ac53c..0000000 --- a/frontend/src/pages/developer/dynamic-code/create.vue +++ /dev/null @@ -1,258 +0,0 @@ - - - - - - - - - - - - {{ t('admin.cloudFunction.create.config') }} - - {{ t('admin.cloudFunction.create.configDesc') }} - - - - - {{ t('admin.cloudFunction.create.application') }} - - - - - - - - {{ app.label }} - - - - - - - - {{ t('admin.cloudFunction.create.name') }} - - - - - - - {{ t('admin.cloudFunction.create.description') }} - - - - - - - {{ t('admin.cloudFunction.create.code') }} - - - - {{ t('admin.cloudFunction.create.codeHelp') }} - - - - - - - {{ t('admin.cloudFunction.create.enabled') }} - - - - - - - - - - - - - - {{ t('admin.cloudFunction.create.preview') }} - - - - - - {{ t('admin.cloudFunction.create.name') }} - {{ form.name || '-' }} - - - {{ t('admin.cloudFunction.create.application') }} - {{ selectedApplication?.name || '-' }} - - - {{ t('admin.cloudFunction.create.description') }} - {{ form.description || '-' }} - - - {{ t('admin.cloudFunction.create.status') }} - - - - - - {{ t('admin.cloudFunction.create.code') }} - - {{ form.code || '-' }} - - - - - - - - - - - - - {{ t('admin.cloudFunction.create.submitBtn') }} - - - {{ t('admin.cloudFunction.create.cancelBtn') }} - - - - - - - - - diff --git a/frontend/src/pages/developer/dynamic-code/data/schema.ts b/frontend/src/pages/developer/dynamic-code/data/schema.ts deleted file mode 100644 index e25db31..0000000 --- a/frontend/src/pages/developer/dynamic-code/data/schema.ts +++ /dev/null @@ -1,20 +0,0 @@ -export interface DynamicCode { - id: number - name: string - code: string - description: string - application_id: number - application_name: string - enabled: boolean - created_at: string - updated_at: string - creator?: { - id: number - username: string - } -} - -export interface Application { - id: number - name: string -} diff --git a/frontend/src/pages/developer/dynamic-code/index.vue b/frontend/src/pages/developer/dynamic-code/index.vue deleted file mode 100644 index 59f8b26..0000000 --- a/frontend/src/pages/developer/dynamic-code/index.vue +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - {{ t('admin.cloudFunction.createBtn') }} - - - - - - - - - {{ t('admin.cloudFunction.totalCodes') }} - - - - - - {{ filteredCodes.length }} - - - - - - - - {{ t('admin.cloudFunction.enabled') }} - - - - - - {{ enabledCount }} - - - - - - - - {{ t('admin.cloudFunction.disabled') }} - - - - - - {{ disabledCount }} - - - - - - - - {{ t('admin.cloudFunction.linkedApps') }} - - - - - - {{ linkedAppsCount }} - - - - - - - - r.original.id) || [], true)" - @batch-disable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [], false)" - @batch-delete="confirmBatchDelete(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - > - - - - - - - - - - - - - - {{ t('admin.cloudFunction.deleteCode') }} - - - {{ t('admin.cloudFunction.deleteCodeConfirm', { codeName: deleteTarget?.name }) }} - - - - - - {{ t('admin.cloudFunction.batchDelete') }} - - - {{ t('admin.cloudFunction.batchDeleteConfirm', { count: batchDeleteIds.length }) }} - - - - diff --git a/frontend/src/pages/developer/extension/api-keys/create.vue b/frontend/src/pages/developer/extension/api-keys/create.vue deleted file mode 100644 index ab970d0..0000000 --- a/frontend/src/pages/developer/extension/api-keys/create.vue +++ /dev/null @@ -1,318 +0,0 @@ - - - - - - - - - - - - {{ t('admin.extension.apiKeys.create.title') }} - - {{ t('admin.extension.apiKeys.create.description') }} - - - - - {{ t('admin.extension.apiKeys.create.name') }} * - - - - - - - {{ t('admin.extension.apiKeys.create.application') }} * - - - - - - - - {{ app.label }} - - - - - - - - {{ t('admin.extension.apiKeys.create.permissions') }} * - - g.permissions.map(p => p.value))"> - {{ t('admin.extension.apiKeys.create.selectAll') }} - - - {{ t('admin.extension.apiKeys.create.deselectAll') }} - - - - - - - - {{ group.name }} - - - - {{ t('admin.extension.apiKeys.create.selectAll') }} - - - {{ t('admin.extension.apiKeys.create.deselectAll') }} - - - - - - - - - {{ perm.label }} - - - {{ perm.description }} - - - - - - - - - - - {{ t('admin.extension.apiKeys.create.enabled') }} - - - - - - - - - - - - {{ t('admin.extension.apiKeys.create.preview') || '预览' }} - - - - - - {{ t('admin.extension.apiKeys.create.name') }} - {{ form.name || '-' }} - - - {{ t('admin.extension.apiKeys.create.application') }} - {{ selectedApplication?.name || '-' }} - - - {{ t('admin.extension.apiKeys.create.permissions') }} - {{ form.permissions.length }} {{ t('admin.extension.apiKeys.create.selected') || '已选择' }} - - - - - - - - - - - - {{ t('admin.extension.apiKeys.create.submitBtn') }} - - - {{ t('admin.extension.apiKeys.create.cancelBtn') }} - - - - - - - - - diff --git a/frontend/src/pages/developer/extension/components/apikey-columns.ts b/frontend/src/pages/developer/extension/components/apikey-columns.ts deleted file mode 100644 index 63e2861..0000000 --- a/frontend/src/pages/developer/extension/components/apikey-columns.ts +++ /dev/null @@ -1,156 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' -import type { Composer } from 'vue-i18n' - -import { Ban, MoreHorizontal, RefreshCw, Trash2 } from 'lucide-vue-next' -import { h } from 'vue' - -import type { APIKey } from '@/pages/admin/extension/data/schema' - -import { Copy } from '@/components/sva-ui/copy' -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getAPIKeyColumns(actions: { - onToggleStatus: (row: APIKey) => void - onRegenerate: (row: APIKey) => void - onDelete: (row: APIKey) => void -}, t: Composer['t']): ColumnDef[] { - return [ - { - accessorKey: 'name', - header: () => t('admin.extension.apiKeys.columns.name'), - cell: ({ row }) => { - const name = row.getValue('name') as string - return name || '-' - }, - }, - { - accessorKey: 'application_name', - header: () => t('admin.extension.apiKeys.columns.application'), - cell: ({ row }) => { - const appName = row.getValue('application_name') as string - return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-' - }, - }, - { - accessorKey: 'access_key', - header: () => t('admin.extension.apiKeys.columns.accessKey'), - cell: ({ row }) => { - const accessKey = row.getValue('access_key') as string - return accessKey - ? h('div', { class: 'flex items-center space-x-2' }, [ - h('code', { class: 'text-xs bg-muted px-2 py-1 rounded font-mono' }, accessKey), - h(Copy, { class: 'h-4 w-4', size: 'sm', content: accessKey }), - ]) - : '-' - }, - }, - { - accessorKey: 'secret_key', - header: () => t('admin.extension.apiKeys.columns.secretKey'), - cell: ({ row }) => { - const secretKey = row.getValue('secret_key') as string - return secretKey ? h('code', { class: 'text-xs bg-muted px-2 py-1 rounded font-mono' }, '••••••••••••••••') : '-' - }, - }, - { - accessorKey: 'permissions', - header: () => t('admin.extension.apiKeys.columns.permissions'), - cell: ({ row }) => { - const permissions = row.getValue('permissions') as string - try { - const permissionList = JSON.parse(permissions || '[]') - return h('div', { class: 'flex flex-wrap gap-1' }, permissionList.slice(0, 3).map((permission: string) => - h(Badge, { variant: 'outline', class: 'text-xs' }, () => permission), - )) - } - catch { - return '-' - } - }, - }, - { - accessorKey: 'status', - header: () => t('admin.extension.apiKeys.columns.status'), - cell: ({ row }) => { - const status = row.getValue('status') as string - return h(Badge, { variant: status === 'active' ? 'default' : 'secondary' }, () => - status === 'active' ? t('admin.extension.enabled') : t('admin.extension.disabled')) - }, - }, - { - accessorKey: 'created_at', - header: () => t('admin.extension.apiKeys.columns.createdAt'), - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, t('common.actions')), - cell: ({ row }) => { - const apiKey = row.original - const isDisabled = apiKey.status === 'inactive' - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, t('common.openMenu')), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => [ - h(DropdownMenuItem, { onClick: () => actions.onRegenerate(apiKey) }, () => [ - h(RefreshCw, { class: 'mr-2 h-4 w-4' }), - t('admin.extension.apiKeys.regenerate'), - ]), - h(DropdownMenuItem, { onClick: () => actions.onToggleStatus(apiKey) }, () => [ - h(Ban, { class: 'mr-2 h-4 w-4' }), - isDisabled ? t('admin.extension.apiKeys.enable') : t('admin.extension.apiKeys.disable'), - ]), - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(apiKey) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - t('admin.extension.apiKeys.delete'), - ]), - ], - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/extension/components/apikey-data-table-toolbar.vue b/frontend/src/pages/developer/extension/components/apikey-data-table-toolbar.vue deleted file mode 100644 index 6ecad1c..0000000 --- a/frontend/src/pages/developer/extension/components/apikey-data-table-toolbar.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - {{ t('admin.extension.apiKeys.reset') }} - - - - diff --git a/frontend/src/pages/developer/extension/components/apikey-data-table.vue b/frontend/src/pages/developer/extension/components/apikey-data-table.vue deleted file mode 100644 index 001757c..0000000 --- a/frontend/src/pages/developer/extension/components/apikey-data-table.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - {{ t('admin.extension.apiKeys.enable') }} - - - {{ t('admin.extension.apiKeys.disable') }} - - - {{ t('admin.extension.apiKeys.batchDelete') }} - - - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/extension/components/webhook-columns.ts b/frontend/src/pages/developer/extension/components/webhook-columns.ts deleted file mode 100644 index b9da936..0000000 --- a/frontend/src/pages/developer/extension/components/webhook-columns.ts +++ /dev/null @@ -1,165 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' -import type { Composer } from 'vue-i18n' - -import { Ban, Eye, MoreHorizontal, Send, Trash2 } from 'lucide-vue-next' -import { h } from 'vue' - -import type { Webhook } from '@/pages/admin/extension/data/schema' - -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getWebhookColumns(actions: { - onToggleStatus: (row: Webhook) => void - onTest: (row: Webhook) => void - onViewLogs?: (row: Webhook) => void - onDelete: (row: Webhook) => void -}, t: Composer['t']): ColumnDef[] { - return [ - { - accessorKey: 'name', - header: () => t('admin.extension.webhooks.columns.name'), - cell: ({ row }) => { - const name = row.getValue('name') as string - return name || '-' - }, - }, - { - accessorKey: 'url', - header: () => t('admin.extension.webhooks.columns.url'), - cell: ({ row }) => { - const url = row.getValue('url') as string - return url ? h('code', { class: 'text-xs bg-muted px-2 py-1 rounded font-mono max-w-[200px] truncate inline-block' }, url) : '-' - }, - }, - { - accessorKey: 'application_name', - header: () => t('admin.extension.webhooks.columns.application'), - cell: ({ row }) => { - const appName = row.getValue('application_name') as string - return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-' - }, - }, - { - accessorKey: 'events', - header: () => t('admin.extension.webhooks.columns.events'), - cell: ({ row }) => { - const events = row.getValue('events') as string - try { - const eventList = JSON.parse(events || '[]') - return h('div', { class: 'flex flex-wrap gap-1' }, eventList.slice(0, 3).map((event: string) => - h(Badge, { variant: 'outline', class: 'text-xs' }, () => event), - )) - } - catch { - return '-' - } - }, - }, - { - accessorKey: 'status', - header: () => t('admin.extension.webhooks.columns.status'), - cell: ({ row }) => { - const status = row.getValue('status') as string - return h(Badge, { variant: status === 'active' ? 'default' : 'secondary' }, () => - status === 'active' ? t('admin.extension.enabled') : t('admin.extension.disabled')) - }, - }, - { - accessorKey: 'retry_count', - header: () => t('admin.extension.webhooks.columns.retryCount'), - cell: ({ row }) => { - const retryCount = row.getValue('retry_count') as number - return retryCount ?? '-' - }, - }, - { - accessorKey: 'timeout', - header: () => t('admin.extension.webhooks.columns.timeout'), - cell: ({ row }) => { - const timeout = row.getValue('timeout') as number - return timeout ? `${timeout}s` : '-' - }, - }, - { - accessorKey: 'created_at', - header: () => t('admin.extension.webhooks.columns.createdAt'), - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, t('common.actions')), - cell: ({ row }) => { - const webhook = row.original - const isDisabled = webhook.status === 'inactive' - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, t('common.openMenu')), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => [ - h(DropdownMenuItem, { onClick: () => actions.onTest(webhook) }, () => [ - h(Send, { class: 'mr-2 h-4 w-4' }), - t('admin.extension.webhooks.test'), - ]), - actions.onViewLogs - ? h(DropdownMenuItem, { onClick: () => actions.onViewLogs?.(webhook) }, () => [ - h(Eye, { class: 'mr-2 h-4 w-4' }), - t('admin.extension.webhooks.viewLogs'), - ]) - : null, - h(DropdownMenuItem, { onClick: () => actions.onToggleStatus(webhook) }, () => [ - h(Ban, { class: 'mr-2 h-4 w-4' }), - isDisabled ? t('admin.extension.webhooks.enable') : t('admin.extension.webhooks.disable'), - ]), - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(webhook) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - t('admin.extension.webhooks.delete'), - ]), - ], - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/extension/components/webhook-data-table-toolbar.vue b/frontend/src/pages/developer/extension/components/webhook-data-table-toolbar.vue deleted file mode 100644 index 18200e2..0000000 --- a/frontend/src/pages/developer/extension/components/webhook-data-table-toolbar.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - {{ t('admin.extension.webhooks.reset') }} - - - - diff --git a/frontend/src/pages/developer/extension/components/webhook-data-table.vue b/frontend/src/pages/developer/extension/components/webhook-data-table.vue deleted file mode 100644 index 0716e7a..0000000 --- a/frontend/src/pages/developer/extension/components/webhook-data-table.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - {{ t('admin.extension.webhooks.enable') }} - - - {{ t('admin.extension.webhooks.disable') }} - - - {{ t('admin.extension.webhooks.batchDelete') }} - - - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/extension/data/schema.ts b/frontend/src/pages/developer/extension/data/schema.ts deleted file mode 100644 index f633048..0000000 --- a/frontend/src/pages/developer/extension/data/schema.ts +++ /dev/null @@ -1,40 +0,0 @@ -export interface Webhook { - id: number - name: string - url: string - secret_key: string - events: string - retry_count: number - timeout: number - status: 'active' | 'inactive' - application_id: number - application_name: string - created_at: string - updated_at: string - creator?: { - id: number - username: string - } -} - -export interface APIKey { - id: number - name: string - access_key: string - secret_key: string - permissions: string - status: 'active' | 'inactive' - application_id: number - application_name: string - created_at: string - updated_at: string - creator?: { - id: number - username: string - } -} - -export interface Application { - id: number - name: string -} diff --git a/frontend/src/pages/developer/extension/index.vue b/frontend/src/pages/developer/extension/index.vue deleted file mode 100644 index b158ef7..0000000 --- a/frontend/src/pages/developer/extension/index.vue +++ /dev/null @@ -1,560 +0,0 @@ - - - - - - - - {{ t('admin.extension.webhooks.createBtn') }} - - - - {{ t('admin.extension.apiKeys.createBtn') }} - - - - - - - - - {{ t('admin.extension.totalWebhooks') }} - - - - - - {{ filteredWebhooks.length }} - - - - - - - - {{ t('admin.extension.totalAPIKeys') }} - - - - - - {{ filteredAPIKeys.length }} - - - - - - - - {{ t('admin.extension.enabled') }} - - - - - - {{ activeWebhookCount + activeAPIKeyCount }} - - - - - - - - {{ t('admin.extension.disabled') }} - - - - - - {{ inactiveWebhookCount + inactiveAPIKeyCount }} - - - - - - - - - - {{ t('admin.extension.webhooks.title') }} - - - - {{ t('admin.extension.apiKeys.title') }} - - - - - - - r.original.status === 'inactive').map((r: any) => r.original.id) || [], 'active')" - @batch-disable="batchToggleWebhookStatus(webhookTableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'active').map((r: any) => r.original.id) || [], 'inactive')" - @batch-delete="confirmBatchDelete('webhook', webhookTableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - @update:search-filter="webhookSearchFilter = $event" - > - - - - - - - - - - - - - - r.original.status === 'inactive').map((r: any) => r.original.id) || [], 'active')" - @batch-disable="batchToggleAPIKeyStatus(apiKeyTableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'active').map((r: any) => r.original.id) || [], 'inactive')" - @batch-delete="confirmBatchDelete('apikey', apiKeyTableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - @update:search-filter="apiKeySearchFilter = $event" - > - - - - - - - - - - - - - - - {{ deleteType === 'webhook' ? t('admin.extension.webhooks.deleteWebhook') : t('admin.extension.apiKeys.deleteAPIKey') }} - - - {{ deleteType === 'webhook' - ? t('admin.extension.webhooks.deleteConfirm', { webhookName: (deleteTarget as Webhook)?.name }) - : t('admin.extension.apiKeys.deleteConfirm', { apiKeyName: (deleteTarget as APIKey)?.name }) - }} - - - - - - {{ deleteType === 'webhook' ? t('admin.extension.webhooks.batchDelete') : t('admin.extension.apiKeys.batchDelete') }} - - - {{ deleteType === 'webhook' - ? t('admin.extension.webhooks.batchDeleteConfirm', { count: batchDeleteIds.length }) - : t('admin.extension.apiKeys.batchDeleteConfirm', { count: batchDeleteIds.length }) - }} - - - - - {{ t('admin.extension.apiKeys.regenerate') }} - - - {{ t('admin.extension.apiKeys.regenerateConfirm') }} - - - - diff --git a/frontend/src/pages/developer/extension/webhooks/create.vue b/frontend/src/pages/developer/extension/webhooks/create.vue deleted file mode 100644 index 9d1ae21..0000000 --- a/frontend/src/pages/developer/extension/webhooks/create.vue +++ /dev/null @@ -1,315 +0,0 @@ - - - - - - - - - - - - {{ t('admin.extension.webhooks.create.title') }} - - {{ t('admin.extension.webhooks.create.description') }} - - - - - {{ t('admin.extension.webhooks.create.name') }} * - - - - - - - {{ t('admin.extension.webhooks.create.application') }} * - - - - - - - - {{ app.label }} - - - - - - - - {{ t('admin.extension.webhooks.create.url') }} * - - - - - - - {{ t('admin.extension.webhooks.create.secretKey') }} - - - - - - {{ t('admin.extension.webhooks.create.events') }} * - - - - {{ event.label }} - - - - - - - - {{ t('admin.extension.webhooks.create.retryCount') }} - - - - - - - {{ t('admin.extension.webhooks.create.timeout') }} - - - - - - - - - {{ t('admin.extension.webhooks.create.enabled') }} - - - - - - - - - - - - {{ t('admin.extension.webhooks.create.preview') || '预览' }} - - - - - - {{ t('admin.extension.webhooks.create.name') }} - {{ form.name || '-' }} - - - {{ t('admin.extension.webhooks.create.application') }} - {{ selectedApplication?.name || '-' }} - - - {{ t('admin.extension.webhooks.create.events') }} - {{ form.events.length }} {{ t('admin.extension.webhooks.create.selected') || '已选择' }} - - - {{ t('admin.extension.webhooks.create.retryCount') }} - {{ form.retry_count }} - - - {{ t('admin.extension.webhooks.create.timeout') }} - {{ form.timeout }}s - - - - - - - - - - - - {{ t('admin.extension.webhooks.create.submitBtn') }} - - - {{ t('admin.extension.webhooks.create.cancelBtn') }} - - - - - - - - - diff --git a/frontend/src/pages/developer/finance/index.vue b/frontend/src/pages/developer/finance/index.vue deleted file mode 100644 index aaf729c..0000000 --- a/frontend/src/pages/developer/finance/index.vue +++ /dev/null @@ -1,580 +0,0 @@ - - - - - - - - - - {{ t('admin.finance.rechargeCount') }} - - - - - - {{ stats.rechargeCount }} - - - - - - - - {{ t('admin.finance.rechargeAmount') }} - - - - - - ¥{{ stats.rechargeAmount.toFixed(2) }} - - - - - - - - {{ t('admin.finance.consumptionCount') }} - - - - - - {{ stats.consumptionCount }} - - - - - - - - {{ t('admin.finance.consumptionAmount') }} - - - - - - ¥{{ stats.consumptionAmount.toFixed(2) }} - - - - - - - - {{ t('admin.finance.profit') }} - - - - - - ¥{{ stats.profit.toFixed(2) }} - - - - - - - - {{ t('admin.finance.transactionRecords') }} - - {{ t('admin.finance.transactionRecordsDesc') }} - - - - - - - - {{ t('admin.finance.batchDeleteBtn') }} - - - - - - - - - - - {{ t('admin.finance.allApplications') }} - - - {{ app.name }} - - - - - - - - - - - {{ t('admin.finance.allStatus') }} - - - {{ t('admin.finance.success') }} - - - {{ t('admin.finance.pending') }} - - - {{ t('admin.finance.failed') }} - - - - - - {{ t('admin.finance.to') }} - - - {{ t('admin.finance.today') }} - - - {{ t('admin.finance.last7Days') }} - - - {{ t('admin.finance.thisMonth') }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ t('admin.finance.noRecords') }} - - - - - - - - - - - - - - - - {{ t('admin.finance.delete') }} - - - {{ t('admin.finance.deleteConfirm') }} - - - - - - {{ t('admin.finance.batchDeleteBtn') }} - - - {{ t('admin.finance.batchDeleteConfirm', { count: table.getSelectedRowModel().rows.length }) }} - - - diff --git a/frontend/src/pages/developer/index.vue b/frontend/src/pages/developer/index.vue deleted file mode 100644 index b215af6..0000000 --- a/frontend/src/pages/developer/index.vue +++ /dev/null @@ -1,695 +0,0 @@ - - - - - - - - - {{ t('admin.loading') }}... - - - - - - - - - - {{ t('admin.totalApplications') }} - - - - - - {{ formatNumber(stats.totalApplications) }} - - - +12.5% {{ t('admin.lastMonth') }} - - - - - - - - {{ t('admin.totalCards') }} - - - - - - {{ formatNumber(stats.totalCards) }} - - - +8.2% {{ t('admin.lastMonth') }} - - - - - - - - {{ t('admin.totalUsers') }} - - - - - - {{ formatNumber(totalUsers) }} - - - +15.3% {{ t('admin.lastMonth') }} - - - - - - - - {{ t('admin.monthlyRevenue') }} - - - - - - ¥{{ formatNumber(stats.monthlyRevenue) }} - - - +23.1% {{ t('admin.lastMonth') }} - - - - - - - - - - - {{ t('admin.userDistribution') }} - {{ t('admin.userDistributionDesc') }} - - - - - {{ t('admin.domesticUsers') }}: {{ formatNumber(domesticUsers) }} - - - - {{ t('admin.overseasUsers') }}: {{ formatNumber(overseasUsers) }} - - - - - - - - - - - - - - {{ t('admin.onlineTrend') }} - {{ t('admin.onlineTrendDesc') }} - - - - - - - - - - - - - - {{ t('admin.recentTickets') }} - {{ t('admin.recentTicketsDesc') }} - - - {{ t('admin.viewAll') }} - - - - - - - - - - - - - - - {{ ticket.title }} - - - - {{ getTicketStatusText(ticket.status) }} - - - {{ getTicketPriorityText(ticket.priority) }} - - - {{ formatDate(ticket.createdAt) }} - - - - - - - {{ t('admin.noTickets') }} - - - - - - - - {{ t('admin.recentActivities') }} - {{ t('admin.recentActivitiesDesc') }} - - - - - - - - - - - - {{ activity.title }} - - - {{ activity.description }} - - - {{ formatDate(activity.createdAt) }} - - - - - - - - {{ t('admin.noActivities') }} - - - - - - - diff --git a/frontend/src/pages/developer/logs.vue b/frontend/src/pages/developer/logs.vue deleted file mode 100644 index 113b377..0000000 --- a/frontend/src/pages/developer/logs.vue +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - - - - - {{ t('admin.logs.totalLogs') }} - - - - - - {{ filteredLogs.length }} - - - - - - - - {{ t('admin.logs.successCount') }} - - - - - - {{ successCount }} - - - - - - - - {{ t('admin.logs.failedCount') }} - - - - - - {{ failedCount }} - - - - - - - - {{ t('admin.logs.operationCount') }} - - - - - - {{ operationCount }} - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/logs/components/data-table.vue b/frontend/src/pages/developer/logs/components/data-table.vue deleted file mode 100644 index bb40175..0000000 --- a/frontend/src/pages/developer/logs/components/data-table.vue +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/logs/data/schema.ts b/frontend/src/pages/developer/logs/data/schema.ts deleted file mode 100644 index 858d83e..0000000 --- a/frontend/src/pages/developer/logs/data/schema.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from 'zod' - -export const logTypeSchema = z.enum(['operation', 'verification', 'exception']) -export const logStatusSchema = z.enum(['success', 'failed']) - -export const logSchema = z.object({ - id: z.union([z.string(), z.number()]), - log_type: logTypeSchema, - action: z.string(), - resource: z.string().optional().nullable(), - resource_id: z.union([z.string(), z.number()]).optional().nullable(), - details: z.string(), - status: logStatusSchema, - ip_address: z.string(), - user_agent: z.string().optional().nullable(), - device_id: z.string().optional().nullable(), - level: z.string().optional().nullable(), - error_message: z.string().optional().nullable(), - application_id: z.union([z.string(), z.number()]).optional().nullable(), - user_id: z.union([z.string(), z.number()]).optional().nullable(), - app_user_id: z.union([z.string(), z.number()]).optional().nullable(), - created_at: z.string(), - application: z.object({ - id: z.union([z.string(), z.number()]), - name: z.string(), - }).optional().nullable(), - user: z.object({ - id: z.union([z.string(), z.number()]), - username: z.string(), - }).optional().nullable(), - app_user: z.object({ - id: z.union([z.string(), z.number()]), - username: z.string(), - }).optional().nullable(), -}) - -export type Log = z.infer diff --git a/frontend/src/pages/developer/risk-control/[id].vue b/frontend/src/pages/developer/risk-control/[id].vue deleted file mode 100644 index c6d24b7..0000000 --- a/frontend/src/pages/developer/risk-control/[id].vue +++ /dev/null @@ -1,289 +0,0 @@ - - - - - - - - 添加规则 - - - - - - - - - 生效中 - - - - - - {{ activeCount }} - - - - - - - - 已失效 - - - - - - {{ inactiveCount }} - - - - - - - - IP封禁 - - - - - - {{ ipCount }} - - - - - - - - 设备封禁 - - - - - - {{ deviceCount }} - - - - - - - - r.original.status === 'active').map((r: any) => r.original.id) || [], 'inactive')" - @batch-enable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'inactive').map((r: any) => r.original.id) || [], 'active')" - @batch-delete="confirmBatchDelete(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - > - - - - - - - - - - - - 删除风控规则 - - - 确定要删除该风控规则吗?此操作不可撤销。 - - - - - - 批量删除风控规则 - - - 确定要删除选中的 {{ batchDeleteIds.length }} 条规则吗?此操作不可撤销。 - - - - diff --git a/frontend/src/pages/developer/risk-control/components/columns.ts b/frontend/src/pages/developer/risk-control/components/columns.ts deleted file mode 100644 index 26fc2da..0000000 --- a/frontend/src/pages/developer/risk-control/components/columns.ts +++ /dev/null @@ -1,227 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' - -import { MoreHorizontal, Pencil, ShieldCheck, ShieldOff, Trash2 } from 'lucide-vue-next' -import { h } from 'vue' - -import type { RiskRule } from '@/pages/admin/risk-control/data/schema' - -import { Copy } from '@/components/sva-ui/copy' -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -const countries: Record = { - CN: { name: '中国', flag: '🇨🇳' }, - US: { name: '美国', flag: '🇺🇸' }, - JP: { name: '日本', flag: '🇯🇵' }, - KR: { name: '韩国', flag: '🇰🇷' }, - GB: { name: '英国', flag: '🇬🇧' }, - DE: { name: '德国', flag: '🇩🇪' }, - FR: { name: '法国', flag: '🇫🇷' }, - IT: { name: '意大利', flag: '🇮🇹' }, - ES: { name: '西班牙', flag: '🇪🇸' }, - RU: { name: '俄罗斯', flag: '🇷🇺' }, - CA: { name: '加拿大', flag: '🇨🇦' }, - AU: { name: '澳大利亚', flag: '🇦🇺' }, - BR: { name: '巴西', flag: '🇧🇷' }, - IN: { name: '印度', flag: '🇮🇳' }, - MX: { name: '墨西哥', flag: '🇲🇽' }, - NL: { name: '荷兰', flag: '🇳🇱' }, - SG: { name: '新加坡', flag: '🇸🇬' }, - HK: { name: '中国香港', flag: '🇭🇰' }, - TW: { name: '中国台湾', flag: '🇹🇼' }, - TH: { name: '泰国', flag: '🇹🇭' }, - VN: { name: '越南', flag: '🇻🇳' }, - MY: { name: '马来西亚', flag: '🇲🇾' }, - ID: { name: '印度尼西亚', flag: '🇮🇩' }, - PH: { name: '菲律宾', flag: '🇵🇭' }, - AE: { name: '阿联酋', flag: '🇦🇪' }, - SA: { name: '沙特阿拉伯', flag: '🇸🇦' }, - TR: { name: '土耳其', flag: '🇹🇷' }, - PL: { name: '波兰', flag: '🇵🇱' }, - SE: { name: '瑞典', flag: '🇸🇪' }, - CH: { name: '瑞士', flag: '🇨🇭' }, - AT: { name: '奥地利', flag: '🇦🇹' }, - BE: { name: '比利时', flag: '🇧🇪' }, - DK: { name: '丹麦', flag: '🇩🇰' }, - FI: { name: '芬兰', flag: '🇫🇮' }, - NO: { name: '挪威', flag: '🇳🇴' }, - IE: { name: '爱尔兰', flag: '🇮🇪' }, - PT: { name: '葡萄牙', flag: '🇵🇹' }, - CZ: { name: '捷克', flag: '🇨🇿' }, - RO: { name: '罗马尼亚', flag: '🇷🇴' }, - HU: { name: '匈牙利', flag: '🇭🇺' }, - IL: { name: '以色列', flag: '🇮🇱' }, - ZA: { name: '南非', flag: '🇿🇦' }, - EG: { name: '埃及', flag: '🇪🇬' }, - NG: { name: '尼日利亚', flag: '🇳🇬' }, - AR: { name: '阿根廷', flag: '🇦🇷' }, - CL: { name: '智利', flag: '🇨🇱' }, - CO: { name: '哥伦比亚', flag: '🇨🇴' }, - PE: { name: '秘鲁', flag: '🇵🇪' }, - NZ: { name: '新西兰', flag: '🇳🇿' }, -} - -function getCountryDisplay(code: string) { - const country = countries[code] - return country ? `${country.flag} ${country.name}` : code -} - -export function getColumns(actions: { - onToggleStatus: (row: RiskRule) => void - onEdit: (row: RiskRule) => void - onDelete: (row: RiskRule) => void -}): ColumnDef[] { - return [ - { - accessorKey: 'type', - header: '类型', - cell: ({ row }) => { - const type = row.getValue('type') as string - const typeMap: Record = { - ip: { text: 'IP封禁', variant: 'default' }, - device: { text: '设备封禁', variant: 'secondary' }, - user: { text: '用户封禁', variant: 'outline' }, - region: { text: '地区封禁', variant: 'destructive' }, - } - const { text, variant } = typeMap[type] || { text: type, variant: 'default' } - return h(Badge, { variant }, () => text) - }, - }, - { - accessorKey: 'value', - header: '封禁值', - cell: ({ row }) => { - const value = row.getValue('value') as string - const type = row.getValue('type') as string - if (!value) - return '-' - - const displayValue = type === 'region' ? getCountryDisplay(value) : value - - return h('div', { class: 'flex items-center space-x-2' }, [ - h('code', { class: 'text-xs bg-muted px-2 py-1 rounded font-mono' }, displayValue), - h(Copy, { class: 'h-4 w-4', size: 'sm', content: value }), - ]) - }, - }, - { - accessorKey: 'reason', - header: '原因', - cell: ({ row }) => { - const reason = row.getValue('reason') as string - return reason || '-' - }, - }, - { - accessorKey: 'status', - header: '状态', - cell: ({ row }) => { - const status = row.getValue('status') as string - const statusMap: Record = { - active: { text: '生效中', variant: 'default' }, - inactive: { text: '已失效', variant: 'secondary' }, - } - const { text, variant } = statusMap[status] || { text: status, variant: 'secondary' } - return h(Badge, { variant }, () => text) - }, - }, - { - accessorKey: 'expires_at', - header: '过期时间', - cell: ({ row }) => { - const expiresAt = row.getValue('expires_at') - if (!expiresAt) - return '永久' - try { - const date = new Date(expiresAt as string) - if (Number.isNaN(date.getTime())) - return '永久' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '永久' - } - }, - }, - { - accessorKey: 'created_at', - header: '创建时间', - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, '操作'), - cell: ({ row }) => { - const rule = row.original - const isActive = rule.status === 'active' - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, '打开菜单'), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => [ - h(DropdownMenuItem, { onClick: () => actions.onToggleStatus(rule) }, () => [ - isActive ? h(ShieldOff, { class: 'mr-2 h-4 w-4' }) : h(ShieldCheck, { class: 'mr-2 h-4 w-4' }), - isActive ? '禁用' : '启用', - ]), - h(DropdownMenuItem, { onClick: () => actions.onEdit(rule) }, () => [ - h(Pencil, { class: 'mr-2 h-4 w-4' }), - '编辑', - ]), - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(rule) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - '删除', - ]), - ], - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/risk-control/components/data-table-toolbar.vue b/frontend/src/pages/developer/risk-control/components/data-table-toolbar.vue deleted file mode 100644 index 8898f44..0000000 --- a/frontend/src/pages/developer/risk-control/components/data-table-toolbar.vue +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - diff --git a/frontend/src/pages/developer/risk-control/components/data-table.vue b/frontend/src/pages/developer/risk-control/components/data-table.vue deleted file mode 100644 index 1310d53..0000000 --- a/frontend/src/pages/developer/risk-control/components/data-table.vue +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - 批量启用 - - - 批量禁用 - - - 批量删除 - - - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/risk-control/create.vue b/frontend/src/pages/developer/risk-control/create.vue deleted file mode 100644 index 6a1c2c6..0000000 --- a/frontend/src/pages/developer/risk-control/create.vue +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - 规则配置 - - 配置风控规则的类型和封禁目标 - - - - 规则类型 - - - - - - {{ option.label }} - - - {{ option.description }} - - - - - - - - 生效范围 - - - - - - 全局封禁 - - - 对所有应用生效 - - - - - - - - 应用级封禁 - - - 仅对指定应用生效 - - - - - - - - - 目标应用 - - - - - - - - {{ app.name }} - - - - - {{ currentTypeInfo.scopeType === 'app' ? '用户封禁必须指定应用' : '选择要应用此封禁规则的应用' }} - - - - - - 封禁值 - - - - - {{ form.type === 'region' ? '选择要封禁的国家或地区' : '根据规则类型输入对应的封禁目标' }} - - - - - - 封禁原因 - - - - - - - 过期时间 - - - - 留空表示永久封禁 - - - - - - - - - - - - 规则预览 - - - - - - 生效范围 - - - {{ form.is_global ? '全局封禁' : '应用级封禁' }} - - - - 目标应用 - {{ applications.find(a => a.id === form.application_id)?.name || '-' }} - - - 规则类型 - - - {{ currentTypeInfo.label }} - - - - 封禁值 - - {{ form.type === 'region' && form.value ? getCountryDisplay(form.value) : (form.value || '-') }} - - - - 封禁原因 - {{ form.reason || '-' }} - - - 过期时间 - {{ form.expires_at || '永久' }} - - - - - - - - - - - - 添加规则 - - - 取消 - - - - - - - - - diff --git a/frontend/src/pages/developer/risk-control/data/schema.ts b/frontend/src/pages/developer/risk-control/data/schema.ts deleted file mode 100644 index 96e6be2..0000000 --- a/frontend/src/pages/developer/risk-control/data/schema.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { z } from 'zod' - -export const riskRuleTypeSchema = z.enum(['ip', 'device', 'user', 'region']) - -export const riskRuleStatusSchema = z.enum(['active', 'inactive']) - -export const riskRuleSchema = z.object({ - id: z.union([z.string(), z.number()]), - type: riskRuleTypeSchema, - value: z.string(), - reason: z.string().optional().nullable(), - status: riskRuleStatusSchema, - expires_at: z.string().optional().nullable(), - created_at: z.string(), -}) - -export type RiskRule = z.infer diff --git a/frontend/src/pages/developer/risk-control/index.vue b/frontend/src/pages/developer/risk-control/index.vue deleted file mode 100644 index c6d24b7..0000000 --- a/frontend/src/pages/developer/risk-control/index.vue +++ /dev/null @@ -1,289 +0,0 @@ - - - - - - - - 添加规则 - - - - - - - - - 生效中 - - - - - - {{ activeCount }} - - - - - - - - 已失效 - - - - - - {{ inactiveCount }} - - - - - - - - IP封禁 - - - - - - {{ ipCount }} - - - - - - - - 设备封禁 - - - - - - {{ deviceCount }} - - - - - - - - r.original.status === 'active').map((r: any) => r.original.id) || [], 'inactive')" - @batch-enable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'inactive').map((r: any) => r.original.id) || [], 'active')" - @batch-delete="confirmBatchDelete(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - > - - - - - - - - - - - - 删除风控规则 - - - 确定要删除该风控规则吗?此操作不可撤销。 - - - - - - 批量删除风控规则 - - - 确定要删除选中的 {{ batchDeleteIds.length }} 条规则吗?此操作不可撤销。 - - - - diff --git a/frontend/src/pages/developer/sessions/components/columns.ts b/frontend/src/pages/developer/sessions/components/columns.ts deleted file mode 100644 index b47c958..0000000 --- a/frontend/src/pages/developer/sessions/components/columns.ts +++ /dev/null @@ -1,154 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' - -import { LogOut, MoreHorizontal } from 'lucide-vue-next' -import { h } from 'vue' - -import type { Session } from '@/pages/admin/sessions/data/schema' - -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getColumns(actions: { - onForceOffline: (row: Session) => void -}): ColumnDef[] { - return [ - { - accessorKey: 'instance_id', - header: '实例标识', - cell: ({ row }) => { - const instanceId = row.getValue('instance_id') as string - if (!instanceId) - return '-' - const displayText = instanceId.length > 16 - ? `${instanceId.substring(0, 8)}...${instanceId.substring(instanceId.length - 4)}` - : instanceId - return h('span', { class: 'font-mono text-xs' }, displayText) - }, - }, - { - accessorKey: 'device_identifier', - header: '设备指纹', - cell: ({ row }) => { - const deviceId = row.getValue('device_identifier') as string - if (!deviceId) - return '-' - const displayText = deviceId.length > 16 - ? `${deviceId.substring(0, 8)}...${deviceId.substring(deviceId.length - 4)}` - : deviceId - return h('span', { class: 'font-mono text-xs' }, displayText) - }, - }, - { - accessorKey: 'device_name', - header: '设备名称', - cell: ({ row }) => { - const deviceName = row.getValue('device_name') as string - return deviceName || '-' - }, - }, - { - accessorKey: 'username', - header: '所属用户', - cell: ({ row }) => { - const username = row.getValue('username') as string - return username || '-' - }, - }, - { - accessorKey: 'app_name', - header: '所属应用', - cell: ({ row }) => { - const appName = row.getValue('app_name') as string - return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-' - }, - }, - { - accessorKey: 'last_heartbeat', - header: '最后心跳', - cell: ({ row }) => { - const lastHeartbeat = row.getValue('last_heartbeat') - if (!lastHeartbeat) - return '-' - try { - const date = new Date(lastHeartbeat as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - accessorKey: 'created_at', - header: '创建时间', - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, '操作'), - cell: ({ row }) => { - const session = row.original - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, '打开菜单'), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => [ - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onForceOffline(session) }, () => [ - h(LogOut, { class: 'mr-2 h-4 w-4' }), - '强制下线', - ]), - ], - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/sessions/components/data-table-toolbar.vue b/frontend/src/pages/developer/sessions/components/data-table-toolbar.vue deleted file mode 100644 index c228c80..0000000 --- a/frontend/src/pages/developer/sessions/components/data-table-toolbar.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - 重置 - - - - - - - 刷新 - - diff --git a/frontend/src/pages/developer/sessions/components/data-table.vue b/frontend/src/pages/developer/sessions/components/data-table.vue deleted file mode 100644 index 295083b..0000000 --- a/frontend/src/pages/developer/sessions/components/data-table.vue +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/sessions/data/schema.ts b/frontend/src/pages/developer/sessions/data/schema.ts deleted file mode 100644 index 3554d0a..0000000 --- a/frontend/src/pages/developer/sessions/data/schema.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { z } from 'zod' - -export const sessionSchema = z.object({ - id: z.union([z.string(), z.number()]), - device_id: z.union([z.string(), z.number()]), - user_id: z.union([z.string(), z.number()]), - application_id: z.union([z.string(), z.number()]), - instance_id: z.string(), - last_heartbeat: z.string().nullable().optional(), - created_at: z.string(), - updated_at: z.string().optional(), - device_identifier: z.string().optional(), - device_name: z.string().optional().nullable(), - username: z.string().optional(), - app_name: z.string().optional(), - is_online: z.boolean().optional(), -}) - -export type Session = z.infer diff --git a/frontend/src/pages/developer/sessions/index.vue b/frontend/src/pages/developer/sessions/index.vue deleted file mode 100644 index fc3198e..0000000 --- a/frontend/src/pages/developer/sessions/index.vue +++ /dev/null @@ -1,247 +0,0 @@ - - - - - - - - - - 在线实例数 - - - - - - {{ totalSessions }} - - - - - - - - 应用数量 - - - - - - {{ applications.length }} - - - - - - - - 在线设备数 - - - - - - {{ onlineDevices }} - - - - - - - - 在线用户数 - - - - - - {{ onlineUsers }} - - - - - - - - - - - - - - - - - - - - 强制下线 - - - 确定要将实例 "{{ forceOfflineTarget?.instance_id }}" 强制下线吗?该设备将需要重新登录。 - - - - diff --git a/frontend/src/pages/developer/tickets.vue b/frontend/src/pages/developer/tickets.vue deleted file mode 100644 index 403c428..0000000 --- a/frontend/src/pages/developer/tickets.vue +++ /dev/null @@ -1,334 +0,0 @@ - - - - - - - - {{ t('admin.tickets.createTicket') }} - - - - - - - - - {{ t('admin.tickets.openCount') }} - - - - - - {{ openCount }} - - - - - - - - {{ t('admin.tickets.pendingCount') }} - - - - - - {{ pendingCount }} - - - - - - - - {{ t('admin.tickets.resolvedCount') }} - - - - - - {{ resolvedCount }} - - - - - - - - {{ t('admin.tickets.totalTickets') }} - - - - - - {{ filteredTickets.length }} - - - - - - - - r.original.id) || [])" - > - - - - - - - - - - - - - - - {{ t('admin.tickets.deleteTicket') }} - - - {{ t('admin.tickets.deleteTicketConfirm', { title: deleteTarget?.title }) }} - - - - - - {{ t('admin.tickets.batchDeleteTickets') }} - - - {{ t('admin.tickets.batchDeleteConfirm', { count: batchDeleteIds.length }) }} - - - - diff --git a/frontend/src/pages/developer/tickets/[id].vue b/frontend/src/pages/developer/tickets/[id].vue deleted file mode 100644 index af0c9fb..0000000 --- a/frontend/src/pages/developer/tickets/[id].vue +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - {{ t('admin.tickets.create.config') }} - - {{ t('admin.tickets.create.configDesc') }} - - - - - {{ t('admin.tickets.create.ticketTypeLabel') }} * - - - - - - - - - - {{ t('admin.tickets.create.ticketTypeSystem') }} - - - - - - {{ t('admin.tickets.create.ticketTypeApplication') }} - - - - - - - - - {{ t('admin.tickets.create.applicationLabel') }} * - - - - - - - - {{ app.name }} - - - - - {{ t('admin.tickets.create.noApplications') }} - - - - - - {{ t('admin.tickets.create.titleLabel') }} * - - - - - - - {{ t('admin.tickets.create.priorityLabel') }} - - - - - - - - - - {{ t('admin.tickets.priority.low') }} - - - - - - {{ t('admin.tickets.priority.normal') }} - - - - - - {{ t('admin.tickets.priority.high') }} - - - - - - {{ t('admin.tickets.priority.urgent') }} - - - - - - - - - {{ t('admin.tickets.create.descriptionLabel') }} * - - - - {{ t('admin.tickets.create.descriptionHint') }} - - - - - - {{ t('admin.tickets.create.imagesLabel') }} - - - - - - - - - - - - - - - {{ t('admin.tickets.create.imagesHint') }} - - - - - - - - - - - - {{ t('admin.tickets.create.tips') }} - - - - - - - {{ t('admin.tickets.create.tip1') }} - - - - {{ t('admin.tickets.create.tip2') }} - - - - {{ t('admin.tickets.create.tip3') }} - - - - {{ t('admin.tickets.create.tip4') }} - - - - - - - - - - - - {{ t('admin.tickets.create.submit') }} - - - {{ t('admin.tickets.create.cancel') }} - - - - - - - - - diff --git a/frontend/src/pages/developer/tickets/components/columns.ts b/frontend/src/pages/developer/tickets/components/columns.ts deleted file mode 100644 index 3737d9f..0000000 --- a/frontend/src/pages/developer/tickets/components/columns.ts +++ /dev/null @@ -1,195 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' -import type { Composer } from 'vue-i18n' - -import { Check, Eye, MoreHorizontal, Play, Trash2, X } from 'lucide-vue-next' -import { h } from 'vue' - -import type { Ticket } from '@/pages/admin/tickets/data/schema' - -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getColumns(actions: { - onUpdateStatus: (row: Ticket, status: string) => void - onViewDetail: (row: Ticket) => void - onDelete: (row: Ticket) => void -}, t: Composer['t']): ColumnDef[] { - return [ - { - accessorKey: 'type', - header: () => t('admin.tickets.columns.ticketType'), - cell: ({ row }) => { - const ticketType = row.getValue('type') as string - const typeMap: Record = { - system: { text: t('admin.tickets.create.ticketTypeSystem'), variant: 'secondary' }, - application: { text: t('admin.tickets.create.ticketTypeApplication'), variant: 'default' }, - } - const { text, variant } = typeMap[ticketType] || { text: ticketType, variant: 'default' } - return h(Badge, { variant }, () => text) - }, - }, - { - accessorKey: 'title', - header: () => t('admin.tickets.columns.title'), - cell: ({ row }) => { - const title = row.getValue('title') as string - return h('div', { class: 'font-medium' }, title) - }, - }, - { - accessorKey: 'priority', - header: () => t('admin.tickets.columns.priority'), - cell: ({ row }) => { - const priority = row.getValue('priority') as string - const priorityMap: Record = { - low: { text: t('admin.tickets.priority.low'), variant: 'secondary' }, - normal: { text: t('admin.tickets.priority.normal'), variant: 'default' }, - high: { text: t('admin.tickets.priority.high'), variant: 'destructive' }, - urgent: { text: t('admin.tickets.priority.urgent'), variant: 'destructive' }, - } - const { text, variant } = priorityMap[priority] || { text: priority, variant: 'default' } - return h(Badge, { variant }, () => text) - }, - }, - { - accessorKey: 'status', - header: () => t('admin.tickets.columns.status'), - cell: ({ row }) => { - const status = row.getValue('status') as string - const statusMap: Record = { - open: { text: t('admin.tickets.status.open'), variant: 'destructive' }, - processing: { text: t('admin.tickets.status.processing'), variant: 'secondary' }, - resolved: { text: t('admin.tickets.status.resolved'), variant: 'default' }, - closed: { text: t('admin.tickets.status.closed'), variant: 'outline' }, - } - const { text, variant } = statusMap[status] || { text: status, variant: 'default' } - return h(Badge, { variant }, () => text) - }, - }, - { - accessorKey: 'app_name', - header: () => t('admin.tickets.columns.application'), - cell: ({ row }) => { - const appName = row.getValue('app_name') as string - return appName || '-' - }, - }, - { - accessorKey: 'user_name', - header: () => t('admin.tickets.columns.submitter'), - cell: ({ row }) => { - const userName = row.getValue('user_name') as string - return userName || '-' - }, - }, - { - accessorKey: 'created_at', - header: () => t('admin.tickets.columns.createdAt'), - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - accessorKey: 'updated_at', - header: () => t('admin.tickets.columns.updatedAt'), - cell: ({ row }) => { - const updatedAt = row.getValue('updated_at') - if (!updatedAt) - return '-' - try { - const date = new Date(updatedAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, t('common.actions')), - cell: ({ row }) => { - const ticket = row.original - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, t('common.openMenu')), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => [ - h(DropdownMenuItem, { onClick: () => actions.onViewDetail(ticket) }, () => [ - h(Eye, { class: 'mr-2 h-4 w-4' }), - t('admin.tickets.actions.viewDetail'), - ]), - ticket.status === 'open' - ? h(DropdownMenuItem, { onClick: () => actions.onUpdateStatus(ticket, 'processing') }, () => [ - h(Play, { class: 'mr-2 h-4 w-4' }), - t('admin.tickets.actions.startProcess'), - ]) - : null, - ticket.status === 'processing' - ? h(DropdownMenuItem, { onClick: () => actions.onUpdateStatus(ticket, 'resolved') }, () => [ - h(Check, { class: 'mr-2 h-4 w-4' }), - t('admin.tickets.actions.markResolved'), - ]) - : null, - ticket.status === 'resolved' - ? h(DropdownMenuItem, { onClick: () => actions.onUpdateStatus(ticket, 'closed') }, () => [ - h(X, { class: 'mr-2 h-4 w-4' }), - t('admin.tickets.actions.closeTicket'), - ]) - : null, - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(ticket) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - t('admin.tickets.actions.delete'), - ]), - ].filter(Boolean), - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/tickets/components/data-table-toolbar.vue b/frontend/src/pages/developer/tickets/components/data-table-toolbar.vue deleted file mode 100644 index b6ad3f0..0000000 --- a/frontend/src/pages/developer/tickets/components/data-table-toolbar.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - {{ t('common.reset') }} - - - - diff --git a/frontend/src/pages/developer/tickets/components/data-table.vue b/frontend/src/pages/developer/tickets/components/data-table.vue deleted file mode 100644 index a2470db..0000000 --- a/frontend/src/pages/developer/tickets/components/data-table.vue +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - {{ t('admin.tickets.batchDeleteBtn') }} - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/tickets/create.vue b/frontend/src/pages/developer/tickets/create.vue deleted file mode 100644 index af0c9fb..0000000 --- a/frontend/src/pages/developer/tickets/create.vue +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - - - - - - - {{ t('admin.tickets.create.config') }} - - {{ t('admin.tickets.create.configDesc') }} - - - - - {{ t('admin.tickets.create.ticketTypeLabel') }} * - - - - - - - - - - {{ t('admin.tickets.create.ticketTypeSystem') }} - - - - - - {{ t('admin.tickets.create.ticketTypeApplication') }} - - - - - - - - - {{ t('admin.tickets.create.applicationLabel') }} * - - - - - - - - {{ app.name }} - - - - - {{ t('admin.tickets.create.noApplications') }} - - - - - - {{ t('admin.tickets.create.titleLabel') }} * - - - - - - - {{ t('admin.tickets.create.priorityLabel') }} - - - - - - - - - - {{ t('admin.tickets.priority.low') }} - - - - - - {{ t('admin.tickets.priority.normal') }} - - - - - - {{ t('admin.tickets.priority.high') }} - - - - - - {{ t('admin.tickets.priority.urgent') }} - - - - - - - - - {{ t('admin.tickets.create.descriptionLabel') }} * - - - - {{ t('admin.tickets.create.descriptionHint') }} - - - - - - {{ t('admin.tickets.create.imagesLabel') }} - - - - - - - - - - - - - - - {{ t('admin.tickets.create.imagesHint') }} - - - - - - - - - - - - {{ t('admin.tickets.create.tips') }} - - - - - - - {{ t('admin.tickets.create.tip1') }} - - - - {{ t('admin.tickets.create.tip2') }} - - - - {{ t('admin.tickets.create.tip3') }} - - - - {{ t('admin.tickets.create.tip4') }} - - - - - - - - - - - - {{ t('admin.tickets.create.submit') }} - - - {{ t('admin.tickets.create.cancel') }} - - - - - - - - - diff --git a/frontend/src/pages/developer/tickets/data/schema.ts b/frontend/src/pages/developer/tickets/data/schema.ts deleted file mode 100644 index e7fc717..0000000 --- a/frontend/src/pages/developer/tickets/data/schema.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { z } from 'zod' - -export const ticketStatusSchema = z.enum(['open', 'processing', 'resolved', 'closed']) -export const ticketPrioritySchema = z.enum(['low', 'normal', 'high', 'urgent']) -export const ticketTypeSchema = z.enum(['system', 'application']) - -export const ticketSchema = z.object({ - id: z.union([z.string(), z.number()]), - title: z.string(), - content: z.string(), - status: ticketStatusSchema, - priority: ticketPrioritySchema, - type: ticketTypeSchema, - application_id: z.union([z.string(), z.number()]).optional().nullable(), - app_name: z.string().optional().nullable(), - user_id: z.union([z.string(), z.number()]), - user_name: z.string().optional().nullable(), - images: z.array(z.string()).optional().default([]), - created_at: z.string(), - updated_at: z.string().optional().nullable(), -}) - -export type Ticket = z.infer diff --git a/frontend/src/pages/developer/users/[id].vue b/frontend/src/pages/developer/users/[id].vue deleted file mode 100644 index f64b151..0000000 --- a/frontend/src/pages/developer/users/[id].vue +++ /dev/null @@ -1,385 +0,0 @@ - - - - - - - - {{ t('admin.users.addUser') }} - - - - - - - - - {{ t('admin.users.totalUsers') }} - - - - - - {{ filteredUsers.length }} - - - - - - - - {{ t('admin.users.online') }} - - - - - - {{ onlineCount }} - - - - - - - - {{ t('admin.users.offline') }} - - - - - - {{ offlineCount }} - - - - - - - - {{ t('admin.users.banned') }} - - - - - - {{ bannedCount }} - - - - - - - - r.original.status === 'banned').map((r: any) => r.original.id) || [], 'active')" - @batch-ban="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status !== 'banned').map((r: any) => r.original.id) || [], 'banned')" - @batch-delete="confirmBatchDelete(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - > - - - - - - - - - - - - - - - {{ t('admin.users.deleteUser') }} - - - {{ t('admin.users.deleteConfirm', { username: deleteTarget?.username }) }} - - - - - - {{ t('admin.users.batchDeleteUsers') }} - - - {{ t('admin.users.batchDeleteConfirm', { count: batchDeleteIds.length }) }} - - - - diff --git a/frontend/src/pages/developer/users/components/columns.ts b/frontend/src/pages/developer/users/components/columns.ts deleted file mode 100644 index 616607a..0000000 --- a/frontend/src/pages/developer/users/components/columns.ts +++ /dev/null @@ -1,231 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' -import type { Composer } from 'vue-i18n' - -import { Ban, Monitor, MoreHorizontal, Pencil, Trash2 } from 'lucide-vue-next' -import { h } from 'vue' - -import type { User } from '@/pages/admin/users/data/schema' - -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' -import { getOnlineStatusLabel, getOnlineStatusVariant } from '@/pages/admin/users/data/data' - -export function getColumns(actions: { - onEdit: (row: User) => void - onToggleStatus: (row: User) => void - onDelete: (row: User) => void - onManageDevices: (row: User) => void -}, t: Composer['t']): ColumnDef[] { - return [ - { - accessorKey: 'username', - header: () => t('admin.users.columns.username'), - cell: ({ row }) => { - const username = row.getValue('username') as string - return h('div', { class: 'flex items-center space-x-3' }, [ - h('div', { class: 'h-8 w-8 rounded bg-primary/10 flex items-center justify-center flex-shrink-0' }, [ - h('span', { class: 'text-primary font-bold text-sm' }, username.charAt(0).toUpperCase()), - ]), - h('div', {}, [ - h('p', { class: 'font-medium' }, username), - ]), - ]) - }, - }, - { - accessorKey: 'email', - header: () => t('admin.users.columns.email'), - cell: ({ row }) => { - const email = row.getValue('email') as string - return email || '-' - }, - }, - { - accessorKey: 'application.name', - header: () => t('admin.users.columns.application'), - cell: ({ row }) => { - const appName = row.original.application?.name - return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-' - }, - }, - { - accessorKey: 'online_status', - header: () => t('admin.users.columns.status'), - cell: ({ row }) => { - const status = (row.getValue('online_status') as string) || 'offline' - return h(Badge, { variant: getOnlineStatusVariant(status as any) }, () => getOnlineStatusLabel(status as any)) - }, - }, - { - accessorKey: 'device_count', - header: () => t('admin.users.columns.deviceCount'), - cell: ({ row }) => { - const count = row.getValue('device_count') as number - return h('span', { class: 'font-medium' }, count ?? 0) - }, - }, - { - accessorKey: 'balance', - header: () => t('admin.users.columns.balance'), - cell: ({ row }) => { - const balance = row.getValue('balance') as number - const billingType = row.original.application?.billing_type - - if (balance === -1) { - return h(Badge, { variant: 'default', class: 'bg-green-500 hover:bg-green-600' }, () => t('admin.users.expiry.permanent')) - } - - if (billingType === 'subscription') { - if (balance === null || balance === undefined) - return '-' - return h('span', { class: balance > 0 ? 'text-green-600 font-medium' : 'text-muted-foreground' }, balance.toFixed(2)) - } - - if (balance === null || balance === undefined) - return '-' - - return h('span', { class: balance > 0 ? 'text-green-600 font-medium' : 'text-muted-foreground' }, balance.toFixed(2)) - }, - }, - { - accessorKey: 'expiry_at', - header: () => t('admin.users.columns.expiryAt'), - cell: ({ row }) => { - const expiryAt = row.getValue('expiry_at') as string - const billingType = row.original.application?.billing_type - - if (billingType !== 'subscription') { - return h('span', { class: 'text-muted-foreground' }, '-') - } - - if (!expiryAt) { - return h('span', { class: 'text-muted-foreground' }, '-') - } - - try { - const date = new Date(expiryAt) - if (Number.isNaN(date.getTime())) - return '-' - - const now = new Date() - if (date < now) { - return h(Badge, { variant: 'destructive' }, () => t('admin.users.expiry.expired')) - } - - return h('span', {}, date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - })) - } - catch { - return '-' - } - }, - }, - { - accessorKey: 'last_login_at', - header: () => t('admin.users.columns.lastLoginAt'), - cell: ({ row }) => { - const lastLoginAt = row.getValue('last_login_at') - if (!lastLoginAt) - return '-' - try { - const date = new Date(lastLoginAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - accessorKey: 'created_at', - header: () => t('admin.users.columns.createdAt'), - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, t('common.actions')), - cell: ({ row }) => { - const user = row.original - const isBanned = user.status === 'banned' - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [ - h(MoreHorizontal, { class: 'h-4 w-4' }), - h('span', { class: 'sr-only' }, t('common.openMenu')), - ])), - h( - DropdownMenuContent, - { align: 'end' }, - () => [ - h(DropdownMenuItem, { onClick: () => actions.onEdit(user) }, () => [ - h(Pencil, { class: 'mr-2 h-4 w-4' }), - t('common.edit'), - ]), - h(DropdownMenuItem, { onClick: () => actions.onManageDevices(user) }, () => [ - h(Monitor, { class: 'mr-2 h-4 w-4' }), - t('admin.users.devices'), - ]), - h(DropdownMenuItem, { onClick: () => actions.onToggleStatus(user) }, () => [ - h(Ban, { class: 'mr-2 h-4 w-4' }), - isBanned ? t('common.unban') : t('common.ban'), - ]), - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(user) }, () => [ - h(Trash2, { class: 'mr-2 h-4 w-4' }), - t('common.delete'), - ]), - ], - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/developer/users/components/data-table-toolbar.vue b/frontend/src/pages/developer/users/components/data-table-toolbar.vue deleted file mode 100644 index 0b74e33..0000000 --- a/frontend/src/pages/developer/users/components/data-table-toolbar.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - {{ t('admin.users.reset') }} - - - - diff --git a/frontend/src/pages/developer/users/components/data-table.vue b/frontend/src/pages/developer/users/components/data-table.vue deleted file mode 100644 index f28e845..0000000 --- a/frontend/src/pages/developer/users/components/data-table.vue +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - {{ t('admin.users.batchUnbanBtn') }} - - - {{ t('admin.users.batchBanBtn') }} - - - {{ t('admin.users.batchDeleteBtn') }} - - - - - - - - - - - - - diff --git a/frontend/src/pages/developer/users/create.vue b/frontend/src/pages/developer/users/create.vue deleted file mode 100644 index d8bf566..0000000 --- a/frontend/src/pages/developer/users/create.vue +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - - - - - - {{ t('admin.users.create.basicInfo') }} - - {{ t('admin.users.create.basicInfoDesc') }} - - - - - {{ t('admin.users.create.application') }} - - - - - - - - {{ app.name }} - - - - - - - - {{ t('admin.users.create.username') }} - - - - - - - {{ t('admin.users.create.email') }} - - - - - - - {{ t('admin.users.create.password') }} - - - - - - - - - - - - - {{ t('admin.users.create.preview') }} - - - - - - {{ t('admin.users.create.app') }} - {{ selectedApplication?.name || '-' }} - - - {{ t('admin.users.create.usernameLabel') }} - {{ form.username || '-' }} - - - {{ t('admin.users.create.emailLabel') }} - {{ form.email || '-' }} - - - - - - - - - - - - {{ t('admin.users.create.submit') }} - - - {{ t('admin.users.create.cancel') }} - - - - - - - - - diff --git a/frontend/src/pages/developer/users/data/data.ts b/frontend/src/pages/developer/users/data/data.ts deleted file mode 100644 index 65bf22f..0000000 --- a/frontend/src/pages/developer/users/data/data.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Ban, CheckCircle2, Clock } from 'lucide-vue-next' -import { h } from 'vue' - -import type { FacetedFilterOption } from '@/components/data-table/types' - -import type { OnlineStatus } from './schema' - -export const userStatuses: FacetedFilterOption[] = [ - { - label: '正常', - value: 'active', - icon: h(CheckCircle2), - }, - { - label: '封禁', - value: 'banned', - icon: h(Ban), - }, -] - -export const onlineStatuses: (FacetedFilterOption & { style: string })[] = [ - { - label: '在线', - value: 'online', - icon: h(CheckCircle2), - style: 'bg-green-500/10 text-green-500', - }, - { - label: '离线', - value: 'offline', - icon: h(Clock), - style: 'bg-gray-500/10 text-gray-500', - }, - { - label: '封禁', - value: 'banned', - icon: h(Ban), - style: 'bg-red-500/10 text-red-500', - }, -] - -export function getOnlineStatusLabel(status: OnlineStatus): string { - const labels: Record = { - online: '在线', - offline: '离线', - banned: '封禁', - } - return labels[status] ?? status -} - -export function getOnlineStatusVariant(status: OnlineStatus): 'default' | 'secondary' | 'destructive' { - const variants: Record = { - online: 'default', - offline: 'secondary', - banned: 'destructive', - } - return variants[status] ?? 'default' -} diff --git a/frontend/src/pages/developer/users/data/schema.ts b/frontend/src/pages/developer/users/data/schema.ts deleted file mode 100644 index da93c35..0000000 --- a/frontend/src/pages/developer/users/data/schema.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { z } from 'zod' - -export const userStatusSchema = z.enum(['active', 'banned']) - -export const onlineStatusSchema = z.enum(['online', 'offline', 'banned']) - -export const userSchema = z.object({ - id: z.union([z.string(), z.number()]), - username: z.string(), - email: z.string().optional().nullable(), - device_id: z.string().optional().nullable(), - avatar: z.string().optional().nullable(), - status: userStatusSchema, - online_status: onlineStatusSchema.optional(), - device_count: z.number().default(0), - application_id: z.union([z.string(), z.number()]).nullable(), - balance: z.number().nullable(), - expiry_at: z.string().optional().nullable(), - last_login_at: z.string().optional().nullable(), - last_heartbeat_at: z.string().optional().nullable(), - is_trial_user: z.boolean().default(false), - trial_start_at: z.string().optional().nullable(), - trial_end_at: z.string().optional().nullable(), - created_at: z.string(), - updated_at: z.string().optional(), - application: z.object({ - id: z.union([z.string(), z.number()]), - name: z.string(), - billing_type: z.string().optional().nullable(), - }).optional().nullable(), -}) - -export type User = z.infer -export type UserStatus = z.infer -export type OnlineStatus = z.infer diff --git a/frontend/src/pages/developer/users/index.vue b/frontend/src/pages/developer/users/index.vue deleted file mode 100644 index f64b151..0000000 --- a/frontend/src/pages/developer/users/index.vue +++ /dev/null @@ -1,385 +0,0 @@ - - - - - - - - {{ t('admin.users.addUser') }} - - - - - - - - - {{ t('admin.users.totalUsers') }} - - - - - - {{ filteredUsers.length }} - - - - - - - - {{ t('admin.users.online') }} - - - - - - {{ onlineCount }} - - - - - - - - {{ t('admin.users.offline') }} - - - - - - {{ offlineCount }} - - - - - - - - {{ t('admin.users.banned') }} - - - - - - {{ bannedCount }} - - - - - - - - r.original.status === 'banned').map((r: any) => r.original.id) || [], 'active')" - @batch-ban="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status !== 'banned').map((r: any) => r.original.id) || [], 'banned')" - @batch-delete="confirmBatchDelete(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - > - - - - - - - - - - - - - - - {{ t('admin.users.deleteUser') }} - - - {{ t('admin.users.deleteConfirm', { username: deleteTarget?.username }) }} - - - - - - {{ t('admin.users.batchDeleteUsers') }} - - - {{ t('admin.users.batchDeleteConfirm', { count: batchDeleteIds.length }) }} - - - - diff --git a/frontend/src/pages/developer/versions/[id].vue b/frontend/src/pages/developer/versions/[id].vue deleted file mode 100644 index 1f3140e..0000000 --- a/frontend/src/pages/developer/versions/[id].vue +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - - - {{ t('admin.versions.create') }} - - - - - - - - - {{ t('admin.versions.totalVersions') }} - - - - - - {{ filteredVersions.length }} - - - - - - - - {{ t('admin.versions.forcedCount') }} - - - - - - {{ forcedCount }} - - - - - - - - {{ t('admin.versions.totalSize') }} - - - - - - {{ formatFileSize(totalSize) }} - - - - - - - - {{ t('admin.versions.appCount') }} - - - - - - {{ new Set(filteredVersions.map(v => v.application_id)).size }} - - - - - - - - r.original.id) || [])" - @batch-export="handleBatchExport(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - > - - - - - - - - - - - - - {{ t('admin.versions.deleteVersion') }} - - - {{ t('admin.versions.deleteConfirm', { version: deleteTarget?.version }) }} - - - - - - {{ t('admin.versions.batchDelete') }} - - - {{ t('admin.versions.batchDeleteConfirm', { count: batchDeleteIds.length }) }} - - - - diff --git a/frontend/src/pages/developer/versions/components/columns.ts b/frontend/src/pages/developer/versions/components/columns.ts deleted file mode 100644 index b669278..0000000 --- a/frontend/src/pages/developer/versions/components/columns.ts +++ /dev/null @@ -1,130 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' - -import { Icon } from '@iconify/vue' -import { h } from 'vue' - -import type { Version } from '@/pages/admin/versions/data/schema' - -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' - -interface ColumnOptions { - onDownload: (row: Version) => void - onEdit: (row: Version) => void - onDelete: (row: Version) => void -} - -function formatFileSize(bytes: number): string { - if (bytes === 0) - return '0 B' - const k = 1024 - const sizes = ['B', 'KB', 'MB', 'GB'] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}` -} - -export function getColumns(options: ColumnOptions, t: (key: string) => string): ColumnDef[] { - return [ - { - accessorKey: 'version', - header: () => t('admin.versions.columns.version'), - cell: ({ row }) => { - return h('code', { class: 'text-sm bg-muted px-2 py-1 rounded' }, `v${row.original.version}`) - }, - }, - { - accessorKey: 'application_name', - header: () => t('admin.versions.columns.application'), - cell: ({ row }) => { - const appName = row.original.application_name - return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-' - }, - }, - { - accessorKey: 'description', - header: () => t('admin.versions.columns.description'), - cell: ({ row }) => { - return h('div', { class: 'max-w-xs truncate' }, row.original.description || '-') - }, - }, - { - accessorKey: 'file_size', - header: () => t('admin.versions.columns.fileSize'), - cell: ({ row }) => { - return formatFileSize(row.original.file_size) - }, - }, - { - accessorKey: 'update_strategy', - header: () => t('admin.versions.columns.strategy'), - cell: ({ row }) => { - const strategyClasses: Record = { - optional: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400', - forced: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400', - } - const strategyLabels: Record = { - optional: t('admin.versions.strategies.optional'), - forced: t('admin.versions.strategies.forced'), - } - return h(Badge, { class: strategyClasses[row.original.update_strategy] }, () => strategyLabels[row.original.update_strategy]) - }, - }, - { - accessorKey: 'update_method', - header: () => t('admin.versions.columns.method'), - cell: ({ row }) => { - const methodClasses: Record = { - manual: 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400', - auto: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400', - } - const methodLabels: Record = { - manual: t('admin.versions.methods.manual'), - auto: t('admin.versions.methods.auto'), - } - return h(Badge, { class: methodClasses[row.original.update_method] }, () => methodLabels[row.original.update_method]) - }, - }, - { - accessorKey: 'created_at', - header: () => t('admin.versions.columns.createdAt'), - cell: ({ row }) => { - return h('span', { class: 'text-sm text-muted-foreground' }, new Date(row.original.created_at).toLocaleString('zh-CN')) - }, - }, - { - id: 'actions', - header: () => t('admin.versions.columns.actions'), - cell: ({ row }) => { - const buttons = [] - if (row.original.file_path) { - buttons.push( - h(Button, { - variant: 'ghost', - size: 'sm', - onClick: () => options.onDownload(row.original), - }, () => [ - h(Icon, { icon: 'lucide:download', class: 'h-4 w-4' }), - ]), - ) - } - buttons.push( - h(Button, { - variant: 'ghost', - size: 'sm', - onClick: () => options.onEdit(row.original), - }, () => [ - h(Icon, { icon: 'lucide:edit', class: 'h-4 w-4' }), - ]), - h(Button, { - variant: 'ghost', - size: 'sm', - onClick: () => options.onDelete(row.original), - }, () => [ - h(Icon, { icon: 'lucide:trash-2', class: 'h-4 w-4 text-destructive' }), - ]), - ) - return h('div', { class: 'flex items-center justify-end gap-1' }, buttons) - }, - }, - ] -} diff --git a/frontend/src/pages/developer/versions/components/data-table-toolbar.vue b/frontend/src/pages/developer/versions/components/data-table-toolbar.vue deleted file mode 100644 index d185d36..0000000 --- a/frontend/src/pages/developer/versions/components/data-table-toolbar.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - {{ t('admin.versions.reset') }} - - - - diff --git a/frontend/src/pages/developer/versions/components/data-table.vue b/frontend/src/pages/developer/versions/components/data-table.vue deleted file mode 100644 index 362c1d0..0000000 --- a/frontend/src/pages/developer/versions/components/data-table.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - {{ t('admin.versions.batchExportBtn') }} - - - {{ t('admin.versions.batchDeleteBtn') }} - - - - - - - - - - {{ t('admin.versions.exportBtn') }} - - - - - - - - diff --git a/frontend/src/pages/developer/versions/create.vue b/frontend/src/pages/developer/versions/create.vue deleted file mode 100644 index 7697559..0000000 --- a/frontend/src/pages/developer/versions/create.vue +++ /dev/null @@ -1,530 +0,0 @@ - - - - - - - - - - - - 版本配置 - - 填写版本的基本信息和上传更新文件 - - - - 选择应用 * - - - - - - - {{ app.name }} - - - - - - - - 版本号 * - - - - - - - 最低版本 - - - - - - - 版本描述 - - - - - - 更新文件 (ZIP格式) - - - - - - 拖拽ZIP文件到此处或点击上传 - - - - 选择文件 - - - - - - - - - {{ formData.file.name }} - - - {{ fileSize }} - - - - - - - - 上传解析 - - - - - - - - - - 文件已上传并解析 - - - 文件哈希: {{ uploadedData.file_hash.substring(0, 16) }}... - 包含 {{ uploadedData.files.length }} 个文件 - - - - - - - - 文件列表 - - - - - - 文件名 - - - 路径 - - - 大小 - - - 类型 - - - - - - - {{ file.file_name }} - - - {{ file.file_path }} - - - {{ formatFileSize(file.file_size) }} - - - - {{ file.file_type }} - - - - - - - - - - 入口文件 - - 选择更新完成后要启动的主程序文件(可选) - - - - - - - - {{ file.file_name }} ({{ formatFileSize(file.file_size) }}) - - - - - - - - - 强制更新 - - - 启用后用户必须更新到此版本才能继续使用 - - - - - - - - - 自动更新 - - - 启用后应用将自动下载并安装更新 - - - - - - - - 更新日志 - - - - - - - - - - - - - 预览 - - - - - - 所属应用 - {{ selectedApplication?.name || '-' }} - - - 版本号 - {{ formData.version || '-' }} - - - 最低版本 - {{ formData.min_version || '-' }} - - - 版本描述 - {{ formData.description || '-' }} - - - 更新文件 - {{ formData.file ? formData.file.name : '未上传' }} - - - 文件数量 - {{ uploadedData ? uploadedData.files.length : '-' }} - - - 入口文件 - {{ formData.entry_file || '-' }} - - - 强制更新 - {{ formData.update_strategy === 'forced' ? '是' : '否' }} - - - 自动更新 - {{ formData.update_method === 'auto' ? '是' : '否' }} - - - - - - 更新日志预览 - - - - {{ formData.changelog || '暂无更新日志' }} - - - - - - - - - - - - - 发布版本 - - - 取消 - - - - - - - - - diff --git a/frontend/src/pages/developer/versions/data/schema.ts b/frontend/src/pages/developer/versions/data/schema.ts deleted file mode 100644 index b19eb45..0000000 --- a/frontend/src/pages/developer/versions/data/schema.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { z } from 'zod' - -export const updateStrategySchema = z.enum(['optional', 'forced']) -export const updateMethodSchema = z.enum(['manual', 'auto']) - -export const versionSchema = z.object({ - id: z.number(), - application_id: z.number(), - application_name: z.string(), - version: z.string(), - description: z.string(), - file_path: z.string(), - file_size: z.number(), - force_update: z.boolean(), - update_strategy: updateStrategySchema, - update_method: updateMethodSchema, - changelog: z.string(), - created_at: z.string(), - updated_at: z.string(), -}) - -export type Version = z.infer diff --git a/frontend/src/pages/developer/versions/index.vue b/frontend/src/pages/developer/versions/index.vue deleted file mode 100644 index 1f3140e..0000000 --- a/frontend/src/pages/developer/versions/index.vue +++ /dev/null @@ -1,359 +0,0 @@ - - - - - - - - {{ t('admin.versions.create') }} - - - - - - - - - {{ t('admin.versions.totalVersions') }} - - - - - - {{ filteredVersions.length }} - - - - - - - - {{ t('admin.versions.forcedCount') }} - - - - - - {{ forcedCount }} - - - - - - - - {{ t('admin.versions.totalSize') }} - - - - - - {{ formatFileSize(totalSize) }} - - - - - - - - {{ t('admin.versions.appCount') }} - - - - - - {{ new Set(filteredVersions.map(v => v.application_id)).size }} - - - - - - - - r.original.id) || [])" - @batch-export="handleBatchExport(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])" - > - - - - - - - - - - - - - {{ t('admin.versions.deleteVersion') }} - - - {{ t('admin.versions.deleteConfirm', { version: deleteTarget?.version }) }} - - - - - - {{ t('admin.versions.batchDelete') }} - - - {{ t('admin.versions.batchDeleteConfirm', { count: batchDeleteIds.length }) }} - - - - diff --git a/frontend/src/router/guard/index.ts b/frontend/src/router/guard/index.ts index 85e38cb..9e702c5 100644 --- a/frontend/src/router/guard/index.ts +++ b/frontend/src/router/guard/index.ts @@ -15,7 +15,7 @@ export function createRouterGuard(router: Router) { } } - const requiresAuth = to.path.startsWith('/developer') || to.path.startsWith('/agent') + const requiresAuth = to.path.startsWith('/admin') || to.path.startsWith('/agent') const isAuthPage = to.path === '/login' || to.path === '/register' if (requiresAuth && !token) { @@ -30,7 +30,7 @@ export function createRouterGuard(router: Router) { if (user.role === 'agent' || user.parent_agent_id) { return '/agent' } - return '/developer' + return '/admin' } }) } diff --git a/frontend/src/types/auto-import-components.d.ts b/frontend/src/types/auto-import-components.d.ts index 5336b1e..bcb5dbe 100644 --- a/frontend/src/types/auto-import-components.d.ts +++ b/frontend/src/types/auto-import-components.d.ts @@ -12,6 +12,8 @@ export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { + AdminSidebar: typeof import('./../components/admin-sidebar/index.vue')['default'] + AdminSidebarNavFooter: typeof import('./../components/admin-sidebar/nav-footer.vue')['default'] AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default'] AppSidebarNavFooter: typeof import('./../components/app-sidebar/nav-footer.vue')['default'] AppSidebarNavTeam: typeof import('./../components/app-sidebar/nav-team.vue')['default'] @@ -40,8 +42,6 @@ declare module 'vue' { DataTableTableLoading: typeof import('./../components/data-table/table-loading.vue')['default'] DataTableTablePagination: typeof import('./../components/data-table/table-pagination.vue')['default'] DataTableViewOptions: typeof import('./../components/data-table/view-options.vue')['default'] - DeveloperSidebar: typeof import('./../components/developer-sidebar/index.vue')['default'] - DeveloperSidebarNavFooter: typeof import('./../components/developer-sidebar/nav-footer.vue')['default'] GlobalLayoutBasicHeader: typeof import('./../components/global-layout/basic-header.vue')['default'] GlobalLayoutBasicPage: typeof import('./../components/global-layout/basic-page.vue')['default'] GlobalLayoutTwoCol: typeof import('./../components/global-layout/two-col.vue')['default'] @@ -415,6 +415,8 @@ declare module 'vue' { // For TSX support declare global { + const AdminSidebar: typeof import('./../components/admin-sidebar/index.vue')['default'] + const AdminSidebarNavFooter: typeof import('./../components/admin-sidebar/nav-footer.vue')['default'] const AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default'] const AppSidebarNavFooter: typeof import('./../components/app-sidebar/nav-footer.vue')['default'] const AppSidebarNavTeam: typeof import('./../components/app-sidebar/nav-team.vue')['default'] @@ -443,8 +445,6 @@ declare global { const DataTableTableLoading: typeof import('./../components/data-table/table-loading.vue')['default'] const DataTableTablePagination: typeof import('./../components/data-table/table-pagination.vue')['default'] const DataTableViewOptions: typeof import('./../components/data-table/view-options.vue')['default'] - const DeveloperSidebar: typeof import('./../components/developer-sidebar/index.vue')['default'] - const DeveloperSidebarNavFooter: typeof import('./../components/developer-sidebar/nav-footer.vue')['default'] const GlobalLayoutBasicHeader: typeof import('./../components/global-layout/basic-header.vue')['default'] const GlobalLayoutBasicPage: typeof import('./../components/global-layout/basic-page.vue')['default'] const GlobalLayoutTwoCol: typeof import('./../components/global-layout/two-col.vue')['default'] diff --git a/frontend/src/types/route-map.d.ts b/frontend/src/types/route-map.d.ts index ef2c0dd..4443d94 100644 --- a/frontend/src/types/route-map.d.ts +++ b/frontend/src/types/route-map.d.ts @@ -484,383 +484,6 @@ declare module 'vue-router/auto-routes' { Record, | never >, - '/developer/': RouteRecordInfo< - '/developer/', - '/developer', - Record, - Record, - | never - >, - '/developer/agent-apps/': RouteRecordInfo< - '/developer/agent-apps/', - '/developer/agent-apps', - Record, - Record, - | never - >, - '/developer/agent-apps/[id]/card-types': RouteRecordInfo< - '/developer/agent-apps/[id]/card-types', - '/developer/agent-apps/:id/card-types', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/agent-apps/[id]/edit': RouteRecordInfo< - '/developer/agent-apps/[id]/edit', - '/developer/agent-apps/:id/edit', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/agent-apps/[id]/recharge': RouteRecordInfo< - '/developer/agent-apps/[id]/recharge', - '/developer/agent-apps/:id/recharge', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/agent-apps/invite': RouteRecordInfo< - '/developer/agent-apps/invite', - '/developer/agent-apps/invite', - Record, - Record, - | never - >, - '/developer/agent-apps/request': RouteRecordInfo< - '/developer/agent-apps/request', - '/developer/agent-apps/request', - Record, - Record, - | never - >, - '/developer/agents/': RouteRecordInfo< - '/developer/agents/', - '/developer/agents', - Record, - Record, - | never - >, - '/developer/agents/[id]': RouteRecordInfo< - '/developer/agents/[id]', - '/developer/agents/:id', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/agents/create': RouteRecordInfo< - '/developer/agents/create', - '/developer/agents/create', - Record, - Record, - | never - >, - '/developer/announcements/': RouteRecordInfo< - '/developer/announcements/', - '/developer/announcements', - Record, - Record, - | never - >, - '/developer/announcements/[id]': RouteRecordInfo< - '/developer/announcements/[id]', - '/developer/announcements/:id', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/announcements/create': RouteRecordInfo< - '/developer/announcements/create', - '/developer/announcements/create', - Record, - Record, - | never - >, - '/developer/applications': RouteRecordInfo< - '/developer/applications', - '/developer/applications', - Record, - Record, - | '/developer/applications/[id]/email' - | '/developer/applications/[id]/security' - | '/developer/applications/[id]/settings' - | '/developer/applications/create' - >, - '/developer/applications/[id]/email': RouteRecordInfo< - '/developer/applications/[id]/email', - '/developer/applications/:id/email', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/applications/[id]/security': RouteRecordInfo< - '/developer/applications/[id]/security', - '/developer/applications/:id/security', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/applications/[id]/settings': RouteRecordInfo< - '/developer/applications/[id]/settings', - '/developer/applications/:id/settings', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/applications/create': RouteRecordInfo< - '/developer/applications/create', - '/developer/applications/create', - Record, - Record, - | never - >, - '/developer/card-types/': RouteRecordInfo< - '/developer/card-types/', - '/developer/card-types', - Record, - Record, - | never - >, - '/developer/card-types/[id]': RouteRecordInfo< - '/developer/card-types/[id]', - '/developer/card-types/:id', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/card-types/create': RouteRecordInfo< - '/developer/card-types/create', - '/developer/card-types/create', - Record, - Record, - | never - >, - '/developer/cards/': RouteRecordInfo< - '/developer/cards/', - '/developer/cards', - Record, - Record, - | never - >, - '/developer/cards/create': RouteRecordInfo< - '/developer/cards/create', - '/developer/cards/create', - Record, - Record, - | never - >, - '/developer/cloud-constants/': RouteRecordInfo< - '/developer/cloud-constants/', - '/developer/cloud-constants', - Record, - Record, - | never - >, - '/developer/cloud-constants/[id]': RouteRecordInfo< - '/developer/cloud-constants/[id]', - '/developer/cloud-constants/:id', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/cloud-constants/create': RouteRecordInfo< - '/developer/cloud-constants/create', - '/developer/cloud-constants/create', - Record, - Record, - | never - >, - '/developer/cloud-variables/': RouteRecordInfo< - '/developer/cloud-variables/', - '/developer/cloud-variables', - Record, - Record, - | never - >, - '/developer/cloud-variables/[id]': RouteRecordInfo< - '/developer/cloud-variables/[id]', - '/developer/cloud-variables/:id', - { id: ParamValue }, - { id: ParamValue }, - | '/developer/cloud-variables/[id]/records' - >, - '/developer/cloud-variables/[id]/records': RouteRecordInfo< - '/developer/cloud-variables/[id]/records', - '/developer/cloud-variables/:id/records', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/cloud-variables/create': RouteRecordInfo< - '/developer/cloud-variables/create', - '/developer/cloud-variables/create', - Record, - Record, - | never - >, - '/developer/devices/': RouteRecordInfo< - '/developer/devices/', - '/developer/devices', - Record, - Record, - | never - >, - '/developer/dynamic-code': RouteRecordInfo< - '/developer/dynamic-code', - '/developer/dynamic-code', - Record, - Record, - | '/developer/dynamic-code/' - | '/developer/dynamic-code/[id]' - | '/developer/dynamic-code/create' - >, - '/developer/dynamic-code/': RouteRecordInfo< - '/developer/dynamic-code/', - '/developer/dynamic-code', - Record, - Record, - | never - >, - '/developer/dynamic-code/[id]': RouteRecordInfo< - '/developer/dynamic-code/[id]', - '/developer/dynamic-code/:id', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/dynamic-code/create': RouteRecordInfo< - '/developer/dynamic-code/create', - '/developer/dynamic-code/create', - Record, - Record, - | never - >, - '/developer/extension/': RouteRecordInfo< - '/developer/extension/', - '/developer/extension', - Record, - Record, - | never - >, - '/developer/extension/api-keys/create': RouteRecordInfo< - '/developer/extension/api-keys/create', - '/developer/extension/api-keys/create', - Record, - Record, - | never - >, - '/developer/extension/webhooks/create': RouteRecordInfo< - '/developer/extension/webhooks/create', - '/developer/extension/webhooks/create', - Record, - Record, - | never - >, - '/developer/finance/': RouteRecordInfo< - '/developer/finance/', - '/developer/finance', - Record, - Record, - | never - >, - '/developer/logs': RouteRecordInfo< - '/developer/logs', - '/developer/logs', - Record, - Record, - | never - >, - '/developer/risk-control/': RouteRecordInfo< - '/developer/risk-control/', - '/developer/risk-control', - Record, - Record, - | never - >, - '/developer/risk-control/[id]': RouteRecordInfo< - '/developer/risk-control/[id]', - '/developer/risk-control/:id', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/risk-control/create': RouteRecordInfo< - '/developer/risk-control/create', - '/developer/risk-control/create', - Record, - Record, - | never - >, - '/developer/sessions/': RouteRecordInfo< - '/developer/sessions/', - '/developer/sessions', - Record, - Record, - | never - >, - '/developer/tickets': RouteRecordInfo< - '/developer/tickets', - '/developer/tickets', - Record, - Record, - | '/developer/tickets/[id]' - | '/developer/tickets/create' - >, - '/developer/tickets/[id]': RouteRecordInfo< - '/developer/tickets/[id]', - '/developer/tickets/:id', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/tickets/create': RouteRecordInfo< - '/developer/tickets/create', - '/developer/tickets/create', - Record, - Record, - | never - >, - '/developer/users/': RouteRecordInfo< - '/developer/users/', - '/developer/users', - Record, - Record, - | never - >, - '/developer/users/[id]': RouteRecordInfo< - '/developer/users/[id]', - '/developer/users/:id', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/users/create': RouteRecordInfo< - '/developer/users/create', - '/developer/users/create', - Record, - Record, - | never - >, - '/developer/versions/': RouteRecordInfo< - '/developer/versions/', - '/developer/versions', - Record, - Record, - | never - >, - '/developer/versions/[id]': RouteRecordInfo< - '/developer/versions/[id]', - '/developer/versions/:id', - { id: ParamValue }, - { id: ParamValue }, - | never - >, - '/developer/versions/create': RouteRecordInfo< - '/developer/versions/create', - '/developer/versions/create', - Record, - Record, - | never - >, '/errors': RouteRecordInfo< '/errors', '/errors', @@ -1330,334 +953,6 @@ declare module 'vue-router/auto-routes' { views: | never } - 'src/pages/developer/index.vue': { - routes: - | '/developer/' - views: - | never - } - 'src/pages/developer/agent-apps/index.vue': { - routes: - | '/developer/agent-apps/' - views: - | never - } - 'src/pages/developer/agent-apps/[id]/card-types.vue': { - routes: - | '/developer/agent-apps/[id]/card-types' - views: - | never - } - 'src/pages/developer/agent-apps/[id]/edit.vue': { - routes: - | '/developer/agent-apps/[id]/edit' - views: - | never - } - 'src/pages/developer/agent-apps/[id]/recharge.vue': { - routes: - | '/developer/agent-apps/[id]/recharge' - views: - | never - } - 'src/pages/developer/agent-apps/invite.vue': { - routes: - | '/developer/agent-apps/invite' - views: - | never - } - 'src/pages/developer/agent-apps/request.vue': { - routes: - | '/developer/agent-apps/request' - views: - | never - } - 'src/pages/developer/agents/index.vue': { - routes: - | '/developer/agents/' - views: - | never - } - 'src/pages/developer/agents/[id].vue': { - routes: - | '/developer/agents/[id]' - views: - | never - } - 'src/pages/developer/agents/create.vue': { - routes: - | '/developer/agents/create' - views: - | never - } - 'src/pages/developer/announcements/index.vue': { - routes: - | '/developer/announcements/' - views: - | never - } - 'src/pages/developer/announcements/[id].vue': { - routes: - | '/developer/announcements/[id]' - views: - | never - } - 'src/pages/developer/announcements/create.vue': { - routes: - | '/developer/announcements/create' - views: - | never - } - 'src/pages/developer/applications.vue': { - routes: - | '/developer/applications' - | '/developer/applications/[id]/email' - | '/developer/applications/[id]/security' - | '/developer/applications/[id]/settings' - | '/developer/applications/create' - views: - | 'default' - } - 'src/pages/developer/applications/[id]/email.vue': { - routes: - | '/developer/applications/[id]/email' - views: - | never - } - 'src/pages/developer/applications/[id]/security.vue': { - routes: - | '/developer/applications/[id]/security' - views: - | never - } - 'src/pages/developer/applications/[id]/settings.vue': { - routes: - | '/developer/applications/[id]/settings' - views: - | never - } - 'src/pages/developer/applications/create.vue': { - routes: - | '/developer/applications/create' - views: - | never - } - 'src/pages/developer/card-types/index.vue': { - routes: - | '/developer/card-types/' - views: - | never - } - 'src/pages/developer/card-types/[id].vue': { - routes: - | '/developer/card-types/[id]' - views: - | never - } - 'src/pages/developer/card-types/create.vue': { - routes: - | '/developer/card-types/create' - views: - | never - } - 'src/pages/developer/cards/index.vue': { - routes: - | '/developer/cards/' - views: - | never - } - 'src/pages/developer/cards/create.vue': { - routes: - | '/developer/cards/create' - views: - | never - } - 'src/pages/developer/cloud-constants/index.vue': { - routes: - | '/developer/cloud-constants/' - views: - | never - } - 'src/pages/developer/cloud-constants/[id].vue': { - routes: - | '/developer/cloud-constants/[id]' - views: - | never - } - 'src/pages/developer/cloud-constants/create.vue': { - routes: - | '/developer/cloud-constants/create' - views: - | never - } - 'src/pages/developer/cloud-variables/index.vue': { - routes: - | '/developer/cloud-variables/' - views: - | never - } - 'src/pages/developer/cloud-variables/[id].vue': { - routes: - | '/developer/cloud-variables/[id]' - | '/developer/cloud-variables/[id]/records' - views: - | 'default' - } - 'src/pages/developer/cloud-variables/[id]/records.vue': { - routes: - | '/developer/cloud-variables/[id]/records' - views: - | never - } - 'src/pages/developer/cloud-variables/create.vue': { - routes: - | '/developer/cloud-variables/create' - views: - | never - } - 'src/pages/developer/devices/index.vue': { - routes: - | '/developer/devices/' - views: - | never - } - 'src/pages/developer/dynamic-code.vue': { - routes: - | '/developer/dynamic-code' - | '/developer/dynamic-code/' - | '/developer/dynamic-code/[id]' - | '/developer/dynamic-code/create' - views: - | 'default' - } - 'src/pages/developer/dynamic-code/index.vue': { - routes: - | '/developer/dynamic-code/' - views: - | never - } - 'src/pages/developer/dynamic-code/[id].vue': { - routes: - | '/developer/dynamic-code/[id]' - views: - | never - } - 'src/pages/developer/dynamic-code/create.vue': { - routes: - | '/developer/dynamic-code/create' - views: - | never - } - 'src/pages/developer/extension/index.vue': { - routes: - | '/developer/extension/' - views: - | never - } - 'src/pages/developer/extension/api-keys/create.vue': { - routes: - | '/developer/extension/api-keys/create' - views: - | never - } - 'src/pages/developer/extension/webhooks/create.vue': { - routes: - | '/developer/extension/webhooks/create' - views: - | never - } - 'src/pages/developer/finance/index.vue': { - routes: - | '/developer/finance/' - views: - | never - } - 'src/pages/developer/logs.vue': { - routes: - | '/developer/logs' - views: - | never - } - 'src/pages/developer/risk-control/index.vue': { - routes: - | '/developer/risk-control/' - views: - | never - } - 'src/pages/developer/risk-control/[id].vue': { - routes: - | '/developer/risk-control/[id]' - views: - | never - } - 'src/pages/developer/risk-control/create.vue': { - routes: - | '/developer/risk-control/create' - views: - | never - } - 'src/pages/developer/sessions/index.vue': { - routes: - | '/developer/sessions/' - views: - | never - } - 'src/pages/developer/tickets.vue': { - routes: - | '/developer/tickets' - | '/developer/tickets/[id]' - | '/developer/tickets/create' - views: - | 'default' - } - 'src/pages/developer/tickets/[id].vue': { - routes: - | '/developer/tickets/[id]' - views: - | never - } - 'src/pages/developer/tickets/create.vue': { - routes: - | '/developer/tickets/create' - views: - | never - } - 'src/pages/developer/users/index.vue': { - routes: - | '/developer/users/' - views: - | never - } - 'src/pages/developer/users/[id].vue': { - routes: - | '/developer/users/[id]' - views: - | never - } - 'src/pages/developer/users/create.vue': { - routes: - | '/developer/users/create' - views: - | never - } - 'src/pages/developer/versions/index.vue': { - routes: - | '/developer/versions/' - views: - | never - } - 'src/pages/developer/versions/[id].vue': { - routes: - | '/developer/versions/[id]' - views: - | never - } - 'src/pages/developer/versions/create.vue': { - routes: - | '/developer/versions/create' - views: - | never - } 'src/pages/errors.vue': { routes: | '/errors'
- {{ developerMenus.main.title }} + {{ adminMenus.main.title }}
- {{ developerMenus.business.title }} + {{ adminMenus.business.title }}
- {{ developerMenus.advanced.title }} + {{ adminMenus.advanced.title }}
- 代理商购买卡密的折扣,范围0-1 -
- 请向代理商索取其ID -
- 请向管理员索取其ID -
- {{ t('admin.agents.create.passwordHint') }} -
- {{ t('admin.agents.create.parentAgentHint') }} -
- {{ t('admin.agents.form.balancePlaceholder') }} -
- {{ t('admin.agents.create.canCreateAgentHint') }} -
- 启用后公告将对用户可见 -
- 置顶的公告将在应用内优先显示 -
- 内容预览 -
- {{ formData.content || '暂无内容' }} -
- 允许用户使用邮箱注册和验证 -
- 注册时必须验证邮箱才能完成注册 -
- 允许用户通过邮箱验证码重置密码 -
- 推荐开启,端口 465 通常需要 SSL -
暂无自定义模板,将使用系统默认模板
- {{ template.name }} -
- {{ template.subject }} -
- 不加密 -
- 数据明文传输 -
- AES 加密 -
- 对称加密,速度快 -
- RSA 加密 -
- 非对称加密,更安全 -
- 请妥善保管密钥,丢失后无法恢复 -
- 不绑定 -
- 不限制设备或IP -
- 绑定设备 -
- 绑定用户设备硬件信息 -
- 绑定IP -
- 绑定用户IP地址 -
- 混合绑定 -
- 同时绑定设备和IP -
- 用户最多可绑定的设备数量 -
- 允许更换绑定的次数 -
- 两次更换之间的最小间隔 -
- 拒绝更换 -
- 超出次数后禁止更换绑定 -
- 扣除时长 -
- 超出次数后扣除使用时长 -
- 扣除点数 -
- 超出次数后扣除点数 -
- {{ form.change_exceed_action === 'deduct_time' ? '每次超出更换将扣除的时长' : '每次超出更换将扣除的点数' }} -
- 禁止多开 -
- 只允许运行一个实例 -
- 有限多开 -
- 限制同时运行的实例数 -
- 无限多开 -
- 不限制实例数量 -
- 同一账号最多可同时运行的实例数量 -
- 允许多开模式将不限制应用实例数量,请谨慎使用此选项。 -
- 禁止多开模式将只允许应用运行一个实例,尝试启动第二个实例将被阻止。 -
- 客户端发送心跳的间隔时间 -
- 超过此时间未收到心跳则判定离线 -
- 连续登录失败超过此次数将锁定 -
- 账号被锁定后的解锁时间 -
- 正常 -
- 应用正常运行 -
- 维护 -
- 应用维护中 -
- 停止 -
- 应用已停止 -
- 支持 JPG、PNG、GIF 格式,建议尺寸 512x512 -
- 免费模式 -
- 用户无需付费即可使用所有功能 -
- 订阅模式 -
- 用户按周期付费订阅使用 -
- 余额模式 -
- 用户按使用量计费,从余额扣除 -
- ⚡ 自动扣费 -
- 系统自动触发扣费 -
- 🔧 手动扣费 -
- 通过 API 自行控制扣费 -
- 登录扣费 -
- 每次登录验证时扣费 -
- 计时扣费 -
- 按设定的时间间隔自动扣费 -
- 每次扣费时扣除的余额数量 -
- 手动扣费说明 -
- 选择手动扣费后,系统不会自动扣除用户余额。您需要通过 API 接口自行控制扣费时机和金额。 -
- 宽松模式 -
- 用户到期后仍可登录,由客户端控制功能 -
- 严格模式 -
- 用户到期后无法登录,必须续费 -
- 允许注册 -
- 是否允许新用户注册账号 -
- 选择允许用户使用的注册方式 -
- 至少选择一种注册方式 -
- 启用试用 -
- 新用户注册后自动获得试用资格 -
- 新用户注册后自动获得的余额数量 -
- 新用户注册后自动获得的试用天数 -
- 启用免费时段 -
- 在设定的时间段内,所有用户可免费使用 -
- 日期范围 -
- 指定开始和结束日期 -
- 每周重复 -
- 每周指定日期和时间 -
- {{ t('admin.applications.freeMode') }} -
- {{ t('admin.applications.freeModeDesc') }} -
- {{ t('admin.applications.subscriptionMode') }} -
- {{ t('admin.applications.subscriptionModeDesc') }} -
- {{ t('admin.applications.timeMode') }} -
- {{ t('admin.applications.timeModeDesc') }} -
- {{ t('admin.applications.countMode') }} -
- {{ t('admin.applications.countModeDesc') }} -
- {{ t('admin.applications.looseMode') }} -
- {{ t('admin.applications.looseModeDesc') }} -
- {{ t('admin.applications.strictMode') }} -
- {{ t('admin.applications.strictModeDesc') }} -
- {{ t('admin.applications.hybridMode') }} -
- {{ t('admin.applications.hybridModeDesc') }} -
- {{ t('admin.applications.enableTrial') }} -
- 新用户注册后自动获得试用权益 -
- 新用户注册后获得的余额 -
- {{ t('admin.applications.enableFreePeriod') }} -
- {{ t('admin.applications.dateRange') }} -
- 指定开始和结束日期时间 -
- {{ t('admin.applications.weekly') }} -
- 每周固定日期和时间段 -
- 选择每周哪些日期免费 -
- 每天免费开始时间 -
- 每天免费结束时间 -
- 数据明文传输,仅用于测试环境 - 使用AES-GCM算法加密,安全性高 - 非对称加密,安全性最高 - 使用RC4流加密,性能较好 -
- 用于加密通信数据的密钥,留空则自动生成 -
- {{ t('admin.applications.deviceBind') }} -
- 启用后,用户登录时需要绑定设备,可限制同时登录的设备数量 -
- 每个账号最多绑定的设备数量 -
- {{ t('admin.applications.allowMultiOpen') }} -
- 允许同一账号在同一设备上同时运行多个实例 -
- 余额充值 -
- 充值账户余额,适用于计时/计次模式 -
- 订阅充值 -
- 充值会员时长,适用于订阅模式 -
- 永久会员 -
- 开启后,使用此卡密的用户将成为永久会员 -
- 用户充值后获得的余额数量 -
- {{ t('admin.cards.create.noCardType') }} -
- {{ t('admin.cards.create.maxCount') }} -
- {{ t('admin.cloudConstants.create.keyHint') }} -
- 拖拽文件到此处或点击选择 -
- {{ selectedFile.name }} -
- {{ formatFileSize(selectedFile.size) }} -
{{ formatData(record.data) }}
- 共 {{ total }} 条记录 -
- 超过限制时自动删除最旧的记录,0表示不限制 -
- {{ t('admin.cloudVariables.create.keyHint') }} -
- {{ t('admin.cloudVariables.create.appScope') }} -
- {{ t('admin.cloudVariables.create.appScopeDesc') }} -
- {{ t('admin.cloudVariables.create.userScope') }} -
- {{ t('admin.cloudVariables.create.userScopeDesc') }} -
- {{ t('admin.cloudVariables.create.developerOnly') }} -
- {{ form.scope === 'app' ? t('admin.cloudVariables.create.developerOnlyAppDesc') : t('admin.cloudVariables.create.developerOnlyDesc') }} -
- {{ t('admin.cloudVariables.create.userWritable') }} -
- {{ form.scope === 'app' ? t('admin.cloudVariables.create.userWritableAppDesc') : t('admin.cloudVariables.create.userWritableDesc') }} -
- 加载中... -
暂无云端函数
- {{ code.name }} -
- {{ code.appName }} -
{{ selectedCode?.code }}
- {{ t('admin.cloudFunction.create.codeHelp') }} -
- {{ t('admin.cloudFunction.create.enabled') }} -
- {{ t('admin.loading') }}... -
- +12.5% {{ t('admin.lastMonth') }} -
- +8.2% {{ t('admin.lastMonth') }} -
- +15.3% {{ t('admin.lastMonth') }} -
- +23.1% {{ t('admin.lastMonth') }} -
- {{ ticket.title }} -
- {{ t('admin.noTickets') }} -
- {{ activity.title }} -
- {{ activity.description }} -
- {{ formatDate(activity.createdAt) }} -
- {{ t('admin.noActivities') }} -
- {{ currentTypeInfo.scopeType === 'app' ? '用户封禁必须指定应用' : '选择要应用此封禁规则的应用' }} -
- {{ form.type === 'region' ? '选择要封禁的国家或地区' : '根据规则类型输入对应的封禁目标' }} -
- 留空表示永久封禁 -
- {{ t('admin.tickets.create.noApplications') }} -
- {{ t('admin.tickets.create.descriptionHint') }} -
- {{ t('admin.tickets.create.imagesHint') }} -
{{ t('admin.tickets.create.tip1') }}
{{ t('admin.tickets.create.tip2') }}
{{ t('admin.tickets.create.tip3') }}
{{ t('admin.tickets.create.tip4') }}
- 拖拽ZIP文件到此处或点击上传 -
- {{ formData.file.name }} -
- {{ fileSize }} -
文件哈希: {{ uploadedData.file_hash.substring(0, 16) }}...
包含 {{ uploadedData.files.length }} 个文件
- 选择更新完成后要启动的主程序文件(可选) -
- 启用后用户必须更新到此版本才能继续使用 -
- 启用后应用将自动下载并安装更新 -
- 更新日志预览 -
- {{ formData.changelog || '暂无更新日志' }} -