feat: remove forum feature (Phase 1)

This commit is contained in:
2026-08-02 20:02:44 +08:00
parent f3e4cdd9cf
commit 2bbd7c01a3
173 changed files with 51 additions and 27360 deletions
+1 -2
View File
@@ -94,8 +94,7 @@ class ApiClient {
async request(endpointOrConfig, options = {}) {
// Dual-signature support:
// request('/forum/overview')
// request({ url: '/aksrt/v1/forum/topics', method: 'GET', params: { limit: 20 } })
// request('/users/1/restrictions')
// request({ url: '/aksrt/v1/users/1/restrictions', method: 'GET' })
let endpoint, method, headers, params, body, rest
@@ -1,111 +0,0 @@
import { renderFaIcon, schemaPage } from './helpers'
// Forum SPA = config only. Ops moved to native CPT menus (Phase 4).
export const forumSection = {
key: 'forum',
label: '社区&论坛',
icon: renderFaIcon('fas fa-comments'),
route: '/forum',
children: [
schemaPage({
key: 'forum-settings-basic',
name: 'ForumSettingsBasic',
label: '基础设置',
route: '/forum/settings',
title: '论坛基础设置',
desc: '论坛总开关、管理员和名称定义。',
canonicalDomain: 'forum',
section: 'basic',
}),
schemaPage({
key: 'forum-settings-names',
name: 'ForumSettingsNames',
label: '名称定义',
route: '/forum/settings/names',
title: '名称定义',
desc: '自定义论坛各实体的显示名称。',
canonicalDomain: 'forum',
section: 'names',
}),
schemaPage({
key: 'forum-settings-homepage',
name: 'ForumSettingsHomepage',
label: '首页设置',
route: '/forum/settings/homepage',
title: '论坛首页设置',
desc: '首页 Tab 引擎、快速发帖入口和移动端交互配置。',
canonicalDomain: 'forum',
section: 'homepage',
}),
schemaPage({
key: 'forum-settings-board-page',
name: 'ForumSettingsBoardPage',
label: '版块页面',
route: '/forum/settings/board-page',
title: '版块页面设置',
desc: '版块页布局、Tab 栏目和移动端交互配置。',
canonicalDomain: 'forum',
section: 'board_page',
}),
schemaPage({
key: 'forum-settings-post-page',
name: 'ForumSettingsPostPage',
label: '帖子页面',
route: '/forum/settings/post-page',
title: '帖子页面设置',
desc: '面包屑、评分、高度限制和封面配置。',
canonicalDomain: 'forum',
section: 'post_page',
}),
schemaPage({
key: 'forum-settings-reply',
name: 'ForumSettingsReply',
label: '回复评论',
route: '/forum/settings/reply',
title: '回复评论设置',
desc: '回复翻页方式、工具栏功能和快捷回复配置。',
canonicalDomain: 'forum',
section: 'reply',
}),
schemaPage({
key: 'forum-settings-permissions',
name: 'ForumSettingsPermissions',
label: '权限配置',
route: '/forum/settings/permissions',
title: '论坛权限配置',
desc: '发帖限制、帖子付费和版块付费关注。',
canonicalDomain: 'forum',
section: 'permissions',
}),
schemaPage({
key: 'forum-settings-other',
name: 'ForumSettingsOther',
label: '其它设置',
route: '/forum/settings/other',
title: '论坛其它设置',
desc: '投票、版主申请说明、标题字数和 URL 别名。',
canonicalDomain: 'forum',
section: 'other',
}),
schemaPage({
key: 'forum-settings-upload',
name: 'ForumSettingsUpload',
label: '上传权限',
route: '/forum/settings/upload',
title: '上传权限设置',
desc: '图片、视频和文件上传大小与格式限制。',
canonicalDomain: 'forum',
section: 'upload',
}),
schemaPage({
key: 'forum-settings-credits',
name: 'ForumSettingsCredits',
label: '积分设置',
route: '/forum/settings/credits',
title: '论坛积分设置',
desc: '论坛行为积分奖励矩阵和每日上限。',
canonicalDomain: 'forum',
section: 'credits',
}),
],
}
@@ -4,8 +4,6 @@ import { postListSection } from './post-list'
import { permissionsSection } from './permissions'
import { usersSection } from './users'
import { paymentSection } from './payment'
import { shopSection } from './shop'
import { forumSection } from './forum'
import { extensionsSection } from './extensions'
import { themeInfoSection } from './theme-info'
import { docsSection } from './docs'
@@ -18,8 +16,6 @@ const SECTION_DEFAULT_CHILDREN = {
permissions: 'permissions-comment',
users: 'users-auth',
payment: 'payment-paid',
shop: 'shop-basic',
forum: 'forum-settings-basic',
extensions: 'extensions-ip',
tools: 'tools-import-export',
'theme-info': 'theme-info-dashboard',
@@ -47,8 +43,6 @@ export const ADMIN_SECTIONS = [
attachSectionDefaultRoute(permissionsSection),
attachSectionDefaultRoute(usersSection),
attachSectionDefaultRoute(paymentSection),
attachSectionDefaultRoute(shopSection),
attachSectionDefaultRoute(forumSection),
attachSectionDefaultRoute(extensionsSection),
attachSectionDefaultRoute(toolsSection),
attachSectionDefaultRoute(themeInfoSection),
@@ -372,7 +372,6 @@ const typeOptions = [
{ label: '主题切换', value: 'theme_mode' },
{ label: '页面二维码', value: 'qrcode' },
{ label: '发布入口', value: 'add' },
{ label: '购物车', value: 'shop_cart' },
{ label: 'QQ 客服', value: 'service_qq' },
{ label: '微信客服', value: 'service_wechat' },
{ label: '会员入口', value: 'pay_vip' },
@@ -397,7 +396,7 @@ const addPresetOptions = computed(() => {
})
// pay_vip always opens the site VIP modal on the front-end — no link required.
const needsLink = computed(() => ['custom', 'add', 'shop_cart'].includes(editingItem.type) && !editingItem.hover)
const needsLink = computed(() => ['custom', 'add'].includes(editingItem.type) && !editingItem.hover)
// Custom hover HTML only; VIP uses modal, not free-form hover content.
const supportsHover = computed(() => editingItem.type === 'custom')
const supportsActionMode = computed(() => ['service_qq', 'service_wechat'].includes(editingItem.type))
@@ -444,7 +443,6 @@ function applyTypeDefaults(type) {
service_wechat: { icon: 'fab fa-weixin', icon_dark: '', icon_light: '', text: '微信', desc: '扫码添加微信', link: '', hover: '', action_mode: 'link', qr_size: 120 },
pay_vip: { icon: 'fas fa-gem', icon_dark: '', icon_light: '', text: '会员', desc: '开通/升级会员弹窗', link: '', hover: '', action_mode: 'link', qr_size: 120 },
add: { icon: 'fas fa-plus', icon_dark: '', icon_light: '', text: '发布', desc: '发布内容', link: '', hover: '', action_mode: 'link', qr_size: 120 },
shop_cart: { icon: 'fas fa-shopping-cart', icon_dark: '', icon_light: '', text: '购物车', desc: '购物车入口', link: '', hover: '', action_mode: 'link', qr_size: 120 },
}
return map[type] || map.custom
}
@@ -186,8 +186,8 @@
</n-radio-group>
</n-form-item>
<n-form-item v-if="['link', 'msg', 'pay_vip', 'shop_cart'].includes(editingItem.type)" label="链接地址">
<n-input v-model:value="editingItem.link" :placeholder="editingItem.type === 'msg' ? '留空使用默认消息中心地址' : editingItem.type === 'pay_vip' ? '留空使用默认会员页地址' : editingItem.type === 'shop_cart' ? '留空使用默认购物车地址' : 'https:// 或 /path'" />
<n-form-item v-if="['link', 'msg', 'pay_vip'].includes(editingItem.type)" label="链接地址">
<n-input v-model:value="editingItem.link" :placeholder="editingItem.type === 'msg' ? '留空使用默认消息中心地址' : editingItem.type === 'pay_vip' ? '留空使用默认会员页地址' : 'https:// 或 /path'" />
</n-form-item>
<n-form-item v-if="editingItem.type === 'link'" label="新窗口打开">
@@ -312,7 +312,6 @@ const typeOptions = [
{ label: '搜索', value: 'search' },
{ label: '消息中心', value: 'msg' },
{ label: '开通会员', value: 'pay_vip' },
{ label: '购物车', value: 'shop_cart' },
]
const typeLabelMap = {
@@ -325,7 +324,6 @@ const typeLabelMap = {
search: '搜索',
msg: '消息中心',
pay_vip: '开通会员',
shop_cart: '购物车',
}
const typeDefaults = {
@@ -338,7 +336,6 @@ const typeDefaults = {
search: { icon: 'fas fa-search', text: '搜索' },
msg: { icon: 'fas fa-bell', text: '消息' },
pay_vip: { icon: 'fas fa-crown', text: '会员' },
shop_cart: { icon: 'fas fa-shopping-cart', text: '购物车' },
}
const enabledPreviewItems = computed(() => form.items.filter((item) => item.enabled))
@@ -360,7 +357,6 @@ function getItemSummary(item) {
if (item.type === 'auth') return '登录用户显示我的,游客显示登录'
if (item.type === 'msg') return '跳转到消息中心页面'
if (item.type === 'pay_vip') return item.link || '跳转到会员开通页'
if (item.type === 'shop_cart') return item.link || '跳转到购物车页面'
return item.link || '自定义链接按钮'
}
@@ -405,7 +401,7 @@ function applyTypeDefaults(type, force = false) {
const preset = typeDefaults[type] || typeDefaults.link
if (force || !editingItem.icon) editingItem.icon = preset.icon
if (force || !editingItem.text) editingItem.text = preset.text
if (!['link', 'msg', 'pay_vip', 'shop_cart'].includes(type)) editingItem.link = ''
if (!['link', 'msg', 'pay_vip'].includes(type)) editingItem.link = ''
if (type !== 'category') editingItem.category_id = null
if (type !== 'search') editingItem.search_default_type = 'post'
if (type !== 'add') editingItem.add_items = []
@@ -483,7 +479,7 @@ function saveItem() {
type: editingItem.type,
icon: editingItem.icon,
text: editingItem.text,
link: ['link', 'msg', 'pay_vip', 'shop_cart'].includes(editingItem.type) ? editingItem.link : '',
link: ['link', 'msg', 'pay_vip'].includes(editingItem.type) ? editingItem.link : '',
badge: (editingItem.badge || '').trim(),
badge_mode: editingItem.badge_mode,
category_id: editingItem.type === 'category' ? (editingItem.category_id || 0) : 0,
@@ -1,195 +0,0 @@
<template>
<div class="aksrt-page-container">
<PageHeader title="版块管理" desc="创建、编辑和维护论坛版块信息,并观察版主申请积压情况。" />
<n-card size="small" class="aksrt-forum-boards__section">
<n-space justify="space-between" align="center">
<div class="aksrt-forum-boards__summary">共 {{ boards.length }} 个版块</div>
<n-button type="primary" @click="openCreate">新增版块</n-button>
</n-space>
</n-card>
<n-card size="small" class="aksrt-forum-boards__section">
<n-data-table
:columns="columns"
:data="boards"
:loading="loading"
:bordered="false"
size="small"
:row-key="(row) => row.id"
/>
</n-card>
<n-modal :show="modalVisible" preset="card" style="width: 520px" :title="editingId ? '编辑版块' : '新增版块'" @update:show="modalVisible = $event">
<n-form ref="formRef" :model="form" :rules="rules" label-placement="top">
<n-form-item label="版块名称" path="name">
<n-input v-model:value="form.name" placeholder="输入版块名称" />
</n-form-item>
<n-form-item label="版块描述" path="description">
<n-input v-model:value="form.description" type="textarea" :rows="3" placeholder="描述主题方向和讨论边界" />
</n-form-item>
<n-form-item label="图标" path="icon">
<n-input v-model:value="form.icon" placeholder="例如:fas fa-comments" />
</n-form-item>
<n-form-item label="排序" path="sort_order">
<n-input-number v-model:value="form.sort_order" :min="0" style="width: 100%" />
</n-form-item>
</n-form>
<template #footer>
<n-space justify="end">
<n-button @click="modalVisible = false">取消</n-button>
<n-button type="primary" :loading="saving" @click="handleSave">保存</n-button>
</n-space>
</template>
</n-modal>
</div>
</template>
<script setup>
import { computed, h, onMounted, reactive, ref } from 'vue'
import { NButton, NSpace, NTag, useDialog, useMessage } from 'naive-ui'
import PageHeader from '@/components/PageHeader.vue'
import api from '@/api/index.js'
const message = useMessage()
const dialog = useDialog()
const loading = ref(false)
const saving = ref(false)
const boards = ref([])
const modalVisible = ref(false)
const editingId = ref(null)
const formRef = ref(null)
const form = reactive({
name: '',
description: '',
icon: 'fas fa-comments',
sort_order: 0,
})
const rules = {
name: { required: true, message: '请输入版块名称', trigger: 'blur' },
}
const columns = computed(() => [
{ title: '排序', key: 'sort_order', width: 70 },
{ title: '版块名称', key: 'name', minWidth: 180 },
{ title: '描述', key: 'description', ellipsis: { tooltip: true } },
{
title: '治理状态',
key: 'governance',
width: 180,
render: (row) => h(NSpace, { size: 'small' }, () => [
h(NTag, { size: 'small', type: 'info' }, () => `${row.post_count || 0} 主题`),
h(NTag, { size: 'small', type: 'success' }, () => `${row.moderator_count || 0} 版主`),
h(NTag, { size: 'small', type: (row.pending_application_count || 0) > 0 ? 'warning' : 'default' }, () => `${row.pending_application_count || 0} 申请`),
]),
},
{
title: '操作',
key: 'actions',
width: 150,
render: (row) => h(NSpace, { size: 'small' }, () => [
h(NButton, { size: 'tiny', quaternary: true, type: 'info', onClick: () => openEdit(row) }, () => '编辑'),
h(NButton, { size: 'tiny', quaternary: true, type: 'error', onClick: () => handleDelete(row) }, () => '删除'),
]),
},
])
function resetForm() {
form.name = ''
form.description = ''
form.icon = 'fas fa-comments'
form.sort_order = boards.value.length
}
async function loadBoards() {
loading.value = true
try {
const res = await api.request('/forum/boards')
boards.value = res?.data?.items || []
} catch (error) {
message.error(error?.message || '加载版块列表失败')
} finally {
loading.value = false
}
}
function openCreate() {
editingId.value = null
resetForm()
modalVisible.value = true
}
function openEdit(row) {
editingId.value = row.id
form.name = row.name || ''
form.description = row.description || ''
form.icon = row.icon || 'fas fa-comments'
form.sort_order = row.sort_order || 0
modalVisible.value = true
}
async function handleSave() {
try {
await formRef.value?.validate()
} catch {
return
}
saving.value = true
try {
if (editingId.value) {
await api.request(`/forum/boards/${editingId.value}`, {
method: 'POST',
data: { ...form },
})
message.success('版块已更新')
} else {
await api.request('/forum/boards', {
method: 'POST',
data: { ...form },
})
message.success('版块已创建')
}
modalVisible.value = false
await loadBoards()
} catch (error) {
message.error(error?.message || '版块保存失败')
} finally {
saving.value = false
}
}
function handleDelete(row) {
dialog.warning({
title: '确认删除版块',
content: `确定删除“${row.name}”吗?如果仍有主题,请先完成迁移。`,
positiveText: '删除',
negativeText: '取消',
onPositiveClick: async () => {
try {
await api.request(`/forum/boards/${row.id}`, { method: 'DELETE' })
message.success('版块已删除')
await loadBoards()
} catch (error) {
message.error(error?.message || '版块删除失败')
}
},
})
}
onMounted(loadBoards)
</script>
<style scoped>
.aksrt-forum-boards__section {
margin-bottom: 16px;
}
.aksrt-forum-boards__summary {
color: #6b7280;
font-size: 13px;
}
</style>
@@ -1,223 +0,0 @@
<template>
<div class="aksrt-page-container">
<PageHeader title="论坛概览" desc="从待审核主题、版主申请和版块状态三个视角观察论坛治理负载。" />
<div class="aksrt-forum-dashboard__stats">
<div v-for="card in statCards" :key="card.key" class="aksrt-forum-dashboard__stat-card">
<div class="aksrt-forum-dashboard__stat-icon" :style="{ background: card.color }">
<i :class="card.icon"></i>
</div>
<div class="aksrt-forum-dashboard__stat-info">
<div class="aksrt-forum-dashboard__stat-value">{{ stats[card.key] || 0 }}</div>
<div class="aksrt-forum-dashboard__stat-label">{{ card.label }}</div>
</div>
</div>
</div>
<div class="aksrt-forum-dashboard__grid">
<n-card size="small" class="aksrt-forum-dashboard__section">
<template #header>
<div class="aksrt-forum-dashboard__section-head">
<strong>待审核主题</strong>
<n-button size="small" quaternary type="primary" @click="goTopics">进入主题治理</n-button>
</div>
</template>
<n-data-table
v-if="pendingTopics.length"
:columns="topicColumns"
:data="pendingTopics"
:bordered="false"
size="small"
/>
<n-empty v-else description="暂无待审核主题" />
</n-card>
<n-card size="small" class="aksrt-forum-dashboard__section">
<template #header>
<div class="aksrt-forum-dashboard__section-head">
<strong>待处理版主申请</strong>
<n-button size="small" quaternary type="primary" @click="goApplications">进入申请处理</n-button>
</div>
</template>
<n-data-table
v-if="pendingApplications.length"
:columns="applicationColumns"
:data="pendingApplications"
:bordered="false"
size="small"
/>
<n-empty v-else description="暂无待处理申请" />
</n-card>
</div>
<n-card size="small" class="aksrt-forum-dashboard__section">
<template #header>
<div class="aksrt-forum-dashboard__section-head">
<strong>版块概况</strong>
<n-button size="small" quaternary type="primary" @click="goBoards">进入版块管理</n-button>
</div>
</template>
<n-data-table
v-if="boards.length"
:columns="boardColumns"
:data="boards"
:bordered="false"
size="small"
/>
<n-empty v-else description="暂无版块" />
</n-card>
</div>
</template>
<script setup>
import { h, onMounted, ref } from 'vue'
import { NButton, NTag, useMessage } from 'naive-ui'
import PageHeader from '@/components/PageHeader.vue'
import api from '@/api/index.js'
const message = useMessage()
const stats = ref({})
const boards = ref([])
const pendingTopics = ref([])
const pendingApplications = ref([])
const statCards = [
{ key: 'board_count', label: '版块数', icon: 'fas fa-th-large', color: '#dbeafe' },
{ key: 'topic_count', label: '公开主题', icon: 'fas fa-list-alt', color: '#dcfce7' },
{ key: 'pending_topic_count', label: '待审核主题', icon: 'fas fa-hourglass-half', color: '#fef3c7' },
{ key: 'pending_application_count', label: '待处理申请', icon: 'fas fa-user-shield', color: '#fee2e2' },
]
const topicColumns = [
{ title: '标题', key: 'title', minWidth: 260 },
{ title: '作者', key: 'author_name', width: 110, render: (row) => row.author?.display_name || '-' },
{ title: '版块', key: 'board_name', width: 120, render: (row) => row.boards?.[0]?.name || '-' },
{ title: '时间', key: 'date', width: 160 },
{
title: '操作',
key: 'actions',
width: 100,
render: (row) => h(NButton, { size: 'small', quaternary: true, type: 'info', onClick: () => goTopics() }, () => '治理'),
},
]
const applicationColumns = [
{ title: '版块', key: 'board_name', width: 140 },
{ title: '申请人', key: 'display_name', width: 140 },
{ title: '理由', key: 'reason', ellipsis: { tooltip: true } },
{ title: '时间', key: 'created_at', width: 160 },
{
title: '状态',
key: 'status',
width: 100,
render: (row) => h(NTag, { size: 'small', type: 'warning' }, () => row.status_label || row.status || '-'),
},
]
const boardColumns = [
{ title: '版块名称', key: 'name', minWidth: 180 },
{ title: '主题数', key: 'post_count', width: 90 },
{ title: '版主数', key: 'moderator_count', width: 90 },
{ title: '待处理申请', key: 'pending_application_count', width: 120 },
]
async function loadOverview() {
try {
const res = await api.request('/forum/overview')
const data = res?.data || {}
stats.value = data.stats || {}
boards.value = data.boards || []
pendingTopics.value = data.pending_topics || []
pendingApplications.value = data.pending_applications || []
} catch (error) {
message.error(error?.message || '加载论坛概览失败')
}
}
function goBoards() {
window.location.hash = '#/forum/boards'
}
function goTopics() {
window.location.hash = '#/forum/topics'
}
function goApplications() {
window.location.hash = '#/forum/moderator-applications'
}
onMounted(loadOverview)
</script>
<style scoped>
.aksrt-forum-dashboard__stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.aksrt-forum-dashboard__stat-card {
display: flex;
align-items: center;
gap: 16px;
padding: 18px;
border-radius: 12px;
background: #fff;
border: 1px solid #eef2f7;
}
.aksrt-forum-dashboard__stat-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
color: #1d4ed8;
font-size: 18px;
}
.aksrt-forum-dashboard__stat-info {
display: flex;
flex-direction: column;
}
.aksrt-forum-dashboard__stat-value {
color: #111827;
font-size: 24px;
font-weight: 700;
}
.aksrt-forum-dashboard__stat-label {
color: #6b7280;
font-size: 13px;
}
.aksrt-forum-dashboard__grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 16px;
}
.aksrt-forum-dashboard__section {
margin-bottom: 16px;
}
.aksrt-forum-dashboard__section-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
@media (max-width: 1023px) {
.aksrt-forum-dashboard__grid {
grid-template-columns: 1fr;
}
}
</style>
@@ -1,201 +0,0 @@
<template>
<div class="aksrt-page-container">
<PageHeader title="版主申请处理" desc="处理前台版主申请,完成审核、通过和拒绝反馈闭环。" />
<n-card size="small" class="aksrt-forum-apps__section">
<n-space wrap>
<n-input v-model:value="filters.keyword" clearable placeholder="搜索版块 / 用户 / 理由" style="width: 220px" @keyup.enter="loadData" />
<n-select v-model:value="filters.board_id" :options="boardOptions" clearable placeholder="版块" style="width: 180px" />
<n-select v-model:value="filters.status" :options="statusOptions" clearable placeholder="状态" style="width: 160px" />
<n-button type="primary" @click="loadData">查询</n-button>
<n-button @click="resetFilters">重置</n-button>
</n-space>
</n-card>
<n-card size="small" class="aksrt-forum-apps__section">
<n-data-table
:columns="columns"
:data="items"
:loading="loading"
:bordered="false"
size="small"
/>
</n-card>
<n-modal :show="reviewVisible" preset="card" title="处理版主申请" style="width: 520px" @update:show="reviewVisible = $event">
<n-space vertical :size="14">
<n-alert type="info" :show-icon="false">
<div>{{ activeApplication?.display_name || '-' }} / {{ activeApplication?.board_name || '-' }}</div>
<div class="aksrt-forum-apps__reason">{{ activeApplication?.reason || '暂无申请理由' }}</div>
</n-alert>
<n-radio-group v-model:value="reviewForm.status">
<n-radio-button value="approved">通过</n-radio-button>
<n-radio-button value="rejected">拒绝</n-radio-button>
</n-radio-group>
<n-input v-model:value="reviewForm.note" type="textarea" :rows="4" placeholder="记录审核结论和补充说明" />
</n-space>
<template #footer>
<n-space justify="end">
<n-button @click="reviewVisible = false">取消</n-button>
<n-button type="primary" :loading="reviewing" @click="submitReview">提交处理</n-button>
</n-space>
</template>
</n-modal>
</div>
</template>
<script setup>
import { h, onMounted, reactive, ref } from 'vue'
import { NButton, NTag, useMessage } from 'naive-ui'
import PageHeader from '@/components/PageHeader.vue'
import api from '@/api/index.js'
const message = useMessage()
const loading = ref(false)
const items = ref([])
const boardOptions = ref([{ label: '全部版块', value: null }])
const statusOptions = ref([{ label: '全部状态', value: null }])
const reviewVisible = ref(false)
const reviewing = ref(false)
const activeApplication = ref(null)
const filters = reactive({
keyword: '',
board_id: null,
status: null,
})
const reviewForm = reactive({
status: 'approved',
note: '',
})
const columns = [
{
title: '版块 / 用户',
key: 'board_name',
minWidth: 220,
render: (row) => h('div', { class: 'aksrt-forum-apps__identity' }, [
h('div', { class: 'aksrt-forum-apps__board' }, row.board_name || '-'),
h('div', { class: 'aksrt-forum-apps__user' }, `${row.display_name || '-'} · ${row.user_email || '-'}`),
]),
},
{ title: '申请理由', key: 'reason', ellipsis: { tooltip: true } },
{
title: '状态',
key: 'status',
width: 110,
render: (row) => h(NTag, { size: 'small', type: resolveStatusType(row.status) }, () => row.status_label || row.status || '-'),
},
{ title: '申请时间', key: 'created_at', width: 160 },
{ title: '处理时间', key: 'reviewed_at', width: 160, render: (row) => row.reviewed_at || '-' },
{
title: '操作',
key: 'actions',
width: 160,
render: (row) => {
if (row.status !== 'pending') {
return '-'
}
return h('div', { style: 'display:flex;gap:6px;' }, [
h(NButton, { size: 'small', quaternary: true, type: 'success', onClick: () => openReview(row, 'approved') }, () => '通过'),
h(NButton, { size: 'small', quaternary: true, type: 'error', onClick: () => openReview(row, 'rejected') }, () => '拒绝'),
])
},
},
]
function resolveStatusType(status) {
if (status === 'approved') return 'success'
if (status === 'rejected') return 'error'
return 'warning'
}
async function loadData() {
loading.value = true
try {
const res = await api.request('/forum/moderator/applications', {
params: {
keyword: filters.keyword || '',
board_id: filters.board_id || '',
status: filters.status || '',
},
})
const data = res?.data || {}
items.value = data.items || []
boardOptions.value = [{ label: '全部版块', value: null }, ...((data.options?.boards || []).map((item) => ({ label: item.name, value: item.id })))]
statusOptions.value = data.options?.status || [{ label: '全部状态', value: null }]
} catch (error) {
message.error(error?.message || '加载版主申请失败')
} finally {
loading.value = false
}
}
function resetFilters() {
filters.keyword = ''
filters.board_id = null
filters.status = null
loadData()
}
function openReview(row, status) {
activeApplication.value = row
reviewForm.status = status
reviewForm.note = row.review_note || ''
reviewVisible.value = true
}
async function submitReview() {
if (!activeApplication.value?.id) {
return
}
reviewing.value = true
try {
await api.request(`/forum/moderator/applications/${activeApplication.value.id}/review`, {
method: 'POST',
data: {
status: reviewForm.status,
note: reviewForm.note,
},
})
message.success('申请状态已更新')
reviewVisible.value = false
activeApplication.value = null
await loadData()
} catch (error) {
message.error(error?.message || '申请处理失败')
} finally {
reviewing.value = false
}
}
onMounted(loadData)
</script>
<style scoped>
.aksrt-forum-apps__section {
margin-bottom: 16px;
}
.aksrt-forum-apps__identity {
display: flex;
flex-direction: column;
gap: 4px;
}
.aksrt-forum-apps__board {
color: #111827;
font-weight: 600;
}
.aksrt-forum-apps__user,
.aksrt-forum-apps__reason {
color: #6b7280;
font-size: 12px;
}
</style>
@@ -1,363 +0,0 @@
<template>
<div class="aksrt-page-container">
<PageHeader title="版主管理" desc="按版块维护版主名单,支持角色分层和权限档位。" />
<n-grid cols="1 s:2 m:4" :x-gap="16" :y-gap="16" responsive="screen" style="margin-bottom:16px;">
<n-grid-item>
<n-statistic label="总版主数" :value="stats.total_moderators" />
</n-grid-item>
<n-grid-item>
<n-statistic label="总版块数" :value="stats.total_boards" />
</n-grid-item>
<n-grid-item>
<n-statistic label="待处理申请" :value="stats.pending_applications">
<template #suffix>
<n-tag v-if="stats.pending_applications > 0" type="warning" size="small">待处理</n-tag>
</template>
</n-statistic>
</n-grid-item>
<n-grid-item>
<n-statistic label="高级版主" :value="stats.senior_moderators" />
</n-grid-item>
</n-grid>
<n-card size="small" class="aksrt-forum-mod__toolbar">
<n-space justify="space-between" align="center">
<span class="aksrt-forum-mod__summary">已加载 {{ boards.length }} 个版块</span>
<n-space>
<n-button @click="goApplications">查看版主申请</n-button>
<n-button type="primary" @click="showBatchModal = true">批量管理</n-button>
</n-space>
</n-space>
</n-card>
<n-card v-for="board in boards" :key="board.board_id" size="small" class="aksrt-forum-mod__section">
<template #header>
<div class="aksrt-forum-mod__header">
<strong>{{ board.board_name }}</strong>
<n-space size="small">
<n-tag size="small" type="info">{{ board.topic_count }} 主题</n-tag>
<n-tag size="small" :type="board.pending_application_count > 0 ? 'warning' : 'default'">
{{ board.pending_application_count }} 申请待处理
</n-tag>
</n-space>
</div>
</template>
<!-- 版主列表 -->
<div class="aksrt-forum-mod__list">
<div v-for="moderator in board.moderators" :key="moderator.id" class="aksrt-forum-mod__item">
<n-avatar :src="moderator.avatar_url" :size="36" round />
<div class="aksrt-forum-mod__info">
<div class="aksrt-forum-mod__name-row">
<span class="aksrt-forum-mod__name">{{ moderator.display_name }}</span>
<n-tag :type="getRoleTagType(moderator.role)" size="tiny">{{ getRoleLabel(moderator.role) }}</n-tag>
</div>
<span class="aksrt-forum-mod__email">{{ moderator.user_email }}</span>
<div v-if="moderator.permissions?.length" class="aksrt-forum-mod__permissions">
<n-tag v-for="perm in moderator.permissions" :key="perm" size="tiny" type="default">{{ getPermissionLabel(perm) }}</n-tag>
</div>
</div>
<n-space size="small">
<n-select
v-model:value="moderator.role"
:options="roleOptions"
size="tiny"
style="width:100px;"
@update:value="handleRoleChange(board.board_id, moderator.id, $event)"
/>
<n-button size="tiny" quaternary type="error" @click="handleRemove(board.board_id, moderator.id)">移除</n-button>
</n-space>
</div>
<n-empty v-if="board.moderators.length === 0" description="暂无版主" size="small" />
</div>
<!-- 添加版主 -->
<div class="aksrt-forum-mod__add">
<n-input
v-model:value="board._newUser"
size="small"
style="width:140px"
placeholder="用户 ID"
@keyup.enter="handleAdd(board)"
/>
<n-select
v-model:value="board._newRole"
:options="roleOptions"
size="small"
style="width:100px;"
placeholder="角色"
/>
<n-button size="small" type="primary" :disabled="!board._newUser" @click="handleAdd(board)">添加版主</n-button>
</div>
</n-card>
<n-empty v-if="boards.length === 0 && !loading" description="暂无论坛版块" />
<!-- 批量管理弹窗 -->
<n-modal v-model:show="showBatchModal" preset="card" title="批量管理版主" style="max-width:560px;" :bordered="false">
<n-form label-placement="top">
<n-form-item label="选择版块">
<n-select v-model:value="batchForm.board_id" :options="boardOptions" placeholder="选择版块" />
</n-form-item>
<n-form-item label="操作">
<n-radio-group v-model:value="batchForm.action">
<n-space>
<n-radio value="add">批量添加</n-radio>
<n-radio value="remove">批量移除</n-radio>
<n-radio value="update_role">批量修改角色</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
<n-form-item label="用户 ID 列表">
<n-input v-model:value="batchForm.user_ids" type="textarea" :rows="3" placeholder="每行一个用户 ID" />
</n-form-item>
<n-form-item v-if="batchForm.action === 'update_role'" label="目标角色">
<n-select v-model:value="batchForm.target_role" :options="roleOptions" />
</n-form-item>
</n-form>
<template #footer>
<n-space justify="end">
<n-button @click="showBatchModal = false">取消</n-button>
<n-button type="primary" :loading="batchLoading" @click="handleBatch">执行</n-button>
</n-space>
</template>
</n-modal>
</div>
</template>
<script setup>
import { computed, onMounted, reactive, ref } from 'vue'
import { useMessage } from 'naive-ui'
import PageHeader from '@/components/PageHeader.vue'
import api from '@/api/index.js'
const message = useMessage()
const loading = ref(false)
const boards = ref([])
const stats = reactive({ total_moderators: 0, total_boards: 0, pending_applications: 0, senior_moderators: 0 })
// 批量管理
const showBatchModal = ref(false)
const batchLoading = ref(false)
const batchForm = reactive({ board_id: null, action: 'add', user_ids: '', target_role: 'moderator' })
// 角色选项
const roleOptions = [
{ label: '超级版主', value: 'super_moderator' },
{ label: '高级版主', value: 'senior_moderator' },
{ label: '版主', value: 'moderator' },
{ label: '实习版主', value: 'trainee' },
]
// 权限选项
const permissionLabels = {
manage_topics: '管理主题',
manage_comments: '管理评论',
manage_users: '管理用户',
pin_topics: '置顶主题',
close_topics: '关闭主题',
move_topics: '移动主题',
delete_topics: '删除主题',
ban_users: '封禁用户',
}
const boardOptions = computed(() => boards.value.map(b => ({ label: b.board_name, value: b.board_id })))
function getRoleLabel(role) {
const opt = roleOptions.find(r => r.value === role)
return opt?.label || role || '版主'
}
function getRoleTagType(role) {
const map = { super_moderator: 'error', senior_moderator: 'warning', moderator: 'info', trainee: 'default' }
return map[role] || 'default'
}
function getPermissionLabel(perm) {
return permissionLabels[perm] || perm
}
async function loadModerators() {
loading.value = true
try {
const res = await api.request('/forum/moderators')
boards.value = (res?.data?.boards || []).map(item => ({
...item,
_newUser: '',
_newRole: 'moderator',
}))
// 计算统计
stats.total_boards = boards.value.length
stats.total_moderators = boards.value.reduce((sum, b) => sum + (b.moderators?.length || 0), 0)
stats.pending_applications = boards.value.reduce((sum, b) => sum + (b.pending_application_count || 0), 0)
stats.senior_moderators = boards.value.reduce((sum, b) =>
sum + (b.moderators?.filter(m => ['super_moderator', 'senior_moderator'].includes(m.role))?.length || 0), 0)
} catch (error) {
message.error(error?.message || '加载版主列表失败')
} finally {
loading.value = false
}
}
async function handleAdd(board) {
const userId = parseInt(board._newUser || '', 10)
if (!userId || userId <= 0) {
message.warning('请输入有效的用户 ID')
return
}
try {
await api.request('/forum/moderators', {
method: 'POST',
data: {
board_id: board.board_id,
user_id: userId,
role: board._newRole || 'moderator',
action: 'add',
},
})
message.success('版主已添加')
board._newUser = ''
board._newRole = 'moderator'
await loadModerators()
} catch (error) {
message.error(error?.message || '添加版主失败')
}
}
async function handleRemove(boardId, userId) {
try {
await api.request('/forum/moderators', {
method: 'POST',
data: { board_id: boardId, user_id: userId, action: 'remove' },
})
message.success('版主已移除')
await loadModerators()
} catch (error) {
message.error(error?.message || '移除版主失败')
}
}
async function handleRoleChange(boardId, userId, newRole) {
try {
await api.request('/forum/moderators', {
method: 'POST',
data: { board_id: boardId, user_id: userId, role: newRole, action: 'update_role' },
})
message.success('角色已更新')
await loadModerators()
} catch (error) {
message.error(error?.message || '角色更新失败')
}
}
async function handleBatch() {
const userIds = batchForm.user_ids.split('\n').map(s => parseInt(s.trim(), 10)).filter(n => n > 0)
if (!batchForm.board_id || userIds.length === 0) {
message.warning('请选择版块并输入用户 ID')
return
}
batchLoading.value = true
try {
await api.request('/forum/moderators/batch', {
method: 'POST',
data: {
board_id: batchForm.board_id,
user_ids: userIds,
action: batchForm.action,
target_role: batchForm.target_role,
},
})
message.success('批量操作完成')
showBatchModal.value = false
batchForm.user_ids = ''
await loadModerators()
} catch (error) {
message.error(error?.message || '批量操作失败')
} finally {
batchLoading.value = false
}
}
function goApplications() {
window.location.hash = '#/forum/moderator-applications'
}
onMounted(loadModerators)
</script>
<style scoped>
.aksrt-forum-mod__toolbar,
.aksrt-forum-mod__section {
margin-bottom: 16px;
}
.aksrt-forum-mod__summary {
color: var(--color-ink-3);
font-size: 13px;
}
.aksrt-forum-mod__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.aksrt-forum-mod__list {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 12px;
}
.aksrt-forum-mod__item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border-radius: 8px;
background: var(--surface-2);
border: 1px solid var(--color-line-soft);
}
.aksrt-forum-mod__info {
flex: 1;
min-width: 0;
}
.aksrt-forum-mod__name-row {
display: flex;
align-items: center;
gap: 6px;
}
.aksrt-forum-mod__name {
display: block;
color: var(--color-ink);
font-weight: 600;
}
.aksrt-forum-mod__email {
display: block;
color: var(--color-ink-3);
font-size: 12px;
}
.aksrt-forum-mod__permissions {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 4px;
}
.aksrt-forum-mod__add {
display: flex;
gap: 8px;
align-items: center;
padding-top: 12px;
border-top: 1px solid var(--color-line-soft);
}
</style>
@@ -1,186 +0,0 @@
<template>
<div class="aksrt-page-container">
<PageHeader title="主题治理" desc="集中处理论坛主题的审核、可见性、版块移动和治理状态。" />
<n-card size="small" class="aksrt-forum-topics__section">
<n-space wrap>
<n-input v-model:value="filters.keyword" clearable placeholder="搜索标题 / 作者 / 版块" style="width: 220px" @keyup.enter="loadData(1)" />
<n-select v-model:value="filters.board_id" :options="boardOptions" clearable placeholder="版块" style="width: 180px" />
<n-select v-model:value="filters.status" :options="statusOptions" placeholder="帖子状态" style="width: 160px" />
<n-select v-model:value="filters.review_status" :options="reviewOptions" clearable placeholder="审核状态" style="width: 160px" />
<n-select v-model:value="filters.visibility" :options="visibilityOptions" clearable placeholder="可见性" style="width: 160px" />
<n-button type="primary" @click="loadData(1)">查询</n-button>
<n-button @click="resetFilters">重置</n-button>
</n-space>
</n-card>
<n-card size="small" class="aksrt-forum-topics__section">
<n-data-table
:columns="columns"
:data="items"
:loading="loading"
:bordered="false"
size="small"
:pagination="pagination"
@update:page="handlePageChange"
/>
</n-card>
<TopicModerationDrawer
v-model:show="detailVisible"
:topic-id="detailTopicId"
@updated="loadData(page)"
/>
</div>
</template>
<script setup>
import { h, onMounted, reactive, ref } from 'vue'
import { NButton, NTag, useMessage } from 'naive-ui'
import PageHeader from '@/components/PageHeader.vue'
import api from '@/api/index.js'
import TopicModerationDrawer from './components/TopicModerationDrawer.vue'
const message = useMessage()
const loading = ref(false)
const page = ref(1)
const items = ref([])
const detailVisible = ref(false)
const detailTopicId = ref(null)
const boardOptions = ref([])
const statusOptions = ref([{ label: '全部', value: 'all' }])
const reviewOptions = ref([])
const visibilityOptions = ref([])
const filters = reactive({
keyword: '',
board_id: null,
status: 'all',
review_status: null,
visibility: null,
})
const pagination = reactive({
page: 1,
pageSize: 20,
itemCount: 0,
})
const columns = [
{
title: '主题',
key: 'title',
minWidth: 280,
render: (row) => h('div', { class: 'aksrt-forum-topics__title-cell' }, [
h('div', { class: 'aksrt-forum-topics__title' }, row.title || '-'),
h('div', { class: 'aksrt-forum-topics__subtitle' }, [
row.boards?.[0]?.name || '未分配版块',
' · ',
row.author?.display_name || '未知作者',
]),
]),
},
{
title: '可见性',
key: 'visibility',
width: 110,
render: (row) => h(NTag, { size: 'small', type: row.visibility?.key === 'public' ? 'default' : 'warning' }, () => row.visibility?.label || '-'),
},
{
title: '审核',
key: 'review_status',
width: 110,
render: (row) => h(NTag, { size: 'small', type: resolveReviewType(row.review_status?.key) }, () => row.review_status?.label || '-'),
},
{ title: '回复', key: 'comment_count', width: 80 },
{ title: '浏览', key: 'views', width: 80 },
{ title: '发布时间', key: 'date', width: 160 },
{
title: '操作',
key: 'actions',
width: 110,
render: (row) => h(NButton, { size: 'small', quaternary: true, type: 'info', onClick: () => openDetail(row) }, () => '治理'),
},
]
function resolveReviewType(status) {
if (status === 'approved') return 'success'
if (status === 'rejected') return 'error'
return 'warning'
}
async function loadData(nextPage = 1) {
loading.value = true
page.value = nextPage
pagination.page = nextPage
try {
const res = await api.request('/forum/topics', {
params: {
page: nextPage,
per_page: pagination.pageSize,
keyword: filters.keyword || '',
board_id: filters.board_id || '',
status: filters.status || 'all',
review_status: filters.review_status || '',
visibility: filters.visibility || '',
},
})
const data = res?.data || {}
items.value = data.items || []
pagination.itemCount = data.total || 0
boardOptions.value = [{ label: '全部版块', value: null }, ...((data.options?.boards || []).map((item) => ({ label: item.name, value: item.id })))]
statusOptions.value = data.options?.status || [{ label: '全部', value: 'all' }]
reviewOptions.value = [{ label: '全部审核', value: null }, ...(data.options?.review || [])]
visibilityOptions.value = [{ label: '全部可见性', value: null }, ...(data.options?.visibility || [])]
} catch (error) {
message.error(error?.message || '加载主题列表失败')
} finally {
loading.value = false
}
}
function handlePageChange(nextPage) {
loadData(nextPage)
}
function resetFilters() {
filters.keyword = ''
filters.board_id = null
filters.status = 'all'
filters.review_status = null
filters.visibility = null
loadData(1)
}
function openDetail(row) {
detailTopicId.value = row.id
detailVisible.value = true
}
onMounted(() => loadData(1))
</script>
<style scoped>
.aksrt-forum-topics__section {
margin-bottom: 16px;
}
.aksrt-forum-topics__title-cell {
display: flex;
flex-direction: column;
gap: 4px;
}
.aksrt-forum-topics__title {
color: #111827;
font-weight: 600;
}
.aksrt-forum-topics__subtitle {
color: #6b7280;
font-size: 12px;
}
</style>
@@ -1,284 +0,0 @@
<template>
<n-drawer :show="show" :width="620" @update:show="$emit('update:show', $event)">
<n-drawer-content title="主题治理" closable>
<n-spin :show="loading">
<template v-if="detail.topic">
<n-descriptions bordered :column="1" size="small" label-placement="left">
<n-descriptions-item label="主题标题">{{ detail.topic.title || '-' }}</n-descriptions-item>
<n-descriptions-item label="所属版块">{{ primaryBoardName }}</n-descriptions-item>
<n-descriptions-item label="作者">{{ detail.topic.author?.display_name || '-' }}</n-descriptions-item>
<n-descriptions-item label="可见性">{{ detail.topic.visibility?.label || '-' }}</n-descriptions-item>
<n-descriptions-item label="审核状态">{{ detail.topic.review_status?.label || '-' }}</n-descriptions-item>
<n-descriptions-item label="主题状态">{{ detail.topic.status || '-' }}</n-descriptions-item>
<n-descriptions-item label="置顶">{{ stickySummary }}</n-descriptions-item>
<n-descriptions-item label="精华">{{ detail.topic.featured ? '是' : '否' }}</n-descriptions-item>
<n-descriptions-item label="帖子类型">{{ bbsTypeLabel }}</n-descriptions-item>
<n-descriptions-item v-if="detail.topic.bbs_type === 'question'" label="提问状态">{{ questionStatusLabel }}</n-descriptions-item>
<n-descriptions-item label="收藏">{{ detail.topic.favorite_count ?? 0 }}</n-descriptions-item>
<n-descriptions-item label="评分">{{ detail.topic.score ?? 0 }}</n-descriptions-item>
<n-descriptions-item label="专题">{{ detail.topic.special_topic || '-' }}</n-descriptions-item>
<n-descriptions-item label="投票">{{ voteSummary }}</n-descriptions-item>
<n-descriptions-item label="治理备注">{{ detail.topic.moderation_note || '-' }}</n-descriptions-item>
</n-descriptions>
<div class="aksrt-topic-drawer__excerpt">
<div class="aksrt-topic-drawer__section-title">内容摘要</div>
<p>{{ detail.topic.excerpt || '暂无摘要' }}</p>
</div>
<div class="aksrt-topic-drawer__actions">
<div class="aksrt-topic-drawer__section-title">执行治理动作</div>
<n-space vertical :size="12">
<n-select v-model:value="form.action" :options="actionOptions" />
<n-select
v-if="form.action === 'review'"
v-model:value="form.review_status"
:options="detail.options?.review || []"
placeholder="选择审核状态"
/>
<n-select
v-if="form.action === 'move_board'"
v-model:value="form.target_board_id"
:options="boardOptions"
placeholder="选择目标版块"
/>
<n-select
v-if="form.action === 'visibility'"
v-model:value="form.visibility"
:options="detail.options?.visibility || []"
placeholder="选择可见性"
/>
<n-select
v-if="form.action === 'sticky'"
v-model:value="form.sticky_value"
:options="stickyLevelOptions"
placeholder="选择置顶级别"
/>
<n-select
v-if="form.action === 'question_status'"
v-model:value="form.question_status_value"
:options="questionStatusOptions"
placeholder="选择提问状态"
/>
<n-select
v-if="booleanActions.includes(form.action) && form.action !== 'sticky'"
v-model:value="form.value"
:options="booleanOptions"
placeholder="选择状态"
/>
<n-input
v-model:value="form.note"
type="textarea"
:rows="3"
placeholder="记录本次处理原因,便于后续审计"
/>
<n-space>
<n-button type="primary" :loading="submitting" @click="handleSubmit">执行</n-button>
<n-button @click="loadDetail">刷新详情</n-button>
</n-space>
</n-space>
</div>
</template>
<n-empty v-else description="暂无主题详情" />
</n-spin>
</n-drawer-content>
</n-drawer>
</template>
<script setup>
import { computed, reactive, ref, watch } from 'vue'
import { useMessage } from 'naive-ui'
import api from '@/api/index.js'
const props = defineProps({
show: { type: Boolean, default: false },
topicId: { type: [Number, String], default: null },
})
const emit = defineEmits(['update:show', 'updated'])
const message = useMessage()
const loading = ref(false)
const submitting = ref(false)
const detail = ref({ topic: null, options: { boards: [], visibility: [], review: [] } })
// sticky 现在是 0~3 分级选择,不再是布尔动作(§7.3 置顶分级)。
const booleanActions = ['featured', 'locked', 'close', 'hide']
const form = reactive({
action: 'review',
review_status: 'pending',
target_board_id: null,
visibility: 'public',
value: '1',
sticky_value: 0,
question_status_value: 0,
note: '',
})
const actionOptions = [
{ label: '审核状态', value: 'review' },
{ label: '移动版块', value: 'move_board' },
{ label: '修改可见性', value: 'visibility' },
{ label: '置顶分级', value: 'sticky' },
{ label: '精华切换', value: 'featured' },
{ label: '锁定切换', value: 'locked' },
{ label: '关闭帖子', value: 'close' },
{ label: '隐藏帖子', value: 'hide' },
{ label: '提问状态', value: 'question_status' },
]
const booleanOptions = [
{ label: '启用', value: '1' },
{ label: '关闭', value: '0' },
]
// 置顶分级选项(对标 zibll topping 0/1/2/3)。
const stickyLevelOptions = [
{ label: '不置顶', value: 0 },
{ label: '版块置顶', value: 1 },
{ label: '超级置顶', value: 2 },
{ label: '全局置顶', value: 3 },
]
const questionStatusOptions = [
{ label: '未解决', value: 0 },
{ label: '已解决', value: 1 },
]
const primaryBoardName = computed(() => detail.value?.topic?.boards?.[0]?.name || '-')
const boardOptions = computed(() => (detail.value?.options?.boards || []).map((item) => ({
label: item.name,
value: item.id,
})))
// 置顶摘要:优先用后端返回的 sticky_label,否则按 sticky_level 映射。
const stickySummary = computed(() => {
const topic = detail.value?.topic
if (!topic) return '-'
const label = topic.sticky_label
if (label) return label
const level = topic.sticky_level ?? 0
const found = stickyLevelOptions.find((opt) => opt.value === level)
return found ? found.label : '不置顶'
})
const voteSummary = computed(() => {
const vote = detail.value?.topic?.vote
if (!vote?.enabled) {
return '未开启'
}
return `${vote.total_votes || 0} 票 / ${vote.multiple ? '多选' : '单选'}`
})
const bbsTypeLabel = computed(() => {
const t = detail.value?.topic?.bbs_type
return t === 'question' ? '提问' : '标准帖子'
})
const questionStatusLabel = computed(() => {
const s = detail.value?.topic?.question_status
return s === 1 ? '已解决' : '未解决'
})
watch(() => props.show, async (visible) => {
if (visible && props.topicId) {
await loadDetail()
} else if (!visible) {
detail.value = { topic: null, options: { boards: [], visibility: [], review: [] } }
resetForm()
}
})
function resetForm() {
form.action = 'review'
form.review_status = detail.value?.topic?.review_status?.key || 'pending'
form.target_board_id = detail.value?.topic?.boards?.[0]?.id || null
form.visibility = detail.value?.topic?.visibility?.key || 'public'
form.value = '1'
form.sticky_value = detail.value?.topic?.sticky_level ?? 0
form.question_status_value = detail.value?.topic?.question_status ?? 0
form.note = ''
}
async function loadDetail() {
if (!props.topicId) {
return
}
loading.value = true
try {
const res = await api.request(`/forum/topics/${props.topicId}`)
detail.value = res?.data || { topic: null, options: { boards: [], visibility: [], review: [] } }
resetForm()
} catch (error) {
message.error(error?.message || '加载主题详情失败')
} finally {
loading.value = false
}
}
async function handleSubmit() {
if (!props.topicId) {
return
}
const payload = {
action: form.action,
note: form.note,
}
if (form.action === 'review') {
payload.review_status = form.review_status
} else if (form.action === 'move_board') {
payload.target_board_id = form.target_board_id
} else if (form.action === 'visibility') {
payload.visibility = form.visibility
} else if (form.action === 'sticky') {
// 置顶分级:发送 0~3 整数。
payload.value = form.sticky_value
} else if (form.action === 'question_status') {
payload.value = form.question_status_value
} else {
payload.value = form.value === '1'
}
submitting.value = true
try {
await api.request(`/forum/topics/${props.topicId}/moderate`, {
method: 'POST',
data: payload,
})
message.success('治理动作已执行')
await loadDetail()
emit('updated')
} catch (error) {
message.error(error?.message || '治理动作执行失败')
} finally {
submitting.value = false
}
}
</script>
<style scoped>
.aksrt-topic-drawer__excerpt,
.aksrt-topic-drawer__actions {
margin-top: 18px;
}
.aksrt-topic-drawer__section-title {
margin-bottom: 8px;
font-weight: 600;
color: #1f2937;
}
.aksrt-topic-drawer__excerpt p {
margin: 0;
color: #4b5563;
line-height: 1.7;
}
</style>
@@ -64,11 +64,11 @@ onMounted(async () => {
loading.value = true
try {
const [gwRes, ovRes] = await Promise.all([
api.request('/shop/gateways').catch(() => null),
api.request('/payment/overview').catch(() => null),
])
if (gwRes?.data?.gateways) {
gateways.value = gwRes.data.gateways
if (gwRes?.data?.gateway?.gateways) {
const gwList = Object.entries(gwRes.data.gateway.gateways).map(([id, enabled]) => ({ id, name: id, configured: !!enabled }))
gateways.value = gwList
}
if (ovRes?.data?.gateway) {
overview.value = ovRes.data.gateway
@@ -91,7 +91,6 @@ const guides = [
{ key: 'meilisearch', title: 'Meilisearch', desc: '配置搜索索引与同步策略', url: '#/global/search' },
{ key: 'i18n', title: '多语言翻译', desc: '检查语言切换与术语配置', url: '#/global/i18n' },
{ key: 'cdn', title: '静态资源与 CDN', desc: '核对前端资源地址与缓存策略', url: '#/global/common' },
{ key: 'forum', title: '论坛体系', desc: '检查版块、话题与版主管理', url: '#/forum' },
{ key: 'ip-geo', title: 'IP 归属地', desc: '验证 IP 查询服务与密钥配置', url: '#/extensions/security' },
{ key: 'payment', title: '支付与会员', desc: '核对网关、会员与充值配置', url: '#/payment/gateway' },
{ key: 'credits', title: '积分与签到', desc: '检查积分规则、签到与邀请奖励', url: '#/users/signin' },
@@ -130,7 +130,6 @@ export default {
{ key: 'system_notice', desc: '系统公告通知' },
{ key: 'order_paid', desc: '订单支付通知' },
{ key: 'credits_change', desc: '积分变动通知' },
{ key: 'forum_reply', desc: '论坛回复通知' },
];
const allowedTypes = ref([]);
@@ -1,2 +0,0 @@
// Forum ops Vue pages removed (Phase 4). Config uses SchemaPage.
export const forumPageLoaders = {}
@@ -2,8 +2,6 @@ import { corePageLoaders } from './core'
import { usersPageLoaders } from './users'
import { paymentPageLoaders } from './payment'
import { toolsPageLoaders } from './tools'
import { shopPageLoaders } from './shop'
import { forumPageLoaders } from './forum'
import { extensionsPageLoaders } from './extensions'
export const componentLoaders = {
@@ -11,7 +9,5 @@ export const componentLoaders = {
...usersPageLoaders,
...paymentPageLoaders,
...toolsPageLoaders,
...shopPageLoaders,
...forumPageLoaders,
...extensionsPageLoaders,
}
-12
View File
@@ -76,18 +76,6 @@ function createSectionRedirectRoutes() {
* Bookmarks under #/shop/products etc. land on NativeOpsRedirect.
*/
const NATIVE_OPS_REDIRECTS = [
{ path: '/shop/products', title: '商品管理', nativeAdminPath: 'edit.php?post_type=aksrt_product' },
{ path: '/shop/orders', title: '订单管理', nativeAdminPath: 'admin.php?page=aksrt_orders' },
{ path: '/shop/after-sale', title: '售后管理', nativeAdminPath: 'admin.php?page=aksrt_after_sale' },
{ path: '/shop/shipping', title: '物流管理', nativeAdminPath: 'admin.php?page=aksrt_shipping' },
{ path: '/shop/coupons', title: '优惠券', nativeAdminPath: 'admin.php?page=aksrt_coupons' },
{ path: '/shop/withdrawals', title: '提现管理', nativeAdminPath: 'admin.php?page=aksrt_withdraw' },
{ path: '/shop/dashboard', title: '商城概览', nativeAdminPath: 'admin.php?page=aksrt_pay' },
{ path: '/forum/boards', title: '板块管理', nativeAdminPath: 'edit.php?post_type=aksrt_forum_plate' },
{ path: '/forum/topics', title: '主题治理', nativeAdminPath: 'edit.php?post_type=aksrt_forum_topic' },
{ path: '/forum/moderators', title: '版主管理', nativeAdminPath: 'edit.php?post_type=aksrt_forum_plate' },
{ path: '/forum/moderator-applications', title: '版主申请', nativeAdminPath: 'edit.php?post_type=aksrt_forum_plate' },
{ path: '/forum/dashboard', title: '论坛概览', nativeAdminPath: 'edit.php?post_type=aksrt_forum_topic' },
{ path: '/payment/charge-card', title: '充值卡', nativeAdminPath: 'admin.php?page=aksrt_charge_cards' },
{ path: '/payment/revenue', title: '创作分成', nativeAdminPath: 'admin.php?page=aksrt_income' },
{ path: '/payment/rebate', title: '返佣管理', nativeAdminPath: 'admin.php?page=aksrt_rebate' },
-6
View File
@@ -34,8 +34,6 @@ import { usersSchemas } from './sections/users/index.js'
import { paymentSchemas } from './sections/payment/index.js'
import { extensionsSchemas } from './sections/extensions/index.js'
import { toolsSchemas } from './sections/tools/index.js'
import { shopSchemas } from './sections/shop/index.js'
import { forumSchemas } from './sections/forum/index.js'
export function normalizeSchemaDomain(domain = '') {
const value = String(domain).trim()
@@ -55,8 +53,6 @@ export const CANONICAL_SCHEMA_DOMAINS = [
'payment',
'extensions',
'tools',
'shop',
'forum',
]
/** All schemas organized by domain */
@@ -69,8 +65,6 @@ export const ALL_SCHEMAS = Object.freeze({
payment: paymentSchemas,
extensions: extensionsSchemas,
tools: toolsSchemas,
shop: shopSchemas,
forum: forumSchemas,
})
/**
@@ -42,7 +42,6 @@ export const auditSchema = {
fields: [
{ key: 'content_audit_text_enabled', label: '文本审核', type: 'switch', default: true, dependsOn: { key: 'content_audit_enabled', value: true } },
{ key: 'content_audit_image_enabled', label: '图片审核', type: 'switch', default: false, dependsOn: { key: 'content_audit_enabled', value: true } },
{ key: 'audit_bbs_term', label: '审核论坛分类', type: 'switch', default: false, dependsOn: { key: 'content_audit_enabled', value: true } },
],
},
{
@@ -1,28 +0,0 @@
export const basicSchema = {
key: 'forum.basic',
title: '基础设置',
icon: 'fas fa-power-off',
description: '论坛总开关与管理员配置。',
defaults: {
bbs_s: true,
bbs_admin_users: [],
},
groups: [
{
key: 'switch',
title: '功能开关',
icon: 'fas fa-toggle-on',
fields: [
{ key: 'bbs_s', label: '启用论坛', type: 'switch', default: true, help: '关闭后论坛页面将返回 404' },
],
},
{
key: 'admin',
title: '论坛管理',
icon: 'fas fa-user-shield',
fields: [
{ key: 'bbs_admin_users', label: '论坛管理员', type: 'user_select', multiple: true, default: [], help: '拥有跨版块全部治理权限,绕过 ban' },
],
},
],
}
@@ -1,31 +0,0 @@
export const boardPageSchema = {
key: 'forum.board_page',
title: '版块页面',
icon: 'fas fa-th-large',
description: '版块页布局、Tab 栏目和移动端交互配置。',
defaults: {
bbs_plate_top_info_s: true,
bbs_plate_tab: [
{ label: '全部', sort: 'date' },
{ label: '推荐', sort: 'featured' },
{ label: '最新回复', sort: 'last_reply' },
{ label: '热门', sort: 'hot' },
{ label: '精华', sort: 'essence' },
],
bbs_plate_tab_active_index: 0,
bbs_plate_tab_swiper: true,
},
groups: [
{
key: 'layout',
title: '版块页布局',
icon: 'fas fa-layer-group',
fields: [
{ key: 'bbs_plate_top_info_s', label: '版块信息卡', type: 'switch', default: true, help: '版块页顶部显示版块详情卡片' },
{ key: 'bbs_plate_tab', label: '版块栏目', type: 'group', default: [{ label: '全部', sort: 'date' }, { label: '推荐', sort: 'featured' }, { label: '最新回复', sort: 'last_reply' }, { label: '热门', sort: 'hot' }, { label: '精华', sort: 'essence' }] },
{ key: 'bbs_plate_tab_active_index', label: '默认选中 Tab', type: 'number', default: 0, min: 0 },
{ key: 'bbs_plate_tab_swiper', label: '移动端滑动切换', type: 'switch', default: true },
],
},
],
}
@@ -1,37 +0,0 @@
export const creditsSchema = {
key: 'forum.credits',
title: '积分设置',
icon: 'fas fa-coins',
description: '论坛行为积分奖励矩阵和每日积分上限。',
defaults: {
bbs_credits_post_new: 5,
bbs_credits_plate_new: 10,
bbs_credits_comment_new: 2,
bbs_credits_comment_like: 1,
bbs_credits_score_extra: 3,
bbs_credits_essence: 10,
bbs_credits_posts_hot: 5,
bbs_hot_recommend_threshold: 500,
bbs_credits_adopted: 20,
bbs_credits_daily_max: 50,
},
groups: [
{
key: 'earn',
title: '积分奖励',
icon: 'fas fa-gift',
fields: [
{ key: 'bbs_credits_post_new', label: '发帖奖励', type: 'number', default: 5, min: 0 },
{ key: 'bbs_credits_plate_new', label: '建版奖励', type: 'number', default: 10, min: 0 },
{ key: 'bbs_credits_comment_new', label: '回复奖励', type: 'number', default: 2, min: 0 },
{ key: 'bbs_credits_comment_like', label: '评论被赞', type: 'number', default: 1, min: 0 },
{ key: 'bbs_credits_score_extra', label: '被加分', type: 'number', default: 3, min: 0 },
{ key: 'bbs_credits_essence', label: '被设精华', type: 'number', default: 10, min: 0 },
{ key: 'bbs_credits_posts_hot', label: '成为热帖', type: 'number', default: 5, min: 0 },
{ key: 'bbs_hot_recommend_threshold', label: '热帖推荐分阈值', type: 'number', default: 500, min: 1, help: '推荐分达到该值触发热帖奖励与勋章累计' },
{ key: 'bbs_credits_adopted', label: '被采纳', type: 'number', default: 20, min: 0 },
{ key: 'bbs_credits_daily_max', label: '每日积分上限', type: 'number', default: 50, min: 0, help: '0 = 不限' },
],
},
],
}
@@ -1,29 +0,0 @@
export const homepageSchema = {
key: 'forum.homepage',
title: '首页设置',
icon: 'fas fa-home',
description: '论坛首页 Tab 引擎、快速发帖入口和移动端交互配置。',
defaults: {
bbs_home_tab: [
{ type: 'follow', label: '关注' },
{ type: 'synthesis', label: '综合' },
{ type: 'plate', label: '版块' },
],
bbs_home_tab_active_index: 1,
bbs_home_tab_quick_posts_s: true,
bbs_home_tab_swiper: true,
},
groups: [
{
key: 'tabs',
title: '首页 Tab',
icon: 'fas fa-columns',
fields: [
{ key: 'bbs_home_tab', label: '首页栏目', type: 'group', default: [{ type: 'follow', label: '关注' }, { type: 'synthesis', label: '综合' }, { type: 'plate', label: '版块' }], help: '可拖拽排序,支持 follow / synthesis / plate / tabs 类型' },
{ key: 'bbs_home_tab_active_index', label: '默认选中 Tab', type: 'number', default: 1, min: 0 },
{ key: 'bbs_home_tab_quick_posts_s', label: '快速发帖', type: 'switch', default: true, help: '首页顶部显示快速发帖入口' },
{ key: 'bbs_home_tab_swiper', label: '移动端滑动切换', type: 'switch', default: true },
],
},
],
}
@@ -1,31 +0,0 @@
/**
* Forum domain schemas.
*
* 12 sections matching PHP schema_forum():
* basic / names / homepage / board_page / post_page / reply /
* permissions / other / upload / credits
*/
import { basicSchema } from './basic.js'
import { namesSchema } from './names.js'
import { homepageSchema } from './homepage.js'
import { boardPageSchema } from './board-page.js'
import { postPageSchema } from './post-page.js'
import { replySchema } from './reply.js'
import { permissionsSchema } from './permissions.js'
import { otherSchema } from './other.js'
import { uploadSchema } from './upload.js'
import { creditsSchema } from './credits.js'
export const forumSchemas = {
basic: basicSchema,
names: namesSchema,
homepage: homepageSchema,
board_page: boardPageSchema,
post_page: postPageSchema,
reply: replySchema,
permissions: permissionsSchema,
other: otherSchema,
upload: uploadSchema,
credits: creditsSchema,
}
@@ -1,41 +0,0 @@
export const namesSchema = {
key: 'forum.names',
title: '名称定义',
icon: 'fas fa-language',
description: '自定义论坛各实体的显示名称(12 词条)。',
defaults: {
bbs_forum_display_name: '论坛',
bbs_plate_display_name: '版块',
bbs_posts_display_name: '帖子',
bbs_topic_display_name: '话题',
bbs_tag_display_name: '标签',
bbs_comment_display_name: '回复',
bbs_question_badge_name: '提问',
bbs_question_ok_badge_name: '已解决',
bbs_cat_moderator_name: '分区版主',
bbs_plate_author_name: '超级版主',
bbs_plate_moderator_name: '版主',
bbs_plate_follow_name: '关注',
},
groups: [
{
key: 'display_names',
title: '名词自定义',
icon: 'fas fa-tags',
fields: [
{ key: 'bbs_forum_display_name', label: '论坛', type: 'input', default: '论坛' },
{ key: 'bbs_plate_display_name', label: '版块', type: 'input', default: '版块' },
{ key: 'bbs_posts_display_name', label: '帖子', type: 'input', default: '帖子' },
{ key: 'bbs_topic_display_name', label: '话题', type: 'input', default: '话题' },
{ key: 'bbs_tag_display_name', label: '标签', type: 'input', default: '标签' },
{ key: 'bbs_comment_display_name', label: '回复', type: 'input', default: '回复' },
{ key: 'bbs_question_badge_name', label: '提问徽章', type: 'input', default: '提问' },
{ key: 'bbs_question_ok_badge_name', label: '已解决徽章', type: 'input', default: '已解决' },
{ key: 'bbs_cat_moderator_name', label: '分区版主', type: 'input', default: '分区版主' },
{ key: 'bbs_plate_author_name', label: '超级版主', type: 'input', default: '超级版主' },
{ key: 'bbs_plate_moderator_name', label: '版主', type: 'input', default: '版主' },
{ key: 'bbs_plate_follow_name', label: '关注', type: 'input', default: '关注' },
],
},
],
}
@@ -1,64 +0,0 @@
export const otherSchema = {
key: 'forum.other',
title: '其它设置',
icon: 'fas fa-cog',
description: '投票、版主申请说明、标题字数限制和 URL 别名配置。',
defaults: {
bbs_vote_number_type: 'percent',
bbs_vote_max: 8,
bbs_apply_moderator_desc: '欢迎有热情的用户申请成为版主,协助管理社区秩序。',
bbs_term_thumb: '',
bbs_post_lists_hide_pay_s: false,
bbs_post_title_strlen_limit: 30,
bbs_t_placeholder: '请输入标题',
bbs_c_placeholder: '请输入内容',
bbs_rewrite_suffix_html_s: true,
bbs_plate_rewrite_slug: 'board',
bbs_posts_rewrite_slug: 'topic',
bbs_posts_edit_rewrite_slug: 'topic-edit',
bbs_posts_show_in_rest_s: false,
},
groups: [
{
key: 'vote',
title: '投票',
icon: 'fas fa-poll',
fields: [
{ key: 'bbs_vote_number_type', label: '投票显示', type: 'select', default: 'percent', options: [{ label: '百分比', value: 'percent' }, { label: '票数', value: 'number' }] },
{ key: 'bbs_vote_max', label: '选项上限', type: 'number', default: 8, min: 2, max: 20 },
],
},
{
key: 'moderator_apply',
title: '版主申请',
icon: 'fas fa-user-plus',
fields: [
{ key: 'bbs_apply_moderator_desc', label: '申请说明', type: 'textarea', default: '欢迎有热情的用户申请成为版主,协助管理社区秩序。' },
],
},
{
key: 'misc',
title: '杂项',
icon: 'fas fa-sliders-h',
fields: [
{ key: 'bbs_term_thumb', label: '标签默认缩略图', type: 'media', default: '' },
{ key: 'bbs_post_lists_hide_pay_s', label: '列表隐藏付费标记', type: 'switch', default: false },
{ key: 'bbs_post_title_strlen_limit', label: '标题字数限制', type: 'number', default: 30, min: 5, max: 100, help: '最小 5,最大 100' },
{ key: 'bbs_t_placeholder', label: '标题占位符', type: 'input', default: '请输入标题' },
{ key: 'bbs_c_placeholder', label: '内容占位符', type: 'input', default: '请输入内容' },
],
},
{
key: 'url',
title: 'URL / 别名',
icon: 'fas fa-link',
fields: [
{ key: 'bbs_rewrite_suffix_html_s', label: '.html 后缀', type: 'switch', default: true },
{ key: 'bbs_plate_rewrite_slug', label: '版块别名', type: 'input', default: 'board' },
{ key: 'bbs_posts_rewrite_slug', label: '帖子别名', type: 'input', default: 'topic' },
{ key: 'bbs_posts_edit_rewrite_slug', label: '编辑别名', type: 'input', default: 'topic-edit' },
{ key: 'bbs_posts_show_in_rest_s', label: '古腾堡编辑器', type: 'switch', default: false, help: '开启后帖子可使用 Gutenberg' },
],
},
],
}
@@ -1,64 +0,0 @@
export const permissionsSchema = {
key: 'forum.permissions',
title: '权限配置',
icon: 'fas fa-lock',
description: '发帖限制、按钮显示、帖子付费和版块付费关注配置。',
defaults: {
bbs_posts_add_limit_opt_max: 10,
bbs_plate_add_limit_opt_max: 3,
bbs_show_new_plate: true,
bbs_show_new_topic: true,
bbs_show_apply_moderator: true,
bbs_post_pay_hide_type_s: true,
bbs_post_pay_vip_price_s: true,
bbs_post_pay_vip_1_discount: 80,
bbs_post_pay_vip_2_discount: 50,
bbs_post_pay_price_limit: 1000,
bbs_plate_follow_pay_s: false,
bbs_plate_follow_pay_price: 10,
bbs_plate_follow_pay_type: 'money',
},
groups: [
{
key: 'posting',
title: '发帖限制',
icon: 'fas fa-ban',
fields: [
{ key: 'bbs_posts_add_limit_opt_max', label: '每日发帖上限', type: 'number', default: 10, min: 0, help: '0 = 不限' },
{ key: 'bbs_plate_add_limit_opt_max', label: '建版数量上限', type: 'number', default: 3, min: 0 },
],
},
{
key: 'display_btns',
title: '按钮显示',
icon: 'fas fa-eye',
fields: [
{ key: 'bbs_show_new_plate', label: '始终显示建版按钮', type: 'switch', default: true },
{ key: 'bbs_show_new_topic', label: '始终显示发帖按钮', type: 'switch', default: true },
{ key: 'bbs_show_apply_moderator', label: '始终显示申请版主', type: 'switch', default: true },
],
},
{
key: 'paid_content',
title: '帖子付费',
icon: 'fas fa-money-bill-wave',
fields: [
{ key: 'bbs_post_pay_hide_type_s', label: '启用付费内容', type: 'switch', default: true },
{ key: 'bbs_post_pay_vip_price_s', label: 'VIP 特价', type: 'switch', default: true, dependsOn: { key: 'bbs_post_pay_hide_type_s', value: true } },
{ key: 'bbs_post_pay_vip_1_discount', label: '一级 VIP 折扣 (%)', type: 'number', default: 80, min: 0, max: 100, dependsOn: { key: 'bbs_post_pay_vip_price_s', value: true } },
{ key: 'bbs_post_pay_vip_2_discount', label: '二级 VIP 折扣 (%)', type: 'number', default: 50, min: 0, max: 100, dependsOn: { key: 'bbs_post_pay_vip_price_s', value: true } },
{ key: 'bbs_post_pay_price_limit', label: '最高定价', type: 'number', default: 1000, min: 1, dependsOn: { key: 'bbs_post_pay_hide_type_s', value: true } },
],
},
{
key: 'plate_follow',
title: '版块付费关注',
icon: 'fas fa-heart',
fields: [
{ key: 'bbs_plate_follow_pay_s', label: '启用付费关注', type: 'switch', default: false },
{ key: 'bbs_plate_follow_pay_price', label: '关注价格', type: 'number', default: 10, min: 0, dependsOn: { key: 'bbs_plate_follow_pay_s', value: true } },
{ key: 'bbs_plate_follow_pay_type', label: '支付方式', type: 'select', default: 'money', options: [{ label: '现金', value: 'money' }, { label: '积分', value: 'points' }], dependsOn: { key: 'bbs_plate_follow_pay_s', value: true } },
],
},
],
}
@@ -1,60 +0,0 @@
export const postPageSchema = {
key: 'forum.post_page',
title: '帖子页面',
icon: 'fas fa-file-alt',
description: '帖子页面包屑、评分、高度限制、封面比例和视频静音配置。',
defaults: {
bbs_breadcrumbs_s: true,
bbs_breadcrumbs_home: true,
bbs_breadcrumbs_bbs_home: true,
bbs_breadcrumbs_bbs_home_name: '论坛',
bbs_breadcrumbs_plate_cat: true,
bbs_score_extra_max: 5,
bbs_score_deduct_max: 3,
bbs_posts_maxheight_s: false,
bbs_posts_maxheight: 800,
bbs_posts_cover_ratio: '16:9',
bbs_posts_cover_video_mute: true,
},
groups: [
{
key: 'breadcrumbs',
title: '面包屑',
icon: 'fas fa-angle-double-right',
fields: [
{ key: 'bbs_breadcrumbs_s', label: '显示面包屑', type: 'switch', default: true },
{ key: 'bbs_breadcrumbs_home', label: '显示首页', type: 'switch', default: true, dependsOn: { key: 'bbs_breadcrumbs_s', value: true } },
{ key: 'bbs_breadcrumbs_bbs_home', label: '显示论坛首页', type: 'switch', default: true, dependsOn: { key: 'bbs_breadcrumbs_s', value: true } },
{ key: 'bbs_breadcrumbs_bbs_home_name', label: '论坛首页名称', type: 'input', default: '论坛', dependsOn: { key: 'bbs_breadcrumbs_bbs_home', value: true } },
{ key: 'bbs_breadcrumbs_plate_cat', label: '显示分区', type: 'switch', default: true, dependsOn: { key: 'bbs_breadcrumbs_s', value: true } },
],
},
{
key: 'score',
title: '评分设置',
icon: 'fas fa-star',
fields: [
{ key: 'bbs_score_extra_max', label: '加分上限', type: 'number', default: 5, min: 1, max: 50 },
{ key: 'bbs_score_deduct_max', label: '扣分上限', type: 'number', default: 3, min: 1, max: 50 },
],
},
{
key: 'display',
title: '内容显示',
icon: 'fas fa-expand-arrows-alt',
fields: [
{ key: 'bbs_posts_maxheight_s', label: '内容高度限制', type: 'switch', default: false },
{ key: 'bbs_posts_maxheight', label: '最大高度 (px)', type: 'number', default: 800, min: 300, max: 3000, dependsOn: { key: 'bbs_posts_maxheight_s', value: true } },
],
},
{
key: 'cover',
title: '封面设置',
icon: 'fas fa-image',
fields: [
{ key: 'bbs_posts_cover_ratio', label: '封面比例', type: 'select', default: '16:9', options: [{ label: '16:9', value: '16:9' }, { label: '4:3', value: '4:3' }, { label: '1:1', value: '1:1' }, { label: '3:4', value: '3:4' }] },
{ key: 'bbs_posts_cover_video_mute', label: '视频封面静音', type: 'switch', default: true },
],
},
],
}
@@ -1,44 +0,0 @@
export const replySchema = {
key: 'forum.reply',
title: '回复评论',
icon: 'fas fa-reply-all',
description: '回复翻页、工具栏功能、表情、代码和快捷回复配置。',
defaults: {
bbs_reply_paginate_type: 'ajax',
bbs_reply_paginate_ias_s: true,
bbs_reply_paginate_ias_max: 3,
bbs_comment_smilie: true,
bbs_comment_code: true,
bbs_comment_img: true,
bbs_comment_upload_img: true,
bbs_comment_quick_s: true,
bbs_comment_quick_often: '不错,学习了\n感谢分享\n期待更新',
bbs_comment_placeholder: '写下你的回复…',
},
groups: [
{
key: 'paginate',
title: '翻页',
icon: 'fas fa-chevron-circle-right',
fields: [
{ key: 'bbs_reply_paginate_type', label: '翻页方式', type: 'select', default: 'ajax', options: [{ label: 'AJAX 加载', value: 'ajax' }, { label: '传统翻页', value: 'page' }] },
{ key: 'bbs_reply_paginate_ias_s', label: '无限滚动', type: 'switch', default: true },
{ key: 'bbs_reply_paginate_ias_max', label: '自动加载页数', type: 'number', default: 3, min: 0, max: 10, dependsOn: { key: 'bbs_reply_paginate_ias_s', value: true } },
],
},
{
key: 'toolbar',
title: '回复工具栏',
icon: 'fas fa-toolbox',
fields: [
{ key: 'bbs_comment_smilie', label: '表情', type: 'switch', default: true },
{ key: 'bbs_comment_code', label: '代码', type: 'switch', default: true },
{ key: 'bbs_comment_img', label: '插图', type: 'switch', default: true },
{ key: 'bbs_comment_upload_img', label: '上传图片', type: 'switch', default: true },
{ key: 'bbs_comment_quick_s', label: '快捷回复', type: 'switch', default: true },
{ key: 'bbs_comment_quick_often', label: '预设短语', type: 'textarea', default: '不错,学习了\n感谢分享\n期待更新', help: '每行一条', dependsOn: { key: 'bbs_comment_quick_s', value: true } },
{ key: 'bbs_comment_placeholder', label: '回复占位符', type: 'input', default: '写下你的回复…' },
],
},
],
}
@@ -1,27 +0,0 @@
export const uploadSchema = {
key: 'forum.upload',
title: '上传权限',
icon: 'fas fa-cloud-upload-alt',
description: '论坛帖子图片、视频和文件的上传大小与格式限制。',
defaults: {
bbs_upload_img_size: 2048,
bbs_image_upload_multiple: 9,
bbs_upload_video_size: 20480,
bbs_upload_file_size: 10240,
bbs_upload_file_mimes: 'jpg,jpeg,png,gif,mp4,zip,rar,pdf',
},
groups: [
{
key: 'limits',
title: '上传限制',
icon: 'fas fa-weight-hanging',
fields: [
{ key: 'bbs_upload_img_size', label: '图片大小 (KB)', type: 'number', default: 2048, min: 100 },
{ key: 'bbs_image_upload_multiple', label: '批量上传数', type: 'number', default: 9, min: 1, max: 20 },
{ key: 'bbs_upload_video_size', label: '视频大小 (KB)', type: 'number', default: 20480, min: 1024 },
{ key: 'bbs_upload_file_size', label: '文件大小 (KB)', type: 'number', default: 10240, min: 100 },
{ key: 'bbs_upload_file_mimes', label: '允许格式', type: 'input', default: 'jpg,jpeg,png,gif,mp4,zip,rar,pdf', help: '逗号分隔' },
],
},
],
}
@@ -25,8 +25,6 @@ export const searchSchema = {
search_type_user: false,
search_type_category: true,
search_type_tag: true,
search_type_product: false,
search_type_topic: false,
search_default_type: 'post',
search_ms_enabled: false,
search_ms_api_url: '',
@@ -63,8 +61,6 @@ export const searchSchema = {
{ key: 'search_type_user', label: '用户', type: 'switch', default: false },
{ key: 'search_type_category', label: '分类', type: 'switch', default: true },
{ key: 'search_type_tag', label: '标签', type: 'switch', default: true },
{ key: 'search_type_product', label: '商品', type: 'switch', default: false },
{ key: 'search_type_topic', label: '帖子', type: 'switch', default: false },
{
key: 'search_default_type',
label: '默认类型',
@@ -75,8 +71,6 @@ export const searchSchema = {
{ label: '用户', value: 'user' },
{ label: '分类', value: 'category' },
{ label: '标签', value: 'tag' },
{ label: '商品', value: 'product' },
{ label: '帖子', value: 'topic' },
],
},
],
@@ -136,15 +136,9 @@ export const pageDisplaySchemas = {
], dependsOn: { key: 'header_newposts_enabled', value: true } },
{ key: 'header_newposts_types', label: '发布类型', type: 'checkbox', default: ['post'], options: [
{ label: '文章', value: 'post' },
{ label: '帖子', value: 'topic' },
{ label: '商品', value: 'product' },
{ label: '版块', value: 'plate' },
], dependsOn: { key: 'header_newposts_enabled', value: true }, help: '未开启的模块不会显示' },
{ key: 'header_user_newposts_btn', label: '用户卡发布类型', type: 'select', default: 'post', options: [
{ label: '文章', value: 'post' },
{ label: '帖子', value: 'topic' },
{ label: '商品', value: 'product' },
{ label: '版块', value: 'plate' },
] },
{ key: 'header_user_newposts_text', label: '用户卡发布文案', type: 'input', default: '发布内容' },
{ key: 'header_nav_pay_vip', label: '导航栏开通会员按钮', type: 'switch', default: true, help: '顶部导航显示开通会员' },
@@ -349,7 +343,7 @@ export const pageDisplaySchemas = {
title: 'Tab 配置',
icon: 'fas fa-columns',
fields: [
{ key: 'user_home_tabs', label: '显示的 Tab', type: 'input', default: 'posts,favorites,comments,follow', help: '逗号分隔;默认 posts,favorites,comments,follow(粉丝)。可选 credits,product,plate,forum' },
{ key: 'user_home_tabs', label: '显示的 Tab', type: 'input', default: 'posts,favorites,comments,follow', help: '逗号分隔;默认 posts,favorites,comments,follow(粉丝)。可选 credits' },
{ key: 'user_home_tabs_order', label: 'Tab 显示顺序', type: 'input', default: 'posts,favorites,comments,follow', help: '逗号分隔,控制栏目顺序' },
{ key: 'user_home_tabs_pc_hide', label: 'PC 端隐藏', type: 'input', default: '', help: '逗号分隔要在 PC 端隐藏的栏目' },
{ key: 'user_home_tabs_mobile_hide', label: '移动端隐藏', type: 'input', default: '', help: '逗号分隔要在移动端隐藏的栏目' },
@@ -669,9 +663,6 @@ export const pageDisplaySchemas = {
{ label: '用户页', value: 'author' },
{ label: '文章页', value: 'single' },
{ label: '搜索页', value: 'search' },
{ label: '[论坛]首页', value: 'forum_home' },
{ label: '[论坛]板块页面', value: 'forum_plate' },
{ label: '[论坛]帖子页面', value: 'forum_post' },
{ label: '其它页面', value: 'page' },
], help: '选择启用顶部多功能组件的页面类型,一个都不选则为关闭此功能' },
{ key: 'header_components_device_visibility', label: '设备显示规则', type: 'radio', default: '', options: [
@@ -717,13 +708,10 @@ export const pageDisplaySchemas = {
{ key: 'header_components_search_popular_limit', label: '关键词最多显示', type: 'number', default: 6, min: 2, max: 50, dependsOn: { key: 'header_components_search_show_keywords', value: true } },
{ key: 'header_components_search_show_input_cat', label: '分类搜索', type: 'switch', default: false, dependsOn: { key: 'header_components_search_enabled', value: true }, help: '显示分类搜索选择' },
{ key: 'header_components_search_in_cat', label: '默认搜索的分类', type: 'input', default: '', dependsOn: { key: 'header_components_search_show_input_cat', value: true }, help: '分类 ID,可留空' },
{ key: 'header_components_search_show_type', label: '显示类型选择', type: 'switch', default: false, dependsOn: { key: 'header_components_search_enabled', value: true }, help: '显示切换搜索类型的按钮(文章、用户、版块、帖子、商品)' },
{ key: 'header_components_search_show_type', label: '显示类型选择', type: 'switch', default: false, dependsOn: { key: 'header_components_search_enabled', value: true }, help: '显示切换搜索类型的按钮(文章、用户)' },
{ key: 'header_components_search_in_type', label: '默认搜索类型', type: 'radio', default: 'post', dependsOn: { key: 'header_components_search_show_type', value: true }, options: [
{ label: '文章', value: 'post' },
{ label: '用户', value: 'user' },
{ label: '商品', value: 'product' },
{ label: '帖子', value: 'forum' },
{ label: '版块', value: 'plate' },
]},
{
key: 'header_components_search_before_html',
@@ -7,16 +7,10 @@ export const MEDAL_GET_TYPES = [
{ label: '注册天数', value: 'registration_time' },
{ label: '累计签到天数', value: 'checkin_all_day' },
{ label: '发布文章数', value: 'new_post' },
{ label: '论坛发帖数', value: 'bbs_post' },
{ label: '评论数', value: 'comment' },
{ label: '粉丝数', value: 'followed' },
{ label: '文章获赞', value: 'like' },
{ label: '评论获赞', value: 'comment_like' },
{ label: '回复被采纳', value: 'bbs_adopt' },
{ label: '神评次数', value: 'bbs_comment_hot' },
{ label: '热帖次数', value: 'bbs_post_hot' },
{ label: '精华帖数', value: 'bbs_essence' },
{ label: '帖子评分累计', value: 'bbs_score' },
{ label: '内容阅读量', value: 'views' },
{ label: '创作分成累计(元)', value: 'pay_income' },
{ label: '推广佣金累计(元)', value: 'pay_rebate' },
@@ -19,13 +19,6 @@ export const creditsSchema = {
points_free_comment_new: 1,
points_free_comment_like: 1,
points_free_followed: 1,
points_free_bbs_posts_new: 5,
points_free_bbs_reply: 2,
points_free_bbs_essence: 10,
points_free_bbs_adopt: 20,
points_free_bbs_posts_hot: 5,
points_free_bbs_comment_hot: 5,
points_free_bbs_score_extra: 3,
points_earn_invite: 10,
points_spend_comment: 0,
points_spend_download: 0,
@@ -74,13 +67,6 @@ export const creditsSchema = {
{ key: 'points_free_comment_new', label: '发表评论', type: 'number', default: 1, min: 0, max: 100 },
{ key: 'points_free_comment_like', label: '评论被赞', type: 'number', default: 1, min: 0, max: 100 },
{ key: 'points_free_followed', label: '被关注', type: 'number', default: 1, min: 0, max: 100 },
{ key: 'points_free_bbs_posts_new', label: '论坛发帖', type: 'number', default: 5, min: 0, max: 1000 },
{ key: 'points_free_bbs_reply', label: '论坛回复', type: 'number', default: 2, min: 0, max: 1000 },
{ key: 'points_free_bbs_essence', label: '帖子加精', type: 'number', default: 10, min: 0, max: 1000 },
{ key: 'points_free_bbs_adopt', label: '回复被采纳', type: 'number', default: 20, min: 0, max: 1000 },
{ key: 'points_free_bbs_posts_hot', label: '热帖奖励', type: 'number', default: 5, min: 0, max: 1000 },
{ key: 'points_free_bbs_comment_hot', label: '神评奖励', type: 'number', default: 5, min: 0, max: 1000 },
{ key: 'points_free_bbs_score_extra', label: '帖子加分', type: 'number', default: 3, min: 0, max: 1000 },
{ key: 'points_earn_invite', label: '邀请注册奖励', type: 'number', default: 10, min: 0, max: 1000, help: '邀请不计入免费日上限' },
],
},
@@ -11,14 +11,6 @@ export const INTEGRAL_CATALOG = [
{ key: 'post_favorite', label: '文章被收藏', default: 2, group: '文章' },
{ key: 'comment_new', label: '发表评论', default: 2, group: '文章' },
{ key: 'comment_like', label: '评论获赞', default: 1, group: '文章' },
{ key: 'bbs_posts_new', label: '发布帖子', default: 3, group: '论坛' },
{ key: 'bbs_score_extra', label: '帖子被加分', default: 1, group: '论坛' },
{ key: 'bbs_essence', label: '帖子评为精华', default: 2, group: '论坛' },
{ key: 'bbs_posts_hot', label: '帖子成为热门', default: 2, group: '论坛' },
{ key: 'bbs_plate_new', label: '创建版块', default: 2, group: '论坛' },
{ key: 'bbs_plate_hot', label: '版块成为热门', default: 2, group: '论坛' },
{ key: 'bbs_adopt', label: '回答被采纳', default: 2, group: '论坛' },
{ key: 'bbs_comment_hot', label: '评论成为神评', default: 2, group: '论坛' },
]
export function defaultLevelOpt(max = 10) {
-76
View File
@@ -1,76 +0,0 @@
@import url("forum/board.css");
@import url("forum/topic.css");
@import url("forum/compose.css");
@import url("forum/extras.css");
/* Forum home shell: sidebar compose CTA */
.aksrt-forum-sidebar-card--compose {
padding: 12px;
}
.aksrt-forum-sidebar-card--compose .aksrt-forum-quick-post__input {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 10px 12px;
border-radius: 10px;
background: var(--body-bg-color, #f5f6f7);
color: var(--muted-color, #777);
text-decoration: none;
}
.aksrt-forum-sidebar-card--compose .aksrt-forum-quick-post__input:hover {
color: var(--focus-color, #f04494);
}
.aksrt-forum-sidebar-card--compose .aksrt-forum-header__action {
display: inline-flex;
width: 100%;
justify-content: center;
}
.aksrt-forum-header__stats {
display: none; /* stats demoted on home shell; counts remain in eyebrow meta */
}
/* Forum pages share the single/archive content stream shell */
.content-shell--forum .content-wrap {
padding-top: 8px;
}
.content-shell--forum .content-layout__primary > .aksrt-forum-board,
.content-shell--forum .content-layout__primary > .aksrt-forum-topic,
.content-shell--forum .content-layout__primary > .aksrt-forum-tag,
.content-shell--forum .content-layout__primary > .aksrt-forum-plate-cat {
width: 100%;
}
.aksrt-forum-topic__cover {
margin: 0 0 16px;
border-radius: var(--main-radius, 8px);
overflow: hidden;
background: var(--body-bg-color, #f5f6f7);
}
.aksrt-forum-topic__cover--image {
aspect-ratio: var(--cover-ratio, 16/9);
}
.aksrt-forum-topic__cover img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.aksrt-forum-topic__cover--slide {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 100%;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.aksrt-forum-topic__cover--slide img {
scroll-snap-align: start;
aspect-ratio: 16/9;
}
.aksrt-forum-topic__paywall {
margin: 12px 0 20px;
padding: 16px;
}
-755
View File
@@ -1,755 +0,0 @@
/* Forum home-shell refinement */
.aksrt-forum-board__hero {
margin-bottom: 20px;
padding: 24px;
border-radius: var(--radius-2xl, 16px);
background: linear-gradient(180deg, rgba(240,68,148,0.05), rgba(255,255,255,0.96));
border: none;
box-shadow: var(--shadow-sm);
}
.aksrt-forum-board__summary {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 16px;
}
.aksrt-forum-board__summary-item {
min-width: 120px;
padding: 10px 14px;
border-radius: 999px;
background: var(--surface-1, #fff);
border: 1px solid var(--color-line-soft, rgba(0,0,0,0.06));
}
.aksrt-forum-board__summary-label {
display: block;
margin-bottom: 4px;
font-size: 12px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum-board__layout {
align-items: start;
}
.aksrt-forum-board__governance--inline {
margin-bottom: 20px;
}
.aksrt-forum-board__sidebar {
display: flex;
flex-direction: column;
gap: 16px;
}
.aksrt-forum-board__governance-card--sidebar {
position: sticky;
top: calc(var(--nav-height, 64px) + 16px);
}
.aksrt-forum-board__moderators--stacked {
flex-direction: column;
align-items: stretch;
}
.aksrt-forum-board {
padding-top: 24px;
padding-bottom: 48px;
}
.aksrt-forum-board__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 24px;
padding: 22px;
background: var(--surface-1, #fff);
border: none;
border-radius: var(--radius-2xl, 16px);
box-shadow: var(--shadow-sm);
}
.aksrt-forum-board__title {
margin: 0 0 4px;
font-size: 22px;
font-weight: 700;
color: var(--color-ink);
}
.aksrt-forum-board__desc {
margin: 0 0 8px;
font-size: 14px;
color: var(--color-ink-3, #8a909c);
}
.aksrt-forum-board__meta {
display: flex;
gap: 16px;
font-size: 13px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum-board__meta i {
margin-right: 4px;
}
.aksrt-forum-board__compose-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 18px;
border-radius: 8px;
background: var(--color-primary);
color: #fff;
text-decoration: none;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
transition: opacity 0.15s;
}
.aksrt-forum-board__compose-btn:hover {
opacity: 0.85;
}
.aksrt-forum-board__actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: flex-end;
}
.aksrt-forum-board__manage-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
border-radius: 8px;
border: 1px solid var(--color-line, #d9d9d9);
background: var(--surface-1, #fff);
color: var(--color-ink);
text-decoration: none;
cursor: pointer;
font-size: 13px;
}
.aksrt-forum-board__manage-btn--danger {
border-color: var(--color-error, #f5222d);
color: var(--color-error, #f5222d);
}
.aksrt-forum-board__manage-btn--danger:hover {
background: var(--color-error, #f5222d);
color: #fff;
}
.aksrt-forum-board__governance {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
margin-bottom: 20px;
}
.aksrt-forum-board__governance-card {
padding: 18px;
background: var(--surface-1, #fff);
border: none;
border-radius: var(--main-radius, var(--radius, 8px));
box-shadow: var(--shadow-sm);
}
.aksrt-forum-board__section-title {
margin: 0 0 10px;
font-size: 15px;
font-weight: 600;
color: var(--color-ink);
}
.aksrt-forum-board__moderators {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.aksrt-forum-board__moderator {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border-radius: 999px;
background: var(--surface-1, #f8f9fa);
}
.aksrt-forum-board__moderator img {
width: 36px;
height: 36px;
border-radius: 50%;
}
.aksrt-forum-board__application-form textarea {
width: 100%;
border: 1px solid var(--color-line, #d9d9d9);
border-radius: 8px;
padding: 10px 12px;
resize: vertical;
font-family: inherit;
}
.aksrt-forum-board__application-actions {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
}
.aksrt-forum-board__apply-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 14px;
border: none;
border-radius: 8px;
background: var(--color-primary);
color: #fff;
cursor: pointer;
}
.aksrt-forum-board__application-note {
color: var(--color-ink-3, #8a909c);
}
.aksrt-forum-board__application-reason {
margin: 10px 0;
padding: 10px 12px;
border-left: 3px solid color-mix(in srgb, var(--color-primary) 55%, transparent);
background: var(--surface-2, #f8fafc);
color: var(--color-ink-3, #64748b);
font-size: 13px;
}
.aksrt-forum-board__governance-card .aksrt-forum-board__apply-btn {
text-decoration: none;
}
/* Sticky section */
.aksrt-forum-board__sticky {
margin-bottom: 16px;
padding: 12px 16px;
background: var(--surface-1, #fff);
border-radius: 8px;
border-left: 3px solid color-mix(in srgb, var(--color-primary) 55%, transparent);
}
.aksrt-forum-board__sticky h3 {
margin: 0 0 8px;
font-size: 13px;
color: var(--color-ink-3, #8a909c);
}
.aksrt-forum-board__sticky-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 0;
text-decoration: none;
color: var(--color-ink);
font-size: 14px;
}
.aksrt-forum-board__sticky-item:hover {
color: var(--color-primary);
}
.aksrt-forum-board__sticky-meta {
font-size: 12px;
color: var(--color-ink-4, #c0c4cc);
}
.aksrt-forum-board__sticky-badge {
display: inline-block;
padding: 1px 6px;
border-radius: 3px;
font-size: 11px;
font-weight: 500;
background: var(--color-primary);
color: #fff;
margin-right: 6px;
flex-shrink: 0;
}
/* Topic list items */
.aksrt-forum-board__topics {
display: flex;
flex-direction: column;
gap: 1px;
}
.aksrt-forum-topic-item {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 18px;
background: var(--surface-1, #fff);
border-bottom: 1px solid var(--color-line-soft);
transition: background 0.15s, box-shadow 0.15s;
}
.aksrt-forum-topic-item:first-child {
border-radius: 8px 8px 0 0;
}
.aksrt-forum-topic-item:last-child {
border-radius: 0 0 8px 8px;
}
.aksrt-forum-topic-item:hover {
background: var(--surface-4, #f8f9fa);
}
.aksrt-forum-topic-item.is-sticky {
background: var(--surface-2, #f0f5ff);
}
.aksrt-forum-topic-item.is-featured {
border-left: 3px solid var(--color-success, #52c41a);
}
.aksrt-forum-topic-item__avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
}
.aksrt-forum-topic-item__body {
flex: 1;
min-width: 0;
}
.aksrt-forum-topic-item__title {
font-size: 15px;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
}
.aksrt-forum-topic-item__title a {
color: var(--color-ink);
text-decoration: none;
}
.aksrt-forum-topic-item__title a:hover {
color: var(--color-primary);
}
.aksrt-forum-topic-item__badge {
display: inline-block;
padding: 1px 6px;
border-radius: 3px;
font-size: 11px;
font-weight: 500;
}
.aksrt-forum-topic-item__badge--sticky {
background: var(--color-primary);
color: #fff;
}
/* 置顶分级视觉区分(§7.3):超级置顶加深色,全局置顶用红色凸显。 */
.aksrt-forum-topic-item__badge--sticky-super {
background: var(--focus-color, #fd53a1);
color: #fff;
}
.aksrt-forum-topic-item__badge--sticky-global {
background: #e53e3e;
color: #fff;
}
.aksrt-forum-topic-item__badge--featured {
background: var(--color-success, #52c41a);
color: #fff;
}
.aksrt-forum-topic-item__badge--muted {
background: var(--surface-2, #f5f7fa);
color: var(--color-ink-3, #8a909c);
}
.aksrt-forum-topic-item__lock {
color: var(--color-warning, #fa8c16);
font-size: 12px;
}
.aksrt-forum-topic-item__meta {
display: flex;
gap: 12px;
margin-top: 3px;
font-size: 12px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum-topic-item__stats {
display: flex;
gap: 12px;
font-size: 12px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum-topic-item__stats i {
margin-right: 3px;
}
.aksrt-forum-topic-item__actions {
display: flex;
flex-direction: column;
gap: 6px;
}
.aksrt-forum-topic-item__action {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 6px 10px;
border-radius: 999px;
border: 1px solid var(--color-line, #d9d9d9);
background: #fff;
color: var(--color-ink);
text-decoration: none;
cursor: pointer;
font-size: 12px;
white-space: nowrap;
}
.aksrt-forum-topic-item__action--link {
background: var(--surface-1, #f8f9fa);
}
/* Pagination */
.aksrt-forum-board__pagination {
display: flex;
justify-content: center;
padding: 20px 0;
}
/* Empty state */
.aksrt-forum-board__empty {
text-align: center;
padding: 48px 24px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum-board__empty i {
font-size: 40px;
margin-bottom: 12px;
display: block;
}
.aksrt-forum-board__empty a {
display: inline-block;
margin-top: 12px;
color: var(--color-primary);
}
/* Board sort tabs */
.aksrt-forum-board__tabs {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 0 0 16px;
padding: 8px;
border-radius: var(--radius-xl, 12px);
background: var(--surface-1, #fff);
box-shadow: var(--shadow-sm);
}
.aksrt-forum-board__tab {
display: inline-flex;
align-items: center;
padding: 8px 14px;
border-radius: 999px;
color: var(--color-ink-3, #5c6370);
text-decoration: none;
font-size: 14px;
font-weight: 600;
transition: background .15s ease, color .15s ease;
}
.aksrt-forum-board__tab:hover {
background: rgba(240, 68, 148, 0.08);
color: var(--color-primary, #f04494);
}
.aksrt-forum-board__tab.is-active {
background: var(--color-primary, #f04494);
color: #fff;
}
.aksrt-forum-board__governance--footer {
margin-top: 28px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.aksrt-forum-topic-row {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 14px 16px;
margin-bottom: 10px;
border-radius: var(--radius-xl, 12px);
background: var(--surface-1, #fff);
box-shadow: var(--shadow-sm);
}
.aksrt-forum-topic-row__avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.aksrt-forum-topic-row__body {
flex: 1;
min-width: 0;
}
.aksrt-forum-topic-row__title-wrap {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
}
.aksrt-forum-topic-row__title {
margin: 0;
font-size: 16px;
font-weight: 700;
}
.aksrt-forum-topic-row__title a {
color: var(--color-ink);
text-decoration: none;
}
.aksrt-forum-topic-row__meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 6px;
color: var(--color-ink-4, #8a909c);
font-size: 12px;
}
.aksrt-forum-topic-row__badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 999px;
font-size: 12px;
font-weight: 700;
line-height: 1.4;
}
.aksrt-forum-topic-row__badge--sticky { background: rgba(255, 149, 0, 0.12); color: #c77700; }
.aksrt-forum-topic-row__badge--featured { background: rgba(240, 68, 148, 0.12); color: #f04494; }
.aksrt-forum-topic-row__badge--question { background: rgba(47, 128, 237, 0.12); color: #2f80ed; }
.aksrt-forum-topic-row__badge--question.is-solved { background: rgba(39, 174, 96, 0.12); color: #27ae60; }
.aksrt-forum-topic-row__cover {
position: relative;
width: 84px;
height: 60px;
flex: 0 0 auto;
border-radius: 10px;
overflow: hidden;
background: var(--surface-2, #f5f6f8);
}
.aksrt-forum-topic-row__cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
.aksrt-forum-topic-row__cover .fa-play-circle,
.aksrt-forum-topic-row__cover-count {
position: absolute;
right: 6px;
bottom: 6px;
color: #fff;
text-shadow: 0 1px 2px rgba(0,0,0,.35);
}
/* detail list card (zibll forum-posts detail) */
.aksrt-forum-topic-row.detail,
.aksrt-forum-posts.detail {
display: block;
padding: 16px;
margin-bottom: 12px;
border-radius: 14px;
background: var(--surface-1, #fff);
box-shadow: var(--shadow-sm, 0 1px 4px rgba(0,0,0,.06));
}
.aksrt-forum-topic-row__author-card { gap: 10px; }
.aksrt-forum-topic-row__author-name { font-weight: 700; font-size: 14px; }
.aksrt-forum-topic-row.detail .aksrt-forum-topic-row__cover,
.aksrt-forum-posts.detail .forum-lists-cover {
display: block;
width: 100%;
height: auto;
max-height: 280px;
margin: 0 0 12px;
border-radius: 12px;
overflow: hidden;
}
.aksrt-forum-topic-row.detail .aksrt-forum-topic-row__cover img {
width: 100%;
height: auto;
max-height: 280px;
object-fit: cover;
display: block;
}
.aksrt-forum-topic-row .excerpt {
margin-top: 6px;
font-size: 14px;
line-height: 1.65;
color: var(--color-ink-2, #4b5563);
}
.aksrt-forum-topic-row .lists-imgs {
display: grid;
gap: 6px;
margin-top: 8px;
}
.aksrt-forum-topic-row .lists-imgs.count-1 { grid-template-columns: 1fr; max-width: 280px; }
.aksrt-forum-topic-row .lists-imgs.count-2 { grid-template-columns: 1fr 1fr; }
.aksrt-forum-topic-row .lists-imgs.count-3,
.aksrt-forum-topic-row .lists-imgs.count-4 { grid-template-columns: 1fr 1fr; }
.aksrt-forum-topic-row .lists-imgs__item {
display: block;
border-radius: 10px;
overflow: hidden;
aspect-ratio: 1;
background: var(--surface-2, #f3f4f6);
}
.aksrt-forum-topic-row .lists-imgs__item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.aksrt-forum-topic-row__vote { display: grid; gap: 6px; }
.aksrt-forum-topic-row__vote-opt {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(80px, 2fr) 42px;
gap: 8px;
align-items: center;
font-size: 13px;
}
.aksrt-forum-topic-row__vote-bar {
height: 8px;
border-radius: 999px;
background: var(--surface-4, #eef0f3);
overflow: hidden;
}
.aksrt-forum-topic-row__vote-bar i {
display: block;
height: 100%;
background: var(--color-primary, #f04494);
}
.aksrt-forum-topic-row__footer .but-plate,
.aksrt-forum-topic-row__board.but {
display: inline-flex;
align-items: center;
max-width: 46%;
padding: 4px 10px;
border-radius: 999px;
background: var(--surface-4, #f3f4f6);
color: var(--color-ink-2, #5c6370);
text-decoration: none;
font-size: 12px;
font-weight: 600;
}
.aksrt-forum-topic-row__actions {
display: inline-flex;
gap: 12px;
color: var(--color-ink-3, #8a909c);
font-size: 13px;
}
.aksrt-forum-topic-row__actions a { color: inherit; text-decoration: none; }
.aksrt-forum-topic-row.mini { display: flex; gap: 12px; align-items: flex-start; }
.aksrt-forum-board-card__grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 12px;
}
.aksrt-forum-board-card {
display: flex;
flex-direction: column;
overflow: hidden;
border-radius: var(--radius-xl, 12px);
background: var(--surface-1, #fff);
box-shadow: var(--shadow-sm);
text-decoration: none;
color: inherit;
}
.aksrt-forum-board-card__cover {
display: block;
height: 96px;
background-size: cover;
background-position: center;
}
.aksrt-forum-board-card__cover--empty {
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, rgba(240,68,148,.08), rgba(47,128,237,.08));
color: var(--color-primary, #f04494);
font-size: 24px;
}
.aksrt-forum-board-card__body {
display: flex;
flex-direction: column;
gap: 6px;
padding: 12px 14px 14px;
}
.aksrt-forum-board-card__title {
font-weight: 700;
}
.aksrt-forum-board-card__desc,
.aksrt-forum-board-card__meta {
font-size: 12px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum__empty,
.aksrt-forum-board__empty,
.aksrt-forum-tag__empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
padding: 48px 20px;
text-align: center;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum__empty a,
.aksrt-forum-board__empty a {
color: var(--color-primary, #f04494);
font-weight: 600;
text-decoration: none;
}
-714
View File
@@ -1,714 +0,0 @@
/* ============================================================
* Compose page
* ============================================================ */
.aksrt-forum-compose {
padding-top: 24px;
padding-bottom: 48px;
max-width: 1100px;
margin: 0 auto;
}
.aksrt-forum-compose__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.aksrt-forum-compose__header h1 {
margin: 0;
font-size: 22px;
}
.aksrt-forum-compose__notice {
margin: 8px 0 0;
color: var(--color-ink-3, #8a909c);
}
.aksrt-forum-compose__back {
color: var(--color-ink-3, #8a909c);
text-decoration: none;
font-size: 14px;
}
.aksrt-forum-compose__back:hover {
color: var(--color-primary);
}
/* §7.4 — Two-column layout: main + sidebar */
.aksrt-forum-compose__layout {
display: grid;
grid-template-columns: 1fr 311px;
gap: 24px;
align-items: start;
}
.aksrt-forum-compose__main {
min-width: 0;
}
.aksrt-forum-compose__sidebar {
display: flex;
flex-direction: column;
gap: 16px;
}
/* §7.4 — Sidebar panels */
.aksrt-forum-compose__panel {
background: var(--surface-1, #fff);
border: none;
border-radius: var(--main-radius, var(--radius, 8px));
box-shadow: var(--shadow-sm);
}
.aksrt-forum-compose__panel-title {
margin: 0;
padding: 12px 16px;
font-size: 14px;
font-weight: 600;
color: var(--color-ink);
border-bottom: 1px solid var(--color-line, #eceff4);
display: flex;
align-items: center;
gap: 6px;
}
.aksrt-forum-compose__panel-title i {
color: var(--color-ink-3, #8a909c);
font-size: 13px;
}
.aksrt-forum-compose__panel-body {
padding: 14px 16px;
}
.aksrt-forum-compose__readonly {
margin: 0;
color: var(--color-ink-2, #555);
font-size: 14px;
}
.aksrt-forum-compose__status-line {
margin: 0 0 12px;
font-size: 13px;
color: var(--color-ink-3, #8a909c);
display: flex;
align-items: center;
gap: 6px;
}
.aksrt-forum-compose__form {
padding: 0;
background: transparent;
border-radius: 0;
box-shadow: none;
}
.aksrt-forum-compose__main {
padding: 24px;
background: var(--surface-1, #fff);
border: none;
border-radius: var(--radius-2xl, 16px);
box-shadow: var(--shadow-sm);
}
.aksrt-forum-compose__field {
margin-bottom: 16px;
}
.aksrt-forum-compose__field:last-child {
margin-bottom: 0;
}
.aksrt-forum-compose__field label {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--color-ink);
margin-bottom: 6px;
}
.aksrt-forum-compose__field .required {
color: var(--color-error, #f5222d);
}
.aksrt-forum-compose__field select,
.aksrt-forum-compose__field input[type="text"],
.aksrt-forum-compose__field input[type="number"],
.aksrt-forum-compose__field input[type="url"] {
width: 100%;
border: 1px solid var(--color-line, #d9d9d9);
border-radius: 8px;
padding: 8px 12px;
font-size: 14px;
font-family: inherit;
box-sizing: border-box;
}
.aksrt-forum-compose__field textarea {
width: 100%;
border: 1px solid var(--color-line, #d9d9d9);
border-radius: 8px;
padding: 10px 12px;
font-size: 14px;
resize: vertical;
font-family: inherit;
box-sizing: border-box;
}
.aksrt-forum-compose__checkbox {
display: inline-flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
/* §7.4 — wp_editor wrapper */
.aksrt-forum-compose__editor-wrap {
margin-bottom: 0;
}
.aksrt-forum-compose__editor-wrap .wp-editor-wrap {
border-radius: 8px;
overflow: hidden;
}
.aksrt-forum-compose__editor-wrap .wp-editor-container {
border: 1px solid var(--color-line, #d9d9d9);
border-radius: 8px;
}
/* §7.4 — Character counter */
.aksrt-forum-compose__char-counter {
display: block;
text-align: right;
font-size: 12px;
color: var(--color-ink-3, #8a909c);
margin-top: 4px;
}
.aksrt-forum-compose__char-counter.is-near {
color: var(--color-warning, #d48806);
}
.aksrt-forum-compose__char-counter.is-over {
color: var(--color-error, #cf1322);
font-weight: 600;
}
/* §7.4 — Draft notice */
.aksrt-forum-compose__draft-notice {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-bottom: 20px;
padding: 12px 16px;
background: var(--color-info-bg, #e6f7ff);
border: 1px solid var(--color-info-border, #91d5ff);
border-radius: 8px;
font-size: 14px;
color: var(--color-info, #0050b3);
}
.aksrt-forum-compose__draft-toggle {
border: none;
background: none;
color: var(--color-primary);
cursor: pointer;
font-size: 13px;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 0;
}
.aksrt-forum-compose__draft-list {
width: 100%;
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.aksrt-forum-compose__draft-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--surface-1, #fff);
border-radius: 6px;
font-size: 13px;
}
.aksrt-forum-compose__draft-title {
flex: 1;
color: var(--color-ink);
text-decoration: none;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.aksrt-forum-compose__draft-title:hover {
color: var(--color-primary);
}
.aksrt-forum-compose__draft-meta {
color: var(--color-ink-3, #8a909c);
font-size: 12px;
flex-shrink: 0;
}
.aksrt-forum-compose__draft-delete {
border: none;
background: none;
color: var(--color-ink-3, #8a909c);
cursor: pointer;
padding: 2px;
font-size: 12px;
}
.aksrt-forum-compose__draft-delete:hover {
color: var(--color-error, #cf1322);
}
/* §7.4 — Remote search select */
.aksrt-remote-select {
position: relative;
}
.aksrt-remote-select__input {
width: 100%;
}
.aksrt-remote-select__dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 100;
background: var(--surface-1, #fff);
border: none;
border-top: none;
border-radius: 0 0 12px 12px;
max-height: 200px;
overflow-y: auto;
box-shadow: var(--shadow-lg);
}
.aksrt-remote-select__option {
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
}
.aksrt-remote-select__option:hover {
background: var(--color-primary-bg, #e6f7ff);
}
/* §7.4 — Tag tokenizer */
.aksrt-tag-input {
position: relative;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
padding: 8px 10px;
border: 1px solid var(--color-line, #d9d9d9);
border-radius: 12px;
background: var(--surface-4, #fff);
min-height: 42px;
}
.aksrt-tag-input__tokens {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.aksrt-tag-input__token {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
background: var(--color-primary-bg, #e6f7ff);
color: var(--color-primary, #1890ff);
border-radius: 4px;
font-size: 13px;
}
.aksrt-tag-input__token-remove {
border: none;
background: none;
color: var(--color-primary, #1890ff);
cursor: pointer;
padding: 0;
font-size: 14px;
line-height: 1;
opacity: 0.7;
}
.aksrt-tag-input__token-remove:hover {
opacity: 1;
}
.aksrt-tag-input__input {
flex: 1;
min-width: 80px;
border: none !important;
padding: 2px 4px !important;
font-size: 13px;
outline: none;
box-shadow: none !important;
}
.aksrt-tag-input__dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 100;
background: var(--surface-1, #fff);
border: none;
border-top: none;
border-radius: 0 0 12px 12px;
max-height: 160px;
overflow-y: auto;
box-shadow: var(--shadow-lg);
}
.aksrt-tag-input__suggestion {
padding: 6px 12px;
cursor: pointer;
font-size: 13px;
}
.aksrt-tag-input__suggestion:hover {
background: var(--color-primary-bg, #e6f7ff);
}
.aksrt-forum-compose__vote-fields[hidden] {
display: none;
}
.aksrt-forum-compose__vote {
margin-bottom: 20px;
padding: 18px;
border: none;
border-radius: var(--radius-2xl, 16px);
background: var(--surface-1, #f8f9fa);
box-shadow: var(--shadow-sm);
}
.aksrt-forum-compose__vote-head {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 12px;
}
.aksrt-forum-compose__vote-head p {
margin: 0;
color: var(--color-ink-3, #8a909c);
font-size: 13px;
}
.aksrt-forum-compose__feedback,
.aksrt-forum-board__application-feedback,
.aksrt-forum-topic__vote-feedback,
.aksrt-forum-topic__moderation-feedback {
font-size: 13px;
color: var(--color-ink-3, #8a909c);
}
.aksrt-forum-compose__feedback.is-success,
.aksrt-forum-board__application-feedback.is-success,
.aksrt-forum-topic__vote-feedback.is-success,
.aksrt-forum-topic__moderation-feedback.is-success {
color: var(--color-success, #389e0d);
}
.aksrt-forum-compose__feedback.is-error,
.aksrt-forum-board__application-feedback.is-error,
.aksrt-forum-topic__vote-feedback.is-error,
.aksrt-forum-topic__moderation-feedback.is-error {
color: var(--color-error, #cf1322);
}
.aksrt-forum-compose__feedback.is-loading,
.aksrt-forum-board__application-feedback.is-loading,
.aksrt-forum-topic__vote-feedback.is-loading,
.aksrt-forum-topic__moderation-feedback.is-loading {
color: var(--color-primary);
}
.aksrt-forum-compose__actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
}
.aksrt-forum-compose__submit {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 10px 24px;
border: none;
border-radius: 8px;
background: var(--color-primary);
color: #fff;
cursor: pointer;
font-size: 15px;
font-weight: 500;
}
.aksrt-forum-compose__submit:hover {
opacity: 0.85;
}
.aksrt-forum-compose__submit:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.aksrt-forum-compose__draft {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 10px 24px;
border: 1px solid var(--color-line, #d9d9d9);
border-radius: 8px;
background: var(--surface-1, #fff);
color: var(--color-ink-3, #8a909c);
cursor: pointer;
font-size: 15px;
}
.aksrt-forum-compose__draft:hover {
border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb));
color: var(--color-primary);
background: var(--color-primary-softer, color-mix(in srgb, var(--color-primary) 6%, transparent));
}
/* ── topic cover (§7.3) ── */
.aksrt-forum-topic__cover {
margin-bottom: 20px;
border-radius: 12px;
overflow: hidden;
background: var(--surface-2, #f5f7fa);
}
.aksrt-forum-topic__cover-img {
width: 100%;
display: block;
object-fit: cover;
}
.aksrt-forum-topic__cover-slides {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
}
.aksrt-forum-topic__cover-slides img {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
}
.aksrt-forum-topic__cover-player {
position: relative;
cursor: pointer;
}
.aksrt-forum-topic__cover-poster {
width: 100%;
display: block;
object-fit: cover;
}
.aksrt-forum-topic__cover-play-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: none;
background: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 48px;
border-radius: 50%;
width: 72px;
height: 72px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.15s;
}
.aksrt-forum-topic__cover-play-btn:hover {
background: rgba(0, 0, 0, 0.7);
}
/* ── topic list cover thumbnails (§7.3) ── */
.aksrt-forum-topic-item__cover {
flex-shrink: 0;
width: 120px;
height: 80px;
border-radius: 8px;
overflow: hidden;
background: var(--surface-2, #f5f7fa);
}
.aksrt-forum-topic-item__cover img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.aksrt-forum-topic-item__cover-video,
.aksrt-forum-topic-item__cover-slide {
position: relative;
width: 100%;
height: 100%;
}
.aksrt-forum-topic-item__cover-video img,
.aksrt-forum-topic-item__cover-slide img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.aksrt-forum-topic-item__cover-video i {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
color: #fff;
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}
.aksrt-forum-topic-item__cover-count {
position: absolute;
bottom: 4px;
right: 4px;
padding: 1px 6px;
border-radius: 3px;
background: rgba(0, 0, 0, 0.55);
color: #fff;
font-size: 11px;
}
/* ── compose cover upload (§7.3) ── */
.aksrt-forum-compose__cover-upload {
margin-bottom: 20px;
}
.aksrt-forum-compose__cover-preview {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 10px;
min-height: 60px;
}
.aksrt-forum-compose__cover-preview img {
width: 120px;
height: 80px;
object-fit: cover;
border-radius: 12px;
border: none;
box-shadow: var(--shadow-sm);
}
.aksrt-forum-compose__cover-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 10px 18px;
border: 1px dashed var(--color-line, #d9d9d9);
border-radius: 12px;
background: var(--surface-4, #fff);
color: var(--color-ink-3, #8a909c);
cursor: pointer;
font-size: 14px;
transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.aksrt-forum-compose__cover-btn:hover {
border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb));
color: var(--color-primary);
background: var(--color-primary-soft);
}
.aksrt-forum-compose__field input[type="url"] {
width: 100%;
border: 1px solid var(--color-line, #d9d9d9);
border-radius: 8px;
padding: 10px 12px;
font-size: 14px;
font-family: inherit;
}
@media (max-width: 767px) {
.aksrt-forum-board__header,
.aksrt-forum-board__actions,
.aksrt-forum-topic__vote-head,
.aksrt-forum-topic__moderation-head,
.aksrt-forum-topic__vote-actions,
.aksrt-forum-topic__moderation-actions,
.aksrt-forum-compose__header,
.aksrt-forum-compose__actions {
flex-direction: column;
align-items: stretch;
}
.aksrt-forum-topic-item {
flex-wrap: wrap;
}
.aksrt-forum-topic-item__actions {
width: 100%;
flex-direction: row;
flex-wrap: wrap;
}
.aksrt-forum-topic-item__cover {
width: 80px;
height: 56px;
}
.aksrt-forum-topic__cover-slides {
grid-template-columns: repeat(2, 1fr);
}
.aksrt-forum-topic__moderation-grid,
.aksrt-forum-compose__grid {
grid-template-columns: 1fr;
}
/* §7.4 — Stack sidebar below main on mobile */
.aksrt-forum-compose__layout {
grid-template-columns: 1fr;
}
.aksrt-forum-compose__sidebar {
order: 2;
}
.aksrt-forum-compose {
max-width: 100%;
}
}
-7
View File
@@ -1,7 +0,0 @@
@import url("extras/base.css");
@import url("extras/reply-header-tabs-filters.css");
@import url("extras/load-more.css");
@import url("extras/additional-role-badges.css");
@import url("extras/like-button.css");
@import url("extras/reply-action-buttons.css");
@import url("extras/quick-post-bar.css");
@@ -1,52 +0,0 @@
/* --- additional role badges --- */
.aksrt-forum-reply__role-badge--author {
background: #fff7e6;
color: #d46b08;
}
.aksrt-forum-reply__role-badge--super {
background: #f6ffed;
color: #389e0d;
}
.aksrt-forum-reply__role-badge--catmod {
background: #fff0f6;
color: #c41d7f;
}
/* --- hot comment (神评) badge --- */
.aksrt-forum-reply__badge {
display: inline-flex;
align-items: center;
gap: 3px;
padding: 1px 6px;
border-radius: var(--radius-pill, 999px);
font-size: 11px;
font-weight: 600;
line-height: 1.5;
}
.aksrt-forum-reply__badge--hot {
background: #fff2e8;
color: #d4380d;
}
.aksrt-forum-reply__badge--pinned {
background: #e6f7ff;
color: #096dd9;
}
.aksrt-forum-reply.is-hot {
border-left: 3px solid #fa541c;
}
.aksrt-forum-reply.is-pinned {
border-left: 3px solid color-mix(in srgb, var(--color-primary) 55%, transparent);
}
.aksrt-forum-reply.is-pinned.is-hot {
border-left: 3px solid #fa541c;
}
-621
View File
@@ -1,621 +0,0 @@
/* ============================================================
* Forum tag archive page
* ============================================================ */
.aksrt-forum-tag {
padding-top: 24px;
padding-bottom: 48px;
}
.aksrt-forum-tag__header {
margin-bottom: 24px;
padding: 20px;
background: var(--surface-1, #fff);
border-radius: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
.aksrt-forum-tag__title {
margin: 0 0 4px;
font-size: 22px;
font-weight: 700;
color: var(--color-ink);
display: flex;
align-items: center;
gap: 8px;
}
.aksrt-forum-tag__title i {
color: var(--color-primary);
}
.aksrt-forum-tag__desc {
margin: 0 0 8px;
font-size: 14px;
color: var(--color-ink-3, #8a909c);
}
.aksrt-forum-tag__count {
margin: 0;
font-size: 13px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum-tag__list {
display: flex;
flex-direction: column;
gap: 1px;
}
.aksrt-forum-topic-card {
display: flex;
padding: 16px;
background: var(--surface-1, #fff);
transition: background 0.15s;
}
.aksrt-forum-topic-card:first-child {
border-radius: 8px 8px 0 0;
}
.aksrt-forum-topic-card:last-child {
border-radius: 0 0 8px 8px;
}
.aksrt-forum-topic-card:hover {
background: var(--surface-1, #f8f9fa);
}
.aksrt-forum-topic-card__body {
flex: 1;
min-width: 0;
}
.aksrt-forum-topic-card__title {
margin: 0 0 6px;
font-size: 16px;
font-weight: 500;
}
.aksrt-forum-topic-card__title a {
color: var(--color-ink);
text-decoration: none;
}
.aksrt-forum-topic-card__title a:hover {
color: var(--color-primary);
}
.aksrt-forum-topic-card__excerpt {
margin: 0 0 8px;
font-size: 13px;
color: var(--color-ink-3, #8a909c);
line-height: 1.5;
}
.aksrt-forum-topic-card__meta {
display: flex;
gap: 16px;
font-size: 12px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum-topic-card__meta i {
margin-right: 3px;
}
.aksrt-forum-tag__pagination {
display: flex;
justify-content: center;
padding: 20px 0;
}
.aksrt-forum-tag__empty {
text-align: center;
padding: 48px 24px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum-tag__empty i {
font-size: 40px;
margin-bottom: 12px;
display: block;
}
/* ============================================================
* Forum widgets
* ============================================================ */
.aksrt-forum-widget__list {
margin: 0;
padding: 0;
list-style: none;
}
.aksrt-forum-widget__item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 0;
border-bottom: 1px solid var(--color-line-soft, #f0f1f3);
}
.aksrt-forum-widget__item:last-child {
border-bottom: none;
}
.aksrt-forum-widget__rank {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 6px;
background: var(--surface-2, #f4f5f7);
color: var(--color-ink-3, #8a909c);
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
}
.aksrt-forum-widget__item:nth-child(1) .aksrt-forum-widget__rank {
background: var(--color-error, #ef4444);
color: #fff;
}
.aksrt-forum-widget__item:nth-child(2) .aksrt-forum-widget__rank {
background: var(--color-warning, #f59e0b);
color: #fff;
}
.aksrt-forum-widget__item:nth-child(3) .aksrt-forum-widget__rank {
background: var(--color-info, #3b82f6);
color: #fff;
}
.aksrt-forum-widget__link {
flex: 1;
min-width: 0;
color: var(--color-ink);
text-decoration: none;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.aksrt-forum-widget__link:hover {
color: var(--color-primary);
}
.aksrt-forum-widget__views,
.aksrt-forum-widget__author {
font-size: 12px;
color: var(--color-ink-4, #8a909c);
white-space: nowrap;
}
.aksrt-forum-widget__boards {
margin: 0;
padding: 0;
list-style: none;
}
.aksrt-forum-widget__board {
padding: 8px 0;
border-bottom: 1px solid var(--color-line-soft, #f0f1f3);
}
.aksrt-forum-widget__board:last-child {
border-bottom: none;
}
.aksrt-forum-widget__board a {
display: flex;
align-items: center;
justify-content: space-between;
color: var(--color-ink);
text-decoration: none;
font-size: 14px;
}
.aksrt-forum-widget__board a:hover {
color: var(--color-primary);
}
.aksrt-forum-widget__board a i {
margin-right: 6px;
color: var(--color-primary);
}
.aksrt-forum-widget__count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 24px;
height: 20px;
padding: 0 6px;
background: var(--surface-2, #f4f5f7);
border-radius: var(--radius-pill, 999px);
color: var(--color-ink-3, #8a909c);
font-size: 11px;
font-weight: 600;
}
.aksrt-forum-widget__empty {
text-align: center;
padding: 16px;
color: var(--color-ink-4, #8a909c);
font-size: 13px;
}
/* ── bbs_type / question badges (§7.3) ── */
.aksrt-forum-topic__badge--question {
background: #e6f7ff;
color: var(--color-primary);
}
.aksrt-forum-topic__badge--solved {
background: #f6ffed;
color: #52c41a;
}
.aksrt-forum-topic-item__badge--question {
background: #e6f7ff;
color: var(--color-primary);
}
.aksrt-forum-topic-item__badge--solved {
background: #f6ffed;
color: #52c41a;
}
/* ── favorite button (§7.3) ── */
.js-aksrt-topic-favorite {
background: none;
border: none;
cursor: pointer;
transition: color .15s;
}
.js-aksrt-topic-favorite[data-favorited="1"] {
color: var(--color-warning, #faad14);
}
.js-aksrt-topic-favorite:disabled {
opacity: .5;
cursor: not-allowed;
}
/* ── score actions (§7.3) ── */
.aksrt-forum-topic__score-actions {
display: flex;
align-items: center;
gap: 10px;
margin: 16px 0;
padding: 12px 16px;
background: var(--surface-1, #fff);
border: 1px solid var(--border-2, #e8e8e8);
border-radius: var(--radius-m, 8px);
}
.aksrt-forum-topic__score-total {
font-size: 14px;
font-weight: 600;
color: var(--color-ink, #333);
}
.aksrt-forum-topic__score-total i {
color: var(--color-warning, #faad14);
margin-right: 4px;
}
.aksrt-forum-topic__score-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: 1px solid var(--border-2, #e0e0e0);
border-radius: var(--radius-s, 4px);
background: var(--surface-1, #fff);
cursor: pointer;
font-size: 12px;
transition: all .15s;
}
.aksrt-forum-topic__score-btn:hover:not(:disabled) {
border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb));
color: var(--color-primary);
}
.aksrt-forum-topic__score-btn--up:hover:not(:disabled) {
color: var(--color-success, #52c41a);
border-color: var(--color-success, #52c41a);
}
.aksrt-forum-topic__score-btn--down:hover:not(:disabled) {
color: var(--color-error, #f5222d);
border-color: var(--color-error, #f5222d);
}
.aksrt-forum-topic__score-btn:disabled {
opacity: .4;
cursor: not-allowed;
}
.aksrt-forum-topic__score-badge {
color: var(--color-warning, #faad14);
font-weight: 600;
}
.aksrt-forum-topic__score-feedback {
font-size: 13px;
color: var(--color-ink-4, #999);
}
/* ── vote enhancements (§7.3) ── */
.aksrt-forum-topic__vote--pk .aksrt-forum-topic__vote-options {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.aksrt-forum-topic__vote--expired {
opacity: .7;
}
.aksrt-forum-topic__vote-type-badge {
display: inline-block;
padding: 1px 6px;
background: var(--color-primary);
color: #fff;
border-radius: var(--radius-pill, 999px);
font-size: 11px;
font-weight: 700;
margin-left: 6px;
}
.aksrt-forum-topic__vote-expired-badge {
display: inline-block;
padding: 1px 6px;
background: var(--color-error, #f5222d);
color: #fff;
border-radius: var(--radius-pill, 999px);
font-size: 11px;
font-weight: 600;
margin-left: 6px;
}
.aksrt-forum-topic__vote-expiry-info {
color: var(--color-ink-4, #999);
font-size: 12px;
margin-left: 6px;
}
/* ── topic_cat chip (§7.3) ── */
.aksrt-forum-topic__chip--topic-cat {
background: var(--color-primary-bg, #e6f7ff);
color: var(--color-primary);
font-weight: 600;
}
/* ── report modal (§7.5) ── */
.aksrt-forum-topic__header-top {
display: flex;
flex-direction: column;
gap: 12px;
}
.aksrt-forum-report-modal {
position: fixed;
inset: 0;
z-index: 9999;
}
.aksrt-forum-report-modal__backdrop {
position: absolute;
inset: 0;
background: rgba(15, 23, 42, 0.55);
}
.aksrt-forum-report-modal__dialog {
position: relative;
max-width: 520px;
margin: 8vh auto 0;
padding: 20px;
border-radius: 14px;
background: var(--surface-1, #fff);
box-shadow: 0 18px 60px rgba(15, 23, 42, 0.2);
z-index: 1;
}
.aksrt-forum-report-modal__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
}
.aksrt-forum-report-modal__head h2 {
margin: 0;
font-size: 18px;
}
.aksrt-forum-report-modal__close {
border: none;
background: transparent;
font-size: 24px;
line-height: 1;
cursor: pointer;
color: var(--color-ink-3, #64748b);
}
.aksrt-forum-report-modal__target {
margin: 0 0 14px;
color: var(--color-ink-3, #64748b);
font-size: 13px;
}
.aksrt-forum-report-modal__actions {
display: flex;
align-items: center;
gap: 12px;
}
.aksrt-forum-report-modal__feedback {
font-size: 13px;
}
.is-report-modal-open {
overflow: hidden;
}
.aksrt-forum-reply__report-btn {
display: inline-flex;
align-items: center;
gap: 4px;
border: none;
background: transparent;
color: var(--color-ink-4, #94a3b8);
cursor: pointer;
padding: 0;
font-size: 12px;
}
.aksrt-forum-reply__report-btn:hover {
color: var(--color-error, #ef4444);
}
/* ── moderation enhancements (§7.5) ── */
.aksrt-forum-topic__moderation-note {
margin-bottom: 16px;
padding: 12px 14px;
border-radius: 10px;
background: var(--surface-2, #f8fafc);
color: var(--color-ink-3, #64748b);
font-size: 13px;
}
.aksrt-forum-topic__moderation-note strong {
color: var(--color-ink, #334155);
margin-right: 6px;
}
.aksrt-forum-topic__field small {
display: block;
margin-top: 6px;
color: var(--color-ink-4, #94a3b8);
font-size: 12px;
}
/* ── paywall / credits unlock (§7.3) ── */
.aksrt-forum-topic__pay-price {
display: inline-block;
margin-top: 12px;
padding: 6px 16px;
background: #fff7e6;
color: #d46b08;
border-radius: 8px;
font-size: 15px;
font-weight: 600;
}
.aksrt-forum-topic__unlock-feedback {
display: block;
margin-top: 10px;
font-size: 13px;
}
.aksrt-forum-compose__visibility-extra select[multiple] {
min-height: 90px;
}
.aksrt-forum-compose__visibility-extra input[type="number"] {
width: 100%;
border: 1px solid var(--color-line, #d9d9d9);
border-radius: 8px;
padding: 10px 12px;
font-size: 14px;
font-family: inherit;
}
/* ── adopted answer (§7.3) ── */
.aksrt-forum-reply.is-adopted {
border: 2px solid var(--color-success, #52c41a);
border-radius: var(--radius-m, 8px);
padding: 12px;
background: #f6ffed;
}
.aksrt-forum-reply__adopted-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
background: var(--color-success, #52c41a);
color: #fff;
border-radius: var(--radius-pill, 999px);
font-size: 11px;
font-weight: 600;
}
.aksrt-forum-reply__adopt-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border: 1px solid var(--color-success, #52c41a);
border-radius: var(--radius-s, 4px);
background: transparent;
color: var(--color-success, #52c41a);
font-size: 12px;
cursor: pointer;
transition: all .15s;
}
.aksrt-forum-reply__adopt-btn:hover:not(:disabled) {
background: var(--color-success, #52c41a);
color: #fff;
}
.aksrt-forum-reply__adopt-btn:disabled,
.aksrt-forum-reply__adopt-btn.is-adopted {
opacity: .6;
cursor: not-allowed;
}
/* ── moderator role badge (§7.5) ── */
.aksrt-forum-reply__role-badge {
display: inline-flex;
align-items: center;
padding: 1px 6px;
border-radius: var(--radius-pill, 999px);
font-size: 11px;
font-weight: 600;
line-height: 1.5;
}
.aksrt-forum-reply__role-badge--admin {
background: #fff1f0;
color: #cf1322;
}
.aksrt-forum-reply__role-badge--mod {
background: #e6f7ff;
color: #096dd9;
}
/* ── §7.6 forum comment engine ── */
@@ -1,62 +0,0 @@
/* --- like button --- */
.aksrt-forum-reply__like-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border: none;
background: none;
color: var(--c-text-3, #999);
font-size: 13px;
cursor: pointer;
transition: color .2s;
}
.aksrt-forum-reply__like-btn:hover {
color: var(--color-primary);
}
.aksrt-forum-reply__like-btn.is-active {
color: var(--color-primary);
}
.aksrt-forum-reply__like-count {
font-size: 12px;
min-width: 10px;
}
/* --- nested reply (楼中楼) --- */
.aksrt-forum-reply--nested {
padding-left: 0;
margin-left: 0;
}
.aksrt-forum-reply__children {
margin-top: 8px;
padding: 8px 0 0 12px;
border-left: 2px solid var(--c-border, #f0f0f0);
}
.aksrt-forum-reply__children .aksrt-forum-reply {
padding: 8px 0;
margin-bottom: 0;
border-bottom: 1px solid var(--c-border-light, #f5f5f5);
}
.aksrt-forum-reply__children .aksrt-forum-reply:last-child {
border-bottom: none;
}
.aksrt-forum-reply__avatar--sm img {
width: 28px;
height: 28px;
border-radius: 50%;
}
.aksrt-forum-reply__reply-to {
font-size: 12px;
color: var(--c-text-3, #999);
}
@@ -1,23 +0,0 @@
/* --- load more --- */
.aksrt-forum-replies__load-more {
text-align: center;
padding: 16px 0;
}
.aksrt-forum-replies__more-btn {
padding: 8px 24px;
border: 1px solid var(--c-border, #d9d9d9);
border-radius: var(--radius, 6px);
background: none;
color: var(--c-text-2, #666);
font-size: 14px;
cursor: pointer;
transition: all .2s;
}
.aksrt-forum-replies__more-btn:hover {
border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb));
color: var(--color-primary);
}
@@ -1,31 +0,0 @@
/* Quick post bar */
.aksrt-forum-quick-post {
margin-bottom: 16px;
}
.aksrt-forum-quick-post__input {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
border: 1px solid var(--c-border, #e8e8e8);
border-radius: var(--radius, 6px);
background: var(--c-fill-1, #fafafa);
color: var(--c-text-3, #999);
font-size: 14px;
text-decoration: none;
transition: border-color .2s, box-shadow .2s;
}
.aksrt-forum-quick-post__input:hover {
border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb));
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent);
}
.aksrt-forum-quick-post__input img {
width: 32px;
height: 32px;
border-radius: 50%;
}
@@ -1,110 +0,0 @@
/* --- reply action buttons --- */
.aksrt-forum-reply__reply-btn,
.aksrt-forum-reply__top-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border: none;
background: none;
color: var(--c-text-3, #999);
font-size: 13px;
cursor: pointer;
transition: color .2s;
}
.aksrt-forum-reply__reply-btn:hover,
.aksrt-forum-reply__top-btn:hover {
color: var(--color-primary);
}
/* ── §7.3 Hidden content block [aksrt_hide] ── */
.aksrt-forum-hide {
margin: 16px 0;
}
.aksrt-forum-hide--locked {
padding: 16px;
border: 1px dashed var(--c-border, #d9d9d9);
border-radius: var(--radius, 6px);
background: var(--c-fill-1, #fafafa);
text-align: center;
}
.aksrt-forum-hide__message {
font-size: 13px;
color: var(--c-text-3, #999);
}
.aksrt-forum-hide__message i {
margin-right: 4px;
color: var(--c-warning, #faad14);
}
.aksrt-forum-hide--revealed {
padding: 12px 16px;
border-left: 3px solid var(--c-success, #52c41a);
background: var(--c-fill-1, #f6ffed);
border-radius: 0 var(--radius, 6px) var(--radius, 6px) 0;
}
/* ── §7.10 Forum home tab engine ── */
.aksrt-forum-home-tabs {
display: flex;
gap: 4px;
padding: 0 0 12px;
margin-bottom: 16px;
border-bottom: 1px solid var(--c-border, #e8e8e8);
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.aksrt-forum-home-tabs__btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 6px 16px;
border: 1px solid var(--c-border, #d9d9d9);
border-radius: var(--radius-pill, 999px);
background: none;
color: var(--c-text-3, #999);
font-size: 14px;
font-weight: 500;
cursor: pointer;
white-space: nowrap;
transition: all .2s;
}
.aksrt-forum-home-tabs__btn:hover {
color: var(--c-text-1, #333);
border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb));
}
.aksrt-forum-home-tabs__btn.is-active {
background: var(--color-primary);
color: #fff;
border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb));
}
.aksrt-forum-home-tabs__badge {
display: inline-block;
min-width: 18px;
height: 18px;
padding: 0 5px;
border-radius: 9px;
background: #ff4d4f;
color: #fff;
font-size: 11px;
font-weight: 700;
line-height: 18px;
text-align: center;
}
.aksrt-forum-home__load-more {
text-align: center;
padding: 16px 0;
}
@@ -1,69 +0,0 @@
/* --- reply header tabs & filters --- */
.aksrt-forum-replies__header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--c-border, #e8e8e8);
}
.aksrt-forum-replies__tabs {
display: flex;
align-items: center;
gap: 4px;
}
.aksrt-forum-replies__tab {
padding: 4px 12px;
border: 1px solid var(--c-border, #d9d9d9);
border-radius: var(--radius-pill, 999px);
background: none;
color: var(--c-text-3, #999);
font-size: 13px;
cursor: pointer;
transition: all .2s;
}
.aksrt-forum-replies__tab:hover {
color: var(--c-text-1, #333);
border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb));
}
.aksrt-forum-replies__tab.is-active {
background: var(--color-primary);
color: #fff;
border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb));
}
.aksrt-forum-replies__filter {
display: flex;
align-items: center;
gap: 4px;
font-size: 13px;
color: var(--c-text-3, #999);
cursor: pointer;
margin-left: 8px;
}
.aksrt-forum-replies__filter input[type="checkbox"] {
width: 14px;
height: 14px;
}
.aksrt-forum-replies__empty {
padding: 40px 0;
text-align: center;
color: var(--c-text-3, #bbb);
font-size: 14px;
}
.aksrt-forum-replies__empty i {
font-size: 24px;
display: block;
margin-bottom: 8px;
}
-402
View File
@@ -1,402 +0,0 @@
/* ============================================================
* Topic detail page
* ============================================================ */
.aksrt-forum-topic {
display: grid;
grid-template-columns: minmax(0, 1fr) 280px;
gap: 24px;
padding-top: 24px;
padding-bottom: 48px;
}
.aksrt-forum-topic__shell {
min-width: 0;
}
.aksrt-forum-topic__sidebar {
display: flex;
flex-direction: column;
gap: 16px;
}
.aksrt-forum-topic__sidebar-card {
padding: 18px;
background: var(--surface-1, #fff);
border: none;
border-radius: var(--main-radius, var(--radius, 8px));
box-shadow: var(--shadow-sm);
}
.aksrt-forum-topic__sidebar-card h2 {
margin: 0 0 12px;
font-size: 15px;
}
.aksrt-forum-topic__sidebar-link {
display: flex;
flex-direction: column;
gap: 6px;
color: inherit;
text-decoration: none;
}
.aksrt-forum-topic__sidebar-link span {
font-size: 13px;
color: var(--color-ink-3, #8a909c);
}
.aksrt-forum-topic__sidebar-author {
display: flex;
align-items: center;
gap: 12px;
}
.aksrt-forum-topic__sidebar-author img {
border-radius: 50%;
}
.aksrt-forum-topic__sidebar-author strong,
.aksrt-forum-topic__board-link {
color: var(--color-ink);
}
.aksrt-forum-topic__board-link-wrap {
display: inline-flex;
align-items: center;
gap: 6px;
}
.aksrt-forum-topic__breadcrumb {
font-size: 13px;
color: var(--color-ink-4, #8a909c);
margin-bottom: 16px;
}
.aksrt-forum-topic__breadcrumb a {
color: var(--color-primary);
text-decoration: none;
}
.aksrt-forum-topic__sep {
margin: 0 6px;
}
.aksrt-forum-topic__header {
margin-bottom: 20px;
}
.aksrt-forum-topic__title {
margin: 0 0 8px;
font-size: 22px;
font-weight: 700;
color: var(--color-ink);
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.aksrt-forum-topic__badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.aksrt-forum-topic__badge--sticky {
background: var(--color-primary);
color: #fff;
}
.aksrt-forum-topic__badge--sticky-super {
background: var(--focus-color, #fd53a1);
color: #fff;
}
.aksrt-forum-topic__badge--sticky-global {
background: #e53e3e;
color: #fff;
}
.aksrt-forum-topic__badge--featured {
background: var(--color-success, #52c41a);
color: #fff;
}
.aksrt-forum-topic__badge--locked {
background: #fff7e6;
color: #d46b08;
}
.aksrt-forum-topic__badge--visibility {
background: var(--surface-2, #f5f7fa);
color: var(--color-ink-3, #8a909c);
}
.aksrt-forum-topic__meta {
display: flex;
flex-wrap: wrap;
gap: 16px;
font-size: 13px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum-topic__author {
display: flex;
align-items: center;
gap: 6px;
}
.aksrt-forum-topic__author img {
border-radius: 50%;
}
.aksrt-forum-topic__quick-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 14px;
}
.aksrt-forum-topic__quick-action,
.aksrt-forum-topic__primary-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 9px 16px;
border: none;
border-radius: 999px;
background: var(--surface-1, #fff);
color: var(--color-ink);
text-decoration: none;
cursor: pointer;
}
.aksrt-forum-topic__primary-btn {
background: var(--color-primary);
color: #fff;
}
.aksrt-forum-topic__taxonomy {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 20px;
}
.aksrt-forum-topic__chip {
display: inline-flex;
align-items: center;
padding: 6px 10px;
border-radius: 999px;
background: var(--surface-2, #f5f7fa);
color: var(--color-ink-3, #8a909c);
text-decoration: none;
font-size: 13px;
}
.aksrt-forum-topic__chip--special {
background: #fff1f0;
color: #cf1322;
}
.aksrt-forum-topic__content {
padding: 22px;
background: var(--surface-1, #fff);
border: none;
border-radius: var(--radius-2xl, 16px);
box-shadow: var(--shadow-sm);
margin-bottom: 24px;
line-height: 1.8;
font-size: 15px;
}
.aksrt-forum-topic__content img {
max-width: 100%;
border-radius: 8px;
}
.aksrt-forum-topic__vote,
.aksrt-forum-topic__moderation {
margin-bottom: 24px;
padding: 20px;
background: var(--surface-1, #fff);
border: none;
border-radius: var(--radius-2xl, 16px);
box-shadow: var(--shadow-sm);
}
.aksrt-forum-topic__vote-head,
.aksrt-forum-topic__moderation-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
}
.aksrt-forum-topic__vote-head h2,
.aksrt-forum-topic__moderation-head h2 {
margin: 0;
font-size: 18px;
}
.aksrt-forum-topic__vote-head span,
.aksrt-forum-topic__moderation-head p {
color: var(--color-ink-3, #8a909c);
font-size: 13px;
}
.aksrt-forum-topic__vote-options {
display: grid;
gap: 10px;
}
.aksrt-forum-topic__vote-option {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 10px;
padding: 12px 14px;
border-radius: 10px;
background: var(--surface-1, #f8f9fa);
}
.aksrt-forum-topic__vote-option.is-selected {
outline: none;
border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb));
background: var(--color-primary-soft, color-mix(in srgb, var(--color-primary) 12%, transparent));
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent);
}
.aksrt-forum-topic__vote-count {
font-size: 12px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum-topic__vote-actions,
.aksrt-forum-topic__moderation-actions {
display: flex;
align-items: center;
gap: 12px;
margin-top: 16px;
}
.aksrt-forum-topic__moderation-grid,
.aksrt-forum-compose__grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.aksrt-forum-topic__field select,
.aksrt-forum-topic__field textarea,
.aksrt-forum-compose__field input[type="text"],
.aksrt-forum-compose__field textarea,
.aksrt-forum-compose__field select {
width: 100%;
}
.aksrt-forum-topic__field label {
display: block;
margin-bottom: 6px;
font-size: 14px;
font-weight: 500;
}
.aksrt-forum-topic__field select,
.aksrt-forum-topic__field textarea {
border: 1px solid var(--color-line, #d9d9d9);
border-radius: 8px;
padding: 10px 12px;
font-family: inherit;
font-size: 14px;
}
.aksrt-forum-topic__denied {
padding: 56px 24px;
text-align: center;
background: var(--surface-1, #fff);
border-radius: 12px;
}
.aksrt-forum-topic__denied i {
font-size: 42px;
margin-bottom: 14px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-forum-topic__replies {
margin-bottom: 24px;
}
.aksrt-forum-topic__replies-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
color: var(--color-ink);
}
.aksrt-forum-topic__reply-form {
padding: 20px;
background: var(--surface-1, #fff);
border: none;
border-radius: var(--radius-2xl, 16px);
box-shadow: var(--shadow-sm);
}
.aksrt-forum-topic__reply-form h2,
.aksrt-forum-topic__reply-form h3 {
margin: 0 0 12px;
font-size: 15px;
color: var(--color-ink);
}
.aksrt-forum-topic__reply-field textarea {
width: 100%;
border: 1px solid var(--color-line, #d9d9d9);
border-radius: 8px;
padding: 12px;
font-size: 14px;
resize: vertical;
font-family: inherit;
}
.aksrt-forum-topic__reply-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 20px;
border: none;
border-radius: 8px;
background: var(--color-primary);
color: #fff;
cursor: pointer;
font-size: 14px;
}
.aksrt-forum-topic__reply-btn:hover {
opacity: 0.85;
}
.aksrt-forum-topic__locked-notice,
.aksrt-forum-topic__login-notice {
text-align: center;
padding: 20px;
color: var(--color-ink-3, #8a909c);
font-size: 14px;
}
@media (max-width: 960px) {
.aksrt-forum-topic {
grid-template-columns: minmax(0, 1fr);
}
}
.aksrt-forum-topic__login-notice a {
color: var(--color-primary);
}
-58
View File
@@ -1,58 +0,0 @@
/* Board cards */
.aksrt-forum-boards__grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 12px;
margin-bottom: 32px;
}
.aksrt-forum-board-card {
display: flex;
gap: 12px;
padding: 16px;
background: var(--surface-1, #fff);
border: 1px solid var(--color-line, #e8e8e8);
border-radius: 8px;
transition: box-shadow 0.2s;
}
.aksrt-forum-board-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
}
.aksrt-forum-board-card__icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
background: var(--color-primary-soft, #e6f4ff);
color: var(--color-primary);
font-size: 20px;
flex-shrink: 0;
}
.aksrt-forum-board-card__title {
margin: 0 0 4px;
font-size: 14px;
font-weight: 600;
color: var(--color-ink);
}
.aksrt-forum-board-card__desc {
margin: 0 0 8px;
font-size: 12px;
color: var(--color-ink-3, #999);
line-height: 1.5;
}
.aksrt-forum-board-card__meta {
font-size: 11px;
color: var(--color-ink-3, #999);
}
.aksrt-forum-board-card__meta i {
margin-right: 3px;
}
-56
View File
@@ -1,56 +0,0 @@
/* Topic rows */
.aksrt-forum-topics__list {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 32px;
}
.aksrt-forum-topic-row {
display: flex;
gap: 12px;
padding: 12px;
background: var(--surface-1, #fff);
border: 1px solid var(--color-line, #e8e8e8);
border-radius: 8px;
transition: border-color 0.2s;
}
.aksrt-forum-topic-row:hover {
border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb));
box-shadow: var(--shadow-sm);
}
.aksrt-forum-topic-row__avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.aksrt-forum-topic-row__title {
margin: 0 0 4px;
font-size: 14px;
font-weight: 600;
}
.aksrt-forum-topic-row__title a {
color: var(--color-ink);
text-decoration: none;
}
.aksrt-forum-topic-row__title a:hover {
color: var(--color-primary);
}
.aksrt-forum-topic-row__meta {
display: flex;
gap: 12px;
font-size: 11px;
color: var(--color-ink-3, #999);
}
.aksrt-forum-topic-row__meta i {
margin-right: 3px;
}
+4 -41
View File
@@ -367,8 +367,6 @@
}
function payApiBase(apiKind) {
if (apiKind === 'topic') return '/pay/topic/';
if (apiKind === 'plate_follow') return '/pay/plate-follow/';
return '/pay/content/';
}
@@ -448,34 +446,15 @@
window.location.reload();
return;
}
// Content free: reload. Topic/plate free: auto free-order POST (VIP free / free-after-coupon).
if (preview.is_free && preview.pay_type !== 'topic' && preview.pay_type !== 'plate_follow') {
// Content free: reload.
if (preview.is_free) {
window.location.reload();
return;
}
const isPoints = preview.pay_modo === 'points';
const priceType = (preview._price_type || preview.price_type || '');
const apiKind = preview._api_kind
|| (preview.pay_type === 'topic' ? 'topic' : (preview.pay_type === 'plate_follow' ? 'plate_follow' : 'content'));
// Auto-submit free topic/plate orders so user doesn't click through empty cash UI.
if (preview.is_free && (apiKind === 'topic' || apiKind === 'plate_follow') && !isPoints) {
open(
'<h3 class="aksrt-dl-modal__title">' + tt('confirm_buy', '确认购买') + '</h3>' +
'<p class="aksrt-dl-modal__msg">' + tt('processing', '处理中…') + '</p>',
returnFocusEl
);
const freeBtn = document.createElement('button');
freeBtn.type = 'button';
freeBtn.style.display = 'none';
freeBtn.setAttribute('data-price-type', priceType || '');
document.body.appendChild(freeBtn);
const freeCode = String(preview.coupon_code || preview._coupon_code || '');
submitPurchase(preview.post_id, 'cash', 'balance', freeBtn, returnFocusEl, freeCode, apiKind);
try { freeBtn.remove(); } catch (e0) { /* ignore */ }
return;
}
const apiKind = preview._api_kind || 'content';
let amountLabel = escapeHtml(preview.amount_label || '');
let baseLabel = escapeHtml(preview.base_label || '');
@@ -697,22 +676,6 @@
return;
}
const topicBuyBtn = e.target.closest('[data-aksrt-pay-topic]');
if (topicBuyBtn) {
e.preventDefault();
const topicId = parseInt(topicBuyBtn.getAttribute('data-aksrt-pay-topic') || '0', 10);
if (topicId) openCashier(topicId, topicBuyBtn, '', '', 'topic');
return;
}
const plateFollowBuyBtn = e.target.closest('[data-aksrt-pay-plate-follow]');
if (plateFollowBuyBtn) {
e.preventDefault();
const plateId = parseInt(plateFollowBuyBtn.getAttribute('data-aksrt-pay-plate-follow') || '0', 10);
if (plateId) openCashier(plateId, plateFollowBuyBtn, '', '', 'plate_follow');
return;
}
const buyBtn = e.target.closest('[data-action="aksrt-need-purchase"], [data-aksrt-pay-content]');
if (buyBtn) {
e.preventDefault();
@@ -743,7 +706,7 @@
requestDownload(btn);
});
// Bridge for forum-governance plate money cashier.
// Public cashier bridge (content pay).
window.aksrtOpenCashier = function (postId, returnFocusEl, priceType, couponCode, apiKind) {
openCashier(postId, returnFocusEl || null, priceType || '', couponCode || '', apiKind || 'content');
};
+1 -1
View File
@@ -248,7 +248,7 @@
closestPager(link);
if (!container) {
// Fall back: parent stream list.
container = link.closest(".aksrt-product-grid, .aksrt-forum-topics__list, [data-aksrt-ajax-list]");
container = link.closest("[data-aksrt-ajax-list]");
}
if (!container) return Promise.resolve();
-47
View File
@@ -1,47 +0,0 @@
/**
* AKSRT Forum Edit — 帖子编辑页交互
*
* 处理论坛帖子编辑页的表单增强和标签输入。
*
* @package AKSRT
*/
(function () {
'use strict';
var form = document.querySelector('.aksrt-forum-edit__form');
if (!form) return;
// 标签输入增强:逗号分隔转标签 chips
var tagInput = form.querySelector('#topic_tags');
if (tagInput) {
tagInput.addEventListener('keydown', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
}
});
}
// 表单提交确认
form.addEventListener('submit', function (e) {
var title = form.querySelector('#topic_title');
var content = form.querySelector('#topic_content');
if (title && title.value.trim() === '') {
e.preventDefault();
alert('标题不能为空');
title.focus();
return;
}
// TinyMCE 检查
if (typeof tinyMCE !== 'undefined' && tinyMCE.get('topic_content')) {
var editorContent = tinyMCE.get('topic_content').getContent().trim();
if (editorContent === '') {
e.preventDefault();
alert('内容不能为空');
return;
}
}
});
})();
File diff suppressed because it is too large Load Diff
-149
View File
@@ -156,73 +156,6 @@
});
}
// Forum multi-tab topics.
function initForumTabTopics() {
document.querySelectorAll('.aksrt-ftab[data-aksrt-ftab]').forEach(function (root) {
if (root.dataset.aksrtFtabBound === '1') return;
root.dataset.aksrtFtabBound = '1';
var rest = root.getAttribute('data-rest') || '';
var count = root.getAttribute('data-count') || '6';
var style = root.getAttribute('data-style') || 'mini';
var plate = root.getAttribute('data-plate') || '0';
var btns = [].slice.call(root.querySelectorAll('.aksrt-ftab__btn'));
var panels = [].slice.call(root.querySelectorAll('.aksrt-ftab__panel'));
function load(panel, orderby) {
if (!panel || panel.getAttribute('data-loaded') === '1' || !rest) return;
var qs = new URLSearchParams({
page: '1',
count: count,
orderby: orderby || 'date',
style: style,
plate: plate,
period: 'all',
paginate: 'none',
});
var url = rest + (rest.indexOf('?') > -1 ? '&' : '?') + qs.toString();
panel.classList.add('is-ajax-loading');
fetch(url, { credentials: 'same-origin', headers: restHeaders() })
.then(function (r) { return r.json(); })
.then(function (j) {
var d = (j && j.data) || j || {};
var html = d.html || '';
panel.innerHTML = html || '<div class="ajax-item aksrt-ftab__empty muted-2-color">暂无内容</div>';
panel.setAttribute('data-loaded', '1');
panel.classList.remove('is-ajax-loading');
})
.catch(function () {
panel.classList.remove('is-ajax-loading');
});
}
function go(i) {
btns.forEach(function (b, idx) {
var on = idx === i;
b.classList.toggle('is-active', on);
b.setAttribute('aria-selected', on ? 'true' : 'false');
});
panels.forEach(function (p, idx) {
var on = idx === i;
p.classList.toggle('is-active', on);
if (on) {
p.removeAttribute('hidden');
} else {
p.setAttribute('hidden', '');
}
});
var btn = btns[i];
var panel = panels[i];
if (btn && panel) load(panel, btn.getAttribute('data-orderby') || 'date');
}
btns.forEach(function (b) {
b.addEventListener('click', function () {
go(parseInt(b.getAttribute('data-index') || '0', 10));
});
});
});
}
// Product multi-tab.
function initProductTabs() {
document.querySelectorAll('[data-aksrt-product-tab]').forEach(function (root) {
@@ -383,86 +316,6 @@
}
function initModeratorApplyModal() {
document.addEventListener('click', function (e) {
var openBtn = e.target.closest('[data-aksrt-mod-apply-open]');
if (openBtn) {
e.preventDefault();
var id = openBtn.getAttribute('data-aksrt-mod-apply-open');
var modal = id ? document.getElementById(id) : null;
if (modal) {
modal.hidden = false;
document.body.classList.add('aksrt-mod-apply-open');
}
return;
}
var closeBtn = e.target.closest('[data-aksrt-mod-apply-close]');
if (closeBtn) {
var box = closeBtn.closest('.aksrt-mod-apply-modal');
if (box) {
box.hidden = true;
document.body.classList.remove('aksrt-mod-apply-open');
}
}
});
document.querySelectorAll('.js-aksrt-moderator-apply-form').forEach(function (form) {
if (form.dataset.aksrtModApplyBound === '1') return;
form.dataset.aksrtModApplyBound = '1';
form.addEventListener('submit', function (event) {
event.preventDefault();
var boardId = parseInt(form.getAttribute('data-board-id') || '0', 10);
var reasonField = form.querySelector('[name="reason"]');
var reason = reasonField ? reasonField.value.trim() : '';
var feedback = form.querySelector('.js-aksrt-action-feedback');
var submitButton = form.querySelector('button[type="submit"]');
if (!boardId || !reason) {
if (feedback) {
feedback.textContent = '请填写完整的申请理由。';
feedback.className = 'js-aksrt-action-feedback mb10 is-error';
}
return;
}
if (submitButton) submitButton.disabled = true;
if (feedback) {
feedback.textContent = '正在提交申请…';
feedback.className = 'js-aksrt-action-feedback mb10 is-loading';
}
var siteUrl = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/+$/, '') : '';
var url = siteUrl + '/wp-json/aksrt/v1/forum/moderator/apply';
fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
'X-WP-Nonce': (window.aksrtData && aksrtData.restNonce) || '',
},
body: JSON.stringify({ board_id: boardId, reason: reason }),
})
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); })
.then(function (res) {
if (!res.ok || (res.j && res.j.success === false)) {
throw new Error((res.j && res.j.message) || '申请提交失败');
}
var msg = (res.j && (res.j.message || (res.j.data && res.j.data.message))) || '申请已提交';
if (feedback) {
feedback.textContent = msg;
feedback.className = 'js-aksrt-action-feedback mb10 is-success';
}
if (reasonField) reasonField.disabled = true;
})
.catch(function (err) {
if (feedback) {
feedback.textContent = (err && err.message) || '申请提交失败。';
feedback.className = 'js-aksrt-action-feedback mb10 is-error';
}
if (submitButton) submitButton.disabled = false;
});
});
});
}
function initFloatingQRCodes() {
var wrap = document.querySelector('.float-right');
if (!wrap) return;
@@ -508,11 +361,9 @@
function init() {
initWidgetTabs();
initCategoryTabs();
initForumTabTopics();
initProductTabs();
initMainPostsLazy();
initFeatureIntro();
initModeratorApplyModal();
initFloatingQRCodes();
}
-10
View File
@@ -396,12 +396,6 @@ if ($floating_enabled) :
<i class="fas fa-sun float-btn__icon-sun" aria-hidden="true"></i>
<i class="fas fa-moon float-btn__icon-moon" aria-hidden="true"></i>
</a>
<?php elseif ($item_type === 'shop_cart') :
$cart_html = function_exists('aksrt_get_float_shop_cart_html') ? aksrt_get_float_shop_cart_html($item) : '';
if ($cart_html !== '') {
echo $cart_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
<?php elseif ($item_type === 'pay_vip') :
$vip_html = function_exists('aksrt_get_float_pay_vip_html') ? aksrt_get_float_pay_vip_html($item) : '';
if ($vip_html !== '') {
@@ -490,10 +484,6 @@ if ($floating_enabled) :
<?php endforeach;
// After loop: inject missing shop cart once. Never auto-inject pay_vip
// (admin may already add one; previous in-loop inject created duplicates).
$float_types = array_column($floating_items, 'type');
if (!in_array('shop_cart', $float_types, true) && function_exists('aksrt_get_float_shop_cart_html')) {
echo aksrt_get_float_shop_cart_html(['icon' => 'fas fa-shopping-cart']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
?>
</div>
<?php endif; ?>
+1 -31
View File
@@ -211,12 +211,6 @@ if (class_exists('\AKSRT\Content\Download')) {
if (class_exists('\AKSRT\Content\Pay')) {
\AKSRT\Content\Pay::get_instance()->init();
}
if (class_exists('\AKSRT\Forum\TopicPay')) {
\AKSRT\Forum\TopicPay::get_instance()->init();
}
if (class_exists('\AKSRT\Forum\PlateFollowPay')) {
\AKSRT\Forum\PlateFollowPay::get_instance()->init();
}
if (class_exists('\AKSRT\Content\VipService')) {
\AKSRT\Content\VipService::get_instance()->init();
}
@@ -355,10 +349,7 @@ if (class_exists('\AKSRT\Payment\Gateway')) {
do_action('aksrt_register_payment_gateways', $gw);
}
if (class_exists('\AKSRT\Forum\Forum')) {
\AKSRT\Forum\Forum::get_instance()->init();
}
if (is_admin() && class_exists('\AKSRT\Core\ResourceMetaBox')) {
if (class_exists('\AKSRT\Core\ResourceMetaBox')) {
\AKSRT\Core\ResourceMetaBox::get_instance()->init();
}
@@ -386,27 +377,6 @@ if (is_admin() && class_exists('\AKSRT\Shop\ProductVariantsMetaBox')) {
\AKSRT\Shop\ProductVariantsMetaBox::get_instance()->init();
}
// 论坛版块版主 meta box
if (is_admin() && class_exists('\AKSRT\Forum\PlateMetaBox')) {
\AKSRT\Forum\PlateMetaBox::get_instance()->init();
}
// 论坛帖子原生列表治理列/筛选/批量
if (is_admin() && class_exists('\AKSRT\Forum\TopicAdminList')) {
\AKSRT\Forum\TopicAdminList::get_instance()->init();
}
// 论坛帖子治理 meta box
if (is_admin() && class_exists('\AKSRT\Forum\TopicMetaBox')) {
\AKSRT\Forum\TopicMetaBox::get_instance()->init();
}
// 论坛版块列表列
if (is_admin() && class_exists('\AKSRT\Forum\PlateAdminList')) {
\AKSRT\Forum\PlateAdminList::get_instance()->init();
}
// 商品评价/评分系统
if (class_exists('\AKSRT\Shop\ProductReview')) {
\AKSRT\Shop\ProductReview::get_instance()->init();
-1
View File
@@ -38,7 +38,6 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-archive.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-archive-filters.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-comments.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-forum.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-forum-widgets.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-slider.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-vip.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-auth-ui.php';
-335
View File
@@ -83,339 +83,4 @@ function aksrt_register_topics_taxonomy() {
}
add_action('init', 'aksrt_register_topics_taxonomy');
// ============================================================
// 注册自定义文章类型 - Forum Topic & 分类法 - Forum Board
// ============================================================
function aksrt_register_forum_cpt() {
// Respect bbs_s switch — skip CPT registration when forum is disabled.
// Admin context always registers so the backend settings page works.
if (!is_admin() && class_exists('\AKSRT\Settings\Settings')) {
$forum = \AKSRT\Settings\Settings::get_forum_settings();
if (empty($forum['enabled'])) {
return;
}
}
// Read custom slugs / names from settings when available
$plate_slug = 'board';
$topic_slug = 'topic';
$forum_enabled = true;
$forum_settings = [];
if (class_exists('\AKSRT\Settings\Settings')) {
$forum_settings = \AKSRT\Settings\Settings::get_forum_settings();
$plate_slug = !empty($forum_settings['other']['plate_slug']) ? $forum_settings['other']['plate_slug'] : 'board';
$topic_slug = !empty($forum_settings['other']['posts_slug']) ? $forum_settings['other']['posts_slug'] : 'topic';
$forum_enabled = !empty($forum_settings['enabled']);
}
// Admin always registers types; menu follows bbs_s (zibll: show when forum on).
$forum_show_menu = is_admin() ? $forum_enabled : false;
$names = !empty($forum_settings['names']) ? $forum_settings['names'] : [];
$forum_label = sanitize_text_field((string) ($names['forum'] ?? __('论坛', 'aksrt')));
$posts_label = sanitize_text_field((string) ($names['posts'] ?? __('帖子', 'aksrt')));
$topic_label = sanitize_text_field((string) ($names['topic'] ?? __('话题', 'aksrt')));
$tag_label = sanitize_text_field((string) ($names['tag'] ?? __('标签', 'aksrt')));
// Legacy board taxonomy: keep rewrite/bridge, hide from admin (plate CPT is canonical).
register_taxonomy('aksrt_forum_board', 'aksrt_forum_topic', [
'labels' => [
'name' => __('论坛版块', 'aksrt'),
'singular_name' => __('版块', 'aksrt'),
'search_items' => __('搜索版块', 'aksrt'),
'all_items' => __('所有版块', 'aksrt'),
'edit_item' => __('编辑版块', 'aksrt'),
'add_new_item' => __('添加版块', 'aksrt'),
],
'hierarchical' => true,
'public' => true,
'show_ui' => false,
'show_in_menu' => false,
'show_in_rest' => true,
'show_admin_column' => false,
'rewrite' => ['slug' => $plate_slug],
]);
// Forum Tag taxonomy
register_taxonomy('aksrt_forum_tag', 'aksrt_forum_topic', [
'labels' => [
'name' => sprintf(/* translators: %s: forum name */ __('%s标签', 'aksrt'), $forum_label),
'singular_name' => $tag_label,
'search_items' => sprintf(/* translators: %s: tag name */ __('搜索%s', 'aksrt'), $tag_label),
'all_items' => sprintf(/* translators: %s: tag name */ __('所有%s', 'aksrt'), $tag_label),
'edit_item' => sprintf(/* translators: %s: tag name */ __('编辑%s', 'aksrt'), $tag_label),
'add_new_item' => sprintf(/* translators: %s: tag name */ __('添加%s', 'aksrt'), $tag_label),
'menu_name' => $tag_label,
],
'hierarchical' => false,
'public' => true,
'show_ui' => true,
'show_in_menu' => $forum_show_menu,
'show_in_rest' => true,
'show_admin_column' => true,
'rewrite' => ['slug' => 'forum-tag'],
]);
// Forum Topic taxonomy (话题,单选,区别于标签)
register_taxonomy('aksrt_forum_topic_cat', 'aksrt_forum_topic', [
'labels' => [
'name' => sprintf(/* translators: %s: forum name */ __('%s话题', 'aksrt'), $forum_label),
'singular_name' => $topic_label,
'search_items' => sprintf(/* translators: %s: topic name */ __('搜索%s', 'aksrt'), $topic_label),
'all_items' => sprintf(/* translators: %s: topic name */ __('所有%s', 'aksrt'), $topic_label),
'edit_item' => sprintf(/* translators: %s: topic name */ __('编辑%s', 'aksrt'), $topic_label),
'add_new_item' => sprintf(/* translators: %s: topic name */ __('添加%s', 'aksrt'), $topic_label),
'menu_name' => $topic_label,
],
'hierarchical' => false,
'public' => true,
'show_ui' => true,
'show_in_menu' => $forum_show_menu,
'show_in_rest' => true,
'show_admin_column' => true,
'rewrite' => ['slug' => 'forum-topic'],
]);
// Forum Topic CPT — native sidebar (zibll: forum_post @ pos 40)
register_post_type('aksrt_forum_topic', [
'labels' => [
'name' => $forum_label . $posts_label,
'singular_name' => $posts_label,
'add_new' => sprintf(/* translators: %s: posts name */ __('添加%s', 'aksrt'), $posts_label),
'add_new_item' => sprintf(/* translators: %s: posts name */ __('发布新%s', 'aksrt'), $posts_label),
'edit_item' => sprintf(/* translators: %s: posts name */ __('编辑%s', 'aksrt'), $posts_label),
'view_item' => sprintf(/* translators: %s: posts name */ __('查看%s', 'aksrt'), $posts_label),
'all_items' => sprintf(/* translators: %s: posts name */ __('所有%s', 'aksrt'), $posts_label),
'menu_name' => $forum_label . $posts_label,
],
'public' => true,
'show_ui' => true,
'show_in_menu' => $forum_show_menu,
'show_in_rest' => true,
'has_archive' => true,
'menu_position' => 40,
'menu_icon' => 'dashicons-buddicons-topics',
'supports' => ['title', 'editor', 'author', 'comments', 'thumbnail', 'excerpt'],
'capability_type' => 'post',
'rewrite' => ['slug' => $topic_slug],
]);
}
add_action('init', 'aksrt_register_forum_cpt');
// ============================================================
// 注册板块 CPT (aksrt_forum_plate) + 板块分区 taxonomy (aksrt_plate_cat)
// §7.0 plate CPT 脚手架:板块从 taxonomy 迁移为独立 CPT,
// 支持作者(超级版主)、正文/摘要/封面、层级父子、独立 meta 扩展位。
// ============================================================
function aksrt_register_forum_plate_cpt() {
if (!is_admin() && class_exists('\AKSRT\Settings\Settings')) {
$forum = \AKSRT\Settings\Settings::get_forum_settings();
if (empty($forum['enabled'])) {
return;
}
}
$plate_slug = 'board';
$forum_enabled = true;
$forum = [];
if (class_exists('\AKSRT\Settings\Settings')) {
$forum = \AKSRT\Settings\Settings::get_forum_settings();
$plate_slug = !empty($forum['other']['plate_slug']) ? $forum['other']['plate_slug'] : 'board';
$forum_enabled = !empty($forum['enabled']);
}
$forum_show_menu = is_admin() ? $forum_enabled : false;
$names = !empty($forum['names']) ? $forum['names'] : [];
$forum_label = sanitize_text_field((string) ($names['forum'] ?? __('论坛', 'aksrt')));
$plate_label = sanitize_text_field((string) ($names['plate'] ?? __('版块', 'aksrt')));
// Plate category taxonomy (分区)
register_taxonomy('aksrt_plate_cat', 'aksrt_forum_plate', [
'labels' => [
'name' => sprintf(/* translators: %s: plate name */ __('%s分区', 'aksrt'), $plate_label),
'singular_name' => __('分区', 'aksrt'),
'search_items' => __('搜索分区', 'aksrt'),
'all_items' => __('所有分区', 'aksrt'),
'edit_item' => __('编辑分区', 'aksrt'),
'add_new_item' => __('添加分区', 'aksrt'),
'menu_name' => __('分区', 'aksrt'),
],
'hierarchical' => true,
'public' => true,
'show_ui' => true,
'show_in_menu' => $forum_show_menu,
'show_in_rest' => true,
'show_admin_column' => true,
'rewrite' => ['slug' => 'plate-cat'],
]);
// Plate CPT (板块) — native sidebar (zibll: plate @ pos 40)
register_post_type('aksrt_forum_plate', [
'labels' => [
'name' => $forum_label . $plate_label,
'singular_name' => $plate_label,
'add_new' => sprintf(/* translators: %s: plate name */ __('添加%s', 'aksrt'), $plate_label),
'add_new_item' => sprintf(/* translators: %s: plate name */ __('创建%s', 'aksrt'), $plate_label),
'edit_item' => sprintf(/* translators: %s: plate name */ __('编辑%s', 'aksrt'), $plate_label),
'view_item' => sprintf(/* translators: %s: plate name */ __('查看%s', 'aksrt'), $plate_label),
'search_items' => sprintf(/* translators: %s: plate name */ __('搜索%s', 'aksrt'), $plate_label),
'not_found' => sprintf(/* translators: %s: plate name */ __('未找到%s', 'aksrt'), $plate_label),
'not_found_in_trash' => sprintf(/* translators: %s: plate name */ __('回收站中未找到%s', 'aksrt'), $plate_label),
'all_items' => sprintf(/* translators: %s: plate name */ __('所有%s', 'aksrt'), $plate_label),
'menu_name' => $forum_label . $plate_label,
],
'public' => true,
'hierarchical' => true,
'show_ui' => true,
'show_in_menu' => $forum_show_menu,
'show_in_rest' => true,
'has_archive' => false,
'menu_position' => 40,
'menu_icon' => 'dashicons-buddicons-forums',
'supports' => ['title', 'editor', 'author', 'excerpt', 'thumbnail', 'page-attributes'],
'capability_type' => 'post',
'rewrite' => ['slug' => $plate_slug, 'with_front' => false],
]);
// Keep aksrt_forum_topic assignable to plates via taxonomy bridge
register_taxonomy_for_object_type('aksrt_forum_board', 'aksrt_forum_topic');
}
add_action('init', 'aksrt_register_forum_plate_cpt');
// ============================================================
// §7.0 forum_post CPT 对齐:注册帖子 meta 扩展位地基
// 为 aksrt_forum_topic 注册全部 meta key,提供类型安全 + REST 暴露。
// ============================================================
function aksrt_register_forum_topic_meta() {
$post_type = 'aksrt_forum_topic';
$meta_fields = [
// 基础治理 meta (已有,补注册)
'_aksrt_visibility' => ['type' => 'string', 'default' => 'public'],
'_aksrt_review_status' => ['type' => 'string', 'default' => ''],
'_aksrt_sticky' => ['type' => 'integer', 'default' => 0],
'_aksrt_featured' => ['type' => 'integer', 'default' => 0],
'_aksrt_locked' => ['type' => 'integer', 'default' => 0],
'_aksrt_highlight' => ['type' => 'string', 'default' => ''],
'_aksrt_views' => ['type' => 'integer', 'default' => 0],
'_aksrt_moderation_note' => ['type' => 'string', 'default' => ''],
'_aksrt_moderated_by' => ['type' => 'integer', 'default' => 0],
'_aksrt_moderated_at' => ['type' => 'string', 'default' => ''],
// 帖子类型 + 问答状态 (Wave3)
'_aksrt_bbs_type' => ['type' => 'string', 'default' => 'standard'],
'_aksrt_question_status' => ['type' => 'integer', 'default' => 0],
'_aksrt_adopted_comment' => ['type' => 'integer', 'default' => 0],
// 投票 (已有,补注册)
'_aksrt_vote_enabled' => ['type' => 'integer', 'default' => 0],
'_aksrt_vote_title' => ['type' => 'string', 'default' => ''],
'_aksrt_vote_multiple' => ['type' => 'integer', 'default' => 0],
'_aksrt_vote_type' => ['type' => 'string', 'default' => 'standard'],
'_aksrt_vote_expiry_days' => ['type' => 'integer', 'default' => 0],
// 评分 (Wave3)
'_aksrt_score' => ['type' => 'integer', 'default' => 0],
'_aksrt_recommend_score' => ['type' => 'number', 'default' => 0],
'_aksrt_hot_rewarded' => ['type' => 'integer', 'default' => 0],
// 收藏 (Wave3)
'_aksrt_favorite_count' => ['type' => 'integer', 'default' => 0],
// 封面扩展位(论坛帖子封面;前台 forum-topic 渲染)
'_aksrt_cover_type' => ['type' => 'string', 'default' => ''],
'_aksrt_cover_image' => ['type' => 'string', 'default' => ''],
'_aksrt_featured_slide' => ['type' => 'array', 'default' => []],
'_aksrt_featured_video' => ['type' => 'string', 'default' => ''],
'_aksrt_cover_ratio' => ['type' => 'string', 'default' => '16:9'],
// 付费内容扩展位(论坛帖子付费门槛;前台 forum-topic 渲染)
'_aksrt_allow_view' => ['type' => 'string', 'default' => 'public'],
'_aksrt_pay_price' => ['type' => 'number', 'default' => 0],
'_aksrt_pay_vip_price' => ['type' => 'number', 'default' => 0], // legacy single VIP
'_aksrt_pay_vip1_price' => ['type' => 'number', 'default' => 0],
'_aksrt_pay_vip2_price' => ['type' => 'number', 'default' => 0],
'_aksrt_pay_credits' => ['type' => 'integer', 'default' => 0], // legacy alias of credits_cost
'_aksrt_credits_cost' => ['type' => 'integer', 'default' => 0], // runtime unlock price
'_aksrt_credits_vip1' => ['type' => 'integer', 'default' => 0],
'_aksrt_credits_vip2' => ['type' => 'integer', 'default' => 0],
'_aksrt_pay_hide_part' => ['type' => 'integer', 'default' => 0],
'_aksrt_virtual_sales' => ['type' => 'integer', 'default' => 0],
'_aksrt_topic_coupon_s' => ['type' => 'integer', 'default' => 0],
'_aksrt_visibility_roles' => ['type' => 'array', 'default' => []],
// 状态工作流扩展位 (Wave4)
'_aksrt_closed' => ['type' => 'integer', 'default' => 0],
'_aksrt_hidden' => ['type' => 'integer', 'default' => 0],
// 板块 CPT 关联 (§7.0 新增)
'_aksrt_plate_id' => ['type' => 'integer', 'default' => 0],
];
foreach ($meta_fields as $key => $args) {
$schema = [
'type' => $args['type'],
'description' => $key,
'single' => true,
'default' => $args['default'],
'show_in_rest' => false,
];
if ($args['type'] === 'array') {
$schema['show_in_rest'] = false;
$schema['type'] = 'array';
}
register_post_meta($post_type, $key, $schema);
}
// Plate CPT meta registration
$plate_meta = [
'_aksrt_sort_order' => ['type' => 'integer', 'default' => 0],
'_aksrt_icon' => ['type' => 'string', 'default' => ''],
'_aksrt_moderators' => ['type' => 'array', 'default' => []],
'_aksrt_moderator_roles' => ['type' => 'array', 'default' => []],
'_aksrt_posts_count' => ['type' => 'integer', 'default' => 0],
'_aksrt_reply_count' => ['type' => 'integer', 'default' => 0],
'_aksrt_today_reply_count' => ['type' => 'integer', 'default' => 0],
'_aksrt_plate_views' => ['type' => 'integer', 'default' => 0],
'_aksrt_follow_count' => ['type' => 'integer', 'default' => 0],
'_aksrt_last_reply' => ['type' => 'string', 'default' => ''],
'_aksrt_last_post' => ['type' => 'string', 'default' => ''],
'_aksrt_allow_view' => ['type' => 'string', 'default' => 'public'],
'_aksrt_add_limit' => ['type' => 'integer', 'default' => 0],
'_aksrt_follow_pay' => ['type' => 'integer', 'default' => 0],
'_aksrt_follow_pay_modo' => ['type' => 'string', 'default' => 'money'],
'_aksrt_follow_pay_price' => ['type' => 'number', 'default' => 0],
'_aksrt_follow_pay_points' => ['type' => 'integer', 'default' => 0],
'_aksrt_follow_pay_vip_price' => ['type' => 'number', 'default' => 0], // legacy read fallback → vip1
'_aksrt_follow_pay_vip1_price' => ['type' => 'number', 'default' => 0],
'_aksrt_follow_pay_vip2_price' => ['type' => 'number', 'default' => 0],
'_aksrt_follow_pay_coupon_s' => ['type' => 'integer', 'default' => 0],
];
foreach ($plate_meta as $key => $args) {
register_post_meta('aksrt_forum_plate', $key, [
'type' => $args['type'] === 'array' ? 'array' : $args['type'],
'description' => $key,
'single' => true,
'default' => $args['default'],
'show_in_rest' => false,
]);
}
}
add_action('init', 'aksrt_register_forum_topic_meta');
// Include forum topics in frontend search results (§7.10).
add_action('pre_get_posts', function ($query) {
if (!is_admin() && $query->is_main_query() && $query->is_search()) {
$post_types = $query->get('post_type');
if (empty($post_types) || $post_types === 'post') {
$query->set('post_type', ['post', 'page', 'aksrt_forum_topic']);
} elseif (is_array($post_types) && !in_array('aksrt_forum_topic', $post_types, true)) {
$post_types[] = 'aksrt_forum_topic';
$query->set('post_type', $post_types);
}
}
});
// ============================================================
-9
View File
@@ -47,8 +47,6 @@ if (!function_exists('aksrt_credits_label')) {
'download' => __('下载消费', 'aksrt'),
'admin' => __('管理员调整', 'aksrt'),
'refund' => __('退款', 'aksrt'),
'forum_topic' => __('论坛发帖', 'aksrt'),
'forum_reply' => __('论坛回复', 'aksrt'),
'free_sign_up' => __('注册奖励', 'aksrt'),
'free_sign_in' => __('每日签到', 'aksrt'),
'free_login' => __('每日登录', 'aksrt'),
@@ -58,13 +56,6 @@ if (!function_exists('aksrt_credits_label')) {
'free_post_favorite'=> __('文章被收藏', 'aksrt'),
'free_comment_new'=> __('发表评论', 'aksrt'),
'free_comment_like'=> __('评论被赞', 'aksrt'),
'free_bbs_posts_new'=> __('论坛发帖', 'aksrt'),
'free_bbs_reply'=> __('论坛回复', 'aksrt'),
'free_bbs_essence'=> __('帖子加精', 'aksrt'),
'free_bbs_adopt'=> __('回复被采纳', 'aksrt'),
'free_bbs_posts_hot'=> __('热帖奖励', 'aksrt'),
'free_bbs_comment_hot'=> __('神评奖励', 'aksrt'),
'free_bbs_score_extra'=> __('帖子加分', 'aksrt'),
];
if (isset($map[$reason])) {
return $map[$reason];
-363
View File
@@ -1,363 +0,0 @@
<?php
/**
* Shared forum widget renderers (zibll BBS posts/plate mini parity).
*
* Used by WidgetForumTopicLists, WidgetForumTabTopics, WidgetForumPlateLists,
* WidgetForumBoardList, WidgetForumPlateModerator.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!function_exists('aksrt_forum_topic_meta_flags')) :
/**
* @return array{topping:bool,essence:bool,views:int,replies:int,plate_id:int}
*/
function aksrt_forum_topic_meta_flags(int $post_id): array {
return [
'topping' => (string) get_post_meta($post_id, '_aksrt_topping', true) === '1'
|| (string) get_post_meta($post_id, '_aksrt_is_top', true) === '1',
'essence' => (string) get_post_meta($post_id, '_aksrt_essence', true) === '1',
'views' => (int) get_post_meta($post_id, '_aksrt_views', true),
'replies' => (int) get_comments_number($post_id),
'plate_id' => (int) get_post_meta($post_id, '_aksrt_plate_id', true),
];
}
endif;
if (!function_exists('aksrt_forum_topic_badges_html')) :
/**
* Topping / essence badges for forum topic rows.
*/
function aksrt_forum_topic_badges_html(int $post_id, bool $show_topping = true): string {
$flags = aksrt_forum_topic_meta_flags($post_id);
$html = '';
if ($show_topping && !empty($flags['topping'])) {
$html .= '<badge class="img-badge left jb-red"><i>' . esc_html__('置顶', 'aksrt') . '</i></badge>';
}
if (!empty($flags['essence'])) {
$html .= '<badge class="img-badge right jb-yellow"><i>' . esc_html__('精华', 'aksrt') . '</i></badge>';
}
return $html;
}
endif;
if (!function_exists('aksrt_render_forum_topic_mini')) :
/**
* Mini forum topic row (zibll zib_bbs_get_posts_mini_list parity).
*
* @param int|null $post_id Null = current post in loop.
* @param string $class Extra classes on outer element.
* @param bool $show_topping Whether to show topping badge.
*/
function aksrt_render_forum_topic_mini(?int $post_id = null, string $class = 'ajax-item', bool $show_topping = true): string {
$post_id = $post_id ?: (int) get_the_ID();
if ($post_id <= 0) {
return '';
}
$post = get_post($post_id);
if (!$post || $post->post_status !== 'publish') {
return '';
}
$author_id = (int) $post->post_author;
$flags = aksrt_forum_topic_meta_flags($post_id);
$permalink = get_permalink($post_id);
$title = get_the_title($post_id);
$avatar_url = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($author_id, ['size' => 48])
: get_avatar_url($author_id, ['size' => 48]);
$home = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($author_id)
: get_author_posts_url($author_id);
$name = function_exists('aksrt_get_user_name')
? aksrt_get_user_name([
'id' => $author_id,
'class' => 'flex ac flex1',
'follow' => false,
'medal' => false,
'level' => false,
])
: '<a href="' . esc_url($home) . '">' . esc_html(get_the_author_meta('display_name', $author_id)) . '</a>';
// Prefer last reply time when available.
$time_raw = (string) get_post_meta($post_id, '_aksrt_last_reply_time', true);
if ($time_raw === '' || !strtotime($time_raw)) {
$time_raw = (string) $post->post_date;
}
$time_txt = function_exists('aksrt_time_ago')
? aksrt_time_ago($time_raw)
: human_time_diff(strtotime($time_raw), current_time('timestamp')) . __('前', 'aksrt');
$views_txt = function_exists('aksrt_format_number')
? aksrt_format_number($flags['views'])
: (string) $flags['views'];
$cls = trim('forum-posts mini aksrt-ftopic-mini relative ' . $class);
$html = '<posts class="' . esc_attr($cls) . '">';
$html .= aksrt_forum_topic_badges_html($post_id, $show_topping);
$html .= '<div class="forum-posts-header flex ac">';
$html .= '<a class="avatar-img mr6" href="' . esc_url($home) . '"><img class="avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar_url) . '" src="' . esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg') . '" alt="" loading="lazy" width="28" height="28" /></a>';
$html .= '<div class="flex1 min-w0">';
$html .= '<div class="forum-title text-ellipsis"><a class="icon-circle" href="' . esc_url($permalink) . '">' . esc_html($title) . '</a></div>';
$html .= '<div class="item-meta em09 muted-2-color flex ac flex-wrap">';
$html .= '<span class="forum-user-name">' . $name . '</span>';
$html .= '<span class="ml6">' . esc_html($time_txt) . '</span>';
$html .= '<span class="ml6"><i class="fa fa-eye" aria-hidden="true"></i> ' . esc_html((string) $views_txt) . '</span>';
if ($flags['replies'] > 0) {
$html .= '<span class="ml6"><i class="fa fa-comment-o" aria-hidden="true"></i> ' . esc_html((string) $flags['replies']) . '</span>';
}
$html .= '</div></div></div></posts>';
return $html;
}
endif;
if (!function_exists('aksrt_render_forum_topic_detail')) :
/**
* Detail forum topic row (richer entry meta).
*
* @param int|null $post_id
* @param string $class
* @param bool $show_topping
*/
function aksrt_render_forum_topic_detail(?int $post_id = null, string $class = 'ajax-item', bool $show_topping = true): string {
$post_id = $post_id ?: (int) get_the_ID();
if ($post_id <= 0) {
return '';
}
$post = get_post($post_id);
if (!$post || $post->post_status !== 'publish') {
return '';
}
$author_id = (int) $post->post_author;
$flags = aksrt_forum_topic_meta_flags($post_id);
$permalink = get_permalink($post_id);
$title = get_the_title($post_id);
$excerpt = wp_trim_words(wp_strip_all_tags((string) $post->post_content), 36, '…');
$avatar_url = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($author_id, ['size' => 64])
: get_avatar_url($author_id, ['size' => 64]);
$home = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($author_id)
: get_author_posts_url($author_id);
$name = function_exists('aksrt_get_user_name')
? aksrt_get_user_name([
'id' => $author_id,
'class' => 'flex ac',
'follow' => false,
'medal' => true,
'level' => true,
])
: '<a href="' . esc_url($home) . '">' . esc_html(get_the_author_meta('display_name', $author_id)) . '</a>';
$time_raw = (string) get_post_meta($post_id, '_aksrt_last_reply_time', true);
if ($time_raw === '' || !strtotime($time_raw)) {
$time_raw = (string) $post->post_date;
}
$time_txt = function_exists('aksrt_time_ago')
? aksrt_time_ago($time_raw)
: human_time_diff(strtotime($time_raw), current_time('timestamp')) . __('前', 'aksrt');
$views_txt = function_exists('aksrt_format_number')
? aksrt_format_number($flags['views'])
: (string) $flags['views'];
// Optional plate name chip.
$plate_chip = '';
if ($flags['plate_id'] > 0) {
$plate = get_post($flags['plate_id']);
if ($plate instanceof WP_Post) {
$plate_chip = '<a class="but p2-10 em09 mr6" href="' . esc_url(get_permalink($plate)) . '">' . esc_html(get_the_title($plate)) . '</a>';
}
}
$cls = trim('forum-posts detail aksrt-ftopic-detail relative ' . $class);
$html = '<posts class="' . esc_attr($cls) . '">';
$html .= aksrt_forum_topic_badges_html($post_id, $show_topping);
$html .= '<div class="forum-posts-header flex ac">';
$html .= '<a class="avatar-img mr10" href="' . esc_url($home) . '"><img class="avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar_url) . '" src="' . esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg') . '" alt="" loading="lazy" width="40" height="40" /></a>';
$html .= '<div class="flex1 min-w0">' . $name . '<div class="em09 muted-2-color mt3">' . esc_html($time_txt) . '</div></div>';
$html .= '</div>';
$html .= '<h2 class="forum-title mt10"><a href="' . esc_url($permalink) . '">' . esc_html($title) . '</a></h2>';
if ($excerpt !== '') {
$html .= '<div class="forum-excerpt muted-2-color em09 mt6">' . esc_html($excerpt) . '</div>';
}
$html .= '<div class="item-meta flex ac flex-wrap mt10 em09 muted-2-color">';
$html .= $plate_chip;
$html .= '<span class="mr10"><i class="fa fa-eye" aria-hidden="true"></i> ' . esc_html((string) $views_txt) . '</span>';
$html .= '<span class="mr10"><i class="fa fa-comment-o" aria-hidden="true"></i> ' . esc_html((string) $flags['replies']) . '</span>';
$html .= '</div></posts>';
return $html;
}
endif;
if (!function_exists('aksrt_render_plate_thumb_html')) :
/**
* Shared plate thumb with lazy pattern.
*/
function aksrt_render_plate_thumb_html(int $plate_id, string $size = 'thumbnail'): string {
$thumb = get_the_post_thumbnail_url($plate_id, $size);
$icon = (string) get_post_meta($plate_id, '_aksrt_icon', true);
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
if ($thumb) {
return '<span class="plate-thumb aksrt-plate-item__thumb"><img class="aksrt-lazy lazyload fit-cover" data-src="' . esc_url($thumb) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" width="48" height="48" /></span>';
}
if ($icon !== '') {
return '<span class="plate-thumb aksrt-plate-item__thumb is-icon"><i class="' . esc_attr($icon) . '" aria-hidden="true"></i></span>';
}
return '<span class="plate-thumb aksrt-plate-item__thumb is-empty"><i class="fa fa-comments" aria-hidden="true"></i></span>';
}
endif;
if (!function_exists('aksrt_render_plate_row')) :
/**
* Plate list row (zibll plate-item flex).
*
* @param WP_Post|int $plate
*/
function aksrt_render_plate_row($plate): string {
$post = $plate instanceof WP_Post ? $plate : get_post((int) $plate);
if (!$post instanceof WP_Post) {
return '';
}
$id = (int) $post->ID;
$url = get_permalink($id);
$count = (int) get_post_meta($id, '_aksrt_posts_count', true);
$follow = (int) get_post_meta($id, '_aksrt_follow_count', true);
$desc = $post->post_excerpt !== '' ? $post->post_excerpt : wp_trim_words(wp_strip_all_tags((string) $post->post_content), 18);
$html = '<a class="plate-item flex aksrt-plate-item ajax-item" href="' . esc_url($url) . '">';
$html .= aksrt_render_plate_thumb_html($id, 'thumbnail');
$html .= '<div class="item-info flex1 aksrt-plate-item__body">';
$html .= '<div class="info-header"><h2 class="forum-title aksrt-plate-item__title">' . esc_html(get_the_title($post)) . '</h2></div>';
if ($desc !== '') {
$html .= '<div class="muted-2-color em09 mt3 text-ellipsis aksrt-plate-item__desc">' . esc_html($desc) . '</div>';
}
$html .= '<div class="item-meta em09 muted-2-color mt3 aksrt-plate-item__meta">';
$html .= '<span>' . esc_html(sprintf(/* translators: %d topic count */ __('%d 帖', 'aksrt'), $count)) . '</span>';
if ($follow > 0) {
$html .= '<span class="ml10">' . esc_html(sprintf(/* translators: %d follow count */ __('%d 关注', 'aksrt'), $follow)) . '</span>';
}
$html .= '</div></div></a>';
return $html;
}
endif;
if (!function_exists('aksrt_render_plate_card')) :
/**
* Plate card slide item.
*
* @param WP_Post|int $plate
*/
function aksrt_render_plate_card($plate): string {
$post = $plate instanceof WP_Post ? $plate : get_post((int) $plate);
if (!$post instanceof WP_Post) {
return '';
}
$id = (int) $post->ID;
$url = get_permalink($id);
$thumb = get_the_post_thumbnail_url($id, 'medium');
$count = (int) get_post_meta($id, '_aksrt_posts_count', true);
$follow = (int) get_post_meta($id, '_aksrt_follow_count', true);
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
$html = '<a class="plate-card aksrt-plate-card ajax-item" href="' . esc_url($url) . '">';
$html .= '<div class="plate-thumb aksrt-plate-card__cover">';
if ($thumb) {
$html .= '<img class="aksrt-lazy lazyload fit-cover" data-src="' . esc_url($thumb) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" />';
} else {
$html .= '<span class="is-empty"><i class="fa fa-comments" aria-hidden="true"></i></span>';
}
$html .= '</div>';
$html .= '<div class="aksrt-plate-card__title forum-title mt10">' . esc_html(get_the_title($post)) . '</div>';
$html .= '<div class="aksrt-plate-card__meta muted-2-color em09 mt6">';
$html .= '<span>' . esc_html(sprintf(__('%d 帖', 'aksrt'), $count)) . '</span>';
$html .= '<span class="ml10">' . esc_html(sprintf(__('%d 关注', 'aksrt'), $follow)) . '</span>';
$html .= '</div></a>';
return $html;
}
endif;
if (!function_exists('aksrt_render_moderator_row')) :
/**
* Moderator identity row with optional follow.
*
* @param int $user_id
* @param string $role author|super|moderator
* @param bool $show_avatar
* @param string $style list|grid
*/
function aksrt_render_moderator_row(int $user_id, string $role = 'moderator', bool $show_avatar = true, string $style = 'list'): string {
if ($user_id <= 0) {
return '';
}
$user = get_userdata($user_id);
if (!$user) {
return '';
}
$avatar = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($user_id, ['size' => 64])
: get_avatar_url($user_id, ['size' => 64]);
$home = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($user_id)
: get_author_posts_url($user_id);
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
if ($role === 'author' || $role === 'super') {
$role_label = __('超级版主', 'aksrt');
} else {
$role_label = __('版主', 'aksrt');
}
$name = function_exists('aksrt_get_user_name')
? aksrt_get_user_name([
'id' => $user_id,
'class' => 'flex ac flex1',
'follow' => false,
'medal' => true,
'level' => true,
])
: '<a href="' . esc_url($home) . '">' . esc_html($user->display_name) . '</a>';
// Follow control (not for self).
$follow_html = '';
$cuid = get_current_user_id();
if ($cuid > 0 && $cuid !== $user_id && class_exists('\AKSRT\User\Follow')) {
$following = \AKSRT\User\Follow::get_instance()->is_following($cuid, $user_id);
$label = $following ? __('已关注', 'aksrt') : __('关注', 'aksrt');
$follow_html = '<button type="button" class="but hollow c-blue p2-10 em09 ml10 flex0' . ($following ? ' is-following' : '') . '" data-aksrt-follow="' . (int) $user_id . '">' . esc_html($label) . '</button>';
}
if ($style === 'grid') {
$html = '<div class="aksrt-plate-mods__item role-' . esc_attr($role) . ' is-grid">';
if ($show_avatar) {
$html .= '<a href="' . esc_url($home) . '"><img class="aksrt-plate-mods__avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" width="48" height="48" /></a>';
}
$html .= '<div class="aksrt-plate-mods__name">' . $name . '</div>';
$html .= '<div class="aksrt-plate-mods__role muted-2-color em09">' . esc_html($role_label) . '</div>';
$html .= $follow_html;
$html .= '</div>';
return $html;
}
$html = '<div class="aksrt-plate-mods__item role-' . esc_attr($role) . ' flex ac">';
if ($show_avatar) {
$html .= '<a class="avatar-img mr10" href="' . esc_url($home) . '"><img class="aksrt-plate-mods__avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" width="40" height="40" /></a>';
}
$html .= '<div class="flex1 min-w0">';
$html .= '<div class="aksrt-plate-mods__name">' . $name . '</div>';
$html .= '<div class="aksrt-plate-mods__role muted-2-color em09">' . esc_html($role_label) . '</div>';
$html .= '</div>';
$html .= $follow_html;
$html .= '</div>';
return $html;
}
endif;
+4 -48
View File
@@ -1,8 +1,9 @@
<?php
/**
* Forum frontend URL helpers.
* Generic page-template URL helpers.
*
* Page-template assignment is the source of truth for forum home / compose URLs.
* Page-template assignment is the source of truth for resolving public URLs
* for custom page templates (submit form, etc.).
*
* @package AKSRT
*/
@@ -13,7 +14,7 @@ if (!function_exists('aksrt_get_page_url_by_template')) :
/**
* Resolve the first published page that uses a given page template file.
*
* @param string $template_file Relative theme template path (e.g. templates/pages/page-forums.php).
* @param string $template_file Relative theme template path (e.g. templates/pages/page-submit.php).
*/
function aksrt_get_page_url_by_template(string $template_file): string {
$template_file = ltrim(str_replace('\\', '/', $template_file), '/');
@@ -51,48 +52,3 @@ if (!function_exists('aksrt_get_page_url_by_template')) :
return $url;
}
endif;
if (!function_exists('aksrt_get_forum_home_url')) :
/**
* Public forum home URL (page template AKSRT - 论坛).
*/
function aksrt_get_forum_home_url(): string {
$url = aksrt_get_page_url_by_template('templates/pages/page-forums.php');
return $url !== '' ? $url : home_url('/');
}
endif;
if (!function_exists('aksrt_get_forum_compose_url')) :
/**
* Public forum compose URL (page template AKSRT - 发帖).
*
* @param array<string, scalar> $args Optional query args (board, plate, …).
*/
function aksrt_get_forum_compose_url(array $args = []): string {
$url = aksrt_get_page_url_by_template('templates/forum/forum-compose.php');
if ($url === '') {
$url = home_url('/');
}
// Normalize plate → board for compose form contract.
if (isset($args['plate']) && !isset($args['board'])) {
$args['board'] = $args['plate'];
unset($args['plate']);
}
$clean = [];
foreach ($args as $key => $value) {
$key = sanitize_key((string) $key);
if ($key === '') {
continue;
}
if (is_bool($value)) {
$clean[$key] = $value ? '1' : '0';
} else {
$clean[$key] = (string) $value;
}
}
return !empty($clean) ? add_query_arg($clean, $url) : $url;
}
endif;
-58
View File
@@ -21,17 +21,6 @@ if (!function_exists('aksrt_get_header_newposts_config')) :
$types = is_array($cfg['types'] ?? null) ? $cfg['types'] : [];
if (!$types) {
$types = ['post'];
if (class_exists('\AKSRT\Settings\Settings')) {
$forum = \AKSRT\Settings\Settings::get_forum_settings();
$shop = \AKSRT\Settings\Settings::get_shop_settings();
if (!empty($forum['enabled'])) {
$types[] = 'topic';
$types[] = 'plate';
}
if (!empty($shop['enabled'])) {
$types[] = 'product';
}
}
}
$cfg['types'] = $types;
if (empty($cfg['class'])) {
@@ -65,53 +54,6 @@ if (!function_exists('aksrt_get_new_add_actions')) :
'label' => __('写文章', 'aksrt'),
'icon' => 'fas fa-file-alt',
];
} elseif ($type === 'topic') {
$url = function_exists('aksrt_get_forum_compose_url') ? aksrt_get_forum_compose_url() : '';
if ($url === '') {
continue;
}
$out[] = [
'type' => 'topic',
'url' => $url,
'label' => __('发帖', 'aksrt'),
'icon' => 'fas fa-comments',
];
} elseif ($type === 'product') {
$url = '';
if (function_exists('aksrt_get_product_edit_url')) {
$url = (string) aksrt_get_product_edit_url();
}
if ($url === '') {
$url = admin_url('post-new.php?post_type=aksrt_product');
}
$out[] = [
'type' => 'product',
'url' => $url,
'label' => __('发布商品', 'aksrt'),
'icon' => 'fas fa-shopping-bag',
];
} elseif ($type === 'plate') {
// zibll bbs_plate → AKSRT forum plate CPT (admin create for entitled users).
$can = current_user_can('manage_options');
if (!$can && class_exists('\AKSRT\Forum\Forum')) {
try {
$forum = \AKSRT\Forum\Forum::get_instance();
if (method_exists($forum, 'forum_user_can')) {
$can = (bool) $forum->forum_user_can(get_current_user_id(), 'plate_create');
}
} catch (\Throwable $e) {
$can = false;
}
}
if (!$can || !post_type_exists('aksrt_forum_plate')) {
continue;
}
$out[] = [
'type' => 'plate',
'url' => admin_url('post-new.php?post_type=aksrt_forum_plate'),
'label' => __('创建版块', 'aksrt'),
'icon' => 'fas fa-th-large',
];
}
}
return $out;
+1 -32
View File
@@ -356,26 +356,7 @@ if (!function_exists('aksrt_content_order_cookie_token_key')) {
}
/**
* Whether guest cash purchase is allowed for forum topic / plate follow.
* Reuses global pay_no_logged_in (same switch as content).
*
* @param WP_Post|int|null $post Topic or plate post (optional filter context).
*/
if (!function_exists('aksrt_allow_guest_forum_pay')) {
function aksrt_allow_guest_forum_pay($post = null): bool {
$order = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_order_settings() : [];
$paid = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_paid_content_settings() : [];
$allow = !empty($order['no_logged_in']) || !empty($paid['no_logged_in']);
/**
* @param bool $allow
* @param WP_Post|int|null $post
*/
return (bool) apply_filters('aksrt_allow_guest_forum_pay', $allow, $post);
}
}
/**
* Set guest paid cookies after cash order paid (content / topic / plate_follow).
* Set guest paid cookies after cash order paid (content).
*
* @param array<string,mixed> $order
*/
@@ -495,18 +476,6 @@ if (!function_exists('aksrt_guest_has_paid_access')) {
}
}
/**
* Guest paid plate follow access (cookie).
*/
if (!function_exists('aksrt_guest_has_plate_follow_access')) {
function aksrt_guest_has_plate_follow_access(int $plate_id): bool {
$type = class_exists('\AKSRT\Core\DomainModel')
? \AKSRT\Core\DomainModel::ORDER_PRODUCT_PLATE_FOLLOW
: 'plate_follow';
return null !== aksrt_get_guest_paid_order($plate_id, $type);
}
}
/**
* Resolve the amount a given user should pay for a post (Zibll VIP tier min).
*
-45
View File
@@ -7,51 +7,6 @@
defined('ABSPATH') || exit;
if (!function_exists('aksrt_get_float_shop_cart_html')) :
/**
* Floating shop cart button with badge.
*/
function aksrt_get_float_shop_cart_html(array $item = []): string {
if (!is_user_logged_in()) {
return '';
}
$shop_on = true;
if (class_exists('\AKSRT\Settings\Settings')) {
$shop = \AKSRT\Settings\Settings::get_shop_settings();
$shop_on = !empty($shop['enabled']);
}
if (!$shop_on) {
return '';
}
// Skip on cart page.
if (function_exists('is_page_template') && is_page_template('templates/pages/page-cart.php')) {
return '';
}
$url = function_exists('aksrt_get_cart_page_url') ? aksrt_get_cart_page_url() : '';
if ($url === '') {
return '';
}
$count = 0;
if (class_exists('\AKSRT\Shop\Shop')) {
$count = (int) \AKSRT\Shop\Shop::get_instance()->get_cart_count(get_current_user_id());
}
$icon = !empty($item['icon']) ? (string) $item['icon'] : 'fas fa-shopping-cart';
$style = [];
if (!empty($item['color'])) {
$style[] = '--this-color:' . esc_attr((string) $item['color']);
}
if (!empty($item['bg'])) {
$style[] = '--this-bg:' . esc_attr((string) $item['bg']);
}
$style_attr = $style ? ' style="' . implode(';', $style) . '"' : '';
$badge = $count > 0 ? (string) min(99, $count) : '';
return '<a class="float-btn float-btn-shop-cart nowave" href="' . esc_url($url) . '" title="' . esc_attr__('购物车', 'aksrt') . '"' . $style_attr . '>'
. '<i class="' . esc_attr($icon) . '" aria-hidden="true"></i>'
. '<badge cart-count="' . esc_attr($badge) . '">' . esc_html($badge) . '</badge>'
. '</a>';
}
endif;
if (!function_exists('aksrt_get_float_pay_vip_html')) :
/**
* Floating VIP open button.
-12
View File
@@ -485,18 +485,8 @@ if (!function_exists('aksrt_get_user_center_render_context')) {
$wallet_balance = (float) ($dashboard['wallet_balance'] ?? 0.0);
$runtime_config = aksrt_user_center_runtime_config($user_id, $tabs['default'], $active_tab, $tabs['urls']);
$runtime_config['avatarUpload'] = aksrt_user_center_avatar_upload_config($allow_avatar_upload);
$shop_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_shop_settings() : [];
$forum_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_forum_settings() : [];
$revenue_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_payment_revenue_settings() : [];
$shop_enabled = !empty($shop_settings['enabled']);
$forum_enabled = !empty($forum_settings['enabled']);
$new_post_url = function_exists('aksrt_get_new_post_url') ? aksrt_get_new_post_url() : '';
$new_product_url = $shop_enabled && function_exists('aksrt_get_page_url_by_template')
? aksrt_get_page_url_by_template('templates/pages/page-product-edit.php')
: '';
$forum_compose_url = $forum_enabled && function_exists('aksrt_get_page_url_by_template')
? aksrt_get_page_url_by_template('templates/forum/forum-compose.php')
: '';
$revenue_url = (!empty($revenue_settings['enabled']) || !empty($affiliate_settings['enabled'])) && function_exists('aksrt_get_revenue_page_url')
? aksrt_get_revenue_page_url()
: '';
@@ -523,8 +513,6 @@ if (!function_exists('aksrt_get_user_center_render_context')) {
'notification_unread_count' => function_exists('aksrt_get_user_unread_notification_count') ? aksrt_get_user_unread_notification_count($user_id) : 0,
'messages_url' => $messages_enabled && function_exists('aksrt_get_messages_page_url') ? aksrt_get_messages_page_url() : '',
'new_post_url' => $new_post_url,
'new_product_url' => $new_product_url,
'forum_compose_url' => $forum_compose_url,
'revenue_url' => $revenue_url,
'per_page' => 10,
'posts_page' => max(1, isset($_GET['posts_page']) ? (int) $_GET['posts_page'] : 1),
-42
View File
@@ -79,52 +79,16 @@ if (!function_exists('aksrt_get_user_home_page_context')) {
$display_tabs = $is_mobile ? $enabled_tabs_mobile : $enabled_tabs_pc;
$default_tab = !empty($display_tabs) ? $display_tabs[0] : $enabled_tabs[0];
$forum_instance = class_exists('\AKSRT\Forum\Forum') ? \AKSRT\Forum\Forum::get_instance() : null;
$forum_stats = $forum_instance ? $forum_instance->get_user_forum_stats($user_id) : [];
$forum_topic_count = (int) ($forum_stats['topic_count'] ?? 0);
$product_count = 0;
if (class_exists('\AKSRT\Shop\Shop')) {
$product_count = (int) \AKSRT\Shop\Shop::get_instance()->count_author_products($user_id);
}
$follow_count = 0;
if (class_exists('\AKSRT\User\Follow')) {
$follow_count = (int) \AKSRT\User\Follow::get_instance()->get_follower_count($user_id);
}
$plate_count = 0;
if ($forum_instance) {
$seen_plates = [];
if (method_exists($forum_instance, 'get_user_followed_plates')) {
foreach ((array) $forum_instance->get_user_followed_plates($user_id) as $plate) {
$pid = (int) ($plate['id'] ?? 0);
if ($pid > 0) {
$seen_plates[$pid] = true;
}
}
}
if (method_exists($forum_instance, 'get_user_managed_plates')) {
$managed = (array) $forum_instance->get_user_managed_plates($user_id);
foreach (['owned', 'moderating'] as $bucket) {
foreach ((array) ($managed[$bucket] ?? []) as $plate) {
$pid = (int) ($plate['id'] ?? 0);
if ($pid > 0) {
$seen_plates[$pid] = true;
}
}
}
}
$plate_count = count($seen_plates);
}
$tab_counts = [
'posts' => $post_count,
'comments' => $comment_count,
'favorites' => $favorite_count,
'credits' => $credits_enabled && $is_own_profile ? (int) round((float) $credit_balance) : 0,
'product' => $product_count,
'plate' => $plate_count,
'forum' => $forum_topic_count,
'follow' => $follow_count,
];
@@ -133,9 +97,6 @@ if (!function_exists('aksrt_get_user_home_page_context')) {
'comments' => __('评论', 'aksrt'),
'favorites' => __('收藏', 'aksrt'),
'credits' => sprintf(__('我的%s', 'aksrt'), $currency_name),
'product' => __('商品', 'aksrt'),
'plate' => __('圈子', 'aksrt'),
'forum' => __('论坛', 'aksrt'),
'follow' => __('粉丝', 'aksrt'), // Zibll: 粉丝
];
foreach ($visible_tabs as $tab_key => $tab_config) {
@@ -174,9 +135,6 @@ if (!function_exists('aksrt_get_user_home_page_context')) {
'active_tab' => $active_tab,
'tab_counts' => $tab_counts,
'tab_labels' => $tab_labels,
'forum_instance' => $forum_instance,
'forum_stats' => $forum_stats,
'forum_topic_count' => $forum_topic_count,
'home_url' => $home_url,
'posts_page' => max(1, isset($_GET['posts_page']) ? (int) $_GET['posts_page'] : 1),
'comments_page' => max(1, isset($_GET['comments_page']) ? (int) $_GET['comments_page'] : 1),
@@ -208,19 +208,7 @@ if (!function_exists('aksrt_header_user_box_html')) :
if ($pub_text === '') {
$pub_text = __('发布内容', 'aksrt');
}
$pub_type = sanitize_key((string) ($all['header_user_newposts_btn'] ?? 'post'));
$pub_url = (string) $ctx['publish_url'];
if ($pub_type === 'topic' && function_exists('aksrt_get_forum_compose_url')) {
$u = aksrt_get_forum_compose_url();
if ($u) { $pub_url = $u; }
} elseif ($pub_type === 'product') {
if (function_exists('aksrt_get_product_edit_url')) {
$u = (string) aksrt_get_product_edit_url();
if ($u !== '') { $pub_url = $u; }
} else {
$pub_url = admin_url('post-new.php?post_type=aksrt_product');
}
}
$href .= '<a rel="nofollow" href="' . esc_url($pub_url) . '">';
$href .= '<div class="badg mb6 toggle-radius c-green"><i class="fas fa-pen" aria-hidden="true"></i></div>';
$href .= '<div class="c-green">' . esc_html($pub_text) . '</div></a>';
@@ -240,13 +228,6 @@ if (!function_exists('aksrt_header_user_box_html')) :
$href .= '<a rel="nofollow" target="_blank" href="' . esc_url(admin_url('widgets.php')) . '">';
$href .= '<div class="badg mb6 toggle-radius c-yellow"><i class="fas fa-chart-pie" aria-hidden="true"></i></div>';
$href .= '<div class="c-yellow">' . esc_html__('模块布局', 'aksrt') . '</div></a>';
// zibll: 商城中心.
$shop_url = post_type_exists('aksrt_product')
? admin_url('edit.php?post_type=aksrt_product')
: admin_url('admin.php?page=aksrt_pay');
$href .= '<a rel="nofollow" target="_blank" href="' . esc_url($shop_url) . '">';
$href .= '<div class="badg mb6 toggle-radius c-yellow"><i class="fas fa-shopping-bag" aria-hidden="true"></i></div>';
$href .= '<div class="c-yellow">' . esc_html__('商城中心', 'aksrt') . '</div></a>';
$href .= '<a rel="nofollow" target="_blank" href="' . esc_url(admin_url()) . '">';
$href .= '<div class="badg mb6 toggle-radius c-yellow"><i class="fas fa-cog" aria-hidden="true"></i></div>';
$href .= '<div class="c-yellow">' . esc_html__('后台管理', 'aksrt') . '</div></a>';
@@ -22,9 +22,6 @@ if (!function_exists('aksrt_header_components_page_type_keys')) {
'author',
'single',
'search',
'forum_home',
'forum_plate',
'forum_post',
'page',
];
}
@@ -46,7 +43,6 @@ if (!function_exists('aksrt_header_slide_excluded_page_templates')) {
'templates/pages/page-verify-email.php',
'templates/pages/page-download.php',
'templates/pages/page-download-file.php',
'templates/pages/page-forums.php',
];
}
}
@@ -77,18 +73,6 @@ if (!function_exists('aksrt_header_slide_match_page_type')) {
return is_tax('topics');
}
if ($type === 'forum_home') {
return is_page_template('templates/pages/page-forums.php');
}
if ($type === 'forum_plate') {
return is_singular('aksrt_forum_plate') || $post_type === 'aksrt_forum_plate';
}
if ($type === 'forum_post') {
return is_singular('aksrt_forum_topic') || $post_type === 'aksrt_forum_topic';
}
// Generic WP_Query is_* flags (home/category/tag/author/single/search/page).
$query_flag = 'is_' . $type;
if (empty($wp_query->{$query_flag})) {
@@ -23,22 +23,6 @@ $aksrt_points_task_action = static function (string $key): array {
$submit = (string) aksrt_get_page_url_by_template('templates/pages/page-submit.php');
}
$forum_on = false;
$forum_home = '';
$forum_compose = '';
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_forum_settings')) {
$f = \AKSRT\Settings\Settings::get_forum_settings();
$forum_on = !empty($f['enabled']);
}
if ($forum_on) {
if (function_exists('aksrt_get_forum_home_url')) {
$forum_home = (string) aksrt_get_forum_home_url();
}
if (function_exists('aksrt_get_forum_compose_url')) {
$forum_compose = (string) aksrt_get_forum_compose_url();
}
}
switch ($key) {
case 'sign_in':
return [
@@ -93,37 +77,10 @@ $aksrt_points_task_action = static function (string $key): array {
'label' => __('去主页', 'aksrt'),
'href' => $center !== '' ? $center : $home,
];
case 'bbs_posts_new':
case 'bbs_reply':
if (!$forum_on) {
default:
return ['type' => 'none', 'label' => ''];
}
$href = $forum_compose !== '' ? $forum_compose : $forum_home;
if ($href === '') {
return ['type' => 'none', 'label' => ''];
}
return [
'type' => 'link',
'label' => __('去完成', 'aksrt'),
'href' => $href,
];
case 'bbs_essence':
case 'bbs_adopt':
case 'bbs_posts_hot':
case 'bbs_comment_hot':
case 'bbs_score_extra':
if (!$forum_on || $forum_home === '') {
return ['type' => 'none', 'label' => ''];
}
return [
'type' => 'link',
'label' => __('去看看', 'aksrt'),
'href' => $forum_home,
];
default:
return ['type' => 'none', 'label' => ''];
}
};
}
};
$user_id = (int) ($user_id ?? get_current_user_id());
$today = \AKSRT\Credits\PointsFree::today_total($user_id);
-3
View File
@@ -16,7 +16,6 @@ add_filter('query_vars', function ($vars) {
$vars[] = 'aksrt_user_center';
$vars[] = 'user_nicename';
$vars[] = 'aksrt_orders';
$vars[] = 'aksrt_cart';
$vars[] = 'aksrt_revenue';
return $vars;
});
@@ -40,13 +39,11 @@ add_action('init', function () {
add_rewrite_tag('%aksrt_user_center%', '([^&]+)');
add_rewrite_tag('%user_nicename%', '([^/]+)');
add_rewrite_tag('%aksrt_orders%', '([0-1])');
add_rewrite_tag('%aksrt_cart%', '([0-1])');
add_rewrite_tag('%aksrt_revenue%', '([0-1])');
add_rewrite_rule('^profile/?$', 'index.php?aksrt_user_center=1', 'top');
add_rewrite_rule('^user/([^/]+)/?$', 'index.php?user_nicename=$matches[1]', 'top');
// Commerce center aliases (work even before WP page is assigned).
add_rewrite_rule('^orders/?$', 'index.php?aksrt_orders=1', 'top');
add_rewrite_rule('^cart/?$', 'index.php?aksrt_cart=1', 'top');
add_rewrite_rule('^revenue/?$', 'index.php?aksrt_revenue=1', 'top');
});
@@ -137,20 +137,6 @@ $_aksrt_special_templates = [
'title' => '发布文章',
'fallback' => '/submit/',
],
'product-edit' => [
'file' => 'templates/pages/page-product-edit.php',
'slug' => 'product-edit',
'title' => '发布商品',
'fallback' => '/product-edit/',
'auto_create' => false,
],
'forum-compose' => [
'file' => 'templates/forum/forum-compose.php',
'slug' => 'forum-compose',
'title' => '论坛发帖',
'fallback' => '/forum-compose/',
'auto_create' => false,
],
];
/** @var array 缓存 key=模板文件路径 => value=WP_Post|null */
-11
View File
@@ -84,17 +84,6 @@ function aksrt_get_orders_page_url(): string {
}
endif;
if (!function_exists('aksrt_get_cart_page_url')) :
/**
* 购物车页面 URL(模板 templates/pages/page-cart.php)。
*/
function aksrt_get_cart_page_url(): string {
return function_exists('aksrt_get_template_url')
? aksrt_get_template_url('cart')
: home_url('/cart/');
}
endif;
if (!function_exists('aksrt_get_revenue_page_url')) :
/**
* 收益中心页面 URL(模板 templates/pages/page-revenue.php)。
-30
View File
@@ -92,36 +92,6 @@ function aksrt_register_widgets() {
register_widget('\AKSRT\Widgets\WidgetIconCoverCards');
register_widget('\AKSRT\Widgets\WidgetBookmarkLinks');
// Forum widgets
if (class_exists('\AKSRT\Widgets\WidgetForumBoardList')) {
register_widget('\AKSRT\Widgets\WidgetForumBoardList');
}
if (class_exists('\AKSRT\Widgets\WidgetForumHotTopics')) {
register_widget('\AKSRT\Widgets\WidgetForumHotTopics');
}
if (class_exists('\AKSRT\Widgets\WidgetForumRecentTopics')) {
register_widget('\AKSRT\Widgets\WidgetForumRecentTopics');
}
if (class_exists('\AKSRT\Widgets\WidgetForumPlateLists')) {
register_widget('\AKSRT\Widgets\WidgetForumPlateLists');
}
if (class_exists('\AKSRT\Widgets\WidgetForumPlateInfo')) {
register_widget('\AKSRT\Widgets\WidgetForumPlateInfo');
}
if (class_exists('\AKSRT\Widgets\WidgetForumPlateModerator')) {
register_widget('\AKSRT\Widgets\WidgetForumPlateModerator');
}
if (class_exists('\AKSRT\Widgets\WidgetForumTopicLists')) {
register_widget('\AKSRT\Widgets\WidgetForumTopicLists');
}
if (class_exists('\AKSRT\Widgets\WidgetForumTopicTerms')) {
register_widget('\AKSRT\Widgets\WidgetForumTopicTerms');
}
if (class_exists('\AKSRT\Widgets\WidgetForumTabTopics')) {
register_widget('\AKSRT\Widgets\WidgetForumTabTopics');
}
// Shop product widgets (§6.9)
register_widget('\AKSRT\Widgets\WidgetProductList');
register_widget('\AKSRT\Widgets\WidgetProductOneline');
-9
View File
@@ -1,9 +0,0 @@
<?php
/**
* Obsolete one-off script (pre-PSR-4). Forum class is now AKSRT\Forum\Forum under src/AKSRT/.
*
* @package AKSRT
*/
fwrite(STDERR, "slim-forum.php is obsolete after the PSR-4 migration.\n");
exit(1);
-2
View File
@@ -182,8 +182,6 @@ class Admin {
PaymentController::class,
ToolsController::class,
ImportExportController::class,
ShopController::class,
ForumController::class,
ExtensionController::class,
CommentController::class,
MessageController::class,
-411
View File
@@ -1,411 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin;
defined('ABSPATH') || exit;
class ForumController extends ControllerBase {
use ForumControllerPart1;
use ForumControllerPart2;
private static $instance = null;
private $admin;
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function set_admin(Admin $admin): void {
$this->admin = $admin;
}
public function register_routes(): void {
register_rest_route(self::API_NAMESPACE, '/forum/overview', [
'methods' => 'GET',
'callback' => [$this, 'get_overview'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/boards', [
'methods' => 'GET',
'callback' => [$this, 'get_boards'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/boards', [
'methods' => 'POST',
'callback' => [$this, 'create_board'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/boards/(?P<id>\d+)', [
'methods' => 'POST',
'callback' => [$this, 'update_board'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/boards/(?P<id>\d+)', [
'methods' => 'DELETE',
'callback' => [$this, 'delete_board'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/boards/reorder', [
'methods' => 'POST',
'callback' => [$this, 'reorder_boards'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/topics', [
'methods' => 'GET',
'callback' => [$this, 'get_topics'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/topics/(?P<id>\d+)', [
'methods' => 'GET',
'callback' => [$this, 'get_topic_detail'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/topics/(?P<id>\d+)/moderate', [
'methods' => 'POST',
'callback' => [$this, 'moderate_topic'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/topics/(?P<id>\d+)/vote', [
'methods' => 'POST',
'callback' => [$this, 'vote_topic'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/update-topic', [
'methods' => 'POST',
'callback' => [$this, 'update_topic_legacy'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/moderators', [
'methods' => 'GET',
'callback' => [$this, 'get_moderators'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/moderators', [
'methods' => 'POST',
'callback' => [$this, 'update_moderators'],
'permission_callback' => [$this, 'check_permission'],
]);
// Change moderator role
register_rest_route(self::API_NAMESPACE, '/forum/moderators/role', [
'methods' => 'POST',
'callback' => [$this, 'change_moderator_role'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/moderator/apply', [
'methods' => 'POST',
'callback' => [$this, 'apply_moderator'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/moderator/applications', [
'methods' => 'GET',
'callback' => [$this, 'get_moderator_applications'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/moderator/applications/(?P<id>[A-Za-z0-9-]+)/review', [
'methods' => 'POST',
'callback' => [$this, 'review_moderator_application'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/topics/(?P<id>\d+)/favorite', [
'methods' => 'POST',
'callback' => [$this, 'toggle_favorite'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/topics/(?P<id>\d+)/score', [
'methods' => 'POST',
'callback' => [$this, 'score_topic'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/topics/(?P<id>\d+)/adopt', [
'methods' => 'POST',
'callback' => [$this, 'adopt_answer'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
// §7.0 Plate CPT endpoints
register_rest_route(self::API_NAMESPACE, '/forum/plates', [
'methods' => 'GET',
'callback' => [$this, 'get_plates'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/plates', [
'methods' => 'POST',
'callback' => [$this, 'create_plate'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/plates/(?P<id>\d+)', [
'methods' => 'GET',
'callback' => [$this, 'get_plate'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/plates/(?P<id>\d+)', [
'methods' => 'POST',
'callback' => [$this, 'update_plate'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/plates/(?P<id>\d+)', [
'methods' => 'DELETE',
'callback' => [$this, 'delete_plate'],
'permission_callback' => [$this, 'check_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/plates/reorder', [
'methods' => 'POST',
'callback' => [$this, 'reorder_plates'],
'permission_callback' => [$this, 'check_permission'],
]);
// §7.2 Follow plate
register_rest_route(self::API_NAMESPACE, '/forum/plates/(?P<id>\d+)/follow', [
'methods' => 'POST',
'callback' => [$this, 'toggle_follow_plate'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
// §7.2 Plate categories (分区)
register_rest_route(self::API_NAMESPACE, '/forum/plate-categories', [
'methods' => 'GET',
'callback' => [$this, 'get_plate_categories'],
'permission_callback' => [$this, 'check_permission'],
]);
// §7.2 Sorted plates
register_rest_route(self::API_NAMESPACE, '/forum/plates/sorted', [
'methods' => 'GET',
'callback' => [$this, 'get_sorted_plates'],
'permission_callback' => '__return_true',
]);
// §7.2 Front-end plate CRUD (forum admin/moderator)
register_rest_route(self::API_NAMESPACE, '/forum/front/plates', [
'methods' => 'POST',
'callback' => [$this, 'frontend_create_plate'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/front/plates/(?P<id>\d+)', [
'methods' => 'POST',
'callback' => [$this, 'frontend_update_plate'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
register_rest_route(self::API_NAMESPACE, '/forum/front/plates/(?P<id>\d+)', [
'methods' => 'DELETE',
'callback' => [$this, 'frontend_delete_plate'],
'permission_callback' => [$this, 'check_logged_in_permission'],
]);
}
public function get_overview(): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->success(['data' => ['stats' => [], 'boards' => [], 'recent_topics' => [], 'pending_applications' => []]]);
}
$pending_topics = $forum->get_topic_list([
'limit' => 6,
'status' => 'pending',
'review_status' => \AKSRT\Forum\Forum::REVIEW_PENDING,
'viewer_user_id' => get_current_user_id(),
'include_hidden' => true,
]);
return $this->success([
'data' => [
'stats' => $forum->get_stats(),
'boards' => array_slice($forum->get_boards(), 0, 6),
'pending_topics' => $pending_topics['items'],
'pending_applications' => array_slice($forum->get_moderator_application_list(['status' => \AKSRT\Forum\Forum::APPLICATION_PENDING]), 0, 6),
],
]);
}
public function get_boards(): \WP_REST_Response {
$forum = $this->get_forum_service();
$items = $forum ? $forum->get_boards() : [];
return $this->success([
'data' => [
'items' => $items,
'total' => count($items),
],
]);
}
public function create_board(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$params = $this->get_json_params($request);
$name = sanitize_text_field((string) ($params['name'] ?? ''));
if ($name === '') {
return $this->error(__('版块名称不能为空', 'aksrt'));
}
$term_id = $forum->create_board($params);
if ($term_id <= 0) {
return $this->error(__('创建版块失败', 'aksrt'));
}
return $this->success([
'message' => __('版块已创建', 'aksrt'),
'data' => ['id' => $term_id],
], 201);
}
public function update_board(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$board_id = (int) $request->get_param('id');
if ($board_id <= 0) {
return $this->error(__('参数无效', 'aksrt'));
}
$success = $forum->update_board($board_id, $this->get_json_params($request));
if (!$success) {
return $this->error(__('版块更新失败', 'aksrt'));
}
return $this->success(['message' => __('版块已更新', 'aksrt')]);
}
public function delete_board(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$board_id = (int) $request->get_param('id');
if ($board_id <= 0) {
return $this->error(__('参数无效', 'aksrt'));
}
$success = $forum->delete_board($board_id);
if (!$success) {
return $this->error(__('版块删除失败', 'aksrt'));
}
return $this->success(['message' => __('版块已删除', 'aksrt')]);
}
public function reorder_boards(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$params = $this->get_json_params($request);
$order = $params['order'] ?? [];
if (!is_array($order) || empty($order)) {
return $this->error(__('排序参数无效', 'aksrt'));
}
$forum->reorder_boards($order);
return $this->success(['message' => __('排序已更新', 'aksrt')]);
}
// ================================================================
// §7.0 Plate CPT handlers
// ================================================================
public function get_plates(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->success(['data' => ['items' => [], 'total' => 0]]);
}
$args = [];
$parent = $request->get_param('parent');
if ($parent !== null) {
$args['parent'] = (int) $parent;
}
$plate_cat = $request->get_param('plate_cat');
if ($plate_cat) {
$args['plate_cat'] = (int) $plate_cat;
}
$items = $forum->get_plates($args);
return $this->success([
'data' => [
'items' => $items,
'total' => count($items),
],
]);
}
public function get_plate(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$plate_id = (int) $request->get_param('id');
$plate = $forum->get_plate($plate_id);
if (!$plate) {
return $this->error(__('版块不存在', 'aksrt'), 404);
}
return $this->success(['data' => $plate]);
}
public function create_plate(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$params = $this->get_json_params($request);
$name = sanitize_text_field((string) ($params['name'] ?? ''));
if ($name === '') {
return $this->error(__('版块名称不能为空', 'aksrt'));
}
$plate_id = $forum->create_plate($params);
if ($plate_id <= 0) {
return $this->error(__('创建版块失败', 'aksrt'));
}
return $this->success([
'message' => __('版块已创建', 'aksrt'),
'data' => ['id' => $plate_id],
], 201);
}
}
-390
View File
@@ -1,390 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin;
defined('ABSPATH') || exit;
trait ForumControllerPart1 {
public function update_plate(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$plate_id = (int) $request->get_param('id');
$params = $this->get_json_params($request);
$result = $forum->update_plate($plate_id, $params);
if (!$result) {
return $this->error(__('更新版块失败', 'aksrt'));
}
return $this->success(['message' => __('版块已更新', 'aksrt')]);
}
public function delete_plate(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$plate_id = (int) $request->get_param('id');
$params = $this->get_json_params($request);
$cascade = sanitize_key((string) ($params['cascade'] ?? 'trash'));
$move_to = (int) ($params['move_to'] ?? 0);
$result = $forum->delete_plate($plate_id, $cascade, $move_to);
if (!$result) {
return $this->error(__('删除版块失败', 'aksrt'));
}
return $this->success(['message' => __('版块已删除', 'aksrt')]);
}
public function reorder_plates(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$params = $this->get_json_params($request);
$order = $params['order'] ?? [];
if (!is_array($order) || empty($order)) {
return $this->error(__('排序参数无效', 'aksrt'));
}
$forum->reorder_plates($order);
return $this->success(['message' => __('排序已更新', 'aksrt')]);
}
public function toggle_follow_plate(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$plate_id = (int) $request->get_param('id');
$user_id = get_current_user_id();
$result = $forum->toggle_follow_plate($user_id, $plate_id);
return $this->success($result);
}
public function get_plate_categories(): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->success(['data' => []]);
}
return $this->success(['data' => $forum->get_plate_categories()]);
}
public function get_sorted_plates(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->success(['data' => []]);
}
$orderby = sanitize_key((string) ($request->get_param('orderby') ?: 'menu_order'));
$order = sanitize_key((string) ($request->get_param('order') ?: 'ASC'));
$limit = max(1, min(100, (int) ($request->get_param('limit') ?: 50)));
return $this->success(['data' => $forum->get_sorted_plates($orderby, $order, $limit)]);
}
public function get_topics(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'total_pages' => 1]]);
}
$args = [
'page' => max(1, (int) ($request->get_param('page') ?: 1)),
'per_page' => max(1, min(100, (int) ($request->get_param('per_page') ?: 20))),
'status' => sanitize_key((string) ($request->get_param('status') ?: 'all')),
'board_id' => (int) ($request->get_param('board_id') ?: 0),
'visibility' => sanitize_key((string) ($request->get_param('visibility') ?: '')),
'review_status' => sanitize_key((string) ($request->get_param('review_status') ?: '')),
'keyword' => sanitize_text_field((string) ($request->get_param('keyword') ?: '')),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?: 'date')),
'viewer_user_id' => get_current_user_id(),
'include_hidden' => true,
];
$view = $forum->get_topic_list($args);
return $this->success([
'data' => [
'items' => $view['items'],
'total' => $view['total'],
'page' => $view['page'],
'total_pages' => $view['max_pages'],
'options' => [
'boards' => $forum->get_boards(),
'visibility' => $forum->get_visibility_options(),
'review' => $forum->get_review_options(),
'status' => [
['label' => __('全部', 'aksrt'), 'value' => 'all'],
['label' => __('已发布', 'aksrt'), 'value' => 'publish'],
['label' => __('待审核', 'aksrt'), 'value' => 'pending'],
['label' => __('草稿/拒绝', 'aksrt'), 'value' => 'draft'],
],
],
],
]);
}
public function get_topic_detail(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$topic_id = (int) $request->get_param('id');
if ($topic_id <= 0) {
return $this->error(__('参数无效', 'aksrt'));
}
return $this->success([
'data' => $forum->get_topic_detail($topic_id, get_current_user_id()),
]);
}
public function moderate_topic(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$topic_id = (int) $request->get_param('id');
$params = $this->get_json_params($request);
$action = sanitize_key((string) ($params['action'] ?? ''));
if ($topic_id <= 0 || $action === '') {
return $this->error(__('参数无效', 'aksrt'));
}
$result = $forum->moderate_topic($topic_id, $action, $params, get_current_user_id());
if (is_wp_error($result)) {
return $this->error($result->get_error_message(), $result->get_error_code() === 'forbidden' ? 403 : 400);
}
return $this->success([
'message' => $result['message'] ?? __('治理动作已执行', 'aksrt'),
'data' => $result,
]);
}
public function vote_topic(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$topic_id = (int) $request->get_param('id');
$params = $this->get_json_params($request);
$option_ids = $params['option_ids'] ?? [];
if (!is_array($option_ids)) {
$option_ids = [];
}
$result = $forum->submit_vote($topic_id, get_current_user_id(), $option_ids);
if (is_wp_error($result)) {
$status = $result->get_error_code() === 'not_logged_in' ? 401 : 400;
return $this->error($result->get_error_message(), $status);
}
return $this->success([
'message' => $result['message'] ?? __('投票成功', 'aksrt'),
'data' => $result,
]);
}
/**
* Legacy admin moderation endpoint kept for compatibility.
*/
public function update_topic_legacy(\WP_REST_Request $request): \WP_REST_Response {
$params = $this->get_json_params($request);
$topic_id = (int) ($params['topic_id'] ?? 0);
if ($topic_id <= 0) {
return $this->error(__('参数无效', 'aksrt'));
}
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$actions = [];
if (isset($params['sticky'])) {
$actions[] = ['action' => 'sticky', 'value' => !empty($params['sticky'])];
}
if (isset($params['featured'])) {
$actions[] = ['action' => 'featured', 'value' => !empty($params['featured'])];
}
if (isset($params['locked'])) {
$actions[] = ['action' => 'locked', 'value' => !empty($params['locked'])];
}
foreach ($actions as $action) {
$result = $forum->moderate_topic($topic_id, $action['action'], ['value' => $action['value']], get_current_user_id());
if (is_wp_error($result)) {
return $this->error($result->get_error_message());
}
}
return $this->success(['message' => __('治理状态已更新', 'aksrt')]);
}
public function get_moderators(): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->success(['data' => ['boards' => []]]);
}
$board_id = isset($_GET['board_id']) ? (int) $_GET['board_id'] : 0;
$result = [];
foreach ($forum->get_boards() as $board) {
$current_board_id = (int) ($board['id'] ?? 0);
if ($board_id > 0 && $current_board_id !== $board_id) {
continue;
}
$result[] = [
'board_id' => $current_board_id,
'board_name' => (string) ($board['name'] ?? ''),
'topic_count' => (int) ($board['post_count'] ?? 0),
'pending_application_count' => (int) ($board['pending_application_count'] ?? 0),
'moderators' => $forum->get_board_moderator_profiles_with_roles($current_board_id),
];
}
return $this->success(['data' => ['boards' => $result]]);
}
public function update_moderators(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$params = $this->get_json_params($request);
$board_id = (int) ($params['board_id'] ?? 0);
$user_id = (int) ($params['user_id'] ?? 0);
$action = sanitize_key((string) ($params['action'] ?? ''));
$role = sanitize_key((string) ($params['role'] ?? 'moderator'));
if ($board_id <= 0 || $user_id <= 0 || !in_array($action, ['add', 'remove'], true)) {
return $this->error(__('参数无效', 'aksrt'));
}
$valid_roles = ['super_moderator', 'moderator', 'junior_moderator'];
if (!in_array($role, $valid_roles, true)) {
$role = 'moderator';
}
$board = get_term($board_id, 'aksrt_forum_board');
if (!$board || is_wp_error($board)) {
return $this->error(__('版块不存在', 'aksrt'), 404);
}
$user = get_userdata($user_id);
if (!$user) {
return $this->error(__('用户不存在', 'aksrt'), 404);
}
if ($action === 'add') {
$forum->add_moderator($user_id, $board_id, $role);
} else {
$forum->remove_moderator($user_id, $board_id);
}
return $this->success([
'message' => $action === 'add' ? __('版主已添加', 'aksrt') : __('版主已移除', 'aksrt'),
]);
}
public function change_moderator_role(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$params = $this->get_json_params($request);
$board_id = (int) ($params['board_id'] ?? 0);
$user_id = (int) ($params['user_id'] ?? 0);
$role = sanitize_key((string) ($params['role'] ?? ''));
if ($board_id <= 0 || $user_id <= 0 || !$role) {
return $this->error(__('参数无效', 'aksrt'));
}
$valid_roles = ['super_moderator', 'moderator', 'junior_moderator'];
if (!in_array($role, $valid_roles, true)) {
return $this->error(__('无效的角色', 'aksrt'));
}
$result = $forum->set_moderator_role($user_id, $board_id, $role);
if (!$result) {
return $this->error(__('设置角色失败,用户可能不是该版块版主', 'aksrt'));
}
return $this->success(['message' => __('角色已更新', 'aksrt')]);
}
public function apply_moderator(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$params = $this->get_json_params($request);
$board_id = (int) ($params['board_id'] ?? 0);
$reason = sanitize_textarea_field((string) ($params['reason'] ?? ''));
$result = $forum->apply_moderator(get_current_user_id(), $board_id, $reason);
if (is_wp_error($result)) {
$status = $result->get_error_code() === 'not_logged_in' ? 401 : 400;
return $this->error($result->get_error_message(), $status);
}
return $this->success([
'message' => $result['message'] ?? __('申请已提交', 'aksrt'),
'data' => $result,
]);
}
public function get_moderator_applications(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->success(['data' => ['items' => [], 'total' => 0]]);
}
$items = $forum->get_moderator_application_list([
'status' => sanitize_key((string) ($request->get_param('status') ?: '')),
'board_id' => (int) ($request->get_param('board_id') ?: 0),
'keyword' => sanitize_text_field((string) ($request->get_param('keyword') ?: '')),
]);
return $this->success([
'data' => [
'items' => $items,
'total' => count($items),
'options' => [
'boards' => $forum->get_boards(),
'status' => [
['label' => __('全部', 'aksrt'), 'value' => ''],
['label' => __('待处理', 'aksrt'), 'value' => \AKSRT\Forum\Forum::APPLICATION_PENDING],
['label' => __('已通过', 'aksrt'), 'value' => \AKSRT\Forum\Forum::APPLICATION_APPROVED],
['label' => __('已拒绝', 'aksrt'), 'value' => \AKSRT\Forum\Forum::APPLICATION_REJECTED],
],
],
],
]);
}
}
-188
View File
@@ -1,188 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin;
defined('ABSPATH') || exit;
trait ForumControllerPart2 {
public function review_moderator_application(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$application_id = (string) $request->get_param('id');
$params = $this->get_json_params($request);
$status = sanitize_key((string) ($params['status'] ?? ''));
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
$result = $forum->review_moderator_application($application_id, $status, $note, get_current_user_id());
if (is_wp_error($result)) {
return $this->error($result->get_error_message());
}
return $this->success([
'message' => $result['message'] ?? __('申请状态已更新', 'aksrt'),
'data' => $result,
]);
}
public function toggle_favorite(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$topic_id = (int) $request->get_param('id');
$result = $forum->toggle_favorite(get_current_user_id(), $topic_id);
if (is_wp_error($result)) {
$status = $result->get_error_code() === 'not_logged_in' ? 401 : 400;
return $this->error($result->get_error_message(), $status);
}
return $this->success($result);
}
public function score_topic(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$topic_id = (int) $request->get_param('id');
$params = $this->get_json_params($request);
$amount = (int) ($params['amount'] ?? 0);
$result = $forum->score_topic(get_current_user_id(), $topic_id, $amount);
if (is_wp_error($result)) {
$status = $result->get_error_code() === 'not_logged_in' ? 401 : 400;
return $this->error($result->get_error_message(), $status);
}
return $this->success($result);
}
public function adopt_answer(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$topic_id = (int) $request->get_param('id');
$params = $this->get_json_params($request);
$comment_id = (int) ($params['comment_id'] ?? 0);
$result = $forum->adopt_answer($topic_id, $comment_id, get_current_user_id());
if (is_wp_error($result)) {
$status = $result->get_error_code() === 'forbidden' ? 403 : 400;
return $this->error($result->get_error_message(), $status);
}
return $this->success($result);
}
public function check_logged_in_permission(): bool {
return is_user_logged_in();
}
private function get_forum_service(): ?\AKSRT\Forum\Forum {
return class_exists('\AKSRT\Forum\Forum') ? \AKSRT\Forum\Forum::get_instance() : null;
}
public function frontend_create_plate(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$user_id = get_current_user_id();
if (!$forum->is_forum_admin($user_id)) {
return $this->error(__('仅论坛管理员可创建版块', 'aksrt'), 403);
}
$params = $this->get_json_params($request);
$data = [
'name' => sanitize_text_field((string) ($params['name'] ?? '')),
'description' => sanitize_textarea_field((string) ($params['description'] ?? '')),
'icon' => sanitize_text_field((string) ($params['icon'] ?? '')),
'cover_url' => esc_url_raw((string) ($params['cover_url'] ?? '')),
'parent_id' => (int) ($params['parent_id'] ?? 0),
'plate_cat' => (int) ($params['plate_cat'] ?? 0),
];
if ($data['name'] === '') {
return $this->error(__('版块名称不能为空', 'aksrt'));
}
$result = $forum->create_plate($data);
if (is_wp_error($result)) {
return $this->error($result->get_error_message());
}
return $this->success($result);
}
public function frontend_update_plate(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$user_id = get_current_user_id();
$plate_id = (int) $request->get_param('id');
$post = get_post($plate_id);
if (!$post || $post->post_type !== 'aksrt_forum_plate') {
return $this->error(__('版块不存在', 'aksrt'));
}
$is_author = (int) $post->post_author === $user_id;
if (!$is_author && !$forum->is_forum_admin($user_id)) {
return $this->error(__('无权编辑此版块', 'aksrt'), 403);
}
$params = $this->get_json_params($request);
$data = [
'name' => sanitize_text_field((string) ($params['name'] ?? '')),
'description' => sanitize_textarea_field((string) ($params['description'] ?? '')),
'icon' => sanitize_text_field((string) ($params['icon'] ?? '')),
'cover_url' => esc_url_raw((string) ($params['cover_url'] ?? '')),
];
$result = $forum->update_plate($plate_id, $data);
if (is_wp_error($result)) {
return $this->error($result->get_error_message());
}
return $this->success($result);
}
public function frontend_delete_plate(\WP_REST_Request $request): \WP_REST_Response {
$forum = $this->get_forum_service();
if (!$forum) {
return $this->error(__('论坛系统未启用', 'aksrt'));
}
$user_id = get_current_user_id();
if (!$forum->is_forum_admin($user_id)) {
return $this->error(__('仅论坛管理员可删除版块', 'aksrt'), 403);
}
$plate_id = (int) $request->get_param('id');
$params = $this->get_json_params($request);
$cascade = sanitize_key((string) ($params['cascade'] ?? 'trash'));
$move_to = (int) ($params['move_to'] ?? 0);
$result = $forum->delete_plate($plate_id, $cascade, $move_to);
if (!$result) {
return $this->error(__('删除版块失败', 'aksrt'));
}
return $this->success(['message' => __('版块已删除', 'aksrt')]);
}
}
+1 -1
View File
@@ -185,7 +185,7 @@ trait GlobalControllerPart1 {
}
private function sanitize_floating_button_item(array $item): array {
$valid_types = ['back_top', 'theme_mode', 'qrcode', 'service_qq', 'service_wechat', 'pay_vip', 'custom', 'add', 'shop_cart'];
$valid_types = ['back_top', 'theme_mode', 'qrcode', 'service_qq', 'service_wechat', 'pay_vip', 'custom', 'add'];
$type = sanitize_text_field($item['type'] ?? 'custom');
if (!in_array($type, $valid_types, true)) {
$type = 'custom';
-1
View File
@@ -92,7 +92,6 @@ class PayOrders {
'vip' => __('VIP', 'aksrt'),
'recharge' => __('充值', 'aksrt'),
'shop_product' => __('商城商品', 'aksrt'),
'plate_follow' => __('版块关注', 'aksrt'),
];
echo '<ul class="subsubsub" style="float:none;margin:8px 0 12px;">';
$tab_bits = [];
+1 -1
View File
@@ -573,7 +573,7 @@ class UsersMenus {
}
echo '</select></label></p>';
echo '<p><label>' . esc_html__('范围', 'aksrt') . '<br><select name="scope">';
foreach (['global' => __('全局', 'aksrt'), 'forum' => __('论坛', 'aksrt'), 'shop' => __('商城', 'aksrt'), 'comment' => __('评论', 'aksrt')] as $key => $label) {
foreach (['global' => __('全局', 'aksrt'), 'comment' => __('评论', 'aksrt')] as $key => $label) {
printf('<option value="%s">%s</option>', esc_attr($key), esc_html($label));
}
echo '</select></label></p>';
@@ -28,7 +28,6 @@ class SchemaExtensionsAudit {
['key' => 'scope', 'title' => '审核范围', 'fields' => [
['key' => 'content_audit_text_enabled', 'label' => '文本审核', 'type' => 'switch', 'default' => true],
['key' => 'content_audit_image_enabled', 'label' => '图片审核', 'type' => 'switch', 'default' => false],
['key' => 'audit_bbs_term', 'label' => '审核论坛分类', 'type' => 'switch', 'default' => false],
]],
['key' => 'fallback', 'title' => '失败策略', 'fields' => [
['key' => 'content_audit_on_failure', 'label' => '审核失败时', 'type' => 'radio', 'default' => 'allow', 'options' => [
@@ -81,9 +81,6 @@ class SchemaExtensionsSecurity {
['key' => 'audit_msg_private', 'label' => '审核私信', 'type' => 'switch', 'default' => false],
['key' => 'audit_be_like_text', 'label' => '文本允许疑似合规', 'type' => 'switch', 'default' => false],
['key' => 'audit_be_like_img', 'label' => '图片允许疑似合规', 'type' => 'switch', 'default' => false],
['key' => 'audit_bbs_posts', 'label' => '审核论坛帖子', 'type' => 'switch', 'default' => false],
['key' => 'audit_bbs_plate', 'label' => '审核论坛板块', 'type' => 'switch', 'default' => false],
['key' => 'audit_bbs_term', 'label' => '审核论坛分类', 'type' => 'switch', 'default' => false],
]],
],
];
-126
View File
@@ -1,126 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Schema;
defined('ABSPATH') || exit;
class SchemaForum {
public static function build(): array {
return [
'domain' => 'forum',
'title' => '社区&论坛',
'sections' => [
'basic' => SchemaForumBasic::build(),
'names' => SchemaForumNames::build(),
'homepage' => SchemaForumHomepage::build(),
'board_page' => SchemaForumBoardPage::build(),
'post_page' => SchemaForumPostPage::build(),
'reply' => SchemaForumReply::build(),
'permissions' => SchemaForumPermissions::build(),
'upload' => SchemaForumUpload::build(),
'credits' => SchemaForumCredits::build(),
'other' => SchemaForumOther::build(),
],
];
}
// ================================================================
// §8.1 Capability matrix schema builder
// ================================================================
/**
* Build groups for the capabilities section from cap_definitions().
*
* Each capability becomes a group with role columns as fields.
* Grouped by prefix: user_, new_post_, comment_.
*/
private static function build_cap_matrix_groups(): array {
if (!class_exists('\AKSRT\Settings\Settings')) {
return [];
}
$defs = \AKSRT\Settings\Settings::cap_definitions();
$cols = \AKSRT\Settings\Settings::cap_role_columns();
$col_labels = [
'all' => __('所有人', 'aksrt'),
'logged' => __('已登录', 'aksrt'),
'level' => __('等级≥', 'aksrt'),
'vip' => __('VIP≥', 'aksrt'),
'auth' => __('已认证', 'aksrt'),
'moderator' => __('版主', 'aksrt'),
'plate_author' => __('超级版主', 'aksrt'),
'cat_moderator' => __('分区版主', 'aksrt'),
];
// Group caps by prefix
$buckets = [
'user' => ['title' => __('用户管理', 'aksrt'), 'caps' => []],
'post' => ['title' => __('投稿权限', 'aksrt'), 'caps' => []],
'comment' => ['title' => __('评论权限', 'aksrt'), 'caps' => []],
];
foreach ($defs as $cap_id => $def) {
if (str_starts_with($cap_id, 'comment_')) {
$buckets['comment']['caps'][$cap_id] = $def;
} elseif (str_starts_with($cap_id, 'new_post_')) {
$buckets['post']['caps'][$cap_id] = $def;
} else {
$buckets['user']['caps'][$cap_id] = $def;
}
}
$groups = [];
foreach ($buckets as $bucket_key => $bucket) {
if (empty($bucket['caps'])) continue;
$fields = [];
foreach ($bucket['caps'] as $cap_id => $def) {
$excludes = $def['exclude_roles'] ?? [];
$only_roles = $def['roles'] ?? null;
$defaults = $def['default'] ?? [];
// Each cap gets one field per role column
foreach ($cols as $col) {
if (in_array($col, $excludes, true)) continue;
if ($only_roles !== null && !in_array($col, $only_roles, true)
&& !in_array($col, ['all', 'logged', 'level', 'vip', 'auth'], true)) {
continue;
}
$field_key = 'user_caps.' . $cap_id . '.' . $col;
$is_spinner = in_array($col, ['level', 'vip'], true);
$field = [
'key' => $field_key,
'label' => $def['label'] . ' — ' . ($col_labels[$col] ?? $col),
'type' => $is_spinner ? 'number' : 'switch',
'default' => $defaults[$col] ?? ($is_spinner ? -1 : false),
'cap_id' => $cap_id,
'cap_col' => $col,
];
if ($is_spinner) {
$field['min'] = -1;
$field['max'] = $col === 'level' ? 10 : 2;
$field['help'] = __('-1 = 不限', 'aksrt');
}
$fields[] = $field;
}
}
$groups[] = [
'key' => 'cap_' . $bucket_key,
'title' => $bucket['title'],
'fields' => $fields,
];
}
return $groups;
}
}
@@ -1,28 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Schema;
defined('ABSPATH') || exit;
class SchemaForumBasic {
public static function build(): array {
return [
'key' => 'basic',
'title' => '基础设置',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
['key' => 'switch', 'title' => '功能开关', 'fields' => [
['key' => 'bbs_s', 'label' => '启用论坛', 'type' => 'switch', 'default' => true, 'help' => '关闭后论坛页面将返回 404'],
]],
['key' => 'admin', 'title' => '论坛管理', 'fields' => [
['key' => 'bbs_admin_users', 'label' => '论坛管理员', 'type' => 'user_select', 'multiple' => true, 'default' => [], 'help' => '拥有跨版块全部管理权限,绕过 ban'],
]],
],
];
}
}
@@ -1,34 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Schema;
defined('ABSPATH') || exit;
class SchemaForumBoardPage {
public static function build(): array {
return [
'key' => 'board_page',
'title' => '版块页面',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
['key' => 'layout', 'title' => '版块页布局', 'fields' => [
['key' => 'bbs_plate_top_info_s', 'label' => '版块信息卡', 'type' => 'switch', 'default' => true, 'help' => '版块页顶部显示版块详情卡片'],
['key' => 'bbs_plate_tab', 'label' => '版块栏目', 'type' => 'group', 'default' => [
['label' => '全部', 'sort' => 'date'],
['label' => '推荐', 'sort' => 'featured'],
['label' => '最新回复', 'sort' => 'last_reply'],
['label' => '热门', 'sort' => 'hot'],
['label' => '精华', 'sort' => 'essence'],
]],
['key' => 'bbs_plate_tab_active_index', 'label' => '默认选中 Tab', 'type' => 'number', 'default' => 0, 'min' => 0],
['key' => 'bbs_plate_tab_swiper', 'label' => '移动端滑动切换', 'type' => 'switch', 'default' => true],
]],
],
];
}
}
@@ -1,34 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Schema;
defined('ABSPATH') || exit;
class SchemaForumCredits {
public static function build(): array {
return [
'key' => 'credits',
'title' => '积分设置',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
['key' => 'earn', 'title' => '积分奖励', 'fields' => [
['key' => 'bbs_credits_post_new', 'label' => '发帖奖励', 'type' => 'number', 'default' => 5, 'min' => 0],
['key' => 'bbs_credits_plate_new', 'label' => '建版奖励', 'type' => 'number', 'default' => 10, 'min' => 0],
['key' => 'bbs_credits_comment_new', 'label' => '回复奖励', 'type' => 'number', 'default' => 2, 'min' => 0],
['key' => 'bbs_credits_comment_like', 'label' => '评论被赞', 'type' => 'number', 'default' => 1, 'min' => 0],
['key' => 'bbs_credits_score_extra', 'label' => '被加分', 'type' => 'number', 'default' => 3, 'min' => 0],
['key' => 'bbs_credits_essence', 'label' => '被设精华', 'type' => 'number', 'default' => 10, 'min' => 0],
['key' => 'bbs_credits_posts_hot', 'label' => '成为热帖', 'type' => 'number', 'default' => 5, 'min' => 0],
['key' => 'bbs_hot_recommend_threshold', 'label' => '热帖推荐分阈值', 'type' => 'number', 'default' => 500, 'min' => 1, 'help' => '推荐分达到该值触发热帖奖励与勋章累计'],
['key' => 'bbs_credits_adopted', 'label' => '被采纳', 'type' => 'number', 'default' => 20, 'min' => 0],
['key' => 'bbs_credits_daily_max', 'label' => '每日积分上限', 'type' => 'number', 'default' => 50, 'min' => 0, 'help' => '0 = 不限'],
]],
],
];
}
}
@@ -1,32 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Schema;
defined('ABSPATH') || exit;
class SchemaForumHomepage {
public static function build(): array {
return [
'key' => 'homepage',
'title' => '首页设置',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
['key' => 'tabs', 'title' => '首页 Tab', 'fields' => [
['key' => 'bbs_home_tab', 'label' => '首页栏目', 'type' => 'group', 'default' => [
['type' => 'follow', 'label' => '关注'],
['type' => 'synthesis', 'label' => '综合'],
['type' => 'plate', 'label' => '版块'],
], 'help' => '可拖拽排序,支持 follow / synthesis / plate / tabs 类型'],
['key' => 'bbs_home_tab_active_index', 'label' => '默认选中 Tab', 'type' => 'number', 'default' => 1, 'min' => 0],
['key' => 'bbs_home_tab_quick_posts_s', 'label' => '快速发帖', 'type' => 'switch', 'default' => true, 'help' => '首页顶部显示快速发帖入口'],
['key' => 'bbs_home_tab_swiper', 'label' => '移动端滑动切换', 'type' => 'switch', 'default' => true],
]],
],
];
}
}
@@ -1,36 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Schema;
defined('ABSPATH') || exit;
class SchemaForumNames {
public static function build(): array {
return [
'key' => 'names',
'title' => '名称定义',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
['key' => 'display_names', 'title' => '名词自定义', 'fields' => [
['key' => 'bbs_forum_display_name', 'label' => '论坛', 'type' => 'text', 'default' => '论坛'],
['key' => 'bbs_plate_display_name', 'label' => '版块', 'type' => 'text', 'default' => '版块'],
['key' => 'bbs_posts_display_name', 'label' => '帖子', 'type' => 'text', 'default' => '帖子'],
['key' => 'bbs_topic_display_name', 'label' => '话题', 'type' => 'text', 'default' => '话题'],
['key' => 'bbs_tag_display_name', 'label' => '标签', 'type' => 'text', 'default' => '标签'],
['key' => 'bbs_comment_display_name', 'label' => '回复', 'type' => 'text', 'default' => '回复'],
['key' => 'bbs_question_badge_name', 'label' => '提问徽章', 'type' => 'text', 'default' => '提问'],
['key' => 'bbs_question_ok_badge_name', 'label' => '已解决徽章', 'type' => 'text', 'default' => '已解决'],
['key' => 'bbs_cat_moderator_name', 'label' => '分区版主', 'type' => 'text', 'default' => '分区版主'],
['key' => 'bbs_plate_author_name', 'label' => '超级版主', 'type' => 'text', 'default' => '超级版主'],
['key' => 'bbs_plate_moderator_name', 'label' => '版主', 'type' => 'text', 'default' => '版主'],
['key' => 'bbs_plate_follow_name', 'label' => '关注', 'type' => 'text', 'default' => '关注'],
]],
],
];
}
}
@@ -1,46 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Schema;
defined('ABSPATH') || exit;
class SchemaForumOther {
public static function build(): array {
return [
'key' => 'other',
'title' => '其它设置',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
['key' => 'vote', 'title' => '投票', 'fields' => [
['key' => 'bbs_vote_number_type', 'label' => '投票显示', 'type' => 'select', 'default' => 'percent', 'options' => [
['label' => '百分比', 'value' => 'percent'],
['label' => '票数', 'value' => 'number'],
]],
['key' => 'bbs_vote_max', 'label' => '选项上限', 'type' => 'number', 'default' => 8, 'min' => 2, 'max' => 20],
]],
['key' => 'moderator_apply', 'title' => '版主申请', 'fields' => [
['key' => 'bbs_apply_moderator_desc', 'label' => '申请说明', 'type' => 'textarea', 'default' => '欢迎有热情的用户申请成为版主,协助管理社区秩序。'],
]],
['key' => 'misc', 'title' => '杂项', 'fields' => [
['key' => 'bbs_term_thumb', 'label' => '标签默认缩略图', 'type' => 'media', 'default' => ''],
['key' => 'bbs_post_lists_hide_pay_s', 'label' => '列表隐藏付费标记', 'type' => 'switch', 'default' => false],
['key' => 'bbs_post_title_strlen_limit', 'label' => '标题字数限制', 'type' => 'number', 'default' => 30, 'min' => 5, 'max' => 100, 'help' => '最小 5,最大 100'],
['key' => 'bbs_t_placeholder', 'label' => '标题占位符', 'type' => 'text', 'default' => '请输入标题'],
['key' => 'bbs_c_placeholder', 'label' => '内容占位符', 'type' => 'text', 'default' => '请输入内容'],
]],
['key' => 'url', 'title' => 'URL / 别名', 'fields' => [
['key' => 'bbs_rewrite_suffix_html_s', 'label' => '.html 后缀', 'type' => 'switch', 'default' => true],
['key' => 'bbs_plate_rewrite_slug', 'label' => '版块别名', 'type' => 'text', 'default' => 'board'],
['key' => 'bbs_posts_rewrite_slug', 'label' => '帖子别名', 'type' => 'text', 'default' => 'topic'],
['key' => 'bbs_posts_edit_rewrite_slug', 'label' => '编辑别名', 'type' => 'text', 'default' => 'topic-edit'],
['key' => 'bbs_posts_show_in_rest_s', 'label' => '古腾堡编辑器', 'type' => 'switch', 'default' => false, 'help' => '开启后帖子可使用 Gutenberg'],
]],
],
];
}
}
@@ -1,46 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Schema;
defined('ABSPATH') || exit;
class SchemaForumPermissions {
public static function build(): array {
return [
'key' => 'permissions',
'title' => '权限配置',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
['key' => 'posting', 'title' => '发帖限制', 'fields' => [
['key' => 'bbs_posts_add_limit_opt_max', 'label' => '每日发帖上限', 'type' => 'number', 'default' => 10, 'min' => 0, 'help' => '0 = 不限'],
['key' => 'bbs_plate_add_limit_opt_max', 'label' => '建版数量上限', 'type' => 'number', 'default' => 3, 'min' => 0],
]],
['key' => 'display_btns', 'title' => '按钮显示', 'fields' => [
['key' => 'bbs_show_new_plate', 'label' => '始终显示建版按钮', 'type' => 'switch', 'default' => true],
['key' => 'bbs_show_new_topic', 'label' => '始终显示发帖按钮', 'type' => 'switch', 'default' => true],
['key' => 'bbs_show_apply_moderator', 'label' => '始终显示申请版主', 'type' => 'switch', 'default' => true],
]],
['key' => 'paid_content', 'title' => '帖子付费', 'fields' => [
['key' => 'bbs_post_pay_hide_type_s', 'label' => '启用付费内容', 'type' => 'switch', 'default' => true],
['key' => 'bbs_post_pay_vip_price_s', 'label' => 'VIP 特价', 'type' => 'switch', 'default' => true],
['key' => 'bbs_post_pay_vip_1_discount', 'label' => '一级 VIP 折扣 (%)', 'type' => 'number', 'default' => 80, 'min' => 0, 'max' => 100],
['key' => 'bbs_post_pay_vip_2_discount', 'label' => '二级 VIP 折扣 (%)', 'type' => 'number', 'default' => 50, 'min' => 0, 'max' => 100],
['key' => 'bbs_post_pay_price_limit', 'label' => '最高定价', 'type' => 'number', 'default' => 1000, 'min' => 1],
]],
['key' => 'plate_follow', 'title' => '版块付费关注', 'fields' => [
['key' => 'bbs_plate_follow_pay_s', 'label' => '启用付费关注', 'type' => 'switch', 'default' => false],
['key' => 'bbs_plate_follow_pay_price', 'label' => '关注价格', 'type' => 'number', 'default' => 10, 'min' => 0],
['key' => 'bbs_plate_follow_pay_type', 'label' => '支付方式', 'type' => 'select', 'default' => 'money', 'options' => [
['label' => '现金', 'value' => 'money'],
['label' => '积分', 'value' => 'points'],
]],
]],
],
];
}
}
@@ -1,46 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Schema;
defined('ABSPATH') || exit;
class SchemaForumPostPage {
public static function build(): array {
return [
'key' => 'post_page',
'title' => '帖子页面',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
['key' => 'breadcrumbs', 'title' => '面包屑', 'fields' => [
['key' => 'bbs_breadcrumbs_s', 'label' => '显示面包屑', 'type' => 'switch', 'default' => true],
['key' => 'bbs_breadcrumbs_home', 'label' => '显示首页', 'type' => 'switch', 'default' => true],
['key' => 'bbs_breadcrumbs_bbs_home', 'label' => '显示论坛首页', 'type' => 'switch', 'default' => true],
['key' => 'bbs_breadcrumbs_bbs_home_name', 'label' => '论坛首页名称', 'type' => 'text', 'default' => '论坛'],
['key' => 'bbs_breadcrumbs_plate_cat', 'label' => '显示分区', 'type' => 'switch', 'default' => true],
]],
['key' => 'score', 'title' => '评分设置', 'fields' => [
['key' => 'bbs_score_extra_max', 'label' => '加分上限', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 50],
['key' => 'bbs_score_deduct_max', 'label' => '扣分上限', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 50],
]],
['key' => 'display', 'title' => '显示', 'fields' => [
['key' => 'bbs_posts_maxheight_s', 'label' => '内容高度限制', 'type' => 'switch', 'default' => false],
['key' => 'bbs_posts_maxheight', 'label' => '最大高度 (px)', 'type' => 'number', 'default' => 800, 'min' => 300, 'max' => 3000, 'condition' => ['bbs_posts_maxheight_s' => true]],
]],
['key' => 'cover', 'title' => '封面设置', 'fields' => [
['key' => 'bbs_posts_cover_ratio', 'label' => '封面比例', 'type' => 'select', 'default' => '16:9', 'options' => [
['label' => '16:9', 'value' => '16:9'],
['label' => '4:3', 'value' => '4:3'],
['label' => '1:1', 'value' => '1:1'],
['label' => '3:4', 'value' => '3:4'],
]],
['key' => 'bbs_posts_cover_video_mute', 'label' => '视频封面静音', 'type' => 'switch', 'default' => true],
]],
],
];
}
}
@@ -1,39 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Schema;
defined('ABSPATH') || exit;
class SchemaForumReply {
public static function build(): array {
return [
'key' => 'reply',
'title' => '回复评论',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
['key' => 'paginate', 'title' => '翻页', 'fields' => [
['key' => 'bbs_reply_paginate_type', 'label' => '翻页方式', 'type' => 'select', 'default' => 'ajax', 'options' => [
['label' => 'AJAX 加载', 'value' => 'ajax'],
['label' => '传统翻页', 'value' => 'page'],
]],
['key' => 'bbs_reply_paginate_ias_s', 'label' => '无限滚动', 'type' => 'switch', 'default' => true],
['key' => 'bbs_reply_paginate_ias_max', 'label' => '自动加载页数', 'type' => 'number', 'default' => 3, 'min' => 0, 'max' => 10],
]],
['key' => 'toolbar', 'title' => '回复工具栏', 'fields' => [
['key' => 'bbs_comment_smilie', 'label' => '表情', 'type' => 'switch', 'default' => true],
['key' => 'bbs_comment_code', 'label' => '代码', 'type' => 'switch', 'default' => true],
['key' => 'bbs_comment_img', 'label' => '插图', 'type' => 'switch', 'default' => true],
['key' => 'bbs_comment_upload_img', 'label' => '上传图片', 'type' => 'switch', 'default' => true],
['key' => 'bbs_comment_quick_s', 'label' => '快捷回复', 'type' => 'switch', 'default' => true],
['key' => 'bbs_comment_quick_often', 'label' => '预设短语', 'type' => 'textarea', 'default' => "不错,学习了\n感谢分享\n期待更新", 'help' => '每行一条'],
['key' => 'bbs_comment_placeholder', 'label' => '回复占位符', 'type' => 'text', 'default' => '写下你的回复…'],
]],
],
];
}
}
@@ -1,29 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin\Schema;
defined('ABSPATH') || exit;
class SchemaForumUpload {
public static function build(): array {
return [
'key' => 'upload',
'title' => '上传权限',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
['key' => 'limits', 'title' => '上传限制', 'fields' => [
['key' => 'bbs_upload_img_size', 'label' => '图片大小 (KB)', 'type' => 'number', 'default' => 2048, 'min' => 100],
['key' => 'bbs_image_upload_multiple', 'label' => '批量上传数', 'type' => 'number', 'default' => 9, 'min' => 1, 'max' => 20],
['key' => 'bbs_upload_video_size', 'label' => '视频大小 (KB)', 'type' => 'number', 'default' => 20480, 'min' => 1024],
['key' => 'bbs_upload_file_size', 'label' => '文件大小 (KB)', 'type' => 'number', 'default' => 10240, 'min' => 100],
['key' => 'bbs_upload_file_mimes', 'label' => '允许格式', 'type' => 'text', 'default' => 'jpg,jpeg,png,gif,mp4,zip,rar,pdf', 'help' => '逗号分隔'],
]],
],
];
}
}
+1 -4
View File
@@ -98,9 +98,6 @@ class SchemaPageDisplay {
['label' => '用户页', 'value' => 'author'],
['label' => '文章页', 'value' => 'single'],
['label' => '搜索页', 'value' => 'search'],
['label' => '[论坛]首页', 'value' => 'forum_home'],
['label' => '[论坛]板块页面', 'value' => 'forum_plate'],
['label' => '[论坛]帖子页面', 'value' => 'forum_post'],
['label' => '其它页面', 'value' => 'page'],
]],
['key' => 'header_components_device_visibility', 'label' => '设备显示规则', 'type' => 'radio', 'default' => '', 'options' => [
@@ -255,7 +252,7 @@ class SchemaPageDisplay {
['key' => 'user_home_paging_ajax', 'label' => 'AJAX 翻页', 'type' => 'switch', 'default' => true],
]],
['key' => 'tabs', 'title' => 'Tab 配置', 'fields' => [
['key' => 'user_home_tabs', 'label' => '显示的 Tab', 'type' => 'input', 'default' => 'posts,favorites,comments,follow', 'help' => '逗号分隔;默认对齐 Zibll:posts,favorites,comments,follow(粉丝)。可选 credits,product,plate,forum'],
['key' => 'user_home_tabs', 'label' => '显示的 Tab', 'type' => 'input', 'default' => 'posts,favorites,comments,follow', 'help' => '逗号分隔;默认对齐 Zibll:posts,favorites,comments,follow(粉丝)。可选 credits'],
['key' => 'user_home_tabs_order', 'label' => 'Tab 显示顺序', 'type' => 'input', 'default' => 'posts,favorites,comments,follow'],
['key' => 'user_home_tabs_pc_hide', 'label' => 'PC 端隐藏', 'type' => 'input', 'default' => ''],
['key' => 'user_home_tabs_mobile_hide', 'label' => '移动端隐藏', 'type' => 'input', 'default' => ''],
+1 -11
View File
@@ -23,10 +23,7 @@ class SchemaUsers {
return [
'day_max' => 100, 'sign_up' => 20, 'sign_in' => 5, 'followed' => 5,
'post_new' => 5, 'post_like' => 1, 'post_favorite' => 2,
'comment_new' => 2, 'comment_like' => 1, 'bbs_posts_new' => 3,
'bbs_score_extra' => 1, 'bbs_essence' => 2, 'bbs_posts_hot' => 2,
'bbs_plate_new' => 2, 'bbs_plate_hot' => 2, 'bbs_adopt' => 2,
'bbs_comment_hot' => 2,
'comment_new' => 2, 'comment_like' => 1,
];
}
@@ -459,13 +456,6 @@ class SchemaUsers {
['key' => 'points_free_comment_new', 'label' => '发表评论', 'type' => 'number', 'default' => 1, 'min' => 0, 'max' => 100],
['key' => 'points_free_comment_like', 'label' => '评论被赞', 'type' => 'number', 'default' => 1, 'min' => 0, 'max' => 100],
['key' => 'points_free_followed', 'label' => '被关注', 'type' => 'number', 'default' => 1, 'min' => 0, 'max' => 100],
['key' => 'points_free_bbs_posts_new', 'label' => '论坛发帖', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 1000],
['key' => 'points_free_bbs_reply', 'label' => '论坛回复', 'type' => 'number', 'default' => 2, 'min' => 0, 'max' => 1000],
['key' => 'points_free_bbs_essence', 'label' => '帖子加精', 'type' => 'number', 'default' => 10, 'min' => 0, 'max' => 1000],
['key' => 'points_free_bbs_adopt', 'label' => '回复被采纳', 'type' => 'number', 'default' => 20, 'min' => 0, 'max' => 1000],
['key' => 'points_free_bbs_posts_hot', 'label' => '热帖奖励', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 1000],
['key' => 'points_free_bbs_comment_hot', 'label' => '神评奖励', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 1000],
['key' => 'points_free_bbs_score_extra', 'label' => '帖子加分', 'type' => 'number', 'default' => 3, 'min' => 0, 'max' => 1000],
['key' => 'points_earn_invite', 'label' => '邀请注册奖励', 'type' => 'number', 'default' => 10, 'min' => 0, 'max' => 1000, 'help' => '邀请奖励不走免费日上限'],
]],
['key' => 'spend', 'title' => '积分消费', 'fields' => [
-6
View File
@@ -43,8 +43,6 @@ class SchemaProvider {
'payment',
'extensions',
'tools',
'shop',
'forum',
];
}
@@ -92,8 +90,6 @@ class SchemaProvider {
'payment' => 'aksrt_settings',
'extensions' => 'aksrt_settings',
'tools' => 'aksrt_settings',
'shop' => 'aksrt_settings',
'forum' => 'aksrt_settings',
];
return $primary[$this->normalize_domain($domain)] ?? 'aksrt_settings';
@@ -275,8 +271,6 @@ class SchemaProvider {
'payment' => Schema\SchemaPayment::class,
'extensions' => Schema\SchemaExtensions::class,
'tools' => Schema\SchemaTools::class,
'shop' => Schema\SchemaShop::class,
'forum' => Schema\SchemaForum::class,
];
}
-7
View File
@@ -155,13 +155,6 @@ class ToolsController extends ControllerBase {
}
return $this->success(['message' => sprintf(__('已优化 %d 个数据表', 'aksrt'), $optimized)]);
case 'rebuild_index':
// Rebuild search index / recount stats
if (class_exists('\AKSRT\Forum\Forum') && method_exists(\AKSRT\Forum\Forum::get_instance(), 'recount_stats')) {
\AKSRT\Forum\Forum::get_instance()->recount_stats();
}
return $this->success(['message' => __('索引重建完成', 'aksrt')]);
default:
return $this->error('未知的维护操作: ' . esc_html($action));
}
+1 -2
View File
@@ -152,11 +152,10 @@ trait PayRest {
$order = $repo->get_user_order($uid, $order_number);
} else {
$order = $repo->get($order_number);
// Guest may only poll own guest cash orders (user_id=0): content/topic resource or plate_follow.
// Guest may only poll own guest cash orders (user_id=0): content resource.
$ptype = sanitize_key((string) ($order['product_type'] ?? ''));
$guest_types = [
\AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE,
\AKSRT\Core\DomainModel::ORDER_PRODUCT_PLATE_FOLLOW,
];
if ($order && ((int) ($order['user_id'] ?? -1) !== 0 || !in_array($ptype, $guest_types, true))) {
$order = null;
-6
View File
@@ -17,12 +17,6 @@ class DBMigrator {
const OPT_KEY = 'aksrt_db_schema_version';
/**
* Historical flag still readable by older code paths.
* Plate CPT is now native when registered; prefer post_type_exists().
*/
const OPT_PLATE_CPT_MIGRATED = 'aksrt_plate_cpt_migrated';
/**
* Register hooks.
*/

Some files were not shown because too many files have changed in this diff Show More