fix(admin): replicate reference advanced-filter settings page 1:1
- Rewrite post_list/filter schema (PHP + JS) with the reference theme's
field set, order, defaults and dependencies; topics fields excluded
(no topics taxonomy), AKSRT private filter_* toggles dropped
- Support dynamic checkbox options in AksField (options as formData key)
so cat_custom_filter.custom_filters lists the custom_filter dimensions
- Map PostDisplay filter settings to the new keys without legacy fallback;
cat_custom_filter now carries lists/dropdown fieldsets and array filters
- Wire the archive chain to reference semantics: cat_custom_filter
overrides per-category filter projects, custom_filter_show gates the
default display, dimension chip groups render on category/tag archives
- Filter archive queries (main query + AJAX endpoint) by _aksrt_cf_{key}
meta with exact-quoted LIKE matching
- Add advanced-filter settings reference contract test
This commit is contained in:
@@ -481,6 +481,7 @@
|
||||
:key="field.key"
|
||||
:schema="field"
|
||||
:model-value="fieldsetValue(field)"
|
||||
:form-data="formData"
|
||||
@update:model-value="updateFieldsetValue(field.key, $event)"
|
||||
/>
|
||||
</div>
|
||||
@@ -501,7 +502,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="item.fields && item.fields.length" 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)" />
|
||||
<AksField v-for="field in item.fields" :key="field.key" :schema="field" :model-value="modelValue?.[item.key]?.[field.key] ?? field.default" :form-data="formData" @update:model-value="updateSortable(item.key, field.key, $event)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -531,6 +532,7 @@
|
||||
v-if="matchDependency(field.dependsOn, item)"
|
||||
:schema="field"
|
||||
:model-value="item[field.key]"
|
||||
:form-data="formData"
|
||||
:disabled="schema.disabled"
|
||||
@update:model-value="updateRepeaterItem(index, field.key, $event)"
|
||||
/>
|
||||
@@ -646,6 +648,7 @@
|
||||
:key="field.key"
|
||||
:schema="field"
|
||||
:model-value="repeaterDraft[field.key]"
|
||||
:form-data="formData"
|
||||
@update:model-value="updateRepeaterDraft(field.key, $event)"
|
||||
/>
|
||||
</div>
|
||||
@@ -696,6 +699,8 @@ const props = defineProps({
|
||||
},
|
||||
/** Current field value */
|
||||
modelValue: { default: null },
|
||||
/** Whole-form data — used to resolve dynamic options (schema.options as string = formData key). */
|
||||
formData: { type: Object, default: () => ({}) },
|
||||
/** Error message to display */
|
||||
errorMessage: { type: String, default: '' },
|
||||
})
|
||||
@@ -729,10 +734,22 @@ function updateFieldsetValue(key, value) {
|
||||
emit('update:modelValue', { ...current, [key]: value })
|
||||
}
|
||||
|
||||
/** Normalize options: support both string[] and {label, value}[] */
|
||||
/** Normalize options: support string[], {label, value}[], and dynamic formData-key references */
|
||||
const normalizedOptions = computed(() => {
|
||||
const opts = props.schema.options || []
|
||||
return opts.map(opt => {
|
||||
const opts = props.schema.options
|
||||
if (typeof opts === 'string') {
|
||||
// 动态选项:引用 formData 中同名字段(如 custom_filter 维度定义 [{key,name,vals}])。
|
||||
const source = props.formData?.[opts]
|
||||
if (!Array.isArray(source)) return []
|
||||
return source
|
||||
.map(item => {
|
||||
const value = String(item?.key ?? '')
|
||||
if (value === '') return null
|
||||
return { label: String(item?.name ?? item?.key ?? value), value }
|
||||
})
|
||||
.filter(Boolean)
|
||||
}
|
||||
return (Array.isArray(opts) ? opts : []).map(opt => {
|
||||
if (typeof opt === 'string') return { label: opt, value: opt }
|
||||
if (typeof opt === 'object' && opt !== null) {
|
||||
return { ...opt, label: opt.label ?? opt.name ?? opt.value, value: opt.value ?? opt.label ?? opt.name }
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
v-if="isFieldVisible(sub)"
|
||||
:schema="sub"
|
||||
:model-value="getFieldValue(sub.key)"
|
||||
:form-data="formData"
|
||||
:error-message="errors?.[sub.key]"
|
||||
@update:model-value="setFieldValue(sub.key, $event)"
|
||||
@action="emit('field-action', $event)"
|
||||
@@ -89,6 +90,7 @@
|
||||
<AksField
|
||||
:schema="field"
|
||||
:model-value="getFieldValue(field.key)"
|
||||
:form-data="formData"
|
||||
:error-message="errors?.[field.key]"
|
||||
@update:model-value="setFieldValue(field.key, $event)"
|
||||
@action="emit('field-action', $event)"
|
||||
@@ -98,6 +100,7 @@
|
||||
v-else
|
||||
:schema="field"
|
||||
:model-value="getFieldValue(field.key)"
|
||||
:form-data="formData"
|
||||
:error-message="errors?.[field.key]"
|
||||
@update:model-value="setFieldValue(field.key, $event)"
|
||||
@action="emit('field-action', $event)"
|
||||
|
||||
@@ -1,101 +1,128 @@
|
||||
export const filterSchema = {
|
||||
key: 'post_list.filter',
|
||||
title: '高级筛选',
|
||||
icon: 'fa fas fa-filter',
|
||||
description: '为文章列表配置筛选栏、排序方式和分类筛选功能。',
|
||||
icon: 'fa fas fa-align-left',
|
||||
description: '通过自定义筛选类型实现更精细化的内容筛选,可按分类绑定不同的筛选项目。',
|
||||
defaults: {
|
||||
filter_enabled: false,
|
||||
filter_categories: true,
|
||||
filter_tags: false,
|
||||
filter_sort: true,
|
||||
filter_ajax_enabled: true,
|
||||
filter_cat_show_cat: true,
|
||||
filter_cat_show_tag: false,
|
||||
filter_tag_show_cat: true,
|
||||
filter_tag_show_tag: false,
|
||||
custom_filter_show: ['cat', 'tag'],
|
||||
custom_filter_show: [],
|
||||
custom_filter: [],
|
||||
cat_custom_filter: [],
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'basic',
|
||||
title: '筛选基础',
|
||||
icon: 'fa fas fa-filter',
|
||||
fields: [
|
||||
{ key: 'filter_enabled', label: '启用筛选栏', type: 'switch', default: false },
|
||||
{ key: 'custom_filter_show', label: '筛选显示页面', type: 'checkbox', default: ['cat', 'tag'], options: [{ label: '分类页', value: 'cat' }, { label: '标签页', value: 'tag' }], help: '选择在哪些页面显示筛选栏', dependsOn: { key: 'filter_enabled', value: true } },
|
||||
{ key: 'filter_categories', label: '分类筛选', type: 'switch', default: true, dependsOn: { key: 'filter_enabled', value: true } },
|
||||
{ key: 'filter_tags', label: '标签筛选', type: 'switch', default: false, dependsOn: { key: 'filter_enabled', value: true } },
|
||||
{ key: 'filter_sort', label: '排序选项', type: 'switch', default: true, dependsOn: { key: 'filter_enabled', value: true } },
|
||||
{ key: 'filter_ajax_enabled', label: 'AJAX 无刷新筛选', type: 'switch', default: true, help: '启用后分类/标签/排序切换将通过 AJAX 加载,无需刷新页面', dependsOn: { key: 'filter_enabled', value: true } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'ajax',
|
||||
title: 'AJAX 筛选维度',
|
||||
icon: 'fa fas fa-clone',
|
||||
fields: [
|
||||
{ key: 'filter_cat_show_cat', label: '分类页:显示分类筛选', type: 'switch', default: true },
|
||||
{ key: 'filter_cat_show_tag', label: '分类页:显示标签筛选', type: 'switch', default: false },
|
||||
{ key: 'filter_tag_show_cat', label: '标签页:显示分类筛选', type: 'switch', default: true },
|
||||
{ key: 'filter_tag_show_tag', label: '标签页:显示标签筛选', type: 'switch', default: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'custom_filter',
|
||||
title: '自定义筛选维度',
|
||||
icon: 'fa fas fa-sliders-h',
|
||||
key: 'advanced_filter',
|
||||
title: '高级筛选',
|
||||
icon: 'fa fas fa-align-left',
|
||||
fields: [
|
||||
{
|
||||
key: 'filter_intro',
|
||||
type: 'content',
|
||||
ui_only: true,
|
||||
content: '<p style="margin:0;font-size:12px;color:var(--aksrt-color-muted);line-height:1.7;"><b>文章高级筛选功能:</b>通过此功能可实现更加复杂、精细化的内容筛选<br/>添加筛选类型和内容值之后,编写文章时候,可以进行选择,且在前台也可以实现精准筛选<br/>例如影视网站:[类型]有[剧情/喜剧/悬疑/惊悚/犯罪],[地区]有[大陆/美国/日韩/港台/印度],[年份]有[2022/2021/2020/10年代/00年代]等<br/><span style="color:#d48806;">注意事项:添加类型key时候,只能使用英文加下划线,不能有空格,且尽量复杂一点(不能为tag,category等系统已存在的类型),避免与其他mate_key重复!类型名称不能为分类、标签等系统已存在的类型</span><br/><a href="https://www.zibll.com/15886.html" target="_blank">查看官网教程</a></p>',
|
||||
},
|
||||
{
|
||||
key: 'custom_filter_show',
|
||||
label: '高级筛选默认显示',
|
||||
type: 'checkbox',
|
||||
default: [],
|
||||
options: [
|
||||
{ label: '分类页面显示', value: 'cat' },
|
||||
{ label: '标签页面显示', value: 'tag' },
|
||||
],
|
||||
description: '勾选后将会在对应页面默认显示全部筛选,您可以在下方为单独的分类添加自定义筛选',
|
||||
},
|
||||
{
|
||||
key: 'custom_filter',
|
||||
label: '筛选维度定义',
|
||||
label: '自定义高级筛选明细',
|
||||
type: 'repeater',
|
||||
default: [],
|
||||
addText: '添加筛选维度',
|
||||
help: '每项定义一个筛选维度(如"类型""地区""年份"),及其可选值列表',
|
||||
addText: '添加筛选类别',
|
||||
description: '添加筛选类型及明细',
|
||||
fields: [
|
||||
{ key: 'name', label: '维度名称', type: 'input', default: '', placeholder: '如:类型' },
|
||||
{ key: 'key', label: '维度标识', type: 'input', default: '', placeholder: '如:type(英文下划线)' },
|
||||
{ key: 'name', label: '类型名称', type: 'input', default: '' },
|
||||
{ key: 'key', label: '类型key', type: 'input', default: '', description: '必填,只能使用英文加下划线,不能有空格!尽量复杂一点,且配置唯一前缀,以避免与其他mate_key重复,例如(axb_type,axb_year)' },
|
||||
{
|
||||
key: 'vals',
|
||||
label: '可选值',
|
||||
label: '可选择的值',
|
||||
type: 'repeater',
|
||||
default: [],
|
||||
addText: '添加可选值',
|
||||
addText: '添加值',
|
||||
fields: [
|
||||
{ key: 'key', label: '标识', type: 'input', default: '', placeholder: '如:action' },
|
||||
{ key: 'name', label: '显示名', type: 'input', default: '', placeholder: '如:动作' },
|
||||
{ key: 'key', label: '值', type: 'input', default: '' },
|
||||
{ key: 'name', label: '显示名称', type: 'input', default: '' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'cat_custom_filter',
|
||||
title: '按分类绑定筛选',
|
||||
icon: 'fa fas fa-object-group',
|
||||
fields: [
|
||||
{
|
||||
key: 'cat_custom_filter',
|
||||
label: '分类筛选绑定',
|
||||
label: '自定义分类的高级筛选',
|
||||
type: 'repeater',
|
||||
default: [],
|
||||
addText: '添加绑定规则',
|
||||
help: '为指定分类绑定不同的筛选维度组合,实现分类级别的差异化筛选',
|
||||
addText: '添加自定义分类筛选',
|
||||
description: '在此处为不同的分类添加不同的筛选项目,实现更加精细化的筛选。同时你也可以在页面显示/分类页面设置分类的默认筛选方式',
|
||||
fields: [
|
||||
{ key: 'cats', label: '绑定分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [] },
|
||||
{ key: 'cat_s', label: '显示分类筛选', type: 'switch', default: false },
|
||||
{ key: 'tag_s', label: '显示标签筛选', type: 'switch', default: false },
|
||||
{ key: 'orderby_s', label: '显示排序按钮', type: 'switch', default: true },
|
||||
{ key: 'cats', label: '绑定分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], description: '支持添加多个分类' },
|
||||
{ key: 'cat_s', label: '分类筛选', type: 'switch', default: false, description: '在所选分类页显示的分类筛选列表' },
|
||||
{
|
||||
key: 'cat_lists',
|
||||
label: ' ',
|
||||
type: 'fieldset',
|
||||
default: { lists: [], dropdown: false },
|
||||
dependsOn: { key: 'cat_s', value: true },
|
||||
fields: [
|
||||
{ key: 'lists', label: '按钮列表', type: 'async_select', source: 'categories', multiple: true, sortable: true, default: [], description: '选择并排序需要显示的按钮,建议选择同级内容,会自动获取二级三级内容。所选按钮内没有文章则不会显示' },
|
||||
{ key: 'dropdown', label: '下拉列表', type: 'switch', default: false, description: '用下拉框显示更多内容' },
|
||||
],
|
||||
},
|
||||
{ key: 'tag_s', label: '标签筛选', type: 'switch', default: false, description: '在所选分类页显示的标签筛选列表' },
|
||||
{
|
||||
key: 'tag_lists',
|
||||
label: ' ',
|
||||
type: 'fieldset',
|
||||
default: { lists: [], dropdown: false },
|
||||
dependsOn: { key: 'tag_s', value: true },
|
||||
fields: [
|
||||
{ key: 'lists', label: '按钮列表', type: 'async_select', source: 'tags', multiple: true, sortable: true, default: [], description: '选择并排序需要显示的按钮' },
|
||||
{ key: 'dropdown', label: '下拉列表', type: 'switch', default: false, description: '用下拉框显示更多内容' },
|
||||
],
|
||||
},
|
||||
{ key: 'orderby_s', label: '排序方式', type: 'switch', default: true, description: '在所选分类页显示排序方式按钮' },
|
||||
{
|
||||
key: 'orderby_lists',
|
||||
label: ' ',
|
||||
type: 'fieldset',
|
||||
default: { lists: ['modified', 'views', 'like', 'comment_count'], dropdown: false },
|
||||
dependsOn: { key: 'orderby_s', value: true },
|
||||
fields: [
|
||||
{
|
||||
key: 'lists',
|
||||
label: '显示排序方式',
|
||||
type: 'select',
|
||||
multiple: true,
|
||||
sortable: true,
|
||||
default: ['modified', 'views', 'like', 'comment_count'],
|
||||
options: [
|
||||
{ label: '更新', value: 'modified' },
|
||||
{ label: '发布', value: 'date' },
|
||||
{ label: '浏览', value: 'views' },
|
||||
{ label: '点赞', value: 'like' },
|
||||
{ label: '评论', value: 'comment_count' },
|
||||
{ label: '收藏', value: 'favorite' },
|
||||
{ label: '随机', value: 'rand' },
|
||||
],
|
||||
description: '选择需要的排序方式按钮',
|
||||
},
|
||||
{ key: 'dropdown', label: '更多排序方式', type: 'switch', default: false, description: '用下拉框显示全部排序方式' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'custom_filters',
|
||||
label: '启用的自定义维度',
|
||||
type: 'input',
|
||||
default: '',
|
||||
placeholder: '逗号分隔维度标识,如:type,region,year',
|
||||
help: '留空表示使用全部已定义的筛选维度',
|
||||
label: '自定义高级筛选',
|
||||
type: 'checkbox',
|
||||
default: [],
|
||||
options: 'custom_filter',
|
||||
description: '在所选分类页显示的自定义高级筛选项目。如果此处没有选项,或者您修改了上方的文章高级筛选明细,请先保存主题配置并刷新页面后再设置此处',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
var state = {
|
||||
filter_cat: "",
|
||||
filter_tag: "",
|
||||
custom: {},
|
||||
sort: "date",
|
||||
page: 1
|
||||
};
|
||||
@@ -32,6 +33,14 @@
|
||||
if (activeTag) state.filter_tag = activeTag.dataset.filterTag || "";
|
||||
var activeSort = bar.querySelector("[data-filter-sort].is-active");
|
||||
if (activeSort) state.sort = activeSort.dataset.filterSort || "date";
|
||||
// 自定义高级筛选维度:以当前 URL 参数为准(SSR active 状态与 URL 一致)。
|
||||
if (bar.querySelector("[data-filter-dim]")) {
|
||||
new URLSearchParams(window.location.search).forEach(function (value, key) {
|
||||
if (bar.querySelector('[data-filter-dim="' + key + '"]')) {
|
||||
state.custom[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildUrl() {
|
||||
var params = new URLSearchParams();
|
||||
@@ -39,6 +48,7 @@
|
||||
params.set("term_id", String(termId));
|
||||
if (state.filter_cat) params.set("filter_cat", state.filter_cat);
|
||||
if (state.filter_tag) params.set("filter_tag", state.filter_tag);
|
||||
if (Object.keys(state.custom).length) params.set("custom_filter", JSON.stringify(state.custom));
|
||||
|
||||
params.set("sort", state.sort || "date");
|
||||
params.set("page", String(state.page));
|
||||
@@ -55,6 +65,10 @@
|
||||
bar.querySelectorAll("[data-filter-sort]").forEach(function (el) {
|
||||
el.classList.toggle("is-active", (el.dataset.filterSort || "date") === state.sort);
|
||||
});
|
||||
bar.querySelectorAll("[data-filter-dim]").forEach(function (el) {
|
||||
var current = state.custom[el.dataset.filterDim] || "";
|
||||
el.classList.toggle("is-active", (el.dataset.filterDimValue || "") !== "" && (el.dataset.filterDimValue || "") === current);
|
||||
});
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
@@ -110,8 +124,14 @@
|
||||
url.searchParams.delete("paged");
|
||||
url.searchParams.delete("aksrt_filter_cat");
|
||||
url.searchParams.delete("aksrt_filter_tag");
|
||||
bar.querySelectorAll("[data-filter-dim]").forEach(function (el) {
|
||||
url.searchParams.delete(el.dataset.filterDim);
|
||||
});
|
||||
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);
|
||||
Object.keys(state.custom).forEach(function (key) {
|
||||
url.searchParams.set(key, state.custom[key]);
|
||||
});
|
||||
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());
|
||||
@@ -123,7 +143,7 @@
|
||||
}
|
||||
|
||||
bar.addEventListener("click", function (e) {
|
||||
var chip = e.target.closest("[data-filter-cat], [data-filter-tag], [data-filter-sort]");
|
||||
var chip = e.target.closest("[data-filter-cat], [data-filter-tag], [data-filter-sort], [data-filter-dim]");
|
||||
if (!chip) return;
|
||||
e.preventDefault();
|
||||
|
||||
@@ -149,6 +169,18 @@
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (chip.dataset.filterDim !== undefined) {
|
||||
var dimKey = chip.dataset.filterDim;
|
||||
var dimValue = chip.dataset.filterDimValue || "";
|
||||
if (dimValue !== (state.custom[dimKey] || "")) {
|
||||
if (dimValue === "") {
|
||||
delete state.custom[dimKey];
|
||||
} else {
|
||||
state.custom[dimKey] = dimValue;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
state.page = 1;
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user