Selaa lähdekoodia

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>
wuzilong 2 viikkoa sitten
vanhempi
commit
3c154d69ee
1 muutettua tiedostoa jossa 71 lisäystä ja 85 poistoa
  1. 71 85
      web-admin/src/views/m/crm/product/categories.vue

+ 71 - 85
web-admin/src/views/m/crm/product/categories.vue

@@ -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()) {