feat(topics): restore topic taxonomy workflow

This commit is contained in:
kate522 committed 2026-11-10 22:31:11 +08:00
1 parent f3dd1ec3b1
commit a254cbd3e9
59 files changed
+771 -192

No files matched your search

@@ -60,6 +60,15 @@ export const pageDisplaySection = {
canonicalDomain: 'page_display',
section: 'tag',
}),
schemaPage({
key: 'page-display-topics',
name: 'PageDisplayTopics',
label: '专题页面',
route: '/page-display/topics',
title: '专题页面',
canonicalDomain: 'page_display',
section: 'topics',
}),
schemaPage({
key: 'page-display-user-home',
name: 'PageDisplayUserHome',
@@ -63,12 +63,13 @@ export const searchSchema = {
{ key: 'search_custom_key_s', label: '自定义匹配范围', type: 'switch', default: true, help: '启用后可配置搜索匹配字段范围' },
{ 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: 'category' }, { label: '标签', value: 'post_tag' }, { label: '专题', value: 'topics' },
{ label: '评论', value: 'coment' }, { label: '作者昵称', value: 'user' },
] },
{ 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: 'post_tag', label: '[文章] 标签', type: 'taxonomy_select', taxonomy: 'post_tag', multiple: true, default: [] },
{ key: 'topics', label: '[文章] 专题', type: 'taxonomy_select', taxonomy: 'topics', multiple: true, default: [] },
] },
],
},
@@ -18,7 +18,7 @@ export const filterSchema = {
key: 'filter_intro',
type: 'content',
ui_only: true,
content: '<p class="jio-hint"><b>文章高级筛选功能:</b>通过此功能可实现更加复杂、精细化的内容筛选<br/>添加筛选类型和内容值之后,编写文章时候,可以进行选择,且在前台也可以实现精准筛选<br/>例如影视网站:[类型]有[剧情/喜剧/悬疑/惊悚/犯罪],[地区]有[大陆/美国/日韩/港台/印度],[年份]有[2022/2021/2020/10年代/00年代]等<br/><span class="jio-warn">注意事项:添加类型key时候,只能使用英文加下划线,不能有空格,且尽量复杂一点(不能为tag,category等系统已存在的类型),避免与其他mate_key重复!类型名称不能为分类、标签等系统已存在的类型</span></p>',
content: '<p class="jio-hint"><b>文章高级筛选功能:</b>通过此功能可实现更加复杂、精细化的内容筛选<br/>添加筛选类型和内容值之后,编写文章时候,可以进行选择,且在前台也可以实现精准筛选<br/>例如影视网站:[类型]有[剧情/喜剧/悬疑/惊悚/犯罪],[地区]有[大陆/美国/日韩/港台/印度],[年份]有[2022/2021/2020/10年代/00年代]等<br/><span class="jio-warn">注意事项:添加类型key时候,只能使用英文加下划线,不能有空格,且尽量复杂一点(不能为tag,category,topics等系统已存在的类型),避免与其他mate_key重复!类型名称不能为分类、专题、标签等系统已存在的类型</span></p>',
},
{
key: 'custom_filter_show',
@@ -28,6 +28,7 @@ export const filterSchema = {
options: [
{ label: '分类页面显示', value: 'cat' },
{ label: '标签页面显示', value: 'tag' },
{ label: '专题页面显示', value: 'topics' },
],
description: '勾选后将会在对应页面默认显示全部筛选,您可以在下方为单独的分类添加自定义筛选',
},
@@ -87,6 +88,18 @@ export const filterSchema = {
{ key: 'dropdown', label: '下拉列表', type: 'switch', default: false, description: '用下拉框显示更多内容' },
],
},
{ key: 'topics_s', label: '专题筛选', type: 'switch', default: false, description: '在所选分类页显示的专题筛选列表' },
{
key: 'topics_lists',
label: ' ',
type: 'fieldset',
default: { lists: [], dropdown: false },
dependsOn: { key: 'topics_s', value: true },
fields: [
{ key: 'lists', label: '按钮列表', type: 'async_select', source: 'topics', multiple: true, sortable: true, default: [] },
{ key: 'dropdown', label: '下拉列表', type: 'switch', default: false },
],
},
{ key: 'orderby_s', label: '排序方式', type: 'switch', default: true, description: '在所选分类页显示排序方式按钮' },
{
key: 'orderby_lists',
@@ -13,11 +13,12 @@ export const listSchema = {
post_list_author: true,
post_list_time: true,
list_meta_show: 'like',
list_badge_show: ['pay', 'cat', 'tag'],
list_badge_show: ['pay', 'tag', 'topics', 'cat'],
list_show_type: 'separate',
list_type: 'thumb',
list_card_home: false,
list_card_tag: false,
list_card_topics: false,
list_card_author: false,
list_card_cat: [],
list_list_option: { style: 'null', img_position: 'left', scale: 70 },
@@ -39,7 +40,7 @@ export const listSchema = {
{ label: '发布时间', value: 'date' }, { label: '更新时间', value: 'modified' },
] },
{ key: 'sticky_s', label: '显示置顶文章', type: 'checkbox', default: ['home', 'cat'], options: [
{ label: '首页', value: 'home' }, { label: '分类页', value: 'cat' }, { label: '标签页', value: 'tag' },
{ label: '首页', value: 'home' }, { label: '分类页', value: 'cat' }, { label: '标签页', value: 'tag' }, { label: '专题页', value: 'topics' },
] },
],
},
@@ -52,8 +53,8 @@ export const listSchema = {
{ key: 'list_meta_show', label: '列表小部件(移动端优先)', type: 'radio', default: 'like', options: [
{ label: '阅读量', value: 'view' }, { label: '点赞数', value: 'like' }, { label: '评论', value: 'comm' },
] },
{ key: 'list_badge_show', label: '显示列表标签', type: 'checkbox', default: ['pay', 'cat', 'tag'], options: [
{ label: '付费信息', value: 'pay' }, { label: '分类', value: 'cat' }, { label: '标签', value: 'tag' },
{ key: 'list_badge_show', label: '显示列表标签', type: 'checkbox', default: ['pay', 'tag', 'topics', 'cat'], options: [
{ label: '付费信息', value: 'pay' }, { label: '分类', value: 'cat' }, { label: '专题', value: 'topics' }, { label: '标签', value: 'tag' },
] },
],
},
@@ -70,8 +71,9 @@ export const listSchema = {
], help: '文字、自动图文和多图模式仅在开启侧栏的页面有效。' },
{ key: 'list_card_home', label: '首页列表卡片模式', type: 'switch', default: false, dependsOn: { key: 'list_type', not: true, value: 'card' } },
{ key: 'list_card_tag', label: '标签页列表卡片模式', type: 'switch', default: false, dependsOn: { key: 'list_type', not: true, value: 'card' } },
{ key: 'list_card_topics', label: '专题页列表卡片模式', type: 'switch', default: false, dependsOn: { key: 'list_type', not: true, value: 'card' } },
{ key: 'list_card_author', label: '用户页列表卡片模式', type: 'switch', default: false, dependsOn: { key: 'list_type', not: true, value: 'card' } },
{ key: 'list_card_cat', label: '分类页自定义卡片模式', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], dependsOn: { key: 'list_type', not: true, value: 'card' } },
{ key: 'list_card_cat', label: '分类、专题页自定义卡片模式', type: 'taxonomy_select', taxonomy: 'category,topics', multiple: true, default: [], dependsOn: { key: 'list_type', not: true, value: 'card' } },
{ key: 'list_list_option', label: '列表模式 UI 配置', type: 'fieldset', default: { style: 'null', img_position: 'left', scale: 70 }, fields: [
{ key: 'style', label: '列表样式', type: 'radio', default: 'null', options: [
{ label: '默认', value: 'null' }, { label: '样式 2', value: 'style2' },
@@ -7,6 +7,8 @@ export const submitSchema = {
post_article_s: true,
post_article_btn_txte: '发布文章',
post_article_cat: [],
post_article_topics_s: false,
post_article_topics: [],
post_article_custom_filter_s: true,
post_article_tag: true,
post_article_user: 1,
@@ -34,6 +36,8 @@ export const submitSchema = {
},
{ key: 'post_article_btn_txte', label: '按钮文字', type: 'input', default: '发布文章', dependsOn: { key: 'post_article_s', value: true }, description: '投稿按钮的显示的文字,推荐4个字更加协调' },
{ key: 'post_article_cat', label: '投稿允许选择的分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], placeholder: '输入关键词以搜索分类', dependsOn: { key: 'post_article_s', value: true }, description: '投稿允许选择的分类,为空则允许选择全部分类,所选分类下如果有子分类则会自动显示,如果选择了上级分类,则不要再选该分类下的子分类。注意:请确保所选分类包含子分类总数不要超过50个,否则可能会引起页面崩溃' },
{ key: 'post_article_topics_s', label: '允许选择专题', type: 'switch', default: false, dependsOn: { key: 'post_article_s', value: true } },
{ key: 'post_article_topics', label: '投稿允许选择的专题', type: 'taxonomy_select', taxonomy: 'topics', multiple: true, default: [], dependsOn: { all: [{ key: 'post_article_s', value: true }, { key: 'post_article_topics_s', value: true }] } },
{ key: 'post_article_custom_filter_s', label: '允许选择高级筛选', type: 'switch', default: true, dependsOn: { key: 'post_article_s', value: true } },
{
key: 'submit_filter_notice',
@@ -19,9 +19,10 @@ type Props = {
options: CategoryOption[];
value: number[];
onChange: (ids: number[]) => void;
label?: string;
};
export function CategorySelect({ options, value, onChange }: Props) {
export function CategorySelect({ options, value, onChange, label = __('分类', 'jio') }: Props) {
const [open, setOpen] = useState(false);
const [query, setQuery] = useState('');
const { triggerRef, panelRef, position } = useAnchoredPanel<HTMLDivElement>({
@@ -71,16 +72,16 @@ export function CategorySelect({ options, value, onChange }: Props) {
<input
type="search"
value={query}
placeholder={__('搜索分类', 'jio')}
aria-label={__('搜索分类', 'jio')}
placeholder={sprintf(__('搜索%s', 'jio'), label)}
aria-label={sprintf(__('搜索%s', 'jio'), label)}
// 打开即可输入:分类列表通常很长,键盘操作比翻找更快。
autoFocus
onChange={(event) => setQuery(event.target.value)}
/>
</div>
<div className="jio-creator-picker__list" aria-label={__('分类', 'jio')}>
<div className="jio-creator-picker__list" aria-label={label}>
{filtered.length === 0 && (
<p className="jio-creator-picker__empty">{__('没有匹配的分类', 'jio')}</p>
<p className="jio-creator-picker__empty">{sprintf(__('没有匹配的%s', 'jio'), label)}</p>
)}
{filtered.map((option) => {
const active = selected.has(option.id);
@@ -133,7 +134,7 @@ export function CategorySelect({ options, value, onChange }: Props) {
>
<span className="jio-creator-select__tokens">
{chosen.length === 0 ? (
<span className="jio-creator-select__placeholder">{__('选择分类(可多选)', 'jio')}</span>
<span className="jio-creator-select__placeholder">{sprintf(__('选择%s(可多选)', 'jio'), label)}</span>
) : (
chosen.map((option) => (
<span key={option.id} className="jio-creator-select__token">
@@ -143,7 +144,7 @@ export function CategorySelect({ options, value, onChange }: Props) {
<button
type="button"
className="jio-creator-select__token-x"
aria-label={sprintf(__('移除分类 %s', 'jio'), option.name)}
aria-label={sprintf(__('移除%s %s', 'jio'), label, option.name)}
onClick={(event) => {
// 令牌上的叉号只移除这一项,不要顺带展开面板。
event.stopPropagation();
@@ -19,6 +19,7 @@ type Featured = { type: string; data: Record<string, any>; mediaId?: number };
export type TaxonomyDef = {
categories: CategoryOption[];
topics: { enabled: boolean; items: CategoryOption[] };
/** 标签是否开放;开放时附带站点已有标签,用于输入建议。 */
tags: { enabled: boolean; suggestions: string[] };
customDefs: Array<{ key: string; name: string; values: Array<{ key: string; name: string }> }>;
@@ -34,12 +35,14 @@ type Props = {
canPay: boolean;
taxonomy: TaxonomyDef;
categories: number[];
topics: number[];
tags: string[];
custom: Record<string, string[]>;
onFeaturedChange: (featured: Featured) => void;
onScheduleChange: (enabled: boolean, at: string) => void;
onPayEnabledChange: (enabled: boolean) => void;
onCategoriesChange: (categories: number[]) => void;
onTopicsChange: (topics: number[]) => void;
onTagsChange: (tags: string[]) => void;
onCustomChange: (custom: Record<string, string[]>) => void;
/** 打开发布抽屉并直接定位到付费配置。 */
@@ -69,12 +72,14 @@ export function PublishSettings({
canPay,
taxonomy,
categories,
topics,
tags,
custom,
onFeaturedChange,
onScheduleChange,
onPayEnabledChange,
onCategoriesChange,
onTopicsChange,
onTagsChange,
onCustomChange,
onOpenPaySettings,
@@ -141,6 +146,13 @@ export function PublishSettings({
)}
</div>
{taxonomy.topics.enabled && taxonomy.topics.items.length > 0 && (
<div className="jio-creator__setting-row jio-creator__setting-row--stack">
<div className="jio-creator__setting-copy"><span className="jio-creator__setting-label">{__('专题', 'jio')}</span></div>
<CategorySelect options={taxonomy.topics.items} value={topics} onChange={onTopicsChange} label={__('专题', 'jio')} />
</div>
)}
{taxonomy.tags.enabled && (
<div className="jio-creator__setting-row jio-creator__setting-row--stack">
<div className="jio-creator__setting-copy">
+3
View File
@@ -57,6 +57,7 @@ export type Draft = {
content: string;
schedule: Schedule;
categories: number[];
topics: number[];
tags: string[];
custom: Record<string, string[]>;
featured: Featured;
@@ -66,6 +67,7 @@ export type Draft = {
export function initialDraft(bootstrap: Record<string, any>): Draft {
const pay = bootstrap.pay || {};
const selected = (bootstrap.categories && bootstrap.categories.selected) || [];
const selectedTopics = (bootstrap.topics && bootstrap.topics.selected) || [];
const tags = (bootstrap.tags && bootstrap.tags.items) || [];
return {
postId: Number(bootstrap.sourceId || 0),
@@ -75,6 +77,7 @@ export function initialDraft(bootstrap: Record<string, any>): Draft {
content: String(bootstrap.content || ''),
schedule: { enabled: false, at: '' },
categories: selected.map(Number),
topics: selectedTopics.map(Number),
tags: tags.slice(),
custom: bootstrap.custom || {},
featured: bootstrap.featured || { type: '', data: {} },
+7
View File
@@ -207,6 +207,7 @@ function Studio(): JSX.Element {
title: current.title,
content: current.content,
categories: current.categories,
topics: current.topics,
tags: current.tags,
custom: current.custom,
featured: current.featured,
@@ -859,6 +860,10 @@ function Studio(): JSX.Element {
canPay={!!capabilities.canPay}
taxonomy={{
categories: (bootstrap.categories && bootstrap.categories.items) || [],
topics: {
enabled: !!bootstrap.topics?.enabled,
items: (bootstrap.topics && bootstrap.topics.items) || [],
},
tags: {
enabled: !!bootstrap.tags?.enabled,
suggestions: (bootstrap.tags && bootstrap.tags.suggestions) || [],
@@ -866,12 +871,14 @@ function Studio(): JSX.Element {
customDefs: bootstrap.customDefs || [],
}}
categories={draft.categories}
topics={draft.topics}
tags={draft.tags}
custom={draft.custom}
onFeaturedChange={(featured) => patchDraft({ featured })}
onScheduleChange={(enabled, at) => patchDraft({ schedule: { enabled, at } })}
onPayEnabledChange={(enabled) => patchDraft({ pay: { ...draftRef.current.pay, enabled } })}
onCategoriesChange={(categories) => patchDraft({ categories })}
onTopicsChange={(topics) => patchDraft({ topics })}
onTagsChange={(tags) => patchDraft({ tags })}
onCustomChange={(custom) => patchDraft({ custom })}
onOpenPaySettings={() => setDrawerOpen(true)}