feat: 添加简单模式功能
新增简单模式设置,适合个人使用场景: - 隐藏多用户管理相关菜单(用户管理、兑换码等) - 自动关闭用户注册功能 - 管理员并发数自动设为无限制(99999) - 侧边栏根据模式动态调整菜单项 同时优化分组页面的"专属分组"功能,添加帮助提示说明使用场景
This commit is contained in:
@@ -272,34 +272,66 @@
|
||||
/>
|
||||
<p class="input-hint">{{ t('admin.groups.rateMultiplierHint') }}</p>
|
||||
</div>
|
||||
<div v-if="createForm.subscription_type !== 'subscription'" class="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="createForm.is_exclusive = !createForm.is_exclusive"
|
||||
:class="[
|
||||
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
||||
createForm.is_exclusive ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
|
||||
]"
|
||||
>
|
||||
<span
|
||||
<div v-if="createForm.subscription_type !== 'subscription'">
|
||||
<div class="mb-1.5 flex items-center gap-1">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.groups.form.exclusive') }}
|
||||
</label>
|
||||
<!-- Help Tooltip -->
|
||||
<div class="group relative inline-flex">
|
||||
<svg
|
||||
class="h-3.5 w-3.5 cursor-help text-gray-400 transition-colors hover:text-primary-500 dark:text-gray-500 dark:hover:text-primary-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<!-- Tooltip Popover -->
|
||||
<div class="pointer-events-none absolute bottom-full left-0 z-50 mb-2 w-72 opacity-0 transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100">
|
||||
<div class="rounded-lg bg-gray-900 p-3 text-white shadow-lg dark:bg-gray-800">
|
||||
<p class="mb-2 text-xs font-medium">{{ t('admin.groups.exclusiveTooltip.title') }}</p>
|
||||
<p class="mb-2 text-xs leading-relaxed text-gray-300">
|
||||
{{ t('admin.groups.exclusiveTooltip.description') }}
|
||||
</p>
|
||||
<div class="rounded bg-gray-800 p-2 dark:bg-gray-700">
|
||||
<p class="text-xs leading-relaxed text-gray-300">
|
||||
<span class="text-primary-400">💡 {{ t('admin.groups.exclusiveTooltip.example') }}</span>
|
||||
{{ t('admin.groups.exclusiveTooltip.exampleContent') }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Arrow -->
|
||||
<div class="absolute -bottom-1.5 left-3 h-3 w-3 rotate-45 bg-gray-900 dark:bg-gray-800"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="createForm.is_exclusive = !createForm.is_exclusive"
|
||||
:class="[
|
||||
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
|
||||
createForm.is_exclusive ? 'translate-x-6' : 'translate-x-1'
|
||||
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
||||
createForm.is_exclusive ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
<label class="text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.groups.exclusiveHint') }}
|
||||
</label>
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
|
||||
createForm.is_exclusive ? 'translate-x-6' : 'translate-x-1'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ createForm.is_exclusive ? t('admin.groups.exclusive') : t('admin.groups.public') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Subscription Configuration -->
|
||||
<div class="mt-4 border-t pt-4">
|
||||
<h4 class="mb-4 text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ t('admin.groups.subscription.title') }}
|
||||
</h4>
|
||||
|
||||
<div class="mb-4">
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.groups.subscription.type') }}</label>
|
||||
<Select v-model="createForm.subscription_type" :options="subscriptionTypeOptions" />
|
||||
<p class="input-hint">{{ t('admin.groups.subscription.typeHint') }}</p>
|
||||
@@ -422,25 +454,61 @@
|
||||
class="input"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="editForm.subscription_type !== 'subscription'" class="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="editForm.is_exclusive = !editForm.is_exclusive"
|
||||
:class="[
|
||||
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
||||
editForm.is_exclusive ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
|
||||
]"
|
||||
>
|
||||
<span
|
||||
<div v-if="editForm.subscription_type !== 'subscription'">
|
||||
<div class="mb-1.5 flex items-center gap-1">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.groups.form.exclusive') }}
|
||||
</label>
|
||||
<!-- Help Tooltip -->
|
||||
<div class="group relative inline-flex">
|
||||
<svg
|
||||
class="h-3.5 w-3.5 cursor-help text-gray-400 transition-colors hover:text-primary-500 dark:text-gray-500 dark:hover:text-primary-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<!-- Tooltip Popover -->
|
||||
<div class="pointer-events-none absolute bottom-full left-0 z-50 mb-2 w-72 opacity-0 transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100">
|
||||
<div class="rounded-lg bg-gray-900 p-3 text-white shadow-lg dark:bg-gray-800">
|
||||
<p class="mb-2 text-xs font-medium">{{ t('admin.groups.exclusiveTooltip.title') }}</p>
|
||||
<p class="mb-2 text-xs leading-relaxed text-gray-300">
|
||||
{{ t('admin.groups.exclusiveTooltip.description') }}
|
||||
</p>
|
||||
<div class="rounded bg-gray-800 p-2 dark:bg-gray-700">
|
||||
<p class="text-xs leading-relaxed text-gray-300">
|
||||
<span class="text-primary-400">💡 {{ t('admin.groups.exclusiveTooltip.example') }}</span>
|
||||
{{ t('admin.groups.exclusiveTooltip.exampleContent') }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Arrow -->
|
||||
<div class="absolute -bottom-1.5 left-3 h-3 w-3 rotate-45 bg-gray-900 dark:bg-gray-800"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="editForm.is_exclusive = !editForm.is_exclusive"
|
||||
:class="[
|
||||
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
|
||||
editForm.is_exclusive ? 'translate-x-6' : 'translate-x-1'
|
||||
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
||||
editForm.is_exclusive ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
<label class="text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.groups.exclusiveHint') }}
|
||||
</label>
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
|
||||
editForm.is_exclusive ? 'translate-x-6' : 'translate-x-1'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ editForm.is_exclusive ? t('admin.groups.exclusive') : t('admin.groups.public') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.groups.form.status') }}</label>
|
||||
@@ -449,11 +517,7 @@
|
||||
|
||||
<!-- Subscription Configuration -->
|
||||
<div class="mt-4 border-t pt-4">
|
||||
<h4 class="mb-4 text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ t('admin.groups.subscription.title') }}
|
||||
</h4>
|
||||
|
||||
<div class="mb-4">
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.groups.subscription.type') }}</label>
|
||||
<Select
|
||||
v-model="editForm.subscription_type"
|
||||
@@ -619,10 +683,18 @@ const editStatusOptions = computed(() => [
|
||||
{ value: 'inactive', label: t('common.inactive') }
|
||||
])
|
||||
|
||||
const subscriptionTypeOptions = computed(() => [
|
||||
{ value: 'standard', label: t('admin.groups.subscription.standard') },
|
||||
{ value: 'subscription', label: t('admin.groups.subscription.subscription') }
|
||||
])
|
||||
const subscriptionTypeOptions = computed(() => {
|
||||
// 简单模式下只显示订阅模式(配额模式)
|
||||
if (appStore.simpleMode) {
|
||||
return [
|
||||
{ value: 'subscription', label: t('admin.groups.subscription.subscription') }
|
||||
]
|
||||
}
|
||||
return [
|
||||
{ value: 'standard', label: t('admin.groups.subscription.standard') },
|
||||
{ value: 'subscription', label: t('admin.groups.subscription.subscription') }
|
||||
]
|
||||
})
|
||||
|
||||
const groups = ref<Group[]>([])
|
||||
const loading = ref(false)
|
||||
@@ -732,7 +804,7 @@ const closeCreateModal = () => {
|
||||
createForm.platform = 'anthropic'
|
||||
createForm.rate_multiplier = 1.0
|
||||
createForm.is_exclusive = false
|
||||
createForm.subscription_type = 'standard'
|
||||
createForm.subscription_type = appStore.simpleMode ? 'subscription' : 'standard'
|
||||
createForm.daily_limit_usd = null
|
||||
createForm.weekly_limit_usd = null
|
||||
createForm.monthly_limit_usd = null
|
||||
@@ -823,5 +895,9 @@ watch(
|
||||
|
||||
onMounted(() => {
|
||||
loadGroups()
|
||||
// 简单模式下默认使用订阅模式
|
||||
if (appStore.simpleMode) {
|
||||
createForm.subscription_type = 'subscription'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -153,6 +153,58 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usage Mode Settings -->
|
||||
<div class="card">
|
||||
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('admin.settings.usageMode.title') }}
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.usageMode.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-5 p-6">
|
||||
<!-- Simple Mode Toggle -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">
|
||||
{{ t('admin.settings.usageMode.simpleMode') }}
|
||||
</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.usageMode.simpleModeHint') }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle
|
||||
:model-value="form.simple_mode"
|
||||
@update:model-value="onSimpleModeToggle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Warning when simple mode is enabled -->
|
||||
<div
|
||||
v-if="form.simple_mode"
|
||||
class="rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
|
||||
>
|
||||
<div class="flex items-start">
|
||||
<svg
|
||||
class="mt-0.5 h-5 w-5 flex-shrink-0 text-amber-500"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<p class="ml-3 text-sm text-amber-700 dark:text-amber-300">
|
||||
{{ t('admin.settings.usageMode.simpleModeWarning') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Registration Settings -->
|
||||
<div class="card">
|
||||
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
|
||||
@@ -706,6 +758,19 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Simple Mode Confirmation Dialog -->
|
||||
<ConfirmDialog
|
||||
:show="showSimpleModeConfirm"
|
||||
:title="t('admin.settings.usageMode.confirmTitle')"
|
||||
:message="pendingSimpleModeValue
|
||||
? t('admin.settings.usageMode.confirmEnableMessage')
|
||||
: t('admin.settings.usageMode.confirmDisableMessage')"
|
||||
:confirm-text="t('common.confirm')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
@confirm="confirmSimpleModeChange"
|
||||
@cancel="cancelSimpleModeChange"
|
||||
/>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
@@ -716,6 +781,7 @@ import { adminAPI } from '@/api'
|
||||
import type { SystemSettings } from '@/api/admin/settings'
|
||||
import AppLayout from '@/components/layout/AppLayout.vue'
|
||||
import Toggle from '@/components/common/Toggle.vue'
|
||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||
import { useAppStore } from '@/stores'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -728,6 +794,10 @@ const sendingTestEmail = ref(false)
|
||||
const testEmailAddress = ref('')
|
||||
const logoError = ref('')
|
||||
|
||||
// Simple mode confirmation dialog
|
||||
const showSimpleModeConfirm = ref(false)
|
||||
const pendingSimpleModeValue = ref(false)
|
||||
|
||||
// Admin API Key 状态
|
||||
const adminApiKeyLoading = ref(true)
|
||||
const adminApiKeyExists = ref(false)
|
||||
@@ -756,7 +826,9 @@ const form = reactive<SystemSettings>({
|
||||
// Cloudflare Turnstile
|
||||
turnstile_enabled: false,
|
||||
turnstile_site_key: '',
|
||||
turnstile_secret_key: ''
|
||||
turnstile_secret_key: '',
|
||||
// Usage mode
|
||||
simple_mode: false
|
||||
})
|
||||
|
||||
function handleLogoUpload(event: Event) {
|
||||
@@ -827,6 +899,40 @@ async function saveSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
// Simple mode toggle handlers
|
||||
function onSimpleModeToggle(value: boolean) {
|
||||
pendingSimpleModeValue.value = value
|
||||
showSimpleModeConfirm.value = true
|
||||
}
|
||||
|
||||
async function confirmSimpleModeChange() {
|
||||
showSimpleModeConfirm.value = false
|
||||
form.simple_mode = pendingSimpleModeValue.value
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
await adminAPI.settings.updateSettings(form)
|
||||
await appStore.fetchPublicSettings(true)
|
||||
appStore.showSuccess(t('admin.settings.settingsSaved'))
|
||||
// Reload page to apply menu changes
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 500)
|
||||
} catch (error: any) {
|
||||
// Revert on error
|
||||
form.simple_mode = !pendingSimpleModeValue.value
|
||||
appStore.showError(
|
||||
t('admin.settings.failedToSave') + ': ' + (error.message || t('common.unknownError'))
|
||||
)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function cancelSimpleModeChange() {
|
||||
showSimpleModeConfirm.value = false
|
||||
}
|
||||
|
||||
async function testSmtpConnection() {
|
||||
testingSmtp.value = true
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user