fix(header): wire multi-function page-type enable like zibll
Connect header_components_show_on through aksrt_header_slide_is_show with zibll page-type matching, drop the homepage-only gate, align admin options, apply nav color, enqueue by is_show, remove dead float-btn schema, and consume footer_tabbar_single extend mode on post singles.
This commit is contained in:
1 parent
3e3a4631f0
commit
bfb64211ee
12 files changed
+445
-285
No files matched your search
@@ -86,16 +86,6 @@ export const pageDisplaySection = {
|
||||
desc: '首页内容模型、主列表、分类 Tab 与首页交互配置。',
|
||||
component: 'HomepageSettings',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'page-display-float-btn',
|
||||
name: 'PageDisplayFloatBtn',
|
||||
label: '悬浮按钮',
|
||||
route: '/page-display/float-btn',
|
||||
title: '悬浮按钮',
|
||||
desc: '悬浮按钮的显示规则、样式与滚动隐藏设置。',
|
||||
canonicalDomain: 'page_display',
|
||||
section: 'float-btn',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'page-display-category',
|
||||
name: 'PageDisplayCategory',
|
||||
|
||||
@@ -5,21 +5,13 @@
|
||||
<n-card class="aksrt-section-card" style="margin-bottom:16px;">
|
||||
<template #header>
|
||||
<div class="aksrt-card-header-row">
|
||||
<strong>Hero 显示开关</strong>
|
||||
<n-button size="small" secondary @click="goHeaderComponents">顶部多功能组件(搜索/卡片/轮播参数)</n-button>
|
||||
<strong>显示范围</strong>
|
||||
<n-button size="small" secondary @click="goHeaderComponents">顶部多功能组件(页面类型/搜索/卡片)</n-button>
|
||||
</div>
|
||||
</template>
|
||||
<n-form label-placement="left" label-width="160" class="aksrt-form">
|
||||
<n-form-item label="显示首页 Hero">
|
||||
<div class="aksrt-inline-setting">
|
||||
<n-switch v-model:value="heroEnabled" :disabled="settingsLoading || settingsSaving" />
|
||||
<span class="aksrt-inline-setting__hint">关闭后,首页 Hero 区块将不再渲染。</span>
|
||||
</div>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<div class="aksrt-inline-setting__actions">
|
||||
<n-button type="primary" :loading="settingsSaving" :disabled="settingsLoading" @click="saveHeroSettings">保存 Hero 开关</n-button>
|
||||
</div>
|
||||
<p class="aksrt-inline-setting__hint" style="margin:0;">
|
||||
幻灯片内容在此维护。页面类型启用、设备规则、搜索覆盖层与图标卡片请到「顶部多功能组件」配置;全不选页面类型即为关闭。
|
||||
</p>
|
||||
</n-card>
|
||||
|
||||
<n-spin :show="loading" style="width:100%; min-height:200px;">
|
||||
@@ -216,10 +208,7 @@ function goHeaderComponents() {
|
||||
}
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const settingsLoading = ref(false)
|
||||
const settingsSaving = ref(false)
|
||||
const slides = ref([])
|
||||
const heroEnabled = ref(true)
|
||||
|
||||
const modalShow = ref(false)
|
||||
const editing = ref(emptyForm())
|
||||
@@ -348,36 +337,7 @@ async function loadSlides() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadHeroSettings() {
|
||||
settingsLoading.value = true
|
||||
try {
|
||||
const res = await api.getHomepageSettings()
|
||||
const data = res?.data || {}
|
||||
heroEnabled.value = data.homepage_module_hero_enabled !== false
|
||||
} catch (e) {
|
||||
message.error(e.message || '加载 Hero 开关失败')
|
||||
} finally {
|
||||
settingsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveHeroSettings() {
|
||||
settingsSaving.value = true
|
||||
try {
|
||||
const res = await api.saveHomepageSettings({
|
||||
homepage_module_hero_enabled: heroEnabled.value,
|
||||
})
|
||||
if (res?.code === 'success') {
|
||||
message.success('Hero 开关已保存')
|
||||
return
|
||||
}
|
||||
throw new Error(res?.message || '保存 Hero 开关失败')
|
||||
} catch (e) {
|
||||
message.error(e.message || '保存 Hero 开关失败')
|
||||
} finally {
|
||||
settingsSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
editing.value = emptyForm()
|
||||
@@ -495,7 +455,6 @@ function handleDelete(row) {
|
||||
|
||||
onMounted(() => {
|
||||
loadSlides()
|
||||
loadHeroSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -626,14 +626,17 @@ export const pageDisplaySchemas = {
|
||||
{ key: 'header_components_cards_enabled', label: '启用图标卡片', type: 'switch', default: false, help: '在顶部组件区域显示图标卡片入口' },
|
||||
{ key: 'header_components_show_on', label: '按页面类型启用', type: 'checkbox', default: ['home'], options: [
|
||||
{ label: '首页', value: 'home' },
|
||||
{ label: '文章', value: 'single' },
|
||||
{ label: '页面', value: 'page' },
|
||||
{ label: '分类', value: 'category' },
|
||||
{ label: '标签', value: 'tag' },
|
||||
{ label: '搜索', value: 'search' },
|
||||
{ label: '归档', value: 'archive' },
|
||||
{ label: '专题', value: 'special' },
|
||||
], help: '选择哪些页面类型显示顶部组件,不选则关闭' },
|
||||
{ label: '专题页', value: 'topics' },
|
||||
{ label: '分类页', value: 'category' },
|
||||
{ label: '标签页', value: 'tag' },
|
||||
{ label: '用户页', value: 'author' },
|
||||
{ label: '文章页', value: 'single' },
|
||||
{ label: '搜索页', value: 'search' },
|
||||
{ label: '[论坛]首页', value: 'forum_home' },
|
||||
{ label: '[论坛]板块页面', value: 'forum_plate' },
|
||||
{ label: '[论坛]帖子页面', value: 'forum_post' },
|
||||
{ label: '其它页面', value: 'page' },
|
||||
], help: '选择启用顶部多功能组件的页面类型,一个都不选则为关闭此功能' },
|
||||
{ key: 'header_components_device_visibility', label: '设备显示规则', type: 'radio', default: '', options: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '仅电脑', value: 'only_pc' },
|
||||
@@ -742,74 +745,6 @@ export const pageDisplaySchemas = {
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* Floating button settings
|
||||
*/
|
||||
'float-btn': {
|
||||
key: 'page_display.float-btn',
|
||||
title: '悬浮按钮',
|
||||
icon: 'fas fa-circle-chevron-up',
|
||||
description: '配置页面悬浮按钮的显示规则、样式和滚动隐藏行为。',
|
||||
defaults: {
|
||||
float_btn_style: 'round',
|
||||
float_btn_position: 'bottom',
|
||||
float_btn_scroll_hide: false,
|
||||
float_btn_scroll_down_hide: true,
|
||||
float_btn_max: 5,
|
||||
float_btn_size: 44,
|
||||
float_btn_spacing: 10,
|
||||
float_btn_shadow: true,
|
||||
float_btn_animation: true,
|
||||
float_btn_opacity: 100,
|
||||
float_btn_active_color: '',
|
||||
float_btn_bg_color: '',
|
||||
float_btn_pc_s: true,
|
||||
float_btn_m_s: true,
|
||||
float_btn_filter_css: '',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'basic',
|
||||
title: '悬浮按钮设置',
|
||||
icon: 'fas fa-circle-notch',
|
||||
fields: [
|
||||
{
|
||||
key: 'float_btn_style',
|
||||
label: '按钮风格',
|
||||
type: 'radio',
|
||||
default: 'round',
|
||||
options: [
|
||||
{ label: '圆形', value: 'round' },
|
||||
{ label: '方形', value: 'square' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'float_btn_position',
|
||||
label: '按钮位置',
|
||||
type: 'radio',
|
||||
default: 'bottom',
|
||||
options: [
|
||||
{ label: '底部', value: 'bottom' },
|
||||
{ label: '居中', value: 'center' },
|
||||
],
|
||||
},
|
||||
{ key: 'float_btn_scroll_hide', label: '滚动时隐藏', type: 'switch', default: false },
|
||||
{ key: 'float_btn_scroll_down_hide', label: '下滑时隐藏', type: 'switch', default: true },
|
||||
{ key: 'float_btn_max', label: '最大显示数', type: 'number', default: 5, min: 1, max: 10 },
|
||||
{ key: 'float_btn_size', label: '按钮尺寸', type: 'number', default: 44, min: 32, max: 64 },
|
||||
{ key: 'float_btn_spacing', label: '按钮间距', type: 'number', default: 10, min: 4, max: 24 },
|
||||
{ key: 'float_btn_shadow', label: '显示阴影', type: 'switch', default: true },
|
||||
{ key: 'float_btn_animation', label: '悬停动画', type: 'switch', default: true },
|
||||
{ key: 'float_btn_opacity', label: '按钮透明度', type: 'number', default: 100, min: 30, max: 100 },
|
||||
{ key: 'float_btn_active_color', label: '激活颜色', type: 'color', default: '' },
|
||||
{ key: 'float_btn_bg_color', label: '背景颜色', type: 'color', default: '' },
|
||||
{ key: 'float_btn_pc_s', label: 'PC端显示', type: 'switch', default: true },
|
||||
{ key: 'float_btn_m_s', label: '移动端显示', type: 'switch', default: true },
|
||||
{ key: 'float_btn_filter_css', label: '过滤 CSS', type: 'textarea', default: '' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
key: 'page_display.footer',
|
||||
title: '底部页脚',
|
||||
|
||||
+103
-72
@@ -157,85 +157,116 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectActionButtons(scope, selector) {
|
||||
scope = scope || document;
|
||||
var list = [];
|
||||
if (!scope) return list;
|
||||
if (scope.nodeType === 1 && scope.matches && scope.matches(selector)) {
|
||||
list.push(scope);
|
||||
}
|
||||
if (typeof scope.querySelectorAll === "function") {
|
||||
var found = scope.querySelectorAll(selector);
|
||||
for (var i = 0; i < found.length; i++) list.push(found[i]);
|
||||
} else if (scope.nodeType === 9) {
|
||||
var all = document.querySelectorAll(selector);
|
||||
for (var j = 0; j < all.length; j++) list.push(all[j]);
|
||||
}
|
||||
// also keep legacy id hooks
|
||||
var legacyId = selector.indexOf("action-like") >= 0 ? "action-like" : (selector.indexOf("action-favorite") >= 0 ? "action-favorite" : "");
|
||||
if (legacyId) {
|
||||
var legacy = findInScope(scope, legacyId);
|
||||
if (legacy && list.indexOf(legacy) === -1) list.push(legacy);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
function initInteractionBar(root) {
|
||||
var scope = root || document;
|
||||
// Element roots (AJAX rehydrate) do not implement getElementById
|
||||
var likeBtn = findInScope(scope, "action-like");
|
||||
var favBtn = findInScope(scope, "action-favorite");
|
||||
// Bind all like/favorite controls (in-content + footer tabbar extend).
|
||||
var likeBtns = collectActionButtons(scope, ".action-like[data-post-id]");
|
||||
var favBtns = collectActionButtons(scope, ".action-favorite[data-post-id]");
|
||||
|
||||
if (likeBtn && likeBtn.dataset.aksrtInteractionBound) return;
|
||||
if (favBtn && favBtn.dataset.aksrtInteractionBound) return;
|
||||
|
||||
if (likeBtn) {
|
||||
var likePostId = likeBtn.dataset.postId ? String(likeBtn.dataset.postId) : "";
|
||||
|
||||
function applyLikeState(isActive, count) {
|
||||
likeBtn.dataset.initialActive = isActive ? "1" : "0";
|
||||
likeBtn.classList.toggle("is-active", isActive);
|
||||
var likeIcon = likeBtn.querySelector("i");
|
||||
if (likeIcon) {
|
||||
likeIcon.className = isActive ? "fas fa-heart" : "far fa-heart";
|
||||
}
|
||||
if ("number" === typeof count && !isNaN(count)) {
|
||||
setBaseStatCount("likes", count);
|
||||
} else {
|
||||
updateStatCounts("likes", getBaseStatCount("likes"));
|
||||
}
|
||||
}
|
||||
|
||||
applyLikeState(likeBtn.dataset.initialActive === "1", getBaseStatCount("likes"));
|
||||
|
||||
likeBtn.addEventListener("click", function () {
|
||||
if (!likePostId || likeBtn.disabled) return;
|
||||
var nextState = likeBtn.dataset.initialActive !== "1";
|
||||
setButtonBusy(likeBtn, true);
|
||||
requestInteraction("aksrt_toggle_like", likePostId, nextState).then(function (data) {
|
||||
applyLikeState(!!data.active, parseInt(data.count, 10) || 0);
|
||||
showToast(data.active ? "感谢你的点赞" : "已取消点赞", "success");
|
||||
}).catch(function (error) {
|
||||
showToast(error && error.message ? error.message : "点赞失败", "error");
|
||||
}).finally(function () {
|
||||
setButtonBusy(likeBtn, false);
|
||||
});
|
||||
});
|
||||
function paintLike(btn, isActive) {
|
||||
btn.dataset.initialActive = isActive ? "1" : "0";
|
||||
btn.classList.toggle("is-active", isActive);
|
||||
var icon = btn.querySelector("i");
|
||||
if (icon) icon.className = isActive ? "fas fa-heart" : "far fa-heart";
|
||||
}
|
||||
|
||||
if (favBtn) {
|
||||
var favoritePostId = favBtn.dataset.postId ? String(favBtn.dataset.postId) : "";
|
||||
|
||||
function applyFavoriteState(isActive, count) {
|
||||
favBtn.dataset.initialActive = isActive ? "1" : "0";
|
||||
favBtn.classList.toggle("is-active", isActive);
|
||||
var favoriteIcon = favBtn.querySelector("i");
|
||||
if (favoriteIcon) {
|
||||
favoriteIcon.className = isActive ? "fas fa-star" : "far fa-star";
|
||||
}
|
||||
if ("number" === typeof count && !isNaN(count)) {
|
||||
setBaseStatCount("favorites", count);
|
||||
} else {
|
||||
updateStatCounts("favorites", getBaseStatCount("favorites"));
|
||||
}
|
||||
}
|
||||
|
||||
applyFavoriteState(favBtn.dataset.initialActive === "1", getBaseStatCount("favorites"));
|
||||
|
||||
favBtn.addEventListener("click", function () {
|
||||
if (!favoritePostId || favBtn.disabled) return;
|
||||
var nextState = favBtn.dataset.initialActive !== "1";
|
||||
setButtonBusy(favBtn, true);
|
||||
requestInteraction("aksrt_toggle_favorite", favoritePostId, nextState).then(function (data) {
|
||||
applyFavoriteState(!!data.active, parseInt(data.count, 10) || 0);
|
||||
showToast(data.active ? "收藏成功" : "已取消收藏", "success");
|
||||
}).catch(function (error) {
|
||||
showToast(error && error.message ? error.message : "收藏失败", "error");
|
||||
}).finally(function () {
|
||||
setButtonBusy(favBtn, false);
|
||||
});
|
||||
});
|
||||
function paintFavorite(btn, isActive) {
|
||||
btn.dataset.initialActive = isActive ? "1" : "0";
|
||||
btn.classList.toggle("is-active", isActive);
|
||||
var icon = btn.querySelector("i");
|
||||
if (icon) icon.className = isActive ? "fas fa-star" : "far fa-star";
|
||||
}
|
||||
|
||||
if (likeBtn) likeBtn.dataset.aksrtInteractionBound = "1";
|
||||
if (favBtn) favBtn.dataset.aksrtInteractionBound = "1";
|
||||
function applyLikeState(isActive, count) {
|
||||
for (var i = 0; i < likeBtns.length; i++) paintLike(likeBtns[i], isActive);
|
||||
if ("number" === typeof count && !isNaN(count)) {
|
||||
setBaseStatCount("likes", count);
|
||||
} else {
|
||||
updateStatCounts("likes", getBaseStatCount("likes"));
|
||||
}
|
||||
}
|
||||
|
||||
function applyFavoriteState(isActive, count) {
|
||||
for (var i = 0; i < favBtns.length; i++) paintFavorite(favBtns[i], isActive);
|
||||
if ("number" === typeof count && !isNaN(count)) {
|
||||
setBaseStatCount("favorites", count);
|
||||
} else {
|
||||
updateStatCounts("favorites", getBaseStatCount("favorites"));
|
||||
}
|
||||
}
|
||||
|
||||
if (likeBtns.length) {
|
||||
var seedLike = likeBtns[0];
|
||||
if (!seedLike.dataset.aksrtInteractionBound) {
|
||||
applyLikeState(seedLike.dataset.initialActive === "1", getBaseStatCount("likes"));
|
||||
likeBtns.forEach(function (likeBtn) {
|
||||
if (likeBtn.dataset.aksrtInteractionBound) return;
|
||||
likeBtn.dataset.aksrtInteractionBound = "1";
|
||||
likeBtn.addEventListener("click", function () {
|
||||
var postId = likeBtn.dataset.postId ? String(likeBtn.dataset.postId) : "";
|
||||
if (!postId || likeBtn.disabled) return;
|
||||
var nextState = likeBtn.dataset.initialActive !== "1";
|
||||
setButtonBusy(likeBtn, true);
|
||||
requestInteraction("aksrt_toggle_like", postId, nextState).then(function (data) {
|
||||
applyLikeState(!!data.active, parseInt(data.count, 10) || 0);
|
||||
showToast(data.active ? "感谢你的点赞" : "已取消点赞", "success");
|
||||
}).catch(function (error) {
|
||||
showToast(error && error.message ? error.message : "点赞失败", "error");
|
||||
}).finally(function () {
|
||||
setButtonBusy(likeBtn, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (favBtns.length) {
|
||||
var seedFav = favBtns[0];
|
||||
if (!seedFav.dataset.aksrtInteractionBound) {
|
||||
applyFavoriteState(seedFav.dataset.initialActive === "1", getBaseStatCount("favorites"));
|
||||
favBtns.forEach(function (favBtn) {
|
||||
if (favBtn.dataset.aksrtInteractionBound) return;
|
||||
favBtn.dataset.aksrtInteractionBound = "1";
|
||||
favBtn.addEventListener("click", function () {
|
||||
var postId = favBtn.dataset.postId ? String(favBtn.dataset.postId) : "";
|
||||
if (!postId || favBtn.disabled) return;
|
||||
var nextState = favBtn.dataset.initialActive !== "1";
|
||||
setButtonBusy(favBtn, true);
|
||||
requestInteraction("aksrt_toggle_favorite", postId, nextState).then(function (data) {
|
||||
applyFavoriteState(!!data.active, parseInt(data.count, 10) || 0);
|
||||
showToast(data.active ? "收藏成功" : "已取消收藏", "success");
|
||||
}).catch(function (error) {
|
||||
showToast(error && error.message ? error.message : "收藏失败", "error");
|
||||
}).finally(function () {
|
||||
setButtonBusy(favBtn, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.AksrtFrontend.register("aksrt-search-clear", function () { initSearchClear(); });
|
||||
|
||||
+12
-1
@@ -123,8 +123,9 @@
|
||||
}
|
||||
|
||||
function initShareDropdown() {
|
||||
var trigger = document.getElementById('action-share-trigger');
|
||||
var dropdown = document.getElementById('share-dropdown');
|
||||
var triggers = Array.prototype.slice.call(document.querySelectorAll('#action-share-trigger, .action-share[data-type="share"], .footer-tabbar .action-share')).filter(function (el, idx, arr) { return arr.indexOf(el) === idx; });
|
||||
var trigger = triggers[0] || null;
|
||||
if (!trigger || !dropdown || dropdown.dataset.aksrtShareBound === '1') return;
|
||||
|
||||
dropdown.dataset.aksrtShareBound = '1';
|
||||
@@ -160,6 +161,16 @@
|
||||
toggleDropdown();
|
||||
});
|
||||
|
||||
triggers.slice(1).forEach(function (extraTrigger) {
|
||||
extraTrigger.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// Reuse primary trigger click semantics by synthetic toggle
|
||||
trigger.click();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
if (!trigger.contains(event.target) && !dropdown.contains(event.target)) {
|
||||
closeDropdown();
|
||||
|
||||
Reference in new issue
Block a user