用户功能设置收口与废弃后台链路清理
This commit is contained in:
1 parent
081eb0870d
commit
86692ff3f9
59 files changed
+506
-1767
No files matched your search
@@ -1,93 +0,0 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader title="用户功能" desc="用户中心的资料、背景图与公开资料均使用统一的能力矩阵和隐私规则。">
|
||||
<template #actions>
|
||||
<n-space>
|
||||
<n-button quaternary @click="go('/permissions/capabilities')">基本权限</n-button>
|
||||
<n-button quaternary @click="go('/page-display/user-home')">公开主页</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</PageHeader>
|
||||
|
||||
<n-spin :show="loading" style="width: 100%; min-height: 180px;">
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header"><h3>固定资料与操作入口</h3></div>
|
||||
<n-space vertical size="large">
|
||||
<n-alert type="info" :show-icon="false">
|
||||
个人资料固定为昵称、个人签名、隐私设置、性别、个人网站、QQ、微信和 GitHub。公开资料由访问者身份过滤,原始社交资料不会下发给无权访客。
|
||||
</n-alert>
|
||||
<n-alert type="info" :show-icon="false">
|
||||
背景图操作入口始终显示在资料卡右下角的三点菜单中;“上传头像”和“上传用户资料卡背景图”由基本权限矩阵控制。
|
||||
</n-alert>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header"><h3>用户中心面板</h3></div>
|
||||
<n-form label-placement="left" label-width="170" class="aksrt-form">
|
||||
<n-form-item label="可切换业务面板">
|
||||
<n-checkbox-group v-model:value="form.dynamicTabs">
|
||||
<n-space vertical>
|
||||
<n-checkbox value="posts">我的文章</n-checkbox>
|
||||
<n-checkbox value="comments">我的评论</n-checkbox>
|
||||
<n-checkbox value="favorites">我的收藏</n-checkbox>
|
||||
<n-checkbox value="credits">账户余额 / 签到</n-checkbox>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</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 { 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({ dynamicTabs: ['posts', 'comments', 'favorites'] })
|
||||
|
||||
function go(path) { router.push(path) }
|
||||
function extractData(response) { return response?.data ?? response ?? {} }
|
||||
function parseTabs(value) {
|
||||
const values = Array.isArray(value) ? value : String(value || '').split(/[\s,]+/)
|
||||
return values.map((value) => String(value).trim()).filter((value) => ['posts', 'comments', 'favorites', 'credits'].includes(value))
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = extractData(await api.getSchemaPage('users', 'features'))
|
||||
form.dynamicTabs = parseTabs(data.user_center_tabs || 'profile,posts,comments,favorites')
|
||||
} catch (error) {
|
||||
message.error(error.message || '加载用户功能配置失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
await api.saveSchemaPage('users', 'features', { user_center_tabs: ['profile', ...form.dynamicTabs].join(',') })
|
||||
message.success('用户功能配置已保存')
|
||||
} catch (error) {
|
||||
message.error(error.message || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
@@ -1,257 +0,0 @@
|
||||
<template>
|
||||
<UsersSectionPage
|
||||
title="签到奖励"
|
||||
desc="直接管理签到与 Ref 推广奖励,保存后会立即影响前台签到、推广链接和注册页推广码行为。"
|
||||
:links="[
|
||||
{ label: '注册登录', path: '/users/auth' },
|
||||
{ label: '用户积分', path: '/users/credits' },
|
||||
]"
|
||||
>
|
||||
<template #before-form>
|
||||
<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.credits_enabled ? 'success' : 'warning'">
|
||||
积分系统:{{ form.credits_enabled ? '已启用' : '未启用' }}
|
||||
</n-tag>
|
||||
<n-tag round :type="form.signin_enabled ? 'success' : 'default'">
|
||||
签到能力:{{ form.signin_enabled ? '已启用' : '已关闭' }}
|
||||
</n-tag>
|
||||
<n-tag round :type="form.referral_reward_enabled ? 'success' : 'default'">
|
||||
推广奖励:{{ form.referral_reward_enabled ? '已启用' : '已关闭' }}
|
||||
</n-tag>
|
||||
<n-tag round :type="captchaSummary.register ? 'info' : 'default'">
|
||||
注册验证码:{{ captchaSummary.register ? '启用' : '未启用' }}
|
||||
</n-tag>
|
||||
<n-tag round :type="captchaSummary.signin ? 'info' : 'default'">
|
||||
签到验证码:{{ captchaSummary.signin ? '启用' : '未启用' }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<n-card v-if="!form.credits_enabled" class="aksrt-section-card">
|
||||
<n-alert type="warning" :show-icon="false">
|
||||
当前积分系统处于关闭状态。即使保存签到和推广奖励配置,前台也不会真正发放奖励,直到“用户积分”页重新启用积分系统。
|
||||
</n-alert>
|
||||
</n-card>
|
||||
</n-spin>
|
||||
</template>
|
||||
|
||||
<n-form :model="form" label-placement="left" label-width="180" class="aksrt-form">
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header">
|
||||
<h3>每日签到</h3>
|
||||
</div>
|
||||
|
||||
<n-form-item label="启用签到奖励">
|
||||
<n-switch v-model:value="form.signin_enabled" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="基础奖励" v-if="form.signin_enabled">
|
||||
<n-input-number v-model:value="form.signin_base" :min="0" :max="100000" style="width: 180px;" />
|
||||
<template #feedback>
|
||||
<span class="aksrt-hint-text">每次签到基础发放 {{ currencyLabel }} 数量。</span>
|
||||
</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="连续签到加成" v-if="form.signin_enabled">
|
||||
<n-input
|
||||
v-model:value="form.signin_streak_text"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 4, maxRows: 8 }"
|
||||
placeholder="3|2 7|5 30|20"
|
||||
/>
|
||||
<template #feedback>
|
||||
<span class="aksrt-hint-text">每行一个周期,格式 `天数|额外奖励`。例如 `7|5` 表示连续 7 天额外获得 5 {{ currencyLabel }}。</span>
|
||||
</template>
|
||||
</n-form-item>
|
||||
</n-card>
|
||||
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header">
|
||||
<h3>Ref 推广奖励与注册</h3>
|
||||
</div>
|
||||
|
||||
<n-form-item label="启用推广奖励">
|
||||
<n-switch v-model:value="form.referral_reward_enabled" />
|
||||
</n-form-item>
|
||||
|
||||
<template v-if="form.referral_reward_enabled">
|
||||
<n-form-item label="推广人奖励">
|
||||
<n-input-number v-model:value="form.promoter_amount" :min="0" :max="100000" style="width: 180px;" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="新用户奖励">
|
||||
<n-input-number v-model:value="form.referred_amount" :min="0" :max="100000" style="width: 180px;" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="推广等级积分">
|
||||
<n-input-number v-model:value="form.level_points" :min="0" :max="100000" style="width: 180px;" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="验证邮箱后发放">
|
||||
<n-switch v-model:value="form.require_verify" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="同 IP 日上限">
|
||||
<n-input-number v-model:value="form.ip_daily_cap" :min="0" :max="1000" style="width: 180px;" />
|
||||
</n-form-item>
|
||||
</template>
|
||||
</n-card>
|
||||
|
||||
<div class="aksrt-form-actions">
|
||||
<n-button type="primary" :loading="saving" @click="handleSave">保存配置</n-button>
|
||||
</div>
|
||||
</n-form>
|
||||
</UsersSectionPage>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import api from '@/api'
|
||||
import UsersSectionPage from '@/pages/users/UsersSectionPage.vue'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const creditSnapshot = ref({})
|
||||
|
||||
const captchaSummary = reactive({
|
||||
register: false,
|
||||
signin: false,
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
credits_enabled: true,
|
||||
currency_name: '',
|
||||
signin_enabled: true,
|
||||
signin_base: 1,
|
||||
signin_streak_text: '3|2\n7|5\n30|20',
|
||||
referral_reward_enabled: true,
|
||||
promoter_amount: 5,
|
||||
referred_amount: 2,
|
||||
level_points: 0,
|
||||
require_verify: true,
|
||||
ip_daily_cap: 3,
|
||||
})
|
||||
|
||||
const currencyLabel = computed(() => form.currency_name || '积分')
|
||||
|
||||
function extractData(response) {
|
||||
return response?.data ?? response ?? {}
|
||||
}
|
||||
|
||||
function formatStreak(streak) {
|
||||
const entries = Object.entries(streak || {})
|
||||
.map(([day, bonus]) => [Number(day), Number(bonus)])
|
||||
.filter(([day, bonus]) => Number.isFinite(day) && day > 0 && Number.isFinite(bonus) && bonus >= 0)
|
||||
.sort((left, right) => left[0] - right[0])
|
||||
|
||||
return entries.map(([day, bonus]) => `${day}|${bonus}`).join('\n')
|
||||
}
|
||||
|
||||
function parseStreakText(value) {
|
||||
const rows = String(value || '')
|
||||
.split(/\r?\n/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
const streak = {}
|
||||
rows.forEach((row) => {
|
||||
const parts = row.split('|').map((item) => item.trim())
|
||||
if (parts.length !== 2) {
|
||||
return
|
||||
}
|
||||
|
||||
const day = Number(parts[0])
|
||||
const bonus = Number(parts[1])
|
||||
if (!Number.isFinite(day) || !Number.isFinite(bonus) || day <= 0 || bonus < 0) {
|
||||
return
|
||||
}
|
||||
|
||||
streak[Math.trunc(day)] = Math.trunc(bonus)
|
||||
})
|
||||
|
||||
return streak
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [creditRes, captchaRes] = await Promise.all([
|
||||
api.getCreditSettings(),
|
||||
api.getPermissionsLoginSettings(),
|
||||
])
|
||||
|
||||
const creditData = extractData(creditRes)
|
||||
const captchaData = extractData(captchaRes)
|
||||
const dailySignin = creditData.daily_signin || {}
|
||||
const referralReward = creditData.referral_reward || {}
|
||||
const enabledScenarios = Array.isArray(captchaData.enabled_scenarios)
|
||||
? captchaData.enabled_scenarios
|
||||
: []
|
||||
const sceneOn = (key) => enabledScenarios.includes(key)
|
||||
|
||||
creditSnapshot.value = creditData
|
||||
|
||||
form.credits_enabled = creditData.enabled !== false
|
||||
form.currency_name = creditData.currency_name || ''
|
||||
form.signin_enabled = dailySignin.enabled !== false
|
||||
form.signin_base = Number(dailySignin.base || 0)
|
||||
form.signin_streak_text = formatStreak(dailySignin.streak || { 3: 2, 7: 5, 30: 20 })
|
||||
form.referral_reward_enabled = referralReward.enabled !== false
|
||||
form.promoter_amount = Number(referralReward.promoter_amount || 0)
|
||||
form.referred_amount = Number(referralReward.referred_amount || 0)
|
||||
form.level_points = Number(referralReward.level_points || 0)
|
||||
form.require_verify = referralReward.require_verify !== false
|
||||
form.ip_daily_cap = Number(referralReward.ip_daily_cap || 0)
|
||||
|
||||
captchaSummary.register = !!captchaData.enabled && !!captchaData.active_provider && sceneOn('register')
|
||||
captchaSummary.signin = !!captchaData.enabled && !!captchaData.active_provider && sceneOn('signin')
|
||||
} catch (error) {
|
||||
message.error(error.message || '加载签到奖励配置失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
const nextCredit = {
|
||||
...creditSnapshot.value,
|
||||
daily_signin: {
|
||||
...(creditSnapshot.value.daily_signin || {}),
|
||||
enabled: form.signin_enabled,
|
||||
base: Number(form.signin_base || 0),
|
||||
streak: parseStreakText(form.signin_streak_text),
|
||||
},
|
||||
referral_reward: {
|
||||
...(creditSnapshot.value.referral_reward || {}),
|
||||
enabled: form.referral_reward_enabled,
|
||||
promoter_amount: Number(form.promoter_amount || 0),
|
||||
referred_amount: Number(form.referred_amount || 0),
|
||||
level_points: Number(form.level_points || 0),
|
||||
require_verify: form.require_verify,
|
||||
ip_daily_cap: Number(form.ip_daily_cap || 0),
|
||||
},
|
||||
}
|
||||
|
||||
await api.saveCreditSettings(nextCredit)
|
||||
|
||||
creditSnapshot.value = nextCredit
|
||||
message.success('签到奖励配置已保存')
|
||||
} catch (error) {
|
||||
message.error(error.message || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
Reference in new issue
Block a user