188 lines
6.4 KiB
Vue
188 lines
6.4 KiB
Vue
<template>
|
||
<div class="aksrt-page-container">
|
||
<PageHeader
|
||
title="用户等级"
|
||
desc="配置等级来源、展示前缀和阈值规则。前台公开主页会按照这里的规则输出等级身份芯片。"
|
||
>
|
||
<template #actions>
|
||
<n-space>
|
||
<n-button quaternary @click="go('/users/credits')">用户积分</n-button>
|
||
<n-button quaternary @click="go('/users/badge')">用户徽章</n-button>
|
||
</n-space>
|
||
</template>
|
||
</PageHeader>
|
||
|
||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||
<n-card class="aksrt-section-card">
|
||
<div class="aksrt-section-header">
|
||
<h3>当前联动摘要</h3>
|
||
</div>
|
||
<n-space>
|
||
<n-tag round :type="form.user_level_enabled ? 'success' : 'default'">
|
||
等级系统:{{ form.user_level_enabled ? '已启用' : '已关闭' }}
|
||
</n-tag>
|
||
<n-tag round :type="creditsSummary.enabled ? 'info' : 'warning'">
|
||
积分系统:{{ creditsSummary.enabled ? '已启用' : '未启用' }}
|
||
</n-tag>
|
||
<n-tag round type="info">
|
||
当前货币:{{ creditsSummary.currencyName }}
|
||
</n-tag>
|
||
<n-tag round :type="form.user_level_source === 'credits_balance' ? 'success' : 'warning'">
|
||
来源:{{ form.user_level_source === 'credits_balance' ? '积分余额' : '手动 Meta' }}
|
||
</n-tag>
|
||
<n-tag round type="info">
|
||
阈值条目:{{ thresholdCount }}
|
||
</n-tag>
|
||
</n-space>
|
||
</n-card>
|
||
|
||
<n-card class="aksrt-section-card">
|
||
<div class="aksrt-section-header">
|
||
<h3>等级显示</h3>
|
||
</div>
|
||
<n-form :model="form" label-placement="left" label-width="180" class="aksrt-form">
|
||
<n-form-item label="启用等级系统">
|
||
<n-switch v-model:value="form.user_level_enabled" />
|
||
</n-form-item>
|
||
|
||
<n-form-item label="公开页显示等级" v-if="form.user_level_enabled">
|
||
<n-switch v-model:value="form.user_level_public_display" />
|
||
</n-form-item>
|
||
|
||
<n-form-item label="等级前缀" v-if="form.user_level_enabled">
|
||
<n-input v-model:value="form.user_level_prefix" maxlength="12" placeholder="Lv." />
|
||
</n-form-item>
|
||
</n-form>
|
||
</n-card>
|
||
|
||
<n-card class="aksrt-section-card">
|
||
<div class="aksrt-section-header">
|
||
<h3>等级规则</h3>
|
||
</div>
|
||
<n-form :model="form" label-placement="left" label-width="180" class="aksrt-form">
|
||
<n-form-item label="等级来源">
|
||
<n-radio-group v-model:value="form.user_level_source" :disabled="!form.user_level_enabled">
|
||
<n-space size="large">
|
||
<n-radio value="credits_balance">积分余额</n-radio>
|
||
<n-radio value="manual_meta">手动 Meta</n-radio>
|
||
</n-space>
|
||
</n-radio-group>
|
||
</n-form-item>
|
||
|
||
<n-form-item label="等级阈值">
|
||
<n-input
|
||
v-model:value="form.user_level_thresholds"
|
||
type="textarea"
|
||
:autosize="{ minRows: 5, maxRows: 10 }"
|
||
placeholder="1|0 2|30 3|100 4|300"
|
||
:disabled="!form.user_level_enabled"
|
||
/>
|
||
<template #feedback>
|
||
<span class="aksrt-hint-text">每行一个等级,格式 `等级|所需积分`。手动 Meta 模式下仍建议保留完整字典,方便后续切换回自动计算。</span>
|
||
</template>
|
||
</n-form-item>
|
||
</n-form>
|
||
</n-card>
|
||
|
||
<n-card class="aksrt-section-card">
|
||
<n-alert type="info" :show-icon="false">
|
||
手动模式读取 `aksrt_user_level` user meta;自动模式则根据当前积分余额和上方阈值表实时计算等级,不需要单独写库。
|
||
</n-alert>
|
||
</n-card>
|
||
|
||
<div class="aksrt-form-actions">
|
||
<n-button type="primary" :loading="saving" @click="handleSave">保存配置</n-button>
|
||
</div>
|
||
</n-spin>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, onMounted, reactive, ref } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { useMessage } from 'naive-ui'
|
||
import api from '@/api'
|
||
import PageHeader from '@/components/PageHeader.vue'
|
||
|
||
const router = useRouter()
|
||
const message = useMessage()
|
||
|
||
const loading = ref(true)
|
||
const saving = ref(false)
|
||
|
||
const form = reactive({
|
||
user_level_enabled: false,
|
||
user_level_public_display: true,
|
||
user_level_source: 'credits_balance',
|
||
user_level_prefix: 'Lv.',
|
||
user_level_thresholds: '1|0\n2|30\n3|100\n4|300',
|
||
})
|
||
|
||
const creditsSummary = reactive({
|
||
enabled: true,
|
||
currencyName: '积分',
|
||
})
|
||
|
||
const thresholdCount = computed(() => {
|
||
return String(form.user_level_thresholds || '')
|
||
.split(/\r?\n/)
|
||
.map((item) => item.trim())
|
||
.filter(Boolean)
|
||
.length
|
||
})
|
||
|
||
function go(path) {
|
||
router.push(path)
|
||
}
|
||
|
||
function extractData(response) {
|
||
return response?.data ?? response ?? {}
|
||
}
|
||
|
||
async function load() {
|
||
loading.value = true
|
||
try {
|
||
const [schemaRes, creditRes] = await Promise.all([
|
||
api.getSchemaPage('users', 'level'),
|
||
api.getCreditSettings(),
|
||
])
|
||
|
||
const schemaData = extractData(schemaRes)
|
||
const creditData = extractData(creditRes)
|
||
|
||
form.user_level_enabled = !!schemaData.user_level_enabled
|
||
form.user_level_public_display = schemaData.user_level_public_display !== false
|
||
form.user_level_source = schemaData.user_level_source || 'credits_balance'
|
||
form.user_level_prefix = schemaData.user_level_prefix || 'Lv.'
|
||
form.user_level_thresholds = schemaData.user_level_thresholds || '1|0\n2|30\n3|100\n4|300'
|
||
|
||
creditsSummary.enabled = creditData.enabled !== false
|
||
creditsSummary.currencyName = creditData.currency_name || '积分'
|
||
} catch (error) {
|
||
message.error(error.message || '加载用户等级配置失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
async function handleSave() {
|
||
saving.value = true
|
||
try {
|
||
await api.saveSchemaPage('users', 'level', {
|
||
user_level_enabled: form.user_level_enabled,
|
||
user_level_public_display: form.user_level_public_display,
|
||
user_level_source: form.user_level_source,
|
||
user_level_prefix: form.user_level_prefix,
|
||
user_level_thresholds: form.user_level_thresholds,
|
||
})
|
||
message.success('用户等级配置已保存')
|
||
} catch (error) {
|
||
message.error(error.message || '保存失败')
|
||
} finally {
|
||
saving.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(load)
|
||
</script>
|