From 40905cbd04c2e332cd4bc2b9e0c5b3e1da9cccfa Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期一, 30 三月 2026 08:17:32 +0800
Subject: [PATCH] feat: complete rsf-design phase 1 integration

---
 rsf-design/src/views/system/menu/modules/menu-dialog.vue |  412 +++++++++++++++++++++++++++++-----------------------------
 1 files changed, 208 insertions(+), 204 deletions(-)

diff --git a/rsf-design/src/views/system/menu/modules/menu-dialog.vue b/rsf-design/src/views/system/menu/modules/menu-dialog.vue
index 7c928de..c2a5dc9 100644
--- a/rsf-design/src/views/system/menu/modules/menu-dialog.vue
+++ b/rsf-design/src/views/system/menu/modules/menu-dialog.vue
@@ -3,7 +3,7 @@
     :title="dialogTitle"
     :model-value="visible"
     @update:model-value="handleCancel"
-    width="860px"
+    width="760px"
     align-center
     class="menu-dialog"
     @closed="handleClosed"
@@ -13,7 +13,7 @@
       v-model="form"
       :items="formItems"
       :rules="rules"
-      :span="width > 640 ? 12 : 24"
+      :span="24"
       :gutter="20"
       label-width="100px"
       :show-reset="false"
@@ -21,16 +21,16 @@
     >
       <template #menuType>
         <ElRadioGroup v-model="form.menuType" :disabled="disableMenuType">
-          <ElRadioButton value="menu" label="menu">鑿滃崟</ElRadioButton>
-          <ElRadioButton value="button" label="button">鎸夐挳</ElRadioButton>
+          <ElRadioButton value="menu">鑿滃崟</ElRadioButton>
+          <ElRadioButton value="button">鎸夐挳</ElRadioButton>
         </ElRadioGroup>
       </template>
     </ArtForm>
 
     <template #footer>
       <span class="dialog-footer">
-        <ElButton @click="handleCancel">鍙� 娑�</ElButton>
-        <ElButton type="primary" @click="handleSubmit">纭� 瀹�</ElButton>
+        <ElButton @click="handleCancel">鍙栨秷</ElButton>
+        <ElButton type="primary" @click="handleSubmit">纭畾</ElButton>
       </span>
     </template>
   </ElDialog>
@@ -39,248 +39,252 @@
 <script setup>
   import ArtForm from '@/components/core/forms/art-form/index.vue'
 
-  import { ElIcon, ElTooltip } from 'element-plus'
-  import { QuestionFilled } from '@element-plus/icons-vue'
-  import { formatMenuTitle } from '@/utils/router'
-  import { useWindowSize } from '@vueuse/core'
-  const { width } = useWindowSize()
-  const createLabelTooltip = (label, tooltip) => {
-    return () =>
-      h('span', { class: 'flex items-center' }, [
-        h('span', label),
-        h(
-          ElTooltip,
-          {
-            content: tooltip,
-            placement: 'top'
-          },
-          () => h(ElIcon, { class: 'ml-0.5 cursor-help' }, () => h(QuestionFilled))
-        )
-      ])
-  }
+  const createMenuFormState = () => ({
+    menuType: 'menu',
+    id: null,
+    parentId: 0,
+    name: '',
+    route: '',
+    component: '',
+    authority: '',
+    icon: '',
+    sort: 0,
+    status: 1,
+    memo: ''
+  })
+
   const props = defineProps({
     visible: { required: false, default: false },
     type: { required: false, default: 'menu' },
-    lockType: { required: false, default: false }
+    lockType: { required: false, default: false },
+    editData: { required: false, default: null },
+    menuTreeOptions: { required: false, default: () => [] }
   })
+
   const emit = defineEmits(['update:visible', 'submit'])
   const formRef = ref()
