387 lines
10 KiB
TypeScript
387 lines
10 KiB
TypeScript
import type { TableColumnType } from 'antdv-next';
|
|
|
|
import type { PropType } from 'vue';
|
|
|
|
import type { WordpressBlogApi } from '#/api/blog';
|
|
import type {
|
|
KtTableApi,
|
|
KtTableButton,
|
|
KtTableRowAction,
|
|
} from '#/components/ktTable';
|
|
|
|
import { computed, defineComponent, onMounted, ref, watch } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
|
import { Page, useVbenModal } from '@vben/common-ui';
|
|
import { Plus } from '@vben/icons';
|
|
|
|
import { message } from 'antdv-next';
|
|
|
|
import { useVbenForm } from '#/adapter/form';
|
|
import {
|
|
createCategory,
|
|
createTag,
|
|
deleteCategory,
|
|
deleteTag,
|
|
getCategoryList,
|
|
getTagList,
|
|
updateCategory,
|
|
updateTag,
|
|
} from '#/api/blog';
|
|
import { KtTable, useKtTable } from '#/components/ktTable';
|
|
|
|
import { setBlogArticleFilters } from './use-article-filters';
|
|
|
|
type TermSearchValues = {
|
|
search?: string;
|
|
};
|
|
|
|
const AKtTable = KtTable as any;
|
|
|
|
export default defineComponent({
|
|
name: 'BlogTermManagement',
|
|
props: {
|
|
kind: {
|
|
required: true,
|
|
type: String as PropType<'category' | 'tag'>,
|
|
},
|
|
title: {
|
|
required: true,
|
|
type: String,
|
|
},
|
|
},
|
|
setup(props) {
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
|
|
const editingId = ref<string>();
|
|
const tableRows = ref<WordpressBlogApi.Term[]>([]);
|
|
const parentOptions = computed(() =>
|
|
tableRows.value
|
|
.filter((item) => item.id !== editingId.value)
|
|
.map((item) => ({ label: item.name, value: item.id })),
|
|
);
|
|
|
|
const [TermForm, termFormApi] = useVbenForm({
|
|
commonConfig: {
|
|
labelClass: 'w-24',
|
|
},
|
|
layout: 'horizontal',
|
|
schema: [
|
|
{
|
|
component: 'Input',
|
|
componentProps: () => ({
|
|
placeholder: `请输入${props.title}名称`,
|
|
}),
|
|
fieldName: 'name',
|
|
label: '名称',
|
|
rules: 'required',
|
|
},
|
|
{
|
|
component: 'Input',
|
|
componentProps: {
|
|
placeholder: '可选,默认由名称生成',
|
|
},
|
|
fieldName: 'slug',
|
|
label: '别名',
|
|
},
|
|
{
|
|
component: 'Select',
|
|
componentProps: () => ({
|
|
allowClear: true,
|
|
options: parentOptions.value,
|
|
placeholder: '选择父级分类',
|
|
}),
|
|
dependencies: {
|
|
if: () => props.kind === 'category',
|
|
triggerFields: ['name'],
|
|
},
|
|
fieldName: 'parent',
|
|
label: '父级分类',
|
|
},
|
|
{
|
|
component: 'Textarea',
|
|
componentProps: {
|
|
autoSize: { maxRows: 6, minRows: 3 },
|
|
placeholder: '可选',
|
|
},
|
|
fieldName: 'description',
|
|
label: '描述',
|
|
},
|
|
],
|
|
showDefaultActions: false,
|
|
wrapperClass: 'grid-cols-1',
|
|
});
|
|
|
|
const modalTitle = computed(() =>
|
|
editingId.value ? `编辑${props.title}` : `新建${props.title}`,
|
|
);
|
|
const [TermModal, termModalApi] = useVbenModal({
|
|
class: 'w-[620px]',
|
|
fullscreenButton: false,
|
|
async onConfirm() {
|
|
await submitTerm();
|
|
},
|
|
onOpenChange(isOpen: boolean) {
|
|
if (!isOpen) return;
|
|
const { values } = termModalApi.getData<{
|
|
values?: WordpressBlogApi.TermBody;
|
|
}>();
|
|
void resetTermForm(values || getTermFormDefaults());
|
|
},
|
|
});
|
|
const permissionModule = computed(() =>
|
|
props.kind === 'category' ? 'Blog:Category' : 'Blog:Tag',
|
|
);
|
|
const columns = computed<Array<TableColumnType<WordpressBlogApi.Term>>>(
|
|
() => [
|
|
{ dataIndex: 'name', key: 'name', title: '名称', width: 220 },
|
|
{ dataIndex: 'slug', key: 'slug', title: '别名', width: 180 },
|
|
{ dataIndex: 'count', key: 'count', title: '文章数', width: 100 },
|
|
{
|
|
dataIndex: 'description',
|
|
key: 'description',
|
|
title: '描述',
|
|
width: 300,
|
|
},
|
|
],
|
|
);
|
|
const api: KtTableApi<WordpressBlogApi.Term, TermSearchValues> = {
|
|
list: async (params) => {
|
|
const requestParams = {
|
|
hide_empty: false,
|
|
pageNo: params.pageNo,
|
|
pageSize: params.pageSize,
|
|
search: params.search,
|
|
};
|
|
|
|
return props.kind === 'category'
|
|
? await getCategoryList(requestParams)
|
|
: await getTagList(requestParams);
|
|
},
|
|
};
|
|
const buttons = computed<
|
|
Array<KtTableButton<WordpressBlogApi.Term, TermSearchValues>>
|
|
>(() => [
|
|
{
|
|
icon: <Plus class="kt-table__button-icon" />,
|
|
key: 'create',
|
|
label: `新建${props.title}`,
|
|
onClick: openCreate,
|
|
permissionCodes: [`${permissionModule.value}:Create`],
|
|
type: 'primary',
|
|
},
|
|
]);
|
|
const rowActions = computed<
|
|
Array<KtTableRowAction<WordpressBlogApi.Term, TermSearchValues>>
|
|
>(() => [
|
|
{
|
|
key: 'articles',
|
|
label: '查看文章',
|
|
onClick: openRelatedArticles,
|
|
permissionCodes: ['Blog:Article:List'],
|
|
},
|
|
{
|
|
key: 'edit',
|
|
label: '编辑',
|
|
onClick: openEdit,
|
|
permissionCodes: [`${permissionModule.value}:Edit`],
|
|
},
|
|
{
|
|
confirm: (row) =>
|
|
`确认删除${props.title}「${row.name}」吗?本操作不会删除已关联文章。`,
|
|
danger: true,
|
|
key: 'delete',
|
|
label: '删除',
|
|
onClick: async (row, context) => {
|
|
await (props.kind === 'category'
|
|
? deleteCategory(row.id)
|
|
: deleteTag(row.id));
|
|
message.success(`${props.title}删除成功`);
|
|
await context.reload();
|
|
},
|
|
permissionCodes: [`${permissionModule.value}:Delete`],
|
|
},
|
|
]);
|
|
|
|
const [registerTable, tableApi] = useKtTable<
|
|
WordpressBlogApi.Term,
|
|
TermSearchValues
|
|
>({
|
|
afterFetch: (result) => {
|
|
tableRows.value = Array.isArray(result)
|
|
? result
|
|
: result.list || result.records || result.items || [];
|
|
return result;
|
|
},
|
|
api,
|
|
buttons: buttons.value,
|
|
columns: columns.value,
|
|
formOptions: {
|
|
schema: [
|
|
{
|
|
component: 'Input',
|
|
componentProps: {
|
|
allowClear: true,
|
|
placeholder: `搜索${props.title}名称`,
|
|
},
|
|
fieldName: 'search',
|
|
label: '关键词',
|
|
},
|
|
],
|
|
},
|
|
immediate: false,
|
|
rowActions: rowActions.value,
|
|
tableTitle: props.title,
|
|
});
|
|
|
|
function getRouteSearch() {
|
|
const value = route.query.search;
|
|
return Array.isArray(value) ? value[0] || '' : value || '';
|
|
}
|
|
|
|
function syncTableProps() {
|
|
tableApi.setProps({
|
|
buttons: buttons.value,
|
|
columns: columns.value,
|
|
formOptions: {
|
|
schema: [
|
|
{
|
|
component: 'Input',
|
|
componentProps: {
|
|
allowClear: true,
|
|
placeholder: `搜索${props.title}名称`,
|
|
},
|
|
fieldName: 'search',
|
|
label: '关键词',
|
|
},
|
|
],
|
|
},
|
|
rowActions: rowActions.value,
|
|
tableTitle: props.title,
|
|
});
|
|
}
|
|
|
|
function getTermFormDefaults(): WordpressBlogApi.TermBody {
|
|
return {
|
|
description: '',
|
|
name: '',
|
|
parent: undefined,
|
|
slug: '',
|
|
};
|
|
}
|
|
|
|
async function resetTermForm(values: WordpressBlogApi.TermBody) {
|
|
await termFormApi.resetForm();
|
|
await termFormApi.setValues(values);
|
|
await termFormApi.resetValidate();
|
|
}
|
|
|
|
function openCreate() {
|
|
editingId.value = undefined;
|
|
termModalApi.setData({ values: getTermFormDefaults() }).open();
|
|
}
|
|
|
|
function openEdit(row: WordpressBlogApi.Term) {
|
|
editingId.value = row.id;
|
|
termModalApi
|
|
.setData({
|
|
values: {
|
|
description: row.description || '',
|
|
id: row.id,
|
|
name: row.name,
|
|
parent: row.parent || undefined,
|
|
slug: row.slug || '',
|
|
},
|
|
})
|
|
.open();
|
|
}
|
|
|
|
async function submitTerm() {
|
|
const { valid } = await termFormApi.validate();
|
|
if (!valid) return;
|
|
|
|
const values = await termFormApi.getValues<WordpressBlogApi.TermBody>();
|
|
const name = values.name?.trim();
|
|
if (!name) {
|
|
message.warning(`请填写${props.title}名称`);
|
|
return;
|
|
}
|
|
|
|
termModalApi.lock();
|
|
try {
|
|
const payload = {
|
|
...values,
|
|
id: editingId.value,
|
|
name,
|
|
};
|
|
if (props.kind === 'category') {
|
|
await (editingId.value
|
|
? updateCategory(payload)
|
|
: createCategory(payload));
|
|
} else {
|
|
await (editingId.value ? updateTag(payload) : createTag(payload));
|
|
}
|
|
message.success(`${props.title}保存成功`);
|
|
await termModalApi.close();
|
|
await tableApi.reload();
|
|
} finally {
|
|
termModalApi.unlock();
|
|
}
|
|
}
|
|
|
|
function openRelatedArticles(row: WordpressBlogApi.Term) {
|
|
setBlogArticleFilters(
|
|
props.kind === 'category'
|
|
? { categories: [row.name] }
|
|
: { tags: [row.name] },
|
|
);
|
|
router.push({
|
|
name: 'BlogArticle',
|
|
});
|
|
}
|
|
|
|
async function reloadWithRouteSearch() {
|
|
syncTableProps();
|
|
await tableApi.setSearchValues({ search: getRouteSearch() });
|
|
await tableApi.reload();
|
|
}
|
|
|
|
watch(
|
|
() => props.kind,
|
|
() => {
|
|
reloadWithRouteSearch();
|
|
},
|
|
);
|
|
|
|
onMounted(() => {
|
|
reloadWithRouteSearch();
|
|
});
|
|
|
|
return () => (
|
|
<Page autoContentHeight>
|
|
<AKtTable
|
|
onRegister={registerTable}
|
|
v-slots={{
|
|
bodyCell: ({ column, record }: any) => {
|
|
const term = record as WordpressBlogApi.Term;
|
|
|
|
if (column.key === 'description') {
|
|
return (
|
|
<span class="line-clamp-2">{term.description || '-'}</span>
|
|
);
|
|
}
|
|
|
|
return undefined;
|
|
},
|
|
}}
|
|
/>
|
|
|
|
<TermModal title={modalTitle.value}>
|
|
<TermForm class="mx-2" />
|
|
</TermModal>
|
|
</Page>
|
|
);
|
|
},
|
|
});
|