fix(web-admin): restore category parent tree-select and delete tip.

Keep Vant sheet/form shell while fixing parent UX and creator-delete feedback.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
wuzilongandCursor committed 2026-10-18 16:32:35 +08:00
1 parent 5d943cf76b
commit 3c154d69ee
1 file changed
+71 -85
@@ -17,7 +17,10 @@
<div v-if="loading && !tree.length" class="mp-product-loading">
<van-loading vertical>正在加载分类</van-loading>
</div>
<van-empty v-else-if="!flatNodes.length" description="暂无分类,点击右上角新增">
<van-empty
v-else-if="!flatNodes.length"
description="暂无分类,点击右上角「新增」创建顶级分类"
>
<van-button round type="primary" @click="openAdd()">新增分类</van-button>
</van-empty>
<div v-else class="mp-cat-tree">
@@ -77,6 +80,7 @@
v-model:show="actionOpen"
teleport="body"
:actions="actionSheetActions"
:description="actionSheetDescription"
cancel-text="取消"
close-on-click-action
@select="onActionSelect"
@@ -101,14 +105,23 @@
/>
<div class="mp-customer-pool-vant__body">
<van-cell-group inset>
<van-field
is-link
readonly
label="上级分类"
:model-value="parentLabel"
placeholder="选择上级分类(留空为顶级)"
@click="parentPickerOpen = true"
/>
<van-field label="上级分类" label-align="top">
<template #input>
<el-tree-select
v-model="formData.parentId"
:data="categoryTreeOptions"
node-key="id"
:props="{ label: 'categoryName', children: 'children' }"
value-key="id"
check-strictly
filterable
clearable
placeholder="选择上级分类(留空为顶级)"
style="width: 100%"
teleported
/>
</template>
</van-field>
<van-field
v-model="formData.categoryName"
label="分类名称"
@@ -122,14 +135,23 @@
label="排序号"
placeholder="数值越小越靠前"
/>
<van-field
is-link
readonly
label="状态"
:model-value="statusLabel"
placeholder="请选择状态"
@click="statusPickerOpen = true"
/>
<van-field label="状态" label-align="top">
<template #input>
<el-select
v-model="formData.status"
placeholder="请选择状态"
style="width: 100%"
teleported
>
<el-option
v-for="opt in dictStore.getOptions('sys_normal_disable')"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</template>
</van-field>
<van-field
v-model="formData.remark"
rows="3"
@@ -148,24 +170,6 @@
</div>
</div>
</van-popup>
<van-popup v-model:show="parentPickerOpen" position="bottom" round teleport="body">
<van-picker
:columns="parentPickerColumns"
:model-value="[formData.parentId || '0']"
@confirm="onParentConfirm"
@cancel="parentPickerOpen = false"
/>
</van-popup>
<van-popup v-model:show="statusPickerOpen" position="bottom" round teleport="body">
<van-picker
:columns="statusPickerColumns"
:model-value="[formData.status || '0']"
@confirm="onStatusConfirm"
@cancel="statusPickerOpen = false"
/>
</van-popup>
</div>
</MpWebviewShell>
</template>
@@ -224,9 +228,7 @@
const formTitle = ref('')
const submitLoading = ref(false)
const nameError = ref('')
const parentPickerOpen = ref(false)
const statusPickerOpen = ref(false)
const flatParentOptions = ref<Array<{ text: string; value: string }>>([])
const categoryTreeOptions = ref<CategoryNode[]>([])
const formData = reactive({
id: '',
parentId: '' as string,
@@ -247,6 +249,10 @@
canDeleteByCreator(actionNode.value as Record<string, unknown> | null)
)
const actionSheetDescription = computed(() =>
canDeleteActionNode.value ? undefined : CREATOR_DELETE_TIP
)
const actionSheetActions = computed(() => [
{ name: '新增子级', value: 'add' },
{ name: '编辑', value: 'edit' },
@@ -258,28 +264,6 @@
}
])
const parentPickerColumns = computed(() => [
{ text: '顶级分类', value: '0' },
...flatParentOptions.value
])
const statusPickerColumns = computed(() =>
dictStore.getOptions('sys_normal_disable').map((o) => ({
text: o.label,
value: String(o.value)
}))
)
const parentLabel = computed(() => {
const id = formData.parentId || '0'
if (id === '0' || !id) return '顶级分类'
return parentPickerColumns.value.find((c) => c.value === id)?.text || id
})
const statusLabel = computed(() => {
return dictStore.getLabel('sys_normal_disable', formData.status) || ''
})
const flatNodes = computed((): FlatItem[] => {
const out: FlatItem[] = []
const walk = (nodes: CategoryNode[], depth: number) => {
@@ -300,14 +284,6 @@
return 'success'
}
function flattenParents(nodes: CategoryNode[], parents: string[] = []): Array<{ text: string; value: string }> {
return nodes.flatMap((node) => {
const path = [...parents, String(node.categoryName || '')].filter(Boolean)
const current = node.id ? [{ text: path.join(' / '), value: String(node.id) }] : []
return current.concat(flattenParents(node.children || [], path))
})
}
const expandAllLabel = computed(() =>
areAllExpandableExpanded(tree.value, expandedIds.value) ? '全部折叠' : '全部展开'
)
@@ -332,10 +308,17 @@
}
}
async function refreshParentOptions() {
async function getCategoryTreeSelect() {
const res = await fetchCategoryTree({})
const data = ((res as { data?: CategoryNode[] }).data || res || []) as CategoryNode[]
flatParentOptions.value = flattenParents(Array.isArray(data) ? data : [])
categoryTreeOptions.value = [
{
id: '0',
categoryName: '顶级分类',
parentId: '0',
children: Array.isArray(data) ? data : []
}
]
}
function toggleExpand(id: string) {
@@ -366,7 +349,7 @@
async function openAdd(parentRow?: CategoryNode) {
actionOpen.value = false
resetForm()
await refreshParentOptions()
await getCategoryTreeSelect()
if (parentRow?.id) formData.parentId = parentRow.id
formTitle.value = '新增产品分类'
formOpen.value = true
@@ -375,7 +358,7 @@
async function openEdit(node: CategoryNode) {
actionOpen.value = false
resetForm()
await refreshParentOptions()
await getCategoryTreeSelect()
formData.id = node.id
formData.parentId = node.parentId || ''
formData.categoryName = node.categoryName || ''
@@ -391,12 +374,24 @@
actionOpen.value = true
}
function onActionSelect(action: { value?: string }) {
function onActionSelect(action: { value?: string; disabled?: boolean }) {
const node = actionNode.value
if (!node) return
if (action.value === 'add') void openAdd(node)
else if (action.value === 'edit') void openEdit(node)
else if (action.value === 'delete') void onActionDelete()
if (action.value === 'add') {
void openAdd(node)
return
}
if (action.value === 'edit') {
void openEdit(node)
return
}
if (action.value === 'delete') {
if (!canDeleteActionNode.value || action.disabled) {
showFailToast(CREATOR_DELETE_TIP)
return
}
void onActionDelete()
}
}
async function onActionDelete() {
@@ -428,15 +423,6 @@
function closeForm() {
if (!submitLoading.value) formOpen.value = false
}
function onParentConfirm({ selectedValues }: { selectedValues: string[] }) {
const v = selectedValues[0] || '0'
formData.parentId = v === '0' ? '' : v
parentPickerOpen.value = false
}
function onStatusConfirm({ selectedValues }: { selectedValues: string[] }) {
formData.status = selectedValues[0] || '0'
statusPickerOpen.value = false
}
async function submitForm() {
if (!formData.categoryName.trim()) {