-  const isEdit = ref(false)
-  const form = reactive({
-    menuType: 'menu',
-    id: 0,
-    name: '',
-    path: '',
-    label: '',
-    component: '',
-    icon: '',
-    isEnable: true,
-    sort: 1,
-    isMenu: true,
-    keepAlive: true,
-    isHide: false,
-    isHideTab: false,
-    link: '',
-    isIframe: false,
-    showBadge: false,
-    showTextBadge: '',
-    fixedTab: false,
-    activePath: '',
-    roles: [],
-    isFullPage: false,
-    authName: '',
-    authLabel: '',
-    authIcon: '',
-    authSort: 1
-  })
-  const rules = reactive({
-    name: [
-      { required: true, message: '璇疯緭鍏ヨ彍鍗曞悕绉�', trigger: 'blur' },
-      { min: 2, max: 20, message: '闀垮害鍦� 2 鍒� 20 涓瓧绗�', trigger: 'blur' }
-    ],
-    path: [{ required: true, message: '璇疯緭鍏ヨ矾鐢卞湴鍧�', trigger: 'blur' }],
-    label: [{ required: true, message: '杈撳叆鏉冮檺鏍囪瘑', trigger: 'blur' }],
-    authName: [{ required: true, message: '璇疯緭鍏ユ潈闄愬悕绉�', trigger: 'blur' }],
-    authLabel: [{ required: true, message: '璇疯緭鍏ユ潈闄愭爣璇�', trigger: 'blur' }]
-  })
+  const form = reactive(createMenuFormState())
+
+  const isEdit = computed(() => Boolean(form.id))
+  const dialogTitle = computed(() => `${isEdit.value ? '缂栬緫' : '鏂板缓'}${form.menuType === 'button' ? '鎸夐挳' : '鑿滃崟'}`)
+  const disableMenuType = computed(() => props.lockType || isEdit.value)
+
+  const rules = computed(() => ({
+    name: [{ required: true, message: form.menuType === 'button' ? '璇疯緭鍏ユ潈闄愬悕绉�' : '璇疯緭鍏ヨ彍鍗曞悕绉�', trigger: 'blur' }],
+    route:
+      form.menuType === 'menu'
+        ? [{ required: true, message: '璇疯緭鍏ヨ矾鐢卞湴鍧�', trigger: 'blur' }]
+        : [],
+    authority:
+      form.menuType === 'button'
+        ? [{ required: true, message: '璇疯緭鍏ユ潈闄愭爣璇�', trigger: 'blur' }]
+        : []
+  }))
+
   const formItems = computed(() => {
-    const baseItems = [{ label: '鑿滃崟绫诲瀷', key: 'menuType', span: 24 }]
-    const switchSpan = width.value < 640 ? 12 : 6
+    const items = [
+      { label: '鑿滃崟绫诲瀷', key: 'menuType', span: 24 },
+      {
+        label: '涓婄骇鑿滃崟',
+        key: 'parentId',
+        type: 'treeselect',
+        span: 24,
+        props: {
+          data: props.menuTreeOptions,
+          props: {
+            label: 'label',
+            value: 'value',
+            children: 'children'
+          },
+          placeholder: '璇烽�夋嫨涓婄骇鑿滃崟',
+          checkStrictly: true,
+          clearable: false,
+          defaultExpandAll: true
+        }
+      },
+      {
+        label: form.menuType === 'button' ? '鏉冮檺鍚嶇О' : '鑿滃崟鍚嶇О',
+        key: 'name',
+        type: 'input',
+        span: 24,
+        props: {
+          placeholder: form.menuType === 'button' ? '璇疯緭鍏ユ潈闄愬悕绉�' : '璇疯緭鍏ヨ彍鍗曞悕绉�',
+          clearable: true
+        }
+      }
+    ]
+
     if (form.menuType === 'menu') {
-      return [
-        ...baseItems,
-        { label: '鑿滃崟鍚嶇О', key: 'name', type: 'input', props: { placeholder: '鑿滃崟鍚嶇О' } },
+      items.push(
         {
-          label: createLabelTooltip(
-            '璺敱鍦板潃',
-            '涓�绾ц彍鍗曪細浠� / 寮�澶寸殑缁濆璺緞锛堝 /dashboard锛塡n浜岀骇鍙婁互涓嬶細鐩稿璺緞锛堝 console銆乽ser锛�'
-          ),
-          key: 'path',
+          label: '璺敱鍦板潃',
+          key: 'route',
           type: 'input',
-          props: { placeholder: '濡傦細/dashboard 鎴� console' }
+          span: 24,
+          props: {
+            placeholder: '璇疯緭鍏ヨ矾鐢卞湴鍧�',
+            clearable: true
+          }
         },
-        { label: '鏉冮檺鏍囪瘑', key: 'label', type: 'input', props: { placeholder: '濡傦細User' } },
         {
-          label: createLabelTooltip(
-            '缁勪欢璺緞',
-            '涓�绾х埗绾ц彍鍗曪細濉啓 /index/index\n鍏蜂綋椤甸潰锛氬~鍐欑粍浠惰矾寰勶紙濡� /system/user锛塡n鐩綍鑿滃崟锛氱暀绌�'
-          ),
+          label: '缁勪欢鏍囪瘑',
           key: 'component',
           type: 'input',
-          props: { placeholder: '濡傦細/system/user 鎴栫暀绌�' }
-        },
-        { label: '鍥炬爣', key: 'icon', type: 'input', props: { placeholder: '濡傦細ri:user-line' } },
-        {
-          label: createLabelTooltip(
-            '瑙掕壊鏉冮檺',
-            '浠呯敤浜庡墠绔潈闄愭ā寮忥細閰嶇疆瑙掕壊鏍囪瘑锛堝 R_SUPER銆丷_ADMIN锛塡n鍚庣鏉冮檺妯″紡锛氭棤闇�閰嶇疆'
-          ),
-          key: 'roles',
-          type: 'inputtag',
-          props: { placeholder: '杈撳叆瑙掕壊鏍囪瘑鍚庢寜鍥炶溅锛屽锛歊_SUPER' }
-        },
-        {
-          label: '鑿滃崟鎺掑簭',
-          key: 'sort',
-          type: 'number',
-          props: { min: 1, controlsPosition: 'right', style: { width: '100%' } }
-        },
-        {
-          label: '澶栭儴閾炬帴',
-          key: 'link',
-          type: 'input',
-          props: { placeholder: '濡傦細https://www.example.com' }
-        },
-        {
-          label: '鏂囨湰寰界珷',
-          key: 'showTextBadge',
-          type: 'input',
-          props: { placeholder: '濡傦細New銆丠ot' }
-        },
-        {
-          label: createLabelTooltip(
-            '婵�娲昏矾寰�',
-            '鐢ㄤ簬璇︽儏椤电瓑闅愯棌鑿滃崟锛屾寚瀹氶珮浜樉绀虹殑鐖剁骇鑿滃崟璺緞\n渚嬪锛氱敤鎴疯鎯呴〉楂樹寒鏄剧ず"鐢ㄦ埛绠$悊"鑿滃崟'
-          ),
-          key: 'activePath',
-          type: 'input',
-          props: { placeholder: '濡傦細/system/user' }
-        },
-        { label: '鏄惁鍚敤', key: 'isEnable', type: 'switch', span: switchSpan },
-        { label: '椤甸潰缂撳瓨', key: 'keepAlive', type: 'switch', span: switchSpan },
-        { label: '闅愯棌鑿滃崟', key: 'isHide', type: 'switch', span: switchSpan },
-        { label: '鏄惁鍐呭祵', key: 'isIframe', type: 'switch', span: switchSpan },
-        { label: '鏄剧ず寰界珷', key: 'showBadge', type: 'switch', span: switchSpan },
-        { label: '鍥哄畾鏍囩', key: 'fixedTab', type: 'switch', span: switchSpan },
-        { label: '鏍囩闅愯棌', key: 'isHideTab', type: 'switch', span: switchSpan },
-        { label: '鍏ㄥ睆椤甸潰', key: 'isFullPage', type: 'switch', span: switchSpan }
-      ]
-    } else {
-      return [
-        ...baseItems,
-        {
-          label: '鏉冮檺鍚嶇О',
-          key: 'authName',
-          type: 'input',
-          props: { placeholder: '濡傦細鏂板銆佺紪杈戙�佸垹闄�' }
-        },
-        {
-          label: '鏉冮檺鏍囪瘑',
-          key: 'authLabel',
-          type: 'input',
-          props: { placeholder: '濡傦細add銆乪dit銆乨elete' }
-        },
-        {
-          label: '鏉冮檺鎺掑簭',
-          key: 'authSort',
-          type: 'number',
-          props: { min: 1, controlsPosition: 'right', style: { width: '100%' } }
+          span: 24,
+          props: {
+            placeholder: '璇疯緭鍏ョ粍浠舵爣璇�',
+            clearable: true
+          }
         }
-      ]
+      )
     }
+
+    items.push(
+      {
+        label: '鏉冮檺鏍囪瘑',
+        key: 'authority',
+        type: 'input',
+        span: 24,
+        props: {
+          placeholder: '璇疯緭鍏ユ潈闄愭爣璇�',
+          clearable: true
+        }
+      },
+      {
+        label: '鍥炬爣',
+        key: 'icon',
+        type: 'input',
+        span: 24,
+        props: {
+          placeholder: '璇疯緭鍏ュ浘鏍囧悕绉�',
+          clearable: true
+        }
+      },
+      {
+        label: '鎺掑簭',
+        key: 'sort',
+        type: 'number',
+        span: 24,
+        props: {
+          min: 0,
+          controlsPosition: 'right',
+          style: { width: '100%' }
+        }
+      },
+      {
+        label: '鐘舵��',
+        key: 'status',
+        type: 'select',
+        span: 24,
+        props: {
+          placeholder: '璇烽�夋嫨鐘舵��',
+          options: [
+            { label: '鍚敤', value: 1 },
+            { label: '绂佺敤', value: 0 }
+          ]
+        }
+      },
+      {
+        label: '澶囨敞',
+        key: 'memo',
+        type: 'input',
+        span: 24,
+        props: {
+          type: 'textarea',
+          rows: 3,
+          placeholder: '璇疯緭鍏ュ娉�',
+          clearable: true
+        }
+      }
+    )
+
+    return items
   })
