fix: 订阅模式登录验证、永久会员类型区分、动态代码HTTP返回值修复、侧边栏滚动位置保持

- 修复订阅模式登录时错误检查余额的问题
- 区分无限余额和永久订阅两种永久会员类型
- 修复动态代码HTTP请求返回值在JS中无法正确访问的问题
- 添加侧边栏滚动位置保持功能
- 移除developer角色相关代码,统一使用admin
- 添加缺失的i18n翻译key
This commit is contained in:
2026-05-01 16:39:31 +08:00
parent c0edb32614
commit ea8ffb6c74
69 changed files with 554 additions and 369 deletions
@@ -22,8 +22,8 @@ export const agentAppSchema = z.object({
export const agentRequestSchema = z.object({
id: z.number(),
developer_id: z.number(),
developer_name: z.string(),
admin_id: z.number(),
admin_name: z.string(),
agent_id: z.number(),
agent_name: z.string(),
application_id: z.number(),
@@ -66,8 +66,8 @@ const allItems = computed<CombinedItem[]>(() => {
.filter(item => item.status === 'pending')
.map(item => ({
id: item.id,
agent_id: item.developer_id,
agent_name: item.developer_name,
agent_id: item.admin_id,
agent_name: item.admin_name,
application_id: item.application_id,
app_name: item.app_name,
status: item.status,
@@ -13,7 +13,7 @@ const saving = ref(false)
const applications = ref<Array<{ id: number, name: string }>>([])
const form = ref({
developer_id: '',
admin_id: '',
application_id: '',
message: '',
})
@@ -35,7 +35,7 @@ async function fetchApplications() {
}
async function handleSubmit() {
if (!form.value.developer_id) {
if (!form.value.admin_id) {
toast.error('请输入管理员ID')
return
}
@@ -54,7 +54,7 @@ async function handleSubmit() {
'Content-Type': 'application/json',
},
body: JSON.stringify({
developer_id: Number(form.value.developer_id),
admin_id: Number(form.value.admin_id),
application_id: Number(form.value.application_id),
message: form.value.message,
}),
@@ -109,12 +109,12 @@ onMounted(() => {
</UiCardHeader>
<UiCardContent class="space-y-6">
<div class="space-y-2">
<UiLabel for="developer_id">
<UiLabel for="admin_id">
管理员ID
</UiLabel>
<UiInput
id="developer_id"
v-model="form.developer_id"
id="admin_id"
v-model="form.admin_id"
type="number"
placeholder="请输入管理员ID"
/>
+16 -16
View File
@@ -211,13 +211,13 @@ onMounted(() => {
<UiCardHeader>
<UiCardTitle class="flex items-center gap-2">
<Icon icon="lucide:credit-card" class="size-5" />
{{ t('admin.cardTypes.create.rechargeConfig') || '充值配置' }}
{{ t('admin.cardTypes.create.rechargeConfig') }}
</UiCardTitle>
<UiCardDescription>{{ t('admin.cardTypes.create.rechargeConfigDesc') || '设置卡密的充值类型和金额' }}</UiCardDescription>
<UiCardDescription>{{ t('admin.cardTypes.create.rechargeConfigDesc') }}</UiCardDescription>
</UiCardHeader>
<UiCardContent class="space-y-6">
<div class="space-y-2">
<UiLabel>{{ t('admin.cardTypes.create.rechargeType') || '充值类型' }}</UiLabel>
<UiLabel>{{ t('admin.cardTypes.create.rechargeType') }}</UiLabel>
<UiRadioGroup v-model="form.recharge_type" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div
class="flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-colors"
@@ -227,10 +227,10 @@ onMounted(() => {
<UiRadioGroupItem value="balance" class="mt-0.5" />
<div>
<p class="font-medium">
{{ t('admin.cardTypes.create.balanceRecharge') || '余额充值' }}
{{ t('admin.cardTypes.create.balanceRecharge') }}
</p>
<p class="text-sm text-muted-foreground">
{{ t('admin.cardTypes.create.balanceRechargeDesc') || '充值账户余额,适用于计时/计次模式' }}
{{ t('admin.cardTypes.create.balanceRechargeDesc') }}
</p>
</div>
</div>
@@ -243,10 +243,10 @@ onMounted(() => {
<UiRadioGroupItem value="subscription" class="mt-0.5" />
<div>
<p class="font-medium">
{{ t('admin.cardTypes.create.subscriptionRecharge') || '订阅充值' }}
{{ t('admin.cardTypes.create.subscriptionRecharge') }}
</p>
<p class="text-sm text-muted-foreground">
{{ t('admin.cardTypes.create.subscriptionRechargeDesc') || '充值会员时长,适用于订阅模式' }}
{{ t('admin.cardTypes.create.subscriptionRechargeDesc') }}
</p>
</div>
</div>
@@ -272,10 +272,10 @@ onMounted(() => {
<div class="flex items-center justify-between pt-4 border-t">
<div>
<p class="font-medium">
{{ t('admin.cardTypes.create.permanent') || '永久会员' }}
{{ t('admin.cardTypes.create.permanentLabel') }}
</p>
<p class="text-sm text-muted-foreground">
{{ t('admin.cardTypes.create.permanentDesc') || '开启后,使用此卡密的用户将成为永久会员' }}
{{ t('admin.cardTypes.create.permanentLabelDesc') }}
</p>
</div>
<UiSwitch v-model="form.is_permanent" />
@@ -283,7 +283,7 @@ onMounted(() => {
<div v-if="!form.is_permanent" class="space-y-4 pt-4 border-t">
<div v-if="form.recharge_type === 'balance'" class="space-y-2">
<UiLabel>{{ t('admin.cardTypes.create.rechargeAmount') || '充值金额' }}</UiLabel>
<UiLabel>{{ t('admin.cardTypes.create.rechargeAmount') }}</UiLabel>
<UiNumberField v-model="form.value" :min="0.01" :step="0.01" class="max-w-[200px]">
<UiNumberFieldContent>
<UiNumberFieldDecrement />
@@ -292,14 +292,14 @@ onMounted(() => {
</UiNumberFieldContent>
</UiNumberField>
<p class="text-xs text-muted-foreground">
{{ t('admin.cardTypes.create.rechargeAmountDesc') || '用户充值后获得的余额数量' }}
{{ t('admin.cardTypes.create.rechargeAmountDesc') }}
</p>
</div>
<div v-if="form.recharge_type === 'subscription'" class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<UiLabel>{{ t('admin.cardTypes.create.rechargeDuration') || '充值时长' }}</UiLabel>
<UiLabel>{{ t('admin.cardTypes.create.rechargeDuration') }}</UiLabel>
<UiNumberField v-model="form.value" :min="1" :step="1">
<UiNumberFieldContent>
<UiNumberFieldDecrement />
@@ -310,7 +310,7 @@ onMounted(() => {
</div>
<div class="space-y-2">
<UiLabel>{{ t('admin.cardTypes.create.durationUnit') || '时长单位' }}</UiLabel>
<UiLabel>{{ t('admin.cardTypes.create.durationUnit') }}</UiLabel>
<UiSelect v-model="form.value_unit">
<UiSelectTrigger>
<UiSelectValue />
@@ -360,8 +360,8 @@ onMounted(() => {
<span>{{ selectedApplication?.name || '-' }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">{{ t('admin.cardTypes.create.rechargeType') || '充值类型' }}</span>
<span>{{ form.recharge_type === 'balance' ? (t('admin.cardTypes.create.balanceRecharge') || '余额充值') : (t('admin.cardTypes.create.subscriptionRecharge') || '订阅充值') }}</span>
<span class="text-muted-foreground">{{ t('admin.cardTypes.create.rechargeType') }}</span>
<span>{{ form.recharge_type === 'balance' ? t('admin.cardTypes.create.balanceRecharge') : t('admin.cardTypes.create.subscriptionRecharge') }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">{{ t('admin.cardTypes.create.previewPrice') }}</span>
@@ -386,7 +386,7 @@ onMounted(() => {
>
<Icon v-if="saving" icon="lucide:loader-2" class="mr-2 h-4 w-4 animate-spin" />
<Icon v-else icon="lucide:check" class="mr-2 h-4 w-4" />
{{ t('admin.cardTypes.create.saveBtn') || '保存修改' }}
{{ t('admin.cardTypes.create.saveBtn') }}
</UiButton>
<UiButton
variant="outline"
+20 -20
View File
@@ -178,13 +178,13 @@ onMounted(() => {
<UiCardHeader>
<UiCardTitle class="flex items-center gap-2">
<Icon icon="lucide:credit-card" class="size-5" />
充值配置
{{ t('admin.cardTypes.create.rechargeConfig') }}
</UiCardTitle>
<UiCardDescription>设置卡密的充值类型和金额</UiCardDescription>
<UiCardDescription>{{ t('admin.cardTypes.create.rechargeConfigDesc') }}</UiCardDescription>
</UiCardHeader>
<UiCardContent class="space-y-6">
<div class="space-y-2">
<UiLabel>充值类型</UiLabel>
<UiLabel>{{ t('admin.cardTypes.create.rechargeType') }}</UiLabel>
<UiRadioGroup v-model="form.recharge_type" class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div
class="flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-colors"
@@ -194,10 +194,10 @@ onMounted(() => {
<UiRadioGroupItem value="balance" class="mt-0.5" />
<div>
<p class="font-medium">
余额充值
{{ t('admin.cardTypes.create.balanceRecharge') }}
</p>
<p class="text-sm text-muted-foreground">
充值账户余额适用于计时/计次模式
{{ t('admin.cardTypes.create.balanceRechargeDesc') }}
</p>
</div>
</div>
@@ -210,10 +210,10 @@ onMounted(() => {
<UiRadioGroupItem value="subscription" class="mt-0.5" />
<div>
<p class="font-medium">
订阅充值
{{ t('admin.cardTypes.create.subscriptionRecharge') }}
</p>
<p class="text-sm text-muted-foreground">
充值会员时长适用于订阅模式
{{ t('admin.cardTypes.create.subscriptionRechargeDesc') }}
</p>
</div>
</div>
@@ -239,10 +239,10 @@ onMounted(() => {
<div class="flex items-center justify-between pt-4 border-t">
<div>
<p class="font-medium">
永久会员
{{ t('admin.cardTypes.create.permanentLabel') }}
</p>
<p class="text-sm text-muted-foreground">
开启后使用此卡密的用户将成为永久会员
{{ t('admin.cardTypes.create.permanentLabelDesc') }}
</p>
</div>
<UiSwitch v-model="form.is_permanent" />
@@ -250,7 +250,7 @@ onMounted(() => {
<div v-if="!form.is_permanent" class="space-y-4 pt-4 border-t">
<div v-if="form.recharge_type === 'balance'" class="space-y-2">
<UiLabel>充值金额</UiLabel>
<UiLabel>{{ t('admin.cardTypes.create.rechargeAmount') }}</UiLabel>
<UiNumberField v-model="form.value" :min="0.01" :step="0.01" class="max-w-[200px]">
<UiNumberFieldContent>
<UiNumberFieldDecrement />
@@ -259,14 +259,14 @@ onMounted(() => {
</UiNumberFieldContent>
</UiNumberField>
<p class="text-xs text-muted-foreground">
用户充值后获得的余额数量
{{ t('admin.cardTypes.create.rechargeAmountDesc') }}
</p>
</div>
<div v-if="form.recharge_type === 'subscription'" class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<UiLabel>充值时长</UiLabel>
<UiLabel>{{ t('admin.cardTypes.create.rechargeDuration') }}</UiLabel>
<UiNumberField v-model="form.value" :min="1" :step="1">
<UiNumberFieldContent>
<UiNumberFieldDecrement />
@@ -277,26 +277,26 @@ onMounted(() => {
</div>
<div class="space-y-2">
<UiLabel>时长单位</UiLabel>
<UiLabel>{{ t('admin.cardTypes.create.durationUnit') }}</UiLabel>
<UiSelect v-model="form.value_unit">
<UiSelectTrigger>
<UiSelectValue />
</UiSelectTrigger>
<UiSelectContent>
<UiSelectItem value="minute">
分钟
{{ t('admin.cardTypes.units.minute') }}
</UiSelectItem>
<UiSelectItem value="hour">
小时
{{ t('admin.cardTypes.units.hour') }}
</UiSelectItem>
<UiSelectItem value="day">
{{ t('admin.cardTypes.units.day') }}
</UiSelectItem>
<UiSelectItem value="month">
{{ t('admin.cardTypes.units.month') }}
</UiSelectItem>
<UiSelectItem value="year">
{{ t('admin.cardTypes.units.year') }}
</UiSelectItem>
</UiSelectContent>
</UiSelect>
@@ -327,8 +327,8 @@ onMounted(() => {
<span>{{ selectedApplication?.name || '-' }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">充值类型</span>
<span>{{ form.recharge_type === 'balance' ? '余额充值' : '订阅充值' }}</span>
<span class="text-muted-foreground">{{ t('admin.cardTypes.create.rechargeType') }}</span>
<span>{{ form.recharge_type === 'balance' ? t('admin.cardTypes.create.balanceRecharge') : t('admin.cardTypes.create.subscriptionRecharge') }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">{{ t('admin.cardTypes.create.previewPrice') }}</span>
@@ -389,7 +389,7 @@ onMounted(() => {
<div class="flex items-center gap-2">
<Icon icon="lucide:lock" class="size-4 text-primary" />
<p class="font-medium">
{{ t('admin.cloudVariables.create.developerOnly') }}
{{ t('admin.cloudVariables.create.adminOnly') }}
</p>
</div>
</div>
@@ -467,7 +467,7 @@ onMounted(() => {
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">{{ t('admin.cloudVariables.create.writePermission') }}</span>
<UiBadge variant="secondary">
{{ form.write_permission === 'admin' ? t('admin.cloudVariables.create.developerOnly') : form.write_permission === 'app_user' ? '应用用户可写' : t('admin.cloudVariables.create.userWritable') }}
{{ form.write_permission === 'admin' ? t('admin.cloudVariables.create.adminOnly') : form.write_permission === 'app_user' ? '应用用户可写' : t('admin.cloudVariables.create.userWritable') }}
</UiBadge>
</div>
<div class="flex justify-between text-sm items-center">
@@ -74,7 +74,7 @@ export function getColumns(actions: {
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'))
return h(Badge, { variant: 'outline' }, () => t('admin.cloudVariables.create.adminOnly'))
},
},
{
@@ -364,11 +364,11 @@ onMounted(() => {
<div class="flex items-center gap-2">
<Icon icon="lucide:lock" class="size-4 text-primary" />
<p class="font-medium">
{{ t('admin.cloudVariables.create.developerOnly') }}
{{ t('admin.cloudVariables.create.adminOnly') }}
</p>
</div>
<p class="text-sm text-muted-foreground mt-1">
{{ form.scope === 'app' ? t('admin.cloudVariables.create.developerOnlyAppDesc') : t('admin.cloudVariables.create.developerOnlyDesc') }}
{{ form.scope === 'app' ? t('admin.cloudVariables.create.adminOnlyAppDesc') : t('admin.cloudVariables.create.adminOnlyDesc') }}
</p>
</div>
</div>
@@ -451,7 +451,7 @@ onMounted(() => {
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">{{ t('admin.cloudVariables.create.writePermission') }}</span>
<UiBadge variant="secondary">
{{ form.write_permission === 'admin' ? t('admin.cloudVariables.create.developerOnly') : form.write_permission === 'app_user' ? '应用用户可写' : t('admin.cloudVariables.create.userWritable') }}
{{ form.write_permission === 'admin' ? t('admin.cloudVariables.create.adminOnly') : form.write_permission === 'app_user' ? '应用用户可写' : t('admin.cloudVariables.create.userWritable') }}
</UiBadge>
</div>
<div class="flex justify-between text-sm items-center">
+1 -1
View File
@@ -479,7 +479,7 @@ onUnmounted(() => {
<template>
<BasicPage
:title="t('admin.dashboard.title')"
:description="`${t('admin.welcomeBack')}, ${currentUser?.username || t('admin.developer')}`"
:description="`${t('admin.welcomeBack')}, ${currentUser?.username || t('admin.adminRole')}`"
>
<div v-if="loading" class="flex items-center justify-center py-12">
<div class="text-center">
+26 -26
View File
@@ -42,7 +42,7 @@ async function fetchUser() {
}
catch (error) {
console.error('获取用户信息失败:', error)
toast.error(t('admin.users.editFailed'))
toast.error(t('admin.users.edit.failed'))
}
finally {
loading.value = false
@@ -68,15 +68,15 @@ const selectedApplication = computed(() => {
async function handleSave() {
if (!form.value.username) {
toast.error(t('admin.users.create.usernameRequired'))
toast.error(t('admin.users.edit.usernameRequired'))
return
}
if (!form.value.email) {
toast.error(t('admin.users.create.emailRequired'))
toast.error(t('admin.users.edit.emailRequired'))
return
}
if (!form.value.application_id) {
toast.error(t('admin.users.create.applicationRequired'))
toast.error(t('admin.users.edit.applicationRequired'))
return
}
@@ -93,12 +93,12 @@ async function handleSave() {
}
await api.put(`/dev/app-users/${userId.value}`, payload)
toast.success(t('admin.users.editSuccess'))
toast.success(t('admin.users.edit.success'))
router.push('/admin/users')
}
catch (error: any) {
console.error('更新用户失败:', error)
toast.error(error.message || t('admin.users.editFailed'))
toast.error(error.message || t('admin.users.edit.failed'))
}
finally {
saving.value = false
@@ -113,11 +113,11 @@ onMounted(() => {
<template>
<BasicPage
:title="t('admin.users.edit')"
:description="t('admin.users.create.basicInfoDesc')"
:title="t('admin.users.edit.title')"
:description="t('admin.users.edit.basicInfoDesc')"
:breadcrumbs="[
{ title: t('admin.users.title'), href: '/admin/users' },
{ title: t('admin.users.edit') },
{ title: t('admin.users.edit.title') },
]"
sticky
>
@@ -132,18 +132,18 @@ onMounted(() => {
<UiCardHeader>
<UiCardTitle class="flex items-center gap-2">
<Icon icon="lucide:user-plus" class="size-5" />
{{ t('admin.users.create.basicInfo') }}
{{ t('admin.users.edit.basicInfo') }}
</UiCardTitle>
<UiCardDescription>{{ t('admin.users.create.basicInfoDesc') }}</UiCardDescription>
<UiCardDescription>{{ t('admin.users.edit.basicInfoDesc') }}</UiCardDescription>
</UiCardHeader>
<UiCardContent class="space-y-6">
<div class="space-y-2">
<UiLabel for="application">
{{ t('admin.users.create.application') }}
{{ t('admin.users.edit.application') }}
</UiLabel>
<UiSelect v-model="form.application_id">
<UiSelectTrigger>
<UiSelectValue :placeholder="t('admin.users.create.selectApplication')" />
<UiSelectValue :placeholder="t('admin.users.edit.selectApplication')" />
</UiSelectTrigger>
<UiSelectContent>
<UiSelectItem v-for="app in applications" :key="app.id" :value="String(app.id)">
@@ -155,25 +155,25 @@ onMounted(() => {
<div class="space-y-2">
<UiLabel for="username">
{{ t('admin.users.create.username') }}
{{ t('admin.users.edit.username') }}
</UiLabel>
<UiInput id="username" v-model="form.username" :placeholder="t('admin.users.create.usernamePlaceholder')" />
<UiInput id="username" v-model="form.username" :placeholder="t('admin.users.edit.usernamePlaceholder')" />
</div>
<div class="space-y-2">
<UiLabel for="email">
{{ t('admin.users.create.email') }}
{{ t('admin.users.edit.email') }}
</UiLabel>
<UiInput id="email" v-model="form.email" type="email" :placeholder="t('admin.users.create.emailPlaceholder')" />
<UiInput id="email" v-model="form.email" type="email" :placeholder="t('admin.users.edit.emailPlaceholder')" />
</div>
<div class="space-y-2">
<UiLabel for="password">
{{ t('admin.users.create.password') }}
{{ t('admin.users.edit.password') }}
</UiLabel>
<UiInput id="password" v-model="form.password" type="password" :placeholder="t('admin.users.create.passwordPlaceholder')" />
<UiInput id="password" v-model="form.password" type="password" :placeholder="t('admin.users.edit.passwordPlaceholder')" />
<p class="text-xs text-muted-foreground">
留空则不修改密码
{{ t('admin.users.edit.passwordHint') }}
</p>
</div>
</UiCardContent>
@@ -185,21 +185,21 @@ onMounted(() => {
<UiCardHeader>
<UiCardTitle class="flex items-center gap-2">
<Icon icon="lucide:eye" class="size-5" />
{{ t('admin.users.create.preview') }}
{{ t('admin.users.edit.preview') }}
</UiCardTitle>
</UiCardHeader>
<UiCardContent class="space-y-4">
<div class="space-y-3">
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">{{ t('admin.users.create.app') }}</span>
<span class="text-muted-foreground">{{ t('admin.users.edit.app') }}</span>
<span>{{ selectedApplication?.name || '-' }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">{{ t('admin.users.create.usernameLabel') }}</span>
<span class="text-muted-foreground">{{ t('admin.users.edit.usernameLabel') }}</span>
<span>{{ form.username || '-' }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">{{ t('admin.users.create.emailLabel') }}</span>
<span class="text-muted-foreground">{{ t('admin.users.edit.emailLabel') }}</span>
<span>{{ form.email || '-' }}</span>
</div>
</div>
@@ -217,14 +217,14 @@ onMounted(() => {
>
<Icon v-if="saving" icon="lucide:loader-2" class="mr-2 h-4 w-4 animate-spin" />
<Icon v-else icon="lucide:check" class="mr-2 h-4 w-4" />
{{ t('admin.users.create.saveBtn') || '保存修改' }}
{{ t('admin.users.edit.saveBtn') }}
</UiButton>
<UiButton
variant="outline"
class="w-full"
@click="router.back()"
>
{{ t('admin.users.create.cancel') }}
{{ t('admin.users.edit.cancel') }}
</UiButton>
</div>
</UiCardContent>
+2 -2
View File
@@ -92,8 +92,8 @@ onMounted(() => {
<template>
<BasicPage
title="创建用户"
description="为应用创建新的用户账号,设置基本信息和应用关联"
:title="t('admin.users.create.title')"
:description="t('admin.users.create.description')"
:breadcrumbs="[
{ title: t('admin.users.title'), href: '/admin/users' },
{ title: t('admin.users.create.title') },