用户功能设置收口与废弃后台链路清理
This commit is contained in:
@@ -7,9 +7,6 @@ export const toolsApiMethods = {
|
||||
detectUpdate() { return this.request('/tools/update-check', { method: 'POST', body: JSON.stringify({}) }) },
|
||||
dismissUpdate(version) { return this.request('/tools/update-dismiss', { method: 'POST', body: JSON.stringify({ version }) }) },
|
||||
|
||||
getEmailRollbackUsers() { return this.request('/email-rollback/users') },
|
||||
executeEmailRollback(id) { return this.request(`/email-rollback/users/${id}/rollback`, { method: 'POST' }) },
|
||||
|
||||
/* ================================================================
|
||||
* Settings Management (Import / Export / Reset / Health / Schema)
|
||||
* ================================================================ */
|
||||
|
||||
@@ -36,15 +36,6 @@ export const extensionsSection = {
|
||||
canonicalDomain: 'extensions',
|
||||
section: 'push',
|
||||
}),
|
||||
customPage({
|
||||
key: 'extensions-tools',
|
||||
name: 'ExtensionsTools',
|
||||
label: '系统工具',
|
||||
route: '/extensions/tools',
|
||||
component: 'ExtensionTools',
|
||||
title: '系统工具',
|
||||
desc: '额外运维工具与扩展能力。',
|
||||
}),
|
||||
customPage({
|
||||
key: 'extensions-ai',
|
||||
name: 'ExtensionAI',
|
||||
|
||||
@@ -25,16 +25,6 @@ export const toolsSection = {
|
||||
canonicalDomain: 'tools',
|
||||
section: 'backup',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'tools-frontend-modal-rollout',
|
||||
name: 'ToolsFrontendModalRollout',
|
||||
label: '前台弹窗灰度',
|
||||
route: '/tools/frontend-modal-rollout',
|
||||
title: '前台弹窗灰度',
|
||||
desc: '按领域控制前台弹窗新流程的分阶段发布。',
|
||||
canonicalDomain: 'tools',
|
||||
section: 'frontend-modal-rollout',
|
||||
}),
|
||||
customPage({
|
||||
key: 'tools-health',
|
||||
name: 'ToolsHealth',
|
||||
@@ -44,15 +34,6 @@ export const toolsSection = {
|
||||
desc: '查看主题环境、数据表和系统诊断状态。',
|
||||
component: 'ToolsHealth',
|
||||
}),
|
||||
customPage({
|
||||
key: 'tools-email-rollback',
|
||||
name: 'ToolsEmailRollback',
|
||||
label: '邮件回滚',
|
||||
route: '/tools/email-rollback',
|
||||
title: '邮件回滚',
|
||||
desc: '查看 14 天邮箱变更窗口内的账号并执行回滚。',
|
||||
component: 'ToolsEmailRollback',
|
||||
}),
|
||||
customPage({
|
||||
key: 'tools-update',
|
||||
name: 'ToolsUpdate',
|
||||
|
||||
@@ -30,28 +30,16 @@ export const usersSection = {
|
||||
canonicalDomain: 'users',
|
||||
section: 'oauth',
|
||||
}),
|
||||
customPage({
|
||||
schemaPage({
|
||||
key: 'users-features',
|
||||
name: 'UsersFeatures',
|
||||
label: '用户功能',
|
||||
route: '/users/features',
|
||||
component: 'UsersFeatures',
|
||||
title: '用户功能',
|
||||
desc: '用户中心与个人主页模块配置。',
|
||||
desc: '配置用户头像、封面和默认签名。',
|
||||
canonicalDomain: 'users',
|
||||
section: 'features',
|
||||
}),
|
||||
customPage({
|
||||
key: 'users-signin',
|
||||
name: 'UsersSignin',
|
||||
label: '签到奖励',
|
||||
route: '/users/signin',
|
||||
component: 'UsersSignin',
|
||||
title: '签到奖励',
|
||||
desc: '签到规则、连续签到奖励和签到页面配置。',
|
||||
canonicalDomain: 'users',
|
||||
section: 'signin',
|
||||
}),
|
||||
customPage({
|
||||
key: 'users-credits',
|
||||
name: 'UsersCredits',
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
<div class="aksrt-docs__action">
|
||||
<FaIcon icon="fa fas fa-wrench" :size="16" />
|
||||
<div>
|
||||
<strong>系统工具</strong>
|
||||
<p>导入导出、健康检查、迁移状态</p>
|
||||
<strong>工具</strong>
|
||||
<p>导入导出、健康检查、备份与更新</p>
|
||||
<router-link to="/tools/import-export">前往配置 →</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader title="系统工具" desc="额外运维工具与扩展能力。" />
|
||||
|
||||
<!-- Health Overview -->
|
||||
<n-card title="系统健康" size="small" class="aksrt-tools__section">
|
||||
<n-grid :cols="4" :x-gap="12" :y-gap="12">
|
||||
<n-grid-item v-for="item in healthItems" :key="item.key">
|
||||
<div class="aksrt-tools__health-card" :class="'aksrt-tools__health-card--' + item.status">
|
||||
<FaIcon :icon="item.icon" :size="18" :color="item.color" />
|
||||
<div>
|
||||
<strong>{{ item.value }}</strong>
|
||||
<p>{{ item.label }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</n-card>
|
||||
|
||||
<!-- Tool Categories -->
|
||||
<n-card title="工具集合" size="small" class="aksrt-tools__section">
|
||||
<n-grid :cols="3" :x-gap="12" :y-gap="12">
|
||||
<n-grid-item v-for="tool in tools" :key="tool.key">
|
||||
<div class="aksrt-tools__tool-card" @click="navigateTo(tool.route)">
|
||||
<div class="aksrt-tools__tool-icon" :style="{ background: tool.bg }">
|
||||
<FaIcon :icon="tool.icon" :size="18" color="#fff" />
|
||||
</div>
|
||||
<div class="aksrt-tools__tool-body">
|
||||
<strong>{{ tool.title }}</strong>
|
||||
<p>{{ tool.desc }}</p>
|
||||
</div>
|
||||
<FaIcon icon="fa fas fa-chevron-right" :size="12" color="#c0c4cc" />
|
||||
</div>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</n-card>
|
||||
|
||||
<!-- Service Status -->
|
||||
<n-card title="服务状态" size="small" class="aksrt-tools__section">
|
||||
<n-list bordered size="small">
|
||||
<n-list-item v-for="svc in services" :key="svc.key">
|
||||
<n-thing :title="svc.name" :description="svc.description">
|
||||
<template #avatar>
|
||||
<FaIcon :icon="svc.icon" :size="16" :color="svc.color" />
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<n-tag :type="svc.available ? 'success' : 'warning'" size="small">
|
||||
{{ svc.available ? '可用' : '未配置' }}
|
||||
</n-tag>
|
||||
</template>
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
</n-card>
|
||||
|
||||
<!-- Quick Maintenance -->
|
||||
<n-card title="手动维护" size="small" class="aksrt-tools__section">
|
||||
<n-space>
|
||||
<n-button @click="handleFlushCache">
|
||||
<template #icon><FaIcon icon="fa fas fa-magic" :size="12" /></template>
|
||||
清除缓存
|
||||
</n-button>
|
||||
<n-button @click="handleOptimizeDb">
|
||||
<template #icon><FaIcon icon="fa fas fa-database" :size="12" /></template>
|
||||
优化数据表
|
||||
</n-button>
|
||||
<n-button @click="handleRebuildIndex">
|
||||
<template #icon><FaIcon icon="fa fas fa-sync-alt" :size="12" /></template>
|
||||
重建索引
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import FaIcon from '@/components/form/FaIcon.vue'
|
||||
import api from '@/api/index.js'
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
|
||||
const healthItems = ref([
|
||||
{ key: 'php', icon: 'fa fas fa-code', color: 'var(--aksrt-color-primary)', value: '—', label: 'PHP 版本', status: 'ok' },
|
||||
{ key: 'wp', icon: 'fa fab fa-wordpress', color: '#722ed1', value: '—', label: 'WordPress', status: 'ok' },
|
||||
{ key: 'db', icon: 'fa fas fa-database', color: '#52c41a', value: '—', label: '数据库', status: 'ok' },
|
||||
{ key: 'uploads', icon: 'fa fas fa-folder-open', color: '#fa8c16', value: '—', label: '上传目录', status: 'ok' },
|
||||
])
|
||||
|
||||
const tools = ref([
|
||||
{ key: 'import-export', icon: 'fa fas fa-exchange-alt', bg: 'var(--aksrt-color-primary)', title: '导入导出', desc: '设置导入、导出和重置', route: '/tools/import-export' },
|
||||
{ key: 'health', icon: 'fa fas fa-heartbeat', bg: '#52c41a', title: '健康检查', desc: '系统状态检测和诊断', route: '/tools/health' },
|
||||
{ key: 'email-rollback', icon: 'fa fas fa-undo', bg: '#fa8c16', title: '邮箱回滚', desc: '邮箱变更 14 天内回滚', route: '/tools/email-rollback' },
|
||||
])
|
||||
|
||||
const services = ref([
|
||||
{ key: 'sms', icon: 'fa fas fa-comment', color: 'var(--aksrt-color-primary)', name: '短信服务', description: '验证码和通知短信', available: false },
|
||||
{ key: 'wechat', icon: 'fa fab fa-weixin', color: '#52c41a', name: '微信通知', description: '微信模板消息和订阅通知', available: false },
|
||||
{ key: 'captcha', icon: 'fa fas fa-shield-alt', color: '#722ed1', name: '验证码服务', description: '人机验证(极验/腾讯/阿里云)', available: false },
|
||||
{ key: 'push', icon: 'fa fas fa-paper-plane', color: '#fa8c16', name: '搜索引擎推送', description: '百度/Google 主动推送', available: false },
|
||||
{ key: 'audit', icon: 'fa fas fa-shield-alt', color: '#f5222d', name: '内容审核', description: '第三方内容安全审核 API', available: false },
|
||||
])
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await api.request('/tools/health')
|
||||
if (res?.data) {
|
||||
const d = res.data
|
||||
if (d.php_version) healthItems.value[0].value = d.php_version
|
||||
if (d.wp_version) healthItems.value[1].value = d.wp_version
|
||||
if (d.db_status) {
|
||||
healthItems.value[2].value = d.db_status
|
||||
healthItems.value[2].status = d.db_ok ? 'ok' : 'warn'
|
||||
}
|
||||
if (d.upload_dir) {
|
||||
healthItems.value[3].value = d.upload_dir
|
||||
healthItems.value[3].status = d.upload_ok ? 'ok' : 'warn'
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Endpoint may not exist yet
|
||||
}
|
||||
|
||||
// Check service availability from structured status
|
||||
try {
|
||||
const settingsRes = await api.request('/settings/extensions')
|
||||
if (settingsRes?.data) {
|
||||
const s = settingsRes.data
|
||||
// Prefer structured services if available
|
||||
const svc = settingsRes.services || {}
|
||||
services.value[0].available = !!(svc.sms?.available ?? s.sms_enabled)
|
||||
services.value[0].status = svc.sms?.provider || (s.sms_enabled ? '已启用' : '未配置')
|
||||
services.value[1].available = !!(svc.wechat?.available ?? s.wechat_enabled)
|
||||
services.value[1].status = svc.wechat?.provider || (s.wechat_enabled ? '已启用' : '未配置')
|
||||
services.value[2].available = !!(svc.captcha?.available ?? svc.captcha?.configured)
|
||||
services.value[2].status = svc.captcha?.provider || (svc.captcha?.enabled ? '已启用' : '未配置')
|
||||
services.value[3].available = !!(svc.push?.available ?? s.baidu_push_enabled)
|
||||
services.value[3].status = svc.push?.provider || (s.baidu_push_enabled ? '已启用' : '未配置')
|
||||
services.value[4].available = !!(svc.audit?.available ?? s.content_audit_enabled)
|
||||
services.value[4].status = svc.audit?.provider || (s.content_audit_enabled ? '已启用' : '未配置')
|
||||
}
|
||||
} catch {
|
||||
// Use defaults
|
||||
}
|
||||
})
|
||||
|
||||
function navigateTo(route) {
|
||||
router.push(route)
|
||||
}
|
||||
|
||||
async function handleFlushCache() {
|
||||
message.info('正在清除缓存...')
|
||||
try {
|
||||
const res = await api.request('/tools/maintenance', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ action: 'flush_cache' }),
|
||||
})
|
||||
message.success(res?.message || '缓存已清除')
|
||||
} catch (err) {
|
||||
message.error(err?.message || '清除缓存失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOptimizeDb() {
|
||||
message.info('正在优化数据表...')
|
||||
try {
|
||||
const res = await api.request('/tools/maintenance', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ action: 'optimize_db' }),
|
||||
})
|
||||
message.success(res?.message || '数据表已优化')
|
||||
} catch (err) {
|
||||
message.error(err?.message || '优化数据表失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRebuildIndex() {
|
||||
message.info('正在重建索引...')
|
||||
try {
|
||||
const res = await api.request('/tools/maintenance', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ action: 'rebuild_index' }),
|
||||
})
|
||||
message.success(res?.message || '索引重建完成')
|
||||
} catch (err) {
|
||||
message.error(err?.message || '重建索引失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-tools__section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.aksrt-tools__health-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 14px;
|
||||
border-radius: 8px;
|
||||
background: #fafafa;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.aksrt-tools__health-card strong {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: var(--aksrt-color-text);
|
||||
}
|
||||
|
||||
.aksrt-tools__health-card p {
|
||||
margin: 2px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--aksrt-color-muted);
|
||||
}
|
||||
|
||||
.aksrt-tools__health-card--warn {
|
||||
border-color: #faad14;
|
||||
background: #fffbe6;
|
||||
}
|
||||
|
||||
.aksrt-tools__tool-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
border-radius: 8px;
|
||||
background: #fafafa;
|
||||
border: 1px solid #f0f0f0;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-tools__tool-card:hover {
|
||||
border-color: var(--aksrt-color-primary);
|
||||
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1);
|
||||
}
|
||||
|
||||
.aksrt-tools__tool-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.aksrt-tools__tool-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.aksrt-tools__tool-body strong {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: var(--aksrt-color-text);
|
||||
}
|
||||
|
||||
.aksrt-tools__tool-body p {
|
||||
margin: 2px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--aksrt-color-muted);
|
||||
}
|
||||
</style>
|
||||
@@ -1,122 +0,0 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader
|
||||
title="邮件回滚"
|
||||
desc="查看 14 天邮箱变更窗口内的账号,并在必要时执行回滚。"
|
||||
>
|
||||
<template #actions>
|
||||
<n-space>
|
||||
<n-button quaternary @click="go('/tools/health')">健康检查</n-button>
|
||||
<n-button quaternary @click="load">刷新列表</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>
|
||||
<n-tag round type="info">总计 {{ total }}</n-tag>
|
||||
</div>
|
||||
|
||||
<div v-if="items.length" class="aksrt-rollback-list">
|
||||
<div v-for="item in items" :key="item.user_id" class="aksrt-rollback-list__item">
|
||||
<div>
|
||||
<strong>{{ item.display_name || item.login }}</strong>
|
||||
<p style="margin: 6px 0 0; color: #4b5565;">
|
||||
{{ item.current_email }} → {{ item.previous_email }}
|
||||
</p>
|
||||
<p style="margin: 6px 0 0; color: var(--aksrt-color-muted); font-size: 12px;">
|
||||
剩余窗口:{{ Math.max(0, Math.floor((item.expires_in || 0) / 3600)) }} 小时
|
||||
</p>
|
||||
</div>
|
||||
<n-button size="small" :loading="runningId === item.user_id" @click="handleRollback(item.user_id)">
|
||||
执行回滚
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<n-empty v-else description="当前没有可回滚的邮箱变更记录" />
|
||||
</n-card>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, 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 runningId = ref(0)
|
||||
const items = ref([])
|
||||
const total = ref(0)
|
||||
|
||||
function go(path) {
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
function extractData(response) {
|
||||
return response?.data ?? response ?? {}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = extractData(await api.getEmailRollbackUsers())
|
||||
items.value = Array.isArray(data.items) ? data.items : []
|
||||
total.value = Number(data.total || 0)
|
||||
} catch (error) {
|
||||
message.error(error.message || '加载邮件回滚列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRollback(userId) {
|
||||
runningId.value = userId
|
||||
try {
|
||||
const result = await api.executeEmailRollback(userId)
|
||||
if (result.code === 'success') {
|
||||
message.success(result.message || '回滚成功')
|
||||
await load()
|
||||
} else {
|
||||
message.error(result.message || '回滚失败')
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(error.message || '回滚失败')
|
||||
} finally {
|
||||
runningId.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-rollback-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.aksrt-rollback-list__item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid #e7ebf3;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.aksrt-rollback-list__item {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -7,7 +7,6 @@
|
||||
<template #actions>
|
||||
<n-space>
|
||||
<n-button quaternary @click="go('/tools/import-export')">导入导出</n-button>
|
||||
<n-button quaternary @click="go('/tools/email-rollback')">邮件回滚</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</PageHeader>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<n-button :loading="resetting" @click="handleReset">重置所选域</n-button>
|
||||
<n-button type="error" tertiary :loading="resettingAll" @click="handleResetAll">重置全部</n-button>
|
||||
</n-space>
|
||||
<p class="aksrt-tools-hint">仅支持重置 schema 托管的业务域。健康检查、迁移状态和邮件回滚属于运维面板,不参与按域回滚。</p>
|
||||
<p class="aksrt-tools-hint">仅支持重置 schema 托管的业务域。健康检查和迁移状态属于运维面板,不参与按域回滚。</p>
|
||||
</n-card>
|
||||
|
||||
<n-card class="aksrt-section-card">
|
||||
|
||||
@@ -92,7 +92,7 @@ const guides = [
|
||||
{ key: 'cdn', title: '静态资源与 CDN', desc: '核对前端资源地址与缓存策略', url: '#/global/common' },
|
||||
{ key: 'ip-geo', title: 'IP 归属地', desc: '验证 IP 查询服务与密钥配置', url: '#/extensions/ip' },
|
||||
{ key: 'payment', title: '支付与会员', desc: '核对网关、会员与充值配置', url: '#/payment/gateway' },
|
||||
{ key: 'credits', title: '积分与签到', desc: '检查积分规则、签到与推广奖励', url: '#/users/signin' },
|
||||
{ key: 'credits', title: '积分与签到', desc: '检查积分规则、签到与推广奖励', url: '#/users/credits' },
|
||||
{ key: 'badge', title: '等级与徽章', desc: '维护等级、认证与徽章体系', url: '#/users/badge' },
|
||||
]
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -1,4 +1,3 @@
|
||||
export const extensionsPageLoaders = {
|
||||
ExtensionTools: () => import('@/pages/extensions/ExtensionTools.vue'),
|
||||
ExtensionAI: () => import('@/pages/extensions/ExtensionAI.vue'),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export const toolsPageLoaders = {
|
||||
ToolsImportExport: () => import('@/pages/tools/ToolsImportExport.vue'),
|
||||
ToolsHealth: () => import('@/pages/tools/ToolsHealth.vue'),
|
||||
ToolsEmailRollback: () => import('@/pages/tools/ToolsEmailRollback.vue'),
|
||||
ToolsUpdate: () => import('@/pages/tools/ToolsUpdate.vue'),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
export const usersPageLoaders = {
|
||||
UsersFeatures: () => import('@/pages/users/UsersFeatures.vue'),
|
||||
UsersSignin: () => import('@/pages/users/UsersSignin.vue'),
|
||||
UsersVerify: () => import('@/pages/users/UsersVerify.vue'),
|
||||
UsersLevel: () => import('@/pages/users/UsersLevel.vue'),
|
||||
UsersBadge: () => import('@/pages/users/UsersBadge.vue'),
|
||||
|
||||
@@ -67,7 +67,7 @@ export const ipSchema = {
|
||||
{ label: '文章详细页', value: 'post' },
|
||||
],
|
||||
dependsOn: { key: 'ip_geo_enabled', value: true },
|
||||
help: '用户主页/用户中心还需在「用户 → 用户功能」开启「显示城市」;评论区还需在评论设置开启「评论城市」。',
|
||||
help: '用户主页和用户中心由此处直接控制;评论区还需在评论设置开启「评论城市」。',
|
||||
},
|
||||
{
|
||||
key: 'ip_geo_format',
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
export const emailRollbackSchema = {
|
||||
key: 'tools.email-rollback',
|
||||
title: '邮件回滚',
|
||||
icon: 'fa fas fa-undo',
|
||||
description: '批量回滚已发送的通知邮件。',
|
||||
defaults: {},
|
||||
groups: [
|
||||
{
|
||||
key: 'rollback',
|
||||
title: '邮件回滚',
|
||||
icon: 'fa fas fa-envelope-open',
|
||||
fields: [
|
||||
{
|
||||
key: 'rollback_type',
|
||||
label: '回滚类型',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '按时间范围', value: 'date_range' },
|
||||
{ label: '按邮件类型', value: 'email_type' },
|
||||
{ label: '全部回滚', value: 'all' },
|
||||
],
|
||||
},
|
||||
{ key: 'rollback_date_from', label: '开始日期', type: 'date' },
|
||||
{ key: 'rollback_date_to', label: '结束日期', type: 'date' },
|
||||
{ key: 'rollback_execute', label: '执行回滚', type: 'button', danger: true, help: '此操作不可逆' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
export const frontendModalRolloutSchema = {
|
||||
key: 'tools.frontend-modal-rollout',
|
||||
title: '前台弹窗灰度',
|
||||
icon: 'fa fas fa-bolt',
|
||||
description: '按领域控制前台弹窗新流程的分阶段发布。',
|
||||
defaults: {
|
||||
frontend_modal_rollout_identity: true,
|
||||
frontend_modal_rollout_order: true,
|
||||
frontend_modal_rollout_vip: true,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'rollout',
|
||||
title: '弹窗新流程开关',
|
||||
icon: 'fa fas fa-toggle-on',
|
||||
fields: [
|
||||
{
|
||||
key: 'frontend_modal_rollout_identity',
|
||||
label: '官方认证新流程',
|
||||
type: 'switch',
|
||||
help: '启用统一弹窗运行时与认证快照流程。',
|
||||
},
|
||||
{
|
||||
key: 'frontend_modal_rollout_order',
|
||||
label: '订单新流程',
|
||||
type: 'switch',
|
||||
help: '启用订单详情快速开壳与状态同步流程。',
|
||||
},
|
||||
{
|
||||
key: 'frontend_modal_rollout_vip',
|
||||
label: '会员支付新流程',
|
||||
type: 'switch',
|
||||
help: '启用统一会员购买、支付和结果同步流程。',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -5,14 +5,10 @@
|
||||
|
||||
import { importExportSchema } from './import-export.js'
|
||||
import { healthSchema } from './health.js'
|
||||
import { emailRollbackSchema } from './email-rollback.js'
|
||||
import { backupSchema } from './backup.js'
|
||||
import { frontendModalRolloutSchema } from './frontend-modal-rollout.js'
|
||||
|
||||
export const toolsSchemas = {
|
||||
'import-export': importExportSchema,
|
||||
health: healthSchema,
|
||||
'email-rollback': emailRollbackSchema,
|
||||
backup: backupSchema,
|
||||
'frontend-modal-rollout': frontendModalRolloutSchema,
|
||||
}
|
||||
|
||||
@@ -1,47 +1,48 @@
|
||||
const themeRoot = typeof window !== 'undefined' && window.aksrtAdminData?.themeUri
|
||||
? String(window.aksrtAdminData.themeUri).replace(/\/$/, '')
|
||||
: ''
|
||||
|
||||
export const featuresSchema = {
|
||||
key: 'users.features',
|
||||
title: '用户功能',
|
||||
icon: 'fa fas fa-user',
|
||||
description: '配置用户中心和个人主页的功能模块。',
|
||||
description: '配置用户头像、封面和默认签名。',
|
||||
defaults: {
|
||||
user_center_tabs: 'profile,level,posts,comments,favorites,vip,credits,revenue,auth',
|
||||
user_desc_std: '这家伙很懒…',
|
||||
user_city_s: true,
|
||||
user_city_type: 'province',
|
||||
user_join_day_my_name: '本站',
|
||||
user_cover_img: '',
|
||||
user_rewrite_slug: 'user',
|
||||
user_follow_s: true,
|
||||
user_online_s: true,
|
||||
user_last_active_s: true,
|
||||
avatar_upload_s: true,
|
||||
avatar_external: '',
|
||||
avatar_default_img: `${themeRoot}/assets/img/avatar-default.png`,
|
||||
user_cover_upload_s: true,
|
||||
user_cover_img: `${themeRoot}/assets/img/user_t.jpg`,
|
||||
user_desc_std: '这家伙很懒,什么都没有写...',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'center',
|
||||
title: '用户中心',
|
||||
icon: 'fa fas fa-user',
|
||||
key: 'profile-media',
|
||||
title: '用户资料',
|
||||
icon: 'fa fas fa-user-circle',
|
||||
fields: [
|
||||
{ key: 'user_center_tabs', label: '用户中心 Tab', type: 'input', default: 'profile,level,comments,favorites,vip,credits,revenue,auth', help: '逗号分隔。level=我的等级;creator=创作中心;revenue=推广收益中心;auth=官方认证;等级、VIP、订单、创作中心、收益中心和认证会按功能开关自动注入' },
|
||||
{ key: 'user_desc_std', label: '默认签名', type: 'input', default: '这家伙很懒…', help: '用户未设置个人简介时显示的默认文案' },
|
||||
{ key: 'user_city_s', label: '显示城市(IP 归属地)', type: 'switch', default: true, help: '根据用户登录 IP 在用户主页与用户中心显示归属地;需同时在「扩展 → IP归属地」启用并勾选对应展示位置' },
|
||||
{ key: 'avatar_upload_s', label: '允许用户上传头像', type: 'switch', default: true },
|
||||
{
|
||||
key: 'user_city_type',
|
||||
label: '位置精度',
|
||||
key: 'avatar_external',
|
||||
label: '外部头像源',
|
||||
type: 'radio',
|
||||
default: 'province',
|
||||
default: '',
|
||||
options: [
|
||||
{ label: '省份', value: 'province' },
|
||||
{ label: '省份+城市', value: 'province_city' },
|
||||
{ label: '国家+省份', value: 'country_province' },
|
||||
{ label: '关闭', value: '' },
|
||||
{ label: 'Gravatar', value: 'gravatar' },
|
||||
{ label: 'WeAvatar', value: 'weavatar' },
|
||||
{ label: 'Cravatar', value: 'cravatar' },
|
||||
{ label: '极客族 Gravatar 服务', value: 'geekzu' },
|
||||
{ label: 'loli Gravatar 服务', value: 'loli' },
|
||||
{ label: 'sep.cc Gravatar 服务', value: 'sep' },
|
||||
{ label: 'v2ex Gravatar 服务', value: 'v2ex' },
|
||||
],
|
||||
dependsOn: { key: 'user_city_s', value: true },
|
||||
help: '启用后,用户没有自定义头像时按邮箱获取外部头像。',
|
||||
},
|
||||
{ key: 'user_join_day_my_name', label: '站名', type: 'input', default: '本站', help: '用于「已加入 X 天」显示中的站点名称' },
|
||||
{ key: 'user_cover_img', label: '默认封面', type: 'media', default: '' },
|
||||
{ key: 'user_rewrite_slug', label: '用户页 slug', type: 'input', default: 'user' },
|
||||
{ key: 'user_follow_s', label: '启用关注功能', type: 'switch', default: true },
|
||||
{ key: 'user_online_s', label: '显示在线状态', type: 'switch', default: true },
|
||||
{ key: 'user_last_active_s', label: '显示最后活跃', type: 'switch', default: true },
|
||||
{ key: 'avatar_default_img', label: '用户默认头像', type: 'media', default: `${themeRoot}/assets/img/avatar-default.png`, help: '建议尺寸 100×100。' },
|
||||
{ key: 'user_cover_upload_s', label: '允许用户上传封面', type: 'switch', default: true },
|
||||
{ key: 'user_cover_img', label: '用户默认封面', type: 'media', default: `${themeRoot}/assets/img/user_t.jpg`, help: '建议尺寸 1000×400;用户未设置封面时显示此图。' },
|
||||
{ key: 'user_desc_std', label: '用户默认签名', type: 'input', default: '这家伙很懒,什么都没有写...', help: '用户未设置个人简介时显示的默认文案。' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -7,7 +7,6 @@ import { oauthSchema } from './oauth.js'
|
||||
import { notificationsSchema } from './notifications.js'
|
||||
import { creditsSchema } from './credits.js'
|
||||
import { featuresSchema } from './features.js'
|
||||
import { signinSchema } from './signin.js'
|
||||
import { verifySchema } from './verify.js'
|
||||
import { levelSchema } from './level.js'
|
||||
import { badgeSchema } from './badge.js'
|
||||
@@ -21,7 +20,6 @@ export const usersSchemas = {
|
||||
notifications: notificationsSchema,
|
||||
credits: creditsSchema,
|
||||
features: featuresSchema,
|
||||
signin: signinSchema,
|
||||
verify: verifySchema,
|
||||
level: levelSchema,
|
||||
badge: badgeSchema,
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
export const signinSchema = {
|
||||
key: 'users.signin',
|
||||
title: '签到奖励',
|
||||
icon: 'fa fas fa-calendar-check',
|
||||
description: '配置签到规则、连续签到奖励和签到页面。',
|
||||
defaults: {
|
||||
signin_enabled: true,
|
||||
signin_reward: 1,
|
||||
signin_consecutive_bonus: true,
|
||||
signin_consecutive_days: 7,
|
||||
signin_bonus_reward: 5,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'rules',
|
||||
title: '签到规则',
|
||||
icon: 'fa fas fa-calendar',
|
||||
fields: [
|
||||
{ key: 'signin_enabled', label: '启用签到', type: 'switch', default: true },
|
||||
{ key: 'signin_reward', label: '每日签到奖励', type: 'number', default: 1, min: 0, max: 100, dependsOn: { key: 'signin_enabled', value: true } },
|
||||
{ key: 'signin_consecutive_bonus', label: '连续签到加成', type: 'switch', default: true, dependsOn: { key: 'signin_enabled', value: true } },
|
||||
{ key: 'signin_consecutive_days', label: '加成周期 (天)', type: 'number', default: 7, min: 2, max: 30, dependsOn: { key: 'signin_consecutive_bonus', value: true } },
|
||||
{ key: 'signin_bonus_reward', label: '周期奖励', type: 'number', default: 5, min: 1, max: 100, dependsOn: { key: 'signin_consecutive_bonus', value: true } },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -409,13 +409,6 @@
|
||||
return window.AksrtModalCache.invalidate(tags);
|
||||
}
|
||||
|
||||
function rolloutEnabled(kind) {
|
||||
var config = window.aksrtData || window.aksrt || {};
|
||||
var rollout = config.frontendModalRollout;
|
||||
if (!rollout || typeof rollout[kind] === "undefined") return true;
|
||||
return rollout[kind] === true;
|
||||
}
|
||||
|
||||
window.AksrtModalRuntime = {
|
||||
open: open,
|
||||
openRemote: openRemote,
|
||||
@@ -424,7 +417,6 @@
|
||||
registerLoader: registerLoader,
|
||||
openLoader: openLoader,
|
||||
invalidate: invalidate,
|
||||
isRolloutEnabled: rolloutEnabled,
|
||||
getActive: function () { return active ? active.shell : null; },
|
||||
getState: function () { return active ? active.state : null; },
|
||||
};
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
// ===== VIP 积分兑换 =====
|
||||
function initVipPointsExchange() {
|
||||
var root = document.querySelector('[data-aksrt-vip-points]');
|
||||
if (!root || !window.AksrtModalRuntime.isRolloutEnabled('vip')) return;
|
||||
if (!root) return;
|
||||
var tip = root.querySelector('[data-aksrt-vip-points-tip]');
|
||||
var busy = false;
|
||||
var coordinator = window.AksrtVipPurchaseCoordinator;
|
||||
@@ -139,7 +139,7 @@
|
||||
// ===== VIP 开通 =====
|
||||
function initVipShop() {
|
||||
var root = document.querySelector('[data-aksrt-vip-shop]');
|
||||
if (!root || !window.AksrtModalRuntime.isRolloutEnabled('vip')) return;
|
||||
if (!root) return;
|
||||
var tip = root.querySelector('[data-aksrt-vip-tip]');
|
||||
var busy = false;
|
||||
var coordinator = window.AksrtVipPurchaseCoordinator;
|
||||
|
||||
@@ -293,7 +293,7 @@
|
||||
}
|
||||
|
||||
function initRoot(root) {
|
||||
if (instances.has(root) || !window.AksrtModalRuntime.isRolloutEnabled("order")) return;
|
||||
if (instances.has(root)) return;
|
||||
var state = {
|
||||
root: root,
|
||||
detailOrderNumber: "",
|
||||
|
||||
@@ -473,10 +473,6 @@
|
||||
}
|
||||
|
||||
function vipPointsPay() {
|
||||
if (!window.AksrtModalRuntime.isRolloutEnabled("vip")) {
|
||||
notyf("会员兑换暂不可用", "danger");
|
||||
return;
|
||||
}
|
||||
window.AksrtModalRuntime.openRemote({
|
||||
url: "/pay/vip/modal",
|
||||
query: { vip_level: 1, mode: "points" },
|
||||
@@ -505,10 +501,6 @@
|
||||
}
|
||||
|
||||
function vipPay(trigger) {
|
||||
if (!window.AksrtModalRuntime.isRolloutEnabled("vip")) {
|
||||
notyf("会员购买暂不可用", "danger");
|
||||
return;
|
||||
}
|
||||
var level = trigger
|
||||
? parseInt(trigger.getAttribute("vip-level") || trigger.getAttribute("data-vip-level") || "1", 10) || 1
|
||||
: 1;
|
||||
|
||||
@@ -318,7 +318,6 @@
|
||||
}
|
||||
|
||||
function open() {
|
||||
if (!runtime.isRolloutEnabled('identity')) return;
|
||||
stopPolling();
|
||||
abortCurrent();
|
||||
abortRefresh();
|
||||
|
||||
Reference in New Issue
Block a user