-  const dialogTitle = computed(() => {
-    const type = form.menuType === 'menu' ? '鑿滃崟' : '鎸夐挳'
-    return isEdit.value ? `缂栬緫${type}` : `鏂板缓${type}`
-  })
-  const disableMenuType = computed(() => {
-    if (isEdit.value) return true
-    if (!isEdit.value && form.menuType === 'menu' && props.lockType) return true
-    return false
-  })
+
+  const normalizeNumber = (value, fallback = 0) => {
+    if (value === '' || value === null || value === undefined) {
+      return fallback
+    }
+    const normalized = Number(value)
+    return Number.isNaN(normalized) ? fallback : normalized
+  }
+
   const resetForm = () => {
-    formRef.value?.reset()
-    form.menuType = 'menu'
+    Object.assign(form, createMenuFormState())
+    formRef.value?.clearValidate?.()
   }
+
   const loadFormData = () => {
-    if (!props.editData) return
-    isEdit.value = true
-    if (form.menuType === 'menu') {
-      const row = props.editData
-      form.id = row.id || 0
-      form.name = formatMenuTitle(row.meta?.title || '')
-      form.path = row.path || ''
-      form.label = row.name || ''
-      form.component = row.component || ''
-      form.icon = row.meta?.icon || ''
-      form.sort = row.meta?.sort || 1
-      form.isMenu = row.meta?.isMenu ?? true
-      form.keepAlive = row.meta?.keepAlive ?? false
-      form.isHide = row.meta?.isHide ?? false
-      form.isHideTab = row.meta?.isHideTab ?? false
-      form.isEnable = row.meta?.isEnable ?? true
-      form.link = row.meta?.link || ''
-      form.isIframe = row.meta?.isIframe ?? false
-      form.showBadge = row.meta?.showBadge ?? false
-      form.showTextBadge = row.meta?.showTextBadge || ''
-      form.fixedTab = row.meta?.fixedTab ?? false
-      form.activePath = row.meta?.activePath || ''
-      form.roles = row.meta?.roles || []
-      form.isFullPage = row.meta?.isFullPage ?? false
-    } else {
-      const row = props.editData
-      form.authName = row.title || ''
-      form.authLabel = row.authMark || ''
-      form.authIcon = row.icon || ''
-      form.authSort = row.sort || 1
+    resetForm()
+    form.menuType = props.type || 'menu'
+
+    const row = props.editData
+    if (!row || typeof row !== 'object') {
+      return
     }
+
+    form.menuType = Number(row.type) === 1 ? 'button' : props.type || 'menu'
+    form.id = row.id ?? null
+    form.parentId = normalizeNumber(row.parentId, 0)
+    form.name = row.name || ''
+    form.route = row.route || ''
+    form.component = row.component || ''
+    form.authority = row.authority || row.meta?.authMark || ''
+    form.icon = row.icon || row.meta?.icon || ''
+    form.sort = normalizeNumber(row.sort ?? row.meta?.sort, 0)
+    form.status = normalizeNumber(row.status, row.meta?.isEnable === false ? 0 : 1)
+    form.memo = row.memo || ''
   }
+
   const handleSubmit = async () => {
     if (!formRef.value) return
     try {
       await formRef.value.validate()
-      emit('submit', { ...form })
-      ElMessage.success(`${isEdit.value ? '缂栬緫' : '鏂板'}鎴愬姛`)
-      handleCancel()
+      emit('submit', {
+        ...form,
+        type: form.menuType === 'button' ? 1 : 0
+      })
     } catch {
-      ElMessage.error('琛ㄥ崟鏍¢獙澶辫触锛岃妫�鏌ヨ緭鍏�')
+      return
     }
   }
+
   const handleCancel = () => {
     emit('update:visible', false)
   }
+
   const handleClosed = () => {
     resetForm()
-    isEdit.value = false
   }
+
   watch(
     () => props.visible,
-    (newVal) => {
-      if (newVal) {
-        form.menuType = props.type
+    (visible) => {
+      if (visible) {
+        loadFormData()
         nextTick(() => {
-          if (props.editData) {
-            loadFormData()
-          }
+          formRef.value?.clearValidate?.()
         })
       }
-    }
+    },
+    { immediate: true }
   )
+
+  watch(
+    () => props.editData,
+    () => {
+      if (props.visible) {
+        loadFormData()
+      }
+    },
+    { deep: true }
+  )
+
   watch(
     () => props.type,
-    (newType) => {
+    () => {
       if (props.visible) {
-        form.menuType = newType
+        loadFormData()
       }
     }
   )

--
Gitblit v1.9.1