Move /settings/homepage onto a dedicated REST trait that stores Zibll option IDs (home_posts_list_s, index_list_title, home_lists, exclude posts/cats, list1 orderby). Reader exposes both canonical keys and temporary aliases for existing front-end helpers; save path drops superseded AKSRT-only keys. HomepageSettings.vue binds to the new contract while remaining a Vue custom page.
401 lines
16 KiB
Vue
401 lines
16 KiB
Vue
<template>
|
||
<div class="aksrt-page-container content-page">
|
||
<PageHeader
|
||
title="首页配置"
|
||
desc="按 Zibll 首页主线配置内容模型、Tab、主列表、卡片样式与基础交互。"
|
||
>
|
||
<template #actions>
|
||
<n-button type="primary" :loading="saving" @click="handleSave">保存设置</n-button>
|
||
</template>
|
||
</PageHeader>
|
||
|
||
<n-tabs v-model:value="activeTab" type="line" animated>
|
||
<n-tab-pane name="stream" tab="主列表 & Tab">
|
||
<n-spin :show="loading" style="width:100%; min-height:200px;">
|
||
<n-card class="aksrt-section-card" style="margin-bottom:16px;">
|
||
<template #header><strong>首页模型</strong></template>
|
||
<n-form :model="form" label-placement="left" label-width="160" class="aksrt-form">
|
||
<n-form-item label="首页内容模型">
|
||
<n-radio-group v-model:value="form.homepage_content_model">
|
||
<n-space vertical>
|
||
<n-radio value="mixed">
|
||
<n-text>混合内容流</n-text>
|
||
<n-text depth="3" style="margin-left:8px;">文章 + 资源,贴近 Zibll 首页主流呈现。</n-text>
|
||
</n-radio>
|
||
<n-radio value="post">
|
||
<n-text>纯文章流</n-text>
|
||
<n-text depth="3" style="margin-left:8px;">仅标准文章,适合纯资讯站点。</n-text>
|
||
</n-radio>
|
||
</n-space>
|
||
</n-radio-group>
|
||
</n-form-item>
|
||
<n-form-item label="首页说明">
|
||
<n-alert type="info" :show-icon="false">
|
||
当前首页已切换为固定骨架模式,不再使用旧版自由模块画布、导入导出预设或历史模块桥接。
|
||
</n-alert>
|
||
</n-form-item>
|
||
</n-form>
|
||
</n-card>
|
||
|
||
<n-card class="aksrt-section-card" style="margin-bottom:16px;">
|
||
<template #header><strong>主列表</strong></template>
|
||
<n-form :model="form" label-placement="left" label-width="160" class="aksrt-form">
|
||
<n-form-item label="启用主列表">
|
||
<n-switch v-model:value="form.home_posts_list_s" />
|
||
<template #feedback><span class="aksrt-hint-text">首页主内容区保留 Zibll 式内容流,不建议关闭。</span></template>
|
||
</n-form-item>
|
||
<n-form-item label="显示标题">
|
||
<n-input v-model:value="form.index_list_title" placeholder="最新发布" />
|
||
</n-form-item>
|
||
<n-form-item label="主栏目默认排序">
|
||
<n-select v-model:value="form.home_list1_orderby_option.orderby" :options="allSortOptions" />
|
||
</n-form-item>
|
||
<n-form-item label="显示排序方式按钮">
|
||
<n-switch v-model:value="form.home_list1_orderby_s" />
|
||
</n-form-item>
|
||
<n-form-item v-if="form.home_list1_orderby_s" label="排序栏样式">
|
||
<n-radio-group v-model:value="form.home_list_sort_dropdown">
|
||
<n-space size="large">
|
||
<n-radio-button :value="false">内联链接</n-radio-button>
|
||
<n-radio-button :value="true">下拉菜单</n-radio-button>
|
||
</n-space>
|
||
</n-radio-group>
|
||
</n-form-item>
|
||
<n-form-item v-if="form.home_list1_orderby_s" label="排序方式选项">
|
||
<n-select
|
||
v-model:value="form.home_list1_orderby_option.options"
|
||
:options="allSortOptions"
|
||
multiple
|
||
placeholder="选择排序选项"
|
||
/>
|
||
</n-form-item>
|
||
</n-form>
|
||
</n-card>
|
||
|
||
<n-card class="aksrt-section-card" style="margin-bottom:16px;">
|
||
<template #header><strong>排除规则</strong></template>
|
||
<n-form :model="form" label-placement="left" label-width="160" class="aksrt-form">
|
||
<n-form-item label="排除文章">
|
||
<n-input
|
||
v-model:value="excludePostIdsText"
|
||
type="textarea"
|
||
placeholder="逗号分隔的文章 ID,如 12,34,56"
|
||
:rows="2"
|
||
/>
|
||
<template #feedback><span class="aksrt-hint-text">主列表和首页 Tab 查询均排除这些文章。</span></template>
|
||
</n-form-item>
|
||
<n-form-item label="排除分类">
|
||
<n-input
|
||
v-model:value="excludeCategoryIdsText"
|
||
type="textarea"
|
||
placeholder="逗号分隔的分类 ID,如 5,8"
|
||
:rows="2"
|
||
/>
|
||
<template #feedback><span class="aksrt-hint-text">主列表排除这些分类,首页 Tab 不能绑定已排除分类。</span></template>
|
||
</n-form-item>
|
||
</n-form>
|
||
</n-card>
|
||
|
||
<n-card class="aksrt-section-card" style="margin-bottom:16px;">
|
||
<template #header>
|
||
<div style="display:flex; align-items:center; justify-content:space-between;">
|
||
<strong>更多栏目</strong>
|
||
<n-button size="small" @click="addTab">+ 添加栏目</n-button>
|
||
</div>
|
||
</template>
|
||
<n-form :model="form" label-placement="left" label-width="160" class="aksrt-form" style="margin-bottom:16px;">
|
||
<n-form-item label="Tab 数量上限">
|
||
<n-input-number v-model:value="form.homepage_tab_limit" :min="1" :max="12" />
|
||
</n-form-item>
|
||
<n-form-item label="每 Tab 文章数">
|
||
<n-input-number v-model:value="form.homepage_tab_posts_per_page" :min="1" :max="20" />
|
||
</n-form-item>
|
||
</n-form>
|
||
|
||
<div v-if="form.home_lists.length === 0" class="homepage-settings__empty-tabs">
|
||
<n-text depth="3">暂无更多栏目,点击“添加栏目”创建分类/专题 Tab。</n-text>
|
||
</div>
|
||
|
||
<div v-for="(tab, index) in form.home_lists" :key="index" class="homepage-settings__tab-row">
|
||
<div class="homepage-settings__tab-fields">
|
||
<n-select
|
||
:value="tab.term_id"
|
||
:options="categoryOptions"
|
||
placeholder="分类"
|
||
filterable
|
||
style="flex:2;"
|
||
@update:value="(val) => updateTab(index, 'term_id', val)"
|
||
/>
|
||
<n-input
|
||
:value="tab.title"
|
||
placeholder="自定义标题"
|
||
style="flex:1;"
|
||
@update:value="(val) => updateTab(index, 'title', val)"
|
||
/>
|
||
<n-select
|
||
:value="tab.orderby"
|
||
:options="allSortOptions"
|
||
style="flex:1;"
|
||
@update:value="(val) => updateTab(index, 'orderby', val)"
|
||
/>
|
||
<n-switch
|
||
:value="tab.enabled"
|
||
@update:value="(val) => updateTab(index, 'enabled', val)"
|
||
/>
|
||
<n-button size="small" quaternary type="error" @click="removeTab(index)">删除</n-button>
|
||
</div>
|
||
</div>
|
||
</n-card>
|
||
</n-spin>
|
||
</n-tab-pane>
|
||
|
||
<n-tab-pane name="cards" tab="卡片 & 交互">
|
||
<n-spin :show="loading" style="width:100%; min-height:200px;">
|
||
<n-card class="aksrt-section-card" style="margin-bottom:16px;">
|
||
<template #header><strong>卡片样式</strong></template>
|
||
<n-form :model="form" label-placement="left" label-width="160" class="aksrt-form">
|
||
<n-form-item label="卡片风格">
|
||
<n-radio-group v-model:value="form.homepage_card_style">
|
||
<n-space size="large">
|
||
<n-radio-button value="default">默认</n-radio-button>
|
||
<n-radio-button value="style2">风格 2</n-radio-button>
|
||
<n-radio-button value="style3">风格 3</n-radio-button>
|
||
</n-space>
|
||
</n-radio-group>
|
||
</n-form-item>
|
||
<n-form-item label="多图卡片">
|
||
<n-switch v-model:value="form.homepage_multi_thumb_enabled" />
|
||
</n-form-item>
|
||
<n-form-item v-if="form.homepage_multi_thumb_enabled" label="最少图片数">
|
||
<n-input-number v-model:value="form.homepage_multi_thumb_min_images" :min="2" :max="4" />
|
||
</n-form-item>
|
||
<n-form-item label="默认视图">
|
||
<n-radio-group v-model:value="form.homepage_default_view">
|
||
<n-space size="large">
|
||
<n-radio-button value="list">列表</n-radio-button>
|
||
<n-radio-button value="card">卡片</n-radio-button>
|
||
</n-space>
|
||
</n-radio-group>
|
||
</n-form-item>
|
||
</n-form>
|
||
</n-card>
|
||
|
||
<n-card class="aksrt-section-card" style="margin-bottom:16px;">
|
||
<template #header><strong>交互设置</strong></template>
|
||
<n-form :model="form" label-placement="left" label-width="160" class="aksrt-form">
|
||
<n-form-item label="排序栏 AJAX">
|
||
<n-switch v-model:value="form.homepage_sort_ajax_enabled" />
|
||
<template #feedback><span class="aksrt-hint-text">关闭后排序链接使用整页跳转。</span></template>
|
||
</n-form-item>
|
||
<n-form-item label="Tab 吸顶">
|
||
<n-switch v-model:value="form.homepage_sticky_tabs_enabled" />
|
||
</n-form-item>
|
||
<n-form-item label="迷你侧栏列表">
|
||
<n-switch v-model:value="form.homepage_sidebar_mini_enabled" />
|
||
</n-form-item>
|
||
</n-form>
|
||
</n-card>
|
||
|
||
<n-card class="aksrt-section-card">
|
||
<template #header><strong>固定骨架说明</strong></template>
|
||
<n-space vertical>
|
||
<n-alert type="warning" :show-icon="false">
|
||
Hero 与 Banner 已作为固定首页骨架的一部分维护,不再由旧版首页模块编排驱动。
|
||
</n-alert>
|
||
<n-alert type="info" :show-icon="false">
|
||
如需调整首页首屏运营区,请前往对应 Hero / Banner 管理页,而不是返回旧模块工作台。
|
||
</n-alert>
|
||
</n-space>
|
||
</n-card>
|
||
</n-spin>
|
||
</n-tab-pane>
|
||
</n-tabs>
|
||
|
||
<div class="aksrt-form-actions">
|
||
<n-button type="primary" :loading="saving" @click="handleSave">保存设置</n-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, onMounted, reactive, ref } from 'vue'
|
||
import { useMessage } from 'naive-ui'
|
||
import api from '@/api'
|
||
import PageHeader from '@/components/PageHeader.vue'
|
||
|
||
const message = useMessage()
|
||
const loading = ref(true)
|
||
const saving = ref(false)
|
||
const activeTab = ref('stream')
|
||
const categoryOptions = ref([])
|
||
|
||
const allSortOptions = [
|
||
{ label: '最近更新', value: 'modified' },
|
||
{ label: '发布时间', value: 'date' },
|
||
{ label: '浏览量', value: 'views' },
|
||
{ label: '点赞数', value: 'likes' },
|
||
{ label: '评论数', value: 'comments' },
|
||
{ label: '收藏数', value: 'favorites' },
|
||
{ label: '下载量', value: 'downloads' },
|
||
{ label: '随机', value: 'rand' },
|
||
]
|
||
|
||
const form = reactive({
|
||
homepage_content_model: 'mixed',
|
||
home_posts_list_s: true,
|
||
home_list1_orderby_s: true,
|
||
home_list1_orderby_option: {
|
||
orderby: 'modified',
|
||
options: ['modified', 'date', 'views', 'likes', 'comments'],
|
||
},
|
||
index_list_title: '最新发布',
|
||
home_exclude_posts: [],
|
||
home_exclude_cats: [],
|
||
home_lists: [],
|
||
homepage_card_style: 'default',
|
||
homepage_multi_thumb_enabled: true,
|
||
homepage_multi_thumb_min_images: 3,
|
||
homepage_sidebar_mini_enabled: true,
|
||
homepage_default_view: 'list',
|
||
homepage_sort_ajax_enabled: true,
|
||
homepage_sticky_tabs_enabled: true,
|
||
home_list_sort_dropdown: false,
|
||
homepage_tab_posts_per_page: 6,
|
||
homepage_tab_limit: 4,
|
||
})
|
||
|
||
const excludePostIdsText = computed({
|
||
get: () => (form.home_exclude_posts || []).join(','),
|
||
set: (val) => {
|
||
form.home_exclude_posts = val.split(',').map(s => parseInt(s.trim(), 10)).filter(n => n > 0)
|
||
},
|
||
})
|
||
|
||
const excludeCategoryIdsText = computed({
|
||
get: () => (form.home_exclude_cats || []).join(','),
|
||
set: (val) => {
|
||
form.home_exclude_cats = val.split(',').map(s => parseInt(s.trim(), 10)).filter(n => n > 0)
|
||
},
|
||
})
|
||
|
||
function addTab() {
|
||
form.home_lists.push({ term_id: 0, title: '', orderby: 'date', enabled: true })
|
||
}
|
||
|
||
function removeTab(index) {
|
||
form.home_lists.splice(index, 1)
|
||
}
|
||
|
||
function updateTab(index, field, value) {
|
||
form.home_lists[index][field] = value
|
||
}
|
||
|
||
async function loadCategories() {
|
||
try {
|
||
const res = await api.getHomepageCategories()
|
||
const cats = res?.data || []
|
||
categoryOptions.value = cats.map(c => ({
|
||
label: `${c.name} (${c.count})`,
|
||
value: c.term_id,
|
||
}))
|
||
} catch {
|
||
// non-critical
|
||
}
|
||
}
|
||
|
||
async function loadSettings() {
|
||
loading.value = true
|
||
try {
|
||
const res = await api.getHomepageSettings()
|
||
const d = res?.data || {}
|
||
|
||
form.homepage_content_model = d.homepage_content_model || 'mixed'
|
||
form.home_posts_list_s = d.home_posts_list_s !== false && d.home_posts_list_enabled !== false
|
||
form.home_list1_orderby_s = d.home_list1_orderby_s !== false && d.home_list_sort_enabled !== false
|
||
const orderOpt = d.home_list1_orderby_option && typeof d.home_list1_orderby_option === 'object' ? d.home_list1_orderby_option : {}
|
||
form.home_list1_orderby_option = {
|
||
orderby: orderOpt.orderby || d.homepage_default_sort || 'modified',
|
||
options: Array.isArray(orderOpt.options) && orderOpt.options.length
|
||
? orderOpt.options
|
||
: (Array.isArray(d.home_list_sort_options) && d.home_list_sort_options.length
|
||
? d.home_list_sort_options
|
||
: ['modified', 'date', 'views', 'likes', 'comments']),
|
||
}
|
||
form.index_list_title = d.index_list_title || d.home_list_title || '最新发布'
|
||
form.home_exclude_posts = Array.isArray(d.home_exclude_posts)
|
||
? d.home_exclude_posts
|
||
: (Array.isArray(d.home_exclude_post_ids) ? d.home_exclude_post_ids : [])
|
||
form.home_exclude_cats = Array.isArray(d.home_exclude_cats)
|
||
? d.home_exclude_cats
|
||
: (Array.isArray(d.home_exclude_category_ids) ? d.home_exclude_category_ids : [])
|
||
form.home_lists = Array.isArray(d.home_lists)
|
||
? d.home_lists
|
||
: (Array.isArray(d.homepage_tabs) ? d.homepage_tabs : [])
|
||
form.homepage_card_style = d.homepage_card_style || 'default'
|
||
form.homepage_multi_thumb_enabled = d.homepage_multi_thumb_enabled !== false
|
||
form.homepage_multi_thumb_min_images = d.homepage_multi_thumb_min_images || 3
|
||
form.homepage_sidebar_mini_enabled = d.homepage_sidebar_mini_enabled !== false
|
||
form.homepage_default_view = d.homepage_default_view || 'list'
|
||
form.homepage_sort_ajax_enabled = d.homepage_sort_ajax_enabled !== false
|
||
form.homepage_sticky_tabs_enabled = d.homepage_sticky_tabs_enabled !== false
|
||
form.home_list_sort_dropdown = !!d.home_list_sort_dropdown
|
||
form.homepage_tab_posts_per_page = d.homepage_tab_posts_per_page || 6
|
||
form.homepage_tab_limit = d.homepage_tab_limit || 4
|
||
} catch (error) {
|
||
message.error(error.message || '加载设置失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
async function handleSave() {
|
||
saving.value = true
|
||
try {
|
||
const payload = {
|
||
...form,
|
||
}
|
||
|
||
const res = await api.saveHomepageSettings(payload)
|
||
if (res?.code === 'success') {
|
||
message.success('首页设置已保存')
|
||
return
|
||
}
|
||
throw new Error(res?.message || '保存失败')
|
||
} catch (error) {
|
||
message.error(error.message || '保存失败')
|
||
} finally {
|
||
saving.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
loadCategories()
|
||
loadSettings()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.homepage-settings__empty-tabs {
|
||
padding: 24px;
|
||
text-align: center;
|
||
border: 1px dashed var(--n-border-color);
|
||
border-radius: 4px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.homepage-settings__tab-row {
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.homepage-settings__tab-fields {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.aksrt-form-actions {
|
||
margin-top: 16px;
|
||
text-align: right;
|
||
}
|
||
</style>
|