refactor(admin): clean up audit/permission/paid content config

- P1: remove the duplicate "审核联动" (ai_moderate_background/submit)
  block from the attachment permissions page; those settings live in
  the "评论审核" / API content audit surfaces.
- P2: rename the "内容审核" submenu to "评论审核" (permissions domain).
- P4: remove the "下载/付费内容中心" page and its schema/route chain;
  hardcode runtime policy so download always supports credit purchase
  and paid content is limited to posts. Drop the now-dead
  cms_features.download_credit flag and parse_slug_list helper.
This commit is contained in:
2026-08-13 01:14:21 +08:00
parent 2f4ce790a7
commit b4a4b295bc
16 changed files with 33 additions and 461 deletions
@@ -10,7 +10,6 @@ export const permissionsApiMethods = {
savePermissionsLoginSettings(data) { return this.saveSchemaPage('permissions', 'login', data) },
getPermissionsModerationSettings() { return this.getSchemaPage('permissions', 'moderation') },
savePermissionsModerationSettings(data) { return this.saveSchemaPage('permissions', 'moderation', data) },
getCapabilities() { return this.request('/permissions/capabilities') },
saveCapabilities(user_caps) { return this.request('/permissions/capabilities', { method: 'POST', body: JSON.stringify({ user_caps }) }) },
+1 -1
View File
@@ -15,7 +15,7 @@ const SECTION_DEFAULT_CHILDREN = {
'post-list': 'post-list-page',
permissions: 'permissions-comment',
users: 'users-auth',
payment: 'payment-paid',
payment: 'payment-recharge',
extensions: 'extensions-ip',
tools: 'tools-import-export',
'theme-info': 'theme-info-dashboard',
@@ -6,15 +6,6 @@ export const paymentSection = {
icon: renderFaIcon('fas fa-wallet'),
route: '/payment',
children: [
customPage({
key: 'payment-paid',
name: 'PaymentPaid',
label: '下载/付费内容中心',
route: '/payment/paid',
title: '下载/付费内容中心',
desc: '资源下载门槛、付费内容策略和人工交付说明。',
component: 'ResourceDownload',
}),
schemaPage({
key: 'payment-recharge',
name: 'PaymentRecharge',
@@ -30,9 +30,9 @@ export const permissionsSection = {
schemaPage({
key: 'permissions-moderation',
name: 'PermissionsModeration',
label: '内容审核',
label: '评论审核',
route: '/permissions/moderation',
title: '内容审核',
title: '评论审核',
desc: '评论审核、反垃圾、限流与风控配置。',
canonicalDomain: 'permissions',
section: 'moderation',
-221
View File
@@ -1,221 +0,0 @@
<template>
<div class="aksrt-page-container">
<PageHeader
title="下载/付费内容中心"
desc="统一管理资源下载门槛、积分购买模式开关、角色免扣和人工交付说明。付费解锁走收银台订单,下载 token 不再按次扣费。"
>
<template #actions>
<n-space>
<n-button quaternary @click="go('/permissions/capabilities')">基本权限</n-button>
<n-button quaternary @click="go('/payment/gateway')">收款接口</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.download_credit ? 'warning' : 'default'">
积分购买模式:{{ form.download_credit ? '已启用' : '已关闭' }}
</n-tag>
<n-tag round type="info">
默认积分价:{{ form.download_default_cost }} {{ currencyName }}
</n-tag>
<n-tag round :type="form.payment_vip_free_download ? 'success' : 'default'">
角色白名单免费:{{ form.payment_vip_free_download ? '已启用' : '未启用' }}
</n-tag>
<n-tag round type="info">
Token TTL:10 分钟
</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.download_credit" />
<template #feedback>
<span class="aksrt-hint-text">开启后,文章 pay_modo=积分 可走收银台一次购买解锁;下载 token 不再按次扣积分。关闭时积分模式资源按免费/需登录策略处理。</span>
</template>
</n-form-item>
<n-form-item :label="`默认积分价(${currencyName})`">
<n-input-number v-model:value="form.download_default_cost" :min="0" :max="100000" style="width: 180px;" />
<template #feedback>
<span class="aksrt-hint-text">仅作运营参考/兼容字段;实际价格以文章 _aksrt_pay 的积分价/现金价为准。角色白名单命中时免费下载。</span>
</template>
</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-input v-model:value="form.payment_paid_post_types" placeholder="post,course,doc" />
<template #feedback>
<span class="aksrt-hint-text">逗号分隔,标记纳入统一付费策略的内容类型。</span>
</template>
</n-form-item>
<n-form-item label="角色白名单免扣下载">
<n-switch v-model:value="form.payment_vip_free_download" />
</n-form-item>
<n-form-item label="免扣角色列表" v-if="form.payment_vip_free_download">
<n-input v-model:value="form.payment_vip_exempt_roles" placeholder="administrator,editor,vip" />
<template #feedback>
<span class="aksrt-hint-text">命中这些 WordPress role 的用户可免费下载(无需购买)。</span>
</template>
</n-form-item>
<n-form-item label="前台付费提示">
<n-input
v-model:value="form.payment_paid_catalog_note"
type="textarea"
:autosize="{ minRows: 3, maxRows: 6 }"
placeholder="可填写资源购买须知、交付说明或人工确认提示。"
/>
</n-form-item>
<n-form-item label="人工交付备注">
<n-input
v-model:value="form.payment_manual_delivery_note"
type="textarea"
:autosize="{ minRows: 3, maxRows: 6 }"
/>
</n-form-item>
</n-form>
</n-card>
<n-card class="aksrt-section-card">
<n-alert type="info" :show-icon="false">
当前收款模式:{{ gatewayModeLabel }}。充值、VIP、返佣和分成页均已提供稳定结构,订单和结算台账直接衔接这些字段。
</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 featureSnapshot = ref({})
const creditSnapshot = ref({})
const overview = ref({})
const form = reactive({
download_credit: true,
download_default_cost: 0,
payment_paid_post_types: 'post',
payment_vip_free_download: false,
payment_vip_exempt_roles: '',
payment_paid_catalog_note: '',
payment_manual_delivery_note: '付费或人工确认内容请在订单备注中记录交付方式。',
})
const currencyName = computed(() => overview.value.currency_name || creditSnapshot.value.currency_name || '积分')
const gatewayModeLabel = computed(() => {
const mode = overview.value.gateway?.mode || 'manual_review'
if (mode === 'test_gateway') return '测试网关'
if (mode === 'adapter_ready') return '适配器就绪'
return '人工确认'
})
function go(path) {
router.push(path)
}
function extractData(response) {
return response?.data ?? response ?? {}
}
async function load() {
loading.value = true
try {
const [featureRes, creditRes, schemaRes, overviewRes] = await Promise.all([
api.getCmsFeatures(),
api.getCreditSettings(),
api.getSchemaPage('payment', 'paid'),
api.getPaymentOverview(),
])
const featureData = featureRes?.cms_features ?? featureRes?.data?.cms_features ?? {}
const creditData = extractData(creditRes)
const schemaData = extractData(schemaRes)
featureSnapshot.value = featureData
creditSnapshot.value = creditData
overview.value = extractData(overviewRes)
form.download_credit = featureData.download_credit !== false
form.download_default_cost = Number(creditData.download_default_cost || 0)
form.payment_paid_post_types = schemaData.payment_paid_post_types || 'post'
form.payment_vip_free_download = !!schemaData.payment_vip_free_download
form.payment_vip_exempt_roles = schemaData.payment_vip_exempt_roles || ''
form.payment_paid_catalog_note = schemaData.payment_paid_catalog_note || ''
form.payment_manual_delivery_note = schemaData.payment_manual_delivery_note || '付费或人工确认内容请在订单备注中记录交付方式。'
} catch (error) {
message.error(error.message || '加载下载/付费内容中心失败')
} finally {
loading.value = false
}
}
async function handleSave() {
saving.value = true
try {
const nextFeatures = {
...featureSnapshot.value,
download_credit: form.download_credit,
}
const nextCredits = {
...creditSnapshot.value,
download_default_cost: Number(form.download_default_cost || 0),
}
await Promise.all([
api.saveCmsFeatures(nextFeatures),
api.saveCreditSettings(nextCredits),
api.saveSchemaPage('payment', 'paid', {
payment_paid_post_types: form.payment_paid_post_types,
payment_vip_free_download: form.payment_vip_free_download,
payment_vip_exempt_roles: form.payment_vip_exempt_roles,
payment_paid_catalog_note: form.payment_paid_catalog_note,
payment_manual_delivery_note: form.payment_manual_delivery_note,
}),
])
featureSnapshot.value = nextFeatures
creditSnapshot.value = nextCredits
message.success('下载/付费内容中心已保存')
} catch (error) {
message.error(error.message || '保存失败')
} finally {
saving.value = false
}
}
onMounted(load)
</script>
@@ -7,7 +7,6 @@
:links="[
{ label: '余额充值', path: '/payment/recharge' },
{ label: '充值卡', path: '/payment/card-pass' },
{ label: '付费内容', path: '/payment/paid' },
]"
>
<template #before-form>
@@ -70,28 +70,6 @@
</n-form>
</n-card>
<!-- AI Moderation -->
<n-card class="aksrt-section-card">
<div class="aksrt-section-header">
<h3>审核联动</h3>
</div>
<n-form :model="form" label-placement="left" label-width="200" class="aksrt-form">
<n-form-item label="背景图接入 AI 审核">
<n-switch v-model:value="form.ai_moderate_background" />
<template #feedback>
<span class="aksrt-hint-text">命中高风险时会阻断上传,并保留上一次背景图。</span>
</template>
</n-form-item>
<n-form-item label="投稿图片接入 AI 审核">
<n-switch v-model:value="form.ai_moderate_submit" />
<template #feedback>
<span class="aksrt-hint-text">投稿中插入的图片也会经过内容安全审核。</span>
</template>
</n-form-item>
</n-form>
</n-card>
<div class="aksrt-form-actions">
<n-button type="primary" :loading="saving" @click="handleSave">保存配置</n-button>
</div>
@@ -112,15 +90,12 @@ const message = useMessage()
const loading = ref(true)
const saving = ref(false)
const moderationSnapshot = ref({})
const form = reactive({
avatar_max_size: 2,
submit_image_max_size: 5,
comment_image_max_size: 2,
resource_max_size: 50,
ai_moderate_background: true,
ai_moderate_submit: false,
})
const summaryItems = computed(() => [
@@ -139,24 +114,15 @@ function goModeration() { router.push('/permissions/moderation') }
async function load() {
loading.value = true
try {
const [permissionRes, moderationRes] = await Promise.all([
api.getPermissionsAttachmentSettings(),
api.getPermissionsModerationSettings(),
])
const permissionRes = await api.getPermissionsAttachmentSettings()
const permission = permissionRes?.data ?? permissionRes ?? {}
const moderation = moderationRes?.data ?? moderationRes ?? {}
// Map permission fields
form.avatar_max_size = Number(permission.avatar_max_size || 2)
form.submit_image_max_size = Number(permission.submit_image_max_size || 5)
form.comment_image_max_size = Number(permission.comment_image_max_size || 2)
form.resource_max_size = Number(permission.resource_max_size || 50)
form.ai_moderate_background = moderation.ai_moderate_background !== false
form.ai_moderate_submit = !!moderation.ai_moderate_submit
moderationSnapshot.value = { ...moderation }
} catch (error) {
message.error(error.message || '加载附件权限失败')
} finally {
@@ -167,24 +133,12 @@ async function load() {
async function handleSave() {
saving.value = true
try {
await Promise.all([
api.savePermissionsAttachmentSettings({
avatar_max_size: form.avatar_max_size,
submit_image_max_size: form.submit_image_max_size,
comment_image_max_size: form.comment_image_max_size,
resource_max_size: form.resource_max_size,
}),
api.savePermissionsModerationSettings({
...moderationSnapshot.value,
ai_moderate_background: form.ai_moderate_background,
ai_moderate_submit: form.ai_moderate_submit,
}),
])
moderationSnapshot.value = {
...moderationSnapshot.value,
ai_moderate_background: form.ai_moderate_background,
ai_moderate_submit: form.ai_moderate_submit,
}
await api.savePermissionsAttachmentSettings({
avatar_max_size: form.avatar_max_size,
submit_image_max_size: form.submit_image_max_size,
comment_image_max_size: form.comment_image_max_size,
resource_max_size: form.resource_max_size,
})
message.success('附件治理配置已保存')
} catch (error) {
message.error(error.message || '保存失败')
@@ -13,7 +13,6 @@ export const corePageLoaders = {
PermissionsCapabilities: () => import('@/pages/permissions/PermissionsCapabilities.vue'),
AttachmentPermissions: () => import('@/pages/permissions/AttachmentPermissions.vue'),
CmsCredits: () => import('@/pages/CmsCredits.vue'),
ResourceDownload: () => import('@/pages/ResourceDownload.vue'),
ThemeInfoPage: () => import('@/pages/theme/ThemeInfoPage.vue'),
ThemeAuthorizationPage: () => import('@/pages/theme/ThemeAuthorizationPage.vue'),
DocsPage: () => import('@/pages/docs/DocsPage.vue'),
@@ -2,7 +2,6 @@
* Payment & Paid domain schemas.
*/
import { paidSchema } from './paid.js'
import { rechargeSchema } from './recharge.js'
import { vipSchema } from './vip.js'
import { revenueSchema } from './revenue.js'
@@ -13,7 +12,6 @@ import { cardPassSchema } from './cardPass.js'
import { withdrawalSchema } from './withdrawal.js'
export const paymentSchemas = {
paid: paidSchema,
recharge: rechargeSchema,
vip: vipSchema,
revenue: revenueSchema,
@@ -1,85 +0,0 @@
export const paidSchema = {
key: 'payment.paid',
title: '下载/付费内容中心',
icon: 'fas fa-download',
description: '统一管理资源下载、付费内容、角色免扣和人工交付说明。',
defaults: {
payment_paid_post_types: 'post',
payment_vip_free_download: false,
payment_vip_exempt_roles: '',
payment_paid_catalog_note: '',
payment_manual_delivery_note: '付费或人工确认内容请在订单备注中记录交付方式。',
pay_mark: '付费',
payment_currency_symbol: '¥',
pay_show_user: true,
pay_show_paycount: true,
pay_box_position: 'top',
payment_download_enable: false,
payment_down_alone_page: false,
payment_download_remote_s: true,
payment_download_verify_nonce: true,
payment_download_user_log: 50,
payment_download_post_log: 50,
payment_download_remote_size: 2048,
payment_download_remote_ext: 'zip,rar,7z',
coupon_post_s: false,
coupon_desc: '',
},
groups: [
{
key: 'strategy',
title: '付费内容策略',
icon: 'fas fa-layer-group',
fields: [
{ key: 'payment_paid_post_types', label: '受管内容类型', type: 'input', default: 'post', help: '逗号分隔,例如 post,course,doc' },
{ key: 'payment_vip_free_download', label: '角色白名单免扣下载', type: 'switch', default: false },
{ key: 'payment_vip_exempt_roles', label: '免扣角色列表', type: 'input', default: '', help: '逗号分隔的 WordPress role slug,例如 administrator,editor,vip' },
],
},
{
key: 'delivery',
title: '交付说明',
icon: 'fas fa-receipt',
fields: [
{ key: 'payment_paid_catalog_note', label: '前台付费提示', type: 'textarea', default: '', help: '显示在资源/付费内容页的运营说明。' },
{ key: 'payment_manual_delivery_note', label: '人工交付备注', type: 'textarea', default: '付费或人工确认内容请在订单备注中记录交付方式。', help: '人工确认或离线交付流程的标准说明。' },
],
},
{
key: 'display',
title: '展示设置',
icon: 'fas fa-eye',
fields: [
{ key: 'pay_mark', label: '付费标记', type: 'input', default: '付费' },
{ key: 'payment_currency_symbol', label: '货币符号', type: 'input', default: '¥', help: '商品价格前缀,如 ¥、$、¥' },
{ key: 'pay_show_user', label: '显示购买用户', type: 'switch', default: true },
{ key: 'pay_show_paycount', label: '显示购买次数', type: 'switch', default: true },
{ key: 'pay_box_position', label: '付费框位置', type: 'radio', default: 'top', options: [{ label: '文章框顶部(封面后)', value: 'box_top' }, { label: '正文顶部', value: 'top' }, { label: '正文底部', value: 'bottom' }, { label: '文章框底部(相关前)', value: 'box_bottom' }] },
],
},
{
key: 'download',
title: '下载设置',
icon: 'fas fa-cloud-download-alt',
fields: [
{ key: 'payment_download_enable', label: '启用下载功能', type: 'switch', default: false },
{ key: 'payment_down_alone_page', label: '独立下载页面', type: 'switch', default: false, help: '开启后,已获权限的下载入口跳转到独立下载页(模板:单篇资源下载),而不是在正文内联显示全部下载按钮。', dependsOn: { key: 'payment_download_enable', value: true } },
{ key: 'payment_download_remote_s', label: '远程文件下载', type: 'switch', default: true, dependsOn: { key: 'payment_download_enable', value: true } },
{ key: 'payment_download_verify_nonce', label: '下载鉴权', type: 'switch', default: true, dependsOn: { key: 'payment_download_enable', value: true } },
{ key: 'payment_download_user_log', label: '用户下载记录条数', type: 'number', default: 50, min: 0, max: 9999, help: '0 为不保存', dependsOn: { key: 'payment_download_enable', value: true } },
{ key: 'payment_download_post_log', label: '文章下载记录条数', type: 'number', default: 50, min: 0, max: 9999, help: '0 为不保存', dependsOn: { key: 'payment_download_enable', value: true } },
{ key: 'payment_download_remote_size', label: '远程文件大小限制(KB)', type: 'number', default: 2048, min: 1, max: 999999, dependsOn: { key: 'payment_download_remote_s', value: true } },
{ key: 'payment_download_remote_ext', label: '远程文件格式限制', type: 'input', default: 'zip,rar,7z', help: '逗号分隔扩展名', dependsOn: { key: 'payment_download_remote_s', value: true } },
],
},
{
key: 'coupon',
title: '内容优惠码',
icon: 'fas fa-ticket-alt',
fields: [
{ key: 'coupon_post_s', label: '全站内容允许优惠码', type: 'switch', default: false, help: '开启后所有付费内容收银台可用优惠码;关闭时仅单篇勾选「允许优惠码」的文章可用。' },
{ key: 'coupon_desc', label: '优惠码说明', type: 'textarea', default: '', dependsOn: { key: 'coupon_post_s', value: true }, help: '展示在内容收银台优惠码输入框下方。' },
],
},
],
}
@@ -120,7 +120,7 @@ export const permissionsSchemas = {
*/
moderation: {
key: 'permissions.moderation',
title: '内容审核',
title: '评论审核',
icon: 'fas fa-gavel',
description: '管理评论审核、反垃圾、限流与拦截配置。',
defaults: {