Merge pull request 'feat(admin): rebuild page display settings' (#9) from worktree/page-display-rebuild into main
Reviewed-on: #9
@@ -18,6 +18,8 @@
|
||||
!/tests/widget-system/post-visual-contract.test.cjs
|
||||
!/tests/widget-system/lazyload-contract.test.cjs
|
||||
!/tests/widget-system/toast-contract.test.cjs
|
||||
!/tests/widget-system/page-display-contract.test.cjs
|
||||
!/tests/widget-system/page-display-save-harness.php
|
||||
!/tests/visual/docker-compose.zibll.yml
|
||||
!/tests/visual/zibll-Caddyfile
|
||||
!/tests/visual/README.md
|
||||
|
||||
@@ -9,13 +9,6 @@ defined('ABSPATH') || exit;
|
||||
|
||||
get_header();
|
||||
|
||||
$other_page_settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_other_page_settings()
|
||||
: [
|
||||
'page_404_message' => __('页面未找到', 'aksrt'),
|
||||
'page_404_search' => true,
|
||||
'page_404_popular' => true,
|
||||
];
|
||||
$search_placeholder = function_exists('aksrt_get_search_placeholder') ? aksrt_get_search_placeholder() : __('开启精彩搜索', 'aksrt');
|
||||
$hot_keywords = function_exists('aksrt_get_search_hot_keywords') ? aksrt_get_search_hot_keywords() : [];
|
||||
|
||||
@@ -46,7 +39,7 @@ $top_cats = get_terms([
|
||||
<i class="fa fas fa-compass"></i>
|
||||
</p>
|
||||
<p class="error-hero__code" id="error-title">404</p>
|
||||
<h1 class="error-hero__title"><?php echo esc_html($other_page_settings['page_404_message']); ?></h1>
|
||||
<h1 class="error-hero__title"><?php esc_html_e('页面未找到', 'aksrt'); ?></h1>
|
||||
<p class="error-hero__desc">
|
||||
<?php esc_html_e('你访问的页面不存在或已被删除。试试搜索,或者从下方常用入口继续浏览。', 'aksrt'); ?>
|
||||
</p>
|
||||
@@ -54,7 +47,6 @@ $top_cats = get_terms([
|
||||
<?php if (function_exists('aksrt_render_admin_quick_link')) { aksrt_render_admin_quick_link('page-display/other', __('404 页设置', 'aksrt')); } ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($other_page_settings['page_404_search'])) : ?>
|
||||
<form role="search" method="get" class="error-hero__search" action="<?php echo esc_url(home_url('/')); ?>">
|
||||
<span class="error-hero__search-icon" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
@@ -64,7 +56,6 @@ $top_cats = get_terms([
|
||||
<button type="submit" class="btn-primary btn-sm"><?php esc_html_e('搜索', 'aksrt'); ?></button>
|
||||
</form>
|
||||
<?php aksrt_render_search_hot_keywords($hot_keywords, 'aksrt-search-hot-keywords'); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="error-hero__actions">
|
||||
<a href="<?php echo esc_url(home_url('/')); ?>" class="btn-primary"><?php esc_html_e('返回首页', 'aksrt'); ?></a>
|
||||
@@ -86,7 +77,7 @@ $top_cats = get_terms([
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($other_page_settings['page_404_popular']) && $hot_query->have_posts()) : ?>
|
||||
<?php if ($hot_query->have_posts()) : ?>
|
||||
<section class="error-section" aria-label="<?php esc_attr_e('热门内容', 'aksrt'); ?>">
|
||||
<h2 class="error-section__title"><?php esc_html_e('热门内容', 'aksrt'); ?></h2>
|
||||
<ul class="error-section__list">
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
<n-message-provider>
|
||||
<n-dialog-provider>
|
||||
<n-notification-provider>
|
||||
<n-layout v-if="!isMobile" class="aksrt-admin-layout" has-sider>
|
||||
<div v-if="!isMobile" class="aksrt-csf-shell">
|
||||
<header class="aksrt-csf-header">
|
||||
<div>
|
||||
<h1>AKSRT主题设置 <small>页面与功能配置</small></h1>
|
||||
<p>欢迎使用 AKSRT 主题</p>
|
||||
</div>
|
||||
<span class="aksrt-csf-version">V{{ themeVersion }}</span>
|
||||
</header>
|
||||
<n-layout class="aksrt-admin-layout" has-sider>
|
||||
<n-layout-sider
|
||||
bordered
|
||||
collapse-mode="width"
|
||||
@@ -47,6 +55,7 @@
|
||||
</div>
|
||||
</n-layout>
|
||||
</n-layout>
|
||||
</div>
|
||||
|
||||
<div v-else class="aksrt-admin-mobile">
|
||||
<MobileTopBar :title="currentPageTitle" @toggle="drawerActive = true" />
|
||||
@@ -101,6 +110,7 @@ const collapsed = ref(false)
|
||||
const pageError = ref(null)
|
||||
const isMobile = ref(false)
|
||||
const drawerActive = ref(false)
|
||||
const themeVersion = window.aksrtAdminData?.version || '9.0'
|
||||
|
||||
function checkMobile() {
|
||||
isMobile.value = window.innerWidth < 768
|
||||
@@ -205,7 +215,7 @@ function handleMenuClick(key) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 60px;
|
||||
height: 72px;
|
||||
gap: 10px;
|
||||
border-bottom: 1px solid var(--n-border-color, #efeff5);
|
||||
}
|
||||
@@ -222,7 +232,7 @@ function handleMenuClick(key) {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
background: #18a058;
|
||||
background: linear-gradient(135deg, #65bff7 10%, #0d67b9 100%);
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
@@ -237,7 +247,71 @@ function handleMenuClick(key) {
|
||||
}
|
||||
|
||||
.aksrt-admin-content__inner {
|
||||
padding: 24px 28px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.aksrt-csf-shell {
|
||||
max-width: 1380px;
|
||||
margin: 20px auto;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 20px rgba(82, 82, 82, .1);
|
||||
}
|
||||
|
||||
.aksrt-csf-header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 108px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
padding: 22px 34px;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #f97794 10%, #623aa2 100%);
|
||||
}
|
||||
|
||||
.aksrt-csf-header::before {
|
||||
position: absolute;
|
||||
left: 10%;
|
||||
bottom: -25px;
|
||||
content: 'AKSRT';
|
||||
color: rgba(255, 255, 255, .05);
|
||||
font-size: 100px;
|
||||
font-weight: 800;
|
||||
font-style: oblique;
|
||||
transform: rotate(-5deg) scaleX(1.5);
|
||||
}
|
||||
|
||||
.aksrt-csf-header h1,
|
||||
.aksrt-csf-header p {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.aksrt-csf-header h1 {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.aksrt-csf-header h1 small {
|
||||
margin-left: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.aksrt-csf-header p {
|
||||
margin-top: 7px;
|
||||
opacity: .78;
|
||||
}
|
||||
|
||||
.aksrt-csf-version {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 5px 12px;
|
||||
border: 1px solid rgba(255, 255, 255, .3);
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, .1);
|
||||
}
|
||||
|
||||
.aksrt-admin-mobile {
|
||||
|
||||
@@ -19,12 +19,11 @@ function normalizeDomainKey(domain) {
|
||||
/**
|
||||
* Join REST base + endpoint correctly for both pretty and plain permalinks.
|
||||
*
|
||||
* Pretty: https://site/wp-json/aksrt/v1 + /banners?kind=x
|
||||
* → https://site/wp-json/aksrt/v1/banners?kind=x
|
||||
* Pretty: https://site/wp-json/aksrt/v1 + /settings/schema/global/common
|
||||
* → https://site/wp-json/aksrt/v1/settings/schema/global/common
|
||||
*
|
||||
* Plain: https://site/index.php?rest_route=/aksrt/v1 + /banners?kind=x
|
||||
* → https://site/index.php?rest_route=/aksrt/v1/banners&kind=x
|
||||
* (must NOT produce rest_route=/aksrt/v1/banners?kind=x)
|
||||
* Plain: https://site/index.php?rest_route=/aksrt/v1 + /settings/schema/global/common
|
||||
* → https://site/index.php?rest_route=/aksrt/v1/settings/schema/global/common
|
||||
*
|
||||
* @param {string} baseUrl
|
||||
* @param {string} endpoint path starting with /, may include ?query
|
||||
|
||||
@@ -7,9 +7,6 @@ export const globalApiMethods = {
|
||||
saveMailSettings(data) { return this.request('/settings/mail', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
testMail(to) { return this.request('/mail/test', { method: 'POST', body: JSON.stringify({ to }) }) },
|
||||
|
||||
getFooterSettings() { return this.request('/settings/footer') },
|
||||
saveFooterSettings(data) { return this.request('/settings/footer', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
|
||||
getFooterTabbarSettings() { return this.request('/settings/footer-tabbar') },
|
||||
saveFooterTabbarSettings(data) { return this.request('/settings/footer-tabbar', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
reorderFooterTabbar(ids) { return this.request('/settings/footer-tabbar/reorder', { method: 'POST', body: JSON.stringify({ ids }) }) },
|
||||
|
||||
@@ -3,10 +3,6 @@ export const pageDisplayApiMethods = {
|
||||
* 页面&显示 (Page Display)
|
||||
* ================================================================ */
|
||||
|
||||
getHomepageSettings() { return this.request('/settings/homepage') },
|
||||
saveHomepageSettings(data) { return this.request('/settings/homepage', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
getHomepageCategories() { return this.request('/homepage/categories') },
|
||||
|
||||
getCategoryRecommendSettings() { return this.request('/settings/category-recommend') },
|
||||
saveCategoryRecommendSettings(data) { return this.request('/settings/category-recommend', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
getCategories(taxonomy = 'category') {
|
||||
@@ -16,13 +12,9 @@ export const pageDisplayApiMethods = {
|
||||
updateCategoryMeta(id, data) { return this.request(`/categories/${id}/meta`, { method: 'POST', body: JSON.stringify(data) }) },
|
||||
searchUsers(search = '', number = 20) { return this.request(`/users/search?search=${encodeURIComponent(search)}&number=${number}`) },
|
||||
|
||||
getBanners() { return this.request('/banners') },
|
||||
createBanner(data) { return this.request('/banners', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
updateBanner(id, data) { return this.request(`/banners/${id}`, { method: 'PUT', body: JSON.stringify(data) }) },
|
||||
deleteBanner(id) { return this.request(`/banners/${id}`, { method: 'DELETE' }) },
|
||||
|
||||
getHomepageBanners() { return this.getBanners() },
|
||||
createHomepageBanner(data) { return this.createBanner(data) },
|
||||
updateHomepageBanner(id, data) { return this.updateBanner(id, data) },
|
||||
deleteHomepageBanner(id) { return this.deleteBanner(id) },
|
||||
searchPageDisplayOptions(source, query = '', include = []) {
|
||||
const params = new URLSearchParams({ query })
|
||||
include.forEach(value => params.append('include[]', value))
|
||||
return this.request(`/settings/schema/page_display/search/${encodeURIComponent(source)}?${params}`)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="aksrt-field" :class="fieldClasses">
|
||||
<div class="aksrt-field__label-row">
|
||||
<div v-if="schema.label" class="aksrt-field__label-row">
|
||||
<label class="aksrt-field__label">
|
||||
{{ schema.label }}
|
||||
<span v-if="schema.required" class="aksrt-field__required">*</span>
|
||||
@@ -19,14 +19,14 @@
|
||||
|
||||
<!-- Switch -->
|
||||
<n-switch
|
||||
v-if="schema.type === 'switch'"
|
||||
v-if="schema.type === 'switch' || schema.type === 'switcher'"
|
||||
:value="modelValue"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
|
||||
<!-- Input (text, email, url, password) -->
|
||||
<n-input
|
||||
v-else-if="schema.type === 'input' || schema.type === 'text'"
|
||||
v-else-if="schema.type === 'input' || schema.type === 'text' || schema.type === 'url'"
|
||||
:value="modelValue"
|
||||
:placeholder="schema.placeholder || ''"
|
||||
:type="schema.inputType || 'text'"
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
<!-- Number -->
|
||||
<n-input-number
|
||||
v-else-if="schema.type === 'number'"
|
||||
v-else-if="schema.type === 'number' || schema.type === 'spinner'"
|
||||
:value="modelValue"
|
||||
:min="schema.min"
|
||||
:max="schema.max"
|
||||
@@ -62,16 +62,44 @@
|
||||
/>
|
||||
|
||||
<!-- Select -->
|
||||
<n-select
|
||||
v-else-if="schema.type === 'select'"
|
||||
:value="modelValue"
|
||||
:options="normalizedOptions"
|
||||
:placeholder="schema.placeholder || '请选择'"
|
||||
:multiple="schema.multiple"
|
||||
:clearable="schema.clearable !== false"
|
||||
:disabled="schema.disabled"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
<div v-else-if="schema.type === 'select'" class="aksrt-field__ordered-select">
|
||||
<n-select
|
||||
:value="modelValue"
|
||||
:options="normalizedOptions"
|
||||
:placeholder="schema.placeholder || '请选择'"
|
||||
:multiple="schema.multiple"
|
||||
:clearable="schema.clearable !== false"
|
||||
:disabled="schema.disabled"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
<div v-if="schema.multiple && schema.sortable && Array.isArray(modelValue)" class="aksrt-field__selected-order">
|
||||
<div v-for="(value, index) in modelValue" :key="value" class="aksrt-field__selected-order-item">
|
||||
<span>{{ selectedOptionLabel(normalizedOptions, value) }}</span>
|
||||
<span><n-button text size="tiny" :disabled="index === 0" @click="moveSelectedValue(index, -1)">↑</n-button><n-button text size="tiny" :disabled="index === modelValue.length - 1" @click="moveSelectedValue(index, 1)">↓</n-button></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="schema.type === 'async_select'" class="aksrt-field__ordered-select">
|
||||
<n-select
|
||||
:value="schema.multiple === true && !Array.isArray(modelValue) ? [] : modelValue"
|
||||
:options="asyncOptions"
|
||||
:loading="asyncLoading"
|
||||
:placeholder="schema.placeholder || '输入关键词搜索内容'"
|
||||
:multiple="schema.multiple === true"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
@search="handleAsyncSearch"
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
<div v-if="schema.multiple && schema.sortable && Array.isArray(modelValue)" class="aksrt-field__selected-order">
|
||||
<div v-for="(value, index) in modelValue" :key="value" class="aksrt-field__selected-order-item">
|
||||
<span>{{ selectedOptionLabel(asyncOptions, value) }}</span>
|
||||
<span><n-button text size="tiny" :disabled="index === 0" @click="moveSelectedValue(index, -1)">↑</n-button><n-button text size="tiny" :disabled="index === modelValue.length - 1" @click="moveSelectedValue(index, 1)">↓</n-button></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Taxonomy Select (async-loaded category/tag terms) -->
|
||||
<n-select
|
||||
@@ -157,6 +185,19 @@
|
||||
@update:value="emit('update:modelValue', $event)"
|
||||
/>
|
||||
|
||||
<div v-else-if="schema.type === 'palette'" class="aksrt-field__palette">
|
||||
<button
|
||||
v-for="opt in normalizedOptions"
|
||||
:key="opt.value"
|
||||
type="button"
|
||||
class="aksrt-field__palette-item"
|
||||
:class="{ 'is-active': modelValue === opt.value }"
|
||||
:style="{ background: opt.label }"
|
||||
:title="opt.value"
|
||||
@click="emit('update:modelValue', opt.value)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Icon picker -->
|
||||
<FaIconPicker
|
||||
v-else-if="schema.type === 'icon'"
|
||||
@@ -167,7 +208,7 @@
|
||||
|
||||
<!-- Media upload -->
|
||||
<AksMediaField
|
||||
v-else-if="schema.type === 'media'"
|
||||
v-else-if="schema.type === 'media' || schema.type === 'upload'"
|
||||
:model-value="modelValue"
|
||||
:accept="schema.accept"
|
||||
:placeholder="schema.placeholder || '选择图片'"
|
||||
@@ -203,7 +244,7 @@
|
||||
:title="schema.alertTitle"
|
||||
:closable="false"
|
||||
>
|
||||
{{ schema.alertContent || modelValue }}
|
||||
{{ schema.content || schema.alertContent || modelValue }}
|
||||
</n-alert>
|
||||
|
||||
<!-- Pure HTML content (no form field, just display) -->
|
||||
@@ -360,9 +401,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Fieldset (single nested object) -->
|
||||
<div v-else-if="schema.type === 'fieldset'" class="aksrt-field__fieldset">
|
||||
<div v-else-if="schema.type === 'fieldset' || schema.type === 'accordion'" class="aksrt-field__fieldset">
|
||||
<AksField
|
||||
v-for="field in (schema.fields || [])"
|
||||
v-for="field in visibleNestedFields"
|
||||
:key="field.key"
|
||||
:schema="field"
|
||||
:model-value="fieldsetValue(field)"
|
||||
@@ -370,8 +411,29 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="schema.type === 'link'" class="aksrt-field__link">
|
||||
<n-input :value="modelValue?.url || ''" placeholder="https://" @update:value="updateLink('url', $event)" />
|
||||
<n-select :value="modelValue?.target || ''" :options="linkTargetOptions" @update:value="updateLink('target', $event)" />
|
||||
<n-input :value="modelValue?.text || ''" placeholder="链接文字(可选)" @update:value="updateLink('text', $event)" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="schema.type === 'sortable'" class="aksrt-field__sortable">
|
||||
<div v-for="(item, index) in sortableItems" :key="item.key" class="aksrt-field__sortable-item">
|
||||
<div class="aksrt-field__sortable-head">
|
||||
<strong>{{ item.label }}</strong>
|
||||
<span>
|
||||
<n-button text size="tiny" :disabled="index === 0" @click="moveSortable(index, -1)">↑</n-button>
|
||||
<n-button text size="tiny" :disabled="index === sortableItems.length - 1" @click="moveSortable(index, 1)">↓</n-button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="aksrt-field__sortable-body">
|
||||
<AksField v-for="field in item.fields || []" :key="field.key" :schema="field" :model-value="modelValue?.[item.key]?.[field.key] ?? field.default" @update:model-value="updateSortable(item.key, field.key, $event)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Repeater (array of objects) — inline expand (default) -->
|
||||
<div v-else-if="schema.type === 'repeater' && schema.ui !== 'list-modal'" class="aksrt-field__repeater">
|
||||
<div v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui !== 'list-modal'" class="aksrt-field__repeater">
|
||||
<div
|
||||
v-for="(item, index) in (modelValue || [])"
|
||||
:key="item._id || index"
|
||||
@@ -383,7 +445,7 @@
|
||||
size="tiny"
|
||||
type="error"
|
||||
text
|
||||
:disabled="schema.disabled"
|
||||
:disabled="schema.disabled || reachedRepeaterMinimum"
|
||||
@click="removeRepeaterItem(index)"
|
||||
>
|
||||
<FaIcon icon="fa fas fa-trash-alt" :size="12" />
|
||||
@@ -392,6 +454,7 @@
|
||||
<div class="aksrt-field__repeater-fields">
|
||||
<template v-for="field in (schema.fields || [])" :key="field.key">
|
||||
<AksField
|
||||
v-if="matchDependency(field.dependsOn, item)"
|
||||
:schema="field"
|
||||
:model-value="item[field.key]"
|
||||
:disabled="schema.disabled"
|
||||
@@ -403,16 +466,16 @@
|
||||
<n-button
|
||||
size="small"
|
||||
dashed
|
||||
:disabled="schema.disabled"
|
||||
:disabled="schema.disabled || reachedRepeaterMaximum"
|
||||
@click="addRepeaterItem"
|
||||
>
|
||||
<FaIcon icon="fa fas fa-plus" :size="12" style="margin-right: 6px" />
|
||||
{{ schema.addText || '添加一项' }}
|
||||
{{ schema.button_title || schema.addText || '添加一项' }}
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<!-- Repeater list-modal: compact list + modal editor -->
|
||||
<div v-else-if="schema.type === 'repeater' && schema.ui === 'list-modal'" class="aksrt-field__repeater aksrt-field__repeater--list-modal">
|
||||
<div v-else-if="(schema.type === 'repeater' || schema.type === 'group') && schema.ui === 'list-modal'" class="aksrt-field__repeater aksrt-field__repeater--list-modal">
|
||||
<div v-if="(modelValue || []).length" class="aksrt-field__repeater-list">
|
||||
<div
|
||||
v-for="(item, index) in (modelValue || [])"
|
||||
@@ -452,7 +515,7 @@
|
||||
size="tiny"
|
||||
quaternary
|
||||
type="error"
|
||||
:disabled="schema.disabled"
|
||||
:disabled="schema.disabled || reachedRepeaterMinimum"
|
||||
@click="removeRepeaterItem(index)"
|
||||
>
|
||||
<FaIcon icon="fa fas fa-trash-alt" :size="12" />
|
||||
@@ -488,11 +551,11 @@
|
||||
size="small"
|
||||
dashed
|
||||
block
|
||||
:disabled="schema.disabled"
|
||||
:disabled="schema.disabled || reachedRepeaterMaximum"
|
||||
@click="openRepeaterModal(-1)"
|
||||
>
|
||||
<FaIcon icon="fa fas fa-plus" :size="12" style="margin-right: 6px" />
|
||||
{{ schema.addText || '添加一项' }}
|
||||
{{ schema.button_title || schema.addText || '添加一项' }}
|
||||
</n-button>
|
||||
|
||||
<n-modal
|
||||
@@ -505,7 +568,7 @@
|
||||
>
|
||||
<div class="aksrt-field__repeater-modal-fields">
|
||||
<AksField
|
||||
v-for="field in (schema.fields || [])"
|
||||
v-for="field in (schema.fields || []).filter(field => matchDependency(field.dependsOn, repeaterDraft))"
|
||||
:key="field.key"
|
||||
:schema="field"
|
||||
:model-value="repeaterDraft[field.key]"
|
||||
@@ -564,6 +627,10 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const repeaterLength = computed(() => Array.isArray(props.modelValue) ? props.modelValue.length : 0)
|
||||
const reachedRepeaterMinimum = computed(() => repeaterLength.value <= Number(props.schema.min || 0))
|
||||
const reachedRepeaterMaximum = computed(() => Number(props.schema.max || 0) > 0 && repeaterLength.value >= Number(props.schema.max))
|
||||
|
||||
function fieldsetValue(field) {
|
||||
const current = props.modelValue && typeof props.modelValue === 'object' && !Array.isArray(props.modelValue)
|
||||
? props.modelValue
|
||||
@@ -584,12 +651,94 @@ const normalizedOptions = computed(() => {
|
||||
return opts.map(opt => {
|
||||
if (typeof opt === 'string') return { label: opt, value: opt }
|
||||
if (typeof opt === 'object' && opt !== null) {
|
||||
return { label: opt.label ?? opt.name ?? opt.value, value: opt.value ?? opt.label ?? opt.name }
|
||||
return { ...opt, label: opt.label ?? opt.name ?? opt.value, value: opt.value ?? opt.label ?? opt.name }
|
||||
}
|
||||
return { label: String(opt), value: opt }
|
||||
})
|
||||
})
|
||||
|
||||
const visibleNestedFields = computed(() => (props.schema.fields || []).filter(field => matchDependency(field.dependsOn, props.modelValue)))
|
||||
const linkTargetOptions = [{ label: '当前窗口', value: '' }, { label: '当前窗口', value: '_self' }, { label: '新窗口', value: '_blank' }]
|
||||
|
||||
function matchDependency(rule, sourceValue) {
|
||||
if (!rule) return true
|
||||
const source = sourceValue && typeof sourceValue === 'object' ? sourceValue : {}
|
||||
if (rule.all) return rule.all.every(item => matchDependency(item, source))
|
||||
if (rule.any) return rule.any.some(item => matchDependency(item, source))
|
||||
const value = source[rule.key]
|
||||
if (rule.operator === 'truthy') return !!value
|
||||
if (rule.operator === 'falsy') return !value
|
||||
if (rule.operator === 'not_empty') return Array.isArray(value) ? value.length > 0 : value !== '' && value != null
|
||||
if (rule.operator === 'empty') return Array.isArray(value) ? value.length === 0 : value === '' || value == null
|
||||
if (rule.operator === 'eq') return value === rule.value
|
||||
if (rule.operator === 'neq') return value !== rule.value
|
||||
return rule.value === undefined || value === rule.value
|
||||
}
|
||||
|
||||
function updateLink(key, value) {
|
||||
emit('update:modelValue', { ...(props.modelValue || {}), [key]: value })
|
||||
}
|
||||
|
||||
function selectedOptionLabel(options, value) {
|
||||
return options.find(option => option.value === value)?.label || String(value)
|
||||
}
|
||||
|
||||
function moveSelectedValue(index, delta) {
|
||||
if (!Array.isArray(props.modelValue)) return
|
||||
const target = index + delta
|
||||
if (target < 0 || target >= props.modelValue.length) return
|
||||
const values = [...props.modelValue]
|
||||
;[values[index], values[target]] = [values[target], values[index]]
|
||||
emit('update:modelValue', values)
|
||||
}
|
||||
|
||||
const sortableItems = computed(() => {
|
||||
const definitions = new Map((props.schema.items || []).map(item => [item.key, item]))
|
||||
const order = Object.keys(props.modelValue || {}).filter(key => definitions.has(key))
|
||||
for (const key of definitions.keys()) if (!order.includes(key)) order.push(key)
|
||||
return order.map(key => definitions.get(key))
|
||||
})
|
||||
|
||||
function updateSortable(itemKey, fieldKey, value) {
|
||||
const current = props.modelValue || {}
|
||||
emit('update:modelValue', { ...current, [itemKey]: { ...(current[itemKey] || {}), [fieldKey]: value } })
|
||||
}
|
||||
|
||||
function moveSortable(index, delta) {
|
||||
const items = [...sortableItems.value]
|
||||
const target = index + delta
|
||||
if (target < 0 || target >= items.length) return
|
||||
;[items[index], items[target]] = [items[target], items[index]]
|
||||
const current = props.modelValue || {}
|
||||
emit('update:modelValue', Object.fromEntries(items.map(item => [item.key, current[item.key] || {}])))
|
||||
}
|
||||
|
||||
const asyncOptions = ref([])
|
||||
const asyncLoading = ref(false)
|
||||
let asyncTimer = null
|
||||
|
||||
async function loadAsyncOptions(query = '') {
|
||||
if (props.schema.source === 'publish-types') {
|
||||
asyncOptions.value = normalizedOptions.value
|
||||
return
|
||||
}
|
||||
asyncLoading.value = true
|
||||
try {
|
||||
const selected = Array.isArray(props.modelValue) ? props.modelValue : (props.modelValue ? [props.modelValue] : [])
|
||||
const response = await api.searchPageDisplayOptions(props.schema.source, query, query ? [] : selected)
|
||||
asyncOptions.value = response?.data || []
|
||||
} catch (error) {
|
||||
asyncOptions.value = []
|
||||
} finally {
|
||||
asyncLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleAsyncSearch(query) {
|
||||
clearTimeout(asyncTimer)
|
||||
asyncTimer = setTimeout(() => loadAsyncOptions(query), 250)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============ Taxonomy Select (async) ============
|
||||
@@ -621,6 +770,9 @@ onMounted(() => {
|
||||
if (props.schema.type === 'user_select') {
|
||||
loadInitialUsers()
|
||||
}
|
||||
if (props.schema.type === 'async_select') {
|
||||
loadAsyncOptions()
|
||||
}
|
||||
})
|
||||
|
||||
// ============ User Select (async search) ============
|
||||
@@ -740,12 +892,14 @@ function createRepeaterItem() {
|
||||
}
|
||||
|
||||
function addRepeaterItem() {
|
||||
if (reachedRepeaterMaximum.value) return
|
||||
const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
|
||||
current.push(createRepeaterItem())
|
||||
emit('update:modelValue', current)
|
||||
}
|
||||
|
||||
function removeRepeaterItem(index) {
|
||||
if (reachedRepeaterMinimum.value) return
|
||||
const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
|
||||
current.splice(index, 1)
|
||||
emit('update:modelValue', current)
|
||||
@@ -807,6 +961,7 @@ function getRepeaterPreviewDesc(item) {
|
||||
}
|
||||
|
||||
function openRepeaterModal(index) {
|
||||
if (index < 0 && reachedRepeaterMaximum.value) return
|
||||
if (props.schema.disabled) return
|
||||
if (index >= 0) {
|
||||
const item = (props.modelValue || [])[index] || {}
|
||||
@@ -998,6 +1153,74 @@ function confirmRepeaterModal() {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.aksrt-field__palette {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-field__palette-item {
|
||||
width: 34px;
|
||||
height: 26px;
|
||||
padding: 0;
|
||||
border: 3px solid #fff;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 0 1px #d8d8d8;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.aksrt-field__palette-item.is-active {
|
||||
box-shadow: 0 0 0 2px #2f8fce;
|
||||
}
|
||||
|
||||
.aksrt-field__link {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(220px, 1fr) 120px minmax(140px, .5fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-field__sortable {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-field__selected-order {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.aksrt-field__selected-order-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 5px 9px;
|
||||
border: 1px solid #e6e8eb;
|
||||
border-radius: 3px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.aksrt-field__sortable-item {
|
||||
border: 1px solid #e5e5e5;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.aksrt-field__sortable-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 12px;
|
||||
background: #f7f7f7;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.aksrt-field__sortable-body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
/* Repeater */
|
||||
.aksrt-field__fieldset {
|
||||
padding: 14px;
|
||||
@@ -1059,6 +1282,11 @@ function confirmRepeaterModal() {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.aksrt-field__link,
|
||||
.aksrt-field__sortable-body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Repeater list-modal */
|
||||
|
||||
@@ -172,8 +172,16 @@ function matchDependsOn(dependsOn) {
|
||||
return !!props.formData[dependsOn]
|
||||
}
|
||||
if (typeof dependsOn === 'object') {
|
||||
const { key, value, values, not } = dependsOn
|
||||
if (Array.isArray(dependsOn.all)) return dependsOn.all.every(matchDependsOn)
|
||||
if (Array.isArray(dependsOn.any)) return dependsOn.any.some(matchDependsOn)
|
||||
const { key, value, values, not, operator } = dependsOn
|
||||
const currentVal = props.formData[key]
|
||||
if (operator === 'truthy') return !!currentVal
|
||||
if (operator === 'falsy') return !currentVal
|
||||
if (operator === 'not_empty') return Array.isArray(currentVal) ? currentVal.length > 0 : currentVal !== '' && currentVal != null
|
||||
if (operator === 'empty') return Array.isArray(currentVal) ? currentVal.length === 0 : currentVal === '' || currentVal == null
|
||||
if (operator === 'eq') return currentVal === value
|
||||
if (operator === 'neq') return currentVal !== value
|
||||
if (not) {
|
||||
if (Array.isArray(values)) {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
|
||||
@@ -1,313 +0,0 @@
|
||||
<template>
|
||||
<div class="aksrt-module-canvas">
|
||||
<div class="aksrt-module-canvas__toolbar">
|
||||
<n-space>
|
||||
<n-dropdown :options="addModuleOptions" trigger="click" @select="handleAddModule">
|
||||
<n-button type="primary" size="small">
|
||||
<template #icon><FaIcon icon="fa fas fa-plus" :size="12" /></template>
|
||||
添加模块
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<n-button size="small" @click="$emit('pick-preset')">
|
||||
<template #icon><FaIcon icon="fa fas fa-magic" :size="12" /></template>
|
||||
应用预设
|
||||
</n-button>
|
||||
<n-button size="small" @click="$emit('export')">
|
||||
<template #icon><FaIcon icon="fa fas fa-download" :size="12" /></template>
|
||||
导出
|
||||
</n-button>
|
||||
<n-button size="small" @click="$emit('import')">
|
||||
<template #icon><FaIcon icon="fa fas fa-upload" :size="12" /></template>
|
||||
导入
|
||||
</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
|
||||
<div v-if="modules.length === 0" class="aksrt-module-canvas__empty">
|
||||
<FaIcon icon="fa fas fa-th-large" :size="32" color="#c0c4cc" />
|
||||
<p>暂无首页模块,点击"添加模块"或"应用预设"开始配置。</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="aksrt-module-canvas__list">
|
||||
<div
|
||||
v-for="(mod, index) in modules"
|
||||
:key="mod.id || index"
|
||||
class="aksrt-module-canvas__item"
|
||||
:class="{
|
||||
'aksrt-module-canvas__item--selected': selectedIndex === index,
|
||||
'aksrt-module-canvas__item--disabled': !mod.enabled,
|
||||
'aksrt-module-canvas__item--dragging': dragIndex === index,
|
||||
'aksrt-module-canvas__item--drag-over': dragOverIndex === index && dragOverIndex !== dragIndex,
|
||||
}"
|
||||
:draggable="true"
|
||||
@click="$emit('select', index)"
|
||||
@dragstart="handleDragStart(index, $event)"
|
||||
@dragover.prevent="handleDragOver(index)"
|
||||
@dragleave="handleDragLeave"
|
||||
@drop.prevent="handleDrop(index)"
|
||||
@dragend="handleDragEnd"
|
||||
>
|
||||
<div class="aksrt-module-canvas__item-handle" title="拖拽排序">
|
||||
<FaIcon icon="fa fas fa-bars" :size="12" color="#c0c4cc" />
|
||||
</div>
|
||||
<div class="aksrt-module-canvas__item-icon">
|
||||
<FaIcon :icon="getModuleIcon(mod.type)" :size="14" />
|
||||
</div>
|
||||
<div class="aksrt-module-canvas__item-info">
|
||||
<span class="aksrt-module-canvas__item-label">{{ getModuleLabel(mod.type) }}</span>
|
||||
<span v-if="mod.title" class="aksrt-module-canvas__item-title">{{ mod.title }}</span>
|
||||
</div>
|
||||
<div class="aksrt-module-canvas__item-actions">
|
||||
<n-switch
|
||||
:value="mod.enabled !== false"
|
||||
size="small"
|
||||
@update:value="(val) => $emit('toggle', index, val)"
|
||||
@click.stop
|
||||
/>
|
||||
<n-button
|
||||
size="tiny"
|
||||
quaternary
|
||||
title="复制"
|
||||
@click.stop="$emit('copy', index)"
|
||||
>
|
||||
<FaIcon icon="fa fas fa-copy" :size="11" />
|
||||
</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
quaternary
|
||||
type="error"
|
||||
title="删除"
|
||||
@click.stop="$emit('remove', index)"
|
||||
>
|
||||
<FaIcon icon="fa fas fa-trash-alt" :size="11" />
|
||||
</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
quaternary
|
||||
:disabled="index === 0"
|
||||
title="上移"
|
||||
@click.stop="$emit('move', index, -1)"
|
||||
>
|
||||
<FaIcon icon="fa fas fa-chevron-up" :size="11" />
|
||||
</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
quaternary
|
||||
:disabled="index === modules.length - 1"
|
||||
title="下移"
|
||||
@click.stop="$emit('move', index, 1)"
|
||||
>
|
||||
<FaIcon icon="fa fas fa-chevron-down" :size="11" />
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { MODULE_LIBRARY } from '@/utils/home-module-schema'
|
||||
import FaIcon from '@/components/form/FaIcon.vue'
|
||||
|
||||
const props = defineProps({
|
||||
modules: { type: Array, default: () => [] },
|
||||
selectedIndex: { type: Number, default: -1 },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['select', 'add', 'remove', 'copy', 'toggle', 'move', 'pick-preset', 'export', 'import'])
|
||||
|
||||
// Drag-and-drop state
|
||||
const dragIndex = ref(-1)
|
||||
const dragOverIndex = ref(-1)
|
||||
|
||||
function handleDragStart(index, event) {
|
||||
dragIndex.value = index
|
||||
event.dataTransfer.effectAllowed = 'move'
|
||||
event.dataTransfer.setData('text/plain', String(index))
|
||||
}
|
||||
|
||||
function handleDragOver(index) {
|
||||
if (dragIndex.value !== index) {
|
||||
dragOverIndex.value = index
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragLeave() {
|
||||
dragOverIndex.value = -1
|
||||
}
|
||||
|
||||
function handleDrop(targetIndex) {
|
||||
if (dragIndex.value >= 0 && dragIndex.value !== targetIndex) {
|
||||
const offset = targetIndex - dragIndex.value
|
||||
emit('move', dragIndex.value, offset)
|
||||
}
|
||||
dragIndex.value = -1
|
||||
dragOverIndex.value = -1
|
||||
}
|
||||
|
||||
function handleDragEnd() {
|
||||
dragIndex.value = -1
|
||||
dragOverIndex.value = -1
|
||||
}
|
||||
|
||||
const addModuleOptions = computed(() =>
|
||||
MODULE_LIBRARY.map(m => ({
|
||||
label: m.label,
|
||||
key: m.type,
|
||||
icon: () => {
|
||||
const iconMap = {
|
||||
image: 'fa fas fa-image', rectangle: 'fa fas fa-square', folder: 'fa fas fa-folder',
|
||||
switcher: 'fa fas fa-columns', trophy: 'fa fas fa-trophy', clock: 'fa fas fa-clock',
|
||||
tag: 'fa fas fa-tag', link: 'fa fas fa-link', code: 'fa fas fa-code', fire: 'fa fas fa-fire',
|
||||
grid: 'fa fas fa-th', 'font-colors': 'fa fas fa-font', 'th-large': 'fa fas fa-th-large',
|
||||
swap: 'fa fas fa-arrows-alt-v', 'question-circle': 'fa fas fa-question-circle',
|
||||
table: 'fa fas fa-table', star: 'fa fas fa-star', handshake: 'fa fas fa-handshake',
|
||||
'shopping-bag': 'fa fas fa-shopping-bag',
|
||||
}
|
||||
return h('i', { class: iconMap[m.icon] || 'fa fas fa-cube', style: 'font-size:12px;margin-right:6px;' })
|
||||
},
|
||||
}))
|
||||
)
|
||||
|
||||
function getModuleIcon(type) {
|
||||
const meta = MODULE_LIBRARY.find(m => m.type === type)
|
||||
const iconMap = {
|
||||
image: 'fa fas fa-image', rectangle: 'fa fas fa-square', folder: 'fa fas fa-folder',
|
||||
switcher: 'fa fas fa-columns', trophy: 'fa fas fa-trophy', clock: 'fa fas fa-clock',
|
||||
tag: 'fa fas fa-tag', link: 'fa fas fa-link', code: 'fa fas fa-code', fire: 'fa fas fa-fire',
|
||||
grid: 'fa fas fa-th', 'font-colors': 'fa fas fa-font', 'th-large': 'fa fas fa-th-large',
|
||||
swap: 'fa fas fa-arrows-alt-v', 'question-circle': 'fa fas fa-question-circle',
|
||||
table: 'fa fas fa-table', star: 'fa fas fa-star', handshake: 'fa fas fa-handshake',
|
||||
'shopping-bag': 'fa fas fa-shopping-bag',
|
||||
}
|
||||
return iconMap[meta?.icon] || 'fa fas fa-cube'
|
||||
}
|
||||
|
||||
function getModuleLabel(type) {
|
||||
const meta = MODULE_LIBRARY.find(m => m.type === type)
|
||||
return meta?.label || type
|
||||
}
|
||||
|
||||
function handleAddModule(type) {
|
||||
emit('add', type)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { h } from 'vue'
|
||||
export default {
|
||||
// h is used in addModuleOptions computed icon renderer
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-module-canvas__toolbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px 24px;
|
||||
border: 2px dashed #e0e0e0;
|
||||
border-radius: 8px;
|
||||
color: #999;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__empty p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__item:hover {
|
||||
border-color: #d0d0d0;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__item--selected {
|
||||
border-color: #1890ff;
|
||||
background: #e6f4ff;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__item--dragging {
|
||||
opacity: 0.5;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__item--drag-over {
|
||||
border-color: #1890ff;
|
||||
border-width: 2px;
|
||||
background: #f0f7ff;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__item--disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__item-handle {
|
||||
cursor: grab;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__item-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
background: #f5f7fa;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__item-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__item-label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__item-title {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.aksrt-module-canvas__item-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,399 +0,0 @@
|
||||
<template>
|
||||
<div class="aksrt-module-inspector">
|
||||
<div v-if="!module" class="aksrt-module-inspector__empty">
|
||||
<FaIcon icon="fa fas fa-mouse-pointer" :size="24" color="#c0c4cc" />
|
||||
<p>选择左侧模块查看和编辑配置</p>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="aksrt-module-inspector__header">
|
||||
<FaIcon :icon="getModuleIcon(module.type)" :size="16" />
|
||||
<span class="aksrt-module-inspector__type">{{ getModuleLabel(module.type) }}</span>
|
||||
</div>
|
||||
|
||||
<n-scrollbar style="max-height: calc(100vh - 320px);">
|
||||
<n-form
|
||||
:model="localConfig"
|
||||
label-placement="top"
|
||||
size="small"
|
||||
class="aksrt-module-inspector__form"
|
||||
>
|
||||
<!-- Common fields -->
|
||||
<n-form-item label="模块标题">
|
||||
<n-input
|
||||
:value="module.title || ''"
|
||||
placeholder="留空使用默认标题"
|
||||
@update:value="(val) => updateField('title', val)"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="启用模块">
|
||||
<n-switch
|
||||
:value="module.enabled !== false"
|
||||
@update:value="(val) => updateField('enabled', val)"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-divider />
|
||||
|
||||
<!-- Type-specific fields from schema -->
|
||||
<template v-if="metaFields.length > 0">
|
||||
<n-form-item
|
||||
v-for="field in metaFields"
|
||||
:key="field.key"
|
||||
:label="field.label"
|
||||
>
|
||||
<!-- Switch -->
|
||||
<n-switch
|
||||
v-if="field.type === 'switch'"
|
||||
:value="localConfig[field.key] ?? field.default ?? false"
|
||||
@update:value="(val) => updateConfig(field.key, val)"
|
||||
/>
|
||||
|
||||
<!-- Number -->
|
||||
<n-input-number
|
||||
v-else-if="field.type === 'number'"
|
||||
:value="localConfig[field.key] ?? field.default ?? 0"
|
||||
:min="field.min"
|
||||
:max="field.max"
|
||||
style="width: 100%;"
|
||||
@update:value="(val) => updateConfig(field.key, val)"
|
||||
/>
|
||||
|
||||
<!-- Text -->
|
||||
<n-input
|
||||
v-else-if="field.type === 'text'"
|
||||
:value="localConfig[field.key] ?? field.default ?? ''"
|
||||
:placeholder="field.placeholder || ''"
|
||||
@update:value="(val) => updateConfig(field.key, val)"
|
||||
/>
|
||||
|
||||
<!-- Textarea -->
|
||||
<n-input
|
||||
v-else-if="field.type === 'textarea'"
|
||||
type="textarea"
|
||||
:value="localConfig[field.key] ?? field.default ?? ''"
|
||||
:rows="field.rows || 3"
|
||||
@update:value="(val) => updateConfig(field.key, val)"
|
||||
/>
|
||||
|
||||
<!-- Select -->
|
||||
<n-select
|
||||
v-else-if="field.type === 'select'"
|
||||
:value="localConfig[field.key] ?? field.default"
|
||||
:options="field.options || []"
|
||||
@update:value="(val) => updateConfig(field.key, val)"
|
||||
/>
|
||||
|
||||
<!-- Radio -->
|
||||
<n-radio-group
|
||||
v-else-if="field.type === 'radio'"
|
||||
:value="localConfig[field.key] ?? field.default"
|
||||
@update:value="(val) => updateConfig(field.key, val)"
|
||||
>
|
||||
<n-space>
|
||||
<n-radio
|
||||
v-for="opt in field.options"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
|
||||
<!-- Category (single) -->
|
||||
<n-select
|
||||
v-else-if="field.type === 'cat'"
|
||||
:value="localConfig[field.key] ?? field.default ?? 0"
|
||||
:options="categoryOptions"
|
||||
filterable
|
||||
placeholder="选择分类"
|
||||
@update:value="(val) => updateConfig(field.key, val)"
|
||||
/>
|
||||
|
||||
<!-- Categories (multiple) -->
|
||||
<n-select
|
||||
v-else-if="field.type === 'cats'"
|
||||
:value="localConfig[field.key] ?? field.default ?? []"
|
||||
:options="categoryOptions"
|
||||
multiple
|
||||
filterable
|
||||
placeholder="选择分类"
|
||||
@update:value="(val) => updateConfig(field.key, val)"
|
||||
/>
|
||||
|
||||
<!-- Media Picker -->
|
||||
<div v-else-if="field.type === 'media'" class="aksrt-module-inspector__media">
|
||||
<div v-if="localConfig[field.key]" class="aksrt-module-inspector__media-preview">
|
||||
<img :src="localConfig[field.key]" alt="" />
|
||||
<n-button size="tiny" quaternary type="error" @click="updateConfig(field.key, '')">
|
||||
移除
|
||||
</n-button>
|
||||
</div>
|
||||
<n-input
|
||||
v-else
|
||||
:value="localConfig[field.key] ?? ''"
|
||||
placeholder="输入图片 URL 或点击下方选择"
|
||||
@update:value="(val) => updateConfig(field.key, val)"
|
||||
/>
|
||||
<n-button size="tiny" quaternary @click="openMediaPicker(field.key)">
|
||||
<template #icon><FaIcon icon="fa fas fa-image" :size="12" /></template>
|
||||
从媒体库选择
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<!-- Repeater -->
|
||||
<div v-else-if="field.type === 'repeater'" class="aksrt-module-inspector__repeater">
|
||||
<div
|
||||
v-for="(item, idx) in (localConfig[field.key] ?? [])"
|
||||
:key="idx"
|
||||
class="aksrt-module-inspector__repeater-item"
|
||||
>
|
||||
<div class="aksrt-module-inspector__repeater-fields">
|
||||
<n-input
|
||||
v-for="sub in (field.fields || [])"
|
||||
:key="sub.key"
|
||||
:value="item[sub.key] ?? ''"
|
||||
:placeholder="sub.placeholder || sub.label || ''"
|
||||
size="small"
|
||||
@update:value="(val) => updateRepeaterItem(field.key, idx, sub.key, val)"
|
||||
/>
|
||||
</div>
|
||||
<n-button size="tiny" quaternary type="error" @click="removeRepeaterItem(field.key, idx)">
|
||||
<FaIcon icon="fa fas fa-times" :size="10" />
|
||||
</n-button>
|
||||
</div>
|
||||
<n-button size="tiny" quaternary @click="addRepeaterItem(field.key, field.fields)">
|
||||
<template #icon><FaIcon icon="fa fas fa-plus" :size="10" /></template>
|
||||
添加一项
|
||||
</n-button>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<n-text v-else depth="3" style="font-size: 12px;">
|
||||
此模块类型暂无可配置字段。
|
||||
</n-text>
|
||||
</n-form>
|
||||
</n-scrollbar>
|
||||
</template>
|
||||
|
||||
<!-- Image URL input modal (replaces native prompt) -->
|
||||
<n-modal v-model:show="showImageUrlInput" preset="card" title="输入图片 URL" style="max-width: 400px;" :bordered="false">
|
||||
<n-input v-model:value="imageUrlValue" placeholder="https://example.com/image.jpg" @keyup.enter="confirmImageUrl" />
|
||||
<template #footer>
|
||||
<n-space justify="end">
|
||||
<n-button @click="showImageUrlInput = false">取消</n-button>
|
||||
<n-button type="primary" @click="confirmImageUrl">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch, onMounted } from 'vue'
|
||||
import { MODULE_LIBRARY } from '@/utils/home-module-schema'
|
||||
import FaIcon from '@/components/form/FaIcon.vue'
|
||||
import api from '@/api/index.js'
|
||||
|
||||
const props = defineProps({
|
||||
module: { type: Object, default: null },
|
||||
moduleIndex: { type: Number, default: -1 },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update'])
|
||||
|
||||
const categoryOptions = ref([])
|
||||
|
||||
const meta = computed(() => {
|
||||
if (!props.module) return null
|
||||
return MODULE_LIBRARY.find(m => m.type === props.module.type) || null
|
||||
})
|
||||
|
||||
const metaFields = computed(() => {
|
||||
return meta.value?.fields || []
|
||||
})
|
||||
|
||||
const localConfig = computed(() => {
|
||||
return props.module?.config || {}
|
||||
})
|
||||
|
||||
function getModuleIcon(type) {
|
||||
const m = MODULE_LIBRARY.find(x => x.type === type)
|
||||
const iconMap = {
|
||||
image: 'fa fas fa-image', rectangle: 'fa fas fa-square', folder: 'fa fas fa-folder',
|
||||
switcher: 'fa fas fa-columns', trophy: 'fa fas fa-trophy', clock: 'fa fas fa-clock',
|
||||
tag: 'fa fas fa-tag', link: 'fa fas fa-link', code: 'fa fas fa-code', fire: 'fa fas fa-fire',
|
||||
grid: 'fa fas fa-th', 'font-colors': 'fa fas fa-font', 'th-large': 'fa fas fa-th-large',
|
||||
swap: 'fa fas fa-arrows-alt-v', 'question-circle': 'fa fas fa-question-circle',
|
||||
table: 'fa fas fa-table', star: 'fa fas fa-star', handshake: 'fa fas fa-handshake',
|
||||
'shopping-bag': 'fa fas fa-shopping-bag',
|
||||
}
|
||||
return iconMap[m?.icon] || 'fa fas fa-cube'
|
||||
}
|
||||
|
||||
function getModuleLabel(type) {
|
||||
const m = MODULE_LIBRARY.find(x => x.type === type)
|
||||
return m?.label || type
|
||||
}
|
||||
|
||||
function updateField(key, value) {
|
||||
emit('update', props.moduleIndex, key, value)
|
||||
}
|
||||
|
||||
function updateConfig(key, value) {
|
||||
const newConfig = { ...(props.module?.config || {}), [key]: value }
|
||||
emit('update', props.moduleIndex, 'config', newConfig)
|
||||
}
|
||||
|
||||
// Image URL input state (replaces native prompt)
|
||||
const showImageUrlInput = ref(false)
|
||||
const imageUrlFieldKey = ref('')
|
||||
const imageUrlValue = ref('')
|
||||
|
||||
function openMediaPicker(fieldKey) {
|
||||
if (!window.wp || !window.wp.media) {
|
||||
// Fallback: show inline input instead of native prompt
|
||||
imageUrlFieldKey.value = fieldKey
|
||||
imageUrlValue.value = localConfig.value[fieldKey] || ''
|
||||
showImageUrlInput.value = true
|
||||
return
|
||||
}
|
||||
const frame = wp.media({
|
||||
title: '选择图片',
|
||||
button: { text: '使用此图片' },
|
||||
multiple: false,
|
||||
library: { type: 'image' },
|
||||
})
|
||||
frame.on('select', function () {
|
||||
const attachment = frame.state().get('selection').first().toJSON()
|
||||
updateConfig(fieldKey, attachment.url)
|
||||
})
|
||||
frame.open()
|
||||
}
|
||||
|
||||
function confirmImageUrl() {
|
||||
if (imageUrlValue.value.trim()) {
|
||||
updateConfig(imageUrlFieldKey.value, imageUrlValue.value.trim())
|
||||
}
|
||||
showImageUrlInput.value = false
|
||||
}
|
||||
|
||||
function addRepeaterItem(fieldKey, subFields) {
|
||||
const current = [...(props.module?.config?.[fieldKey] || [])]
|
||||
const newItem = {}
|
||||
;(subFields || []).forEach(f => { newItem[f.key] = f.default ?? '' })
|
||||
current.push(newItem)
|
||||
updateConfig(fieldKey, current)
|
||||
}
|
||||
|
||||
function removeRepeaterItem(fieldKey, index) {
|
||||
const current = [...(props.module?.config?.[fieldKey] || [])]
|
||||
current.splice(index, 1)
|
||||
updateConfig(fieldKey, current)
|
||||
}
|
||||
|
||||
function updateRepeaterItem(fieldKey, index, subKey, value) {
|
||||
const current = [...(props.module?.config?.[fieldKey] || [])]
|
||||
if (current[index]) {
|
||||
current[index] = { ...current[index], [subKey]: value }
|
||||
updateConfig(fieldKey, current)
|
||||
}
|
||||
}
|
||||
|
||||
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 */ }
|
||||
}
|
||||
|
||||
onMounted(loadCategories)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-module-inspector__empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px 24px;
|
||||
color: #999;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.aksrt-module-inspector__empty p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.aksrt-module-inspector__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.aksrt-module-inspector__type {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.aksrt-module-inspector__form {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.aksrt-module-inspector__media {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aksrt-module-inspector__media-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-module-inspector__media-preview img {
|
||||
width: 80px;
|
||||
height: 60px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.aksrt-module-inspector__repeater {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aksrt-module-inspector__repeater-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
background: #fafafa;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.aksrt-module-inspector__repeater-fields {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,143 +0,0 @@
|
||||
<template>
|
||||
<n-modal
|
||||
:show="show"
|
||||
title="应用预设模板"
|
||||
preset="dialog"
|
||||
style="width: 560px;"
|
||||
:show-icon="false"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<div class="aksrt-preset-picker">
|
||||
<p class="aksrt-preset-picker__hint">
|
||||
选择一个预设模板快速配置首页模块。应用预设将替换当前所有模块。
|
||||
</p>
|
||||
|
||||
<div class="aksrt-preset-picker__list">
|
||||
<div
|
||||
v-for="preset in presets"
|
||||
:key="preset.key"
|
||||
class="aksrt-preset-picker__item"
|
||||
:class="{ 'aksrt-preset-picker__item--selected': selectedKey === preset.key }"
|
||||
@click="selectedKey = preset.key"
|
||||
>
|
||||
<div class="aksrt-preset-picker__item-header">
|
||||
<strong>{{ preset.label }}</strong>
|
||||
<n-tag size="small" type="info">{{ preset.modules.length }} 个模块</n-tag>
|
||||
</div>
|
||||
<p class="aksrt-preset-picker__item-desc">{{ preset.desc }}</p>
|
||||
<div class="aksrt-preset-picker__item-modules">
|
||||
<n-tag
|
||||
v-for="(mod, i) in preset.modules"
|
||||
:key="i"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
class="aksrt-preset-picker__module-tag"
|
||||
>
|
||||
{{ getModuleLabel(mod.type) }}
|
||||
</n-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="$emit('close')">取消</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
:disabled="!selectedKey"
|
||||
@click="handleApply"
|
||||
>
|
||||
应用预设
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { PRESET_TEMPLATES, MODULE_LIBRARY } from '@/utils/home-module-schema'
|
||||
|
||||
defineProps({
|
||||
show: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close', 'apply'])
|
||||
const selectedKey = ref('')
|
||||
const presets = PRESET_TEMPLATES
|
||||
|
||||
function getModuleLabel(type) {
|
||||
const m = MODULE_LIBRARY.find(x => x.type === type)
|
||||
return m?.label || type
|
||||
}
|
||||
|
||||
function handleApply() {
|
||||
const preset = presets.find(p => p.key === selectedKey.value)
|
||||
if (preset) {
|
||||
emit('apply', preset)
|
||||
selectedKey.value = ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-preset-picker__hint {
|
||||
margin: 0 0 16px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.aksrt-preset-picker__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-preset-picker__item {
|
||||
padding: 12px;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.aksrt-preset-picker__item:hover {
|
||||
border-color: #d0d0d0;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.aksrt-preset-picker__item--selected {
|
||||
border-color: #1890ff;
|
||||
background: #e6f4ff;
|
||||
}
|
||||
|
||||
.aksrt-preset-picker__item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.aksrt-preset-picker__item-header strong {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.aksrt-preset-picker__item-desc {
|
||||
margin: 0 0 8px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.aksrt-preset-picker__item-modules {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.aksrt-preset-picker__module-tag {
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,119 +1,28 @@
|
||||
import { renderFaIcon, schemaPage, customPage } from './helpers'
|
||||
import { renderFaIcon, schemaPage } from './helpers'
|
||||
|
||||
const pages = [
|
||||
['header-nav', '顶部导航栏'],
|
||||
['header-components', '顶部多功能组件'],
|
||||
['footer', '底部页脚'],
|
||||
['homepage', '首页配置'],
|
||||
['category', '分类页面'],
|
||||
['tag', '标签页面'],
|
||||
['user-home', '用户主页'],
|
||||
['other', '其它页面'],
|
||||
]
|
||||
|
||||
export const pageDisplaySection = {
|
||||
key: 'page-display',
|
||||
label: '页面&显示',
|
||||
icon: renderFaIcon('fa fas fa-desktop'),
|
||||
route: '/page-display',
|
||||
children: [
|
||||
schemaPage({
|
||||
key: 'page-display-header-nav',
|
||||
name: 'PageDisplayHeaderNav',
|
||||
label: '顶部导航栏',
|
||||
route: '/page-display/header-nav',
|
||||
title: '顶部导航栏',
|
||||
desc: '导航栏布局、菜单位置、LOGO 与搜索入口配置。',
|
||||
canonicalDomain: 'page_display',
|
||||
section: 'header-nav',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'page-display-background',
|
||||
name: 'PageDisplayBackground',
|
||||
label: '全局背景',
|
||||
route: '/page-display/background',
|
||||
title: '全局背景',
|
||||
desc: '配置站点全局背景图、背景色、暗色模式背景和遮罩。',
|
||||
canonicalDomain: 'page_display',
|
||||
section: 'background',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'page-display-menu-decoration',
|
||||
name: 'PageDisplayMenuDecoration',
|
||||
label: '菜单装饰',
|
||||
route: '/page-display/menu-decoration',
|
||||
title: '菜单装饰',
|
||||
desc: '配置导航菜单的图标、徽章、下拉样式和图文卡片子菜单。',
|
||||
canonicalDomain: 'page_display',
|
||||
section: 'menu-decoration',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'page-display-header-components',
|
||||
name: 'PageDisplayHeaderComponents',
|
||||
label: '顶部多功能组件',
|
||||
route: '/page-display/header-components',
|
||||
title: '顶部多功能组件',
|
||||
desc: '配置顶部组件显示范围、幻灯片内容、轮播参数、搜索覆盖层与图标卡片。',
|
||||
canonicalDomain: 'page_display',
|
||||
section: 'header-components',
|
||||
}),
|
||||
customPage({
|
||||
key: 'page-display-banner',
|
||||
name: 'PageDisplayBanner',
|
||||
label: 'Banner 轮播',
|
||||
route: '/page-display/banner',
|
||||
title: 'Banner 管理',
|
||||
desc: '管理首页内容流 Banner 幻灯片(图片、文案、跳转、启用状态)。',
|
||||
component: 'BannerManager',
|
||||
canonicalDomain: 'page_display',
|
||||
section: 'banner',
|
||||
}),
|
||||
customPage({
|
||||
key: 'page-display-footer',
|
||||
name: 'PageDisplayFooter',
|
||||
label: '底部页脚',
|
||||
route: '/page-display/footer',
|
||||
title: '底部页脚',
|
||||
desc: '页脚布局、友链、版权信息配置。',
|
||||
component: 'FooterSettings',
|
||||
}),
|
||||
customPage({
|
||||
key: 'page-display-homepage',
|
||||
name: 'PageDisplayHomepage',
|
||||
label: '首页配置',
|
||||
route: '/page-display/homepage',
|
||||
title: '首页配置',
|
||||
desc: '首页内容模型、主列表、分类 Tab 与首页交互配置。',
|
||||
component: 'HomepageSettings',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'page-display-category',
|
||||
name: 'PageDisplayCategory',
|
||||
label: '分类页面',
|
||||
route: '/page-display/category',
|
||||
title: '分类页面',
|
||||
desc: '分类归档页的头图、简介、列表模式、侧栏配置。',
|
||||
canonicalDomain: 'page_display',
|
||||
section: 'category',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'page-display-tag',
|
||||
name: 'PageDisplayTag',
|
||||
label: '标签页面',
|
||||
route: '/page-display/tag',
|
||||
title: '标签页面',
|
||||
desc: '标签归档页的展示样式与配置。',
|
||||
canonicalDomain: 'page_display',
|
||||
section: 'tag',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'page-display-user-home',
|
||||
name: 'PageDisplayUserHome',
|
||||
label: '用户主页',
|
||||
route: '/page-display/user-home',
|
||||
title: '用户主页',
|
||||
desc: '用户公开主页的布局与展示配置。',
|
||||
canonicalDomain: 'page_display',
|
||||
section: 'user-home',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'page-display-other',
|
||||
name: 'PageDisplayOther',
|
||||
label: '其它页面',
|
||||
route: '/page-display/other',
|
||||
title: '其它页面',
|
||||
desc: '404 页面、搜索结果页等其它页面配置。',
|
||||
canonicalDomain: 'page_display',
|
||||
section: 'other',
|
||||
}),
|
||||
],
|
||||
children: pages.map(([section, label]) => schemaPage({
|
||||
key: `page-display-${section}`,
|
||||
name: `PageDisplay${section.split('-').map(part => part[0].toUpperCase() + part.slice(1)).join('')}`,
|
||||
label,
|
||||
route: `/page-display/${section}`,
|
||||
title: label,
|
||||
canonicalDomain: 'page_display',
|
||||
section,
|
||||
})),
|
||||
}
|
||||
|
||||
@@ -130,6 +130,7 @@ const message = useMessage()
|
||||
const showTestModal = ref(false)
|
||||
const testLoading = ref(false)
|
||||
const testResult = ref(null)
|
||||
const serverSchema = ref(null)
|
||||
|
||||
/** Resolve domain/section from props or route meta (canonicalDomain + section, with legacy fallback) */
|
||||
const resolvedDomain = computed(() => normalizeSchemaDomain(props.domain || route.meta?.canonicalDomain || route.meta?.schemaDomain || resolveDomainFromPath(route.path)))
|
||||
@@ -150,7 +151,9 @@ function resolveSectionFromPath(path) {
|
||||
|
||||
/** Load schema from the Vue-side registry */
|
||||
const pageMeta = computed(() => {
|
||||
const schema = getSchema(resolvedDomain.value, resolvedSection.value)
|
||||
const schema = resolvedDomain.value === 'page_display'
|
||||
? serverSchema.value
|
||||
: getSchema(resolvedDomain.value, resolvedSection.value)
|
||||
if (schema && schema.groups && schema.groups.length > 0) {
|
||||
return {
|
||||
title: schema.title || '',
|
||||
@@ -170,7 +173,9 @@ const pageMeta = computed(() => {
|
||||
|
||||
/** Default values from schema */
|
||||
const defaults = computed(() => {
|
||||
const schema = getSchema(resolvedDomain.value, resolvedSection.value)
|
||||
const schema = resolvedDomain.value === 'page_display'
|
||||
? serverSchema.value
|
||||
: getSchema(resolvedDomain.value, resolvedSection.value)
|
||||
if (!schema) return {}
|
||||
const d = {}
|
||||
for (const group of schema.groups || []) {
|
||||
@@ -188,7 +193,13 @@ const {
|
||||
formData, loading, saving, loadError, dirty,
|
||||
save: doSave, reload,
|
||||
} = useSectionForm({
|
||||
fetch: () => api.getSchemaPage(resolvedDomain.value, resolvedSection.value),
|
||||
fetch: async () => {
|
||||
const response = await api.getSchemaPage(resolvedDomain.value, resolvedSection.value)
|
||||
if (resolvedDomain.value === 'page_display') {
|
||||
serverSchema.value = response?.schema || null
|
||||
}
|
||||
return response
|
||||
},
|
||||
submit: (data) => api.saveSchemaPage(resolvedDomain.value, resolvedSection.value, data),
|
||||
defaults: defaults.value,
|
||||
onSaved: () => {
|
||||
@@ -426,6 +437,7 @@ function closeTestModal() {
|
||||
watch([resolvedDomain, resolvedSection], ([nextDomain, nextSection], [prevDomain, prevSection]) => {
|
||||
if (nextDomain !== prevDomain || nextSection !== prevSection) {
|
||||
closeTestModal()
|
||||
serverSchema.value = null
|
||||
reload()
|
||||
}
|
||||
})
|
||||
@@ -437,8 +449,8 @@ onBeforeUnmount(() => {
|
||||
|
||||
<style scoped>
|
||||
.aksrt-schema-page {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.aksrt-schema-page__tool-actions {
|
||||
|
||||
@@ -1,403 +0,0 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container content-page">
|
||||
<PageHeader
|
||||
title="Banner 管理"
|
||||
desc="管理首页内容流 Banner 幻灯片(图片、文案、跳转、启用状态)。"
|
||||
/>
|
||||
|
||||
<n-card class="aksrt-section-card" style="margin-bottom:16px;">
|
||||
<template #header>
|
||||
<div class="aksrt-card-header-row">
|
||||
<strong>Banner 显示开关</strong>
|
||||
<n-button size="small" secondary @click="goHeaderComponents">顶部多功能组件(轮播参数)</n-button>
|
||||
</div>
|
||||
</template>
|
||||
<n-form label-placement="left" label-width="180" class="aksrt-form">
|
||||
<n-form-item label="显示首页 Banner">
|
||||
<div class="aksrt-inline-setting">
|
||||
<n-switch v-model:value="bannerEnabled" :disabled="settingsLoading || settingsSaving" />
|
||||
<span class="aksrt-inline-setting__hint">关闭后,首页 Banner 区块将不再渲染。</span>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<div class="aksrt-inline-setting__actions">
|
||||
<n-button type="primary" :loading="settingsSaving" :disabled="settingsLoading" @click="saveBannerSettings">保存 Banner 开关</n-button>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<n-spin :show="loading" style="width:100%; min-height:200px;">
|
||||
<n-card class="aksrt-section-card">
|
||||
<template #header>
|
||||
<div class="aksrt-card-header-row">
|
||||
<div class="aksrt-card-header-row__left">
|
||||
<span class="header-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:18px;height:18px" aria-hidden="true">
|
||||
<rect width="18" height="14" x="3" y="5" rx="2"/>
|
||||
<path d="M3 10h18"/>
|
||||
</svg>
|
||||
</span>
|
||||
<strong class="aksrt-card-header-row__title">Banner 列表 ({{ banners.length }})</strong>
|
||||
</div>
|
||||
<n-button type="primary" size="small" @click="openCreate">+ 添加 Banner</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<n-data-table
|
||||
v-if="banners.length"
|
||||
:columns="columns"
|
||||
:data="banners"
|
||||
:row-key="(row) => row.id"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
/>
|
||||
<n-empty v-else description="暂无 Banner,添加后即可在首页使用。" />
|
||||
</n-card>
|
||||
</n-spin>
|
||||
|
||||
<n-modal
|
||||
v-model:show="modalShow"
|
||||
:mask-closable="false"
|
||||
preset="card"
|
||||
:title="editing.id ? '编辑 Banner' : '添加 Banner'"
|
||||
class="aksrt-modal-card"
|
||||
>
|
||||
<n-form :model="editing" label-placement="top" class="aksrt-form">
|
||||
<n-form-item label="标题">
|
||||
<n-input v-model:value="editing.title" placeholder="开启文字后显示的主标题" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="副标题">
|
||||
<n-input v-model:value="editing.subtitle" placeholder="开启文字后显示的副标题" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="Banner 图片">
|
||||
<n-input-group>
|
||||
<n-input v-model:value="editing.image_url" placeholder="https://..." />
|
||||
<n-button secondary @click="pickImage">媒体库</n-button>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
|
||||
<div v-if="editing.image_url" class="aksrt-modal-preview">
|
||||
<n-image :src="editing.image_url" width="100%" height="160" object-fit="cover" :preview-src="editing.image_url" />
|
||||
</div>
|
||||
|
||||
<n-form-item label="跳转链接">
|
||||
<n-input v-model:value="editing.link_url" placeholder="点击后跳转地址,可留空" />
|
||||
</n-form-item>
|
||||
|
||||
<div class="aksrt-modal-grid">
|
||||
<n-form-item label="显示文字">
|
||||
<n-switch v-model:value="editing.show_text" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="启用">
|
||||
<n-switch v-model:value="editing.enabled" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="显示元信息">
|
||||
<n-switch v-model:value="editing.show_meta" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="排序">
|
||||
<n-input-number v-model:value="editing.sort_order" :min="0" style="width:100%;" />
|
||||
</n-form-item>
|
||||
</div>
|
||||
</n-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="aksrt-modal-footer">
|
||||
<n-button @click="modalShow = false">取消</n-button>
|
||||
<n-button type="primary" :loading="saving" @click="handleSave">保存</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { h, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { NButton, NImage, NTag, useDialog, useMessage } from 'naive-ui'
|
||||
import api from '@/api'
|
||||
import { openMediaPicker } from '@/utils/media'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
|
||||
function goHeaderComponents() {
|
||||
router.push('/page-display/header-components')
|
||||
}
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const settingsLoading = ref(false)
|
||||
const settingsSaving = ref(false)
|
||||
const banners = ref([])
|
||||
const bannerEnabled = ref(true)
|
||||
|
||||
const modalShow = ref(false)
|
||||
const editing = ref(emptyForm())
|
||||
|
||||
function emptyForm() {
|
||||
return {
|
||||
id: 0,
|
||||
title: '',
|
||||
subtitle: '',
|
||||
image_url: '',
|
||||
link_url: '',
|
||||
show_text: true,
|
||||
enabled: true,
|
||||
show_meta: false,
|
||||
sort_order: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '预览',
|
||||
key: 'image_url',
|
||||
width: 90,
|
||||
render(row) {
|
||||
if (!row.image_url) {
|
||||
return h('span', { style: 'color:#bbb;font-size:12px;' }, '无')
|
||||
}
|
||||
return h(NImage, {
|
||||
src: row.image_url,
|
||||
width: 64,
|
||||
height: 36,
|
||||
objectFit: 'cover',
|
||||
previewSrc: row.image_url,
|
||||
style: 'border-radius:4px;display:block;',
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
ellipsis: { tooltip: true },
|
||||
render(row) {
|
||||
return row.title || h('span', { style: 'color:#bbb;' }, '未命名')
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '文字',
|
||||
key: 'show_text',
|
||||
width: 70,
|
||||
className: 'aksrt-table-mobile-hide',
|
||||
render(row) {
|
||||
return h(NTag, { size: 'small', type: row.show_text ? 'info' : 'default' }, {
|
||||
default: () => (row.show_text ? '显示' : '隐藏'),
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
key: 'sort_order',
|
||||
width: 64,
|
||||
className: 'aksrt-table-mobile-hide',
|
||||
},
|
||||
{
|
||||
title: '元信息',
|
||||
key: 'show_meta',
|
||||
width: 78,
|
||||
className: 'aksrt-table-mobile-hide',
|
||||
render(row) {
|
||||
return h(NTag, { size: 'small', type: row.show_meta ? 'info' : 'default' }, {
|
||||
default: () => (row.show_meta ? '显示' : '隐藏'),
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'enabled',
|
||||
width: 84,
|
||||
render(row) {
|
||||
return h(NTag, { size: 'small', type: row.enabled ? 'success' : 'default' }, {
|
||||
default: () => (row.enabled ? '启用' : '禁用'),
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 130,
|
||||
render(row) {
|
||||
return h('div', { style: 'display:flex;gap:6px;' }, [
|
||||
h(NButton, { size: 'tiny', onClick: () => openEdit(row) }, { default: () => '编辑' }),
|
||||
h(NButton, { size: 'tiny', type: 'error', quaternary: true, onClick: () => handleDelete(row) }, { default: () => '删除' }),
|
||||
])
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
async function loadBanners() {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await api.getHomepageBanners()
|
||||
if (res.code === 'success') {
|
||||
banners.value = res.data || []
|
||||
}
|
||||
} catch (e) {
|
||||
message.error(e.message || '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadBannerSettings() {
|
||||
settingsLoading.value = true
|
||||
try {
|
||||
const res = await api.getHomepageSettings()
|
||||
const data = res?.data || {}
|
||||
bannerEnabled.value = data.homepage_module_banner_enabled !== false
|
||||
} catch (e) {
|
||||
message.error(e.message || '加载 Banner 开关失败')
|
||||
} finally {
|
||||
settingsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveBannerSettings() {
|
||||
settingsSaving.value = true
|
||||
try {
|
||||
const res = await api.saveHomepageSettings({
|
||||
homepage_module_banner_enabled: bannerEnabled.value,
|
||||
})
|
||||
if (res?.code === 'success') {
|
||||
message.success('Banner 开关已保存')
|
||||
return
|
||||
}
|
||||
throw new Error(res?.message || '保存 Banner 开关失败')
|
||||
} catch (e) {
|
||||
message.error(e.message || '保存 Banner 开关失败')
|
||||
} finally {
|
||||
settingsSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
editing.value = emptyForm()
|
||||
modalShow.value = true
|
||||
}
|
||||
|
||||
function openEdit(row) {
|
||||
editing.value = {
|
||||
id: row.id,
|
||||
title: row.title || '',
|
||||
subtitle: row.subtitle || '',
|
||||
image_url: row.image_url || '',
|
||||
link_url: row.link_url || '',
|
||||
show_text: row.show_text !== false,
|
||||
enabled: !!row.enabled,
|
||||
show_meta: row.show_meta === true,
|
||||
sort_order: Number(row.sort_order) || 0,
|
||||
}
|
||||
modalShow.value = true
|
||||
}
|
||||
|
||||
async function pickImage() {
|
||||
try {
|
||||
const media = await openMediaPicker({
|
||||
title: '选择 Banner 图片',
|
||||
buttonText: '使用这张 Banner 图',
|
||||
})
|
||||
if (media?.url) {
|
||||
editing.value.image_url = media.url
|
||||
}
|
||||
} catch (e) {
|
||||
message.error(e.message || '打开媒体库失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!editing.value.image_url) {
|
||||
message.warning('请填写 Banner 图片')
|
||||
return
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
if (editing.value.id) {
|
||||
await api.updateHomepageBanner(editing.value.id, editing.value)
|
||||
message.success('已保存')
|
||||
} else {
|
||||
await api.createHomepageBanner(editing.value)
|
||||
message.success('已添加')
|
||||
}
|
||||
modalShow.value = false
|
||||
await loadBanners()
|
||||
} catch (e) {
|
||||
message.error(e.message || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
dialog.warning({
|
||||
title: '确认删除',
|
||||
content: `确定删除 Banner「${row.title || '未命名'}」?`,
|
||||
positiveText: '删除',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await api.deleteHomepageBanner(row.id)
|
||||
message.success('已删除')
|
||||
await loadBanners()
|
||||
} catch (e) {
|
||||
message.error(e.message || '删除失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadBanners()
|
||||
loadBannerSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-inline-setting {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.aksrt-inline-setting__hint {
|
||||
color: #7a7f8a;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.aksrt-inline-setting__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.aksrt-modal-preview {
|
||||
margin: -8px 0 18px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #eef1f6;
|
||||
}
|
||||
|
||||
.aksrt-modal-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.aksrt-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.aksrt-modal-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,986 +0,0 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container content-page">
|
||||
<PageHeader title="页脚配置" desc="管理页脚区域的联系方式、快速链接、更多链接、友情链接和版权信息。">
|
||||
<template #actions>
|
||||
<n-button type="primary" :loading="saving" @click="handleSave">保存</n-button>
|
||||
</template>
|
||||
</PageHeader>
|
||||
|
||||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||||
<n-tabs
|
||||
v-model:value="activeTab"
|
||||
type="line"
|
||||
animated
|
||||
class="aksrt-tabs"
|
||||
>
|
||||
<!-- ============== 品牌与Logo ============== -->
|
||||
<n-tab-pane name="brand" tab="品牌与Logo">
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header">
|
||||
<span class="section-icon"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg></span>
|
||||
<h3>品牌列(第一列)</h3>
|
||||
</div>
|
||||
|
||||
<n-form :model="form" label-placement="left" label-width="160" class="aksrt-form">
|
||||
<n-form-item label="显示设备">
|
||||
<n-checkbox-group v-model:value="form.footer_t1_m_s">
|
||||
<n-space>
|
||||
<n-checkbox value="pc" label="PC" />
|
||||
<n-checkbox value="mobile" label="移动端" />
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="页脚 Logo(日间)">
|
||||
<div class="aksrt-media-row">
|
||||
<n-input v-model:value="form.footer_t1_img" placeholder="图片 URL" readonly style="flex: 1;" />
|
||||
<n-button size="small" @click="pickMedia('footer_t1_img')">选择</n-button>
|
||||
<n-button size="small" quaternary type="error" :disabled="!form.footer_t1_img" @click="form.footer_t1_img = ''">清除</n-button>
|
||||
</div>
|
||||
<img v-if="form.footer_t1_img" :src="form.footer_t1_img" class="aksrt-media-preview" alt="footer logo day" />
|
||||
</n-form-item>
|
||||
<n-form-item label="页脚 Logo(夜间)">
|
||||
<div class="aksrt-media-row">
|
||||
<n-input v-model:value="form.footer_t1_img_dark" placeholder="图片 URL" readonly style="flex: 1;" />
|
||||
<n-button size="small" @click="pickMedia('footer_t1_img_dark')">选择</n-button>
|
||||
<n-button size="small" quaternary type="error" :disabled="!form.footer_t1_img_dark" @click="form.footer_t1_img_dark = ''">清除</n-button>
|
||||
</div>
|
||||
<img v-if="form.footer_t1_img_dark" :src="form.footer_t1_img_dark" class="aksrt-media-preview" alt="footer logo night" />
|
||||
</n-form-item>
|
||||
<n-form-item label="品牌标题">
|
||||
<n-input v-model:value="form.footer_t1_t" placeholder="留空则使用站点名称" />
|
||||
</n-form-item>
|
||||
<n-form-item label="品牌列自定义代码">
|
||||
<n-input v-model:value="form.fcode_t1_code" type="textarea" :rows="3" placeholder="可选,显示在品牌区域底部" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- ============== 联系方式 ============== -->
|
||||
<n-tab-pane name="contacts" tab="联系方式">
|
||||
<n-card class="aksrt-section-card">
|
||||
<template #header>
|
||||
<div class="aksrt-card-header-row">
|
||||
<div class="aksrt-card-header-row__left">
|
||||
<span class="header-icon"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg></span>
|
||||
<strong class="aksrt-card-header-row__title">联系方式图标</strong>
|
||||
</div>
|
||||
<n-button type="primary" size="small" @click="addContact">+ 添加图标</n-button>
|
||||
</div>
|
||||
</template>
|
||||
<p class="section-desc">页脚中的社交平台或联系入口。</p>
|
||||
|
||||
<n-data-table
|
||||
v-if="form.contacts.length"
|
||||
:columns="contactColumns"
|
||||
:data="form.contacts"
|
||||
:row-key="(row) => row._uid"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
/>
|
||||
<n-empty v-else description="暂无联系方式图标" />
|
||||
|
||||
<n-divider>标准联系字段</n-divider>
|
||||
<n-form :model="form" label-placement="left" label-width="160" class="aksrt-form">
|
||||
<n-form-item label="显示设备">
|
||||
<n-checkbox-group v-model:value="form.footer_contact_m_s">
|
||||
<n-space>
|
||||
<n-checkbox value="pc" label="PC" />
|
||||
<n-checkbox value="mobile" label="移动端" />
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="微信二维码">
|
||||
<div class="aksrt-media-row">
|
||||
<n-input v-model:value="form.footer_contact_wechat_img" placeholder="图片 URL" readonly style="flex: 1;" />
|
||||
<n-button size="small" @click="pickMedia('footer_contact_wechat_img')">选择</n-button>
|
||||
<n-button size="small" quaternary type="error" :disabled="!form.footer_contact_wechat_img" @click="form.footer_contact_wechat_img = ''">清除</n-button>
|
||||
</div>
|
||||
<img v-if="form.footer_contact_wechat_img" :src="form.footer_contact_wechat_img" class="aksrt-media-preview" alt="wechat qr" />
|
||||
</n-form-item>
|
||||
<n-form-item label="QQ">
|
||||
<n-input v-model:value="form.footer_contact_qq" placeholder="QQ 号或链接" />
|
||||
</n-form-item>
|
||||
<n-form-item label="微博">
|
||||
<n-input v-model:value="form.footer_contact_weibo" placeholder="微博链接" />
|
||||
</n-form-item>
|
||||
<n-form-item label="邮箱">
|
||||
<n-input v-model:value="form.footer_contact_email" placeholder="邮箱地址" />
|
||||
</n-form-item>
|
||||
<n-form-item label="链接 HTML(fcode_t2_code_1)">
|
||||
<n-input v-model:value="form.fcode_t2_code_1" type="textarea" :rows="3" placeholder="推荐直接写 <a href="/">首页</a> 等链接 HTML;为空时回退快速链接/友链/热门分类" />
|
||||
</n-form-item>
|
||||
<n-form-item label="链接下方自定义代码">
|
||||
<n-input v-model:value="form.fcode_t2_code_2" type="textarea" :rows="2" placeholder="备案号等 HTML" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<n-divider>更多联系项</n-divider>
|
||||
<n-card class="aksrt-section-card" embedded>
|
||||
<template #header>
|
||||
<div class="aksrt-card-header-row">
|
||||
<strong class="aksrt-card-header-row__title">扩展联系项</strong>
|
||||
<n-button type="primary" size="small" @click="addContactMore">+ 添加</n-button>
|
||||
</div>
|
||||
</template>
|
||||
<p class="section-desc">每项含描述、图标、链接、图片(hover 显示),用于补充标准联系方式。</p>
|
||||
<n-data-table
|
||||
v-if="form.footer_contact_more.length"
|
||||
:columns="contactMoreColumns"
|
||||
:data="form.footer_contact_more"
|
||||
:row-key="(_, i) => i"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
/>
|
||||
<n-empty v-else description="暂无扩展联系项" />
|
||||
</n-card>
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- ============== 快速链接 ============== -->
|
||||
<n-tab-pane name="quick" tab="快速链接">
|
||||
<n-card class="aksrt-section-card">
|
||||
<template #header>
|
||||
<div class="aksrt-card-header-row">
|
||||
<div class="aksrt-card-header-row__left">
|
||||
<span class="header-icon"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></span>
|
||||
<strong class="aksrt-card-header-row__title">快速链接</strong>
|
||||
</div>
|
||||
<n-button type="primary" size="small" @click="openLinkEditor('quick_links')">+ 添加链接</n-button>
|
||||
</div>
|
||||
</template>
|
||||
<p class="section-desc">页脚第二列的常用导航链接。</p>
|
||||
|
||||
<n-data-table
|
||||
v-if="form.quick_links.length"
|
||||
:columns="createLinkColumns('quick_links')"
|
||||
:data="form.quick_links"
|
||||
:row-key="(row) => row.id"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
/>
|
||||
<n-empty v-else description="暂无快速链接" />
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- ============== 更多链接 ============== -->
|
||||
<n-tab-pane name="more" tab="更多链接">
|
||||
<n-card class="aksrt-section-card">
|
||||
<template #header>
|
||||
<div class="aksrt-card-header-row">
|
||||
<div class="aksrt-card-header-row__left">
|
||||
<span class="header-icon"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></span>
|
||||
<strong class="aksrt-card-header-row__title">更多链接</strong>
|
||||
</div>
|
||||
<n-button type="primary" size="small" @click="openLinkEditor('more_links')">+ 添加链接</n-button>
|
||||
</div>
|
||||
</template>
|
||||
<p class="section-desc">页脚补充链接区域。</p>
|
||||
|
||||
<n-data-table
|
||||
v-if="form.more_links.length"
|
||||
:columns="createLinkColumns('more_links')"
|
||||
:data="form.more_links"
|
||||
:row-key="(row) => row.id"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
/>
|
||||
<n-empty v-else description="暂无更多链接" />
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- ============== 友情链接 ============== -->
|
||||
<n-tab-pane name="friends" tab="友情链接">
|
||||
<n-card class="aksrt-section-card">
|
||||
<template #header>
|
||||
<div class="aksrt-card-header-row">
|
||||
<div class="aksrt-card-header-row__left">
|
||||
<span class="header-icon"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg></span>
|
||||
<strong class="aksrt-card-header-row__title">友情链接 ({{ friendLinks.length }})</strong>
|
||||
</div>
|
||||
<n-button type="primary" size="small" @click="openFriendEditor()">+ 添加友链</n-button>
|
||||
</div>
|
||||
</template>
|
||||
<p class="section-desc">站点友情链接列表,支持 Logo 头像、名称、URL 与简短描述。</p>
|
||||
|
||||
<n-data-table
|
||||
v-if="friendLinks.length"
|
||||
:columns="friendColumns"
|
||||
:data="friendLinks"
|
||||
:row-key="(row) => row.id"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
/>
|
||||
<n-empty v-else description="暂无友情链接" />
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- ============== 二维码图片组 ============== -->
|
||||
<n-tab-pane name="mini_qr" tab="二维码图片组">
|
||||
<n-card class="aksrt-section-card">
|
||||
<template #header>
|
||||
<div class="aksrt-card-header-row">
|
||||
<div class="aksrt-card-header-row__left">
|
||||
<span class="header-icon"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg></span>
|
||||
<strong class="aksrt-card-header-row__title">二维码图片组(最多 4 个)</strong>
|
||||
</div>
|
||||
<n-button type="primary" size="small" :disabled="form.footer_mini_img.length >= 4" @click="addMiniImg">+ 添加</n-button>
|
||||
</div>
|
||||
</template>
|
||||
<p class="section-desc">显示在页脚第三列区域,每项含文字描述和二维码图片。</p>
|
||||
|
||||
<n-form :model="form" label-placement="left" label-width="120" class="aksrt-form">
|
||||
<n-form-item label="显示设备">
|
||||
<n-checkbox-group v-model:value="form.footer_mini_img_m_s">
|
||||
<n-space>
|
||||
<n-checkbox value="pc" label="PC" />
|
||||
<n-checkbox value="mobile" label="移动端" />
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<div v-for="(item, idx) in form.footer_mini_img" :key="idx" class="aksrt-mini-img-row">
|
||||
<n-space align="center" :size="12" style="width: 100%;">
|
||||
<n-input v-model:value="item.text" placeholder="文字描述" style="width: 160px;" />
|
||||
<div class="aksrt-media-row" style="flex: 1;">
|
||||
<n-input v-model:value="item.img" placeholder="二维码图片 URL" readonly style="flex: 1;" />
|
||||
<n-button size="small" @click="pickMiniImg(idx)">选择</n-button>
|
||||
</div>
|
||||
<n-button size="small" quaternary type="error" @click="removeMiniImg(idx)">删除</n-button>
|
||||
</n-space>
|
||||
<img v-if="item.img" :src="item.img" class="aksrt-media-preview aksrt-media-preview--sm" :alt="item.text || 'qr'" />
|
||||
</div>
|
||||
<n-empty v-if="!form.footer_mini_img.length" description="暂无二维码图片" />
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- ============== 自定义代码 ============== -->
|
||||
<n-tab-pane name="custom_code" tab="自定义代码">
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header">
|
||||
<span class="section-icon"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg></span>
|
||||
<h3>底部自定义代码</h3>
|
||||
</div>
|
||||
<p class="section-desc">插入在页脚底部区域(版权信息之后),支持 HTML。</p>
|
||||
|
||||
<n-form :model="form" label-placement="top" class="aksrt-form">
|
||||
<n-form-item label="fcode_customize_code">
|
||||
<n-input v-model:value="form.fcode_customize_code" type="textarea" :rows="6" placeholder="自定义 HTML 代码,显示在页脚最底部" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
|
||||
<!-- ============== 版权与杂项 ============== -->
|
||||
<n-tab-pane name="misc" tab="版权与杂项">
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header">
|
||||
<span class="section-icon"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span>
|
||||
<h3>其他设置</h3>
|
||||
</div>
|
||||
|
||||
<n-form :model="form" label-placement="left" label-width="140" class="aksrt-form">
|
||||
<n-form-item label="页脚布局模板">
|
||||
<n-radio-group v-model:value="form.layout_template">
|
||||
<n-radio-button value="template_1">模板一(Zibll)</n-radio-button>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
<n-form-item label="热门分类数量">
|
||||
<n-space align="center" :size="10">
|
||||
<n-input-number v-model:value="form.hot_categories" :min="1" :max="20" style="width: 120px;" />
|
||||
<span class="aksrt-hint-text">范围 1 - 20</span>
|
||||
</n-space>
|
||||
</n-form-item>
|
||||
<n-form-item label="自定义版权文案">
|
||||
<n-input
|
||||
v-model:value="form.copyright_text"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="留空则使用默认版权信息"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
|
||||
<div class="aksrt-form-actions">
|
||||
<n-button type="primary" :loading="saving" @click="handleSave">保存页脚配置</n-button>
|
||||
</div>
|
||||
</n-spin>
|
||||
|
||||
<!-- ===== 联系方式 Modal ===== -->
|
||||
<n-modal
|
||||
v-model:show="showContactModal"
|
||||
:mask-closable="false"
|
||||
preset="card"
|
||||
title="编辑联系方式图标"
|
||||
class="aksrt-modal-card"
|
||||
>
|
||||
<n-form :model="editingContact" label-placement="top" class="aksrt-form">
|
||||
<n-form-item label="图标类型">
|
||||
<n-select v-model:value="editingContact.icon" :options="iconOptions" placeholder="选择图标" />
|
||||
</n-form-item>
|
||||
<n-form-item label="链接地址">
|
||||
<n-input v-model:value="editingContact.url" placeholder="https://..." />
|
||||
</n-form-item>
|
||||
<n-form-item label="标题提示">
|
||||
<n-input v-model:value="editingContact.title" placeholder="鼠标悬停提示文案" />
|
||||
</n-form-item>
|
||||
<n-form-item label="启用状态">
|
||||
<n-switch v-model:value="editingContact.enabled" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #footer>
|
||||
<div class="aksrt-modal-footer">
|
||||
<n-button @click="showContactModal = false">取消</n-button>
|
||||
<n-button type="primary" @click="saveContact">保存</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
<!-- ===== 链接 Modal ===== -->
|
||||
<n-modal
|
||||
v-model:show="showLinkModal"
|
||||
:mask-closable="false"
|
||||
preset="card"
|
||||
:title="editingLinkType === 'quick_links' ? '编辑快速链接' : '编辑更多链接'"
|
||||
class="aksrt-modal-card"
|
||||
>
|
||||
<n-form :model="editingLink" label-placement="top" class="aksrt-form">
|
||||
<n-form-item label="链接名称">
|
||||
<n-input v-model:value="editingLink.name" placeholder="例如:首页" />
|
||||
</n-form-item>
|
||||
<n-form-item label="链接地址">
|
||||
<n-input v-model:value="editingLink.url" placeholder="https:// 或 /path" />
|
||||
</n-form-item>
|
||||
<n-form-item label="启用状态">
|
||||
<n-switch v-model:value="editingLink.enabled" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #footer>
|
||||
<div class="aksrt-modal-footer">
|
||||
<n-button @click="showLinkModal = false">取消</n-button>
|
||||
<n-button type="primary" @click="saveLink">保存</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
<!-- ===== 友情链接 Modal ===== -->
|
||||
<n-modal
|
||||
v-model:show="showFriendModal"
|
||||
:mask-closable="false"
|
||||
preset="card"
|
||||
title="添加友情链接"
|
||||
class="aksrt-modal-card"
|
||||
>
|
||||
<n-form :model="editingFriend" label-placement="top" class="aksrt-form">
|
||||
<n-form-item label="站点名称">
|
||||
<n-input v-model:value="editingFriend.name" placeholder="如:某人的博客" />
|
||||
</n-form-item>
|
||||
<n-form-item label="站点 URL">
|
||||
<n-input v-model:value="editingFriend.url" placeholder="https://" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Logo 图片 URL">
|
||||
<n-input v-model:value="editingFriend.logo" placeholder="图标 URL(可选,方形 PNG 推荐)" />
|
||||
</n-form-item>
|
||||
<n-form-item label="描述">
|
||||
<n-input v-model:value="editingFriend.description" placeholder="一句话简介(可选)" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #footer>
|
||||
<div class="aksrt-modal-footer">
|
||||
<n-button @click="showFriendModal = false">取消</n-button>
|
||||
<n-button type="primary" :loading="addingFriend" @click="saveFriend">保存</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
<!-- ===== 扩展联系项 Modal ===== -->
|
||||
<n-modal
|
||||
v-model:show="showContactMoreModal"
|
||||
:mask-closable="false"
|
||||
preset="card"
|
||||
title="编辑扩展联系项"
|
||||
class="aksrt-modal-card"
|
||||
>
|
||||
<n-form :model="editingContactMore" label-placement="top" class="aksrt-form">
|
||||
<n-form-item label="描述">
|
||||
<n-input v-model:value="editingContactMore.desc" placeholder="如:微信公众号" />
|
||||
</n-form-item>
|
||||
<n-form-item label="图标类名">
|
||||
<n-input v-model:value="editingContactMore.icon" placeholder="如:fa fab fa-weixin" />
|
||||
</n-form-item>
|
||||
<n-form-item label="链接地址">
|
||||
<n-input v-model:value="editingContactMore.link" placeholder="https://..." />
|
||||
</n-form-item>
|
||||
<n-form-item label="Hover 图片">
|
||||
<n-input v-model:value="editingContactMore.img" placeholder="图片 URL(可选,hover 显示)" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #footer>
|
||||
<div class="aksrt-modal-footer">
|
||||
<n-button @click="showContactMoreModal = false">取消</n-button>
|
||||
<n-button type="primary" @click="saveContactMore">保存</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { h, onMounted, reactive, ref } from 'vue'
|
||||
import { NAvatar, NButton, NTag, useDialog, useMessage } from 'naive-ui'
|
||||
import api from '@/api'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import { openMediaPicker } from '@/utils/media'
|
||||
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
|
||||
const activeTab = ref('contacts')
|
||||
|
||||
const form = reactive({
|
||||
contacts: [],
|
||||
quick_links: [],
|
||||
more_links: [],
|
||||
hot_categories: 5,
|
||||
copyright_text: '',
|
||||
layout_template: 'template_1',
|
||||
// §2.3 三列结构化字段
|
||||
footer_t1_m_s: ['pc', 'mobile'],
|
||||
footer_t1_img: '',
|
||||
footer_t1_img_dark: '',
|
||||
footer_t1_t: '',
|
||||
fcode_t1_code: '',
|
||||
footer_contact_wechat_img: '',
|
||||
footer_contact_qq: '',
|
||||
footer_contact_weibo: '',
|
||||
footer_contact_email: '',
|
||||
footer_contact_m_s: ['pc', 'mobile'],
|
||||
footer_contact_more: [],
|
||||
fcode_t2_code_1: '',
|
||||
fcode_t2_code_2: '',
|
||||
footer_mini_img_m_s: ['pc', 'mobile'],
|
||||
footer_mini_img: [],
|
||||
fcode_customize_code: '',
|
||||
})
|
||||
|
||||
// ============== 联系方式 ==============
|
||||
const showContactModal = ref(false)
|
||||
const editingContactIndex = ref(-1)
|
||||
const editingContact = reactive({
|
||||
icon: '',
|
||||
url: '',
|
||||
title: '',
|
||||
enabled: true,
|
||||
})
|
||||
|
||||
let contactUid = 0
|
||||
|
||||
const iconOptions = [
|
||||
{ label: 'GitHub', value: 'github' },
|
||||
{ label: 'X (Twitter)', value: 'x' },
|
||||
{ label: 'Email', value: 'email' },
|
||||
{ label: '微信', value: 'wechat' },
|
||||
{ label: 'QQ', value: 'qq' },
|
||||
{ label: '微博', value: 'weibo' },
|
||||
{ label: 'Telegram', value: 'telegram' },
|
||||
{ label: 'Discord', value: 'discord' },
|
||||
{ label: 'Bilibili', value: 'bilibili' },
|
||||
{ label: 'LinkedIn', value: 'linkedin' },
|
||||
{ label: 'Facebook', value: 'facebook' },
|
||||
{ label: 'YouTube', value: 'youtube' },
|
||||
{ label: 'Instagram', value: 'instagram' },
|
||||
{ label: 'RSS / 网站', value: 'web' },
|
||||
]
|
||||
|
||||
function addContact() {
|
||||
Object.assign(editingContact, {
|
||||
icon: '',
|
||||
url: '',
|
||||
title: '',
|
||||
enabled: true,
|
||||
})
|
||||
editingContactIndex.value = -1
|
||||
showContactModal.value = true
|
||||
}
|
||||
|
||||
function editContact(row, index) {
|
||||
Object.assign(editingContact, {
|
||||
icon: row.icon,
|
||||
url: row.url,
|
||||
title: row.title,
|
||||
enabled: row.enabled,
|
||||
})
|
||||
editingContactIndex.value = index
|
||||
showContactModal.value = true
|
||||
}
|
||||
|
||||
function saveContact() {
|
||||
if (!editingContact.icon || !editingContact.url) {
|
||||
message.warning('请填写图标类型和链接地址')
|
||||
return
|
||||
}
|
||||
|
||||
const item = {
|
||||
_uid: editingContactIndex.value >= 0 ? form.contacts[editingContactIndex.value]._uid : ++contactUid,
|
||||
icon: editingContact.icon,
|
||||
url: editingContact.url,
|
||||
title: editingContact.title,
|
||||
enabled: editingContact.enabled,
|
||||
}
|
||||
|
||||
if (editingContactIndex.value >= 0) {
|
||||
form.contacts[editingContactIndex.value] = item
|
||||
} else {
|
||||
form.contacts.push(item)
|
||||
}
|
||||
|
||||
showContactModal.value = false
|
||||
}
|
||||
|
||||
function deleteContact(index) {
|
||||
dialog.warning({
|
||||
title: '确认删除',
|
||||
content: '确定删除这个联系方式图标吗?',
|
||||
positiveText: '删除',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
form.contacts.splice(index, 1)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const contactColumns = [
|
||||
{
|
||||
title: '平台',
|
||||
key: 'icon',
|
||||
width: 120,
|
||||
render: (row) => iconOptions.find((item) => item.value === row.icon)?.label || row.icon,
|
||||
},
|
||||
{
|
||||
title: '链接',
|
||||
key: 'url',
|
||||
ellipsis: { tooltip: true },
|
||||
},
|
||||
{
|
||||
title: '提示',
|
||||
key: 'title',
|
||||
ellipsis: { tooltip: true },
|
||||
className: 'aksrt-table-mobile-hide',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'enabled',
|
||||
width: 80,
|
||||
render: (row) => h(
|
||||
NTag,
|
||||
{ size: 'small', type: row.enabled ? 'success' : 'default' },
|
||||
{ default: () => (row.enabled ? '启用' : '禁用') }
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 130,
|
||||
render: (_, index) => h('div', { style: 'display:flex;gap:6px;' }, [
|
||||
h(NButton, { size: 'tiny', onClick: () => editContact(form.contacts[index], index) }, { default: () => '编辑' }),
|
||||
h(NButton, { size: 'tiny', type: 'error', quaternary: true, onClick: () => deleteContact(index) }, { default: () => '删除' }),
|
||||
]),
|
||||
},
|
||||
]
|
||||
|
||||
// ============== 快速 / 更多链接 ==============
|
||||
const showLinkModal = ref(false)
|
||||
const editingLinkType = ref('quick_links')
|
||||
const editingLinkIndex = ref(-1)
|
||||
const editingLink = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
url: '',
|
||||
enabled: true,
|
||||
sort_order: 0,
|
||||
})
|
||||
|
||||
function openLinkEditor(type, row = null, index = -1) {
|
||||
editingLinkType.value = type
|
||||
editingLinkIndex.value = index
|
||||
|
||||
Object.assign(editingLink, row || {
|
||||
id: '',
|
||||
name: '',
|
||||
url: '',
|
||||
enabled: true,
|
||||
sort_order: type === 'quick_links' ? form.quick_links.length : form.more_links.length,
|
||||
})
|
||||
|
||||
showLinkModal.value = true
|
||||
}
|
||||
|
||||
function saveLink() {
|
||||
if (!editingLink.name || !editingLink.url) {
|
||||
message.warning('请填写链接名称和链接地址')
|
||||
return
|
||||
}
|
||||
|
||||
const target = form[editingLinkType.value]
|
||||
const item = {
|
||||
id: editingLink.id || `link_${Date.now()}`,
|
||||
name: editingLink.name,
|
||||
url: editingLink.url,
|
||||
enabled: editingLink.enabled,
|
||||
sort_order: editingLink.sort_order ?? target.length,
|
||||
}
|
||||
|
||||
if (editingLinkIndex.value >= 0) {
|
||||
target[editingLinkIndex.value] = item
|
||||
} else {
|
||||
target.push(item)
|
||||
}
|
||||
|
||||
showLinkModal.value = false
|
||||
}
|
||||
|
||||
function deleteLink(type, index) {
|
||||
dialog.warning({
|
||||
title: '确认删除',
|
||||
content: '确定删除这个链接吗?',
|
||||
positiveText: '删除',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
form[type].splice(index, 1)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function createLinkColumns(type) {
|
||||
return [
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: 'URL',
|
||||
key: 'url',
|
||||
ellipsis: { tooltip: true },
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'enabled',
|
||||
width: 80,
|
||||
render: (row) => h(
|
||||
NTag,
|
||||
{ size: 'small', type: row.enabled ? 'success' : 'default' },
|
||||
{ default: () => (row.enabled ? '启用' : '禁用') }
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 130,
|
||||
render: (_, index) => h('div', { style: 'display:flex;gap:6px;' }, [
|
||||
h(NButton, { size: 'tiny', onClick: () => openLinkEditor(type, form[type][index], index) }, { default: () => '编辑' }),
|
||||
h(NButton, { size: 'tiny', type: 'error', quaternary: true, onClick: () => deleteLink(type, index) }, { default: () => '删除' }),
|
||||
]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// ============== 友情链接 ==============
|
||||
const friendLinks = ref([])
|
||||
const showFriendModal = ref(false)
|
||||
const addingFriend = ref(false)
|
||||
const editingFriend = reactive({
|
||||
name: '',
|
||||
url: '',
|
||||
logo: '',
|
||||
description: '',
|
||||
})
|
||||
|
||||
const friendColumns = [
|
||||
{
|
||||
title: 'Logo',
|
||||
key: 'logo',
|
||||
width: 60,
|
||||
render: (row) => row.logo ? h(NAvatar, { src: row.logo, size: 32, round: true }) : h('span', { style: 'color:#bbb;font-size:12px;' }, '—'),
|
||||
},
|
||||
{ title: '名称', key: 'name', width: 140, ellipsis: { tooltip: true } },
|
||||
{ title: 'URL', key: 'url', ellipsis: { tooltip: true } },
|
||||
{ title: '描述', key: 'description', ellipsis: { tooltip: true }, className: 'aksrt-table-mobile-hide' },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 90,
|
||||
render: (row) => h(NButton, {
|
||||
quaternary: true,
|
||||
type: 'error',
|
||||
size: 'tiny',
|
||||
onClick: () => deleteFriend(row),
|
||||
}, { default: () => '删除' }),
|
||||
},
|
||||
]
|
||||
|
||||
function openFriendEditor() {
|
||||
Object.assign(editingFriend, {
|
||||
name: '',
|
||||
url: '',
|
||||
logo: '',
|
||||
description: '',
|
||||
})
|
||||
showFriendModal.value = true
|
||||
}
|
||||
|
||||
async function loadFriendLinks() {
|
||||
try {
|
||||
const res = await api.getFriendLinks()
|
||||
if (res.code === 'success') friendLinks.value = res.data || []
|
||||
} catch (e) {
|
||||
console.error('[FriendLinks] load failed:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function saveFriend() {
|
||||
if (!editingFriend.name || !editingFriend.url) {
|
||||
message.warning('请填写站点名称和 URL')
|
||||
return
|
||||
}
|
||||
addingFriend.value = true
|
||||
try {
|
||||
const res = await api.addFriendLink({
|
||||
name: editingFriend.name,
|
||||
url: editingFriend.url,
|
||||
logo: editingFriend.logo,
|
||||
description: editingFriend.description,
|
||||
sort_order: friendLinks.value.length,
|
||||
})
|
||||
if (res.code === 'success') {
|
||||
message.success(res.message || '已添加')
|
||||
showFriendModal.value = false
|
||||
await loadFriendLinks()
|
||||
}
|
||||
} catch (e) {
|
||||
message.error(e.message || '添加失败')
|
||||
} finally {
|
||||
addingFriend.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function deleteFriend(row) {
|
||||
dialog.warning({
|
||||
title: '确认删除',
|
||||
content: `确定删除友链「${row.name}」?`,
|
||||
positiveText: '删除',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await api.deleteFriendLink(row.id)
|
||||
message.success('已删除')
|
||||
await loadFriendLinks()
|
||||
} catch (e) {
|
||||
message.error(e.message || '删除失败')
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ============== 加载 / 保存 ==============
|
||||
async function loadSettings() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [footerRes] = await Promise.all([
|
||||
api.getFooterSettings(),
|
||||
loadFriendLinks(),
|
||||
])
|
||||
if (footerRes.code === 'success' && footerRes.data) {
|
||||
const data = footerRes.data
|
||||
form.contacts = (data.contacts || []).map((item) => ({ ...item, _uid: ++contactUid }))
|
||||
form.quick_links = data.quick_links || []
|
||||
form.more_links = data.more_links || []
|
||||
form.hot_categories = data.hot_categories ?? 5
|
||||
form.copyright_text = data.copyright_text || ''
|
||||
form.layout_template = 'template_1'
|
||||
// §2.3 三列结构化字段
|
||||
form.footer_t1_m_s = Array.isArray(data.footer_t1_m_s) ? data.footer_t1_m_s : ['pc', 'mobile']
|
||||
form.footer_t1_img = data.footer_t1_img || ''
|
||||
form.footer_t1_img_dark = data.footer_t1_img_dark || ''
|
||||
form.footer_t1_t = data.footer_t1_t || ''
|
||||
form.fcode_t1_code = data.fcode_t1_code || ''
|
||||
form.footer_contact_wechat_img = data.footer_contact_wechat_img || ''
|
||||
form.footer_contact_qq = data.footer_contact_qq || ''
|
||||
form.footer_contact_weibo = data.footer_contact_weibo || ''
|
||||
form.footer_contact_email = data.footer_contact_email || ''
|
||||
form.footer_contact_m_s = Array.isArray(data.footer_contact_m_s) ? data.footer_contact_m_s : ['pc', 'mobile']
|
||||
form.footer_contact_more = Array.isArray(data.footer_contact_more) ? data.footer_contact_more : []
|
||||
form.fcode_t2_code_1 = data.fcode_t2_code_1 || ''
|
||||
form.fcode_t2_code_2 = data.fcode_t2_code_2 || ''
|
||||
form.footer_mini_img_m_s = Array.isArray(data.footer_mini_img_m_s) ? data.footer_mini_img_m_s : ['pc', 'mobile']
|
||||
form.footer_mini_img = Array.isArray(data.footer_mini_img) ? data.footer_mini_img : []
|
||||
form.fcode_customize_code = data.fcode_customize_code || ''
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Footer] load failed:', error)
|
||||
message.error(error?.message || '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = {
|
||||
contacts: form.contacts.map(({ _uid, ...rest }) => rest),
|
||||
quick_links: form.quick_links,
|
||||
more_links: form.more_links,
|
||||
hot_categories: form.hot_categories,
|
||||
copyright_text: form.copyright_text,
|
||||
layout_template: form.layout_template,
|
||||
// §2.3 三列结构化字段
|
||||
footer_t1_m_s: form.footer_t1_m_s,
|
||||
footer_t1_img: form.footer_t1_img,
|
||||
footer_t1_img_dark: form.footer_t1_img_dark,
|
||||
footer_t1_t: form.footer_t1_t,
|
||||
fcode_t1_code: form.fcode_t1_code,
|
||||
footer_contact_wechat_img: form.footer_contact_wechat_img,
|
||||
footer_contact_qq: form.footer_contact_qq,
|
||||
footer_contact_weibo: form.footer_contact_weibo,
|
||||
footer_contact_email: form.footer_contact_email,
|
||||
footer_contact_m_s: form.footer_contact_m_s,
|
||||
footer_contact_more: form.footer_contact_more,
|
||||
fcode_t2_code_1: form.fcode_t2_code_1,
|
||||
fcode_t2_code_2: form.fcode_t2_code_2,
|
||||
footer_mini_img_m_s: form.footer_mini_img_m_s,
|
||||
footer_mini_img: form.footer_mini_img,
|
||||
fcode_customize_code: form.fcode_customize_code,
|
||||
}
|
||||
const res = await api.saveFooterSettings(payload)
|
||||
if (res.code === 'success') {
|
||||
message.success(res.message || '保存成功')
|
||||
} else {
|
||||
message.error(res.message || '保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Footer] save failed:', error)
|
||||
message.error(error?.message || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ============== 媒体选择 ==============
|
||||
async function pickMedia(field) {
|
||||
try {
|
||||
const media = await openMediaPicker({ title: '选择图片' })
|
||||
if (media?.url) {
|
||||
form[field] = media.url
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.message) message.warning(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
// ============== 扩展联系项 ==============
|
||||
function addContactMore() {
|
||||
form.footer_contact_more.push({ desc: '', icon: '', link: '', img: '' })
|
||||
}
|
||||
|
||||
function removeContactMore(index) {
|
||||
form.footer_contact_more.splice(index, 1)
|
||||
}
|
||||
|
||||
const showContactMoreModal = ref(false)
|
||||
const editingContactMoreIndex = ref(-1)
|
||||
const editingContactMore = reactive({ desc: '', icon: '', link: '', img: '' })
|
||||
|
||||
function editContactMore(row, index) {
|
||||
Object.assign(editingContactMore, row)
|
||||
editingContactMoreIndex.value = index
|
||||
showContactMoreModal.value = true
|
||||
}
|
||||
|
||||
function saveContactMore() {
|
||||
const item = { ...editingContactMore }
|
||||
if (editingContactMoreIndex.value >= 0) {
|
||||
form.footer_contact_more[editingContactMoreIndex.value] = item
|
||||
}
|
||||
showContactMoreModal.value = false
|
||||
}
|
||||
|
||||
const contactMoreColumns = [
|
||||
{ title: '描述', key: 'desc', width: 120 },
|
||||
{ title: '图标', key: 'icon', width: 100 },
|
||||
{ title: '链接', key: 'link', ellipsis: { tooltip: true } },
|
||||
{ title: '图片', key: 'img', width: 60, render: (row) => row.img ? h('img', { src: row.img, style: 'width:32px;height:32px;object-fit:cover;border-radius:4px;' }) : h('span', { style: 'color:#bbb;' }, '—') },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 130,
|
||||
render: (_, index) => h('div', { style: 'display:flex;gap:6px;' }, [
|
||||
h(NButton, { size: 'tiny', onClick: () => editContactMore(form.footer_contact_more[index], index) }, { default: () => '编辑' }),
|
||||
h(NButton, { size: 'tiny', type: 'error', quaternary: true, onClick: () => removeContactMore(index) }, { default: () => '删除' }),
|
||||
]),
|
||||
},
|
||||
]
|
||||
|
||||
// ============== 二维码图片组 ==============
|
||||
function addMiniImg() {
|
||||
if (form.footer_mini_img.length < 4) {
|
||||
form.footer_mini_img.push({ text: '', img: '' })
|
||||
}
|
||||
}
|
||||
|
||||
function removeMiniImg(idx) {
|
||||
form.footer_mini_img.splice(idx, 1)
|
||||
}
|
||||
|
||||
async function pickMiniImg(idx) {
|
||||
try {
|
||||
const media = await openMediaPicker({ title: '选择二维码图片' })
|
||||
if (media?.url) {
|
||||
form.footer_mini_img[idx].img = media.url
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.message) message.warning(e.message)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadSettings)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
.aksrt-media-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
.aksrt-media-preview {
|
||||
display: block;
|
||||
max-width: 200px;
|
||||
max-height: 80px;
|
||||
margin-top: 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--n-border-color, #e0e0e6);
|
||||
object-fit: contain;
|
||||
}
|
||||
.aksrt-media-preview--sm {
|
||||
max-width: 64px;
|
||||
max-height: 64px;
|
||||
}
|
||||
.aksrt-mini-img-row {
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid var(--n-border-color, #e0e0e6);
|
||||
}
|
||||
.aksrt-mini-img-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,354 +0,0 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container content-page">
|
||||
<PageHeader
|
||||
title="首页配置"
|
||||
desc="配置首页主列表、分类 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-alert type="info" :show-icon="false">
|
||||
首页主列表只使用普通文章。下载/付费是文章能力(meta),不是第二种内容类型。当前为固定骨架模式,不再使用旧版自由模块画布。
|
||||
</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-alert type="info" :show-icon="false">
|
||||
首页文章列表/卡片模式、样式变体与缩略图比例统一由「文章&列表 → 文章列表」中的 Zibll 全局字段控制。
|
||||
</n-alert>
|
||||
</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">
|
||||
顶部多功能组件与 Banner 已作为固定首页骨架的一部分维护,不再由旧版首页模块编排驱动。
|
||||
</n-alert>
|
||||
<n-alert type="info" :show-icon="false">
|
||||
如需调整首页首屏运营区,请前往对应管理页配置幻灯片内容或顶部组件参数。
|
||||
</n-alert>
|
||||
<n-space>
|
||||
<n-button secondary type="primary" @click="router.push('/page-display/banner')">管理 Banner 轮播</n-button>
|
||||
<n-button secondary @click="router.push('/page-display/header-components')">
|
||||
顶部多功能组件
|
||||
</n-button>
|
||||
</n-space>
|
||||
</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 { useRouter } from 'vue-router'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import api from '@/api'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const 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({
|
||||
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_sidebar_mini_enabled: true,
|
||||
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 || c.id,
|
||||
}))
|
||||
} catch {
|
||||
// non-critical
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await api.getHomepageSettings()
|
||||
const d = res?.data || {}
|
||||
|
||||
form.home_posts_list_s = d.home_posts_list_s !== false
|
||||
form.home_list1_orderby_s = d.home_list1_orderby_s !== 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 || 'date',
|
||||
options: Array.isArray(orderOpt.options) && orderOpt.options.length
|
||||
? orderOpt.options
|
||||
: ['modified', 'date', 'views', 'likes', 'comments'],
|
||||
}
|
||||
form.index_list_title = d.index_list_title || '最新发布'
|
||||
form.home_exclude_posts = Array.isArray(d.home_exclude_posts) ? d.home_exclude_posts : []
|
||||
form.home_exclude_cats = Array.isArray(d.home_exclude_cats) ? d.home_exclude_cats : []
|
||||
form.home_lists = Array.isArray(d.home_lists) ? d.home_lists : []
|
||||
form.homepage_sidebar_mini_enabled = d.homepage_sidebar_mini_enabled !== false
|
||||
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>
|
||||
@@ -10,7 +10,7 @@
|
||||
<FaIcon icon="fa fas fa-paint-brush" :size="16" />
|
||||
<div>
|
||||
<strong>全局设置</strong>
|
||||
<p>Logo、背景、菜单装饰、自定义代码</p>
|
||||
<p>Logo、导航栏、自定义代码</p>
|
||||
<router-link to="/global/common">前往配置 →</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,9 +4,6 @@ export const corePageLoaders = {
|
||||
NativeOpsRedirect: () => import('@/pages/placeholder/NativeOpsRedirect.vue'),
|
||||
FloatingButtonsSettings: () => import('@/pages/content/FloatingButtonsSettings.vue'),
|
||||
FooterTabbarSettings: () => import('@/pages/content/FooterTabbarSettings.vue'),
|
||||
FooterSettings: () => import('@/pages/content/FooterSettings.vue'),
|
||||
HomepageSettings: () => import('@/pages/content/HomepageSettings.vue'),
|
||||
BannerManager: () => import('@/pages/content/BannerManager.vue'),
|
||||
PostPageSettings: () => import('@/pages/post-list/PostPageSettings.vue'),
|
||||
CommentSettings: () => import('@/pages/post-list/CommentSettings.vue'),
|
||||
PermissionsCapabilities: () => import('@/pages/permissions/PermissionsCapabilities.vue'),
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
* }
|
||||
*/
|
||||
import { globalSchemas } from './sections/global/index.js'
|
||||
import { pageDisplaySchemas } from './sections/page-display/index.js'
|
||||
import { postListSchemas } from './sections/post-list/index.js'
|
||||
import { permissionsSchemas } from './sections/permissions/index.js'
|
||||
import { usersSchemas } from './sections/users/index.js'
|
||||
@@ -58,7 +57,7 @@ export const CANONICAL_SCHEMA_DOMAINS = [
|
||||
/** All schemas organized by domain */
|
||||
export const ALL_SCHEMAS = Object.freeze({
|
||||
global: globalSchemas,
|
||||
page_display: pageDisplaySchemas,
|
||||
page_display: Object.freeze({}),
|
||||
post_list: postListSchemas,
|
||||
permissions: permissionsSchemas,
|
||||
users: usersSchemas,
|
||||
|
||||
@@ -17,7 +17,7 @@ export const searchSchema = {
|
||||
search_cat_in: '',
|
||||
search_more_cat_obj: [],
|
||||
search_custom_key_s: true,
|
||||
search_custom_key: ['title', 'content', 'excerpt', 'post_tag', 'forum_topic', 'shop_cat', 'shop_discount'],
|
||||
search_custom_key: ['title', 'content', 'excerpt', 'category', 'post_tag'],
|
||||
search_facets: {},
|
||||
search_ms_s: false,
|
||||
search_ms_opt: { api_url: 'http://127.0.0.1:7700', api_key: '', index_prefix: 'aksrt', synonyms: [{ key: 'wordpress', val: 'wp,wordp,wordpre' }] },
|
||||
@@ -59,25 +59,16 @@ export const searchSchema = {
|
||||
{ key: '404_search_s', label: '404 页面搜索', type: 'switch', default: true, help: '在 404 页面显示搜索功能' },
|
||||
{ key: 'search_posts', label: '搜索框热门文章', type: 'switch', default: true, help: '在搜索面板中显示热门文章列表' },
|
||||
{ key: 'search_no_page', label: '排除页面', type: 'switch', default: true, help: '搜索结果中排除页面(仅显示文章)' },
|
||||
{ key: 'search_exclude_cats', label: '排除分类', type: 'taxonomy_select', taxonomy: ['category', 'shop_cat'], multiple: true, default: [], help: '搜索结果中排除的文章或商品分类' },
|
||||
{ key: 'search_exclude_cats', label: '排除分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], help: '搜索结果中排除的文章分类' },
|
||||
{ key: 'search_custom_key_s', label: '自定义匹配范围', type: 'switch', default: true, help: '启用后可配置搜索匹配字段范围' },
|
||||
{ key: 'search_custom_key', label: '匹配字段', type: 'checkbox', default: ['title', 'content', 'excerpt', 'post_tag', 'forum_topic', 'shop_cat', 'shop_discount'], dependsOn: { key: 'search_custom_key_s', value: true }, options: [
|
||||
{ key: 'search_custom_key', label: '匹配字段', type: 'checkbox', default: ['title', 'content', 'excerpt', 'category', 'post_tag'], dependsOn: { key: 'search_custom_key_s', value: true }, options: [
|
||||
{ label: '标题', value: 'title' }, { label: '内容', value: 'content' }, { label: '简介', value: 'excerpt' },
|
||||
{ label: '分类', value: 'category' }, { label: '标签', value: 'post_tag' }, { label: '专题', value: 'topics' },
|
||||
{ label: '分类', value: 'category' }, { label: '标签', value: 'post_tag' },
|
||||
{ label: '评论', value: 'coment' }, { label: '作者昵称', value: 'user' },
|
||||
{ label: '[帖子]话题', value: 'forum_topic' }, { label: '[帖子]标签', value: 'forum_tag' },
|
||||
{ label: '[商品]分类', value: 'shop_cat' }, { label: '[商品]特色标签', value: 'shop_tag' }, { label: '[商品]优惠活动', value: 'shop_discount' },
|
||||
] },
|
||||
{ key: 'search_facets', label: '搜索筛选', type: 'fieldset', sanitizeType: 'array', default: {}, dependsOn: { key: 'search_ms_s', value: false }, help: '搜索结果中显示的筛选列表;启用 Meilisearch 后由搜索引擎生成关联筛选。', fields: [
|
||||
{ key: 'category', label: '[文章] 分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [] },
|
||||
{ key: 'topics', label: '[文章] 专题', type: 'taxonomy_select', taxonomy: 'topics', multiple: true, default: [] },
|
||||
{ key: 'post_tag', label: '[文章] 标签', type: 'taxonomy_select', taxonomy: 'post_tag', multiple: true, default: [] },
|
||||
{ key: 'plate_id', label: '[论坛帖子] 板块 ID', type: 'input', default: '' },
|
||||
{ key: 'forum_topic', label: '[论坛帖子] 话题', type: 'taxonomy_select', taxonomy: 'forum_topic', multiple: true, default: [] },
|
||||
{ key: 'forum_tag', label: '[论坛帖子] 标签', type: 'taxonomy_select', taxonomy: 'forum_tag', multiple: true, default: [] },
|
||||
{ key: 'shop_cat', label: '[商品] 分类', type: 'taxonomy_select', taxonomy: 'shop_cat', multiple: true, default: [] },
|
||||
{ key: 'shop_discount', label: '[商品] 优惠活动', type: 'taxonomy_select', taxonomy: 'shop_discount', multiple: true, default: [] },
|
||||
{ key: 'shop_tag', label: '[商品] 特色标签', type: 'taxonomy_select', taxonomy: 'shop_tag', multiple: true, default: [] },
|
||||
] },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,778 +0,0 @@
|
||||
/**
|
||||
* Page Display domain schemas.
|
||||
* Each key corresponds to a sub-page under the "页面&显示" section.
|
||||
*/
|
||||
|
||||
/** Theme asset base for layout previews (admin SPA). */
|
||||
function themeAsset(path) {
|
||||
const base = (typeof window !== 'undefined' && window.aksrtAdminData && window.aksrtAdminData.themeUri)
|
||||
? String(window.aksrtAdminData.themeUri).replace(/\/$/, '')
|
||||
: ''
|
||||
return base ? `${base}/${path.replace(/^\//, '')}` : path
|
||||
}
|
||||
|
||||
/** Homepage sort options — keep in sync with AKSRT_Settings_Home_Cms::homepage_allowed_sorts() */
|
||||
const sortOptions = [
|
||||
{ label: '最近更新', value: 'modified' },
|
||||
{ label: '发布时间', value: 'date' },
|
||||
{ label: '浏览量', value: 'views' },
|
||||
{ label: '点赞数', value: 'likes' },
|
||||
{ label: '评论数', value: 'comments' },
|
||||
{ label: '收藏数', value: 'favorites' },
|
||||
{ label: '下载量', value: 'downloads' },
|
||||
{ label: '随机', value: 'rand' },
|
||||
]
|
||||
|
||||
export const pageDisplaySchemas = {
|
||||
/**
|
||||
* Header navigation settings
|
||||
*/
|
||||
'header-nav': {
|
||||
key: 'page_display.header-nav',
|
||||
title: '顶部导航栏',
|
||||
icon: 'fa fas fa-bars',
|
||||
description: '配置导航栏布局、菜单结构、搜索入口和用户入口。',
|
||||
defaults: {
|
||||
nav_fixed: true,
|
||||
nav_scroll_down_hide: true,
|
||||
header_layout: '1',
|
||||
nav_pay_vip: true,
|
||||
nav_user_pay_vip: true,
|
||||
nav_newposts_btns: ['post'],
|
||||
nav_user_newposts_btn: 'post',
|
||||
nav_user_newposts_btn_text: '发布文章',
|
||||
mobile_header_layout: 'center',
|
||||
mobile_navbar_align: 'left',
|
||||
nav_newposts: true,
|
||||
nav_newposts_text: '<i class="fa fas fa-fw fa-pen"></i>发布',
|
||||
nav_newposts_radius: true,
|
||||
nav_newposts_class: 'jb-blue',
|
||||
header_theme_custom: false,
|
||||
header_theme_bg_custom: '',
|
||||
header_theme_bg: '',
|
||||
header_theme_color_custom: '',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'layout',
|
||||
title: '导航栏布局',
|
||||
icon: 'fa fas fa-th-large',
|
||||
fields: [
|
||||
{ key: 'nav_fixed', label: '导航栏一直固定在顶部', type: 'switch', default: true },
|
||||
{ key: 'nav_scroll_down_hide', label: '下滚动隐藏,上滚动显示', type: 'switch', default: true, dependsOn: { key: 'nav_fixed', value: true } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'layout-type',
|
||||
title: '布局类型',
|
||||
icon: 'fa fas fa-columns',
|
||||
fields: [
|
||||
{
|
||||
key: 'header_layout',
|
||||
label: '电脑端布局',
|
||||
type: 'image_select',
|
||||
default: '1',
|
||||
options: [
|
||||
{ label: '布局 1(经典)', value: '1', image: themeAsset('assets/images/header-layout/header_layout_1.png') },
|
||||
{ label: '布局 2(内联搜索)', value: '2', image: themeAsset('assets/images/header-layout/header_layout_2.png') },
|
||||
{ label: '布局 3(紧凑)', value: '3', image: themeAsset('assets/images/header-layout/header_layout_3.png') },
|
||||
],
|
||||
help: '不同布局切换导航 DOM 结构(与 zibll image_select 一致)',
|
||||
},
|
||||
{
|
||||
key: 'mobile_header_layout',
|
||||
label: '移动端布局',
|
||||
type: 'image_select',
|
||||
default: 'center',
|
||||
options: [
|
||||
{ label: '居中(含搜索按钮)', value: 'center', image: themeAsset('assets/images/header-layout/mobile_header_layout_center.png') },
|
||||
{ label: '靠左', value: 'left', image: themeAsset('assets/images/header-layout/mobile_header_layout_left.png') },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'mobile_navbar_align',
|
||||
label: '移动端菜单方向',
|
||||
type: 'radio',
|
||||
default: 'left',
|
||||
options: [
|
||||
{ label: '顶部(关闭手势)', value: 'top' },
|
||||
{ label: '左侧滑出', value: 'left' },
|
||||
{ label: '右侧滑出', value: 'right' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'newposts',
|
||||
title: '发布按钮',
|
||||
icon: 'fa fas fa-pen',
|
||||
fields: [
|
||||
{ key: 'nav_newposts', label: '启用发布按钮', type: 'switch', default: true },
|
||||
{ key: 'nav_newposts_text', label: '按钮文案', type: 'input', default: '<i class="fa fas fa-fw fa-pen"></i>发布', dependsOn: { key: 'nav_newposts', value: true } },
|
||||
{ key: 'nav_newposts_radius', label: '按钮圆角(胶囊)', type: 'switch', default: true, dependsOn: { key: 'nav_newposts', value: true } },
|
||||
{ key: 'nav_newposts_class', label: '按钮颜色', type: 'select', default: 'jb-blue', options: [
|
||||
{ label: '蓝色', value: 'jb-blue' },
|
||||
{ label: '红色', value: 'jb-red' },
|
||||
{ label: '绿色', value: 'jb-green' },
|
||||
{ label: '黄色', value: 'jb-yellow' },
|
||||
{ label: '紫色', value: 'jb-purple' },
|
||||
{ label: '粉色', value: 'jb-pink' },
|
||||
], dependsOn: { key: 'nav_newposts', value: true } },
|
||||
{ key: 'nav_newposts_btns', label: '发布类型', type: 'checkbox', default: ['post'], options: [
|
||||
{ label: '文章', value: 'post' },
|
||||
], dependsOn: { key: 'nav_newposts', value: true } },
|
||||
{ key: 'nav_user_newposts_btn', label: '用户卡发布类型', type: 'select', default: 'post', options: [
|
||||
{ label: '文章', value: 'post' },
|
||||
] },
|
||||
{ key: 'nav_user_newposts_btn_text', label: '用户卡发布文案', type: 'input', default: '发布文章' },
|
||||
{ key: 'nav_pay_vip', label: '导航栏开通会员按钮', type: 'switch', default: true },
|
||||
{ key: 'nav_user_pay_vip', label: '用户框开通会员按钮', type: 'switch', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'color',
|
||||
title: '导航配色',
|
||||
icon: 'fa fas fa-paint-brush',
|
||||
fields: [
|
||||
{ key: 'header_theme_custom', label: '自定义导航配色', type: 'switch', default: false, help: '启用后覆盖导航栏默认配色' },
|
||||
{ key: 'header_theme_bg_custom', label: '导航背景色', type: 'color', default: '', dependsOn: { key: 'header_theme_custom', value: true } },
|
||||
{ key: 'header_theme_bg', label: '导航背景色(备选)', type: 'color', default: '', dependsOn: { key: 'header_theme_custom', value: true } },
|
||||
{ key: 'header_theme_color_custom', label: '导航文字色', type: 'color', default: '', dependsOn: { key: 'header_theme_custom', value: true } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Category page settings
|
||||
*/
|
||||
category: {
|
||||
key: 'page_display.category',
|
||||
title: '分类页面',
|
||||
icon: 'fa fas fa-folder-open',
|
||||
description: '为分类归档页配置头图、简介、列表模式、筛选条和侧边栏。',
|
||||
defaults: {
|
||||
category_cover_enabled: true,
|
||||
category_desc_enabled: true,
|
||||
category_header_style: 'cover',
|
||||
category_list_mode: 'grid',
|
||||
category_filter_bar: true,
|
||||
category_sidebar: true,
|
||||
category_per_page: 12,
|
||||
category_default_cover: '',
|
||||
category_post_count: true,
|
||||
category_orderby: 'modified',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'header',
|
||||
title: '分类头图',
|
||||
icon: 'fa fas fa-image',
|
||||
fields: [
|
||||
{ key: 'category_header_style', label: '头部风格', type: 'radio', default: 'cover', options: [
|
||||
{ label: '封面图', value: 'cover' },
|
||||
{ label: '简洁标题', value: 'simple' },
|
||||
{ label: '隐藏', value: 'none' },
|
||||
]},
|
||||
{ key: 'category_cover_enabled', label: '显示封面图', type: 'switch', default: true, dependsOn: { key: 'category_header_style', value: 'cover' } },
|
||||
{ key: 'category_desc_enabled', label: '显示分类简介', type: 'switch', default: true },
|
||||
{ key: 'category_default_cover', label: '默认封面图', type: 'upload', default: '', help: '未设置分类封面时使用的默认图片' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'list',
|
||||
title: '列表设置',
|
||||
icon: 'fa fas fa-list',
|
||||
fields: [
|
||||
{ key: 'category_list_mode', label: '列表模式', type: 'radio', default: 'grid', options: [
|
||||
{ label: '网格', value: 'grid' },
|
||||
{ label: '列表', value: 'list' },
|
||||
{ label: '紧凑', value: 'compact' },
|
||||
]},
|
||||
{ key: 'category_per_page', label: '每页文章数', type: 'number', default: 12, min: 4, max: 48 },
|
||||
{ key: 'category_filter_bar', label: '筛选条', type: 'switch', default: true, help: '子分类筛选和排序' },
|
||||
{ key: 'category_sidebar', label: '侧边栏', type: 'switch', default: true },
|
||||
{ key: 'category_post_count', label: '显示文章数', type: 'switch', default: true, help: '在分类列表中显示每篇文章的浏览次数' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'orderby',
|
||||
title: '排序设置',
|
||||
icon: 'fa fas fa-sort',
|
||||
fields: [
|
||||
{ key: 'category_orderby', label: '默认排序', type: 'select', default: 'modified', options: [
|
||||
{ label: '最近更新', value: 'modified' },
|
||||
{ label: '发布时间', value: 'date' },
|
||||
{ label: '浏览数', value: 'views' },
|
||||
{ label: '评论数', value: 'comments' },
|
||||
{ label: '点赞数', value: 'likes' },
|
||||
]},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Tag page settings
|
||||
*/
|
||||
tag: {
|
||||
key: 'page_display.tag',
|
||||
title: '标签页面',
|
||||
icon: 'fa fas fa-tags',
|
||||
description: '为标签归档页配置展示样式和布局。',
|
||||
defaults: {
|
||||
tag_header_style: 'simple',
|
||||
tag_default_cover: '',
|
||||
tag_list_mode: 'grid',
|
||||
tag_sidebar: true,
|
||||
tag_per_page: 12,
|
||||
tag_post_count: true,
|
||||
tag_orderby: 'count',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'header',
|
||||
title: '标签头图',
|
||||
icon: 'fa fas fa-tag',
|
||||
fields: [
|
||||
{ key: 'tag_header_style', label: '头部风格', type: 'radio', default: 'simple', options: [
|
||||
{ label: '简洁标题', value: 'simple' },
|
||||
{ label: '封面图', value: 'cover' },
|
||||
{ label: '隐藏', value: 'none' },
|
||||
]},
|
||||
{ key: 'tag_default_cover', label: '默认封面图', type: 'upload', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'list',
|
||||
title: '列表设置',
|
||||
icon: 'fa fas fa-list',
|
||||
fields: [
|
||||
{ key: 'tag_list_mode', label: '列表模式', type: 'radio', default: 'grid', options: [
|
||||
{ label: '网格', value: 'grid' },
|
||||
{ label: '列表', value: 'list' },
|
||||
]},
|
||||
{ key: 'tag_per_page', label: '每页文章数', type: 'number', default: 12, min: 4, max: 48 },
|
||||
{ key: 'tag_sidebar', label: '侧边栏', type: 'switch', default: true },
|
||||
{ key: 'tag_post_count', label: '显示文章数', type: 'switch', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'orderby',
|
||||
title: '排序设置',
|
||||
icon: 'fa fas fa-sort',
|
||||
fields: [
|
||||
{ key: 'tag_orderby', label: '默认排序', type: 'select', default: 'count', options: [
|
||||
{ label: '文章数', value: 'count' },
|
||||
{ label: '名称', value: 'name' },
|
||||
{ label: '最近更新', value: 'modified' },
|
||||
]},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* User homepage settings
|
||||
*/
|
||||
'user-home': {
|
||||
key: 'page_display.user-home',
|
||||
title: '用户主页',
|
||||
icon: 'fa fas fa-user-circle',
|
||||
description: '公开用户主页对齐 Zibll 作者页:通栏头图 + Tab 计数;交易/快捷入口仅在用户中心。',
|
||||
defaults: {
|
||||
user_home_tabs: 'posts,favorites,comments,follow',
|
||||
user_home_tabs_order: 'posts,favorites,comments,follow',
|
||||
user_home_tabs_pc_hide: '',
|
||||
user_home_tabs_mobile_hide: '',
|
||||
user_home_sidebar: false,
|
||||
user_home_cover: true,
|
||||
user_home_route_identifier: 'nicename',
|
||||
user_home_follow: true,
|
||||
user_home_posts_per_page: 10,
|
||||
user_home_avatar: true,
|
||||
user_home_name: true,
|
||||
user_home_desc: true,
|
||||
user_home_stats: false,
|
||||
user_home_social: true,
|
||||
author_main_tab_swiper: false,
|
||||
user_home_message: true,
|
||||
user_home_private: true,
|
||||
user_home_paging_ajax: true,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'layout',
|
||||
title: '主页布局',
|
||||
icon: 'fa fas fa-th-large',
|
||||
fields: [
|
||||
{ key: 'user_home_cover', label: '封面区域', type: 'switch', default: true, help: '用户主页顶部封面图' },
|
||||
{ key: 'user_home_route_identifier', label: '主页 URL 标识', type: 'select', default: 'nicename', options: [{ label: '用户名 URL slug', value: 'nicename' }, { label: '数字用户 ID', value: 'uid' }] },
|
||||
{ key: 'user_home_sidebar', label: '侧边栏(已弃用)', type: 'switch', default: false, help: '公开主页为通栏布局(对齐 Zibll author);运行时强制关闭。交易服务请去用户中心。' },
|
||||
{ key: 'user_home_follow', label: '关注按钮', type: 'switch', default: true },
|
||||
{ key: 'user_home_posts_per_page', label: '每页文章数', type: 'number', default: 10, min: 5, max: 50 },
|
||||
{ key: 'user_home_paging_ajax', label: 'AJAX 翻页', type: 'switch', default: true, help: '使用 AJAX 加载替代传统翻页' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'tabs',
|
||||
title: 'Tab 配置',
|
||||
icon: 'fa fas fa-columns',
|
||||
fields: [
|
||||
{ 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: '逗号分隔要在移动端隐藏的栏目' },
|
||||
{ key: 'author_main_tab_swiper', label: '移动端滑动切换', type: 'switch', default: false, help: '移动端左右滑动切换栏目' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'display',
|
||||
title: '展示设置',
|
||||
icon: 'fa fas fa-eye',
|
||||
fields: [
|
||||
{ key: 'user_home_avatar', label: '显示头像', type: 'switch', default: true },
|
||||
{ key: 'user_home_name', label: '显示用户名', type: 'switch', default: true },
|
||||
{ key: 'user_home_desc', label: '显示个人简介', type: 'switch', default: true },
|
||||
{ key: 'user_home_stats', label: '显示统计数据(已弃用)', type: 'switch', default: false, help: '计数已并入 Tab 角标,头图内不再重复文章/评论/收藏。' },
|
||||
{ key: 'user_home_social', label: '显示社交链接', type: 'switch', default: true },
|
||||
{ key: 'user_home_message', label: '私信/消息按钮', type: 'switch', default: true, help: '本人显示消息中心入口;访客显示私信入口' },
|
||||
{ key: 'user_home_private', label: '留言按钮', type: 'switch', default: true, help: '预留开关,公开主页留言入口' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Other pages settings (404, search results, etc.)
|
||||
*/
|
||||
other: {
|
||||
key: 'page_display.other',
|
||||
title: '其它页面',
|
||||
icon: 'fa fas fa-file',
|
||||
description: '配置 404 页面、搜索结果页等特殊页面的展示。',
|
||||
defaults: {
|
||||
page_header_style: '1',
|
||||
page_header_cover_img: '',
|
||||
page_404_search: true,
|
||||
page_404_popular: true,
|
||||
page_404_message: '页面未找到',
|
||||
page_404_back: true,
|
||||
page_search_sidebar: true,
|
||||
page_search_per_page: 12,
|
||||
page_author_cover: true,
|
||||
page_author_sidebar: true,
|
||||
page_author_posts_per_page: 12,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'page-header',
|
||||
title: '统一页头',
|
||||
icon: 'fa fas fa-heading',
|
||||
fields: [
|
||||
{ key: 'page_header_style', label: '页面标题样式', type: 'select', default: '1', options: [
|
||||
{ label: '不显示', value: '' },
|
||||
{ label: '简单', value: '1' },
|
||||
{ label: '卡片', value: '2' },
|
||||
{ label: '封面', value: '3' },
|
||||
], help: '控制归档/搜索/页面的页头样式,单页可通过 post meta 覆盖' },
|
||||
{ key: 'page_header_cover_img', label: '默认封面图', type: 'upload', default: '', help: '封面样式(样式3)的默认背景图,单页可覆盖' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'page_404',
|
||||
title: '404 页面',
|
||||
icon: 'fa fas fa-exclamation-triangle',
|
||||
fields: [
|
||||
{ key: 'page_404_message', label: '404 提示文字', type: 'input', default: '页面未找到' },
|
||||
{ key: 'page_404_search', label: '显示搜索框', type: 'switch', default: true },
|
||||
{ key: 'page_404_popular', label: '显示热门文章', type: 'switch', default: true },
|
||||
{ key: 'page_404_back', label: '显示返回首页', type: 'switch', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'search_results',
|
||||
title: '搜索结果页',
|
||||
icon: 'fa fas fa-search',
|
||||
fields: [
|
||||
{ key: 'page_search_sidebar', label: '搜索结果侧边栏', type: 'switch', default: true },
|
||||
{ key: 'page_search_per_page', label: '每页结果数', type: 'number', default: 12, min: 4, max: 48 },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'author',
|
||||
title: '作者页面',
|
||||
icon: 'fa fas fa-user',
|
||||
fields: [
|
||||
{ key: 'page_author_cover', label: '显示封面', type: 'switch', default: true },
|
||||
{ key: 'page_author_sidebar', label: '显示侧边栏', type: 'switch', default: true },
|
||||
{ key: 'page_author_posts_per_page', label: '每页文章数', type: 'number', default: 12, min: 4, max: 48 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Global background settings
|
||||
*/
|
||||
background: {
|
||||
key: 'page_display.background',
|
||||
title: '全局背景',
|
||||
icon: 'fa fas fa-image',
|
||||
description: '配置站点全局背景图、背景色、暗色模式背景和遮罩。',
|
||||
defaults: {
|
||||
bg_color: '',
|
||||
bg_image: '',
|
||||
bg_size: '100%',
|
||||
bg_position: 'center center',
|
||||
bg_repeat: 'no-repeat',
|
||||
bg_attachment: 'fixed',
|
||||
bg_dark_color: '',
|
||||
bg_dark_image: '',
|
||||
bg_overlay: false,
|
||||
bg_overlay_color: 'rgba(0, 0, 0, 0.3)',
|
||||
page_bg_color: '',
|
||||
page_bg_image: '',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'light',
|
||||
title: '亮色背景',
|
||||
icon: 'fa fas fa-sun',
|
||||
fields: [
|
||||
{ key: 'bg_color', label: '背景颜色', type: 'color', default: '', help: '留空则使用主题默认背景色' },
|
||||
{ key: 'bg_image', label: '背景图片', type: 'media', help: '推荐使用高分辨率图片' },
|
||||
{ key: 'bg_size', label: '图片尺寸', type: 'select', default: 'cover', options: [
|
||||
{ label: '覆盖 (Cover)', value: 'cover' },
|
||||
{ label: '包含 (Contain)', value: 'contain' },
|
||||
{ label: '原始大小', value: 'auto' },
|
||||
{ label: '拉伸填充', value: '100% 100%' },
|
||||
{ label: '100% 宽度(旧版兼容)', value: '100%' },
|
||||
]},
|
||||
{ key: 'bg_position', label: '图片位置', type: 'select', default: 'center center', options: [
|
||||
{ label: '居中', value: 'center center' },
|
||||
{ label: '顶部居中', value: 'top center' },
|
||||
{ label: '底部居中', value: 'bottom center' },
|
||||
]},
|
||||
{ key: 'bg_repeat', label: '图片重复', type: 'select', default: 'no-repeat', options: [
|
||||
{ label: '不重复', value: 'no-repeat' },
|
||||
{ label: '平铺', value: 'repeat' },
|
||||
{ label: '水平平铺', value: 'repeat-x' },
|
||||
{ label: '垂直平铺', value: 'repeat-y' },
|
||||
]},
|
||||
{ key: 'bg_attachment', label: '固定方式', type: 'select', default: 'fixed', options: [
|
||||
{ label: '固定 (视差)', value: 'fixed' },
|
||||
{ label: '随内容滚动', value: 'scroll' },
|
||||
]},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'dark',
|
||||
title: '暗色背景',
|
||||
icon: 'fa fas fa-moon',
|
||||
fields: [
|
||||
{ key: 'bg_dark_color', label: '暗色背景颜色', type: 'color', default: '', help: '留空则使用主题默认暗色背景' },
|
||||
{ key: 'bg_dark_image', label: '暗色背景图片', type: 'media', help: '暗色模式下显示的背景图' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'overlay',
|
||||
title: '遮罩设置',
|
||||
icon: 'fa fas fa-clone',
|
||||
fields: [
|
||||
{ key: 'bg_overlay', label: '启用背景遮罩', type: 'switch', default: false, help: '在背景图上方叠加半透明遮罩,提升文字可读性' },
|
||||
{ key: 'bg_overlay_color', label: '遮罩颜色', type: 'input', default: 'rgba(0, 0, 0, 0.3)', dependsOn: { key: 'bg_overlay', value: true }, help: '支持 rgba/hex 色值' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'page',
|
||||
title: '页面背景',
|
||||
icon: 'fa fas fa-file',
|
||||
fields: [
|
||||
{ key: 'page_bg_color', label: '内容区背景色', type: 'color', default: '', help: '主内容区域的背景色' },
|
||||
{ key: 'page_bg_image', label: '内容区背景图', type: 'media' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Header components
|
||||
*/
|
||||
'header-components': {
|
||||
key: 'page_display.header-components',
|
||||
title: '顶部多功能组件',
|
||||
icon: 'fa fas fa-th',
|
||||
description: 'Zibll 顶部幻灯、搜索覆盖层和图标卡片的原始字段契约。',
|
||||
defaults: {
|
||||
header_slider_show: [],
|
||||
header_slider_show_type: '',
|
||||
header_slider_nav_color: '#ffffff',
|
||||
header_slider: [
|
||||
{ background: themeAsset('assets/vendor/zibll/img/slider-bg.jpg'), link: { url: 'https://www.zibll.com/', target: '_blank' }, text: { desc: '', title: '' }, text_align: 'left-bottom', text_parallax: 30, text_size_m: 20, text_size_pc: 30 },
|
||||
{ background: themeAsset('assets/vendor/zibll/img/user_t.jpg'), link: { url: 'https://www.zibll.com/', target: '_blank' }, text: { desc: '', title: '' }, text_align: 'left-bottom', text_parallax: 30, text_size_m: 20, text_size_pc: 30 },
|
||||
],
|
||||
header_slider_option: { direction: 'horizontal', loop: true, button: true, pagination: true, effect: 'slide', scale_height: false, auto_height: false, pc_height: 550, m_height: 280, spacebetween: 0, speed: 0, autoplay: true, interval: 6 },
|
||||
header_slider_search_s: ['pc_s', 'm_s'],
|
||||
header_slider_search_option: { show_keywords: true, popular_limit: 6, show_input_cat: false, in_cat: '', more_cats: [], show_type: false, in_type: 'post', before_html: '<div class="em16 font-bold mb10">更优雅的Wordpress主题</div>\n<div>这是导航栏幻灯片多功能组件,可在后台进行配置</div>', after_html: '' },
|
||||
header_slider_card_s: ['pc_s', 'm_s'],
|
||||
header_slider_card_option: { show_widget_bg: false, icon_radius4: false, cards: [
|
||||
{ icon: 'zibsvg-vip_1', customize_icon: '', icon_class: 'c-yellow', link: {}, title: '图标卡片', desc: '这是一个图标卡片示例' },
|
||||
{ icon: 'zibsvg-vip_2', customize_icon: '', icon_class: 'c-blue-2', link: {}, title: '原创作品', desc: '这是一个图标卡片示例' },
|
||||
{ icon: 'zibsvg-hot', customize_icon: '', icon_class: 'jb-pink', link: {}, title: '灵感来源<badge class="ml6 jb-yellow">NEW</badge>', desc: '这是一个图标卡片示例' },
|
||||
{ icon: 'fa fas fa-ioxhost', customize_icon: '', icon_class: 'jb-cyan', link: {}, title: '系统工具 <badge class="ml6 jb-blue">GO <span class="fa fas fa-angle-right em12"></span></badge>', desc: '这是一个图标卡片示例' },
|
||||
] },
|
||||
header_slider_filter_blur: ['pc_s', 'm_s'],
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'visibility',
|
||||
title: '显示规则',
|
||||
icon: 'fa fas fa-eye',
|
||||
fields: [
|
||||
{ key: 'header_slider_show', label: '启用顶部多功能组件的页面', type: 'checkbox', default: [], options: [
|
||||
{ label: '首页', value: 'home' },
|
||||
{ label: '分类页', value: 'category' },
|
||||
{ label: '标签页', value: 'tag' },
|
||||
{ label: '用户页', value: 'author' },
|
||||
{ label: '文章页', value: 'single' },
|
||||
{ label: '搜索页', value: 'search' },
|
||||
{ label: '其它页面', value: 'page' },
|
||||
{ label: '专题页', value: 'topics' },
|
||||
{ label: '论坛首页', value: 'forum_home' },
|
||||
{ label: '论坛板块', value: 'forum_plate' },
|
||||
{ label: '论坛帖子', value: 'forum_post' },
|
||||
] },
|
||||
{ key: 'header_slider_show_type', label: '设备显示规则', type: 'radio', default: '', options: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '仅电脑', value: 'only_pc' },
|
||||
{ label: '仅移动', value: 'only_sm' },
|
||||
], dependsOn: { key: 'header_slider_show', not: [] } },
|
||||
{ key: 'header_slider_nav_color', label: '导航栏文字颜色', type: 'color', default: '#ffffff', dependsOn: { key: 'header_slider_show', not: [] } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'slides',
|
||||
title: '背景内容',
|
||||
icon: 'fa fas fa-image',
|
||||
fields: [
|
||||
{ key: 'header_slider', label: '幻灯片项目', type: 'json', sanitizeType: 'array', default: [
|
||||
{ background: themeAsset('assets/vendor/zibll/img/slider-bg.jpg'), link: { url: 'https://www.zibll.com/', target: '_blank' }, text: { desc: '', title: '' }, text_align: 'left-bottom', text_parallax: 30, text_size_m: 20, text_size_pc: 30 },
|
||||
{ background: themeAsset('assets/vendor/zibll/img/user_t.jpg'), link: { url: 'https://www.zibll.com/', target: '_blank' }, text: { desc: '', title: '' }, text_align: 'left-bottom', text_parallax: 30, text_size_m: 20, text_size_pc: 30 },
|
||||
], help: '原始数组字段:background、background_video、video、hide、link、image_layer、text。' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'slider-config',
|
||||
title: '背景显示设置',
|
||||
icon: 'fa fas fa-sliders-h',
|
||||
fields: [
|
||||
{ key: 'header_slider_option', label: '幻灯片参数', type: 'json', sanitizeType: 'array', default: { direction: 'horizontal', loop: true, button: true, pagination: true, effect: 'slide', scale_height: false, auto_height: false, pc_height: 550, m_height: 280, spacebetween: 0, speed: 0, autoplay: true, interval: 6 } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'search',
|
||||
title: '搜索组件',
|
||||
icon: 'fa fas fa-search',
|
||||
fields: [
|
||||
{ key: 'header_slider_search_s', label: '显示设备', type: 'checkbox', default: ['pc_s', 'm_s'], options: [{ label: 'PC 端显示', value: 'pc_s' }, { label: '移动端显示', value: 'm_s' }] },
|
||||
{ key: 'header_slider_search_option', label: '搜索组件参数', type: 'json', sanitizeType: 'array', default: { show_keywords: true, popular_limit: 6, show_input_cat: false, in_cat: '', more_cats: [], show_type: false, in_type: 'post', before_html: '<div class="em16 font-bold mb10">更优雅的Wordpress主题</div>\n<div>这是导航栏幻灯片多功能组件,可在后台进行配置</div>', after_html: '' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'cards',
|
||||
title: '图标卡片组件',
|
||||
icon: 'fa fas fa-th-large',
|
||||
fields: [
|
||||
{ key: 'header_slider_card_s', label: '显示设备', type: 'checkbox', default: ['pc_s', 'm_s'], options: [{ label: 'PC 端显示', value: 'pc_s' }, { label: '移动端显示', value: 'm_s' }] },
|
||||
{ key: 'header_slider_card_option', label: '图标卡片参数', type: 'json', sanitizeType: 'array', default: { show_widget_bg: false, icon_radius4: false, cards: [
|
||||
{ icon: 'zibsvg-vip_1', customize_icon: '', icon_class: 'c-yellow', link: {}, title: '图标卡片', desc: '这是一个图标卡片示例' },
|
||||
{ icon: 'zibsvg-vip_2', customize_icon: '', icon_class: 'c-blue-2', link: {}, title: '原创作品', desc: '这是一个图标卡片示例' },
|
||||
{ icon: 'zibsvg-hot', customize_icon: '', icon_class: 'jb-pink', link: {}, title: '灵感来源<badge class="ml6 jb-yellow">NEW</badge>', desc: '这是一个图标卡片示例' },
|
||||
{ icon: 'fa fas fa-ioxhost', customize_icon: '', icon_class: 'jb-cyan', link: {}, title: '系统工具 <badge class="ml6 jb-blue">GO <span class="fa fas fa-angle-right em12"></span></badge>', desc: '这是一个图标卡片示例' },
|
||||
] } },
|
||||
{ key: 'header_slider_filter_blur', label: '组件高斯模糊特效', type: 'checkbox', default: ['pc_s', 'm_s'], options: [{ label: 'PC 端开启', value: 'pc_s' }, { label: '移动端开启', value: 'm_s' }] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
footer: {
|
||||
key: 'page_display.footer',
|
||||
title: '底部页脚',
|
||||
icon: 'fa fas fa-paw',
|
||||
description: '页脚布局、友链、版权信息配置。实际编辑界面在 FooterSettings.vue(customPage),此处为 REST 聚合描述。',
|
||||
defaults: {
|
||||
footer_columns: 3,
|
||||
footer_copyright: true,
|
||||
footer_icp: true,
|
||||
footer_police: true,
|
||||
footer_theme_custom: false,
|
||||
footer_theme_bg_custom: '',
|
||||
footer_theme_bg: '',
|
||||
footer_theme_color_custom: '',
|
||||
footer_contact_wechat_img: '',
|
||||
footer_contact_qq: '',
|
||||
footer_contact_weibo: '',
|
||||
footer_contact_email: '',
|
||||
footer_t1_m_s: ['pc', 'mobile'],
|
||||
footer_t1_img: '',
|
||||
footer_t1_img_dark: '',
|
||||
footer_t1_t: '',
|
||||
fcode_t1_code: '',
|
||||
footer_contact_m_s: ['pc', 'mobile'],
|
||||
footer_contact_more: '',
|
||||
fcode_t2_code_1: '',
|
||||
fcode_t2_code_2: '',
|
||||
footer_mini_img_m_s: ['pc', 'mobile'],
|
||||
footer_mini_img: '',
|
||||
fcode_customize_code: '',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'layout',
|
||||
title: '页脚布局',
|
||||
icon: 'fa fas fa-columns',
|
||||
fields: [
|
||||
{ key: 'footer_columns', label: '页脚列数', type: 'number', default: 3, min: 1, max: 4 },
|
||||
{ key: 'footer_copyright', label: '显示版权信息', type: 'switch', default: true },
|
||||
{ key: 'footer_icp', label: '显示ICP备案', type: 'switch', default: true },
|
||||
{ key: 'footer_police', label: '显示公安备案', type: 'switch', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'color',
|
||||
title: '页脚颜色',
|
||||
icon: 'fa fas fa-paint-brush',
|
||||
fields: [
|
||||
{ key: 'footer_theme_custom', label: '自定义页脚颜色', type: 'switch', default: false },
|
||||
{ key: 'footer_theme_bg_custom', label: '自定义背景色', type: 'color', default: '' },
|
||||
{ key: 'footer_theme_bg', label: '背景图片', type: 'media', default: '' },
|
||||
{ key: 'footer_theme_color_custom', label: '自定义文字颜色', type: 'color', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'contact',
|
||||
title: '联系方式',
|
||||
icon: 'fa fas fa-address-book',
|
||||
fields: [
|
||||
{ key: 'footer_contact_wechat_img', label: '微信二维码', type: 'media', default: '' },
|
||||
{ key: 'footer_contact_qq', label: 'QQ', type: 'input', default: '' },
|
||||
{ key: 'footer_contact_weibo', label: '微博', type: 'input', default: '' },
|
||||
{ key: 'footer_contact_email', label: '邮箱', type: 'input', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'brand',
|
||||
title: '品牌列(第一列)',
|
||||
icon: 'fa fas fa-building',
|
||||
fields: [
|
||||
{ key: 'footer_t1_m_s', label: '显示设备', type: 'checkbox', default: ['pc', 'mobile'] },
|
||||
{ key: 'footer_t1_img', label: '页脚 Logo(日间)', type: 'media', default: '' },
|
||||
{ key: 'footer_t1_img_dark', label: '页脚 Logo(夜间)', type: 'media', default: '' },
|
||||
{ key: 'footer_t1_t', label: '品牌标题', type: 'input', default: '' },
|
||||
{ key: 'fcode_t1_code', label: '品牌列自定义代码', type: 'textarea', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'contact_ext',
|
||||
title: '联系方式扩展',
|
||||
icon: 'fa fas fa-phone',
|
||||
fields: [
|
||||
{ key: 'footer_contact_m_s', label: '显示设备', type: 'checkbox', default: ['pc', 'mobile'] },
|
||||
{ key: 'footer_contact_more', label: '更多联系项', type: 'textarea', default: '' },
|
||||
{ key: 'fcode_t2_code_1', label: '链接 HTML(fcode_t2_code_1)', type: 'textarea', default: '' },
|
||||
{ key: 'fcode_t2_code_2', label: '链接下方自定义代码', type: 'textarea', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'mini_qr',
|
||||
title: '二维码图片组(第三列)',
|
||||
icon: 'fa fas fa-qrcode',
|
||||
fields: [
|
||||
{ key: 'footer_mini_img_m_s', label: '显示设备', type: 'checkbox', default: ['pc', 'mobile'] },
|
||||
{ key: 'footer_mini_img', label: '二维码组', type: 'textarea', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'custom',
|
||||
title: '底部自定义',
|
||||
icon: 'fa fas fa-code',
|
||||
fields: [
|
||||
{ key: 'fcode_customize_code', label: '底部自定义代码', type: 'textarea', default: '' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Homepage settings (already has a real Vue page)
|
||||
*/
|
||||
homepage: {
|
||||
key: 'page_display.homepage',
|
||||
title: '首页配置',
|
||||
icon: 'fa fas fa-home',
|
||||
description: '配置首页主列表、分类 Tab 与首页交互。文章列表视觉统一由文章列表设置控制。',
|
||||
defaults: {
|
||||
homepage_tabs: [],
|
||||
homepage_sidebar_mini_enabled: true,
|
||||
homepage_default_sort: 'date',
|
||||
homepage_sort_ajax_enabled: true,
|
||||
homepage_sticky_tabs_enabled: true,
|
||||
home_posts_list_s: true,
|
||||
home_list_title: '最新发布',
|
||||
home_list_sort_enabled: true,
|
||||
home_list_sort_options: ['modified', 'date', 'views', 'likes', 'comments'],
|
||||
home_list_sort_dropdown: false,
|
||||
home_exclude_post_ids: [],
|
||||
home_exclude_category_ids: [],
|
||||
homepage_tab_posts_per_page: 6,
|
||||
homepage_tab_limit: 4,
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'main-stream',
|
||||
title: '主列表',
|
||||
icon: 'fa fas fa-list',
|
||||
fields: [
|
||||
{ key: 'home_posts_list_s', label: '启用主列表', type: 'switch', default: true },
|
||||
{ key: 'index_list_title', label: '区块标题', type: 'input', default: '最新发布', dependsOn: { key: 'home_posts_list_s', value: true } },
|
||||
{ key: 'home_list1_orderby_option.orderby', label: '默认排序', type: 'select', default: 'date', dependsOn: { key: 'home_posts_list_s', value: true }, options: sortOptions },
|
||||
{ key: 'home_list1_orderby_s', label: '启用排序栏', type: 'switch', default: true, dependsOn: { key: 'home_posts_list_s', value: true } },
|
||||
{ key: 'home_list_sort_dropdown', label: '排序栏下拉模式', type: 'switch', default: false, dependsOn: { key: 'home_list1_orderby_s', value: true } },
|
||||
{ key: 'home_list1_orderby_option', label: '可用排序选项', type: 'checkbox', default: ['modified', 'date', 'views', 'likes', 'comments'], dependsOn: { key: 'home_list1_orderby_s', value: true }, options: sortOptions },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'exclude',
|
||||
title: '排除规则',
|
||||
icon: 'fa fas fa-filter',
|
||||
fields: [
|
||||
{ key: 'home_exclude_posts', label: '排除文章 ID', type: 'array', default: [], help: '主列表和 Tab 查询均排除这些文章。' },
|
||||
{ key: 'home_exclude_cats', label: '排除分类 ID', type: 'array', default: [], help: '主列表排除这些分类。' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'tabs',
|
||||
title: '首页 Tab',
|
||||
icon: 'fa fas fa-tags',
|
||||
fields: [
|
||||
{ key: 'home_lists', label: '首页 Tab', type: 'array', default: [], help: '分类 Tab 由 HomepageSettings.vue 中的可视化编辑器维护。' },
|
||||
{ key: 'homepage_tab_limit', label: 'Tab 数量上限', type: 'number', default: 4, min: 1, max: 12 },
|
||||
{ key: 'homepage_tab_posts_per_page', label: '每 Tab 文章数', type: 'number', default: 6, min: 1, max: 20 },
|
||||
{ key: 'homepage_sticky_tabs_enabled', label: 'Tab 吸顶', type: 'switch', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'cards-sidebar',
|
||||
title: '侧栏与交互',
|
||||
icon: 'fa fas fa-columns',
|
||||
fields: [
|
||||
{ key: 'homepage_sidebar_mini_enabled', label: '迷你侧栏列表', type: 'switch', default: true },
|
||||
{ key: 'homepage_sort_ajax_enabled', label: '排序栏 AJAX', type: 'switch', default: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
}
|
||||
@@ -37,7 +37,114 @@
|
||||
|
||||
#aksrt-admin-app .aksrt-admin-layout {
|
||||
min-height: 100vh;
|
||||
background: #f5f7fb;
|
||||
background: rgb(249, 250, 252);
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-csf-shell .n-layout-sider {
|
||||
background: #fff;
|
||||
border-right: 1px solid #f2f2f2;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-csf-shell .n-menu-item-content {
|
||||
border-bottom: 1px solid #f4f4f4;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-csf-shell .n-menu-item-content--selected,
|
||||
#aksrt-admin-app .aksrt-csf-shell .n-menu-item-content:hover {
|
||||
color: #0997f5;
|
||||
background: rgb(249, 250, 252);
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-schema-page {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-form-renderer__header {
|
||||
margin: -30px -30px 0;
|
||||
padding: 18px 30px;
|
||||
border-bottom: 1px solid #eee;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-form-renderer__title {
|
||||
color: #0991f5;
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field-group {
|
||||
margin: 0 -30px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field-group__body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(150px, 22%) minmax(0, 1fr);
|
||||
column-gap: 24px;
|
||||
align-items: start;
|
||||
margin: 0;
|
||||
padding: 20px 30px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field > :not(.aksrt-field__label-row):not(.aksrt-field__desc):not(.aksrt-field__error) {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field__label-row {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
margin: 4px 0 0;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field__label {
|
||||
color: #0991f5;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field__desc {
|
||||
grid-column: 1;
|
||||
margin: 26px 0 0;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field--notice {
|
||||
display: block;
|
||||
padding-top: 14px;
|
||||
padding-bottom: 14px;
|
||||
background: #fff8e5;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field__fieldset .aksrt-field,
|
||||
#aksrt-admin-app .aksrt-field__repeater .aksrt-field,
|
||||
#aksrt-admin-app .aksrt-field__sortable .aksrt-field {
|
||||
padding: 12px 0;
|
||||
border-bottom-color: #eee;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field__repeater-item,
|
||||
#aksrt-admin-app .aksrt-field__fieldset {
|
||||
border-radius: 2px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-form-renderer__save-bar {
|
||||
left: auto;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-top: 1px solid #f2f2f2;
|
||||
background: rgba(255, 255, 255, .96);
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-form-renderer__save-bar .n-button--primary-type {
|
||||
border-radius: 30px;
|
||||
background-image: linear-gradient(135deg, #65bff7 10%, #0d67b9 100%);
|
||||
box-shadow: -1px 2px 10px rgba(1, 65, 139, .3);
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-page-container,
|
||||
@@ -253,6 +360,24 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-form-renderer__header {
|
||||
margin: -14px -12px 0;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field-group {
|
||||
margin: 0 -12px;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-field__desc {
|
||||
margin: 2px 0 8px;
|
||||
}
|
||||
|
||||
#aksrt-admin-app .aksrt-page-title {
|
||||
font-size: 18px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
@@ -1,491 +0,0 @@
|
||||
/**
|
||||
* AKSRT CMS Home Module Schema
|
||||
*
|
||||
* 定义每类模块的:
|
||||
* - 元数据(label / icon / desc)
|
||||
* - 默认 config
|
||||
* - 表单字段(用于配置抽屉自动生成 UI)
|
||||
*
|
||||
* 字段类型 type:
|
||||
* - text n-input
|
||||
* - number n-input-number
|
||||
* - select n-select (options)
|
||||
* - radio n-radio-group (options)
|
||||
* - switch n-switch
|
||||
* - cats n-select (multiple, 动态加载分类)
|
||||
* - cat n-select (single, 动态加载分类)
|
||||
* - textarea n-input type=textarea
|
||||
*/
|
||||
|
||||
export const MODULE_TYPES = [
|
||||
'banner',
|
||||
'category',
|
||||
'tabs',
|
||||
'ranking',
|
||||
'recent',
|
||||
'tag_cloud',
|
||||
'friend_links',
|
||||
'html',
|
||||
'hotspot',
|
||||
'category_recommend_cards',
|
||||
'text_title',
|
||||
'button_group',
|
||||
'spacing',
|
||||
'faq',
|
||||
'data_grid',
|
||||
'highlight_cards',
|
||||
'partner_marquee',
|
||||
'product_showcase',
|
||||
'user',
|
||||
'more',
|
||||
]
|
||||
|
||||
export const MODULE_LIBRARY = [
|
||||
{
|
||||
type: 'banner',
|
||||
label: 'Banner 轮播',
|
||||
icon: 'rectangle',
|
||||
desc: '固定首页骨架:正文区轮播条。幻灯片内容在「页面&显示 → Banner 轮播」维护。',
|
||||
defaultConfig: {},
|
||||
fields: [],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: '分类块',
|
||||
icon: 'folder',
|
||||
desc: '指定分类的最新内容网格',
|
||||
defaultConfig: { cat_id: 0, count: 8, layout: 'card', columns: 4, title: '' },
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题(留空用分类名)' },
|
||||
{ key: 'cat_id', type: 'cat', label: '分类' },
|
||||
{ key: 'count', type: 'number', label: '数量', min: 1, max: 20 },
|
||||
{ key: 'columns', type: 'number', label: '列数', min: 1, max: 6 },
|
||||
{
|
||||
key: 'layout', type: 'select', label: '布局',
|
||||
options: [
|
||||
{ label: '卡片', value: 'card' },
|
||||
{ label: '列表', value: 'list' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'tabs',
|
||||
label: '分类 Tabs',
|
||||
icon: 'switcher',
|
||||
desc: '多分类 Tab 切换',
|
||||
defaultConfig: { cat_ids: [], count: 8, layout: 'card', title: '' },
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{ key: 'cat_ids', type: 'cats', label: '分类列表' },
|
||||
{ key: 'count', type: 'number', label: '每个 Tab 数量', min: 1, max: 20 },
|
||||
{
|
||||
key: 'layout', type: 'select', label: '布局',
|
||||
options: [
|
||||
{ label: '卡片', value: 'card' },
|
||||
{ label: '列表', value: 'list' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ranking',
|
||||
label: '排行榜',
|
||||
icon: 'trophy',
|
||||
desc: '按下载量 / 阅读量 / 评论数排序(普通文章)',
|
||||
defaultConfig: { metric: 'downloads', count: 6, title: '' },
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{
|
||||
key: 'metric', type: 'radio', label: '排序依据',
|
||||
options: [
|
||||
{ label: '下载量', value: 'downloads' },
|
||||
{ label: '阅读量', value: 'views' },
|
||||
{ label: '评论数', value: 'comments' },
|
||||
],
|
||||
},
|
||||
{ key: 'count', type: 'number', label: '数量', min: 1, max: 20 },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'recent',
|
||||
label: '最新文章',
|
||||
icon: 'clock',
|
||||
desc: '按时间倒序的最新文章',
|
||||
defaultConfig: { count: 6, layout: 'card', title: '' },
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{ key: 'count', type: 'number', label: '数量', min: 1, max: 20 },
|
||||
{
|
||||
key: 'layout', type: 'select', label: '布局',
|
||||
options: [
|
||||
{ label: '卡片', value: 'card' },
|
||||
{ label: '列表', value: 'list' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'tag_cloud',
|
||||
label: '标签云',
|
||||
icon: 'tag',
|
||||
desc: '按使用频率展示热门标签',
|
||||
defaultConfig: { limit: 30, title: '' },
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{ key: 'limit', type: 'number', label: '数量', min: 5, max: 100 },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'friend_links',
|
||||
label: '友情链接',
|
||||
icon: 'link',
|
||||
desc: '展示已配置的友链',
|
||||
defaultConfig: { category: '', title: '' },
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{ key: 'category', type: 'text', label: '分类筛选(可空)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'html',
|
||||
label: '自定义 HTML',
|
||||
icon: 'code',
|
||||
desc: '自由插入 HTML 片段(受 wp_kses_post 清洗)',
|
||||
defaultConfig: { html: '', title: '' },
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题(可空)' },
|
||||
{ key: 'html', type: 'textarea', label: 'HTML 内容', rows: 6 },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'hotspot',
|
||||
label: '热点推荐',
|
||||
icon: 'fire',
|
||||
desc: '文章推荐列表(纯文本,仅桌面端显示)',
|
||||
defaultConfig: { count: 6, source: 'auto', title: '' },
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{ key: 'count', type: 'number', label: '数量', min: 3, max: 20 },
|
||||
{
|
||||
key: 'source', type: 'select', label: '数据来源',
|
||||
options: [
|
||||
{ label: '自动推荐(推荐文章优先)', value: 'auto' },
|
||||
{ label: '最新文章', value: 'recent' },
|
||||
{ label: '最热文章(按阅读量)', value: 'views' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category_recommend_cards',
|
||||
label: '分类推荐卡片',
|
||||
icon: 'grid',
|
||||
desc: '4 张分类卡片 2×2 布局,含封面图、浏览量胶囊和文章列表',
|
||||
defaultConfig: {
|
||||
title: '分类推荐',
|
||||
cat_ids: [],
|
||||
show_views: true,
|
||||
show_dates: true,
|
||||
},
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{ key: 'cat_ids', type: 'cats', label: '分类(按选择顺序对应卡片 1-4)' },
|
||||
{ key: 'show_views', type: 'switch', label: '显示浏览量胶囊' },
|
||||
{ key: 'show_dates', type: 'switch', label: '显示发布时间' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'text_title',
|
||||
label: '标题文案',
|
||||
icon: 'font-colors',
|
||||
desc: '带标题、副标题和可选按钮的文案区块',
|
||||
defaultConfig: {
|
||||
title: '',
|
||||
desc: '',
|
||||
align: 'left',
|
||||
style: 'default',
|
||||
button_text: '',
|
||||
button_url: '',
|
||||
button_style: 'primary',
|
||||
},
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{ key: 'desc', type: 'textarea', label: '副标题/描述', rows: 2 },
|
||||
{ key: 'align', type: 'radio', label: '对齐', options: [
|
||||
{ label: '左对齐', value: 'left' },
|
||||
{ label: '居中', value: 'center' },
|
||||
{ label: '右对齐', value: 'right' },
|
||||
]},
|
||||
{ key: 'style', type: 'select', label: '样式', options: [
|
||||
{ label: '默认', value: 'default' },
|
||||
{ label: '带左边框', value: 'bordered' },
|
||||
{ label: '带背景', value: 'bg' },
|
||||
]},
|
||||
{ key: 'button_text', type: 'text', label: '按钮文字(可空)' },
|
||||
{ key: 'button_url', type: 'text', label: '按钮链接' },
|
||||
{ key: 'button_style', type: 'select', label: '按钮样式', options: [
|
||||
{ label: '主色', value: 'primary' },
|
||||
{ label: '轮廓', value: 'outline' },
|
||||
{ label: '文字', value: 'text' },
|
||||
]},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'button_group',
|
||||
label: '按钮组',
|
||||
icon: 'th-large',
|
||||
desc: '多按钮横向排列,适合快捷入口',
|
||||
defaultConfig: {
|
||||
title: '',
|
||||
buttons: [],
|
||||
columns: 4,
|
||||
style: 'card',
|
||||
},
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题(可空)' },
|
||||
{ key: 'columns', type: 'number', label: '每行列数', min: 2, max: 6 },
|
||||
{ key: 'style', type: 'select', label: '按钮样式', options: [
|
||||
{ label: '卡片', value: 'card' },
|
||||
{ label: '图标', value: 'icon' },
|
||||
{ label: '文字', value: 'text' },
|
||||
]},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'spacing',
|
||||
label: '间距分隔',
|
||||
icon: 'swap',
|
||||
desc: '模块间间距或分隔线',
|
||||
defaultConfig: {
|
||||
height: 40,
|
||||
divider: false,
|
||||
divider_style: 'solid',
|
||||
},
|
||||
fields: [
|
||||
{ key: 'height', type: 'number', label: '高度 (px)', min: 0, max: 200 },
|
||||
{ key: 'divider', type: 'switch', label: '显示分隔线' },
|
||||
{ key: 'divider_style', type: 'select', label: '分隔线样式', options: [
|
||||
{ label: '实线', value: 'solid' },
|
||||
{ label: '虚线', value: 'dashed' },
|
||||
{ label: '点线', value: 'dotted' },
|
||||
]},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'faq',
|
||||
label: '常见问题',
|
||||
icon: 'question-circle',
|
||||
desc: '折叠式 FAQ 列表',
|
||||
defaultConfig: {
|
||||
title: '常见问题',
|
||||
items: [],
|
||||
style: 'accordion',
|
||||
},
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{ key: 'style', type: 'radio', label: '样式', options: [
|
||||
{ label: '手风琴', value: 'accordion' },
|
||||
{ label: '卡片', value: 'card' },
|
||||
]},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'data_grid',
|
||||
label: '数据网格',
|
||||
icon: 'table',
|
||||
desc: '多列数据卡片展示(统计、特征等)',
|
||||
defaultConfig: {
|
||||
title: '',
|
||||
columns: 4,
|
||||
items: [],
|
||||
style: 'number',
|
||||
},
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题(可空)' },
|
||||
{ key: 'columns', type: 'number', label: '列数', min: 2, max: 6 },
|
||||
{ key: 'style', type: 'select', label: '样式', options: [
|
||||
{ label: '数字突出', value: 'number' },
|
||||
{ label: '图标突出', value: 'icon' },
|
||||
{ label: '简洁', value: 'minimal' },
|
||||
]},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'highlight_cards',
|
||||
label: '高亮卡片',
|
||||
icon: 'star',
|
||||
desc: '带配色的特色卡片网格',
|
||||
defaultConfig: {
|
||||
title: '',
|
||||
columns: 3,
|
||||
cards: [],
|
||||
},
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题(可空)' },
|
||||
{ key: 'columns', type: 'number', label: '列数', min: 2, max: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'partner_marquee',
|
||||
label: '合作伙伴',
|
||||
icon: 'handshake',
|
||||
desc: 'Logo 滚动展示(合作伙伴/品牌墙)',
|
||||
defaultConfig: {
|
||||
title: '合作伙伴',
|
||||
logos: [],
|
||||
speed: 30,
|
||||
pause_on_hover: true,
|
||||
columns: 5,
|
||||
},
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{ key: 'speed', type: 'number', label: '滚动速度', min: 10, max: 100 },
|
||||
{ key: 'pause_on_hover', type: 'switch', label: '鼠标悬停暂停' },
|
||||
{ key: 'columns', type: 'number', label: '可见列数', min: 3, max: 8 },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'product_showcase',
|
||||
label: '产品展示',
|
||||
icon: 'shopping-bag',
|
||||
desc: '产品/商品卡片网格(为商城 Phase 预留)',
|
||||
defaultConfig: {
|
||||
title: '产品展示',
|
||||
columns: 3,
|
||||
count: 6,
|
||||
source: 'manual',
|
||||
show_price: true,
|
||||
show_button: true,
|
||||
},
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{ key: 'columns', type: 'number', label: '列数', min: 2, max: 4 },
|
||||
{ key: 'count', type: 'number', label: '数量', min: 1, max: 12 },
|
||||
{ key: 'source', type: 'select', label: '数据来源', options: [
|
||||
{ label: '手动配置', value: 'manual' },
|
||||
{ label: '最新商品', value: 'latest' },
|
||||
{ label: '热门商品', value: 'popular' },
|
||||
]},
|
||||
{ key: 'show_price', type: 'switch', label: '显示价格' },
|
||||
{ key: 'show_button', type: 'switch', label: '显示购买按钮' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'user',
|
||||
label: '用户卡片',
|
||||
icon: 'user',
|
||||
desc: '推荐用户卡片(头像、简介、关注按钮)',
|
||||
defaultConfig: {
|
||||
title: '推荐用户',
|
||||
user_ids: [],
|
||||
count: 4,
|
||||
columns: 4,
|
||||
order: 'recent',
|
||||
show_stats: true,
|
||||
},
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{ key: 'count', type: 'number', label: '用户数量', min: 1, max: 12 },
|
||||
{ key: 'columns', type: 'number', label: '列数', min: 2, max: 6 },
|
||||
{ key: 'order', type: 'select', label: '排序', options: [
|
||||
{ label: '最近注册', value: 'recent' },
|
||||
{ label: '文章最多', value: 'posts' },
|
||||
{ label: '手动指定', value: 'manual' },
|
||||
]},
|
||||
{ key: 'show_stats', type: 'switch', label: '显示统计数据' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'more',
|
||||
label: '发现更多',
|
||||
icon: 'th',
|
||||
desc: '分类卡片网格(封面、文章数、最近文章)',
|
||||
defaultConfig: {
|
||||
title: '发现更多',
|
||||
cat_ids: [],
|
||||
count: 6,
|
||||
columns: 3,
|
||||
show_post_count: true,
|
||||
show_latest_posts: true,
|
||||
latest_count: 5,
|
||||
},
|
||||
fields: [
|
||||
{ key: 'title', type: 'text', label: '标题' },
|
||||
{ key: 'count', type: 'number', label: '分类数量', min: 1, max: 12 },
|
||||
{ key: 'columns', type: 'number', label: '列数', min: 2, max: 4 },
|
||||
{ key: 'show_post_count', type: 'switch', label: '显示文章数' },
|
||||
{ key: 'show_latest_posts', type: 'switch', label: '显示最近文章' },
|
||||
{ key: 'latest_count', type: 'number', label: '最近文章数', min: 1, max: 10 },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export function getModuleMeta(type) {
|
||||
return MODULE_LIBRARY.find(m => m.type === type) || null
|
||||
}
|
||||
|
||||
export function makeDefaultModule(type) {
|
||||
const meta = getModuleMeta(type)
|
||||
if (!meta) return null
|
||||
return {
|
||||
type,
|
||||
config: JSON.parse(JSON.stringify(meta.defaultConfig || {})),
|
||||
}
|
||||
}
|
||||
|
||||
export const PRESET_TEMPLATES = [
|
||||
{
|
||||
key: 'resource_standard',
|
||||
label: '资源站标准',
|
||||
desc: 'Tabs + 最新文章 + 下载排行;头部幻灯由导航系统独立渲染',
|
||||
modules: [
|
||||
{ type: 'tabs', config: { count: 8, layout: 'card' } },
|
||||
{ type: 'recent', config: { count: 8, layout: 'card' } },
|
||||
{ type: 'ranking', config: { metric: 'downloads', count: 6 } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'news',
|
||||
label: '资讯站',
|
||||
desc: 'Banner(固定骨架)+ 多个分类块 + 标签云',
|
||||
modules: [
|
||||
{ type: 'banner', config: {} },
|
||||
{ type: 'category', config: { count: 8, columns: 4, layout: 'card' } },
|
||||
{ type: 'category', config: { count: 8, columns: 4, layout: 'card' } },
|
||||
{ type: 'tag_cloud', config: { limit: 30 } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'mixed',
|
||||
label: '混合站',
|
||||
desc: '最新文章 + 多个分类块;头部幻灯由导航系统独立渲染',
|
||||
modules: [
|
||||
{ type: 'recent', config: { count: 6, layout: 'card' } },
|
||||
{ type: 'category', config: { count: 6, columns: 3, layout: 'card' } },
|
||||
{ type: 'category', config: { count: 6, columns: 3, layout: 'card' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'landing',
|
||||
label: '落地页',
|
||||
desc: '标题 + 高亮卡片 + FAQ + 按钮组',
|
||||
modules: [
|
||||
{ type: 'text_title', config: { title: '为什么选择我们', align: 'center', style: 'default' } },
|
||||
{ type: 'highlight_cards', config: { columns: 3 } },
|
||||
{ type: 'spacing', config: { height: 40 } },
|
||||
{ type: 'faq', config: { title: '常见问题' } },
|
||||
{ type: 'button_group', config: { columns: 3, style: 'card' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'corporate',
|
||||
label: '企业站',
|
||||
desc: '数据网格 + 产品展示 + 合作伙伴',
|
||||
modules: [
|
||||
{ type: 'data_grid', config: { title: '核心数据', columns: 4, style: 'number' } },
|
||||
{ type: 'product_showcase', config: { title: '产品展示', columns: 3 } },
|
||||
{ type: 'partner_marquee', config: { title: '合作伙伴', speed: 30 } },
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -11,14 +11,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* CMS freeform: do NOT pull the whole container — nav pull is only on .slide-header
|
||||
* (zibll body.nav-fixed .slide-header). Whole-container pull made homepage cards
|
||||
* differ from other page types that share the same component. */
|
||||
.home-modules > .header-components-container:first-child,
|
||||
.home-modules--fullbleed > .header-components-container:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* Fixed homepage hero under-nav is owned by Zibll main.css. */
|
||||
|
||||
/* ============================================================
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
@import url("home-modules/base.css");
|
||||
@import url("home-modules/tabs.css");
|
||||
@import url("home-modules/tag-cloud.css");
|
||||
@import url("home-modules/ranking.css");
|
||||
@import url("home-modules/friend-links.css");
|
||||
@import url("home-modules/html-module.css");
|
||||
@import url("home-modules/responsive.css");
|
||||
@import url("home-modules/text-title.css");
|
||||
@import url("home-modules/button-group.css");
|
||||
@import url("home-modules/spacing.css");
|
||||
@import url("home-modules/faq.css");
|
||||
@import url("home-modules/data-grid.css");
|
||||
@import url("home-modules/highlight-cards.css");
|
||||
@import url("home-modules/partner-marquee.css");
|
||||
@import url("home-modules/product-showcase.css");
|
||||
@import url("home-modules/user-cards.css");
|
||||
@@ -1,89 +0,0 @@
|
||||
/**
|
||||
* Home Modules · CMS 形态首页模块系统样式
|
||||
*
|
||||
* 仅在 site_profile=cms 时(首页)加载。
|
||||
*
|
||||
* 宽度模型:
|
||||
* - .home-modules > .home-module 锁定到 --container(默认 1280)
|
||||
* - .home-modules > .hero 走 full-bleed(不在此处约束)
|
||||
* - banner 模块的精细微调见 hero.css `.banner-slider.home-modules__banner`
|
||||
*/
|
||||
|
||||
.home-modules {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-8, 40px);
|
||||
padding-bottom: var(--space-8, 40px);
|
||||
}
|
||||
|
||||
.home-modules--fullbleed {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.home-modules--fullbleed > .home-module {
|
||||
max-width: 100%;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
/* 容器宽度统一:所有非 hero 的 home-module 都对齐到 --container */
|
||||
.home-modules > .home-module {
|
||||
width: 100%;
|
||||
max-width: var(--container);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.home-modules > .home-module {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============ 公共 header ============ */
|
||||
.home-module__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3, 12px);
|
||||
margin-bottom: var(--space-5, 20px);
|
||||
padding-bottom: var(--space-3, 12px);
|
||||
border-bottom: 1px solid var(--color-line-soft);
|
||||
}
|
||||
|
||||
.home-module__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font: 700 22px/1.2 var(--font-display);
|
||||
margin: 0;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.home-module__title i {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.home-module__more {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font: 500 13px/1 var(--font-ui);
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.home-module__more:hover {
|
||||
color: var(--color-primary-active);
|
||||
}
|
||||
|
||||
.home-module__admin-tools {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/* Button Group */
|
||||
.home-module--button-group {
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
.home-module__buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.aksrt-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 24px;
|
||||
border-radius: var(--radius-btn, 4px);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
transition: opacity 0.2s, transform 0.2s, box-shadow 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-btn:hover {
|
||||
opacity: 1;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.aksrt-btn--primary {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
box-shadow: 0 8px 18px rgba(240, 68, 148, 0.22);
|
||||
}
|
||||
|
||||
.aksrt-btn--outline {
|
||||
background: var(--surface-1);
|
||||
color: var(--color-primary);
|
||||
border: 1px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.aksrt-btn--ghost {
|
||||
background: var(--surface-4);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/* Data Grid */
|
||||
.home-module--data-grid {
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
.home-module__data-grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.aksrt-data-cell {
|
||||
padding: 24px 16px;
|
||||
background: var(--surface-1, #fff);
|
||||
border-radius: var(--main-radius, var(--radius, 8px));
|
||||
border: none;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.aksrt-data-cell:hover {
|
||||
box-shadow: var(--shadow);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.aksrt-data-cell i {
|
||||
font-size: 24px;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aksrt-data-cell__value {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: var(--color-ink);
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.aksrt-data-cell__label {
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/* FAQ */
|
||||
.home-module--faq {
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
.home-module__faq-grid {
|
||||
column-gap: 24px;
|
||||
}
|
||||
|
||||
.aksrt-faq-item {
|
||||
break-inside: avoid;
|
||||
margin-bottom: 12px;
|
||||
border: none;
|
||||
border-radius: var(--main-radius, var(--radius, 8px));
|
||||
box-shadow: var(--shadow-sm);
|
||||
overflow: hidden;
|
||||
background: var(--surface-1, #fff);
|
||||
}
|
||||
|
||||
.aksrt-faq-item__question {
|
||||
padding: 14px 16px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
background: var(--surface-1, #fff);
|
||||
list-style: none;
|
||||
transition: color .18s ease, background .18s ease;
|
||||
}
|
||||
|
||||
.aksrt-faq-item__question:hover {
|
||||
color: var(--color-primary);
|
||||
background: var(--surface-4);
|
||||
}
|
||||
|
||||
.aksrt-faq-item__question::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aksrt-faq-item__question::after {
|
||||
content: '+';
|
||||
float: right;
|
||||
font-weight: 400;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-faq-item[open] .aksrt-faq-item__question::after {
|
||||
content: '−';
|
||||
}
|
||||
|
||||
.aksrt-faq-item__answer {
|
||||
padding: 0 16px 14px;
|
||||
font-size: 14px;
|
||||
color: var(--color-ink-3);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/* ============ Friend Links ============ */
|
||||
.home-module__friends-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: var(--space-3, 12px);
|
||||
}
|
||||
|
||||
.home-module__friend-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3, 12px);
|
||||
padding: 12px;
|
||||
border-radius: var(--main-radius, var(--radius, 8px));
|
||||
border: none;
|
||||
box-shadow: var(--shadow-sm);
|
||||
background: var(--surface-1);
|
||||
color: var(--color-ink);
|
||||
text-decoration: none;
|
||||
transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease;
|
||||
}
|
||||
|
||||
.home-module__friend-card:hover {
|
||||
box-shadow: var(--shadow);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.home-module__friend-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
flex: 0 0 40px;
|
||||
}
|
||||
|
||||
.home-module__friend-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.home-module__friend-meta strong {
|
||||
font: 600 14px/1.2 var(--font-ui);
|
||||
}
|
||||
|
||||
.home-module__friend-meta span {
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-3);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/* Highlight Cards */
|
||||
.home-module--highlight-cards {
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
.home-module__highlight-grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.aksrt-highlight-card {
|
||||
padding: 24px;
|
||||
background: var(--surface-1, #fff);
|
||||
border-radius: var(--main-radius, var(--radius, 8px));
|
||||
border: none;
|
||||
box-shadow: var(--shadow-sm);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.aksrt-highlight-card:hover {
|
||||
box-shadow: var(--shadow);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.aksrt-highlight-card__image {
|
||||
width: 100%;
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.aksrt-highlight-card__icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-md, 8px);
|
||||
background: var(--color-primary-softer);
|
||||
color: var(--color-primary);
|
||||
font-size: 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.aksrt-highlight-card__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.aksrt-highlight-card__desc {
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-3);
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
/* ============ HTML Module ============ */
|
||||
.home-module--html .home-module__html-body {
|
||||
color: var(--color-ink);
|
||||
font-size: 15px;
|
||||
line-height: 1.75;
|
||||
padding: 20px 22px;
|
||||
background: var(--surface-1);
|
||||
border: none;
|
||||
border-radius: var(--radius-2xl, 16px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/* Partner Marquee */
|
||||
.home-module--partner-marquee {
|
||||
padding: 32px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.aksrt-marquee {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aksrt-marquee__track {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
width: max-content;
|
||||
animation: aksrt-marquee-scroll var(--marquee-duration, 30s) linear infinite;
|
||||
}
|
||||
|
||||
.aksrt-marquee__item {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 18px;
|
||||
border-radius: var(--main-radius, var(--radius, 8px));
|
||||
background: var(--surface-1);
|
||||
border: none;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.aksrt-marquee__item img {
|
||||
height: 40px;
|
||||
width: auto;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-marquee__item:hover img {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes aksrt-marquee-scroll {
|
||||
0% { transform: translateX(0); }
|
||||
100% { transform: translateX(-50%); }
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/* Product Showcase */
|
||||
.home-module--product-showcase {
|
||||
padding: 32px 0;
|
||||
}
|
||||
|
||||
.home-module__product-grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.aksrt-product-card {
|
||||
background: var(--surface-1, #fff);
|
||||
border-radius: var(--radius, 8px);
|
||||
border: none;
|
||||
box-shadow: var(--shadow-sm);
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.aksrt-product-card:hover {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.aksrt-product-card__image img {
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius, 8px) var(--radius, 8px) 0 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.aksrt-product-card__body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.aksrt-product-card__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
|
||||
.aksrt-product-card__desc {
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-3);
|
||||
margin: 0 0 8px;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.aksrt-product-card__price {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/* ============ Ranking ============ */
|
||||
.home-module__ranking-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: var(--space-3, 12px);
|
||||
}
|
||||
|
||||
.home-module__ranking-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3, 12px);
|
||||
padding: 12px 14px;
|
||||
background: var(--surface-1);
|
||||
border: none;
|
||||
border-radius: var(--main-radius, var(--radius, 8px));
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: box-shadow .18s ease, border-color .18s ease, transform .18s ease;
|
||||
}
|
||||
|
||||
.home-module__ranking-item:hover {
|
||||
box-shadow: var(--shadow);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.home-module__ranking-num {
|
||||
flex: 0 0 32px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: var(--surface-2);
|
||||
color: var(--color-ink-3);
|
||||
font: 700 13px/1 var(--font-ui);
|
||||
}
|
||||
|
||||
.home-module__ranking-item--top .home-module__ranking-num {
|
||||
background: linear-gradient(135deg, #f59e0b 0%, #f97316 100%);
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 8px rgba(245, 158, 11, .25);
|
||||
}
|
||||
|
||||
.home-module__ranking-link {
|
||||
flex: 1 1 auto;
|
||||
color: var(--color-ink);
|
||||
text-decoration: none;
|
||||
font: 500 14px/1.4 var(--font-ui);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.home-module__ranking-link:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.home-module__ranking-value {
|
||||
flex: 0 0 auto;
|
||||
color: var(--color-ink-3);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font: 500 12px/1 var(--font-ui);
|
||||
}
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
/* ============ Responsive ============ */
|
||||
@media (max-width: 640px) {
|
||||
.home-modules {
|
||||
gap: var(--space-7, 32px);
|
||||
}
|
||||
.home-module__header {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.home-module__title {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============ Hotspot 热点推荐 ============ */
|
||||
|
||||
/* 手机端隐藏 */
|
||||
.hotspot--desktop-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.hotspot--desktop-only {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.hotspot__list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hotspot__item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-4, 16px);
|
||||
padding: var(--space-4, 16px) 0;
|
||||
border-bottom: 1px solid var(--color-line);
|
||||
}
|
||||
|
||||
.hotspot__item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.hotspot__num {
|
||||
flex: 0 0 32px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: var(--radius-avatar, 50%);
|
||||
font: 700 13px/1 var(--font-ui);
|
||||
background: var(--surface-2);
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
.hotspot__num--top {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-on-primary, #fff);
|
||||
}
|
||||
|
||||
.hotspot__body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hotspot__title {
|
||||
display: block;
|
||||
font: 600 15px/1.4 var(--font-ui);
|
||||
color: var(--color-ink);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: color 0.18s ease;
|
||||
}
|
||||
|
||||
.hotspot__title:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.hotspot__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3, 12px);
|
||||
margin-top: var(--space-2, 8px);
|
||||
font: 400 12px/1 var(--font-ui);
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
.hotspot__cat {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--radius-pill, 999px);
|
||||
background: var(--surface-2);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ============ 分类推荐卡片 ============ */
|
||||
|
||||
.cat-cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.cat-card {
|
||||
background: var(--surface-2, #fff);
|
||||
border-radius: 10px;
|
||||
padding: 22px;
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08);
|
||||
transition: box-shadow 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.cat-card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
/* 上部:封面 + 信息 */
|
||||
.cat-card__top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 18px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.cat-card__banner {
|
||||
width: 145px;
|
||||
height: 95px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
background: var(--surface-3, #f5f5f5);
|
||||
}
|
||||
|
||||
.cat-card__cover {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.cat-card:hover .cat-card__cover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
/* 浏览量胶囊:红底白字 */
|
||||
.cat-card__views {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
background: #f54343;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
padding: 2px 7px;
|
||||
border-radius: 15px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.cat-card__info {
|
||||
flex: 1;
|
||||
height: 95px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.cat-card__name {
|
||||
font-size: 19px;
|
||||
font-weight: 600;
|
||||
color: var(--color-ink);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.cat-card__desc {
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-3);
|
||||
margin-top: 4px;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cat-card__foot {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-4);
|
||||
}
|
||||
|
||||
.cat-card__more {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cat-card__more:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 下部:文章列表 */
|
||||
.cat-card__list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.cat-card__list li {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
font-size: 13.5px;
|
||||
color: var(--color-ink);
|
||||
line-height: 1.6;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.cat-card__list li::before {
|
||||
content: "\25CF";
|
||||
color: var(--color-primary);
|
||||
font-size: 10px;
|
||||
margin-right: 8px;
|
||||
margin-top: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cat-card__list li a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.cat-card__list li a:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.cat-card__time {
|
||||
color: var(--color-ink-4);
|
||||
font-size: 12px;
|
||||
margin-left: auto;
|
||||
padding-left: 12px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 767px) {
|
||||
.cat-cards-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.cat-card__name {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.cat-card__info {
|
||||
height: auto;
|
||||
min-height: 95px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Phase 5: 运营型模块样式
|
||||
* ============================================================ */
|
||||
@@ -1,5 +0,0 @@
|
||||
/* Spacing */
|
||||
.home-module--spacing {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/* ============ Tabs ============ */
|
||||
.home-module__header--tabs {
|
||||
flex-wrap: wrap;
|
||||
border-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.home-module__tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2, 8px);
|
||||
border-bottom: 1px solid var(--color-line-soft);
|
||||
margin-top: var(--space-3, 12px);
|
||||
padding-bottom: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.home-module__tab {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 9px 18px;
|
||||
font: 500 14px/1 var(--font-ui);
|
||||
color: var(--color-ink-3);
|
||||
cursor: pointer;
|
||||
border-radius: 999px;
|
||||
border-bottom: 0;
|
||||
transition: color .18s ease, border-color .18s ease, background .18s ease;
|
||||
}
|
||||
|
||||
.home-module__tab:hover {
|
||||
color: var(--color-ink);
|
||||
background: var(--surface-4);
|
||||
}
|
||||
|
||||
.home-module__tab.is-active {
|
||||
color: var(--color-primary);
|
||||
background: var(--color-primary-soft);
|
||||
box-shadow: inset 0 0 0 1px rgba(240, 68, 148, 0.08);
|
||||
}
|
||||
|
||||
.home-module__tab-panel {
|
||||
margin-top: var(--space-5, 20px);
|
||||
}
|
||||
|
||||
.index-tab-main .home-module__tab-panel.tab-pane[hidden],
|
||||
.home-tab-content .home-module__tab-panel.tab-pane[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.home-module__tab-panel[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.home-module__empty {
|
||||
color: var(--color-ink-3);
|
||||
text-align: center;
|
||||
padding: var(--space-7, 32px) 0;
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/* ============ Tag Cloud ============ */
|
||||
.home-module__tag-cloud {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2, 8px);
|
||||
}
|
||||
|
||||
.home-module__tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--surface-4);
|
||||
color: var(--color-ink-2);
|
||||
font: 500 13px/1 var(--font-ui);
|
||||
text-decoration: none;
|
||||
transition: background .18s ease, color .18s ease, box-shadow .18s ease;
|
||||
}
|
||||
|
||||
.home-module__tag:hover {
|
||||
background: var(--color-primary-soft);
|
||||
color: var(--color-primary);
|
||||
box-shadow: inset 0 0 0 1px rgba(240, 68, 148, 0.08);
|
||||
}
|
||||
|
||||
.home-module__tag-count {
|
||||
font-variant-numeric: tabular-nums;
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/* Text Title */
|
||||
.home-module--text-title {
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.home-module--text-title .home-module__title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.home-module--text-title .home-module__subtitle {
|
||||
font-size: 15px;
|
||||
color: var(--color-ink-3);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
/* ============ User Cards ============ */
|
||||
.home-module__users-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--cols, 4), 1fr);
|
||||
gap: var(--space-4, 16px);
|
||||
}
|
||||
|
||||
.home-module__user-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 20px 15px;
|
||||
border-radius: var(--radius, 8px);
|
||||
border: none;
|
||||
background: var(--surface-1);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.home-module__user-card:hover {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.home-module__user-link {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.home-module__user-avatar {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.home-module__user-name {
|
||||
font: 600 14px/1.2 var(--font-ui);
|
||||
}
|
||||
|
||||
.home-module__user-bio {
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-3);
|
||||
margin: 4px 0 0;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.home-module__user-stats {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
|
||||
.home-module__user-stat {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.home-module__user-stat i {
|
||||
font-size: 11px;
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
/* ============ More (Discover Categories) ============ */
|
||||
.home-module__more-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--cols, 3), 1fr);
|
||||
gap: var(--space-4, 16px);
|
||||
}
|
||||
|
||||
.home-module__more-card {
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--color-line);
|
||||
background: var(--surface-1);
|
||||
overflow: hidden;
|
||||
transition: border-color .18s ease, transform .18s ease;
|
||||
}
|
||||
|
||||
.home-module__more-card:hover {
|
||||
border-color: var(--color-line);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.home-module__more-link {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.home-module__more-cover {
|
||||
aspect-ratio: 16 / 9;
|
||||
overflow: hidden;
|
||||
background: var(--color-fill-2);
|
||||
}
|
||||
|
||||
.home-module__more-cover img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.home-module__more-cover--placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
.home-module__more-body {
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.home-module__more-name {
|
||||
font: 600 14px/1.3 var(--font-ui);
|
||||
}
|
||||
|
||||
.home-module__more-count {
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
.home-module__more-posts {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 12px 12px;
|
||||
}
|
||||
|
||||
.home-module__more-posts li {
|
||||
border-top: 1px solid var(--color-line-light, rgba(0,0,0,.06));
|
||||
}
|
||||
|
||||
.home-module__more-posts a {
|
||||
display: block;
|
||||
padding: 6px 0;
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-2);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: color .15s ease;
|
||||
}
|
||||
|
||||
.home-module__more-posts a:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 767px) {
|
||||
.home-module__data-grid,
|
||||
.home-module__highlight-grid,
|
||||
.home-module__product-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
|
||||
.home-module__faq-grid {
|
||||
column-count: 1 !important;
|
||||
}
|
||||
|
||||
.home-module__users-grid,
|
||||
.home-module__more-grid {
|
||||
grid-template-columns: 1fr 1fr !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,22 +127,10 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.aksrt-homepage__content-zone--operated.home-modules,
|
||||
.aksrt-homepage__content-zone--mid.home-modules,
|
||||
.aksrt-homepage__content-zone--supplement.home-modules {
|
||||
gap: 24px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.aksrt-homepage__content-zone--supplement {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.aksrt-homepage__content-zone--featured.home-modules {
|
||||
gap: 24px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.aksrt-homepage__content-zone .widget-card:last-child,
|
||||
.aksrt-homepage__aside .widget:last-child,
|
||||
.aksrt-homepage__fluid-zone .widget-fluid:last-child {
|
||||
|
||||
@@ -593,38 +593,10 @@ body.site-layout-3 .sidebar,
|
||||
}
|
||||
}
|
||||
|
||||
/* CMS 模块在侧栏布局中的适配 */
|
||||
.content-layout__primary--cms {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.content-layout__primary--cms .home-modules {
|
||||
gap: var(--space-7, 32px);
|
||||
}
|
||||
|
||||
.content-layout__primary--cms .home-modules > .home-module {
|
||||
max-width: 100%;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Phase 2 — 背景遮罩层级修正
|
||||
* body.aksrt-bg-overlay 的 ::before 需要 z-index: -1 让内容在其上方
|
||||
* ============================================================ */
|
||||
body.aksrt-bg-overlay {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body.aksrt-bg-overlay::before {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
body.aksrt-bg-overlay .site-wrapper {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Phase 2 — 侧栏 widget 内链接增强
|
||||
* ============================================================ */
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* AKSRT Theme Background System
|
||||
*
|
||||
* 全局背景图、夜间背景图、页面背景色/背景图。
|
||||
* 所有选择器均带 aksrt- 命名空间。
|
||||
*/
|
||||
|
||||
/* ========== 全局背景 ========== */
|
||||
.aksrt-bg-body {
|
||||
background-color: var(--aksrt-bg-color, transparent);
|
||||
background-image: var(--aksrt-bg-image, none);
|
||||
background-size: var(--aksrt-bg-size, cover);
|
||||
background-position: var(--aksrt-bg-position, center center);
|
||||
background-repeat: var(--aksrt-bg-repeat, no-repeat);
|
||||
background-attachment: var(--aksrt-bg-attachment, fixed);
|
||||
}
|
||||
|
||||
/* ========== 夜间模式背景 ========== */
|
||||
.aksrt-bg-dark {
|
||||
background-color: var(--aksrt-bg-dark-color, transparent);
|
||||
background-image: var(--aksrt-bg-dark-image, none);
|
||||
}
|
||||
|
||||
/* ========== 页面级背景 ========== */
|
||||
.aksrt-bg-page {
|
||||
background-color: var(--aksrt-page-bg-color, transparent);
|
||||
background-image: var(--aksrt-page-bg-image, none);
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* ========== 模块级背景 ========== */
|
||||
.aksrt-bg-module {
|
||||
background-color: var(--aksrt-module-bg-color, transparent);
|
||||
background-image: var(--aksrt-module-bg-image, none);
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* ========== 背景遮罩层(提升文字可读性)========== */
|
||||
.aksrt-bg-overlay {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.aksrt-bg-overlay::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--aksrt-bg-overlay-color, rgba(0, 0, 0, 0.3));
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.aksrt-bg-overlay > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* ========== 暗色模式自动切换 ========== */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not(.aksrt-light-mode) .aksrt-bg-body {
|
||||
background-color: var(--aksrt-bg-dark-color, var(--aksrt-bg-color, transparent));
|
||||
background-image: var(--aksrt-bg-dark-image, var(--aksrt-bg-image, none));
|
||||
}
|
||||
}
|
||||
|
||||
html.aksrt-dark-mode .aksrt-bg-body {
|
||||
background-color: var(--aksrt-bg-dark-color, var(--aksrt-bg-color, transparent));
|
||||
background-image: var(--aksrt-bg-dark-image, var(--aksrt-bg-image, none));
|
||||
}
|
||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -1,52 +0,0 @@
|
||||
/**
|
||||
* AKSRT CMS Home Modules
|
||||
*
|
||||
* - Tab 切换:[data-aksrt-tabs] / [data-tab-target] / [data-tab-id]
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function initTabs(scope) {
|
||||
const tablists = (scope || document).querySelectorAll('[data-aksrt-tabs]');
|
||||
tablists.forEach(function (list) {
|
||||
const buttons = list.querySelectorAll('.home-module__tab');
|
||||
const section = list.closest('.home-module--tabs');
|
||||
if (!section) return;
|
||||
const panels = section.querySelectorAll('.home-module__tab-panel');
|
||||
|
||||
buttons.forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
const target = btn.getAttribute('data-tab-target');
|
||||
if (!target) return;
|
||||
|
||||
buttons.forEach(function (b) {
|
||||
b.classList.toggle('is-active', b === btn);
|
||||
b.setAttribute('aria-selected', b === btn ? 'true' : 'false');
|
||||
});
|
||||
panels.forEach(function (p) {
|
||||
const isMatch = p.getAttribute('data-tab-id') === target;
|
||||
p.classList.toggle('is-active', isMatch);
|
||||
if (isMatch) {
|
||||
p.removeAttribute('hidden');
|
||||
} else {
|
||||
p.setAttribute('hidden', '');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 监听 postMessage:iframe 预览模式下接收 reload 请求
|
||||
window.addEventListener('message', function (e) {
|
||||
if (!e.data || e.data.type !== 'aksrt-preview-reload') return;
|
||||
// 简单做:直接 reload
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function () { initTabs(); });
|
||||
} else {
|
||||
initTabs();
|
||||
}
|
||||
})();
|
||||
@@ -21,9 +21,8 @@
|
||||
var state = {
|
||||
filter_cat: "",
|
||||
filter_tag: "",
|
||||
sort: "latest",
|
||||
page: 1,
|
||||
cf: {}
|
||||
sort: "date",
|
||||
page: 1
|
||||
};
|
||||
|
||||
var activeCat = bar.querySelector("[data-filter-cat].is-active");
|
||||
@@ -31,12 +30,7 @@
|
||||
var activeTag = bar.querySelector("[data-filter-tag].is-active");
|
||||
if (activeTag) state.filter_tag = activeTag.dataset.filterTag || "";
|
||||
var activeSort = bar.querySelector("[data-filter-sort].is-active");
|
||||
if (activeSort) state.sort = activeSort.dataset.filterSort || "latest";
|
||||
bar.querySelectorAll("[data-filter-cf].is-active").forEach(function (el) {
|
||||
var dimKey = el.dataset.filterCf || "";
|
||||
var dimVal = el.dataset.filterCfVal || "";
|
||||
if (dimKey) state.cf[dimKey] = dimVal;
|
||||
});
|
||||
if (activeSort) state.sort = activeSort.dataset.filterSort || "date";
|
||||
|
||||
function buildUrl() {
|
||||
var params = new URLSearchParams();
|
||||
@@ -45,20 +39,8 @@
|
||||
if (state.filter_cat) params.set("filter_cat", state.filter_cat);
|
||||
if (state.filter_tag) params.set("filter_tag", state.filter_tag);
|
||||
|
||||
var sortMap = { oldest: { orderby: "date", order: "ASC" }, popular: { orderby: "comment_count", order: "DESC" } };
|
||||
var s = sortMap[state.sort];
|
||||
params.set("sort", s ? s.orderby : "date");
|
||||
params.set("order", s ? s.order : "DESC");
|
||||
params.set("sort", state.sort || "date");
|
||||
params.set("page", String(state.page));
|
||||
var cfActive = {};
|
||||
var hasCf = false;
|
||||
for (var cfK in state.cf) {
|
||||
if (state.cf[cfK]) {
|
||||
cfActive[cfK] = state.cf[cfK];
|
||||
hasCf = true;
|
||||
}
|
||||
}
|
||||
if (hasCf) params.set("custom_filters", JSON.stringify(cfActive));
|
||||
return REST_BASE + "?" + params.toString();
|
||||
}
|
||||
|
||||
@@ -70,13 +52,7 @@
|
||||
el.classList.toggle("is-active", (el.dataset.filterTag || "") === state.filter_tag);
|
||||
});
|
||||
bar.querySelectorAll("[data-filter-sort]").forEach(function (el) {
|
||||
el.classList.toggle("is-active", (el.dataset.filterSort || "latest") === state.sort);
|
||||
});
|
||||
bar.querySelectorAll("[data-filter-cf]").forEach(function (el) {
|
||||
var dimKey = el.dataset.filterCf || "";
|
||||
var dimVal = el.dataset.filterCfVal || "";
|
||||
var isActive = dimKey && (state.cf[dimKey] || "") === dimVal;
|
||||
el.classList.toggle("is-active", isActive);
|
||||
el.classList.toggle("is-active", (el.dataset.filterSort || "date") === state.sort);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,21 +107,9 @@
|
||||
url.searchParams.delete("paged");
|
||||
url.searchParams.delete("aksrt_filter_cat");
|
||||
url.searchParams.delete("aksrt_filter_tag");
|
||||
url.searchParams.forEach(function (v, k) {
|
||||
if (k.indexOf("aksrt_cf_") === 0) url.searchParams.delete(k);
|
||||
});
|
||||
if (state.filter_cat) url.searchParams.set("aksrt_filter_cat", state.filter_cat);
|
||||
if (state.filter_tag) url.searchParams.set("aksrt_filter_tag", state.filter_tag);
|
||||
for (var cfK2 in state.cf) {
|
||||
if (state.cf[cfK2]) url.searchParams.set("aksrt_cf_" + cfK2, state.cf[cfK2]);
|
||||
}
|
||||
if (state.sort === "oldest") {
|
||||
url.searchParams.set("orderby", "date");
|
||||
url.searchParams.set("order", "asc");
|
||||
} else if (state.sort === "popular") {
|
||||
url.searchParams.set("orderby", "comment_count");
|
||||
url.searchParams.set("order", "desc");
|
||||
}
|
||||
if (state.sort) url.searchParams.set("orderby", state.sort);
|
||||
if (state.page > 1) url.searchParams.set("paged", String(state.page));
|
||||
history.pushState({ aksrtArchive: true, state: Object.assign({}, state) }, "", url.toString());
|
||||
}
|
||||
@@ -156,7 +120,7 @@
|
||||
}
|
||||
|
||||
bar.addEventListener("click", function (e) {
|
||||
var chip = e.target.closest("[data-filter-cat], [data-filter-tag], [data-filter-sort], [data-filter-cf]");
|
||||
var chip = e.target.closest("[data-filter-cat], [data-filter-tag], [data-filter-sort]");
|
||||
if (!chip) return;
|
||||
e.preventDefault();
|
||||
|
||||
@@ -176,20 +140,12 @@
|
||||
}
|
||||
}
|
||||
if (chip.dataset.filterSort !== undefined) {
|
||||
var newSort = chip.dataset.filterSort || "latest";
|
||||
var newSort = chip.dataset.filterSort || "date";
|
||||
if (newSort !== state.sort) {
|
||||
state.sort = newSort;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (chip.dataset.filterCf !== undefined) {
|
||||
var cfKey = chip.dataset.filterCf;
|
||||
var cfVal = chip.dataset.filterCfVal || "";
|
||||
if ((state.cf[cfKey] || "") !== cfVal) {
|
||||
state.cf[cfKey] = cfVal;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
state.page = 1;
|
||||
|
||||
@@ -50,130 +50,28 @@ $has_sidebar = function_exists('aksrt_archive_has_sidebar')
|
||||
: (!empty($archive_settings['show_sidebar']) && is_active_sidebar('cat_sidebar'));
|
||||
$description = !empty($archive_settings['show_desc']) ? wp_kses_post((string) $archive_settings['description']) : '';
|
||||
$header_style = (string) $archive_settings['header_style'];
|
||||
$filter_settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_post_filter_settings()
|
||||
: ['enabled' => false, 'sort' => false, 'ajax_enabled' => false];
|
||||
if (!empty($filter_settings['enabled']) && !in_array('cat', $filter_settings['custom_filter_show'] ?? ['cat', 'tag'], true)) {
|
||||
$filter_settings['enabled'] = false;
|
||||
}
|
||||
$ajax_filter = !empty($filter_settings['ajax_enabled']) && !empty($filter_settings['enabled']);
|
||||
$cat_filter_config = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_filter_config_for_category((int) $term->term_id)
|
||||
: ['dimensions' => []];
|
||||
$custom_dimensions = is_array($cat_filter_config['dimensions'] ?? null) ? $cat_filter_config['dimensions'] : [];
|
||||
$sort_state = function_exists('aksrt_archive_get_sort_state') ? aksrt_archive_get_sort_state() : ['current' => 'latest', 'links' => []];
|
||||
$ajax_filter = !empty($archive_settings['filter_bar']);
|
||||
$sort_state = function_exists('aksrt_archive_get_sort_state')
|
||||
? aksrt_archive_get_sort_state((array) ($archive_settings['orderby_option'] ?? []))
|
||||
: ['current' => 'date', 'links' => []];
|
||||
$current_sort = $sort_state['current'];
|
||||
$sort_links = $sort_state['links'];
|
||||
$render_config = function_exists('aksrt_archive_get_render_config')
|
||||
? aksrt_archive_get_render_config(['context' => 'category'])
|
||||
: ['context' => 'category'];
|
||||
|
||||
$filter_terms = [];
|
||||
$filter_all_url = get_category_link($term->term_id);
|
||||
if (!empty($archive_settings['filter_bar'])) {
|
||||
$filter_terms = get_terms([
|
||||
'taxonomy' => 'category',
|
||||
'hide_empty' => true,
|
||||
'parent' => $term->term_id,
|
||||
]);
|
||||
if (empty($filter_terms) && !empty($term->parent)) {
|
||||
$filter_all_url = get_category_link((int) $term->parent);
|
||||
$filter_terms = get_terms([
|
||||
'taxonomy' => 'category',
|
||||
'hide_empty' => true,
|
||||
'parent' => (int) $term->parent,
|
||||
]);
|
||||
}
|
||||
if (is_wp_error($filter_terms)) {
|
||||
$filter_terms = [];
|
||||
}
|
||||
}
|
||||
|
||||
$tag_filter_terms = [];
|
||||
$current_filter_cat = sanitize_text_field((string) get_query_var('aksrt_filter_cat', ''));
|
||||
$current_filter_tag = sanitize_text_field((string) get_query_var('aksrt_filter_tag', ''));
|
||||
$current_cf_values = [];
|
||||
foreach ($custom_dimensions as $dim) {
|
||||
$cf_key = 'aksrt_cf_' . $dim['key'];
|
||||
$current_cf_values[$dim['key']] = isset($_GET[$cf_key]) ? sanitize_key((string) $_GET[$cf_key]) : '';
|
||||
}
|
||||
if ($ajax_filter && !empty($filter_settings['cat_show_tag'])) {
|
||||
$tag_filter_terms = get_terms([
|
||||
'taxonomy' => 'post_tag',
|
||||
'hide_empty' => true,
|
||||
'number' => 20,
|
||||
]);
|
||||
if (is_wp_error($tag_filter_terms)) {
|
||||
$tag_filter_terms = [];
|
||||
}
|
||||
}
|
||||
|
||||
$filter_groups = [];
|
||||
if (!empty($filter_terms)) {
|
||||
$chips = [[
|
||||
'label' => __('全部', 'aksrt'),
|
||||
'url' => $filter_all_url,
|
||||
'active' => empty($term->parent) && $current_filter_cat === '',
|
||||
'attrs' => $ajax_filter ? ' data-filter-cat="0"' : '',
|
||||
]];
|
||||
foreach ($filter_terms as $filter_term) {
|
||||
$is_current = ((int) $filter_term->term_id === (int) $term->term_id)
|
||||
|| ($current_filter_cat !== '' && (int) $current_filter_cat === (int) $filter_term->term_id);
|
||||
$chips[] = [
|
||||
'label' => $filter_term->name,
|
||||
'url' => get_term_link($filter_term),
|
||||
'active' => $is_current,
|
||||
'attrs' => $ajax_filter ? ' data-filter-cat="' . (int) $filter_term->term_id . '"' : '',
|
||||
];
|
||||
if (!empty($archive_settings['ajax_category_enabled']) && function_exists('aksrt_archive_build_term_filter_group')) {
|
||||
$group = aksrt_archive_build_term_filter_group('category', (array) ($archive_settings['ajax_category'] ?? []), __('分类', 'aksrt'), $ajax_filter);
|
||||
if ($group !== []) {
|
||||
$filter_groups[] = $group;
|
||||
}
|
||||
$filter_groups[] = [
|
||||
'label' => __('分类导航', 'aksrt'),
|
||||
'chips' => $chips,
|
||||
];
|
||||
}
|
||||
if (!empty($tag_filter_terms)) {
|
||||
$chips = [[
|
||||
'label' => __('全部', 'aksrt'),
|
||||
'url' => remove_query_arg(['aksrt_filter_tag']),
|
||||
'active' => $current_filter_tag === '',
|
||||
'attrs' => $ajax_filter ? ' data-filter-tag=""' : '',
|
||||
]];
|
||||
foreach ($tag_filter_terms as $tag_term) {
|
||||
$is_current_tag = ($current_filter_tag !== '' && (int) $current_filter_tag === (int) $tag_term->term_id);
|
||||
$chips[] = [
|
||||
'label' => $tag_term->name,
|
||||
'url' => add_query_arg('aksrt_filter_tag', (int) $tag_term->term_id),
|
||||
'active' => $is_current_tag,
|
||||
'attrs' => $ajax_filter ? ' data-filter-tag="' . (int) $tag_term->term_id . '"' : '',
|
||||
];
|
||||
if (!empty($archive_settings['ajax_tag_enabled']) && function_exists('aksrt_archive_build_term_filter_group')) {
|
||||
$group = aksrt_archive_build_term_filter_group('post_tag', (array) ($archive_settings['ajax_tag'] ?? []), __('标签', 'aksrt'), $ajax_filter);
|
||||
if ($group !== []) {
|
||||
$filter_groups[] = $group;
|
||||
}
|
||||
$filter_groups[] = [
|
||||
'label' => __('标签', 'aksrt'),
|
||||
'class' => 'archive-filter-bar__main--tags',
|
||||
'chips' => $chips,
|
||||
];
|
||||
}
|
||||
foreach ($custom_dimensions as $dim) {
|
||||
$chips = [[
|
||||
'label' => __('全部', 'aksrt'),
|
||||
'url' => remove_query_arg('aksrt_cf_' . $dim['key']),
|
||||
'active' => ($current_cf_values[$dim['key']] ?? '') === '',
|
||||
'attrs' => $ajax_filter ? ' data-filter-cf="' . esc_attr($dim['key']) . '" data-filter-cf-val=""' : '',
|
||||
]];
|
||||
foreach ($dim['vals'] as $val) {
|
||||
$is_current_cf = ($current_cf_values[$dim['key']] ?? '') === $val['key'];
|
||||
$chips[] = [
|
||||
'label' => $val['name'],
|
||||
'url' => add_query_arg('aksrt_cf_' . $dim['key'], $val['key']),
|
||||
'active' => $is_current_cf,
|
||||
'attrs' => $ajax_filter ? ' data-filter-cf="' . esc_attr($dim['key']) . '" data-filter-cf-val="' . esc_attr($val['key']) . '"' : '',
|
||||
];
|
||||
}
|
||||
$filter_groups[] = [
|
||||
'label' => $dim['name'],
|
||||
'class' => 'archive-filter-bar__main--custom',
|
||||
'chips' => $chips,
|
||||
];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_archive_open_shell')) {
|
||||
@@ -212,11 +110,12 @@ if (function_exists('aksrt_archive_open_shell')) {
|
||||
'ajax_archive' => 'category',
|
||||
'term_id' => (int) $term->term_id,
|
||||
'aria_label' => __('分类筛选', 'aksrt'),
|
||||
'meta_text' => sprintf(_n('%d 篇文章', '%d 篇文章', (int) $term->count, 'aksrt'), (int) $term->count),
|
||||
'meta_text' => !empty($archive_settings['post_count']) ? sprintf(_n('%d 篇文章', '%d 篇文章', (int) $term->count, 'aksrt'), (int) $term->count) : '',
|
||||
'groups' => $filter_groups,
|
||||
'sort_enabled' => !empty($filter_settings['enabled']) && !empty($filter_settings['sort']),
|
||||
'sort_enabled' => !empty($archive_settings['orderby_enabled']),
|
||||
'sort_current' => $current_sort,
|
||||
'sort_links' => $sort_links,
|
||||
'sort_labels' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::homepage_sort_labels() : [],
|
||||
'sort_as_main' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -13,34 +13,13 @@ defined('ABSPATH') || exit;
|
||||
// 数据准备
|
||||
$blog_name = function_exists('aksrt_get_site_logo_text') ? aksrt_get_site_logo_text() : get_bloginfo('name');
|
||||
$has_settings = class_exists('\AKSRT\Settings\Settings');
|
||||
$search_placeholder = function_exists('aksrt_get_search_placeholder') ? aksrt_get_search_placeholder() : __('开启精彩搜索', 'aksrt');
|
||||
$search_hot_keywords = function_exists('aksrt_get_search_hot_keywords') ? aksrt_get_search_hot_keywords() : [];
|
||||
|
||||
// 联系图标 / 社交链接(legacy contacts list — fallback for more icons)
|
||||
$footer_contacts = $has_settings ? \AKSRT\Settings\Settings::get_footer_contacts() : [];
|
||||
if (empty($footer_contacts) && function_exists('aksrt_get_about_info')) {
|
||||
$about = aksrt_get_about_info();
|
||||
$footer_contacts = $about['contacts'] ?? [];
|
||||
}
|
||||
|
||||
// 友情链接 / 快速链接(仅当 fcode_t2_code_1 为空时回退)
|
||||
$friend_links = function_exists('aksrt_get_friend_links') ? aksrt_get_friend_links() : [];
|
||||
$quick_links = $has_settings ? \AKSRT\Settings\Settings::get_quick_links() : [];
|
||||
$more_links = $has_settings ? \AKSRT\Settings\Settings::get_more_links() : [];
|
||||
$hot_cat_count = $has_settings ? \AKSRT\Settings\Settings::get_hot_categories_count() : 0;
|
||||
|
||||
// 备案 / 版权(customize 为空时回退)
|
||||
$filing = $has_settings ? \AKSRT\Settings\Settings::get_filing_settings() : ['icp_enabled' => true, 'icp_filing' => '', 'police_enabled' => true, 'police_filing' => ''];
|
||||
$icp_filing = !empty($filing['icp_enabled']) ? (string) ($filing['icp_filing'] ?? '') : '';
|
||||
$police_filing = !empty($filing['police_enabled']) ? (string) ($filing['police_filing'] ?? '') : '';
|
||||
$footer_text = $has_settings ? \AKSRT\Settings\Settings::footer_text() : '';
|
||||
$copyright_text = $has_settings ? \AKSRT\Settings\Settings::get_copyright_text() : '';
|
||||
|
||||
// §2.3 三列结构化字段(Zibll keys)
|
||||
$footer_brand = $has_settings ? \AKSRT\Settings\Settings::get_footer_brand_settings() : ['m_s' => ['pc', 'mobile'], 'img' => '', 'img_dark' => '', 'title' => '', 'custom_code' => ''];
|
||||
$footer_contact_ext = $has_settings ? \AKSRT\Settings\Settings::get_footer_contact_extended_settings() : ['m_s' => ['pc', 'mobile'], 'wechat_img' => '', 'qq' => '', 'weibo' => '', 'email' => '', 'more' => [], 'code_1' => '', 'code_2' => ''];
|
||||
$footer_mini = $has_settings ? \AKSRT\Settings\Settings::get_footer_mini_img_settings() : ['m_s' => ['pc', 'mobile'], 'items' => []];
|
||||
$footer_mini = $has_settings ? \AKSRT\Settings\Settings::get_footer_mini_img_settings() : ['m_s' => ['pc', 'mobile'], 'items' => [], 'items_2' => []];
|
||||
$footer_customize_code = $has_settings ? \AKSRT\Settings\Settings::get_footer_customize_code() : '';
|
||||
$footer_template = $has_settings ? \AKSRT\Settings\Settings::get_footer_template() : 'template_1';
|
||||
|
||||
// 设备可见性 → Zibll hidden-xs(仅在不含 mobile 时隐藏移动端)
|
||||
$_brand_show_sm = in_array('mobile', (array) ($footer_brand['m_s'] ?? []), true);
|
||||
@@ -50,67 +29,6 @@ $_brand_xs_cls = $_brand_show_sm ? '' : ' hidden-xs';
|
||||
$_contact_xs_cls = $_contact_show_sm ? '' : ' hidden-xs';
|
||||
$_mini_xs_cls = $_mini_show_sm ? '' : ' hidden-xs';
|
||||
|
||||
/**
|
||||
* 渲染快速链接 URL:支持外链 / 站内相对路径
|
||||
*/
|
||||
$render_link_url = static function ($raw) {
|
||||
if (empty($raw)) {
|
||||
return '#';
|
||||
}
|
||||
if (strpos((string) $raw, 'http') === 0) {
|
||||
return esc_url($raw);
|
||||
}
|
||||
return esc_url(home_url($raw));
|
||||
};
|
||||
|
||||
/**
|
||||
* 构建 fcode-links 回退 HTML(structured links)
|
||||
*/
|
||||
$build_fallback_links_html = static function () use ($quick_links, $more_links, $friend_links, $hot_cat_count, $render_link_url) {
|
||||
$link_bits = [];
|
||||
foreach ((array) $quick_links as $ql) {
|
||||
$label = $ql['name'] ?? $ql['label'] ?? '';
|
||||
$url = $render_link_url($ql['url'] ?? '');
|
||||
if ($label !== '') {
|
||||
$link_bits[] = '<a href="' . esc_url($url) . '">' . esc_html($label) . '</a>';
|
||||
}
|
||||
}
|
||||
foreach ((array) $more_links as $ml) {
|
||||
$label = $ml['name'] ?? $ml['label'] ?? '';
|
||||
$url = $render_link_url($ml['url'] ?? '');
|
||||
if ($label !== '') {
|
||||
$link_bits[] = '<a href="' . esc_url($url) . '">' . esc_html($label) . '</a>';
|
||||
}
|
||||
}
|
||||
foreach ((array) $friend_links as $fl) {
|
||||
$label = $fl['name'] ?? '';
|
||||
$url = $fl['url'] ?? '#';
|
||||
if ($label !== '') {
|
||||
$link_bits[] = '<a href="' . esc_url($url) . '" target="_blank" rel="noopener noreferrer nofollow">' . esc_html($label) . '</a>';
|
||||
}
|
||||
}
|
||||
if ($hot_cat_count > 0) {
|
||||
$_hot_cache_key = 'aksrt_footer_hot_cats_' . (int) $hot_cat_count;
|
||||
$_hot_cats = wp_cache_get($_hot_cache_key, 'aksrt');
|
||||
if (!is_array($_hot_cats)) {
|
||||
$_hot_cats = get_categories([
|
||||
'orderby' => 'count',
|
||||
'order' => 'DESC',
|
||||
'number' => $hot_cat_count,
|
||||
'hide_empty' => true,
|
||||
]);
|
||||
if (!is_array($_hot_cats)) {
|
||||
$_hot_cats = [];
|
||||
}
|
||||
wp_cache_set($_hot_cache_key, $_hot_cats, 'aksrt', 12 * HOUR_IN_SECONDS);
|
||||
}
|
||||
foreach ($_hot_cats as $hc) {
|
||||
$link_bits[] = '<a href="' . esc_url(get_category_link($hc->term_id)) . '">' . esc_html($hc->name) . '</a>';
|
||||
}
|
||||
}
|
||||
return implode(' ', $link_bits);
|
||||
};
|
||||
|
||||
// 品牌 / 联系 / 二维码列内容存在性
|
||||
$_logo_html = '';
|
||||
if (function_exists('aksrt_get_adaptive_theme_img')) {
|
||||
@@ -124,45 +42,17 @@ if (function_exists('aksrt_get_adaptive_theme_img')) {
|
||||
$_has_brand = $_logo_html !== '' || !empty($footer_brand['title']) || !empty($footer_brand['custom_code']);
|
||||
|
||||
$_fcode_links = trim((string) ($footer_contact_ext['code_1'] ?? ''));
|
||||
if ($_fcode_links === '') {
|
||||
$_fcode_links = $build_fallback_links_html();
|
||||
}
|
||||
$_fcode_links_is_raw = trim((string) ($footer_contact_ext['code_1'] ?? '')) !== '';
|
||||
|
||||
$_has_contact_icons = !empty($footer_contacts)
|
||||
|| !empty($footer_contact_ext['wechat_img'])
|
||||
$_has_contact_icons = !empty($footer_contact_ext['wechat_img'])
|
||||
|| !empty($footer_contact_ext['qq'])
|
||||
|| !empty($footer_contact_ext['email'])
|
||||
|| !empty($footer_contact_ext['more'])
|
||||
|| !empty($footer_contact_ext['weibo']);
|
||||
$_has_links_col = $_fcode_links !== '' || !empty($footer_contact_ext['code_2']) || $_has_contact_icons;
|
||||
$_has_mini = !empty($footer_mini['items']);
|
||||
$_has_mini = !empty($footer_mini['items']) || !empty($footer_mini['items_2']);
|
||||
$_has_cols = $_has_brand || $_has_links_col || $_has_mini;
|
||||
|
||||
// 底栏:customize 优先;否则回退版权/备案
|
||||
$_footer_conter = trim((string) $footer_customize_code);
|
||||
if ($_footer_conter === '') {
|
||||
$footer_bits = [];
|
||||
if ($icp_filing !== '') {
|
||||
$footer_bits[] = '<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer nofollow">' . esc_html($icp_filing) . '</a>';
|
||||
}
|
||||
if ($police_filing !== '') {
|
||||
$footer_bits[] = '<a href="https://beian.mps.gov.cn/" target="_blank" rel="noopener noreferrer nofollow">' . esc_html($police_filing) . '</a>';
|
||||
}
|
||||
$copy = '© ' . esc_html(gmdate('Y')) . ' ' . esc_html($blog_name) . '.';
|
||||
if ($copyright_text !== '') {
|
||||
$copy .= ' ' . wp_kses_post($copyright_text);
|
||||
}
|
||||
if ($footer_text !== '') {
|
||||
$copy .= ' ' . esc_html($footer_text);
|
||||
}
|
||||
if (!empty($footer_bits)) {
|
||||
$copy .= ' ' . implode(' · ', $footer_bits);
|
||||
}
|
||||
$_footer_conter = $copy;
|
||||
} else {
|
||||
$_footer_conter = wp_kses_post($_footer_conter);
|
||||
}
|
||||
$_footer_conter = wp_kses_post(trim((string) $footer_customize_code));
|
||||
?>
|
||||
|
||||
<?php if (is_active_sidebar('single_bottom_fluid') && is_singular('post')) : ?>
|
||||
@@ -173,7 +63,7 @@ if ($_footer_conter === '') {
|
||||
|
||||
<?php \AKSRT\WidgetSystem\WidgetAreas::render('all_bottom_fluid', 'fluid-widget-wrap'); ?>
|
||||
|
||||
<footer class="footer" data-aksrt-shell="site-footer" role="contentinfo">
|
||||
<footer class="footer" data-aksrt-shell="site-footer" data-footer-template="<?php echo esc_attr($footer_template); ?>" role="contentinfo">
|
||||
<?php if (is_active_sidebar('all_footer')) : ?>
|
||||
<?php dynamic_sidebar('all_footer'); ?>
|
||||
<?php endif; ?>
|
||||
@@ -202,13 +92,7 @@ if ($_footer_conter === '') {
|
||||
<?php if ($_has_links_col) : ?>
|
||||
<li style="max-width: 550px;">
|
||||
<?php if ($_fcode_links !== '') : ?>
|
||||
<p class="fcode-links"><?php
|
||||
if ($_fcode_links_is_raw) {
|
||||
echo wp_kses_post($_fcode_links);
|
||||
} else {
|
||||
echo $_fcode_links; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built with esc_* above
|
||||
}
|
||||
?></p>
|
||||
<p class="fcode-links"><?php echo wp_kses_post($_fcode_links); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($footer_contact_ext['code_2'])) : ?>
|
||||
@@ -245,7 +129,7 @@ if ($_footer_conter === '') {
|
||||
continue;
|
||||
}
|
||||
$m_img = (string) ($more_item['img'] ?? '');
|
||||
$m_url = (string) ($more_item['link'] ?? '');
|
||||
$m_url = (string) ($more_item['link'] ?? '');
|
||||
$m_text = (string) ($more_item['desc'] ?? '');
|
||||
$m_icon = (string) ($more_item['icon'] ?? '');
|
||||
$m_icon_html = ($m_icon !== '' && function_exists('aksrt_brand_icon'))
|
||||
@@ -268,19 +152,6 @@ if ($_footer_conter === '') {
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php foreach ((array) $footer_contacts as $contact) :
|
||||
$c_icon = $contact['icon'] ?? '';
|
||||
$c_url = $contact['url'] ?? '#';
|
||||
$c_label = $contact['title'] ?? ($contact['label'] ?? (function_exists('aksrt_icon_key_to_name') ? aksrt_icon_key_to_name($c_icon) : ''));
|
||||
$c_icon_html = function_exists('aksrt_brand_icon') ? aksrt_brand_icon((string) $c_icon) : '';
|
||||
if ($c_icon_html === '') {
|
||||
$c_icon_html = '<i class="fa fas fa-globe" aria-hidden="true"></i>';
|
||||
}
|
||||
?>
|
||||
<a class="toggle-radius" href="<?php echo esc_url($c_url); ?>" target="_blank" rel="noopener noreferrer" title="<?php echo esc_attr($c_label); ?>" aria-label="<?php echo esc_attr($c_label); ?>">
|
||||
<?php echo $c_icon_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
@@ -288,21 +159,31 @@ if ($_footer_conter === '') {
|
||||
|
||||
<?php if ($_has_mini) : ?>
|
||||
<li class="<?php echo esc_attr(trim($_mini_xs_cls)); ?>">
|
||||
<?php foreach ($footer_mini['items'] as $mi) :
|
||||
$mi_img = (string) ($mi['img'] ?? $mi['image'] ?? '');
|
||||
$mi_text = (string) ($mi['text'] ?? $mi['label'] ?? '');
|
||||
if ($mi_img === '') {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<div class="footer-miniimg"<?php echo $mi_text !== '' ? ' title="' . esc_attr($mi_text) . '"' : ''; ?>>
|
||||
<p>
|
||||
<img<?php echo aksrt_get_lazy_attr('lazy_other', (string) $mi_img); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="<?php echo esc_attr($mi_text !== '' ? $mi_text : $blog_name); ?>">
|
||||
</p>
|
||||
<?php if ($mi_text !== '') : ?>
|
||||
<span class="opacity8 em09"><?php echo esc_html($mi_text); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php foreach (['items' => 'footer-miniimg flex at hh gap6', 'items_2' => 'footer-miniimg-2 flex at hh gap6'] as $items_key => $items_class) : ?>
|
||||
<?php if (!empty($footer_mini[$items_key])) : ?>
|
||||
<div class="<?php echo esc_attr($items_class); ?>">
|
||||
<?php foreach ($footer_mini[$items_key] as $mi) :
|
||||
$mi_img = (string) ($mi['image'] ?? '');
|
||||
$mi_text = (string) ($mi['text'] ?? '');
|
||||
$mi_link = is_array($mi['link'] ?? null) ? $mi['link'] : [];
|
||||
$mi_url = (string) ($mi_link['url'] ?? '');
|
||||
$mi_target = (string) ($mi_link['target'] ?? '');
|
||||
?>
|
||||
<?php if ($mi_url !== '') : ?>
|
||||
<a href="<?php echo esc_url($mi_url); ?>"<?php echo $mi_target !== '' ? ' target="' . esc_attr($mi_target) . '"' : ''; ?>>
|
||||
<?php endif; ?>
|
||||
<div<?php echo $mi_text !== '' ? ' title="' . esc_attr($mi_text) . '"' : ''; ?>>
|
||||
<img<?php echo aksrt_get_lazy_attr('lazy_other', $mi_img); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="<?php echo esc_attr($mi_text !== '' ? $mi_text : $blog_name); ?>">
|
||||
<?php if ($mi_text !== '') : ?>
|
||||
<div class="opacity8 em09 mt6 text-center"><?php echo esc_html($mi_text); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if ($mi_url !== '') : ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -234,10 +234,6 @@ if (class_exists('\AKSRT\Content\DownloadState')) {
|
||||
if (class_exists('\AKSRT\Core\DocsPresenter')) {
|
||||
\AKSRT\Core\DocsPresenter::get_instance();
|
||||
}
|
||||
// Homepage module preview REST + query var (render still via helpers / static methods)
|
||||
if (class_exists('\AKSRT\Core\HomeModules')) {
|
||||
\AKSRT\Core\HomeModules::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\AKSRT\Content\Visibility')) {
|
||||
\AKSRT\Content\Visibility::get_instance()->init();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$aksrt_header_settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_all()
|
||||
? \AKSRT\Settings\Settings::read_domain('page_display', 'header-nav')
|
||||
: [];
|
||||
$aksrt_header_layout = sanitize_key((string) ($aksrt_header_settings['header_layout'] ?? '1'));
|
||||
if (!in_array($aksrt_header_layout, ['1', '2', '3'], true)) {
|
||||
@@ -47,40 +47,6 @@ $aksrt_body_classes[] = 'header-layout-' . $aksrt_header_layout;
|
||||
$aksrt_body_classes[] = 'mobile-header-' . $aksrt_mobile_layout;
|
||||
$aksrt_body_classes[] = 'mobile-nav-' . $aksrt_mobile_align;
|
||||
|
||||
// Global background system.
|
||||
$aksrt_bg_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_global_background_settings() : [];
|
||||
$aksrt_bg_styles = [];
|
||||
if (!empty($aksrt_bg_settings['bg_color'])) {
|
||||
$aksrt_bg_styles[] = '--aksrt-bg-color:' . sanitize_hex_color($aksrt_bg_settings['bg_color']);
|
||||
}
|
||||
if (!empty($aksrt_bg_settings['bg_image'])) {
|
||||
$aksrt_bg_styles[] = '--aksrt-bg-image:url(' . esc_url($aksrt_bg_settings['bg_image']) . ')';
|
||||
$aksrt_bg_styles[] = '--aksrt-bg-size:' . sanitize_key($aksrt_bg_settings['bg_size'] ?: '100%');
|
||||
$aksrt_bg_styles[] = '--aksrt-bg-position:' . sanitize_text_field($aksrt_bg_settings['bg_position'] ?: 'center center');
|
||||
$aksrt_bg_styles[] = '--aksrt-bg-repeat:' . sanitize_key($aksrt_bg_settings['bg_repeat'] ?: 'no-repeat');
|
||||
$aksrt_bg_styles[] = '--aksrt-bg-attachment:' . sanitize_key($aksrt_bg_settings['bg_attachment'] ?: 'fixed');
|
||||
}
|
||||
if (!empty($aksrt_bg_settings['bg_dark_color'])) {
|
||||
$aksrt_bg_styles[] = '--aksrt-bg-dark-color:' . sanitize_hex_color($aksrt_bg_settings['bg_dark_color']);
|
||||
}
|
||||
if (!empty($aksrt_bg_settings['bg_dark_image'])) {
|
||||
$aksrt_bg_styles[] = '--aksrt-bg-dark-image:url(' . esc_url($aksrt_bg_settings['bg_dark_image']) . ')';
|
||||
}
|
||||
if (!empty($aksrt_bg_settings['bg_overlay'])) {
|
||||
$aksrt_bg_styles[] = '--aksrt-bg-overlay-color:' . sanitize_text_field($aksrt_bg_settings['bg_overlay_color'] ?: 'rgba(0, 0, 0, 0.3)');
|
||||
}
|
||||
if (!empty($aksrt_bg_settings['page_bg_color'])) {
|
||||
$aksrt_bg_styles[] = '--aksrt-page-bg-color:' . sanitize_hex_color($aksrt_bg_settings['page_bg_color']);
|
||||
}
|
||||
if (!empty($aksrt_bg_settings['page_bg_image'])) {
|
||||
$aksrt_bg_styles[] = '--aksrt-page-bg-image:url(' . esc_url($aksrt_bg_settings['page_bg_image']) . ')';
|
||||
}
|
||||
if (!empty($aksrt_bg_styles)) {
|
||||
$aksrt_body_classes[] = 'aksrt-bg-body';
|
||||
if (!empty($aksrt_bg_settings['bg_overlay'])) {
|
||||
$aksrt_body_classes[] = 'aksrt-bg-overlay';
|
||||
}
|
||||
}
|
||||
?><!DOCTYPE html>
|
||||
<html lang="<?php echo esc_attr(str_replace('_', '-', get_bloginfo('language'))); ?>"<?php echo $aksrt_initial_theme_mode === 'dark' ? ' class="dark"' : ''; ?><?php echo $aksrt_has_initial_theme ? ' data-theme="' . esc_attr($aksrt_initial_theme_mode) . '"' : ''; ?>>
|
||||
<head>
|
||||
@@ -88,10 +54,9 @@ if (!empty($aksrt_bg_styles)) {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0, viewport-fit=cover">
|
||||
<?php wp_head(); ?>
|
||||
</head>
|
||||
<body <?php body_class($aksrt_body_classes); ?><?php echo !empty($aksrt_bg_styles) ? ' style="' . esc_attr(implode(';', $aksrt_bg_styles)) . '"' : ''; ?><?php echo $aksrt_has_initial_theme ? ' data-theme="' . esc_attr($aksrt_initial_theme_mode) . '"' : ''; ?>>
|
||||
<body <?php body_class($aksrt_body_classes); ?><?php echo $aksrt_has_initial_theme ? ' data-theme="' . esc_attr($aksrt_initial_theme_mode) . '"' : ''; ?>>
|
||||
<?php wp_body_open(); ?>
|
||||
|
||||
<div id="aksrt-bg-vars" hidden <?php echo !empty($aksrt_bg_styles) ? 'style="' . esc_attr(implode(';', $aksrt_bg_styles)) . '"' : ''; ?>></div>
|
||||
|
||||
<?php
|
||||
if (function_exists('aksrt_header')) {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* 从 functions.php 中抽离的核心功能钩子:
|
||||
* - SMTP 邮件配置注入
|
||||
* - 评论开关控制
|
||||
* - Banner CPT 注册
|
||||
* - Admin Bar 隐藏
|
||||
*
|
||||
* @package AKSRT
|
||||
@@ -36,25 +35,3 @@ add_filter('comment_text', function ($comment_text, $comment = null, $args = [])
|
||||
}, 20, 3);
|
||||
|
||||
// ============================================================
|
||||
// 注册自定义文章类型 - Banner (REST API for SPA)
|
||||
// ============================================================
|
||||
|
||||
function aksrt_register_banner_cpt() {
|
||||
register_post_type('aksrt_banner', array(
|
||||
'labels' => array(
|
||||
'name' => __('Banners', 'aksrt'),
|
||||
'singular_name' => __('Banner', 'aksrt'),
|
||||
),
|
||||
'public' => false,
|
||||
'show_ui' => false,
|
||||
'show_in_rest' => true,
|
||||
'supports' => array('title'),
|
||||
'menu_icon' => 'dashicons-images-alt2',
|
||||
'capability_type' => 'post',
|
||||
'capabilities' => array('create_posts' => 'manage_options'),
|
||||
'map_meta_cap' => true,
|
||||
));
|
||||
}
|
||||
add_action('init', 'aksrt_register_banner_cpt');
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -119,6 +119,67 @@ if (!function_exists('aksrt_archive_render_filter_group')) :
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_archive_build_term_filter_group')) :
|
||||
/**
|
||||
* Build one ordered category/tag filter group from the page-display fieldset.
|
||||
*
|
||||
* @param string $taxonomy category|post_tag.
|
||||
* @param array<string,mixed> $option lists/dropdown/dropdown_lists fieldset.
|
||||
* @param string $label Visible group label.
|
||||
* @param bool $ajax Whether to attach archive AJAX attributes.
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
function aksrt_archive_build_term_filter_group(string $taxonomy, array $option, string $label, bool $ajax): array {
|
||||
if (!in_array($taxonomy, ['category', 'post_tag'], true)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ids = is_array($option['lists'] ?? null) ? $option['lists'] : [];
|
||||
if (!empty($option['dropdown']) && is_array($option['dropdown_lists'] ?? null)) {
|
||||
$ids = array_merge($ids, $option['dropdown_lists']);
|
||||
}
|
||||
$ids = array_values(array_unique(array_filter(array_map('absint', $ids))));
|
||||
if ($ids === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$terms = get_terms([
|
||||
'taxonomy' => $taxonomy,
|
||||
'hide_empty' => true,
|
||||
'include' => $ids,
|
||||
'orderby' => 'include',
|
||||
]);
|
||||
if (is_wp_error($terms) || $terms === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query_key = $taxonomy === 'category' ? 'aksrt_filter_cat' : 'aksrt_filter_tag';
|
||||
$data_key = $taxonomy === 'category' ? 'data-filter-cat' : 'data-filter-tag';
|
||||
$current = isset($_GET[$query_key]) ? absint(wp_unslash($_GET[$query_key])) : 0;
|
||||
$chips = [[
|
||||
'label' => __('全部', 'aksrt'),
|
||||
'url' => remove_query_arg([$query_key, 'paged']),
|
||||
'active' => $current === 0,
|
||||
'attrs' => $ajax ? ' ' . $data_key . '=""' : '',
|
||||
]];
|
||||
|
||||
foreach ($terms as $term) {
|
||||
$term_id = (int) $term->term_id;
|
||||
$chips[] = [
|
||||
'label' => $term->name,
|
||||
'url' => add_query_arg($query_key, $term_id, remove_query_arg('paged')),
|
||||
'active' => $current === $term_id,
|
||||
'attrs' => $ajax ? ' ' . $data_key . '="' . $term_id . '"' : '',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'label' => $label,
|
||||
'chips' => $chips,
|
||||
];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_archive_render_filter_bar')) :
|
||||
/**
|
||||
* Shared filter bar shell for category/tag archives.
|
||||
@@ -134,13 +195,15 @@ if (!function_exists('aksrt_archive_render_filter_bar')) :
|
||||
'meta_text' => '',
|
||||
'groups' => [],
|
||||
'sort_enabled' => false,
|
||||
'sort_current' => 'latest',
|
||||
'sort_current' => 'date',
|
||||
'sort_links' => [],
|
||||
'sort_labels' => [],
|
||||
'sort_as_main' => false,
|
||||
]);
|
||||
|
||||
$groups = is_array($args['groups'] ?? null) ? $args['groups'] : [];
|
||||
$sort_links = is_array($args['sort_links'] ?? null) ? $args['sort_links'] : [];
|
||||
$sort_labels = is_array($args['sort_labels'] ?? null) ? $args['sort_labels'] : [];
|
||||
$has_sort = !empty($args['sort_enabled']) && !empty($sort_links);
|
||||
if (empty($groups) && !$has_sort && (string) $args['meta_text'] === '') {
|
||||
return;
|
||||
@@ -170,7 +233,7 @@ if (!function_exists('aksrt_archive_render_filter_bar')) :
|
||||
$sort_chips = [];
|
||||
foreach ($sort_links as $sort_key => $sort_url) {
|
||||
$sort_chips[] = [
|
||||
'label' => $sort_key === 'oldest' ? __('最早', 'aksrt') : ($sort_key === 'popular' ? __('热门', 'aksrt') : __('最新', 'aksrt')),
|
||||
'label' => (string) ($sort_labels[$sort_key] ?? $sort_key),
|
||||
'url' => (string) $sort_url,
|
||||
'active' => (string) $args['sort_current'] === (string) $sort_key,
|
||||
'attrs' => $ajax ? ' data-filter-sort="' . esc_attr((string) $sort_key) . '"' : '',
|
||||
|
||||
@@ -90,28 +90,35 @@ endif;
|
||||
|
||||
if (!function_exists('aksrt_archive_get_sort_state')) :
|
||||
/**
|
||||
* Shared latest/oldest/popular sort state for term archives.
|
||||
* Build the canonical Zibll orderby state for term archives.
|
||||
*
|
||||
* @param array<string,mixed> $option Page-display orderby fieldset.
|
||||
* @return array{current:string,links:array<string,string>}
|
||||
*/
|
||||
function aksrt_archive_get_sort_state(): array {
|
||||
$current_orderby = sanitize_key((string) get_query_var('orderby'));
|
||||
$current_order = strtoupper(sanitize_key((string) get_query_var('order')));
|
||||
$current = 'latest';
|
||||
function aksrt_archive_get_sort_state(array $option = []): array {
|
||||
$all = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::homepage_allowed_sorts()
|
||||
: ['modified', 'date', 'views', 'like', 'comment_count', 'favorite', 'rand'];
|
||||
$lists = is_array($option['lists'] ?? null) ? $option['lists'] : [];
|
||||
$lists = array_values(array_intersect(array_map('sanitize_key', $lists), $all));
|
||||
if (!empty($option['dropdown'])) {
|
||||
$lists = array_values(array_unique(array_merge($lists, $all)));
|
||||
}
|
||||
|
||||
if ($current_orderby === 'date' && $current_order === 'ASC') {
|
||||
$current = 'oldest';
|
||||
} elseif ($current_orderby === 'comment_count') {
|
||||
$current = 'popular';
|
||||
$current = isset($_GET['orderby']) ? sanitize_key(wp_unslash((string) $_GET['orderby'])) : 'date';
|
||||
if (!in_array($current, $all, true)) {
|
||||
$current = 'date';
|
||||
}
|
||||
|
||||
$base_url = remove_query_arg(['orderby', 'order', 'paged']);
|
||||
$links = [];
|
||||
foreach ($lists as $orderby) {
|
||||
$links[$orderby] = add_query_arg('orderby', $orderby, $base_url);
|
||||
}
|
||||
|
||||
return [
|
||||
'current' => $current,
|
||||
'links' => [
|
||||
'latest' => remove_query_arg(['orderby', 'order', 'paged']),
|
||||
'oldest' => add_query_arg(['orderby' => 'date', 'order' => 'asc', 'paged' => false]),
|
||||
'popular' => add_query_arg(['orderby' => 'comment_count', 'order' => 'desc', 'paged' => false]),
|
||||
],
|
||||
'links' => $links,
|
||||
];
|
||||
}
|
||||
endif;
|
||||
|
||||
@@ -141,7 +141,7 @@ if (!function_exists('aksrt_render_header_menu_buttons')) :
|
||||
* Publish + optional VIP open button for header CTA.
|
||||
*/
|
||||
function aksrt_render_header_menu_buttons(): string {
|
||||
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
||||
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::read_domain('page_display', 'header-nav') : [];
|
||||
$types = is_array($settings['nav_newposts_btns'] ?? null)
|
||||
? array_values(array_intersect(array_map('sanitize_key', $settings['nav_newposts_btns']), ['post']))
|
||||
: ['post'];
|
||||
@@ -180,7 +180,7 @@ if (!function_exists('aksrt_render_header_menu_buttons')) :
|
||||
$show_vip = false;
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$vs = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||||
$show_vip = !empty($vs['enabled']) && (!isset($settings['nav_pay_vip']) || !empty($settings['nav_pay_vip']));
|
||||
$show_vip = !empty($vs['enabled']);
|
||||
}
|
||||
if ($show_vip) {
|
||||
$is_vip = false;
|
||||
|
||||
@@ -143,11 +143,6 @@ if (!function_exists('aksrt_header_payvip_bar')) :
|
||||
return $html;
|
||||
}
|
||||
|
||||
// Open-VIP CTA only when user-box switch is on (zibll nav_user_pay_vip).
|
||||
if (isset($settings['nav_user_pay_vip']) && empty($settings['nav_user_pay_vip'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Match check-in CTA geometry (full width, 6px radius) — not top-nav navbar-cta chips.
|
||||
return '<div class="mt10 em09 aksrt-header-payvip">'
|
||||
. '<a class="pay-vip but block payvip-icon aksrt-header-payvip__cta" href="javascript:;" vip-level="1">'
|
||||
|
||||
@@ -51,15 +51,11 @@ if (!function_exists('aksrt_homepage_get_tab_terms')) {
|
||||
* Homepage category tabs with full config.
|
||||
*
|
||||
* Returns enabled tabs in config order, each with 'term', 'title', 'orderby'.
|
||||
* Uses homepage_tab_limit from settings.
|
||||
* Preserves every configured row in the saved order.
|
||||
*
|
||||
* @return array<int, array{term:WP_Term, title:string, orderby:string, term_id:int}>
|
||||
*/
|
||||
function aksrt_homepage_get_tab_configs(): array {
|
||||
$limit = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::homepage_tab_limit()
|
||||
: 4;
|
||||
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||||
return [];
|
||||
}
|
||||
@@ -73,12 +69,9 @@ if (!function_exists('aksrt_homepage_get_tab_terms')) {
|
||||
$configs = [];
|
||||
|
||||
foreach ($structured_tabs as $tab) {
|
||||
if (empty($tab['enabled']) || $tab['term_id'] <= 0) {
|
||||
if ($tab['term_id'] <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (count($configs) >= $limit) {
|
||||
break;
|
||||
}
|
||||
if (isset($excluded_cat_ids[$tab['term_id']])) {
|
||||
continue;
|
||||
}
|
||||
@@ -95,7 +88,7 @@ if (!function_exists('aksrt_homepage_get_tab_terms')) {
|
||||
'term' => $term,
|
||||
'term_id' => (int) $tab['term_id'],
|
||||
'title' => $tab['title'] !== '' ? $tab['title'] : $term->name,
|
||||
'orderby' => $tab['orderby'] ?: 'date',
|
||||
'orderby' => $tab['orderby'] ?: 'modified',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -143,19 +136,6 @@ if (!function_exists('aksrt_homepage_sticky_tabs_enabled')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_homepage_module_banner_enabled')) {
|
||||
/**
|
||||
* Whether the fixed homepage should render the built-in banner section.
|
||||
*/
|
||||
function aksrt_homepage_module_banner_enabled(): bool {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::homepage_module_banner_enabled();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_homepage_get_sort_mode')) {
|
||||
/**
|
||||
* Current homepage main-stream sort mode.
|
||||
@@ -163,10 +143,10 @@ if (!function_exists('aksrt_homepage_get_sort_mode')) {
|
||||
function aksrt_homepage_get_sort_mode(): string {
|
||||
$allowed = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::homepage_allowed_sorts()
|
||||
: ['modified', 'date', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand'];
|
||||
: ['modified', 'date', 'views', 'like', 'comment_count', 'favorite', 'rand'];
|
||||
$default_sort = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::homepage_default_sort()
|
||||
: 'date';
|
||||
: 'modified';
|
||||
$override_sort = get_query_var('aksrt_homepage_sort_override');
|
||||
$sort = is_string($override_sort) && $override_sort !== ''
|
||||
? sanitize_key($override_sort)
|
||||
@@ -187,8 +167,7 @@ if (!function_exists('aksrt_homepage_get_sort_items')) {
|
||||
? \AKSRT\Settings\Settings::homepage_sort_labels()
|
||||
: [
|
||||
'modified' => '更新', 'date' => '发布', 'views' => '浏览',
|
||||
'likes' => '点赞', 'comments' => '评论', 'favorites' => '收藏',
|
||||
'downloads' => '下载', 'rand' => '随机',
|
||||
'like' => '点赞', 'comment_count' => '评论', 'favorite' => '收藏', 'rand' => '随机',
|
||||
];
|
||||
|
||||
$enabled_keys = class_exists('\AKSRT\Settings\Settings')
|
||||
@@ -239,20 +218,17 @@ if (!function_exists('aksrt_homepage_apply_sort_args')) {
|
||||
$args['orderby'] = 'modified';
|
||||
$args['order'] = 'DESC';
|
||||
break;
|
||||
case 'downloads':
|
||||
$args = $apply_meta_sort($args, '_aksrt_res_download_count');
|
||||
break;
|
||||
case 'views':
|
||||
// Canonical public views key (align hotspot consumers later).
|
||||
$args = $apply_meta_sort($args, 'aksrt_post_views');
|
||||
break;
|
||||
case 'likes':
|
||||
case 'like':
|
||||
$args = $apply_meta_sort($args, '_aksrt_like_count');
|
||||
break;
|
||||
case 'favorites':
|
||||
case 'favorite':
|
||||
$args = $apply_meta_sort($args, '_aksrt_favorite_count');
|
||||
break;
|
||||
case 'comments':
|
||||
case 'comment_count':
|
||||
$args['orderby'] = 'comment_count';
|
||||
$args['order'] = 'DESC';
|
||||
break;
|
||||
@@ -278,10 +254,10 @@ if (!function_exists('aksrt_homepage_apply_named_sort_args')) {
|
||||
function aksrt_homepage_apply_named_sort_args(array $args, string $sort_mode): array {
|
||||
$allowed = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::homepage_allowed_sorts()
|
||||
: ['modified', 'date', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand'];
|
||||
: ['modified', 'date', 'views', 'like', 'comment_count', 'favorite', 'rand'];
|
||||
|
||||
if (!in_array($sort_mode, $allowed, true)) {
|
||||
$sort_mode = 'date';
|
||||
$sort_mode = 'modified';
|
||||
}
|
||||
|
||||
$previous = get_query_var('aksrt_homepage_sort_override');
|
||||
|
||||
@@ -372,7 +372,8 @@ if (!function_exists('aksrt_link_get_search_cover')) :
|
||||
$img = (string) get_post_meta($post_id, 'page_header_cover_img', true);
|
||||
}
|
||||
if ($img === '' && class_exists('\AKSRT\Settings\Settings')) {
|
||||
$img = (string) \AKSRT\Settings\Settings::get_value('page_header_cover_img', '');
|
||||
$other = \AKSRT\Settings\Settings::get_other_page_settings();
|
||||
$img = (string) ($other['header_cover_img'] ?? '');
|
||||
}
|
||||
if ($img === '') {
|
||||
$img = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
|
||||
@@ -168,10 +168,6 @@ if (!function_exists('aksrt_header_user_box_html')) :
|
||||
}
|
||||
}
|
||||
$payvip = function_exists('aksrt_header_payvip_bar') ? aksrt_header_payvip_bar($user_id) : '';
|
||||
// nav_user_pay_vip gate: empty payvip when switch off for non-VIP CTA only — helper already handles VIP status.
|
||||
if (empty($ctx['nav_user_pay_vip']) && $payvip !== '' && strpos($payvip, 'is-vip') === false) {
|
||||
$payvip = '';
|
||||
}
|
||||
$checkin = function_exists('aksrt_header_user_checkin_btn') ? aksrt_header_user_checkin_btn($user_id) : '';
|
||||
$wallet = function_exists('aksrt_header_user_wallet_mini') ? aksrt_header_user_wallet_mini($user_id) : '';
|
||||
$badges = function_exists('aksrt_header_user_badges_row') ? aksrt_header_user_badges_row($user_id) : '';
|
||||
@@ -201,8 +197,7 @@ if (!function_exists('aksrt_header_user_box_html')) :
|
||||
}
|
||||
|
||||
if (!empty($ctx['can_publish'])) {
|
||||
$all = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : [];
|
||||
$pub_text = (string) ($all['nav_user_newposts_btn_text'] ?? '');
|
||||
$pub_text = (string) ($ctx['settings']['nav_user_newposts_btn_text'] ?? '');
|
||||
if ($pub_text === '') {
|
||||
$pub_text = __('发布内容', 'aksrt');
|
||||
}
|
||||
@@ -249,7 +244,7 @@ if (!function_exists('aksrt_header_user_box_html')) :
|
||||
$con .= '<div class="mt10 text-center">' . $href . '</div>';
|
||||
} else {
|
||||
$vip_guest = '';
|
||||
if (!empty($ctx['nav_user_pay_vip']) && class_exists('\AKSRT\Settings\Settings')) {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$vs = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||||
if (!empty($vs['enabled'])) {
|
||||
$vip_guest = '<div><a class="em09 signin-loader but jb-red radius4 payvip-icon btn-block mt10" href="javascript:;">'
|
||||
@@ -296,7 +291,7 @@ if (!function_exists('aksrt_menu_button')) :
|
||||
$user_id = (int) ($ctx['user_id'] ?? 0);
|
||||
$button = '';
|
||||
|
||||
// Publish + VIP (respect nav_pay_vip).
|
||||
// Publish and membership actions.
|
||||
if (function_exists('aksrt_render_header_menu_buttons')) {
|
||||
$action_buttons = aksrt_render_header_menu_buttons();
|
||||
if ($action_buttons !== '') {
|
||||
|
||||
@@ -20,7 +20,7 @@ if (!function_exists('aksrt_get_header_nav_context')) :
|
||||
}
|
||||
|
||||
$settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_all()
|
||||
? \AKSRT\Settings\Settings::read_domain('page_display', 'header-nav')
|
||||
: [];
|
||||
$layout = sanitize_key((string) ($settings['header_layout'] ?? '1'));
|
||||
if (!in_array($layout, ['1', '2', '3'], true)) {
|
||||
@@ -79,8 +79,6 @@ if (!function_exists('aksrt_get_header_nav_context')) :
|
||||
'mobile_align' => $mobile_align,
|
||||
'fixed' => $nav_fixed,
|
||||
'scroll_down_hide' => (!isset($settings['nav_scroll_down_hide']) || !empty($settings['nav_scroll_down_hide'])) && $nav_fixed,
|
||||
'nav_pay_vip' => !isset($settings['nav_pay_vip']) || !empty($settings['nav_pay_vip']),
|
||||
'nav_user_pay_vip' => !isset($settings['nav_user_pay_vip']) || !empty($settings['nav_user_pay_vip']),
|
||||
'brand_name' => $brand_name,
|
||||
'logo_html' => $logo_html,
|
||||
'has_logo' => $logo_html !== '',
|
||||
|
||||
@@ -48,7 +48,6 @@ if (!function_exists('aksrt_homepage_get_latest_query')) {
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_homepage_get_tab_query')) {
|
||||
/**
|
||||
* Build a category-tab query.
|
||||
@@ -100,7 +99,6 @@ if (!function_exists('aksrt_homepage_get_tab_query')) {
|
||||
return $queries[$cache_key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_homepage_has_active_sidebars')) {
|
||||
/**
|
||||
* Check whether any sidebar in the given stack is active.
|
||||
@@ -362,21 +360,3 @@ if (!function_exists('aksrt_homepage_render_posts_grid')) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_homepage_render_section')) {
|
||||
/**
|
||||
* Render the remaining first-class homepage banner section.
|
||||
*
|
||||
* These are fixed homepage sections, not a freeform module canvas bridge.
|
||||
*/
|
||||
function aksrt_homepage_render_section(string $type): void {
|
||||
$type = sanitize_key($type);
|
||||
if ($type !== 'banner') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fixed homepage owns these sections directly.
|
||||
// Header slider is rendered by aksrt_header_slide(), never by this module path.
|
||||
get_template_part('templates/parts/home-modules/module', $type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -65,14 +64,6 @@ if (!function_exists('aksrt_header_slide_matches_query')) {
|
||||
return is_singular('post');
|
||||
case 'search':
|
||||
return is_search();
|
||||
case 'topics':
|
||||
return is_tax('topics');
|
||||
case 'forum_home':
|
||||
return is_page_template('templates/pages/page-forums.php');
|
||||
case 'forum_plate':
|
||||
return is_singular('plate') || is_post_type_archive('plate');
|
||||
case 'forum_post':
|
||||
return is_singular('forum_post');
|
||||
case 'page':
|
||||
if (!is_page()) {
|
||||
return false;
|
||||
|
||||
@@ -12,7 +12,7 @@ if (!function_exists('aksrt_homepage_render_layout')) {
|
||||
* Render the fixed homepage layout (Zibll latest-first content stream).
|
||||
*
|
||||
* Order (zibll-aligned):
|
||||
* fluid top → main.container → top content → banner → tabs/list → bottom content → sidebar
|
||||
* fluid top → main.container → top content → tabs/list → bottom content → sidebar
|
||||
* → fluid bottom. Hero/header-slider is rendered from header.php, not here.
|
||||
*/
|
||||
function aksrt_homepage_render_layout(): void {
|
||||
@@ -21,7 +21,6 @@ if (!function_exists('aksrt_homepage_render_layout')) {
|
||||
$tab_configs = $show_tabs ? aksrt_homepage_get_tab_configs() : [];
|
||||
$latest_contract = aksrt_homepage_get_latest_panel_contract();
|
||||
$view_mode = (($latest_contract['render_config_array']['mode'] ?? 'list') === 'card') ? 'card' : 'list';
|
||||
$banner_on = $show_tabs && aksrt_homepage_module_banner_enabled();
|
||||
$main_list_enabled = !class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::home_posts_list_enabled();
|
||||
$list_title = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::home_list_title()
|
||||
@@ -42,9 +41,6 @@ if (!function_exists('aksrt_homepage_render_layout')) {
|
||||
'aksrt-homepage__content-zone aksrt-homepage__content-zone--top'
|
||||
);
|
||||
|
||||
if ($banner_on && function_exists('aksrt_homepage_render_section')) {
|
||||
aksrt_homepage_render_section('banner');
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if ($main_list_enabled) : ?>
|
||||
@@ -54,7 +50,7 @@ if (!function_exists('aksrt_homepage_render_layout')) {
|
||||
<ul class="list-inline scroll-x no-scrollbar aksrt-homepage__tab-list" role="tablist" data-aksrt-home-tabs style="width:100%;">
|
||||
<li class="aksrt-homepage__tab-item is-active" role="presentation">
|
||||
<button type="button" class="aksrt-homepage__tab-button" role="tab" aria-selected="true" data-home-tab-target="latest">
|
||||
<?php echo esc_html($list_title !== '' ? $list_title : __('最新发布', 'aksrt')); ?>
|
||||
<?php echo wp_kses_post($list_title !== '' ? $list_title : __('最新发布', 'aksrt')); ?>
|
||||
</button>
|
||||
</li>
|
||||
<?php foreach ($tab_configs as $tab_cfg) : ?>
|
||||
@@ -67,7 +63,7 @@ if (!function_exists('aksrt_homepage_render_layout')) {
|
||||
data-home-tab-target="term-<?php echo (int) $tab_cfg['term_id']; ?>"
|
||||
data-tab-orderby="<?php echo esc_attr($tab_cfg['orderby']); ?>"
|
||||
>
|
||||
<?php echo esc_html($tab_cfg['title']); ?>
|
||||
<?php echo wp_kses_post($tab_cfg['title']); ?>
|
||||
</button>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
@@ -77,7 +73,7 @@ if (!function_exists('aksrt_homepage_render_layout')) {
|
||||
<?php elseif ($current_page > 1) : ?>
|
||||
<div class="box-body notop nobottom aksrt-homepage__titlebox">
|
||||
<div class="title-theme">
|
||||
<?php echo esc_html($list_title !== '' ? $list_title : __('最新发布', 'aksrt')); ?>
|
||||
<?php echo wp_kses_post($list_title !== '' ? $list_title : __('最新发布', 'aksrt')); ?>
|
||||
<small class="aksrt-homepage__page-indicator ml10">
|
||||
<?php echo esc_html(sprintf(__('第 %d 页', 'aksrt'), $current_page)); ?>
|
||||
</small>
|
||||
@@ -85,7 +81,7 @@ if (!function_exists('aksrt_homepage_render_layout')) {
|
||||
</div>
|
||||
<?php elseif ($list_title !== '') : ?>
|
||||
<div class="box-body notop nobottom aksrt-homepage__titlebox">
|
||||
<div class="title-theme"><?php echo esc_html($list_title); ?></div>
|
||||
<div class="title-theme"><?php echo wp_kses_post($list_title); ?></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
@@ -7,115 +7,6 @@
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
// ============================================================
|
||||
// 模板标签:Banner 幻灯片
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_decode_slide_layers')):
|
||||
/**
|
||||
* Decode the layers JSON meta for a slide.
|
||||
*
|
||||
* @param int $post_id
|
||||
* @return array
|
||||
*/
|
||||
function aksrt_decode_slide_layers(int $post_id): array {
|
||||
$json = get_post_meta($post_id, '_layers', true);
|
||||
if (!is_string($json) || $json === '') {
|
||||
return [];
|
||||
}
|
||||
$decoded = json_decode($json, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_slider_config')):
|
||||
/**
|
||||
* Get the global slider config for content-stream Banner rendering.
|
||||
* Falls back to sensible defaults when no config is saved.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
function aksrt_get_slider_config(): array {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::get_slider_config();
|
||||
}
|
||||
return [
|
||||
'effect' => 'fade', 'loop' => true, 'autoplay_interval' => 5000,
|
||||
'pagination' => true, 'navigation' => true, 'auto_height' => false,
|
||||
'pc_height' => '', 'mobile_height' => '', 'space_between' => 0,
|
||||
];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_banners')):
|
||||
/**
|
||||
* 获取已启用的首页内容流 Banner。
|
||||
*
|
||||
* @param int $limit 最大数量
|
||||
* @return array
|
||||
*/
|
||||
function aksrt_get_banners($limit = 8) {
|
||||
$limit = max(1, min(20, (int) $limit));
|
||||
$cache_key = 'aksrt_banners_' . $limit;
|
||||
$cached = wp_cache_get($cache_key, 'aksrt');
|
||||
if (is_array($cached)) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'post_type' => 'aksrt_banner',
|
||||
'posts_per_page' => $limit,
|
||||
'post_status' => 'publish',
|
||||
'orderby' => 'menu_order',
|
||||
'order' => 'ASC',
|
||||
'no_found_rows' => true,
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_aksrt_kind',
|
||||
'value' => 'banner',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$query = new WP_Query($args);
|
||||
$banners = array();
|
||||
|
||||
if ($query->have_posts()) {
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$id = get_the_ID();
|
||||
|
||||
// 只返回启用的
|
||||
$enabled = get_post_meta($id, '_enabled', true);
|
||||
if ($enabled === '0') continue;
|
||||
|
||||
$banners[] = array(
|
||||
'id' => $id,
|
||||
'title' => get_the_title(),
|
||||
'subtitle' => get_post_meta($id, '_subtitle', true),
|
||||
'description' => get_post_meta($id, '_description', true),
|
||||
'image' => get_post_meta($id, '_image_url', true),
|
||||
'video_url' => get_post_meta($id, '_video_url', true),
|
||||
'link' => get_post_meta($id, '_link_url', true),
|
||||
'link_target' => get_post_meta($id, '_link_target', true) ?: '_self',
|
||||
'text_align' => get_post_meta($id, '_text_align', true) ?: 'left',
|
||||
'device_visibility' => get_post_meta($id, '_device_visibility', true) ?: 'all',
|
||||
'show_text' => get_post_meta($id, '_show_text', true) !== '0',
|
||||
'show_meta' => get_post_meta($id, '_show_meta', true) === '1',
|
||||
'text_size_pc' => get_post_meta($id, '_text_size_pc', true) ?: '',
|
||||
'text_size_m' => get_post_meta($id, '_text_size_m', true) ?: '',
|
||||
'text_parallax' => get_post_meta($id, '_text_parallax', true) === '1',
|
||||
'layers' => aksrt_decode_slide_layers($id),
|
||||
);
|
||||
}
|
||||
wp_reset_postdata();
|
||||
}
|
||||
|
||||
wp_cache_set($cache_key, $banners, 'aksrt', 15 * MINUTE_IN_SECONDS);
|
||||
return $banners;
|
||||
}
|
||||
endif;
|
||||
|
||||
// ============================================================
|
||||
// 模板标签:图标覆盖卡片(顶部多功能组件叠加层)
|
||||
// 完全模仿 zibll 的 zib_icon_cover_card
|
||||
|
||||
@@ -378,25 +378,6 @@ function aksrt_user_can(string $cap, int $user_id = 0, ?int $context = null): bo
|
||||
endif;
|
||||
|
||||
|
||||
if (!function_exists('aksrt_flush_banner_cache')) :
|
||||
/**
|
||||
* Invalidate banner CPT object-cache keys used by aksrt_get_banners().
|
||||
*/
|
||||
function aksrt_flush_banner_cache($post_id = 0) {
|
||||
if ($post_id && get_post_type($post_id) && get_post_type($post_id) !== 'aksrt_banner') {
|
||||
return;
|
||||
}
|
||||
// Group flush is not available on all object caches; delete common keys.
|
||||
for ($n = 1; $n <= 20; $n++) {
|
||||
wp_cache_delete('aksrt_banners_' . $n, 'aksrt');
|
||||
}
|
||||
}
|
||||
add_action('save_post_aksrt_banner', 'aksrt_flush_banner_cache');
|
||||
add_action('deleted_post', 'aksrt_flush_banner_cache');
|
||||
add_action('trashed_post', 'aksrt_flush_banner_cache');
|
||||
endif;
|
||||
|
||||
|
||||
if (!function_exists('aksrt_should_use_footer_tabbar_single_extend')) :
|
||||
/**
|
||||
* Whether single-post extended footer tabbar should replace the default items.
|
||||
|
||||
@@ -292,7 +292,7 @@ function aksrt_get_search_facets_data(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
$allowed = ['category', 'topics', 'post_tag'];
|
||||
$allowed = ['category', 'post_tag'];
|
||||
$data = [];
|
||||
foreach ($raw as $taxonomy => $ids) {
|
||||
$taxonomy = sanitize_key((string) $taxonomy);
|
||||
@@ -393,7 +393,6 @@ function aksrt_get_search_tab_nav_filter(string $type): string {
|
||||
if ($type === 'post') {
|
||||
$labels = [
|
||||
'category' => __('分类', 'aksrt'),
|
||||
'topics' => __('专题', 'aksrt'),
|
||||
'post_tag' => __('标签', 'aksrt'),
|
||||
];
|
||||
$current = isset($_REQUEST['trem']) ? absint(wp_unslash($_REQUEST['trem'])) : 0;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* AKSRT Theme - Template Tags Module
|
||||
*
|
||||
* 模板标签 / 辅助函数集合:
|
||||
* - Banner 幻灯片获取
|
||||
* - 友情链接 / 站点 Logo / Favicon / About 信息
|
||||
* - 社交链接渲染
|
||||
* - 文章分类标签 / 阅读时间 / 评论数
|
||||
@@ -190,14 +189,6 @@ function aksrt_is_show_sidebar_filter(): bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Search page display setting can force off.
|
||||
if (is_search() && class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_other_page_settings')) {
|
||||
$other = \AKSRT\Settings\Settings::get_other_page_settings();
|
||||
if (is_array($other) && array_key_exists('page_search_sidebar', $other) && empty($other['page_search_sidebar'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Scene switches above already gated; when enabled, show the column
|
||||
// (widgets may be empty — matches zibll empty sidebar column policy).
|
||||
if (is_home() || is_front_page()) {
|
||||
@@ -295,4 +286,3 @@ function aksrt_body_layout_classes(): array {
|
||||
return ['site-layout-' . $layout];
|
||||
}
|
||||
endif;
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ abstract class ControllerBase {
|
||||
return $this->error('无效的设置载荷');
|
||||
}
|
||||
|
||||
$sanitized = $this->sanitize_fields($payload, $provider->get_allowed_fields($domain, $section));
|
||||
$sanitized = $provider->sanitize_payload($domain, $payload, $section);
|
||||
$validation = $provider->validate($domain, $sanitized, $section);
|
||||
if (!$validation['valid']) {
|
||||
return $this->error(implode('; ', $validation['errors']), 422);
|
||||
|
||||
@@ -10,7 +10,6 @@ defined('ABSPATH') || exit;
|
||||
class GlobalController extends ControllerBase {
|
||||
use GlobalControllerPart1;
|
||||
use GlobalControllerPart2;
|
||||
use HomepageSettingsRest;
|
||||
|
||||
|
||||
private static $instance = null;
|
||||
@@ -36,13 +35,6 @@ class GlobalController extends ControllerBase {
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Footer
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/footer', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_footer_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Footer Tabbar
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/footer-tabbar', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
@@ -91,13 +83,6 @@ class GlobalController extends ControllerBase {
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Homepage settings
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/homepage', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_homepage_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Category recommend
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/category-recommend', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
@@ -144,14 +129,11 @@ class GlobalController extends ControllerBase {
|
||||
'args' => [
|
||||
'archive_type' => ['type' => 'string', 'default' => 'category', 'sanitize_callback' => 'sanitize_key'],
|
||||
'term_id' => ['type' => 'integer', 'default' => 0, 'sanitize_callback' => 'absint'],
|
||||
'filter_cat' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'filter_tag' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'sort' => ['type' => 'string', 'default' => 'date', 'sanitize_callback' => 'sanitize_key'],
|
||||
'order' => ['type' => 'string', 'default' => 'DESC', 'sanitize_callback' => 'sanitize_key'],
|
||||
'page' => ['type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint'],
|
||||
'per_page' => ['type' => 'integer', 'default' => 10, 'sanitize_callback' => 'absint'],
|
||||
'custom_filters' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
],
|
||||
'filter_cat' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'filter_tag' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'sort' => ['type' => 'string', 'default' => 'date', 'sanitize_callback' => 'sanitize_key'],
|
||||
'page' => ['type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Homepage categories (for admin tab editor)
|
||||
@@ -307,9 +289,62 @@ class GlobalController extends ControllerBase {
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_archive_posts(\WP_REST_Request $request): \WP_REST_Response {
|
||||
return $this->rest_homepage_tab_posts($request);
|
||||
}
|
||||
public function rest_archive_posts(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$archive_type = sanitize_key((string) ($request->get_param('archive_type') ?: 'category'));
|
||||
$archive_type = in_array($archive_type, ['category', 'tag'], true) ? $archive_type : 'category';
|
||||
$term_id = absint($request->get_param('term_id'));
|
||||
$page = max(1, absint($request->get_param('page')));
|
||||
$page_settings = $archive_type === 'tag'
|
||||
? \AKSRT\Settings\Settings::get_tag_page_settings()
|
||||
: \AKSRT\Settings\Settings::get_category_page_settings();
|
||||
$per_page = min(50, max(1, (int) ($page_settings['per_page'] ?? get_option('posts_per_page', 10))));
|
||||
$sort = sanitize_key((string) ($request->get_param('sort') ?: 'date'));
|
||||
$allowed_sorts = \AKSRT\Settings\Settings::homepage_allowed_sorts();
|
||||
if (!in_array($sort, $allowed_sorts, true)) {
|
||||
$sort = 'date';
|
||||
}
|
||||
|
||||
$args = [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $per_page,
|
||||
'paged' => $page,
|
||||
'ignore_sticky_posts' => true,
|
||||
];
|
||||
$tax_query = [];
|
||||
if ($term_id > 0) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => $archive_type === 'tag' ? 'post_tag' : 'category',
|
||||
'field' => 'term_id',
|
||||
'terms' => [$term_id],
|
||||
];
|
||||
}
|
||||
$filter_cat = absint($request->get_param('filter_cat'));
|
||||
$filter_tag = absint($request->get_param('filter_tag'));
|
||||
if ($filter_cat > 0) {
|
||||
$tax_query[] = ['taxonomy' => 'category', 'field' => 'term_id', 'terms' => [$filter_cat]];
|
||||
}
|
||||
if ($filter_tag > 0) {
|
||||
$tax_query[] = ['taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => [$filter_tag]];
|
||||
}
|
||||
if ($tax_query !== []) {
|
||||
$args['tax_query'] = $tax_query;
|
||||
}
|
||||
if (function_exists('aksrt_homepage_apply_named_sort_args')) {
|
||||
$args = aksrt_homepage_apply_named_sort_args($args, $sort);
|
||||
}
|
||||
|
||||
$query = new \WP_Query($args);
|
||||
$render_config = aksrt_resolve_post_list_render_config(['context' => $archive_type]);
|
||||
$render_config['skip_wrapper'] = true;
|
||||
$html = aksrt_render_post_list($render_config, $query, false);
|
||||
|
||||
return $this->success([
|
||||
'html' => $html,
|
||||
'total' => (int) $query->found_posts,
|
||||
'has_more' => $page < (int) $query->max_num_pages,
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_homepage_categories(): \WP_REST_Response {
|
||||
$data = [];
|
||||
@@ -386,142 +421,6 @@ class GlobalController extends ControllerBase {
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('Order saved.', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_handle_footer_settings(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if ($request->get_method() === 'GET') {
|
||||
$footer = get_option('aksrt_footer_settings', []);
|
||||
$defaults = [
|
||||
'contacts' => [],
|
||||
'quick_links' => [],
|
||||
'more_links' => [],
|
||||
'hot_categories' => 5,
|
||||
'copyright_text' => '',
|
||||
'layout_template' => 'template_1',
|
||||
'footer_t1_m_s' => ['pc', 'mobile'],
|
||||
'footer_t1_img' => '',
|
||||
'footer_t1_img_dark' => '',
|
||||
'footer_t1_t' => '',
|
||||
'fcode_t1_code' => '',
|
||||
'footer_contact_m_s' => ['pc', 'mobile'],
|
||||
'footer_contact_wechat_img' => '',
|
||||
'footer_contact_qq' => '',
|
||||
'footer_contact_weibo' => '',
|
||||
'footer_contact_email' => '',
|
||||
'footer_contact_more' => '',
|
||||
'fcode_t2_code_1' => '',
|
||||
'fcode_t2_code_2' => '',
|
||||
'footer_mini_img_m_s' => ['pc', 'mobile'],
|
||||
'footer_mini_img' => [],
|
||||
'fcode_customize_code' => '',
|
||||
];
|
||||
$data = array_merge($defaults, $footer);
|
||||
return rest_ensure_response(['code' => 'success', 'data' => $data]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$footer = [
|
||||
'contacts' => isset($params['contacts']) ? $this->sanitize_contacts($params['contacts']) : [],
|
||||
'quick_links' => isset($params['quick_links']) ? $this->sanitize_link_list($params['quick_links']) : [],
|
||||
'more_links' => isset($params['more_links']) ? $this->sanitize_link_list($params['more_links']) : [],
|
||||
'hot_categories' => (int) ($params['hot_categories'] ?? 5),
|
||||
'copyright_text' => sanitize_textarea_field($params['copyright_text'] ?? ''),
|
||||
'layout_template' => 'template_1',
|
||||
'footer_t1_m_s' => $this->sanitize_device_visibility($params['footer_t1_m_s'] ?? ['pc', 'mobile']),
|
||||
'footer_t1_img' => esc_url_raw($params['footer_t1_img'] ?? ''),
|
||||
'footer_t1_img_dark' => esc_url_raw($params['footer_t1_img_dark'] ?? ''),
|
||||
'footer_t1_t' => sanitize_text_field($params['footer_t1_t'] ?? ''),
|
||||
'fcode_t1_code' => wp_kses_post($params['fcode_t1_code'] ?? ''),
|
||||
'footer_contact_m_s' => $this->sanitize_device_visibility($params['footer_contact_m_s'] ?? ['pc', 'mobile']),
|
||||
'footer_contact_wechat_img' => esc_url_raw($params['footer_contact_wechat_img'] ?? ''),
|
||||
'footer_contact_qq' => sanitize_text_field($params['footer_contact_qq'] ?? ''),
|
||||
'footer_contact_weibo' => sanitize_text_field($params['footer_contact_weibo'] ?? ''),
|
||||
'footer_contact_email' => sanitize_email($params['footer_contact_email'] ?? ''),
|
||||
'footer_contact_more' => isset($params['footer_contact_more']) && is_array($params['footer_contact_more'])
|
||||
? $this->sanitize_contact_more($params['footer_contact_more']) : [],
|
||||
'fcode_t2_code_1' => wp_kses_post($params['fcode_t2_code_1'] ?? ''),
|
||||
'fcode_t2_code_2' => wp_kses_post($params['fcode_t2_code_2'] ?? ''),
|
||||
'footer_mini_img_m_s' => $this->sanitize_device_visibility($params['footer_mini_img_m_s'] ?? ['pc', 'mobile']),
|
||||
'footer_mini_img' => isset($params['footer_mini_img']) && is_array($params['footer_mini_img'])
|
||||
? $this->sanitize_mini_img_group($params['footer_mini_img']) : [],
|
||||
'fcode_customize_code' => wp_kses_post($params['fcode_customize_code'] ?? ''),
|
||||
];
|
||||
update_option('aksrt_footer_settings', $footer);
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('Footer settings saved.', 'aksrt')]);
|
||||
}
|
||||
|
||||
private function sanitize_contacts(array $contacts): array {
|
||||
$allowed_icons = ['github', 'x', 'twitter', 'email', 'wechat', 'qq', 'weibo',
|
||||
'telegram', 'discord', 'bilibili', 'linkedin', 'facebook',
|
||||
'youtube', 'instagram', 'rss', 'web', 'globe'];
|
||||
$clean = [];
|
||||
foreach ($contacts as $item) {
|
||||
$icon = sanitize_text_field($item['icon'] ?? '');
|
||||
if (!in_array($icon, $allowed_icons)) {
|
||||
continue;
|
||||
}
|
||||
$clean[] = [
|
||||
'icon' => $icon,
|
||||
'url' => esc_url_raw($item['url'] ?? ''),
|
||||
'title' => sanitize_text_field($item['title'] ?? ''),
|
||||
'enabled' => !empty($item['enabled']),
|
||||
];
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
private function sanitize_link_list(array $links): array {
|
||||
$clean = [];
|
||||
foreach ($links as $item) {
|
||||
$clean[] = [
|
||||
'id' => isset($item['id']) ? sanitize_text_field($item['id']) : uniqid('link_'),
|
||||
'name' => sanitize_text_field($item['name'] ?? ''),
|
||||
'url' => esc_url_raw($item['url'] ?? '#'),
|
||||
'enabled' => !empty($item['enabled']),
|
||||
'sort_order' => (int) ($item['sort_order'] ?? 0),
|
||||
];
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
private function sanitize_device_visibility($value): array {
|
||||
$arr = is_array($value) ? $value : [$value];
|
||||
$allowed = ['pc', 'mobile'];
|
||||
$clean = array_values(array_intersect(array_map('sanitize_key', $arr), $allowed));
|
||||
return $clean ?: ['pc', 'mobile'];
|
||||
}
|
||||
|
||||
private function sanitize_contact_more(array $items): array {
|
||||
$clean = [];
|
||||
foreach ($items as $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
$clean[] = [
|
||||
'desc' => sanitize_text_field($item['desc'] ?? ''),
|
||||
'icon' => sanitize_text_field($item['icon'] ?? ''),
|
||||
'link' => esc_url_raw($item['link'] ?? ''),
|
||||
'img' => esc_url_raw($item['img'] ?? ''),
|
||||
];
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
private function sanitize_mini_img_group(array $items): array {
|
||||
$clean = [];
|
||||
foreach ($items as $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
if (count($clean) >= 4) {
|
||||
break;
|
||||
}
|
||||
$clean[] = [
|
||||
'text' => sanitize_text_field($item['text'] ?? ''),
|
||||
'img' => esc_url_raw($item['img'] ?? ''),
|
||||
];
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
public function rest_handle_footer_tabbar(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if ($request->get_method() === 'GET') {
|
||||
$settings = class_exists('\AKSRT\Settings\Settings')
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Admin;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait HomepageSettingsRest {
|
||||
|
||||
public function rest_handle_homepage_settings(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$option_key = 'aksrt_settings';
|
||||
|
||||
if ($request->get_method() === 'GET') {
|
||||
return rest_ensure_response([
|
||||
'code' => 'success',
|
||||
'data' => \AKSRT\Settings\HomeCms::get_homepage_fixed_settings(),
|
||||
]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
if (empty($params)) {
|
||||
$params = $request->get_params();
|
||||
}
|
||||
if (!is_array($params)) {
|
||||
$params = [];
|
||||
}
|
||||
|
||||
$allowed_sorts = \AKSRT\Settings\HomeCms::homepage_allowed_sorts();
|
||||
$settings = [];
|
||||
|
||||
// --- Canonical Zibll main list contract ---
|
||||
if (array_key_exists('home_posts_list_s', $params)) {
|
||||
$settings['home_posts_list_s'] = !empty($params['home_posts_list_s']);
|
||||
}
|
||||
|
||||
if (array_key_exists('home_list1_orderby_s', $params)) {
|
||||
$settings['home_list1_orderby_s'] = !empty($params['home_list1_orderby_s']);
|
||||
}
|
||||
|
||||
if (isset($params['home_list1_orderby_option']) && is_array($params['home_list1_orderby_option'])) {
|
||||
$settings['home_list1_orderby_option'] = $this->sanitize_home_list1_orderby_option(
|
||||
$params['home_list1_orderby_option'],
|
||||
$allowed_sorts
|
||||
);
|
||||
}
|
||||
|
||||
if (array_key_exists('index_list_title', $params)) {
|
||||
$settings['index_list_title'] = sanitize_text_field((string) $params['index_list_title']);
|
||||
}
|
||||
|
||||
if (array_key_exists('home_exclude_posts', $params)) {
|
||||
$settings['home_exclude_posts'] = $this->sanitize_id_list($params['home_exclude_posts']);
|
||||
}
|
||||
|
||||
if (array_key_exists('home_exclude_cats', $params)) {
|
||||
$settings['home_exclude_cats'] = $this->sanitize_id_list($params['home_exclude_cats']);
|
||||
}
|
||||
|
||||
if (array_key_exists('home_lists', $params)) {
|
||||
$settings['home_lists'] = $this->sanitize_home_lists($params['home_lists'], $allowed_sorts);
|
||||
}
|
||||
|
||||
if (array_key_exists('homepage_sidebar_mini_enabled', $params)) {
|
||||
$settings['homepage_sidebar_mini_enabled'] = !empty($params['homepage_sidebar_mini_enabled']);
|
||||
}
|
||||
if (array_key_exists('homepage_sort_ajax_enabled', $params)) {
|
||||
$settings['homepage_sort_ajax_enabled'] = !empty($params['homepage_sort_ajax_enabled']);
|
||||
}
|
||||
if (array_key_exists('homepage_sticky_tabs_enabled', $params)) {
|
||||
$settings['homepage_sticky_tabs_enabled'] = !empty($params['homepage_sticky_tabs_enabled']);
|
||||
}
|
||||
if (array_key_exists('home_list_sort_dropdown', $params)) {
|
||||
$settings['home_list_sort_dropdown'] = !empty($params['home_list_sort_dropdown']);
|
||||
}
|
||||
if (isset($params['homepage_tab_posts_per_page'])) {
|
||||
$settings['homepage_tab_posts_per_page'] = max(1, min(20, (int) $params['homepage_tab_posts_per_page']));
|
||||
}
|
||||
if (isset($params['homepage_tab_limit'])) {
|
||||
$settings['homepage_tab_limit'] = max(1, min(12, (int) $params['homepage_tab_limit']));
|
||||
}
|
||||
if (array_key_exists('homepage_module_banner_enabled', $params)) {
|
||||
$settings['homepage_module_banner_enabled'] = !empty($params['homepage_module_banner_enabled']);
|
||||
}
|
||||
|
||||
$current = get_option($option_key, []);
|
||||
if (!is_array($current)) {
|
||||
$current = [];
|
||||
}
|
||||
|
||||
foreach ($settings as $key => $value) {
|
||||
$current[$key] = $value;
|
||||
}
|
||||
|
||||
// Canonical writes also remove superseded aliases from persisted settings.
|
||||
$superseded = [
|
||||
'home_posts_list_enabled',
|
||||
'home_list_sort_enabled',
|
||||
'home_list_sort_options',
|
||||
'home_list_title',
|
||||
'home_exclude_post_ids',
|
||||
'home_exclude_category_ids',
|
||||
'homepage_tabs',
|
||||
'homepage_default_sort',
|
||||
];
|
||||
foreach ($superseded as $dead_key) {
|
||||
unset($current[$dead_key]);
|
||||
}
|
||||
|
||||
update_option($option_key, $current);
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
\AKSRT\Settings\Settings::flush_settings_cache();
|
||||
}
|
||||
update_option('aksrt_homepage_schema_version', gmdate('Y-m-d'));
|
||||
delete_transient('aksrt_homepage_cached_images');
|
||||
do_action('aksrt_homepage_settings_changed');
|
||||
|
||||
return rest_ensure_response([
|
||||
'code' => 'success',
|
||||
'message' => __('首页设置已保存', 'aksrt'),
|
||||
'data' => \AKSRT\Settings\HomeCms::get_homepage_fixed_settings(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return int[]
|
||||
*/
|
||||
private function sanitize_id_list($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$parts = preg_split('/[\s,]+/', $raw) ?: [];
|
||||
$raw = $parts;
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
return array_values(array_unique(array_filter(array_map('absint', $raw), static fn($id) => $id > 0)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $option
|
||||
* @param string[] $allowed_sorts
|
||||
* @return array{orderby:string,options:string[]}
|
||||
*/
|
||||
private function sanitize_home_list1_orderby_option(array $option, array $allowed_sorts): array {
|
||||
$orderby = sanitize_key((string) ($option['orderby'] ?? $option['default'] ?? 'modified'));
|
||||
if (!in_array($orderby, $allowed_sorts, true)) {
|
||||
$orderby = 'modified';
|
||||
}
|
||||
|
||||
$options = [];
|
||||
$raw_options = $option['options'] ?? $option['orderby_option'] ?? [];
|
||||
if (!is_array($raw_options)) {
|
||||
$raw_options = [];
|
||||
}
|
||||
foreach ($raw_options as $key) {
|
||||
$key = sanitize_key((string) $key);
|
||||
if (in_array($key, $allowed_sorts, true) && !in_array($key, $options, true)) {
|
||||
$options[] = $key;
|
||||
}
|
||||
}
|
||||
if ($options === []) {
|
||||
$options = ['modified', 'date', 'views', 'likes', 'comments'];
|
||||
}
|
||||
if (!in_array($orderby, $options, true)) {
|
||||
array_unshift($options, $orderby);
|
||||
}
|
||||
|
||||
return [
|
||||
'orderby' => $orderby,
|
||||
'options' => array_values($options),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw_tabs
|
||||
* @param string[] $allowed_sorts
|
||||
* @return array<int, array{term_id:int,title:string,orderby:string,enabled:bool}>
|
||||
*/
|
||||
private function sanitize_home_lists($raw_tabs, array $allowed_sorts): array {
|
||||
if (!is_array($raw_tabs)) {
|
||||
return [];
|
||||
}
|
||||
$tabs = [];
|
||||
foreach ($raw_tabs as $tab) {
|
||||
if (!is_array($tab)) {
|
||||
continue;
|
||||
}
|
||||
$term_id = absint($tab['term_id'] ?? 0);
|
||||
if ($term_id <= 0) {
|
||||
continue;
|
||||
}
|
||||
$term = get_term($term_id);
|
||||
if (!$term || is_wp_error($term)) {
|
||||
continue;
|
||||
}
|
||||
$taxonomy = (string) $term->taxonomy;
|
||||
if ($taxonomy !== 'category') {
|
||||
continue;
|
||||
}
|
||||
$orderby = sanitize_key((string) ($tab['orderby'] ?? ''));
|
||||
if ($orderby !== '' && !in_array($orderby, $allowed_sorts, true)) {
|
||||
$orderby = 'date';
|
||||
}
|
||||
$tabs[] = [
|
||||
'term_id' => $term_id,
|
||||
'title' => sanitize_text_field((string) ($tab['title'] ?? '')),
|
||||
'orderby' => $orderby,
|
||||
'enabled' => !array_key_exists('enabled', $tab) || !empty($tab['enabled']),
|
||||
];
|
||||
}
|
||||
return $tabs;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
* Page/display schema option search.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
@@ -9,169 +11,62 @@ defined('ABSPATH') || exit;
|
||||
|
||||
class PageDisplayController extends ControllerBase {
|
||||
|
||||
private static $instance = null;
|
||||
private $admin;
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
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, '/settings/schema/page_display/search/(?P<source>posts|categories|tags)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'search_options'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
'args' => [
|
||||
'query' => ['type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => ''],
|
||||
'include' => ['type' => 'array', 'items' => ['type' => 'integer'], 'default' => []],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
// Homepage content-stream banners.
|
||||
register_rest_route(self::API_NAMESPACE, '/banners', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_get_banners'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/banners', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_create_banner'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/banners/(?P<id>[\w-]+)', [
|
||||
'methods' => ['PUT', 'DELETE'],
|
||||
'callback' => [$this, 'rest_handle_banner_item'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
}
|
||||
public function search_options(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$source = sanitize_key((string) $request->get_param('source'));
|
||||
$query = sanitize_text_field((string) $request->get_param('query'));
|
||||
$include = array_values(array_filter(array_map('absint', (array) $request->get_param('include'))));
|
||||
|
||||
private function get_banner_kind_query(): array {
|
||||
return [
|
||||
[
|
||||
'key' => '_aksrt_kind',
|
||||
'value' => 'banner',
|
||||
],
|
||||
];
|
||||
}
|
||||
if ($source === 'posts') {
|
||||
$posts = get_posts([
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 20,
|
||||
's' => $query,
|
||||
'include' => $include,
|
||||
'orderby' => $include ? 'post__in' : 'relevance',
|
||||
]);
|
||||
$items = array_map(static function ($post): array {
|
||||
return ['label' => get_the_title($post) ?: ('#' . $post->ID), 'value' => (int) $post->ID];
|
||||
}, $posts);
|
||||
return $this->success(['data' => $items]);
|
||||
}
|
||||
|
||||
public function rest_get_banners(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$args = [
|
||||
'post_type' => 'aksrt_banner',
|
||||
'posts_per_page' => 100,
|
||||
'post_status' => 'any',
|
||||
'orderby' => 'menu_order',
|
||||
'order' => 'ASC',
|
||||
'meta_query' => $this->get_banner_kind_query(),
|
||||
];
|
||||
$posts = get_posts($args);
|
||||
$banners = array_map(function ($post) {
|
||||
$layers_json = get_post_meta($post->ID, '_layers', true);
|
||||
return [
|
||||
'id' => $post->ID,
|
||||
'title' => $post->post_title,
|
||||
'subtitle' => get_post_meta($post->ID, '_subtitle', true),
|
||||
'description' => get_post_meta($post->ID, '_description', true),
|
||||
'image_url' => get_post_meta($post->ID, '_image_url', true),
|
||||
'video_url' => get_post_meta($post->ID, '_video_url', true),
|
||||
'link_url' => get_post_meta($post->ID, '_link_url', true),
|
||||
'link_target' => get_post_meta($post->ID, '_link_target', true) ?: '_self',
|
||||
'text_align' => get_post_meta($post->ID, '_text_align', true) ?: 'left',
|
||||
'device_visibility' => get_post_meta($post->ID, '_device_visibility', true) ?: 'all',
|
||||
// Match frontend aksrt_get_banners(): missing _enabled means enabled.
|
||||
'enabled' => get_post_meta($post->ID, '_enabled', true) !== '0',
|
||||
'show_text' => get_post_meta($post->ID, '_show_text', true) !== '0',
|
||||
'show_meta' => get_post_meta($post->ID, '_show_meta', true) === '1',
|
||||
'sort_order' => $post->menu_order,
|
||||
'text_size_pc' => get_post_meta($post->ID, '_text_size_pc', true) ?: '',
|
||||
'text_size_m' => get_post_meta($post->ID, '_text_size_m', true) ?: '',
|
||||
'text_parallax' => get_post_meta($post->ID, '_text_parallax', true) === '1',
|
||||
'layers' => is_string($layers_json) && $layers_json !== '' ? (json_decode($layers_json, true) ?: []) : [],
|
||||
];
|
||||
}, $posts);
|
||||
return rest_ensure_response(['code' => 'success', 'data' => $banners]);
|
||||
}
|
||||
|
||||
public function rest_create_banner(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$title = sanitize_text_field($params['title'] ?? '');
|
||||
$post_id = wp_insert_post([
|
||||
// Title is optional in admin UI; keep a non-empty WP title for list readability.
|
||||
'post_title' => '' !== $title ? $title : 'Banner Slide',
|
||||
'post_type' => 'aksrt_banner',
|
||||
'post_status' => 'publish',
|
||||
'menu_order' => (int) ($params['sort_order'] ?? 0),
|
||||
]);
|
||||
|
||||
if (is_wp_error($post_id)) {
|
||||
return new \WP_REST_Response(['code' => 'error', 'message' => $post_id->get_error_message()], 400);
|
||||
}
|
||||
|
||||
update_post_meta($post_id, '_image_url', esc_url_raw($params['image_url'] ?? ''));
|
||||
update_post_meta($post_id, '_link_url', esc_url_raw($params['link_url'] ?? ''));
|
||||
update_post_meta($post_id, '_link_target', in_array($params['link_target'] ?? '', ['_self', '_blank'], true) ? $params['link_target'] : '_self');
|
||||
update_post_meta($post_id, '_subtitle', sanitize_text_field($params['subtitle'] ?? ''));
|
||||
update_post_meta($post_id, '_description', sanitize_textarea_field($params['description'] ?? ''));
|
||||
update_post_meta($post_id, '_video_url', esc_url_raw($params['video_url'] ?? ''));
|
||||
update_post_meta($post_id, '_text_align', in_array($params['text_align'] ?? '', ['left', 'center', 'right'], true) ? $params['text_align'] : 'left');
|
||||
update_post_meta($post_id, '_device_visibility', in_array($params['device_visibility'] ?? '', ['all', 'pc', 'mobile'], true) ? $params['device_visibility'] : 'all');
|
||||
update_post_meta($post_id, '_enabled', !array_key_exists('enabled', $params) || !empty($params['enabled']) ? '1' : '0');
|
||||
update_post_meta($post_id, '_show_text', !array_key_exists('show_text', $params) || !empty($params['show_text']) ? '1' : '0');
|
||||
update_post_meta($post_id, '_show_meta', !empty($params['show_meta']) ? '1' : '0');
|
||||
update_post_meta($post_id, '_aksrt_kind', 'banner');
|
||||
update_post_meta($post_id, '_text_size_pc', sanitize_text_field($params['text_size_pc'] ?? ''));
|
||||
update_post_meta($post_id, '_text_size_m', sanitize_text_field($params['text_size_m'] ?? ''));
|
||||
update_post_meta($post_id, '_text_parallax', !empty($params['text_parallax']) ? '1' : '0');
|
||||
if (isset($params['layers']) && is_array($params['layers'])) {
|
||||
update_post_meta($post_id, '_layers', wp_json_encode(array_values($params['layers'])));
|
||||
}
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'data' => ['id' => $post_id], 'message' => __('幻灯片已创建', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_handle_banner_item(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$id = absint($request['id']);
|
||||
if (!$id || get_post_type($id) !== 'aksrt_banner' || 'banner' !== get_post_meta($id, '_aksrt_kind', true)) {
|
||||
return new \WP_REST_Response(['code' => 'not_found', 'message' => __('幻灯片不存在', 'aksrt')], 404);
|
||||
}
|
||||
|
||||
if ($request->get_method() === 'DELETE') {
|
||||
wp_delete_post($id, true);
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('幻灯片已删除', 'aksrt')]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
// Optional title: empty is allowed (UI shows “未命名”); do not 422.
|
||||
$title = sanitize_text_field($params['title'] ?? '');
|
||||
|
||||
wp_update_post([
|
||||
'ID' => $id,
|
||||
'post_title' => $title,
|
||||
]);
|
||||
|
||||
update_post_meta($id, '_aksrt_kind', 'banner');
|
||||
update_post_meta($id, '_subtitle', sanitize_text_field($params['subtitle'] ?? ''));
|
||||
update_post_meta($id, '_description', sanitize_textarea_field($params['description'] ?? ''));
|
||||
update_post_meta($id, '_image_url', esc_url_raw($params['image_url'] ?? ''));
|
||||
update_post_meta($id, '_video_url', esc_url_raw($params['video_url'] ?? ''));
|
||||
update_post_meta($id, '_link_url', esc_url_raw($params['link_url'] ?? ''));
|
||||
update_post_meta($id, '_link_target', in_array($params['link_target'] ?? '', ['_blank', '_self'], true) ? $params['link_target'] : '_self');
|
||||
update_post_meta($id, '_text_align', in_array($params['text_align'] ?? '', ['left', 'center', 'right'], true) ? $params['text_align'] : 'left');
|
||||
update_post_meta($id, '_device_visibility', in_array($params['device_visibility'] ?? '', ['all', 'pc', 'mobile'], true) ? $params['device_visibility'] : 'all');
|
||||
update_post_meta($id, '_enabled', !array_key_exists('enabled', $params) || !empty($params['enabled']) ? '1' : '0');
|
||||
update_post_meta($id, '_show_text', !array_key_exists('show_text', $params) || !empty($params['show_text']) ? '1' : '0');
|
||||
update_post_meta($id, '_show_meta', !empty($params['show_meta']) ? '1' : '0');
|
||||
update_post_meta($id, '_text_size_pc', sanitize_text_field($params['text_size_pc'] ?? ''));
|
||||
update_post_meta($id, '_text_size_m', sanitize_text_field($params['text_size_m'] ?? ''));
|
||||
update_post_meta($id, '_text_parallax', !empty($params['text_parallax']) ? '1' : '0');
|
||||
|
||||
if (isset($params['layers']) && is_array($params['layers'])) {
|
||||
update_post_meta($id, '_layers', wp_json_encode(array_values($params['layers'])));
|
||||
} else {
|
||||
delete_post_meta($id, '_layers');
|
||||
}
|
||||
|
||||
if (isset($params['sort_order'])) {
|
||||
wp_update_post([
|
||||
'ID' => $id,
|
||||
'menu_order' => (int) $params['sort_order'],
|
||||
]);
|
||||
}
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('幻灯片已更新', 'aksrt')]);
|
||||
}
|
||||
$taxonomy = $source === 'categories' ? 'category' : 'post_tag';
|
||||
$terms = get_terms([
|
||||
'taxonomy' => $taxonomy,
|
||||
'hide_empty' => false,
|
||||
'number' => 20,
|
||||
'search' => $query,
|
||||
'include' => $include,
|
||||
'orderby' => $include ? 'include' : 'name',
|
||||
]);
|
||||
if (is_wp_error($terms)) {
|
||||
return $this->error($terms->get_error_message(), 400);
|
||||
}
|
||||
$items = array_map(static function ($term): array {
|
||||
return ['label' => $term->name, 'value' => (int) $term->term_id];
|
||||
}, $terms);
|
||||
return $this->success(['data' => $items]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,25 +331,16 @@ class GlobalSchema {
|
||||
['key' => '404_search_s', 'label' => '在 404 页面显示搜索功能', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'search_posts', 'label' => '搜索框热门文章', 'type' => 'switch', 'default' => true, 'help' => '在搜索面板中显示热门文章列表'],
|
||||
['key' => 'search_no_page', 'label' => '排除页面', 'type' => 'switch', 'default' => true, 'help' => '搜索结果中排除页面(仅显示文章)'],
|
||||
['key' => 'search_exclude_cats', 'label' => '排除分类', 'type' => 'taxonomy_select', 'taxonomy' => ['category', 'shop_cat'], 'multiple' => true, 'default' => [], 'help' => '搜索结果中排除的文章或商品分类'],
|
||||
['key' => 'search_exclude_cats', 'label' => '排除分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => [], 'help' => '搜索结果中排除的文章分类'],
|
||||
['key' => 'search_custom_key_s', 'label' => '自定义匹配范围', 'type' => 'switch', 'default' => true, 'help' => '启用后可配置搜索匹配字段范围(标题/内容/分类/标签等)'],
|
||||
['key' => 'search_custom_key', 'label' => '匹配字段', 'type' => 'checkbox', 'default' => ['title', 'content', 'excerpt', 'post_tag', 'forum_topic', 'shop_cat', 'shop_discount'], 'options' => [
|
||||
['key' => 'search_custom_key', 'label' => '匹配字段', 'type' => 'checkbox', 'default' => ['title', 'content', 'excerpt', 'category', 'post_tag'], 'options' => [
|
||||
['label' => '标题', 'value' => 'title'], ['label' => '内容', 'value' => 'content'], ['label' => '简介', 'value' => 'excerpt'],
|
||||
['label' => '分类', 'value' => 'category'], ['label' => '标签', 'value' => 'post_tag'], ['label' => '专题', 'value' => 'topics'],
|
||||
['label' => '分类', 'value' => 'category'], ['label' => '标签', 'value' => 'post_tag'],
|
||||
['label' => '评论', 'value' => 'coment'], ['label' => '作者昵称', 'value' => 'user'],
|
||||
['label' => '[帖子]话题', 'value' => 'forum_topic'], ['label' => '[帖子]标签', 'value' => 'forum_tag'],
|
||||
['label' => '[商品]分类', 'value' => 'shop_cat'], ['label' => '[商品]特色标签', 'value' => 'shop_tag'], ['label' => '[商品]优惠活动', 'value' => 'shop_discount'],
|
||||
]],
|
||||
['key' => 'search_facets', 'label' => '搜索筛选', 'type' => 'fieldset', 'sanitize_type' => 'array', 'default' => [], 'dependsOn' => ['key' => 'search_ms_s', 'value' => false], 'help' => '搜索结果中显示的筛选列表;启用 Meilisearch 后由搜索引擎生成关联筛选。', 'fields' => [
|
||||
['key' => 'category', 'label' => '[文章] 分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => []],
|
||||
['key' => 'topics', 'label' => '[文章] 专题', 'type' => 'taxonomy_select', 'taxonomy' => 'topics', 'multiple' => true, 'default' => []],
|
||||
['key' => 'post_tag', 'label' => '[文章] 标签', 'type' => 'taxonomy_select', 'taxonomy' => 'post_tag', 'multiple' => true, 'default' => []],
|
||||
['key' => 'plate_id', 'label' => '[论坛帖子] 板块 ID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'forum_topic', 'label' => '[论坛帖子] 话题', 'type' => 'taxonomy_select', 'taxonomy' => 'forum_topic', 'multiple' => true, 'default' => []],
|
||||
['key' => 'forum_tag', 'label' => '[论坛帖子] 标签', 'type' => 'taxonomy_select', 'taxonomy' => 'forum_tag', 'multiple' => true, 'default' => []],
|
||||
['key' => 'shop_cat', 'label' => '[商品] 分类', 'type' => 'taxonomy_select', 'taxonomy' => 'shop_cat', 'multiple' => true, 'default' => []],
|
||||
['key' => 'shop_discount', 'label' => '[商品] 优惠活动', 'type' => 'taxonomy_select', 'taxonomy' => 'shop_discount', 'multiple' => true, 'default' => []],
|
||||
['key' => 'shop_tag', 'label' => '[商品] 特色标签', 'type' => 'taxonomy_select', 'taxonomy' => 'shop_tag', 'multiple' => true, 'default' => []],
|
||||
]],
|
||||
]],
|
||||
['key' => 'category-search', 'title' => '分类搜索', 'fields' => [
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
* Zibll V9.0 page/display settings contract.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
@@ -9,478 +11,204 @@ defined('ABSPATH') || exit;
|
||||
|
||||
class SchemaPageDisplay {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'domain' => 'page_display',
|
||||
'title' => '页面&显示',
|
||||
'sections' => [
|
||||
'header-nav' => [
|
||||
'key' => 'header-nav',
|
||||
'title' => '顶部导航栏',
|
||||
'version' => '1.1.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'layout', 'title' => '导航栏布局', 'fields' => [
|
||||
['key' => 'nav_fixed', 'label' => '导航栏一直固定在顶部', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'nav_scroll_down_hide', 'label' => '下滚动隐藏,上滚动显示', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'nav_fixed', 'value' => true]],
|
||||
]],
|
||||
['key' => 'layout-type', 'title' => '布局类型', 'fields' => [
|
||||
['key' => 'header_layout', 'label' => '桌面端布局', 'type' => 'image_select', 'default' => '1', 'options' => self::header_layout_options()],
|
||||
['key' => 'mobile_header_layout', 'label' => '移动端布局', 'type' => 'image_select', 'default' => 'center', 'options' => self::mobile_header_layout_options()],
|
||||
['key' => 'mobile_navbar_align', 'label' => '移动端菜单方向', 'type' => 'radio', 'default' => 'left', 'options' => [
|
||||
['label' => '顶部(关闭手势)', 'value' => 'top'],
|
||||
['label' => '左侧滑出', 'value' => 'left'],
|
||||
['label' => '右侧滑出', 'value' => 'right'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'components', 'title' => '导航组件', 'fields' => [
|
||||
['key' => 'nav_pay_vip', 'label' => '导航栏开通会员按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'nav_user_pay_vip', 'label' => '用户框开通会员按钮', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'newposts', 'title' => '发布按钮', 'fields' => [
|
||||
['key' => 'nav_newposts', 'label' => '启用发布按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'nav_newposts_text', 'label' => '按钮文案', 'type' => 'input', 'default' => '<i class="fa fas fa-fw fa-pen"></i>发布', 'dependsOn' => ['key' => 'nav_newposts', 'value' => true]],
|
||||
['key' => 'nav_newposts_radius', 'label' => '按钮圆角(胶囊)', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'nav_newposts', 'value' => true]],
|
||||
['key' => 'nav_newposts_class', 'label' => '按钮颜色', 'type' => 'select', 'default' => 'jb-blue', 'dependsOn' => ['key' => 'nav_newposts', 'value' => true], 'options' => [
|
||||
['label' => '蓝色', 'value' => 'jb-blue'],
|
||||
['label' => '红色', 'value' => 'jb-red'],
|
||||
['label' => '绿色', 'value' => 'jb-green'],
|
||||
['label' => '黄色', 'value' => 'jb-yellow'],
|
||||
['label' => '紫色', 'value' => 'jb-purple'],
|
||||
['label' => '粉色', 'value' => 'jb-pink'],
|
||||
]],
|
||||
['key' => 'nav_newposts_btns', 'label' => '需显示的按钮', 'type' => 'checkbox', 'default' => ['post', 'bbs_topic', 'bbs_plate', 'bbs_posts'], 'dependsOn' => ['key' => 'nav_newposts', 'value' => true], 'options' => [
|
||||
['label' => '文章', 'value' => 'post'],
|
||||
['label' => '论坛话题', 'value' => 'bbs_topic'],
|
||||
['label' => '论坛版块', 'value' => 'bbs_plate'],
|
||||
['label' => '论坛帖子', 'value' => 'bbs_posts'],
|
||||
]],
|
||||
['key' => 'nav_user_newposts_btn', 'label' => '用户卡发布类型', 'type' => 'select', 'default' => 'post', 'options' => [
|
||||
['label' => '文章', 'value' => 'post'],
|
||||
]],
|
||||
['key' => 'nav_user_newposts_btn_text', 'label' => '用户卡发布文案', 'type' => 'input', 'default' => '发布文章'],
|
||||
]],
|
||||
['key' => 'color', 'title' => '颜色设置', 'fields' => [
|
||||
['key' => 'header_theme_custom', 'label' => '自定义导航栏颜色', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'header_theme_bg_custom', 'label' => '自定义背景色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'header_theme_bg', 'label' => '背景颜色(备选)', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'header_theme_color_custom', 'label' => '自定义文字颜色', 'type' => 'color', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'header-components' => [
|
||||
'key' => 'header-components',
|
||||
'title' => '顶部多功能组件',
|
||||
'description' => 'Zibll 顶部幻灯、搜索覆盖层与图标卡片的原始字段契约。',
|
||||
'version' => '2.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'visibility', 'title' => '显示规则', 'fields' => [
|
||||
['key' => 'header_slider_show', 'label' => '启用顶部多功能组件的页面', 'type' => 'checkbox', 'default' => [], 'options' => [
|
||||
['label' => '首页', 'value' => 'home'],
|
||||
['label' => '分类页', 'value' => 'category'],
|
||||
['label' => '标签页', 'value' => 'tag'],
|
||||
['label' => '用户页', 'value' => 'author'],
|
||||
['label' => '文章页', 'value' => 'single'],
|
||||
['label' => '搜索页', 'value' => 'search'],
|
||||
['label' => '其它页面', 'value' => 'page'],
|
||||
['label' => '专题页', 'value' => 'topics'],
|
||||
['label' => '论坛首页', 'value' => 'forum_home'],
|
||||
['label' => '论坛版块', 'value' => 'forum_plate'],
|
||||
['label' => '论坛帖子', 'value' => 'forum_post'],
|
||||
]],
|
||||
['key' => 'header_slider_show_type', 'label' => '显示规则', 'type' => 'radio', 'default' => '', 'dependsOn' => ['key' => 'header_slider_show', 'not' => []], 'options' => [
|
||||
['label' => '全部', 'value' => ''],
|
||||
['label' => '仅电脑', 'value' => 'only_pc'],
|
||||
['label' => '仅移动', 'value' => 'only_sm'],
|
||||
]],
|
||||
['key' => 'header_slider_nav_color', 'label' => '导航栏文字颜色', 'type' => 'color', 'default' => '#ffffff', 'dependsOn' => ['key' => 'header_slider_show', 'not' => []]],
|
||||
]],
|
||||
['key' => 'slides', 'title' => '幻灯片', 'fields' => [
|
||||
['key' => 'header_slider', 'label' => '幻灯片项目', 'type' => 'repeater', 'sanitize_type' => 'array', 'default' => self::header_slider_defaults(), 'addText' => '添加幻灯片', 'fields' => [
|
||||
['key' => 'background', 'label' => '背景图片', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'background_video', 'label' => '视频背景', 'type' => 'media', 'default' => '', 'help' => 'PC 端视频优先;移动端仅显示背景图片。'],
|
||||
['key' => 'video', 'label' => '视频内容', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'hide', 'label' => '隐藏设备', 'type' => 'radio', 'default' => '', 'options' => [
|
||||
['label' => '不隐藏', 'value' => ''],
|
||||
['label' => 'PC 端不显示', 'value' => 'pc'],
|
||||
['label' => '移动端不显示', 'value' => 'm'],
|
||||
]],
|
||||
['key' => 'link', 'label' => '跳转链接', 'type' => 'json', 'sanitize_type' => 'array', 'default' => ['url' => '', 'target' => '', 'text' => '']],
|
||||
['key' => 'image_layer', 'label' => '叠加图层', 'type' => 'json', 'sanitize_type' => 'array', 'default' => [], 'help' => '每层支持 image、free_size、align、parallax、parallax_scale、parallax_opacity。'],
|
||||
['key' => 'text', 'label' => '叠加文案', 'type' => 'json', 'sanitize_type' => 'array', 'default' => ['title' => '', 'desc' => '', 'text_align' => 'left-bottom', 'text_size_pc' => 30, 'text_size_m' => 20, 'parallax' => 40]],
|
||||
]],
|
||||
['key' => 'header_slider_option', 'label' => '幻灯片参数', 'type' => 'json', 'sanitize_type' => 'array', 'default' => self::header_slider_option_defaults()],
|
||||
]],
|
||||
['key' => 'search', 'title' => '搜索组件', 'fields' => [
|
||||
['key' => 'header_slider_search_s', 'label' => '显示搜索组件', 'type' => 'checkbox', 'default' => ['pc_s', 'm_s'], 'options' => self::device_options()],
|
||||
['key' => 'header_slider_search_option', 'label' => '搜索组件参数', 'type' => 'json', 'sanitize_type' => 'array', 'default' => self::header_slider_search_defaults()],
|
||||
]],
|
||||
['key' => 'cards', 'title' => '图标卡片组件', 'fields' => [
|
||||
['key' => 'header_slider_card_s', 'label' => '显示图标卡片', 'type' => 'checkbox', 'default' => ['pc_s', 'm_s'], 'options' => self::device_options()],
|
||||
['key' => 'header_slider_card_option', 'label' => '图标卡片参数', 'type' => 'json', 'sanitize_type' => 'array', 'default' => self::header_slider_card_defaults()],
|
||||
['key' => 'header_slider_filter_blur', 'label' => '组件高斯模糊特效', 'type' => 'checkbox', 'default' => ['pc_s', 'm_s'], 'options' => self::device_options()],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'category' => [
|
||||
'key' => 'category',
|
||||
'title' => '分类页面',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'header', 'title' => '分类头图', 'fields' => [
|
||||
['key' => 'category_header_style', 'label' => '头部风格', 'type' => 'radio', 'default' => 'cover', 'options' => [
|
||||
['label' => '封面图', 'value' => 'cover'],
|
||||
['label' => '简洁标题', 'value' => 'simple'],
|
||||
['label' => '隐藏', 'value' => 'none'],
|
||||
]],
|
||||
['key' => 'category_cover_enabled', 'label' => '显示封面图', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'category_header_style', 'value' => 'cover']],
|
||||
['key' => 'category_desc_enabled', 'label' => '显示分类简介', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'category_default_cover', 'label' => '默认封面图', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
['key' => 'list', 'title' => '列表设置', 'fields' => [
|
||||
['key' => 'category_list_mode', 'label' => '列表模式', 'type' => 'radio', 'default' => 'grid', 'options' => [
|
||||
['label' => '网格', 'value' => 'grid'],
|
||||
['label' => '列表', 'value' => 'list'],
|
||||
['label' => '紧凑', 'value' => 'compact'],
|
||||
]],
|
||||
['key' => 'category_per_page', 'label' => '每页文章数', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 48],
|
||||
['key' => 'category_filter_bar', 'label' => '筛选条', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'category_sidebar', 'label' => '侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'category_post_count', 'label' => '显示文章数', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'orderby', 'title' => '排序设置', 'fields' => [
|
||||
['key' => 'category_orderby', 'label' => '默认排序', 'type' => 'select', 'default' => 'modified', 'options' => [
|
||||
['label' => '最近更新', 'value' => 'modified'],
|
||||
['label' => '发布时间', 'value' => 'date'],
|
||||
['label' => '浏览数', 'value' => 'views'],
|
||||
['label' => '评论数', 'value' => 'comments'],
|
||||
['label' => '点赞数', 'value' => 'likes'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'tag' => [
|
||||
'key' => 'tag',
|
||||
'title' => '标签页面',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'header', 'title' => '标签头图', 'fields' => [
|
||||
['key' => 'tag_header_style', 'label' => '头部风格', 'type' => 'radio', 'default' => 'simple', 'options' => [
|
||||
['label' => '简洁标题', 'value' => 'simple'],
|
||||
['label' => '封面图', 'value' => 'cover'],
|
||||
['label' => '隐藏', 'value' => 'none'],
|
||||
]],
|
||||
['key' => 'tag_default_cover', 'label' => '默认封面图', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
['key' => 'list', 'title' => '列表设置', 'fields' => [
|
||||
['key' => 'tag_list_mode', 'label' => '列表模式', 'type' => 'radio', 'default' => 'grid', 'options' => [
|
||||
['label' => '网格', 'value' => 'grid'],
|
||||
['label' => '列表', 'value' => 'list'],
|
||||
]],
|
||||
['key' => 'tag_per_page', 'label' => '每页文章数', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 48],
|
||||
['key' => 'tag_sidebar', 'label' => '侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'tag_post_count', 'label' => '显示文章数', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'orderby', 'title' => '排序设置', 'fields' => [
|
||||
['key' => 'tag_orderby', 'label' => '默认排序', 'type' => 'select', 'default' => 'count', 'options' => [
|
||||
['label' => '文章数', 'value' => 'count'],
|
||||
['label' => '名称', 'value' => 'name'],
|
||||
['label' => '最近更新', 'value' => 'modified'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'user-home' => [
|
||||
'key' => 'user-home',
|
||||
'title' => '用户主页',
|
||||
'version' => '1.1.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'layout', 'title' => '主页布局', 'fields' => [
|
||||
['key' => 'user_home_route_identifier', 'label' => '主页 URL 标识', 'type' => 'select', 'default' => 'nicename', 'options' => [['label' => '用户名 URL slug', 'value' => 'nicename'], ['label' => '数字用户 ID', 'value' => 'uid']]],
|
||||
['key' => 'user_home_cover', 'label' => '封面区域', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_sidebar', 'label' => '侧边栏(已弃用)', 'type' => 'switch', 'default' => false, 'help' => '公开主页对齐 Zibll 作者页为通栏布局;交易/快捷入口仅在用户中心展示。此开关保留兼容,运行时强制关闭。'],
|
||||
['key' => 'user_home_follow', 'label' => '关注按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_posts_per_page', 'label' => '每页文章数', 'type' => 'number', 'default' => 10, 'min' => 5, 'max' => 50],
|
||||
['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'],
|
||||
['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' => ''],
|
||||
['key' => 'author_main_tab_swiper', 'label' => '移动端滑动切换', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'user_home_avatar', 'label' => '显示头像', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_name', 'label' => '显示用户名', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_desc', 'label' => '显示个人简介', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_stats', 'label' => '显示统计数据(已弃用)', 'type' => 'switch', 'default' => false, 'help' => '计数已并入 Tab 角标(对齐 Zibll),头图内不再重复展示文章/评论/收藏。'],
|
||||
['key' => 'user_home_social', 'label' => '显示社交链接', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_message', 'label' => '私信/消息按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_private', 'label' => '留言按钮', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'other' => [
|
||||
'key' => 'other',
|
||||
'title' => '其它页面',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'page-header', 'title' => '统一页头', 'fields' => [
|
||||
['key' => 'page_header_style', 'label' => '页面标题样式', 'type' => 'select', 'default' => '1', 'options' => [
|
||||
['label' => '不显示', 'value' => ''],
|
||||
['label' => '简单', 'value' => '1'],
|
||||
['label' => '卡片', 'value' => '2'],
|
||||
['label' => '封面', 'value' => '3'],
|
||||
]],
|
||||
['key' => 'page_header_cover_img', 'label' => '默认封面图', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
['key' => 'page_404', 'title' => '404 页面', 'fields' => [
|
||||
['key' => 'page_404_message', 'label' => '404 提示文字', 'type' => 'input', 'default' => '页面未找到'],
|
||||
['key' => 'page_404_search', 'label' => '显示搜索框', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'page_404_popular', 'label' => '显示热门文章', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'page_404_back', 'label' => '显示返回首页', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'search_results', 'title' => '搜索结果页', 'fields' => [
|
||||
['key' => 'page_search_sidebar', 'label' => '搜索结果侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'page_search_per_page', 'label' => '每页结果数', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 48],
|
||||
]],
|
||||
['key' => 'author', 'title' => '作者页面', 'fields' => [
|
||||
['key' => 'page_author_cover', 'label' => '显示封面', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'page_author_sidebar', 'label' => '显示侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'page_author_posts_per_page', 'label' => '每页文章数', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 48],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'background' => [
|
||||
'key' => 'background',
|
||||
'title' => '全局背景',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'light', 'title' => '亮色背景', 'fields' => [
|
||||
['key' => 'bg_color', 'label' => '背景颜色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'bg_image', 'label' => '背景图片', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'bg_size', 'label' => '图片尺寸', 'type' => 'select', 'default' => 'cover', 'options' => [
|
||||
['label' => '覆盖', 'value' => 'cover'],
|
||||
['label' => '包含', 'value' => 'contain'],
|
||||
['label' => '原始大小', 'value' => 'auto'],
|
||||
['label' => '拉伸填充', 'value' => '100% 100%'],
|
||||
['label' => '100% 宽度(旧版兼容)', 'value' => '100%'],
|
||||
]],
|
||||
['key' => 'bg_position', 'label' => '图片位置', 'type' => 'select', 'default' => 'center center', 'options' => [
|
||||
['label' => '居中', 'value' => 'center center'],
|
||||
['label' => '顶部居中', 'value' => 'top center'],
|
||||
['label' => '底部居中', 'value' => 'bottom center'],
|
||||
]],
|
||||
['key' => 'bg_repeat', 'label' => '图片重复', 'type' => 'select', 'default' => 'no-repeat', 'options' => [
|
||||
['label' => '不重复', 'value' => 'no-repeat'],
|
||||
['label' => '平铺', 'value' => 'repeat'],
|
||||
['label' => '水平平铺', 'value' => 'repeat-x'],
|
||||
['label' => '垂直平铺', 'value' => 'repeat-y'],
|
||||
]],
|
||||
['key' => 'bg_attachment', 'label' => '固定方式', 'type' => 'select', 'default' => 'fixed', 'options' => [
|
||||
['label' => '固定', 'value' => 'fixed'],
|
||||
['label' => '随内容滚动', 'value' => 'scroll'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'dark', 'title' => '暗色背景', 'fields' => [
|
||||
['key' => 'bg_dark_color', 'label' => '暗色背景颜色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'bg_dark_image', 'label' => '暗色背景图片', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
['key' => 'overlay', 'title' => '遮罩设置', 'fields' => [
|
||||
['key' => 'bg_overlay', 'label' => '启用背景遮罩', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'bg_overlay_color', 'label' => '遮罩颜色', 'type' => 'input', 'default' => 'rgba(0, 0, 0, 0.3)'],
|
||||
]],
|
||||
['key' => 'page', 'title' => '页面背景', 'fields' => [
|
||||
['key' => 'page_bg_color', 'label' => '内容区背景色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'page_bg_image', 'label' => '内容区背景图', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'footer' => [
|
||||
'key' => 'footer',
|
||||
'title' => '底部页脚',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'layout', 'title' => '页脚布局', 'fields' => [
|
||||
['key' => 'footer_columns', 'label' => '页脚列数', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 4],
|
||||
['key' => 'footer_copyright', 'label' => '显示版权信息', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_icp', 'label' => '显示ICP备案', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_police', 'label' => '显示公安备案', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'color', 'title' => '页脚颜色', 'fields' => [
|
||||
['key' => 'footer_theme_custom', 'label' => '自定义页脚颜色', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'footer_theme_bg_custom', 'label' => '自定义背景色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'footer_theme_bg', 'label' => '背景图片', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'footer_theme_color_custom', 'label' => '自定义文字颜色', 'type' => 'color', 'default' => ''],
|
||||
]],
|
||||
['key' => 'contact', 'title' => '联系方式', 'fields' => [
|
||||
['key' => 'footer_contact_wechat_img', 'label' => '微信二维码', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'footer_contact_qq', 'label' => 'QQ', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'footer_contact_weibo', 'label' => '微博', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'footer_contact_email', 'label' => '邮箱', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'brand', 'title' => '品牌列(第一列)', 'fields' => [
|
||||
['key' => 'footer_t1_m_s', 'label' => '显示设备', 'type' => 'checkbox', 'default' => ['pc', 'mobile'], 'options' => [
|
||||
['label' => 'PC', 'value' => 'pc'],
|
||||
['label' => '移动端', 'value' => 'mobile'],
|
||||
]],
|
||||
['key' => 'footer_t1_img', 'label' => '页脚 Logo(日间)', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'footer_t1_img_dark', 'label' => '页脚 Logo(夜间)', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'footer_t1_t', 'label' => '品牌标题', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'fcode_t1_code', 'label' => '品牌列自定义代码', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'contact_ext', 'title' => '联系方式扩展', 'fields' => [
|
||||
['key' => 'footer_contact_m_s', 'label' => '显示设备', 'type' => 'checkbox', 'default' => ['pc', 'mobile'], 'options' => [
|
||||
['label' => 'PC', 'value' => 'pc'],
|
||||
['label' => '移动端', 'value' => 'mobile'],
|
||||
]],
|
||||
['key' => 'footer_contact_more', 'label' => '更多联系项', 'type' => 'textarea', 'default' => '', 'help' => 'JSON 数组,每项含 desc/icon/link/img'],
|
||||
['key' => 'fcode_t2_code_1', 'label' => '链接 HTML(fcode_t2_code_1)', 'type' => 'textarea', 'default' => '', 'help' => '直接写 <a> 链接 HTML;为空时回退快速链接/友链/热门分类'],
|
||||
['key' => 'fcode_t2_code_2', 'label' => '链接下方自定义代码', 'type' => 'textarea', 'default' => '', 'help' => '备案号等 HTML'],
|
||||
]],
|
||||
['key' => 'mini_qr', 'title' => '二维码图片组(第三列)', 'fields' => [
|
||||
['key' => 'footer_mini_img_m_s', 'label' => '显示设备', 'type' => 'checkbox', 'default' => ['pc', 'mobile'], 'options' => [
|
||||
['label' => 'PC', 'value' => 'pc'],
|
||||
['label' => '移动端', 'value' => 'mobile'],
|
||||
]],
|
||||
['key' => 'footer_mini_img', 'label' => '二维码组', 'type' => 'textarea', 'default' => '', 'help' => 'JSON 数组,每项含 text/img(最多4个)'],
|
||||
]],
|
||||
['key' => 'custom', 'title' => '底部自定义', 'fields' => [
|
||||
['key' => 'fcode_customize_code', 'label' => '底部自定义代码', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'tabbar' => [
|
||||
'key' => 'tabbar',
|
||||
'title' => '底部Tab栏',
|
||||
'version' => '1.1.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => 'Tab栏设置', 'fields' => [
|
||||
['key' => 'footer_tabbar_s', 'label' => '启用底部Tab栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_scroll_hide', 'label' => '滚动时隐藏', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'footer_tabbar_scroll_down_hide', 'label' => '下滑时隐藏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_max_items', 'label' => '最大Tab数', 'type' => 'number', 'default' => 5, 'min' => 3, 'max' => 7],
|
||||
['key' => 'footer_tabbar_icon_size', 'label' => '图标大小', 'type' => 'number', 'default' => 22, 'min' => 16, 'max' => 32],
|
||||
['key' => 'footer_tabbar_text_size', 'label' => '文字大小', 'type' => 'number', 'default' => 10, 'min' => 8, 'max' => 14],
|
||||
['key' => 'footer_tabbar_show_text', 'label' => '显示文字', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_active_color', 'label' => '选中颜色', 'type' => 'color', 'default' => '#f04494'],
|
||||
['key' => 'footer_tabbar_bg_color', 'label' => '背景颜色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'footer_tabbar_border', 'label' => '显示上边框', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_shadow', 'label' => '显示阴影', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_animation', 'label' => '切换动画', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_double_click_home', 'label' => '双击首页刷新', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_badge_dot', 'label' => '徽标圆点样式', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'footer_tabbar_single', 'label' => '文章页专属Tab', 'type' => 'select', 'default' => 'default', 'options' => [
|
||||
['label' => '默认按钮', 'value' => 'default'],
|
||||
['label' => '文章扩展按钮(评论/收藏/分享)', 'value' => 'extend'],
|
||||
], 'help' => '文章详情页底部显示的Tab按钮类型'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/** @return list<array{label:string,value:string}> */
|
||||
private static function device_options(): array {
|
||||
public static function build(): array {
|
||||
return [
|
||||
['label' => '电脑端', 'value' => 'pc_s'],
|
||||
['label' => '移动端', 'value' => 'm_s'],
|
||||
];
|
||||
}
|
||||
|
||||
/** @return list<array<string,mixed>> */
|
||||
private static function header_slider_defaults(): array {
|
||||
$base = defined('AKSRT_URI') ? trailingslashit(AKSRT_URI) . 'assets/vendor/zibll/img/' : '';
|
||||
$slides = [];
|
||||
foreach (['slider-bg.jpg', 'user_t.jpg'] as $image) {
|
||||
$slides[] = [
|
||||
'background' => $base . $image,
|
||||
'link' => ['url' => 'https://www.zibll.com/', 'target' => '_blank'],
|
||||
'text' => ['desc' => '', 'title' => ''],
|
||||
'text_align' => 'left-bottom',
|
||||
'text_parallax' => 30,
|
||||
'text_size_m' => 20,
|
||||
'text_size_pc' => 30,
|
||||
];
|
||||
}
|
||||
return $slides;
|
||||
}
|
||||
|
||||
/** @return array<string,mixed> */
|
||||
private static function header_slider_option_defaults(): array {
|
||||
return [
|
||||
'direction' => 'horizontal',
|
||||
'loop' => true,
|
||||
'button' => true,
|
||||
'pagination' => true,
|
||||
'effect' => 'slide',
|
||||
'scale_height' => false,
|
||||
'auto_height' => false,
|
||||
'pc_height' => 550,
|
||||
'm_height' => 280,
|
||||
'spacebetween' => 0,
|
||||
'speed' => 0,
|
||||
'autoplay' => true,
|
||||
'interval' => 6,
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string,mixed> */
|
||||
private static function header_slider_search_defaults(): array {
|
||||
return [
|
||||
'show_keywords' => true,
|
||||
'popular_limit' => 6,
|
||||
'show_input_cat' => false,
|
||||
'in_cat' => '',
|
||||
'more_cats' => [],
|
||||
'show_type' => false,
|
||||
'in_type' => 'post',
|
||||
'before_html' => '<div class="em16 font-bold mb10">更优雅的WordPress主题</div><div>这是导航栏幻灯片多功能组件,可在后台进行配置</div>',
|
||||
'after_html' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string,mixed> */
|
||||
private static function header_slider_card_defaults(): array {
|
||||
return [
|
||||
'show_widget_bg' => false,
|
||||
'icon_radius4' => false,
|
||||
'cards' => [
|
||||
['icon' => 'zibsvg-vip_1', 'customize_icon' => '', 'icon_class' => 'c-yellow', 'link' => [], 'title' => '图标卡片', 'desc' => '这是一个图标卡片示例'],
|
||||
['icon' => 'zibsvg-vip_2', 'customize_icon' => '', 'icon_class' => 'c-blue-2', 'link' => [], 'title' => '原创作品', 'desc' => '这是一个图标卡片示例'],
|
||||
['icon' => 'zibsvg-hot', 'customize_icon' => '', 'icon_class' => 'jb-pink', 'link' => [], 'title' => '灵感来源<badge class="ml6 jb-yellow">NEW</badge>', 'desc' => '这是一个图标卡片示例'],
|
||||
['icon' => 'fa fas fa-ioxhost', 'customize_icon' => '', 'icon_class' => 'jb-cyan', 'link' => [], 'title' => '系统工具 <badge class="ml6 jb-blue">GO <span class="fa fas fa-angle-right em12"></span></badge>', 'desc' => '这是一个图标卡片示例'],
|
||||
'domain' => 'page_display',
|
||||
'title' => '页面&显示',
|
||||
'sections' => [
|
||||
'header-nav' => self::header_nav(),
|
||||
'header-components' => self::header_components(),
|
||||
'footer' => self::footer(),
|
||||
'homepage' => self::homepage(),
|
||||
'category' => self::category(),
|
||||
'tag' => self::tag(),
|
||||
'user-home' => self::user_home(),
|
||||
'other' => self::other(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/** @return list<array{label:string,value:string,image:string}> */
|
||||
private static function header_layout_options(): array {
|
||||
$base = defined('AKSRT_URI') ? trailingslashit(AKSRT_URI) . 'assets/images/header-layout/' : '';
|
||||
return [
|
||||
['label' => '布局 1(经典)', 'value' => '1', 'image' => $base . 'header_layout_1.png'],
|
||||
['label' => '布局 2(内联搜索)', 'value' => '2', 'image' => $base . 'header_layout_2.png'],
|
||||
['label' => '布局 3(紧凑)', 'value' => '3', 'image' => $base . 'header_layout_3.png'],
|
||||
];
|
||||
}
|
||||
private static function section(string $key, string $title, array $fields): array {
|
||||
return [
|
||||
'key' => $key,
|
||||
'title' => $title,
|
||||
'version' => '9.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [['key' => $key, 'fields' => $fields]],
|
||||
];
|
||||
}
|
||||
|
||||
/** @return list<array{label:string,value:string,image:string}> */
|
||||
private static function mobile_header_layout_options(): array {
|
||||
$base = defined('AKSRT_URI') ? trailingslashit(AKSRT_URI) . 'assets/images/header-layout/' : '';
|
||||
return [
|
||||
['label' => '居中(含搜索按钮)', 'value' => 'center', 'image' => $base . 'mobile_header_layout_center.png'],
|
||||
['label' => '靠左', 'value' => 'left', 'image' => $base . 'mobile_header_layout_left.png'],
|
||||
];
|
||||
}
|
||||
private static function header_nav(): array {
|
||||
return self::section('header-nav', '顶部导航栏', [
|
||||
self::notice('添加或管理菜单后,可在此配置导航栏显示效果。'),
|
||||
self::field('header_theme_custom', '自定义导航栏颜色', 'switcher', false),
|
||||
self::field('header_theme_bg_custom', '导航栏:背景色', 'color', '', ['dependsOn' => self::enabled('header_theme_custom'), 'description' => '如需选择预置颜色,请先清空此颜色。']),
|
||||
self::field('header_theme_bg', '', 'palette', '', ['dependsOn' => ['all' => [self::enabled('header_theme_custom'), ['key' => 'header_theme_bg_custom', 'operator' => 'empty']]], 'options' => self::solid_palette()]),
|
||||
self::field('header_theme_color_custom', '导航栏:前景色', 'color', '', ['dependsOn' => self::enabled('header_theme_custom'), 'description' => '请注意背景色和前景色的搭配,以免文字看不清。']),
|
||||
self::field('header_layout', '电脑端导航布局', 'image_select', '1', ['options' => self::header_layout_options()]),
|
||||
self::field('mobile_header_layout', '移动端导航布局', 'image_select', 'center', ['options' => self::mobile_header_layout_options()]),
|
||||
self::field('mobile_navbar_align', '移动端菜单弹出方向', 'radio', 'left', ['inline' => true, 'options' => self::options(['top' => '顶部', 'left' => '左边', 'right' => '右边'])]),
|
||||
self::field('nav_fixed', '导航浮动', 'switcher', true, ['description' => '导航栏一直固定在顶部。']),
|
||||
self::field('nav_scroll_down_hide', '页面向下滚动时隐藏', 'switcher', true, ['dependsOn' => self::enabled('nav_fixed'), 'description' => '下滚动隐藏,上滚动显示。']),
|
||||
self::field('nav_newposts', 'Add发布按钮', 'switcher', true, ['description' => '顶部导航显示文章投稿按钮。']),
|
||||
self::field('nav_newposts_radius', '按钮圆角', 'switcher', true, ['dependsOn' => self::enabled('nav_newposts')]),
|
||||
self::field('nav_newposts_class', '按钮颜色', 'palette', 'jb-blue', ['dependsOn' => self::enabled('nav_newposts'), 'options' => self::zib_palette()]),
|
||||
self::field('nav_newposts_text', '按钮文案', 'textarea', '<i class="fa fa-fw fa-pencil"></i>发布', ['dependsOn' => self::enabled('nav_newposts'), 'sanitize_type' => 'html', 'rows' => 1]),
|
||||
self::field('nav_newposts_btns', '需显示的按钮', 'async_select', ['post'], ['dependsOn' => self::enabled('nav_newposts'), 'multiple' => true, 'sortable' => true, 'source' => 'publish-types', 'options' => self::options(['post' => '文章'])]),
|
||||
self::field('nav_user_newposts_btn', '导航栏用户卡片发布按钮', 'select', 'post', ['options' => self::options(['post' => '文章'])]),
|
||||
self::field('nav_user_newposts_btn_text', '按钮文字', 'text', '发布文章'),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function header_components(): array {
|
||||
return self::section('header-components', '顶部多功能组件', [
|
||||
self::notice('顶部多功能组件显示在导航栏下方,支持视频、图片、幻灯片、搜索和图标卡片;各组件可按电脑端、移动端单独启用。'),
|
||||
self::field('header_slider_show', '启用顶部多功能组件', 'checkbox', [], ['options' => self::options(['home' => '首页', 'category' => '分类页', 'tag' => '标签页', 'author' => '用户页', 'single' => '文章页', 'search' => '搜索页', 'page' => '其它页面'])]),
|
||||
self::field('header_slider_show_type', '顶部多功能组件的显示规则', 'radio', '', ['dependsOn' => self::not_empty('header_slider_show'), 'inline' => true, 'options' => self::options(['' => '全部显示', 'only_pc' => '仅在PC端显示', 'only_sm' => '仅在移动端显示'])]),
|
||||
self::field('header_slider_nav_color', '导航栏文字颜色', 'color', '#ffffff', ['dependsOn' => self::not_empty('header_slider_show')]),
|
||||
self::field('header_slider', '背景内容', 'group', self::header_slider_defaults(), ['dependsOn' => self::not_empty('header_slider_show'), 'min' => 1, 'button_title' => '添加背景项目', 'fields' => self::slider_item_fields()]),
|
||||
self::field('header_slider_option', '背景显示设置', 'fieldset', self::slider_options_defaults(), ['dependsOn' => self::not_empty('header_slider_show'), 'fields' => self::slider_option_fields()]),
|
||||
self::field('header_slider_search_s', '叠加搜索组件', 'checkbox', ['pc_s', 'm_s'], ['options' => self::device_options()]),
|
||||
self::field('header_slider_search_option', '搜索组件设置', 'fieldset', self::search_defaults(), ['dependsOn' => self::not_empty('header_slider_search_s'), 'fields' => self::search_fields()]),
|
||||
self::field('header_slider_card_s', '叠加封面卡片组件', 'checkbox', ['pc_s', 'm_s'], ['options' => self::device_options()]),
|
||||
self::field('header_slider_card_option', '图标卡片设置', 'fieldset', self::card_defaults(), ['dependsOn' => self::not_empty('header_slider_card_s'), 'fields' => self::card_fields()]),
|
||||
self::field('header_slider_filter_blur', '组件高斯模糊特效', 'checkbox', ['pc_s', 'm_s'], ['options' => self::options(['pc_s' => 'PC端开启', 'm_s' => '移动端开启'])]),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function footer(): array {
|
||||
$img = self::asset('assets/vendor/zibll/img/');
|
||||
return self::section('footer', '底部页脚', [
|
||||
self::field('footer_theme_custom', '自定义底部页脚颜色', 'switcher', false),
|
||||
self::field('footer_theme_bg_custom', '底部页脚:背景色', 'color', '', ['dependsOn' => self::enabled('footer_theme_custom')]),
|
||||
self::field('footer_theme_bg', '', 'palette', '', ['dependsOn' => ['all' => [self::enabled('footer_theme_custom'), ['key' => 'footer_theme_bg_custom', 'operator' => 'empty']]], 'options' => self::solid_palette()]),
|
||||
self::field('footer_theme_color_custom', '底部页脚:前景色', 'color', '', ['dependsOn' => self::enabled('footer_theme_custom')]),
|
||||
self::field('fcode_template', '页脚布局模板选择', 'image_select', 'template_1', ['options' => [['label' => '模板一', 'value' => 'template_1', 'image' => self::asset('assets/images/footer/fcode_template_1.png')]]]),
|
||||
self::field('footer_t1_m_s', '版块一移动端显示', 'switcher', false),
|
||||
self::field('footer_t1_img', '日间模式图片', 'upload', $img . 'logo.png'),
|
||||
self::field('footer_t1_img_dark', '夜间模式图片', 'upload', $img . 'logo_dark.png'),
|
||||
self::field('footer_t1_t', '首行文字', 'text', ''),
|
||||
self::field('fcode_t1_code', '更多内容', 'textarea', 'Zibll 子比主题专为博客、自媒体、资讯类的网站设计开发,简约优雅的设计风格,全面的前端用户功能,简单的模块化配置,欢迎您的体验', ['sanitize_type' => 'html', 'rows' => 3]),
|
||||
self::field('fcode_t2_code_1', '版块二第一行', 'textarea', '<a href="https://zibll.com">友链申请</a>' . "\n" . '<a href="https://zibll.com">免责声明</a>' . "\n" . '<a href="https://zibll.com">广告合作</a>' . "\n" . '<a href="https://zibll.com">关于我们</a>', ['sanitize_type' => 'html', 'rows' => 4]),
|
||||
self::field('fcode_t2_code_2', '版块二第二行', 'textarea', self::copyright_default(), ['sanitize_type' => 'html', 'rows' => 3]),
|
||||
self::field('footer_contact_m_s', '图标联系方式移动端显示', 'switcher', true),
|
||||
self::field('footer_contact_wechat_img', '微信二维码', 'upload', $img . 'qrcode.png'),
|
||||
self::field('footer_contact_qq', 'QQ号', 'text', '1234567788'),
|
||||
self::field('footer_contact_weibo', '微博链接', 'url', 'https://weibo.com/'),
|
||||
self::field('footer_contact_email', '邮箱', 'text', '1234567788@QQ.COM'),
|
||||
self::field('footer_contact_more', '更多图标联系方式', 'group', [], ['button_title' => '添加图标联系方式', 'fields' => [
|
||||
self::field('desc', '按钮名称', 'text', ''), self::field('icon', '按钮图标', 'icon', 'fa fa-heart'), self::field('link', '按钮链接', 'url', '', ['dependsOn' => ['key' => 'img', 'operator' => 'empty']]), self::field('img', '二维码图片', 'upload', ''),
|
||||
]]),
|
||||
self::field('footer_mini_img_m_s', '版块三移动端显示', 'switcher', true),
|
||||
self::field('footer_mini_img', '版块三大图片', 'group', [['image' => $img . 'qrcode.png', 'text' => '扫码加QQ群'], ['image' => $img . 'qrcode.png', 'text' => '扫码加微信']], ['max' => 4, 'button_title' => '添加图片', 'fields' => self::footer_image_fields()]),
|
||||
self::field('footer_mini_img_2', '版块三小图片', 'group', [], ['max' => 16, 'button_title' => '添加图片', 'fields' => self::footer_image_fields()]),
|
||||
self::field('fcode_customize_code', '页脚自定义HTML', 'textarea', '', ['sanitize_type' => 'html', 'rows' => 3]),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function homepage(): array {
|
||||
return self::section('homepage', '首页配置', [
|
||||
self::notice('首页主文章模块。关闭则首页不显示主文章模块,但仍可通过模块添加。'),
|
||||
self::field('home_posts_list_s', '首页文章', 'switcher', true),
|
||||
self::field('home_list1_orderby_s', '栏目1设置', 'switcher', true, ['dependsOn' => self::enabled('home_posts_list_s')]),
|
||||
self::field('home_list1_orderby_option', '排序方式设置', 'fieldset', '', ['dependsOn' => ['all' => [self::enabled('home_posts_list_s'), self::enabled('home_list1_orderby_s')]], 'fields' => self::orderby_fields()]),
|
||||
self::field('index_list_title', '显示标题', 'textarea', '', ['dependsOn' => self::enabled('home_posts_list_s'), 'sanitize_type' => 'html', 'rows' => 1]),
|
||||
self::field('home_exclude_posts', '排除文章', 'async_select', '', ['dependsOn' => self::enabled('home_posts_list_s'), 'source' => 'posts', 'multiple' => true, 'sortable' => true]),
|
||||
self::field('home_exclude_cats', '排除分类', 'async_select', '', ['dependsOn' => self::enabled('home_posts_list_s'), 'source' => 'categories', 'multiple' => true, 'sortable' => true]),
|
||||
self::field('home_lists', '更多栏目', 'group', [], ['dependsOn' => self::enabled('home_posts_list_s'), 'button_title' => '添加栏目', 'fields' => [self::field('title', '自定义标题', 'textarea', '', ['sanitize_type' => 'html', 'rows' => 1]), self::field('term_id', '显示内容', 'async_select', '', ['source' => 'categories']), self::field('orderby', '排序方式设置', 'select', '', ['options' => self::post_order_options(true)])]]),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function category(): array {
|
||||
return self::section('category', '分类页面', [
|
||||
self::field('page_cover_cat_s', '显示封面图', 'switcher', true),
|
||||
self::field('cat_post_count_s', '显示文章数量', 'switcher', true),
|
||||
self::field('cat_default_cover', '封面图', 'upload', self::asset('assets/vendor/zibll/img/user_t.jpg'), ['dependsOn' => self::enabled('page_cover_cat_s'), 'help' => '默认封面图,建议尺寸2000x800。']),
|
||||
self::field('cat_orderby_s', '排序方式按钮', 'switcher', true),
|
||||
self::field('cat_orderby_option', '排序方式设置', 'fieldset', [], ['dependsOn' => self::enabled('cat_orderby_s'), 'fields' => self::orderby_fields()]),
|
||||
self::notice('AJAX筛选菜单列表:显示分类、标签菜单,通过 AJAX 获取内容。'),
|
||||
self::field('ajax_list_cat_cat', '分类筛选', 'switcher', true),
|
||||
self::field('ajax_list_option_cat_cat', '分类筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_cat_cat'), 'fields' => self::ajax_fields('categories')]),
|
||||
self::field('ajax_list_cat_tag', '标签筛选', 'switcher', false),
|
||||
self::field('ajax_list_option_cat_tag', '标签筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_cat_tag'), 'fields' => self::ajax_fields('tags')]),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function tag(): array {
|
||||
return self::section('tag', '标签页面', [
|
||||
self::field('tag_post_count_s', '显示文章数量', 'switcher', true),
|
||||
self::field('page_cover_tag_s', '显示封面图', 'switcher', true),
|
||||
self::field('tag_default_cover', '封面图', 'upload', self::asset('assets/vendor/zibll/img/user_t.jpg'), ['dependsOn' => self::enabled('page_cover_tag_s'), 'help' => '默认封面图,建议尺寸2000x800。']),
|
||||
self::field('tag_orderby_s', '排序方式按钮', 'switcher', true),
|
||||
self::field('tag_orderby_option', '排序方式设置', 'fieldset', [], ['dependsOn' => self::enabled('tag_orderby_s'), 'fields' => self::orderby_fields()]),
|
||||
self::notice('AJAX筛选菜单列表:显示分类、标签菜单,通过 AJAX 获取内容。'),
|
||||
self::field('ajax_list_tag_cat', '分类筛选', 'switcher', false),
|
||||
self::field('ajax_list_option_tag_cat', '分类筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_tag_cat'), 'fields' => self::ajax_fields('categories')]),
|
||||
self::field('ajax_list_tag_tag', '标签筛选', 'switcher', true),
|
||||
self::field('ajax_list_option_tag_tag', '标签筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_tag_tag'), 'fields' => self::ajax_fields('tags')]),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function user_home(): array {
|
||||
$items = [];
|
||||
foreach (['post' => '文章', 'favorite' => '收藏', 'comment' => '评论', 'follow' => '粉丝'] as $key => $title) {
|
||||
$items[] = ['key' => $key, 'label' => $title, 'fields' => [['key' => 'pc_s', 'label' => 'PC端显示', 'type' => 'switcher', 'default' => true], ['key' => 'm_s', 'label' => '移动端显示', 'type' => 'switcher', 'default' => true]]];
|
||||
}
|
||||
return self::section('user-home', '用户主页', [
|
||||
self::field('author_main_tab_opt', '栏目配置', 'sortable', ['post' => ['pc_s' => true, 'm_s' => true], 'favorite' => ['pc_s' => true, 'm_s' => true], 'comment' => ['pc_s' => true, 'm_s' => true], 'follow' => ['pc_s' => true, 'm_s' => true]], ['items' => $items, 'description' => '用户个人主页栏目显示配置及排序。']),
|
||||
self::field('author_main_tab_swiper', '移动端滑动切换', 'switcher', false),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function other(): array {
|
||||
return self::section('other', '其它页面', [
|
||||
self::field('page_header_style', '页面标题', 'radio', '', ['options' => self::options(['' => '不显示', '1' => '简单样式', '2' => '卡片样式', '3' => '封面图样式'])]),
|
||||
self::field('page_header_cover_img', '页面封面图', 'upload', self::asset('assets/vendor/zibll/img/user_t.jpg')),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function slider_item_fields(): array {
|
||||
return [
|
||||
self::field('background', '图片背景', 'upload', ''), self::field('background_video', '视频背景', 'upload', '', ['media_type' => 'video']), self::field('hide', '显示规则', 'radio', '', ['options' => self::options(['' => '全部显示', 'pc' => 'PC端不显示', 'm' => '移动端不显示'])]), self::field('link', '跳转链接', 'link', []),
|
||||
self::field('image_layer', '叠加图层', 'group', [], ['button_title' => '添加图层', 'fields' => [self::field('image', '图层图片', 'upload', ''), self::field('free_size', '自由尺寸', 'switcher', false), self::field('align', '图层对齐', 'radio', 'center', ['dependsOn' => ['all' => [self::not_empty('image'), self::enabled('free_size')]], 'options' => self::options(['left' => '靠左显示', 'center' => '居中显示', 'right' => '靠右显示'])]), self::field('parallax', '视差滚动', 'number', 0, ['dependsOn' => self::not_empty('image'), 'min' => -200, 'max' => 200, 'step' => 5]), self::field('parallax_scale', '视差缩放', 'number', 100, ['dependsOn' => self::not_empty('image'), 'min' => 1, 'max' => 200, 'step' => 5]), self::field('parallax_opacity', '视差透明', 'number', 100, ['dependsOn' => ['all' => [self::not_empty('image'), self::not_empty('parallax')]], 'min' => 1, 'max' => 100, 'step' => 5])]]),
|
||||
self::field('text', '叠加文案', 'accordion', ['title' => '', 'desc' => '', 'text_align' => 'left-bottom', 'text_size_pc' => 30, 'text_size_m' => 20, 'parallax' => 40], ['fields' => [self::field('title', '幻灯片标题', 'textarea', '', ['sanitize_type' => 'html']), self::field('desc', '幻灯片简介', 'textarea', '', ['dependsOn' => self::not_empty('title'), 'sanitize_type' => 'html']), self::field('text_align', '显示位置', 'image_select', 'left-bottom', ['dependsOn' => self::not_empty('title'), 'options' => self::text_align_options()]), self::field('text_size_pc', 'PC端字体大小', 'number', 30, ['dependsOn' => self::not_empty('title'), 'min' => 12, 'max' => 50, 'step' => 1]), self::field('text_size_m', '移动端字体大小', 'number', 20, ['dependsOn' => self::not_empty('title'), 'min' => 12, 'max' => 50, 'step' => 1]), self::field('parallax', '文案视差滚动', 'number', 40, ['dependsOn' => self::not_empty('title'), 'min' => -200, 'max' => 200, 'step' => 10])]]),
|
||||
];
|
||||
}
|
||||
|
||||
private static function slider_option_fields(): array {
|
||||
return [self::field('direction', '幻灯片方向', 'radio', 'horizontal', ['options' => self::options(['horizontal' => '左右切换', 'vertical' => '上下切换'])]), self::field('loop', '循环切换', 'switcher', true), self::field('button', '显示翻页按钮', 'switcher', false), self::field('pagination', '显示指示器', 'switcher', false), self::field('effect', '切换动画', 'select', 'slide', ['options' => self::options(['slide' => '滑动', 'fade' => '淡出淡入', 'cube' => '3D方块', 'coverflow' => '3D滑入', 'flip' => '3D翻转'])]), self::field('scale_height', '按比例自动高度', 'switcher', true, ['dependsOn' => self::not_value('direction', 'vertical')]), self::field('scale', '长宽比例', 'number', 35, ['dependsOn' => ['all' => [self::not_value('direction', 'vertical'), self::enabled('scale_height')]], 'min' => 10, 'max' => 300, 'step' => 5]), self::field('auto_height', '自动高度', 'switcher', false, ['dependsOn' => ['all' => [self::not_value('direction', 'vertical'), self::disabled('scale_height')]]]), self::field('max_height', '最大高度', 'number', 500, ['dependsOn' => ['all' => [self::enabled('auto_height'), self::not_value('direction', 'vertical'), self::disabled('scale_height')]], 'min' => 120, 'max' => 800, 'step' => 20]), self::field('min_height', '最小高度', 'number', 180, ['dependsOn' => ['all' => [self::enabled('auto_height'), self::not_value('direction', 'vertical'), self::disabled('scale_height')]], 'min' => 100, 'max' => 500, 'step' => 20]), self::field('pc_height', '电脑端高度', 'number', 400, ['min' => 120, 'max' => 800, 'step' => 20]), self::field('m_height', '移动端高度', 'number', 200, ['min' => 100, 'max' => 500, 'step' => 20]), self::field('spacebetween', '幻灯片间距', 'number', 15, ['min' => 0, 'max' => 500, 'step' => 5]), self::field('speed', '切换速度', 'number', 0, ['min' => 0, 'max' => 3000, 'step' => 100]), self::field('autoplay', '自动播放', 'switcher', true), self::field('interval', '停顿时间', 'number', 4, ['dependsOn' => self::enabled('autoplay'), 'min' => 0, 'max' => 20, 'step' => 1])];
|
||||
}
|
||||
|
||||
private static function search_fields(): array {
|
||||
return [self::field('show_keywords', '热门搜索', 'switcher', true), self::field('popular_limit', '关键词最多显示', 'number', 6, ['dependsOn' => self::enabled('show_keywords'), 'min' => 2, 'max' => 50]), self::field('show_input_cat', '分类搜索', 'switcher', false), self::field('in_cat', '默认搜索的分类', 'async_select', '', ['dependsOn' => self::enabled('show_input_cat'), 'source' => 'categories']), self::field('more_cats', '允许用户选择更多分类', 'async_select', '', ['dependsOn' => self::enabled('show_input_cat'), 'source' => 'categories', 'multiple' => true, 'sortable' => true]), self::field('show_type', '显示类型选择', 'switcher', false), self::field('in_type', '默认搜索类型', 'radio', 'post', ['options' => self::options(['post' => '文章', 'user' => '用户'])]), self::field('before_html', '组件上方额外内容', 'textarea', '<div class="em16 font-bold mb10">更优雅的Wordpress主题</div>' . "\n" . '<div>这是导航栏幻灯片多功能组件,可在后台进行配置</div>', ['sanitize_type' => 'html']), self::field('after_html', '组件下方额外内容', 'textarea', '', ['sanitize_type' => 'html'])];
|
||||
}
|
||||
|
||||
private static function card_fields(): array {
|
||||
return [self::field('show_widget_bg', '模块背景', 'switcher', false), self::field('icon_radius4', '方形图标', 'switcher', false), self::field('cards', '添加卡片', 'group', [], ['button_title' => '添加内容', 'fields' => [self::field('title', '文案标题', 'textarea', '', ['sanitize_type' => 'html']), self::field('desc', '文案简介', 'textarea', '', ['sanitize_type' => 'html']), self::field('icon', '选择图标', 'icon', 'fa fa-magic'), self::field('customize_icon', '自定义图标', 'textarea', '', ['sanitize_type' => 'html']), self::field('icon_class', '图标颜色', 'palette', 'c-yellow', ['options' => self::zib_palette(true)]), self::field('link', '跳转链接', 'link', [])]])];
|
||||
}
|
||||
|
||||
private static function orderby_fields(): array { return [self::field('lists', '显示排序方式', 'select', ['modified', 'views', 'like', 'comment_count'], ['multiple' => true, 'sortable' => true, 'options' => self::post_order_options(false)]), self::field('dropdown', '更多排序方式', 'switcher', false)]; }
|
||||
private static function ajax_fields(string $source): array { return [self::field('lists', '按钮列表', 'async_select', [], ['source' => $source, 'multiple' => true, 'sortable' => true]), self::field('dropdown', '下拉列表', 'switcher', false), self::field('dropdown_lists', '下拉菜单列表', 'async_select', [], ['dependsOn' => self::enabled('dropdown'), 'source' => $source, 'multiple' => true, 'sortable' => true])]; }
|
||||
private static function footer_image_fields(): array { return [self::field('text', '显示文字', 'text', ''), self::field('image', '显示图片', 'upload', ''), self::field('link', '点击跳转', 'link', [])]; }
|
||||
private static function header_slider_defaults(): array { $img = self::asset('assets/vendor/zibll/img/'); return [['background' => $img . 'slider-bg.jpg', 'link' => ['url' => 'https://www.zibll.com/', 'target' => '_blank'], 'text' => ['title' => '', 'desc' => ''], 'text_align' => 'left-bottom', 'text_parallax' => 30, 'text_size_m' => 20, 'text_size_pc' => 30], ['background' => $img . 'user_t.jpg', 'link' => ['url' => 'https://www.zibll.com/', 'target' => '_blank'], 'text' => ['title' => '', 'desc' => ''], 'text_align' => 'left-bottom', 'text_parallax' => 30, 'text_size_m' => 20, 'text_size_pc' => 30]]; }
|
||||
private static function slider_options_defaults(): array { return ['direction' => 'horizontal', 'loop' => true, 'button' => true, 'pagination' => true, 'effect' => 'slide', 'scale_height' => false, 'auto_height' => false, 'pc_height' => 550, 'm_height' => 280, 'spacebetween' => 0, 'speed' => 0, 'autoplay' => true, 'interval' => 6]; }
|
||||
private static function search_defaults(): array { return ['show_keywords' => true, 'popular_limit' => 6, 'show_input_cat' => false, 'in_cat' => '', 'more_cats' => '', 'show_type' => false, 'in_type' => 'post', 'before_html' => '<div class="em16 font-bold mb10">更优雅的Wordpress主题</div>' . "\n" . '<div>这是导航栏幻灯片多功能组件,可在后台进行配置</div>', 'after_html' => '']; }
|
||||
private static function card_defaults(): array { return ['show_widget_bg' => false, 'icon_radius4' => false, 'cards' => [['icon' => 'zibsvg-vip_1', 'customize_icon' => '', 'icon_class' => 'c-yellow', 'link' => [], 'title' => '图标卡片', 'desc' => '这是一个图标卡片示例'], ['icon' => 'zibsvg-vip_2', 'customize_icon' => '', 'icon_class' => 'c-blue-2', 'link' => [], 'title' => '原创作品', 'desc' => '这是一个图标卡片示例'], ['icon' => 'zibsvg-hot', 'customize_icon' => '', 'icon_class' => 'jb-pink', 'link' => [], 'title' => '灵感来源<badge class="ml6 jb-yellow">NEW</badge>', 'desc' => '这是一个图标卡片示例'], ['icon' => 'fa fa-ioxhost', 'customize_icon' => '', 'icon_class' => 'jb-cyan', 'link' => [], 'title' => '系统工具 <badge class="ml6 jb-blue">GO <span class="fa fa-angle-right em12"></span></badge>', 'desc' => '这是一个图标卡片示例']]]; }
|
||||
|
||||
private static function field(string $key, string $label, string $type, $default = null, array $extra = []): array { return array_merge(['key' => $key, 'label' => $label, 'type' => $type, 'default' => $default], $extra); }
|
||||
private static function notice(string $content): array { return ['key' => '_notice_' . md5($content), 'type' => 'notice', 'content' => $content, 'tone' => 'warning']; }
|
||||
private static function enabled(string $key): array { return ['key' => $key, 'operator' => 'truthy']; }
|
||||
private static function disabled(string $key): array { return ['key' => $key, 'operator' => 'falsy']; }
|
||||
private static function not_empty(string $key): array { return ['key' => $key, 'operator' => 'not_empty']; }
|
||||
private static function not_value(string $key, string $value): array { return ['key' => $key, 'operator' => 'neq', 'value' => $value]; }
|
||||
private static function options(array $options): array { $out = []; foreach ($options as $value => $label) { $out[] = ['label' => $label, 'value' => (string) $value]; } return $out; }
|
||||
private static function device_options(): array { return self::options(['pc_s' => 'PC端显示', 'm_s' => '移动端显示']); }
|
||||
private static function solid_palette(): array { return self::options(['ff648f' => '#ff648f', 'c246f5' => '#c246f5', '469cf5' => '#469cf5', '27bf41' => '#27bf41', 'fd6b4e' => '#fd6b4e', '2d2422' => '#2d2422']); }
|
||||
private static function zib_palette(bool $transparent = false): array { $colors = $transparent ? ['transparent' => 'rgba(114,114,114,.1)'] : []; $colors += ['c-red' => 'rgba(255,84,115,.4)', 'c-yellow' => 'rgba(255,111,6,.4)', 'c-cyan' => 'rgba(8,196,193,.4)', 'c-blue' => 'rgba(41,151,247,.4)', 'c-blue-2' => 'rgba(77,130,249,.4)', 'c-green' => 'rgba(18,185,40,.4)', 'c-purple' => 'rgba(213,72,245,.4)', 'jb-red' => '#ff5473', 'jb-yellow' => '#ff8a00', 'jb-cyan' => '#08c4c1', 'jb-blue' => '#2997f7', 'jb-green' => '#12b928', 'jb-purple' => '#d548f5', 'jb-pink' => '#ff648f']; return self::options($colors); }
|
||||
private static function post_order_options(bool $with_default): array { $options = $with_default ? ['' => '保持默认'] : []; $options += ['modified' => '更新', 'date' => '发布', 'views' => '浏览', 'like' => '点赞', 'comment_count' => '评论', 'favorite' => '收藏', 'rand' => '随机']; return self::options($options); }
|
||||
private static function header_layout_options(): array { $base = self::asset('assets/images/header-layout/'); return [['label' => '布局一', 'value' => '1', 'image' => $base . 'header_layout_1.png'], ['label' => '布局二', 'value' => '2', 'image' => $base . 'header_layout_2.png'], ['label' => '布局三', 'value' => '3', 'image' => $base . 'header_layout_3.png']]; }
|
||||
private static function mobile_header_layout_options(): array { $base = self::asset('assets/images/header-layout/'); return [['label' => '居中', 'value' => 'center', 'image' => $base . 'mobile_header_layout_center.png'], ['label' => '居左', 'value' => 'left', 'image' => $base . 'mobile_header_layout_left.png']]; }
|
||||
private static function text_align_options(): array { $base = self::asset('assets/images/slider-align/'); return [['label' => '左下', 'value' => 'left-bottom', 'image' => $base . 'left-bottom.jpg'], ['label' => '左中', 'value' => 'left-conter', 'image' => $base . 'left-conter.jpg'], ['label' => '居中', 'value' => 'conter-conter', 'image' => $base . 'conter-conter.jpg'], ['label' => '中下', 'value' => 'conter-bottom', 'image' => $base . 'conter-bottom.jpg'], ['label' => '右中', 'value' => 'right-conter', 'image' => $base . 'right-conter.jpg'], ['label' => '右下', 'value' => 'right-bottom', 'image' => $base . 'right-bottom.jpg']]; }
|
||||
private static function asset(string $path): string { return trailingslashit(get_template_directory_uri()) . ltrim($path, '/'); }
|
||||
private static function copyright_default(): string { return sprintf('Copyright © %s · <a href="%s">%s</a> · 由<a target="_blank" href="https://www.zibll.com">zibll主题</a>强力驱动.', date('Y'), home_url(), get_bloginfo('title')); }
|
||||
}
|
||||
|
||||
@@ -123,6 +123,26 @@ class SchemaProvider {
|
||||
return $allowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a schema payload with the full nested field contract.
|
||||
*/
|
||||
public function sanitize_payload(string $domain, array $data, string $section = ''): array {
|
||||
$schema = $section !== '' ? $this->get_section_schema($domain, $section) : $this->get_schema($domain);
|
||||
if (!$schema) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$clean = [];
|
||||
foreach ($this->get_all_fields($schema) as $field) {
|
||||
$key = (string) $field['key'];
|
||||
if (array_key_exists($key, $data)) {
|
||||
$clean[$key] = $this->sanitize_field_value($data[$key], $field);
|
||||
}
|
||||
}
|
||||
|
||||
return $clean;
|
||||
}
|
||||
|
||||
public function validate(string $domain, array $data, string $section = ''): array {
|
||||
$schemas = [];
|
||||
|
||||
@@ -151,7 +171,7 @@ class SchemaProvider {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $field['type'] ?? 'input';
|
||||
$type = $field['type'] ?? 'input';
|
||||
if (($type === 'number' || $type === 'slider') && !is_numeric($value)) {
|
||||
$errors[] = sprintf('%s must be a number', $field['label'] ?? $key);
|
||||
continue;
|
||||
@@ -162,6 +182,25 @@ class SchemaProvider {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($type, ['fieldset', 'accordion', 'sortable', 'link'], true) && !is_array($value)) {
|
||||
$errors[] = sprintf('%s must be an object', $field['label'] ?? $key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === 'group' && !is_array($value)) {
|
||||
$errors[] = sprintf('%s must be an array of items', $field['label'] ?? $key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($type, ['group', 'repeater'], true) && is_array($value)) {
|
||||
if (isset($field['min']) && count($value) < (int) $field['min']) {
|
||||
$errors[] = sprintf('%s must contain at least %s items', $field['label'] ?? $key, $field['min']);
|
||||
}
|
||||
if (isset($field['max']) && count($value) > (int) $field['max']) {
|
||||
$errors[] = sprintf('%s must contain at most %s items', $field['label'] ?? $key, $field['max']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($type === 'repeater' && !is_array($value)) {
|
||||
$errors[] = sprintf('%s must be an array of items', $field['label'] ?? $key);
|
||||
continue;
|
||||
@@ -175,7 +214,7 @@ class SchemaProvider {
|
||||
$errors[] = sprintf('%s must be at most %s', $field['label'] ?? $key, $field['max']);
|
||||
}
|
||||
|
||||
if (in_array($type, ['select', 'radio'], true) && !empty($field['options'])) {
|
||||
if (in_array($type, ['select', 'radio', 'palette', 'image_select'], true) && !empty($field['options'])) {
|
||||
$valid_values = array_column($field['options'], 'value');
|
||||
// Multi-select: accept array values when schema.multiple is set
|
||||
// (legacy / SPA may still use type=select multiple).
|
||||
@@ -204,6 +243,8 @@ class SchemaProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$errors = array_merge($errors, $this->validate_nested_field($field, $value, (string) ($field['label'] ?? $key)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,14 +367,16 @@ class SchemaProvider {
|
||||
$stored = [];
|
||||
}
|
||||
|
||||
$allowed = $this->collect_allowed_fields($schema);
|
||||
foreach ($allowed as $key => $type) {
|
||||
$fields = [];
|
||||
foreach ($this->get_all_fields($schema) as $field) {
|
||||
$fields[(string) $field['key']] = $field;
|
||||
}
|
||||
foreach ($fields as $key => $field) {
|
||||
if (!array_key_exists($key, $data)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->sanitize_value($data[$key], $type);
|
||||
$stored[$key] = $value;
|
||||
$stored[$key] = $this->sanitize_field_value($data[$key], $field);
|
||||
}
|
||||
|
||||
// Captcha stores a single positive scene list.
|
||||
@@ -431,9 +474,161 @@ class SchemaProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private function sanitize_field_value($value, array $field) {
|
||||
$type = (string) ($field['type'] ?? 'text');
|
||||
$sanitize_type = (string) ($field['sanitize_type'] ?? '');
|
||||
if ($sanitize_type !== '') {
|
||||
return $this->sanitize_value($value, $sanitize_type);
|
||||
}
|
||||
|
||||
if (in_array($type, ['fieldset', 'accordion'], true)) {
|
||||
return $this->sanitize_object_fields($value, (array) ($field['fields'] ?? []));
|
||||
}
|
||||
|
||||
if (in_array($type, ['group', 'repeater'], true)) {
|
||||
if (!is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
$items = [];
|
||||
$limit = isset($field['max']) ? max(0, (int) $field['max']) : 100;
|
||||
foreach (array_slice(array_values($value), 0, $limit ?: 100) as $item) {
|
||||
if (is_array($item)) {
|
||||
$items[] = $this->sanitize_object_fields($item, (array) ($field['fields'] ?? []));
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
if ($type === 'sortable') {
|
||||
if (!is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
$items = [];
|
||||
foreach ((array) ($field['items'] ?? []) as $item_schema) {
|
||||
$item_key = sanitize_key((string) ($item_schema['key'] ?? ''));
|
||||
if ($item_key !== '' && array_key_exists($item_key, $value) && is_array($value[$item_key])) {
|
||||
$items[$item_key] = $this->sanitize_object_fields($value[$item_key], (array) ($item_schema['fields'] ?? []));
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
if ($type === 'link') {
|
||||
$value = is_array($value) ? $value : [];
|
||||
return [
|
||||
'url' => esc_url_raw((string) ($value['url'] ?? '')),
|
||||
'target' => in_array(($value['target'] ?? ''), ['', '_self', '_blank'], true) ? (string) ($value['target'] ?? '') : '',
|
||||
'text' => sanitize_text_field((string) ($value['text'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
if ($type === 'checkbox' || !empty($field['multiple'])) {
|
||||
if (!is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
return array_values(array_map(static function ($item) {
|
||||
return is_numeric($item) ? (int) $item : sanitize_key((string) $item);
|
||||
}, $value));
|
||||
}
|
||||
|
||||
if (in_array($type, ['switch', 'switcher'], true)) {
|
||||
return !empty($value);
|
||||
}
|
||||
if (in_array($type, ['number', 'slider', 'spinner'], true)) {
|
||||
return is_float($value) ? (float) $value : (int) $value;
|
||||
}
|
||||
if (in_array($type, ['media', 'upload', 'url'], true)) {
|
||||
return esc_url_raw((string) $value);
|
||||
}
|
||||
|
||||
return sanitize_text_field((string) $value);
|
||||
}
|
||||
|
||||
private function sanitize_object_fields($value, array $fields): array {
|
||||
if (!is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
$clean = [];
|
||||
foreach ($fields as $field) {
|
||||
if (!is_array($field) || empty($field['key'])) {
|
||||
continue;
|
||||
}
|
||||
$key = (string) $field['key'];
|
||||
if (array_key_exists($key, $value)) {
|
||||
$clean[$key] = $this->sanitize_field_value($value[$key], $field);
|
||||
}
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
private function validate_nested_field(array $field, $value, string $path): array {
|
||||
$type = (string) ($field['type'] ?? 'text');
|
||||
$errors = [];
|
||||
if (in_array($type, ['fieldset', 'accordion'], true) && is_array($value)) {
|
||||
$errors = $this->validate_object_fields((array) ($field['fields'] ?? []), $value, $path);
|
||||
} elseif (in_array($type, ['group', 'repeater'], true) && is_array($value)) {
|
||||
foreach (array_values($value) as $index => $item) {
|
||||
if (is_array($item)) {
|
||||
$errors = array_merge($errors, $this->validate_object_fields((array) ($field['fields'] ?? []), $item, $path . '[' . $index . ']'));
|
||||
}
|
||||
}
|
||||
} elseif ($type === 'sortable' && is_array($value)) {
|
||||
$items = [];
|
||||
foreach ((array) ($field['items'] ?? []) as $item_schema) {
|
||||
if (is_array($item_schema) && !empty($item_schema['key'])) {
|
||||
$items[(string) $item_schema['key']] = $item_schema;
|
||||
}
|
||||
}
|
||||
foreach (array_keys($value) as $item_key) {
|
||||
if (!isset($items[$item_key])) {
|
||||
$errors[] = sprintf('%s has an invalid item', $path);
|
||||
} elseif (is_array($value[$item_key])) {
|
||||
$errors = array_merge($errors, $this->validate_object_fields((array) ($items[$item_key]['fields'] ?? []), $value[$item_key], $path . '.' . $item_key));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
private function validate_object_fields(array $fields, array $value, string $path): array {
|
||||
$errors = [];
|
||||
foreach ($fields as $field) {
|
||||
$key = (string) ($field['key'] ?? '');
|
||||
if ($key === '' || !array_key_exists($key, $value)) {
|
||||
continue;
|
||||
}
|
||||
$type = (string) ($field['type'] ?? 'text');
|
||||
if (in_array($type, ['select', 'radio', 'palette', 'image_select'], true) && !empty($field['options'])) {
|
||||
$allowed = array_column($field['options'], 'value');
|
||||
if (!empty($field['multiple']) && is_array($value[$key])) {
|
||||
foreach ($value[$key] as $item) {
|
||||
if (!in_array($item, $allowed, true)) {
|
||||
$errors[] = sprintf('%s.%s has an invalid value', $path, $key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} elseif (!in_array($value[$key], $allowed, true)) {
|
||||
$errors[] = sprintf('%s.%s has an invalid value', $path, $key);
|
||||
}
|
||||
}
|
||||
if (in_array($type, ['number', 'slider', 'spinner'], true)) {
|
||||
if (!is_numeric($value[$key])) {
|
||||
$errors[] = sprintf('%s.%s must be a number', $path, $key);
|
||||
} elseif (isset($field['min']) && $value[$key] < $field['min']) {
|
||||
$errors[] = sprintf('%s.%s must be at least %s', $path, $key, $field['min']);
|
||||
} elseif (isset($field['max']) && $value[$key] > $field['max']) {
|
||||
$errors[] = sprintf('%s.%s must be at most %s', $path, $key, $field['max']);
|
||||
}
|
||||
}
|
||||
$errors = array_merge($errors, $this->validate_nested_field($field, $value[$key], $path . '.' . $key));
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
private function infer_sanitize_type(string $field_type): string {
|
||||
$map = [
|
||||
'switch' => 'bool',
|
||||
'switcher' => 'bool',
|
||||
'input' => 'string',
|
||||
'text' => 'string',
|
||||
'textarea' => 'textarea',
|
||||
@@ -443,12 +638,19 @@ class SchemaProvider {
|
||||
'radio' => 'string',
|
||||
'checkbox' => 'array',
|
||||
'repeater' => 'array',
|
||||
'group' => 'array',
|
||||
'fieldset' => 'array',
|
||||
'accordion'=> 'array',
|
||||
'sortable' => 'array',
|
||||
'link' => 'array',
|
||||
'async_select' => 'array',
|
||||
'json' => 'array',
|
||||
// 自定义面板(验证码服务商等)按对象存储
|
||||
'custom' => 'array',
|
||||
'color' => 'string',
|
||||
'icon' => 'string',
|
||||
'media' => 'url',
|
||||
'upload' => 'url',
|
||||
'code' => 'code',
|
||||
];
|
||||
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Core;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class HomeModules {
|
||||
|
||||
const PREVIEW_QUERY_VAR = 'aksrt_preview';
|
||||
const PREVIEW_TRANSIENT_PREFIX = 'aksrt_home_preview_';
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_filter('query_vars', [$this, 'add_query_var']);
|
||||
add_action('rest_api_init', [$this, 'register_routes']);
|
||||
}
|
||||
|
||||
public function add_query_var(array $vars): array {
|
||||
$vars[] = self::PREVIEW_QUERY_VAR;
|
||||
return $vars;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
register_rest_route('aksrt/v1', '/preview/homepage', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_create_preview'],
|
||||
'permission_callback' => function () {
|
||||
return current_user_can('manage_options');
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取并校验模块列表(含预览/默认回退逻辑)。
|
||||
*/
|
||||
private static function resolve_modules(): array {
|
||||
$modules = [];
|
||||
|
||||
// 预览模式
|
||||
$preview_id = get_query_var(self::PREVIEW_QUERY_VAR);
|
||||
if ($preview_id && current_user_can('manage_options')) {
|
||||
$cached = get_transient(self::PREVIEW_TRANSIENT_PREFIX . sanitize_key((string) $preview_id));
|
||||
if (is_array($cached)) {
|
||||
$modules = $cached;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($modules) && class_exists('\AKSRT\Settings\Settings')) {
|
||||
$modules = \AKSRT\Settings\Settings::get_homepage_modules();
|
||||
}
|
||||
|
||||
// 校验:过滤无效条目,若全部无效则清空以触发默认模板回退
|
||||
if (!empty($modules)) {
|
||||
$allowed = self::allowed_types();
|
||||
$valid = array_filter($modules, function ($row) use ($allowed) {
|
||||
return is_array($row) && !empty($row['type']) && in_array($row['type'], $allowed, true);
|
||||
});
|
||||
$modules = empty($valid) ? [] : array_values($valid);
|
||||
}
|
||||
|
||||
if (empty($modules)) {
|
||||
$modules = self::get_default_template('resource_standard');
|
||||
}
|
||||
|
||||
return $modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染入口(首页 CMS 形态调用)。
|
||||
*
|
||||
* 优先级:
|
||||
* 1. ?aksrt_preview={id} 且管理员 → 读 transient 渲染
|
||||
* 2. option homepage_modules
|
||||
* 3. 若空 → 渲染默认模板(resource_standard)
|
||||
*/
|
||||
public static function render(): void {
|
||||
$modules = self::resolve_modules();
|
||||
|
||||
echo '<div class="home-modules">';
|
||||
foreach ($modules as $idx => $row) {
|
||||
if (!is_array($row) || empty($row['type'])) continue;
|
||||
$type = sanitize_key((string) $row['type']);
|
||||
$config = isset($row['config']) && is_array($row['config']) ? $row['config'] : [];
|
||||
|
||||
set_query_var('aksrt_module_config', $config);
|
||||
set_query_var('aksrt_module_index', $idx);
|
||||
|
||||
get_template_part('templates/parts/home-modules/module', $type);
|
||||
}
|
||||
// 清理
|
||||
set_query_var('aksrt_module_config', null);
|
||||
set_query_var('aksrt_module_index', null);
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 全屏模块类型(不受侧栏约束)。
|
||||
*/
|
||||
public static function fullbleed_types(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 分区渲染:仅渲染指定区域的模块。
|
||||
*
|
||||
* @param string $zone 'fullbleed' | 'body'
|
||||
*/
|
||||
public static function render_zone(string $zone = 'body'): void {
|
||||
$modules = self::resolve_modules();
|
||||
|
||||
$fullbleed = self::fullbleed_types();
|
||||
$filtered = array_filter($modules, function ($row) use ($fullbleed, $zone) {
|
||||
$type = $row['type'] ?? '';
|
||||
$is_fullbleed = in_array($type, $fullbleed, true);
|
||||
return $zone === 'fullbleed' ? $is_fullbleed : !$is_fullbleed;
|
||||
});
|
||||
|
||||
if (empty($filtered)) return;
|
||||
|
||||
$wrapper_class = $zone === 'fullbleed' ? 'home-modules home-modules--fullbleed' : 'home-modules';
|
||||
echo '<div class="' . esc_attr($wrapper_class) . '">';
|
||||
foreach ($filtered as $idx => $row) {
|
||||
if (!is_array($row) || empty($row['type'])) continue;
|
||||
$type = sanitize_key((string) $row['type']);
|
||||
$config = isset($row['config']) && is_array($row['config']) ? $row['config'] : [];
|
||||
|
||||
set_query_var('aksrt_module_config', $config);
|
||||
set_query_var('aksrt_module_index', $idx);
|
||||
|
||||
get_template_part('templates/parts/home-modules/module', $type);
|
||||
}
|
||||
set_query_var('aksrt_module_config', null);
|
||||
set_query_var('aksrt_module_index', null);
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建预览会话。
|
||||
*/
|
||||
public function rest_create_preview(\WP_REST_Request $request) {
|
||||
$params = $request->get_json_params();
|
||||
if (!is_array($params)) $params = $request->get_params();
|
||||
$modules = isset($params['modules']) && is_array($params['modules']) ? $params['modules'] : [];
|
||||
|
||||
$clean = [];
|
||||
$allowed_types = self::allowed_types();
|
||||
foreach ($modules as $row) {
|
||||
if (!is_array($row)) continue;
|
||||
$type = isset($row['type']) ? sanitize_key((string) $row['type']) : '';
|
||||
if (!in_array($type, $allowed_types, true)) continue;
|
||||
$clean[] = [
|
||||
'type' => $type,
|
||||
'config' => isset($row['config']) && is_array($row['config']) ? $row['config'] : [],
|
||||
];
|
||||
}
|
||||
|
||||
$id = wp_generate_uuid4();
|
||||
set_transient(self::PREVIEW_TRANSIENT_PREFIX . $id, $clean, 30 * MINUTE_IN_SECONDS);
|
||||
|
||||
return new \WP_REST_Response([
|
||||
'preview_id' => $id,
|
||||
'preview_url' => add_query_arg(self::PREVIEW_QUERY_VAR, $id, home_url('/')),
|
||||
'expires_in' => 30 * MINUTE_IN_SECONDS,
|
||||
], 200);
|
||||
}
|
||||
|
||||
public static function allowed_types(): array {
|
||||
return [
|
||||
'banner', 'category', 'tabs', 'ranking',
|
||||
'recent', 'tag_cloud', 'friend_links', 'html', 'hotspot',
|
||||
'category_recommend_cards',
|
||||
// Phase 5: 运营型模块
|
||||
'text_title', 'button_group', 'spacing', 'faq',
|
||||
'data_grid', 'highlight_cards', 'partner_marquee', 'product_showcase',
|
||||
// Widget-区等价模块(补齐 zibll widget-user / widget-more 能力)
|
||||
'user', 'more',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认模板(3 套预设)。
|
||||
*/
|
||||
public static function get_default_template(string $name): array {
|
||||
switch ($name) {
|
||||
case 'news':
|
||||
return [
|
||||
['type' => 'banner', 'config' => []],
|
||||
['type' => 'category', 'config' => ['count' => 8, 'columns' => 4]],
|
||||
['type' => 'category', 'config' => ['count' => 8, 'columns' => 4]],
|
||||
['type' => 'tag_cloud', 'config' => ['limit' => 30]],
|
||||
];
|
||||
|
||||
case 'mixed':
|
||||
return [
|
||||
['type' => 'recent', 'config' => ['count' => 6, 'layout' => 'card']],
|
||||
['type' => 'category', 'config' => ['count' => 6, 'columns' => 3]],
|
||||
['type' => 'category', 'config' => ['count' => 6, 'columns' => 3]],
|
||||
];
|
||||
|
||||
case 'resource_standard':
|
||||
default:
|
||||
return [
|
||||
['type' => 'tabs', 'config' => ['count' => 8]],
|
||||
['type' => 'recent', 'config' => ['count' => 8, 'layout' => 'card']],
|
||||
['type' => 'ranking', 'config' => ['metric' => 'downloads', 'count' => 6]],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,6 @@ trait ScriptsPart1b {
|
||||
$enqueue_module('layout');
|
||||
$enqueue_module('footer');
|
||||
$enqueue_module('floating-buttons');
|
||||
$enqueue_module('theme-background');
|
||||
$enqueue_module('enhancements');
|
||||
|
||||
// 4a-b. 全局补充样式(骨架屏、空状态、分页、加载指示器)
|
||||
@@ -681,24 +680,6 @@ trait ScriptsPart1b {
|
||||
);
|
||||
}
|
||||
|
||||
// Legacy freeform CMS home modules: only for non-fixed CMS archive previews.
|
||||
$is_cms_home = ($is_home_or_archive && $is_cms_mode) && !$is_fixed_homepage;
|
||||
// 管理员预览模式(?aksrt_preview=...):无论站点风格都允许加载模块系统资源
|
||||
$is_preview = is_user_logged_in() && current_user_can('manage_options') && isset($_GET['aksrt_preview']);
|
||||
|
||||
if ($is_cms_home || $is_preview) {
|
||||
$enqueue_module('home-modules');
|
||||
$enqueue_module('post-card'); // 模块内复用
|
||||
|
||||
wp_enqueue_script(
|
||||
'aksrt-cms-home-modules',
|
||||
$base_uri . '/assets/js/cms-home-modules.js',
|
||||
['loader_js'],
|
||||
$version,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if ($is_fixed_homepage) {
|
||||
$enqueue_module('post-card');
|
||||
$homepage_style_deps = ['_main', 'aksrt-layout', 'aksrt-post-card'];
|
||||
|
||||
@@ -69,7 +69,6 @@ class Setup {
|
||||
add_image_size('aksrt-cover', 1200, 675, true);
|
||||
add_image_size('aksrt-card', 400, 260, true);
|
||||
add_image_size('aksrt-thumb', 150, 150, true);
|
||||
add_image_size('aksrt-banner', 1920, 500, true);
|
||||
|
||||
// 内容宽度(用于区块编辑器)
|
||||
if (!isset($GLOBALS['content_width'])) {
|
||||
|
||||
@@ -94,10 +94,10 @@ class TermMeta {
|
||||
$sidebar_enabled = false;
|
||||
}
|
||||
|
||||
$cover_url = isset($saved['cover_url']) ? esc_url_raw((string) $saved['cover_url']) : '';
|
||||
if ($cover_url === '' && $taxonomy === 'category' && !empty($global['cover_enabled']) && class_exists('\AKSRT\Settings\Settings')) {
|
||||
$cover_url = \AKSRT\Settings\Settings::get_category_cover_url($term->term_id);
|
||||
}
|
||||
$cover_url = isset($saved['cover_url']) ? esc_url_raw((string) $saved['cover_url']) : '';
|
||||
if ($cover_url === '' && !empty($global['cover_enabled'])) {
|
||||
$cover_url = esc_url_raw((string) ($global['default_cover'] ?? ''));
|
||||
}
|
||||
|
||||
$description = isset($saved['description']) ? sanitize_textarea_field((string) $saved['description']) : '';
|
||||
if ($description === '') {
|
||||
@@ -110,7 +110,15 @@ class TermMeta {
|
||||
'show_sidebar' => $sidebar_enabled,
|
||||
'show_cover' => $header_style === 'cover' && !empty($global['cover_enabled']) && $cover_url !== '',
|
||||
'show_desc' => $taxonomy === 'post_tag' ? $description !== '' : (!empty($global['desc_enabled']) && $description !== ''),
|
||||
'filter_bar' => $taxonomy === 'category' && !empty($global['filter_bar']),
|
||||
'filter_bar' => !empty($global['filter_bar']),
|
||||
'post_count' => !empty($global['post_count']),
|
||||
'orderby_enabled' => !empty($global['orderby_enabled']),
|
||||
'orderby' => sanitize_key((string) ($global['orderby'] ?? 'date')),
|
||||
'orderby_option'=> is_array($global['orderby_option'] ?? null) ? $global['orderby_option'] : [],
|
||||
'ajax_category_enabled' => !empty($global['ajax_category_enabled']),
|
||||
'ajax_tag_enabled' => !empty($global['ajax_tag_enabled']),
|
||||
'ajax_category' => is_array($global['ajax_category'] ?? null) ? $global['ajax_category'] : [],
|
||||
'ajax_tag' => is_array($global['ajax_tag'] ?? null) ? $global['ajax_tag'] : [],
|
||||
'per_page' => max(4, min(48, (int) ($global['per_page'] ?? 12))),
|
||||
'cover_url' => $cover_url,
|
||||
'description' => $description,
|
||||
@@ -153,10 +161,36 @@ class TermMeta {
|
||||
}
|
||||
|
||||
$settings = self::resolve_archive_settings($term);
|
||||
if (!empty($settings['per_page'])) {
|
||||
$query->set('posts_per_page', (int) $settings['per_page']);
|
||||
}
|
||||
}
|
||||
if (!empty($settings['per_page'])) {
|
||||
$query->set('posts_per_page', (int) $settings['per_page']);
|
||||
}
|
||||
|
||||
$tax_query = is_array($query->get('tax_query')) ? $query->get('tax_query') : [];
|
||||
$filter_cat = isset($_GET['aksrt_filter_cat']) ? absint(wp_unslash($_GET['aksrt_filter_cat'])) : 0;
|
||||
$filter_tag = isset($_GET['aksrt_filter_tag']) ? absint(wp_unslash($_GET['aksrt_filter_tag'])) : 0;
|
||||
if ($filter_cat > 0) {
|
||||
$tax_query[] = ['taxonomy' => 'category', 'field' => 'term_id', 'terms' => [$filter_cat]];
|
||||
}
|
||||
if ($filter_tag > 0) {
|
||||
$tax_query[] = ['taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => [$filter_tag]];
|
||||
}
|
||||
if ($tax_query !== []) {
|
||||
$query->set('tax_query', $tax_query);
|
||||
}
|
||||
|
||||
$orderby = isset($_GET['orderby']) ? sanitize_key(wp_unslash((string) $_GET['orderby'])) : 'date';
|
||||
$allowed = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::homepage_allowed_sorts()
|
||||
: ['modified', 'date', 'views', 'like', 'comment_count', 'favorite', 'rand'];
|
||||
if (!in_array($orderby, $allowed, true)) {
|
||||
$orderby = 'date';
|
||||
}
|
||||
if (function_exists('aksrt_homepage_apply_named_sort_args')) {
|
||||
foreach (aksrt_homepage_apply_named_sort_args([], $orderby) as $key => $value) {
|
||||
$query->set($key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render fields for "Add new term" form.
|
||||
|
||||
@@ -50,7 +50,7 @@ trait DisplayPageGroups {
|
||||
* 获取顶部多功能组件设置
|
||||
*/
|
||||
public static function get_header_slider_settings(): array {
|
||||
$s = self::get_all();
|
||||
$s = self::read_domain('page_display', 'header-components');
|
||||
$allowed_page_types = [
|
||||
'home',
|
||||
'category',
|
||||
@@ -59,10 +59,6 @@ trait DisplayPageGroups {
|
||||
'single',
|
||||
'search',
|
||||
'page',
|
||||
'topics',
|
||||
'forum_home',
|
||||
'forum_plate',
|
||||
'forum_post',
|
||||
];
|
||||
$show_on = isset($s['header_slider_show']) ? (array) $s['header_slider_show'] : [];
|
||||
$show_on = array_values(array_intersect(
|
||||
@@ -218,7 +214,6 @@ trait DisplayPageGroups {
|
||||
'tag' => self::get_tag_page_settings(),
|
||||
'user_home' => self::get_user_home_settings(),
|
||||
'other' => self::get_other_page_settings(),
|
||||
'background' => self::get_global_background_settings(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -9,39 +9,6 @@ defined('ABSPATH') || exit;
|
||||
|
||||
class DisplayRuntime {
|
||||
|
||||
/** 获取Banner列表 */
|
||||
public static function get_banners(string $position = ''): array {
|
||||
$banners = get_option('aksrt_banners', []) ?: [];
|
||||
|
||||
if ($position) {
|
||||
$banners = array_filter($banners, fn($banner) => ($banner['position'] ?? '') === $position);
|
||||
}
|
||||
|
||||
usort($banners, fn($a, $b) => ($a['sort_order'] ?? 0) - ($b['sort_order'] ?? 0));
|
||||
|
||||
return array_filter($banners, fn($banner) => ($banner['status'] ?? 'enabled') === 'enabled');
|
||||
}
|
||||
|
||||
/** 保存Banner */
|
||||
public static function save_banner(array $data): int {
|
||||
$banners = get_option('aksrt_banners', []);
|
||||
if (!empty($data['id'])) {
|
||||
$banners[$data['id']] = $data;
|
||||
} else {
|
||||
$data['id'] = uniqid('banner_');
|
||||
$banners[] = $data;
|
||||
}
|
||||
update_option('aksrt_banners', $banners);
|
||||
return $data['id'];
|
||||
}
|
||||
|
||||
/** 删除Banner */
|
||||
public static function delete_banner(string $id): void {
|
||||
$banners = get_option('aksrt_banners', []);
|
||||
unset($banners[$id]);
|
||||
update_option('aksrt_banners', array_values($banners));
|
||||
}
|
||||
|
||||
/** 获取友链列表 */
|
||||
public static function get_friend_links(): array {
|
||||
$links = get_option('aksrt_friend_links', []) ?: [];
|
||||
@@ -56,83 +23,4 @@ class DisplayRuntime {
|
||||
update_option('aksrt_friend_links', $links);
|
||||
}
|
||||
|
||||
public static function homepage_module_banner_enabled(): bool {
|
||||
$settings = Settings::get_all();
|
||||
return !isset($settings['homepage_module_banner_enabled']) || !empty($settings['homepage_module_banner_enabled']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Global slider configuration shared by Hero and Banner.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function get_slider_config(): array {
|
||||
$settings = Settings::get_all();
|
||||
$config = is_array($settings['header_slider_option'] ?? null) ? $settings['header_slider_option'] : [];
|
||||
|
||||
return [
|
||||
'effect' => in_array($config['effect'] ?? '', ['fade', 'slide', 'cube', 'coverflow', 'flip'], true) ? $config['effect'] : 'fade',
|
||||
'direction' => in_array($config['direction'] ?? '', ['horizontal', 'vertical'], true) ? $config['direction'] : 'horizontal',
|
||||
'loop' => !isset($config['loop']) || !empty($config['loop']),
|
||||
'autoplay_interval' => max(0, min(20000, (int) ($config['interval'] ?? 6) * 1000)),
|
||||
'pagination' => !isset($config['pagination']) || !empty($config['pagination']),
|
||||
'navigation' => !isset($config['navigation']) || !empty($config['navigation']),
|
||||
'button' => !isset($config['button']) || !empty($config['button']),
|
||||
'auto_height' => !empty($config['auto_height']),
|
||||
'pc_height' => isset($config['pc_height']) && $config['pc_height'] !== '' ? sanitize_text_field($config['pc_height']) : '',
|
||||
'mobile_height' => isset($config['m_height']) && $config['m_height'] !== '' ? sanitize_text_field($config['m_height']) : '',
|
||||
'max_height' => isset($config['max_height']) && $config['max_height'] !== '' ? sanitize_text_field($config['max_height']) : '',
|
||||
'min_height' => isset($config['min_height']) && $config['min_height'] !== '' ? sanitize_text_field($config['min_height']) : '',
|
||||
'scale_height' => !empty($config['scale_height']),
|
||||
'scale' => (int) ($config['scale'] ?? 40),
|
||||
'speed' => max(0, min(3000, (int) ($config['speed'] ?? 0))),
|
||||
'space_between' => max(0, min(500, (int) ($config['spacebetween'] ?? 0))),
|
||||
'nav_color' => sanitize_hex_color((string) ($settings['header_slider_nav_color'] ?? '')) ?: '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局背景设置
|
||||
*/
|
||||
public static function get_global_background_settings(): array {
|
||||
$settings = Settings::get_all();
|
||||
|
||||
$valid_sizes = ['cover', 'contain', 'auto', '100%', '100% 100%'];
|
||||
$size = sanitize_text_field((string) ($settings['bg_size'] ?? '100%'));
|
||||
if (!in_array($size, $valid_sizes, true)) {
|
||||
$size = '100%';
|
||||
}
|
||||
|
||||
$valid_positions = ['center center', 'top center', 'bottom center'];
|
||||
$position = sanitize_text_field((string) ($settings['bg_position'] ?? 'center center'));
|
||||
if (!in_array($position, $valid_positions, true)) {
|
||||
$position = 'center center';
|
||||
}
|
||||
|
||||
$valid_repeats = ['no-repeat', 'repeat', 'repeat-x', 'repeat-y'];
|
||||
$repeat = sanitize_key((string) ($settings['bg_repeat'] ?? 'no-repeat'));
|
||||
if (!in_array($repeat, $valid_repeats, true)) {
|
||||
$repeat = 'no-repeat';
|
||||
}
|
||||
|
||||
$attachment = sanitize_key((string) ($settings['bg_attachment'] ?? 'fixed'));
|
||||
if (!in_array($attachment, ['fixed', 'scroll'], true)) {
|
||||
$attachment = 'fixed';
|
||||
}
|
||||
|
||||
return [
|
||||
'bg_color' => sanitize_hex_color((string) ($settings['bg_color'] ?? '')) ?: '',
|
||||
'bg_image' => esc_url_raw((string) ($settings['bg_image'] ?? '')),
|
||||
'bg_size' => $size,
|
||||
'bg_position' => $position,
|
||||
'bg_repeat' => $repeat,
|
||||
'bg_attachment' => $attachment,
|
||||
'bg_dark_color' => sanitize_hex_color((string) ($settings['bg_dark_color'] ?? '')) ?: '',
|
||||
'bg_dark_image' => esc_url_raw((string) ($settings['bg_dark_image'] ?? '')),
|
||||
'bg_overlay' => !empty($settings['bg_overlay']),
|
||||
'bg_overlay_color' => sanitize_text_field((string) ($settings['bg_overlay_color'] ?? 'rgba(0, 0, 0, 0.3)')),
|
||||
'page_bg_color' => sanitize_hex_color((string) ($settings['page_bg_color'] ?? '')) ?: '',
|
||||
'page_bg_image' => esc_url_raw((string) ($settings['page_bg_image'] ?? '')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,6 @@ trait DomainGroups {
|
||||
Integration::update_wechat_settings($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局背景设置
|
||||
*/
|
||||
public static function get_global_background_settings(): array {
|
||||
return DisplayRuntime::get_global_background_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页脚布局模板
|
||||
*/
|
||||
|
||||
@@ -14,16 +14,21 @@ trait FacadeBulk {
|
||||
* @return array{custom:bool,bg_custom:string,bg:string,color_custom:string}
|
||||
*/
|
||||
public static function get_header_theme_config(): array {
|
||||
$s = self::get_all();
|
||||
$s = self::read_domain('page_display', 'header-nav');
|
||||
|
||||
return [
|
||||
'custom' => !empty($s['header_theme_custom']),
|
||||
'bg_custom' => sanitize_hex_color((string) ($s['header_theme_bg_custom'] ?? '')) ?: '',
|
||||
'bg' => sanitize_hex_color((string) ($s['header_theme_bg'] ?? '')) ?: '',
|
||||
'bg' => self::normalize_palette_hex((string) ($s['header_theme_bg'] ?? '')),
|
||||
'color_custom' => sanitize_hex_color((string) ($s['header_theme_color_custom'] ?? '')) ?: '',
|
||||
];
|
||||
}
|
||||
|
||||
private static function normalize_palette_hex(string $value): string {
|
||||
$value = ltrim(trim($value), '#');
|
||||
return preg_match('/^[0-9a-f]{6}$/i', $value) ? '#' . strtolower($value) : '';
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
@@ -31,36 +36,32 @@ trait FacadeBulk {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_category_page_settings(): array {
|
||||
$settings = self::get_all();
|
||||
$header_style = sanitize_key((string) ($settings['category_header_style'] ?? 'cover'));
|
||||
$list_mode = sanitize_key((string) ($settings['category_list_mode'] ?? 'grid'));
|
||||
$per_page = (int) ($settings['category_per_page'] ?? 12);
|
||||
$orderby = sanitize_key((string) ($settings['category_orderby'] ?? 'modified'));
|
||||
public static function get_category_page_settings(): array {
|
||||
$settings = self::read_domain('page_display', 'category');
|
||||
$orderby_option = self::normalize_page_orderby_option($settings['cat_orderby_option'] ?? []);
|
||||
$orderby = sanitize_key((string) (($orderby_option['lists'][0] ?? 'modified')));
|
||||
$ajax_category_enabled = !empty($settings['ajax_list_cat_cat']);
|
||||
$ajax_tag_enabled = !empty($settings['ajax_list_cat_tag']);
|
||||
$orderby_enabled = !empty($settings['cat_orderby_s']);
|
||||
$post_count = !empty($settings['cat_post_count_s']);
|
||||
|
||||
if (!in_array($header_style, ['cover', 'simple', 'none'], true)) {
|
||||
$header_style = 'cover';
|
||||
}
|
||||
|
||||
if (!in_array($list_mode, ['grid', 'list', 'compact'], true)) {
|
||||
$list_mode = 'grid';
|
||||
}
|
||||
|
||||
if (!in_array($orderby, ['modified', 'date', 'views', 'comments', 'likes'], true)) {
|
||||
$orderby = 'modified';
|
||||
}
|
||||
|
||||
return [
|
||||
'header_style' => $header_style,
|
||||
'cover_enabled' => !isset($settings['category_cover_enabled']) || !empty($settings['category_cover_enabled']),
|
||||
'desc_enabled' => !isset($settings['category_desc_enabled']) || !empty($settings['category_desc_enabled']),
|
||||
'default_cover' => esc_url_raw((string) ($settings['category_default_cover'] ?? '')),
|
||||
'list_mode' => $list_mode,
|
||||
'filter_bar' => !isset($settings['category_filter_bar']) || !empty($settings['category_filter_bar']),
|
||||
'sidebar' => !isset($settings['category_sidebar']) || !empty($settings['category_sidebar']),
|
||||
'per_page' => max(4, min(48, $per_page)),
|
||||
'post_count' => !empty($settings['category_post_count']),
|
||||
'orderby' => $orderby,
|
||||
return [
|
||||
'header_style' => !empty($settings['page_cover_cat_s']) ? 'cover' : 'simple',
|
||||
'cover_enabled' => !empty($settings['page_cover_cat_s']),
|
||||
'desc_enabled' => true,
|
||||
'default_cover' => esc_url_raw((string) ($settings['cat_default_cover'] ?? '')),
|
||||
'list_mode' => 'grid',
|
||||
'filter_bar' => $ajax_category_enabled || $ajax_tag_enabled || $orderby_enabled || $post_count,
|
||||
'sidebar' => true,
|
||||
'per_page' => max(1, (int) get_option('posts_per_page', 10)),
|
||||
'post_count' => $post_count,
|
||||
'orderby' => $orderby,
|
||||
'orderby_enabled'=> $orderby_enabled,
|
||||
'orderby_option' => $orderby_option,
|
||||
'ajax_category_enabled' => $ajax_category_enabled,
|
||||
'ajax_tag_enabled' => $ajax_tag_enabled,
|
||||
'ajax_category' => is_array($settings['ajax_list_option_cat_cat'] ?? null) ? $settings['ajax_list_option_cat_cat'] : [],
|
||||
'ajax_tag' => is_array($settings['ajax_list_option_cat_tag'] ?? null) ? $settings['ajax_list_option_cat_tag'] : [],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -71,68 +72,64 @@ trait FacadeBulk {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_tag_page_settings(): array {
|
||||
$settings = self::get_all();
|
||||
$header_style = sanitize_key((string) ($settings['tag_header_style'] ?? 'simple'));
|
||||
$list_mode = sanitize_key((string) ($settings['tag_list_mode'] ?? 'grid'));
|
||||
$per_page = (int) ($settings['tag_per_page'] ?? 12);
|
||||
$orderby = sanitize_key((string) ($settings['tag_orderby'] ?? 'count'));
|
||||
public static function get_tag_page_settings(): array {
|
||||
$settings = self::read_domain('page_display', 'tag');
|
||||
$orderby_option = self::normalize_page_orderby_option($settings['tag_orderby_option'] ?? []);
|
||||
$orderby = sanitize_key((string) (($orderby_option['lists'][0] ?? 'modified')));
|
||||
$ajax_category_enabled = !empty($settings['ajax_list_tag_cat']);
|
||||
$ajax_tag_enabled = !empty($settings['ajax_list_tag_tag']);
|
||||
$orderby_enabled = !empty($settings['tag_orderby_s']);
|
||||
$post_count = !empty($settings['tag_post_count_s']);
|
||||
|
||||
if (!in_array($header_style, ['cover', 'simple', 'none'], true)) {
|
||||
$header_style = 'simple';
|
||||
}
|
||||
|
||||
if (!in_array($list_mode, ['grid', 'list'], true)) {
|
||||
$list_mode = 'grid';
|
||||
}
|
||||
|
||||
if (!in_array($orderby, ['count', 'name', 'modified'], true)) {
|
||||
$orderby = 'count';
|
||||
}
|
||||
|
||||
return [
|
||||
'header_style' => $header_style,
|
||||
return [
|
||||
'header_style' => !empty($settings['page_cover_tag_s']) ? 'cover' : 'simple',
|
||||
'cover_enabled' => !empty($settings['page_cover_tag_s']),
|
||||
'default_cover' => esc_url_raw((string) ($settings['tag_default_cover'] ?? '')),
|
||||
'list_mode' => $list_mode,
|
||||
'sidebar' => !isset($settings['tag_sidebar']) || !empty($settings['tag_sidebar']),
|
||||
'per_page' => max(4, min(48, $per_page)),
|
||||
'post_count' => !empty($settings['tag_post_count']),
|
||||
'orderby' => $orderby,
|
||||
'list_mode' => 'grid',
|
||||
'sidebar' => true,
|
||||
'per_page' => max(1, (int) get_option('posts_per_page', 10)),
|
||||
'post_count' => $post_count,
|
||||
'filter_bar' => $ajax_category_enabled || $ajax_tag_enabled || $orderby_enabled || $post_count,
|
||||
'orderby' => $orderby,
|
||||
'orderby_enabled'=> $orderby_enabled,
|
||||
'orderby_option' => $orderby_option,
|
||||
'ajax_category_enabled' => $ajax_category_enabled,
|
||||
'ajax_tag_enabled' => $ajax_tag_enabled,
|
||||
'ajax_category' => is_array($settings['ajax_list_option_tag_cat'] ?? null) ? $settings['ajax_list_option_tag_cat'] : [],
|
||||
'ajax_tag' => is_array($settings['ajax_list_option_tag_tag'] ?? null) ? $settings['ajax_list_option_tag_tag'] : [],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Misc page display settings (404, search, author).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_other_page_settings(): array {
|
||||
$settings = self::get_all();
|
||||
$message = trim((string) ($settings['page_404_message'] ?? ''));
|
||||
$search_per_page = max(4, min(48, (int) ($settings['page_search_per_page'] ?? 12)));
|
||||
$author_per_page = max(4, min(48, (int) ($settings['page_author_posts_per_page'] ?? 12)));
|
||||
|
||||
$header_style = sanitize_key((string) ($settings['page_header_style'] ?? '1'));
|
||||
/** @return array{header_style:string,header_cover_img:string} */
|
||||
public static function get_other_page_settings(): array {
|
||||
$settings = self::read_domain('page_display', 'other');
|
||||
$header_style = sanitize_key((string) ($settings['page_header_style'] ?? ''));
|
||||
if (!in_array($header_style, ['', '1', '2', '3'], true)) {
|
||||
$header_style = '1';
|
||||
}
|
||||
$header_style = '';
|
||||
}
|
||||
|
||||
return [
|
||||
'header_style' => $header_style,
|
||||
'header_cover_img' => esc_url_raw((string) ($settings['page_header_cover_img'] ?? '')),
|
||||
'page_404_message' => $message !== '' ? $message : __('页面未找到', 'aksrt'),
|
||||
'page_404_search' => !isset($settings['page_404_search']) || !empty($settings['page_404_search']),
|
||||
'page_404_popular' => !isset($settings['page_404_popular']) || !empty($settings['page_404_popular']),
|
||||
'page_404_back' => !isset($settings['page_404_back']) || !empty($settings['page_404_back']),
|
||||
'page_search_sidebar' => !isset($settings['page_search_sidebar']) || !empty($settings['page_search_sidebar']),
|
||||
'page_search_per_page' => $search_per_page,
|
||||
'page_author_cover' => !isset($settings['page_author_cover']) || !empty($settings['page_author_cover']),
|
||||
'page_author_sidebar' => !isset($settings['page_author_sidebar']) || !empty($settings['page_author_sidebar']),
|
||||
'page_author_posts_per_page' => $author_per_page,
|
||||
'header_style' => $header_style,
|
||||
'header_cover_img' => esc_url_raw((string) ($settings['page_header_cover_img'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
/** @param mixed $value */
|
||||
private static function normalize_page_orderby_option($value): array {
|
||||
$option = is_array($value) ? $value : [];
|
||||
$allowed = self::homepage_allowed_sorts();
|
||||
$lists = is_array($option['lists'] ?? null) ? $option['lists'] : [];
|
||||
$lists = array_values(array_intersect(array_map('sanitize_key', $lists), $allowed));
|
||||
if ($lists === []) {
|
||||
$lists = ['modified', 'views', 'like', 'comment_count'];
|
||||
}
|
||||
return [
|
||||
'lists' => $lists,
|
||||
'dropdown' => !empty($option['dropdown']),
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Share and poster settings.
|
||||
*
|
||||
|
||||
@@ -9,65 +9,19 @@ defined('ABSPATH') || exit;
|
||||
|
||||
class HomeCms {
|
||||
|
||||
public static function get_footer_settings(): array {
|
||||
$defaults = [
|
||||
'contacts' => [],
|
||||
'quick_links' => [],
|
||||
'more_links' => [],
|
||||
'hot_categories' => 5,
|
||||
'copyright_text' => '',
|
||||
'layout_template' => 'template_1',
|
||||
];
|
||||
$footer = get_option('aksrt_footer_settings', []);
|
||||
$merged = array_merge($defaults, is_array($footer) ? $footer : []);
|
||||
public static function get_footer_settings(): array {
|
||||
return Settings::read_domain('page_display', 'footer');
|
||||
}
|
||||
|
||||
$s = Settings::get_all();
|
||||
$bridge_keys = [
|
||||
'footer_columns', 'footer_copyright', 'footer_icp', 'footer_police',
|
||||
'footer_theme_custom', 'footer_theme_bg_custom', 'footer_theme_bg', 'footer_theme_color_custom',
|
||||
'footer_contact_wechat_img', 'footer_contact_qq', 'footer_contact_weibo', 'footer_contact_email',
|
||||
];
|
||||
foreach ($bridge_keys as $key) {
|
||||
if (isset($s[$key])) {
|
||||
$merged[$key] = $s[$key];
|
||||
}
|
||||
}
|
||||
return $merged;
|
||||
}
|
||||
|
||||
public static function get_footer_contacts(): array {
|
||||
$footer = self::get_footer_settings();
|
||||
return array_filter($footer['contacts'], fn($c) => !empty($c['enabled']));
|
||||
}
|
||||
|
||||
public static function get_quick_links(): array {
|
||||
$footer = self::get_footer_settings();
|
||||
$links = array_filter($footer['quick_links'], fn($l) => !empty($l['enabled']));
|
||||
usort($links, fn($a, $b) => ($a['sort_order'] ?? 0) <=> ($b['sort_order'] ?? 0));
|
||||
return array_values($links);
|
||||
}
|
||||
|
||||
public static function get_more_links(): array {
|
||||
$footer = self::get_footer_settings();
|
||||
$links = array_filter($footer['more_links'], fn($l) => !empty($l['enabled']));
|
||||
usort($links, fn($a, $b) => ($a['sort_order'] ?? 0) <=> ($b['sort_order'] ?? 0));
|
||||
return array_values($links);
|
||||
}
|
||||
|
||||
public static function get_hot_categories_count(): int {
|
||||
$footer = self::get_footer_settings();
|
||||
return max(1, min(20, (int) ($footer['hot_categories'] ?? 5)));
|
||||
}
|
||||
|
||||
public static function get_copyright_text(): string {
|
||||
$footer = self::get_footer_settings();
|
||||
return $footer['copyright_text'] ?: '';
|
||||
}
|
||||
public static function get_footer_template(): string {
|
||||
$template = sanitize_key((string) (self::get_footer_settings()['fcode_template'] ?? 'template_1'));
|
||||
return $template === 'template_1' ? $template : 'template_1';
|
||||
}
|
||||
|
||||
public static function get_footer_brand_settings(): array {
|
||||
$f = self::get_footer_settings();
|
||||
return [
|
||||
'm_s' => is_array($f['footer_t1_m_s'] ?? null) ? $f['footer_t1_m_s'] : ['pc', 'mobile'],
|
||||
'm_s' => !empty($f['footer_t1_m_s']) ? ['pc', 'mobile'] : ['pc'],
|
||||
'img' => (string) ($f['footer_t1_img'] ?? ''),
|
||||
'img_dark' => (string) ($f['footer_t1_img_dark'] ?? ''),
|
||||
'title' => (string) ($f['footer_t1_t'] ?? ''),
|
||||
@@ -78,7 +32,7 @@ class HomeCms {
|
||||
public static function get_footer_contact_extended_settings(): array {
|
||||
$f = self::get_footer_settings();
|
||||
return [
|
||||
'm_s' => is_array($f['footer_contact_m_s'] ?? null) ? $f['footer_contact_m_s'] : ['pc', 'mobile'],
|
||||
'm_s' => !empty($f['footer_contact_m_s']) ? ['pc', 'mobile'] : ['pc'],
|
||||
'wechat_img' => (string) ($f['footer_contact_wechat_img'] ?? ''),
|
||||
'qq' => (string) ($f['footer_contact_qq'] ?? ''),
|
||||
'weibo' => (string) ($f['footer_contact_weibo'] ?? ''),
|
||||
@@ -91,26 +45,10 @@ class HomeCms {
|
||||
|
||||
public static function get_footer_mini_img_settings(): array {
|
||||
$f = self::get_footer_settings();
|
||||
$raw_items = is_array($f['footer_mini_img'] ?? null) ? $f['footer_mini_img'] : [];
|
||||
$items = [];
|
||||
foreach ($raw_items as $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
$img = (string) ($item['img'] ?? $item['image'] ?? '');
|
||||
if ($img === '') {
|
||||
continue;
|
||||
}
|
||||
$items[] = [
|
||||
'img' => $img,
|
||||
'image' => $img,
|
||||
'text' => (string) ($item['text'] ?? $item['label'] ?? ''),
|
||||
'label' => (string) ($item['label'] ?? $item['text'] ?? ''),
|
||||
];
|
||||
}
|
||||
return [
|
||||
'm_s' => is_array($f['footer_mini_img_m_s'] ?? null) ? $f['footer_mini_img_m_s'] : ['pc', 'mobile'],
|
||||
'items' => $items,
|
||||
'm_s' => !empty($f['footer_mini_img_m_s']) ? ['pc', 'mobile'] : ['pc'],
|
||||
'items' => self::normalize_footer_images($f['footer_mini_img'] ?? []),
|
||||
'items_2' => self::normalize_footer_images($f['footer_mini_img_2'] ?? []),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -120,11 +58,11 @@ class HomeCms {
|
||||
}
|
||||
|
||||
public static function get_footer_theme_config(): array {
|
||||
$s = Settings::get_all();
|
||||
$s = self::get_footer_settings();
|
||||
return [
|
||||
'custom' => !empty($s['footer_theme_custom']),
|
||||
'bg_custom' => sanitize_hex_color((string) ($s['footer_theme_bg_custom'] ?? '')) ?: '',
|
||||
'bg' => sanitize_hex_color((string) ($s['footer_theme_bg'] ?? '')) ?: '',
|
||||
'bg' => self::normalize_palette_hex((string) ($s['footer_theme_bg'] ?? '')),
|
||||
'color_custom' => sanitize_hex_color((string) ($s['footer_theme_color_custom'] ?? '')) ?: '',
|
||||
];
|
||||
}
|
||||
@@ -149,7 +87,7 @@ class HomeCms {
|
||||
}
|
||||
|
||||
public static function homepage_allowed_sorts(): array {
|
||||
return ['modified', 'date', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand'];
|
||||
return ['modified', 'date', 'views', 'like', 'comment_count', 'favorite', 'rand'];
|
||||
}
|
||||
|
||||
public static function homepage_sort_labels(): array {
|
||||
@@ -157,16 +95,15 @@ class HomeCms {
|
||||
'modified' => __('更新', 'aksrt'),
|
||||
'date' => __('发布', 'aksrt'),
|
||||
'views' => __('浏览', 'aksrt'),
|
||||
'likes' => __('点赞', 'aksrt'),
|
||||
'comments' => __('评论', 'aksrt'),
|
||||
'favorites' => __('收藏', 'aksrt'),
|
||||
'downloads' => __('下载', 'aksrt'),
|
||||
'like' => __('点赞', 'aksrt'),
|
||||
'comment_count' => __('评论', 'aksrt'),
|
||||
'favorite' => __('收藏', 'aksrt'),
|
||||
'rand' => __('随机', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function get_homepage_fixed_settings(): array {
|
||||
$s = Settings::get_all();
|
||||
$s = Settings::read_domain('page_display', 'homepage');
|
||||
$allowed_sorts = self::homepage_allowed_sorts();
|
||||
|
||||
// Canonical Zibll-aligned primary list contract.
|
||||
@@ -176,24 +113,21 @@ class HomeCms {
|
||||
$orderby_option = isset($s['home_list1_orderby_option']) && is_array($s['home_list1_orderby_option'])
|
||||
? $s['home_list1_orderby_option']
|
||||
: [];
|
||||
$default_sort = sanitize_key((string) ($orderby_option['orderby'] ?? 'date'));
|
||||
$sort_options_raw = $orderby_option['lists'] ?? [];
|
||||
if (!is_array($sort_options_raw)) {
|
||||
$sort_options_raw = [];
|
||||
}
|
||||
$sort_options = self::normalize_sort_options($sort_options_raw);
|
||||
$default_sort = sanitize_key((string) ($sort_options[0] ?? 'modified'));
|
||||
if (!in_array($default_sort, $allowed_sorts, true)) {
|
||||
$default_sort = 'date';
|
||||
$default_sort = 'modified';
|
||||
}
|
||||
$sort_options_raw = $orderby_option['options'] ?? [];
|
||||
if (!is_array($sort_options_raw)) {
|
||||
$sort_options_raw = [];
|
||||
}
|
||||
$sort_options = self::normalize_sort_options($sort_options_raw);
|
||||
if (!in_array($default_sort, $sort_options, true)) {
|
||||
array_unshift($sort_options, $default_sort);
|
||||
$sort_options = array_values(array_unique($sort_options));
|
||||
}
|
||||
|
||||
$list_title = sanitize_text_field((string) ($s['index_list_title'] ?? ''));
|
||||
if ($list_title === '') {
|
||||
$list_title = __('最新发布', 'aksrt');
|
||||
}
|
||||
$list_title = wp_kses_post((string) ($s['index_list_title'] ?? ''));
|
||||
|
||||
$exclude_posts = self::normalize_int_array($s['home_exclude_posts'] ?? []);
|
||||
$exclude_cats = self::normalize_int_array($s['home_exclude_cats'] ?? []);
|
||||
@@ -203,22 +137,11 @@ class HomeCms {
|
||||
// Canonical Zibll keys.
|
||||
'home_posts_list_s' => $main_list_enabled,
|
||||
'home_list1_orderby_s' => $sort_bar_enabled,
|
||||
'home_list1_orderby_option' => [
|
||||
'orderby' => $default_sort,
|
||||
'options' => $sort_options,
|
||||
],
|
||||
'home_list1_orderby_option' => ['lists' => $sort_options, 'dropdown' => !empty($orderby_option['dropdown'])],
|
||||
'index_list_title' => $list_title,
|
||||
'home_exclude_posts' => $exclude_posts,
|
||||
'home_exclude_cats' => $exclude_cats,
|
||||
'home_lists' => self::normalize_homepage_tabs($tabs_raw),
|
||||
|
||||
'homepage_sidebar_mini_enabled' => !isset($s['homepage_sidebar_mini_enabled']) || !empty($s['homepage_sidebar_mini_enabled']),
|
||||
'homepage_sort_ajax_enabled' => !isset($s['homepage_sort_ajax_enabled']) || !empty($s['homepage_sort_ajax_enabled']),
|
||||
'homepage_sticky_tabs_enabled' => !isset($s['homepage_sticky_tabs_enabled']) || !empty($s['homepage_sticky_tabs_enabled']),
|
||||
'home_list_sort_dropdown' => !empty($s['home_list_sort_dropdown']),
|
||||
'homepage_tab_posts_per_page' => max(1, min(20, (int) ($s['homepage_tab_posts_per_page'] ?? 6))),
|
||||
'homepage_tab_limit' => max(1, min(12, (int) ($s['homepage_tab_limit'] ?? 4))),
|
||||
'homepage_module_banner_enabled' => !isset($s['homepage_module_banner_enabled']) || !empty($s['homepage_module_banner_enabled']),
|
||||
'home_lists' => self::normalize_homepage_tabs($tabs_raw),
|
||||
];
|
||||
|
||||
return $settings;
|
||||
@@ -238,29 +161,26 @@ class HomeCms {
|
||||
return $settings['home_lists'];
|
||||
}
|
||||
|
||||
/** @return array<int, array{term_id:int,title:string,orderby:string,enabled:bool}> */
|
||||
/** @return array<int, array{term_id:int,title:string,orderby:string}> */
|
||||
public static function home_lists(): array {
|
||||
return self::homepage_tabs();
|
||||
}
|
||||
|
||||
public static function homepage_sidebar_mini_enabled(): bool {
|
||||
$settings = self::get_homepage_fixed_settings();
|
||||
return !empty($settings['homepage_sidebar_mini_enabled']);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function homepage_default_sort(): string {
|
||||
$settings = self::get_homepage_fixed_settings();
|
||||
return $settings['home_list1_orderby_option']['orderby'] ?? 'date';
|
||||
return $settings['home_list1_orderby_option']['lists'][0] ?? 'modified';
|
||||
}
|
||||
|
||||
public static function homepage_sort_ajax_enabled(): bool {
|
||||
$settings = self::get_homepage_fixed_settings();
|
||||
return !empty($settings['homepage_sort_ajax_enabled']);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function homepage_sticky_tabs_enabled(): bool {
|
||||
$settings = self::get_homepage_fixed_settings();
|
||||
return !empty($settings['homepage_sticky_tabs_enabled']);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function home_posts_list_enabled(): bool {
|
||||
@@ -278,14 +198,14 @@ class HomeCms {
|
||||
return !empty($settings['home_list1_orderby_s']);
|
||||
}
|
||||
|
||||
public static function home_list_sort_options(): array {
|
||||
$settings = self::get_homepage_fixed_settings();
|
||||
return $settings['home_list1_orderby_option']['options'] ?? ['modified', 'date', 'views', 'likes', 'comments'];
|
||||
public static function home_list_sort_options(): array {
|
||||
$settings = self::get_homepage_fixed_settings();
|
||||
return $settings['home_list1_orderby_option']['lists'] ?? ['modified', 'views', 'like', 'comment_count'];
|
||||
}
|
||||
|
||||
public static function home_list_sort_dropdown(): bool {
|
||||
$settings = self::get_homepage_fixed_settings();
|
||||
return !empty($settings['home_list_sort_dropdown']);
|
||||
return !empty($settings['home_list1_orderby_option']['dropdown']);
|
||||
}
|
||||
|
||||
public static function home_exclude_post_ids(): array {
|
||||
@@ -299,13 +219,7 @@ class HomeCms {
|
||||
}
|
||||
|
||||
public static function homepage_tab_posts_per_page(): int {
|
||||
$settings = self::get_homepage_fixed_settings();
|
||||
return (int) $settings['homepage_tab_posts_per_page'];
|
||||
}
|
||||
|
||||
public static function homepage_tab_limit(): int {
|
||||
$settings = self::get_homepage_fixed_settings();
|
||||
return (int) $settings['homepage_tab_limit'];
|
||||
return 6;
|
||||
}
|
||||
|
||||
public static function get_paid_content_settings(): array {
|
||||
@@ -361,8 +275,8 @@ class HomeCms {
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($result)) {
|
||||
$result = ['modified', 'date', 'views', 'likes', 'comments'];
|
||||
if (empty($result)) {
|
||||
$result = ['modified', 'views', 'like', 'comment_count'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
@@ -389,22 +303,46 @@ class HomeCms {
|
||||
continue;
|
||||
}
|
||||
|
||||
$orderby = sanitize_key($tab['orderby'] ?? 'date');
|
||||
$orderby = sanitize_key($tab['orderby'] ?? 'modified');
|
||||
if (!in_array($orderby, $allowed_orderby, true)) {
|
||||
$orderby = 'date';
|
||||
$orderby = 'modified';
|
||||
}
|
||||
|
||||
$tabs[] = [
|
||||
'term_id' => $term_id,
|
||||
'title' => sanitize_text_field($tab['title'] ?? ''),
|
||||
'title' => wp_kses_post((string) ($tab['title'] ?? '')),
|
||||
'orderby' => $orderby,
|
||||
'enabled' => !empty($tab['enabled']),
|
||||
];
|
||||
}
|
||||
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
private static function normalize_footer_images($raw_items): array {
|
||||
if (!is_array($raw_items)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($raw_items as $item) {
|
||||
if (!is_array($item) || empty($item['image'])) {
|
||||
continue;
|
||||
}
|
||||
$items[] = [
|
||||
'image' => (string) $item['image'],
|
||||
'text' => (string) ($item['text'] ?? ''),
|
||||
'link' => is_array($item['link'] ?? null) ? $item['link'] : [],
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
private static function normalize_palette_hex(string $value): string {
|
||||
$value = ltrim(trim($value), '#');
|
||||
return preg_match('/^[0-9a-f]{6}$/i', $value) ? '#' . strtolower($value) : '';
|
||||
}
|
||||
|
||||
private static function parse_slug_list(string $raw, array $fallback = []): array {
|
||||
$items = preg_split('/[\s,]+/', $raw, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$normalized = [];
|
||||
|
||||
@@ -48,7 +48,7 @@ trait HomepageReader {
|
||||
/**
|
||||
* Structured homepage tabs.
|
||||
*
|
||||
* @return array<int, array{term_id:int, title:string, orderby:string, enabled:bool}>
|
||||
* @return array<int, array{term_id:int, title:string, orderby:string}>
|
||||
*/
|
||||
public static function homepage_tabs(): array {
|
||||
return HomeCms::homepage_tabs();
|
||||
@@ -144,30 +144,6 @@ trait HomepageReader {
|
||||
return HomeCms::homepage_tab_posts_per_page();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum number of category tabs to display.
|
||||
*/
|
||||
public static function homepage_tab_limit(): int {
|
||||
return HomeCms::homepage_tab_limit();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取顶部组件图标卡片配置
|
||||
* @return array
|
||||
*/
|
||||
public static function homepage_module_banner_enabled(): bool {
|
||||
return DisplayRuntime::homepage_module_banner_enabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Global slider configuration shared by Hero and Banner.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function get_slider_config(): array {
|
||||
return DisplayRuntime::get_slider_config();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页脚配色配置
|
||||
*/
|
||||
|
||||
@@ -38,13 +38,11 @@ class Settings {
|
||||
'aksrt_oauth',
|
||||
'aksrt_captcha_config',
|
||||
'aksrt_moderation_config',
|
||||
'aksrt_footer_settings',
|
||||
'aksrt_footer_tabbar',
|
||||
'aksrt_floating_buttons',
|
||||
'aksrt_credit_settings',
|
||||
'aksrt_notification_settings',
|
||||
'aksrt_friend_links',
|
||||
'aksrt_banners',
|
||||
'aksrt_category_recommend',
|
||||
'aksrt_identity_secrets',
|
||||
];
|
||||
@@ -719,27 +717,6 @@ class Settings {
|
||||
|
||||
|
||||
|
||||
/** 获取Banner列表 */
|
||||
public static function get_banners(string $position = ''): array {
|
||||
return DisplayRuntime::get_banners($position);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 保存Banner */
|
||||
public static function save_banner(array $data): int {
|
||||
return DisplayRuntime::save_banner($data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 删除Banner */
|
||||
public static function delete_banner(string $id): void {
|
||||
DisplayRuntime::delete_banner($id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 获取友链列表 */
|
||||
public static function get_friend_links(): array {
|
||||
return DisplayRuntime::get_friend_links();
|
||||
@@ -841,7 +818,7 @@ class Settings {
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 获取底部信息完整配置(统一 aksrt_footer_settings + aksrt_settings page-display footer 字段)
|
||||
* 获取页面&显示中的页脚配置。
|
||||
* @return array
|
||||
*/
|
||||
public static function get_footer_settings(): array {
|
||||
@@ -850,41 +827,6 @@ class Settings {
|
||||
|
||||
|
||||
|
||||
/** 获取联系图标列表(仅返回启用的) */
|
||||
public static function get_footer_contacts(): array {
|
||||
return HomeCms::get_footer_contacts();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 获取快速链接列表(仅返回启用的,按排序) */
|
||||
public static function get_quick_links(): array {
|
||||
return HomeCms::get_quick_links();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 获取更多链接列表(仅返回启用的,按排序) */
|
||||
public static function get_more_links(): array {
|
||||
return HomeCms::get_more_links();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 获取热门分类显示数量 */
|
||||
public static function get_hot_categories_count(): int {
|
||||
return HomeCms::get_hot_categories_count();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 获取版权文字 */
|
||||
public static function get_copyright_text(): string {
|
||||
return HomeCms::get_copyright_text();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取页脚品牌列(第一列)配置
|
||||
* @return array {m_s, img, img_dark, title, custom_code}
|
||||
|
||||
@@ -101,14 +101,14 @@ class ThemeSearch {
|
||||
}
|
||||
|
||||
public static function search_custom_key(): array {
|
||||
$raw = Settings::get_value('search_custom_key', ['title', 'content', 'excerpt', 'post_tag', 'forum_topic', 'shop_cat', 'shop_discount']);
|
||||
$raw = Settings::get_value('search_custom_key', ['title', 'content', 'excerpt', 'category', 'post_tag']);
|
||||
if (is_array($raw)) {
|
||||
$items = $raw;
|
||||
} else {
|
||||
$items = preg_split('/[,,\s]+/', (string) $raw) ?: [];
|
||||
}
|
||||
$items = array_map(static fn($k) => trim(sanitize_key((string) $k)), $items);
|
||||
$valid = ['title', 'content', 'excerpt', 'category', 'post_tag', 'topics', 'coment', 'user', 'forum_topic', 'forum_tag', 'shop_cat', 'shop_tag', 'shop_discount'];
|
||||
$valid = ['title', 'content', 'excerpt', 'category', 'post_tag', 'coment', 'user'];
|
||||
return array_values(array_intersect(array_filter(array_unique($items)), $valid));
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ class ThemeSearch {
|
||||
$terms = [(string) $query->get('s')];
|
||||
}
|
||||
$groups = [];
|
||||
$taxonomies = ['post_tag', 'category', 'topics', 'forum_tag', 'forum_topic', 'shop_tag', 'shop_cat', 'shop_discount'];
|
||||
$taxonomies = ['post_tag', 'category'];
|
||||
foreach ($terms as $term) {
|
||||
$like = (!empty($query->query_vars['exact']) ? '' : '%') . $wpdb->esc_like((string) $term) . (!empty($query->query_vars['exact']) ? '' : '%');
|
||||
$parts = [];
|
||||
@@ -212,7 +212,7 @@ class ThemeSearch {
|
||||
if (in_array('user', $keys, true)) {
|
||||
$join .= " LEFT JOIN {$wpdb->users} AS aksrt_search_users ON ({$wpdb->posts}.post_author = aksrt_search_users.ID)";
|
||||
}
|
||||
if (array_intersect(['post_tag', 'category', 'topics', 'forum_tag', 'forum_topic', 'shop_tag', 'shop_cat', 'shop_discount'], $keys)) {
|
||||
if (array_intersect(['post_tag', 'category'], $keys)) {
|
||||
$join .= " LEFT JOIN {$wpdb->term_relationships} AS aksrt_search_tr ON ({$wpdb->posts}.ID = aksrt_search_tr.object_id)";
|
||||
$join .= " LEFT JOIN {$wpdb->term_taxonomy} AS aksrt_search_tt ON (aksrt_search_tr.term_taxonomy_id = aksrt_search_tt.term_taxonomy_id)";
|
||||
$join .= " LEFT JOIN {$wpdb->terms} AS aksrt_search_terms ON (aksrt_search_tt.term_id = aksrt_search_terms.term_id)";
|
||||
|
||||
@@ -15,65 +15,53 @@ class UserRuntime {
|
||||
* @return array
|
||||
*/
|
||||
public static function get_user_home_settings(): array {
|
||||
$settings = Settings::get_all();
|
||||
$allowed_tabs = ['posts', 'comments', 'favorites', 'credits', 'follow'];
|
||||
|
||||
$parse_csv = function (string $key, string $fallback) use ($settings, $allowed_tabs): array {
|
||||
$raw = $settings[$key] ?? $fallback;
|
||||
$items = is_array($raw) ? $raw : preg_split('/[\s,]+/', (string) $raw, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$clean = [];
|
||||
foreach ((array) $items as $v) {
|
||||
$v = sanitize_key((string) $v);
|
||||
if (in_array($v, $allowed_tabs, true) && !in_array($v, $clean, true)) {
|
||||
$clean[] = $v;
|
||||
}
|
||||
}
|
||||
return $clean;
|
||||
};
|
||||
|
||||
// Public profile tabs supported by this theme.
|
||||
$normalized_tabs = $parse_csv('user_home_tabs', 'posts,favorites,comments,follow');
|
||||
if (empty($normalized_tabs)) {
|
||||
$normalized_tabs = ['posts', 'favorites', 'comments', 'follow'];
|
||||
}
|
||||
|
||||
$raw_order = $parse_csv('user_home_tabs_order', '');
|
||||
$ordered = [];
|
||||
foreach ($raw_order as $tab) {
|
||||
if (in_array($tab, $normalized_tabs, true)) {
|
||||
$ordered[] = $tab;
|
||||
}
|
||||
}
|
||||
foreach ($normalized_tabs as $tab) {
|
||||
if (!in_array($tab, $ordered, true)) {
|
||||
$ordered[] = $tab;
|
||||
}
|
||||
}
|
||||
|
||||
$pc_hide = $parse_csv('user_home_tabs_pc_hide', '');
|
||||
$mobile_hide = $parse_csv('user_home_tabs_mobile_hide', '');
|
||||
$settings = Settings::read_domain('page_display', 'user-home');
|
||||
$tab_options = is_array($settings['author_main_tab_opt'] ?? null)
|
||||
? $settings['author_main_tab_opt']
|
||||
: [
|
||||
'post' => ['pc_s' => true, 'm_s' => true],
|
||||
'favorite' => ['pc_s' => true, 'm_s' => true],
|
||||
'comment' => ['pc_s' => true, 'm_s' => true],
|
||||
'follow' => ['pc_s' => true, 'm_s' => true],
|
||||
];
|
||||
$map = ['post' => 'posts', 'favorite' => 'favorites', 'comment' => 'comments', 'follow' => 'follow'];
|
||||
$ordered = [];
|
||||
$pc_hide = [];
|
||||
$mobile_hide = [];
|
||||
foreach ($tab_options as $key => $option) {
|
||||
$key = sanitize_key((string) $key);
|
||||
if (!isset($map[$key]) || !is_array($option)) {
|
||||
continue;
|
||||
}
|
||||
$tab = $map[$key];
|
||||
$ordered[] = $tab;
|
||||
if (empty($option['pc_s'])) {
|
||||
$pc_hide[] = $tab;
|
||||
}
|
||||
if (empty($option['m_s'])) {
|
||||
$mobile_hide[] = $tab;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'route_identifier' => (($settings['user_home_route_identifier'] ?? 'nicename') === 'uid') ? 'uid' : 'nicename',
|
||||
'tabs' => $normalized_tabs,
|
||||
'route_identifier' => 'nicename',
|
||||
'tabs' => $ordered,
|
||||
'tabs_order' => $ordered,
|
||||
'tabs_pc_hide' => $pc_hide,
|
||||
'tabs_mobile_hide' => $mobile_hide,
|
||||
// Public author page is full-width (Zibll author.php). Sidebar option kept as no-op reserved key.
|
||||
'sidebar' => false,
|
||||
'cover' => !isset($settings['user_home_cover']) || !empty($settings['user_home_cover']),
|
||||
'follow' => !isset($settings['user_home_follow']) || !empty($settings['user_home_follow']),
|
||||
'posts_per_page' => max(5, min(50, (int) ($settings['user_home_posts_per_page'] ?? 10))),
|
||||
'avatar' => !isset($settings['user_home_avatar']) || !empty($settings['user_home_avatar']),
|
||||
'name' => !isset($settings['user_home_name']) || !empty($settings['user_home_name']),
|
||||
'desc' => !isset($settings['user_home_desc']) || !empty($settings['user_home_desc']),
|
||||
// Body stats row removed; counts live in tabs like Zibll.
|
||||
'cover' => true,
|
||||
'follow' => true,
|
||||
'posts_per_page' => max(1, (int) get_option('posts_per_page', 10)),
|
||||
'avatar' => true,
|
||||
'name' => true,
|
||||
'desc' => true,
|
||||
'stats' => false,
|
||||
'social' => !isset($settings['user_home_social']) || !empty($settings['user_home_social']),
|
||||
'social' => true,
|
||||
'tab_swiper' => !empty($settings['author_main_tab_swiper']),
|
||||
'message' => !isset($settings['user_home_message']) || !empty($settings['user_home_message']),
|
||||
'private' => !isset($settings['user_home_private']) || !empty($settings['user_home_private']),
|
||||
'paging_ajax' => !isset($settings['user_home_paging_ajax']) || !empty($settings['user_home_paging_ajax']),
|
||||
'message' => true,
|
||||
'private' => true,
|
||||
'paging_ajax' => true,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,6 @@ Tags: blog, two-columns, right-sidebar, custom-menu, featured-images, translatio
|
||||
AKSRT WordPress Theme v3.0 - Fresh Frontend + Admin Backend
|
||||
|
||||
Fresh frontend templates (dark/light mode, responsive).
|
||||
Full admin panel preserved (settings, OAuth, banners, etc).
|
||||
Full admin panel preserved (settings, OAuth, content tools, etc).
|
||||
============================================================
|
||||
*/
|
||||
|
||||
@@ -31,55 +31,28 @@ $has_sidebar = function_exists('aksrt_archive_has_sidebar')
|
||||
: (!empty($archive_settings['show_sidebar']) && is_active_sidebar('tag_sidebar'));
|
||||
$description = !empty($archive_settings['show_desc']) ? wp_kses_post((string) $archive_settings['description']) : '';
|
||||
$header_style = (string) $archive_settings['header_style'];
|
||||
$filter_settings = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_post_filter_settings()
|
||||
: ['enabled' => false, 'sort' => false, 'ajax_enabled' => false];
|
||||
if (!empty($filter_settings['enabled']) && !in_array('tag', $filter_settings['custom_filter_show'] ?? ['cat', 'tag'], true)) {
|
||||
$filter_settings['enabled'] = false;
|
||||
}
|
||||
$ajax_filter = !empty($filter_settings['ajax_enabled']) && !empty($filter_settings['enabled']);
|
||||
$sort_state = function_exists('aksrt_archive_get_sort_state') ? aksrt_archive_get_sort_state() : ['current' => 'latest', 'links' => []];
|
||||
$ajax_filter = !empty($archive_settings['filter_bar']);
|
||||
$sort_state = function_exists('aksrt_archive_get_sort_state')
|
||||
? aksrt_archive_get_sort_state((array) ($archive_settings['orderby_option'] ?? []))
|
||||
: ['current' => 'date', 'links' => []];
|
||||
$current_sort = $sort_state['current'];
|
||||
$sort_links = $sort_state['links'];
|
||||
$render_config = function_exists('aksrt_archive_get_render_config')
|
||||
? aksrt_archive_get_render_config(['context' => 'tag'])
|
||||
: ['context' => 'tag'];
|
||||
|
||||
$cat_filter_terms = [];
|
||||
$current_filter_cat = sanitize_text_field((string) get_query_var('aksrt_filter_cat', ''));
|
||||
if ($ajax_filter && !empty($filter_settings['tag_show_cat'])) {
|
||||
$cat_filter_terms = get_terms([
|
||||
'taxonomy' => 'category',
|
||||
'hide_empty' => true,
|
||||
'parent' => 0,
|
||||
'number' => 20,
|
||||
]);
|
||||
if (is_wp_error($cat_filter_terms)) {
|
||||
$cat_filter_terms = [];
|
||||
$filter_groups = [];
|
||||
if (!empty($archive_settings['ajax_category_enabled']) && function_exists('aksrt_archive_build_term_filter_group')) {
|
||||
$group = aksrt_archive_build_term_filter_group('category', (array) ($archive_settings['ajax_category'] ?? []), __('分类', 'aksrt'), $ajax_filter);
|
||||
if ($group !== []) {
|
||||
$filter_groups[] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
$filter_groups = [];
|
||||
if (!empty($cat_filter_terms)) {
|
||||
$chips = [[
|
||||
'label' => __('全部', 'aksrt'),
|
||||
'url' => remove_query_arg(['aksrt_filter_cat']),
|
||||
'active' => $current_filter_cat === '',
|
||||
'attrs' => $ajax_filter ? ' data-filter-cat=""' : '',
|
||||
]];
|
||||
foreach ($cat_filter_terms as $cat_term) {
|
||||
$is_current_cat = ($current_filter_cat !== '' && (int) $current_filter_cat === (int) $cat_term->term_id);
|
||||
$chips[] = [
|
||||
'label' => $cat_term->name,
|
||||
'url' => add_query_arg('aksrt_filter_cat', (int) $cat_term->term_id),
|
||||
'active' => $is_current_cat,
|
||||
'attrs' => $ajax_filter ? ' data-filter-cat="' . (int) $cat_term->term_id . '"' : '',
|
||||
];
|
||||
if (!empty($archive_settings['ajax_tag_enabled']) && function_exists('aksrt_archive_build_term_filter_group')) {
|
||||
$group = aksrt_archive_build_term_filter_group('post_tag', (array) ($archive_settings['ajax_tag'] ?? []), __('标签', 'aksrt'), $ajax_filter);
|
||||
if ($group !== []) {
|
||||
$filter_groups[] = $group;
|
||||
}
|
||||
$filter_groups[] = [
|
||||
'label' => __('分类', 'aksrt'),
|
||||
'chips' => $chips,
|
||||
];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_archive_open_shell')) {
|
||||
@@ -112,17 +85,18 @@ if (function_exists('aksrt_archive_open_shell')) {
|
||||
?>
|
||||
|
||||
<?php
|
||||
if (!empty($filter_settings['enabled']) && function_exists('aksrt_archive_render_filter_bar')) {
|
||||
if (!empty($archive_settings['filter_bar']) && function_exists('aksrt_archive_render_filter_bar')) {
|
||||
aksrt_archive_render_filter_bar([
|
||||
'ajax' => $ajax_filter,
|
||||
'ajax_archive' => 'tag',
|
||||
'term_id' => (int) $term->term_id,
|
||||
'aria_label' => __('标签工具条', 'aksrt'),
|
||||
'meta_text' => sprintf(_n('%d 篇文章', '%d 篇文章', (int) $term->count, 'aksrt'), (int) $term->count),
|
||||
'meta_text' => !empty($archive_settings['post_count']) ? sprintf(_n('%d 篇文章', '%d 篇文章', (int) $term->count, 'aksrt'), (int) $term->count) : '',
|
||||
'groups' => $filter_groups,
|
||||
'sort_enabled' => !empty($filter_settings['sort']),
|
||||
'sort_enabled' => !empty($archive_settings['orderby_enabled']),
|
||||
'sort_current' => $current_sort,
|
||||
'sort_links' => $sort_links,
|
||||
'sort_labels' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::homepage_sort_labels() : [],
|
||||
'sort_as_main' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Home Module · Banner — v4.0 Zibll-Inspired
|
||||
*
|
||||
* 内联轮播条,使用与 header-slider 相同的 Swiper 架构,
|
||||
* 但以圆角卡片形式嵌入内容流中(非全宽)。
|
||||
*
|
||||
* 支持配置:
|
||||
* - 自动播放
|
||||
* - 圆角卡片样式
|
||||
* - 标题/副标题叠加
|
||||
* - 点击跳转
|
||||
*
|
||||
* @package AKSRT
|
||||
* @version 4.0.0
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (class_exists('\AKSRT\Settings\Settings') && !\AKSRT\Settings\Settings::homepage_module_banner_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$banners = function_exists('aksrt_get_banners') ? aksrt_get_banners(8) : [];
|
||||
if (empty($banners)) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo '<div class="home-module home-module--banner home-module--banner-empty box-body">';
|
||||
echo '<p class="muted-2-color">' . esc_html__('No Banner slides found. Add banner slides in admin.', 'aksrt') . '</p>';
|
||||
if (function_exists('aksrt_render_admin_quick_link')) {
|
||||
echo '<div class="home-module__admin-tools">';
|
||||
aksrt_render_admin_quick_link('page-display/banner', __('Banner 轮播管理', 'aksrt'));
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$cfg = get_query_var('aksrt_module_config', []);
|
||||
if (!is_array($cfg)) $cfg = [];
|
||||
|
||||
$slide_count = count($banners);
|
||||
|
||||
// Swiper config — Banner uses a subset of the global slider config
|
||||
$slider_cfg = function_exists('aksrt_get_slider_config') ? aksrt_get_slider_config() : [];
|
||||
$swiper_config = [
|
||||
'effect' => 'slide', // Banner always uses slide effect
|
||||
'loop' => $slide_count > 1 && ($slider_cfg['loop'] ?? true),
|
||||
'speed' => 500,
|
||||
'autoplay' => $slide_count > 1 ? ['delay' => ($slider_cfg['autoplay_interval'] ?? 5000), 'disableOnInteraction' => false] : false,
|
||||
'pagination' => false,
|
||||
'navigation' => !empty($slider_cfg['navigation']),
|
||||
'keyboard' => ['enabled' => true],
|
||||
'spaceBetween' => ($slider_cfg['space_between'] ?? 0),
|
||||
];
|
||||
?>
|
||||
<div class="home-module home-module--banner slide-widget mb20">
|
||||
<div class="banner-module zib-slider">
|
||||
<div class="swiper new-swiper banner-module__swiper slide-widget-swiper" data-swiper-config="<?php echo esc_attr(wp_json_encode($swiper_config)); ?>">
|
||||
<div class="swiper-wrapper">
|
||||
<?php foreach ($banners as $index => $slide) :
|
||||
$device_vis = $slide['device_visibility'] ?? 'all';
|
||||
$slide_classes = 'swiper-slide';
|
||||
if ($device_vis === 'pc') {
|
||||
$slide_classes .= ' banner-slide--pc-only';
|
||||
} elseif ($device_vis === 'mobile') {
|
||||
$slide_classes .= ' banner-slide--mobile-only';
|
||||
}
|
||||
$link_target = !empty($slide['link_target']) ? $slide['link_target'] : '_self';
|
||||
?>
|
||||
<div class="<?php echo esc_attr($slide_classes); ?>"<?php if (!empty($slide['link'])) : ?> data-link="<?php echo esc_url($slide['link']); ?>" data-target="<?php echo esc_attr($link_target); ?>"<?php endif; ?>>
|
||||
<div class="banner-module__slide-bg">
|
||||
<?php if (!empty($slide['image'])) : ?>
|
||||
<img<?php echo aksrt_get_lazy_attr('lazy_sider', (string) $slide['image'], 'fit-cover', aksrt_get_lazy_thumb('lg')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
alt="<?php echo esc_attr($slide['title'] ?: get_bloginfo('name')); ?>"
|
||||
decoding="async"
|
||||
>
|
||||
<?php else : ?>
|
||||
<div class="banner-module__slide-fallback"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="banner-module__slide-overlay"></div>
|
||||
<?php
|
||||
$show_text = !isset($slide['show_text']) || $slide['show_text'];
|
||||
$has_title = !empty($slide['title']);
|
||||
$has_subtitle = !empty($slide['subtitle']);
|
||||
?>
|
||||
<?php if ($show_text && ($has_title || $has_subtitle)) : ?>
|
||||
<div class="banner-module__content">
|
||||
<?php if ($has_title) : ?>
|
||||
<h3 class="banner-module__title"><?php echo esc_html($slide['title']); ?></h3>
|
||||
<?php endif; ?>
|
||||
<?php if ($has_subtitle) : ?>
|
||||
<p class="banner-module__subtitle"><?php echo esc_html($slide['subtitle']); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($slide_count > 1) : ?>
|
||||
<!-- 前后箭头 -->
|
||||
<div class="banner-module__prev swiper-button-prev"></div>
|
||||
<div class="banner-module__next swiper-button-next"></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (function_exists('aksrt_render_admin_quick_link')) : ?>
|
||||
<div class="home-module__admin-tools">
|
||||
<?php aksrt_render_admin_quick_link('page-display/banner', __('Banner 轮播管理', 'aksrt')); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Home Module · Button Group
|
||||
*
|
||||
* config: { title?, buttons: [{ label, url, style?, icon? }], align? }
|
||||
*
|
||||
* 按钮组模块,用于 CTA 引导。
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$cfg = get_query_var('aksrt_module_config', []);
|
||||
if (!is_array($cfg)) $cfg = [];
|
||||
|
||||
$title = (string) ($cfg['title'] ?? '');
|
||||
$buttons = isset($cfg['buttons']) && is_array($cfg['buttons']) ? $cfg['buttons'] : [];
|
||||
$align = sanitize_key((string) ($cfg['align'] ?? 'center'));
|
||||
|
||||
if (empty($buttons)) return;
|
||||
?>
|
||||
|
||||
<section class="home-module home-module--button-group container" style="text-align:<?php echo esc_attr($align); ?>">
|
||||
<?php if ($title !== '') : ?>
|
||||
<h2 class="home-module__title"><?php echo esc_html($title); ?></h2>
|
||||
<?php endif; ?>
|
||||
<div class="home-module__buttons">
|
||||
<?php foreach ($buttons as $btn) :
|
||||
$label = (string) ($btn['label'] ?? '');
|
||||
$url = (string) ($btn['url'] ?? '#');
|
||||
$style = sanitize_key((string) ($btn['style'] ?? 'primary'));
|
||||
$icon = (string) ($btn['icon'] ?? '');
|
||||
if ($label === '') continue;
|
||||
?>
|
||||
<a href="<?php echo esc_url($url); ?>" class="aksrt-btn aksrt-btn--<?php echo esc_attr($style); ?>">
|
||||
<?php if ($icon !== '') : ?>
|
||||
<i class="<?php echo esc_attr($icon); ?>" aria-hidden="true"></i>
|
||||
<?php endif; ?>
|
||||
<span><?php echo esc_html($label); ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Home Module · Category
|
||||
*
|
||||
* config: { cat_id, count, layout, columns, title? }
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$cfg = get_query_var('aksrt_module_config', []);
|
||||
if (!is_array($cfg)) $cfg = [];
|
||||
|
||||
$cat_id = isset($cfg['cat_id']) ? (int) $cfg['cat_id'] : 0;
|
||||
$count = max(1, min(20, (int) ($cfg['count'] ?? 8)));
|
||||
$layout = (string) ($cfg['layout'] ?? 'card');
|
||||
$columns = max(1, min(6, (int) ($cfg['columns'] ?? 4)));
|
||||
$title = (string) ($cfg['title'] ?? '');
|
||||
|
||||
if (!in_array($layout, ['card', 'list'], true)) {
|
||||
$layout = 'card';
|
||||
}
|
||||
|
||||
$args = [
|
||||
'post_type' => 'post',
|
||||
'posts_per_page' => $count,
|
||||
'post_status' => 'publish',
|
||||
'no_found_rows' => true,
|
||||
];
|
||||
if ($cat_id > 0) {
|
||||
$args['cat'] = $cat_id;
|
||||
}
|
||||
|
||||
$query = new WP_Query($args);
|
||||
if (!$query->have_posts()) {
|
||||
wp_reset_postdata();
|
||||
return;
|
||||
}
|
||||
|
||||
$display_title = $title;
|
||||
if ($display_title === '' && $cat_id > 0) {
|
||||
$term = get_term($cat_id, 'category');
|
||||
if ($term && !is_wp_error($term)) $display_title = $term->name;
|
||||
}
|
||||
$more_link = $cat_id > 0 ? get_category_link($cat_id) : '';
|
||||
?>
|
||||
|
||||
<section class="home-module home-module--category container" aria-label="<?php echo esc_attr($display_title ?: __('分类', 'aksrt')); ?>">
|
||||
<header class="home-module__header">
|
||||
<h2 class="home-module__title"><?php echo esc_html($display_title ?: __('精选内容', 'aksrt')); ?></h2>
|
||||
<?php if ($more_link) : ?>
|
||||
<a class="home-module__more" href="<?php echo esc_url($more_link); ?>">
|
||||
<?php esc_html_e('查看更多', 'aksrt'); ?> <i class="fa fas fa-arrow-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</header>
|
||||
|
||||
<div class="posts-row ajaxpager">
|
||||
<?php
|
||||
aksrt_render_post_list([
|
||||
'type' => $layout === 'card' ? 'card' : 'list',
|
||||
'is_home' => true,
|
||||
'skip_wrapper' => true,
|
||||
], $query);
|
||||
?>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,115 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Home Module · Category Recommend Cards
|
||||
*
|
||||
* 4 张分类卡片 2×2 布局,每张卡片包含:
|
||||
* 上部:封面图 + 浏览量胶囊 + 分类名/描述 + 文章数 + 更多链接
|
||||
* 下部:5 篇最新文章列表(标题 + 相对发布时间)
|
||||
*
|
||||
* config: { title?, cat_ids[], show_views?, show_dates? }
|
||||
*
|
||||
* 封面图来源:分类 term meta → 最新文章缩略图 → 占位图
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$cfg = get_query_var('aksrt_module_config', []);
|
||||
if (!is_array($cfg)) $cfg = [];
|
||||
|
||||
$module_title = (string) ($cfg['title'] ?? __('分类推荐', 'aksrt'));
|
||||
$cat_ids = array_map('absint', (array) ($cfg['cat_ids'] ?? []));
|
||||
$cat_ids = array_slice(array_filter($cat_ids, fn($id) => $id > 0), 0, 4);
|
||||
$show_views = !isset($cfg['show_views']) || !empty($cfg['show_views']);
|
||||
$show_dates = !isset($cfg['show_dates']) || !empty($cfg['show_dates']);
|
||||
|
||||
if (empty($cat_ids)) return;
|
||||
|
||||
$placeholder = aksrt_get_lazy_thumb('lg');
|
||||
|
||||
// 组装卡片数据
|
||||
$cards = [];
|
||||
foreach ($cat_ids as $idx => $cat_id) {
|
||||
$term = get_term($cat_id, 'category');
|
||||
if (!$term || is_wp_error($term)) continue;
|
||||
|
||||
// 封面图
|
||||
$cover_url = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_category_cover_url($cat_id)
|
||||
: $placeholder;
|
||||
|
||||
// 分类描述(优先 term meta 推荐描述,其次原生 description)
|
||||
$desc = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_category_recommend_desc($cat_id)
|
||||
: (string) $term->description;
|
||||
|
||||
// 5 篇最新文章
|
||||
$posts = get_posts([
|
||||
'category__in' => [$cat_id],
|
||||
'posts_per_page' => 5,
|
||||
'post_status' => 'publish',
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'no_found_rows' => true,
|
||||
]);
|
||||
|
||||
// 浏览量
|
||||
$views = $show_views ? aksrt_get_category_total_views($cat_id) : 0;
|
||||
|
||||
$cards[] = [
|
||||
'term' => $term,
|
||||
'cover' => $cover_url,
|
||||
'desc' => $desc,
|
||||
'posts' => $posts,
|
||||
'views' => $views,
|
||||
'count' => (int) $term->count,
|
||||
'link' => get_category_link($cat_id),
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($cards)) return;
|
||||
?>
|
||||
|
||||
<section class="home-module home-module--cat-cards" aria-label="<?php echo esc_attr($module_title); ?>">
|
||||
<header class="home-module__header">
|
||||
<h2 class="home-module__title"><?php echo esc_html($module_title); ?></h2>
|
||||
</header>
|
||||
|
||||
<div class="cat-cards-grid">
|
||||
<?php foreach ($cards as $card) : ?>
|
||||
<div class="cat-card">
|
||||
<div class="cat-card__top">
|
||||
<div class="cat-card__banner">
|
||||
<?php if ($show_views && $card['views'] > 0) : ?>
|
||||
<span class="cat-card__views"><?php echo esc_html(function_exists('aksrt_format_number') ? aksrt_format_number($card['views']) : number_format_i18n($card['views'])); ?></span>
|
||||
<?php endif; ?>
|
||||
<img<?php echo aksrt_get_lazy_attr('lazy_cover', (string) $card['cover'], 'cat-card__cover', $placeholder); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="<?php echo esc_attr($card['term']->name); ?>">
|
||||
</div>
|
||||
<div class="cat-card__info">
|
||||
<div>
|
||||
<div class="cat-card__name"><?php echo esc_html($card['term']->name); ?></div>
|
||||
<?php if ($card['desc'] !== '') : ?>
|
||||
<div class="cat-card__desc"><?php echo esc_html($card['desc']); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="cat-card__foot">
|
||||
<span><?php printf(esc_html__('%d篇文章', 'aksrt'), $card['count']); ?></span>
|
||||
<a href="<?php echo esc_url($card['link']); ?>" class="cat-card__more"><?php esc_html_e('>更多文章', 'aksrt'); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($card['posts'])) : ?>
|
||||
<ul class="cat-card__list">
|
||||
<?php foreach ($card['posts'] as $p) : ?>
|
||||
<li>
|
||||
<a href="<?php echo esc_url(get_permalink($p->ID)); ?>"><?php echo esc_html(get_the_title($p->ID)); ?></a>
|
||||
<?php if ($show_dates) : ?>
|
||||
<span class="cat-card__time"><?php echo esc_html(aksrt_time_ago($p->post_date)); ?></span>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Home Module · Data Grid
|
||||
*
|
||||
* config: { title?, items: [{ value, label, icon? }], columns? }
|
||||
*
|
||||
* 数据展示网格,用于统计数据、里程碑等。
|
||||
*/
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$cfg = get_query_var('aksrt_module_config', []);
|
||||
if (!is_array($cfg)) $cfg = [];
|
||||
|
||||
$title = (string) ($cfg['title'] ?? '');
|
||||
$items = isset($cfg['items']) && is_array($cfg['items']) ? $cfg['items'] : [];
|
||||
$columns = max(2, min(6, (int) ($cfg['columns'] ?? 4)));
|
||||
|
||||
if (empty($items)) return;
|
||||
?>
|
||||
|
||||
<section class="home-module home-module--data-grid container">
|
||||
<?php if ($title !== '') : ?>
|
||||
<header class="home-module__header">
|
||||
<h2 class="home-module__title"><?php echo esc_html($title); ?></h2>
|
||||
</header>
|
||||
<?php endif; ?>
|
||||
<div class="home-module__data-grid" style="grid-template-columns:repeat(<?php echo esc_attr((string) $columns); ?>, 1fr)">
|
||||
<?php foreach ($items as $item) :
|
||||
$value = (string) ($item['value'] ?? '');
|
||||
$label = (string) ($item['label'] ?? '');
|
||||
$icon = (string) ($item['icon'] ?? '');
|
||||
if ($value === '' && $label === '') continue;
|
||||
?>
|
||||
<div class="aksrt-data-cell">
|
||||
<?php if ($icon !== '') : ?>
|
||||
<i class="<?php echo esc_attr($icon); ?>" aria-hidden="true"></i>
|
||||
<?php endif; ?>
|
||||
<span class="aksrt-data-cell__value"><?php echo esc_html($value); ?></span>
|
||||
<span class="aksrt-data-cell__label"><?php echo esc_html($label); ?></span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||