From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目

---
 src/views/system/menu/index.vue |  318 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 318 insertions(+), 0 deletions(-)

diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue
new file mode 100644
index 0000000..af3e0ad
--- /dev/null
+++ b/src/views/system/menu/index.vue
@@ -0,0 +1,318 @@
+<template>
+  <doc-alert title="鍔熻兘鏉冮檺" url="https://doc.iocoder.cn/resource-permission" />
+  <doc-alert title="鑿滃崟璺敱" url="https://doc.iocoder.cn/vue3/route/" />
+
+  <!-- 鎼滅储宸ヤ綔鏍� -->
+  <ContentWrap>
+    <el-form
+      ref="queryFormRef"
+      :inline="true"
+      :model="queryParams"
+      class="-mb-15px"
+      label-width="68px"
+    >
+      <el-form-item label="鑿滃崟鍚嶇О" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          class="!w-240px"
+          clearable
+          placeholder="璇疯緭鍏ヨ彍鍗曞悕绉�"
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="鐘舵��" prop="status">
+        <el-select
+          v-model="queryParams.status"
+          class="!w-240px"
+          clearable
+          placeholder="璇烽�夋嫨鑿滃崟鐘舵��"
+        >
+          <el-option
+            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery">
+          <Icon class="mr-5px" icon="ep:search" />
+          鎼滅储
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon class="mr-5px" icon="ep:refresh" />
+          閲嶇疆
+        </el-button>
+        <el-button
+          v-hasPermi="['system:menu:create']"
+          plain
+          type="primary"
+          @click="openForm('create')"
+        >
+          <Icon class="mr-5px" icon="ep:plus" />
+          鏂板
+        </el-button>
+        <el-button plain type="danger" @click="toggleExpandAll">
+          <Icon class="mr-5px" icon="ep:sort" />
+          灞曞紑/鎶樺彔
+        </el-button>
+        <el-button plain @click="refreshMenu">
+          <Icon class="mr-5px" icon="ep:refresh" />
+          鍒锋柊鑿滃崟缂撳瓨
+        </el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 鍒楄〃 -->
+  <ContentWrap>
+    <el-auto-resizer>
+      <template #default="{ width }">
+        <el-table-v2
+          v-model:expanded-row-keys="expandedRowKeys"
+          :columns="columns"
+          :data="list"
+          :expand-column-key="columns[0].key"
+          :height="1000"
+          :width="width"
+          fixed
+          row-key="id"
+        />
+      </template>
+    </el-auto-resizer>
+  </ContentWrap>
+
+  <!-- 琛ㄥ崟寮圭獥锛氭坊鍔�/淇敼 -->
+  <MenuForm ref="formRef" @success="getList" />
+</template>
+<script lang="tsx" setup>
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import { handleTree } from '@/utils/tree'
+import * as MenuApi from '@/api/system/menu'
+import { MenuVO } from '@/api/system/menu'
+import MenuForm from './MenuForm.vue'
+import DictTag from '@/components/DictTag/src/DictTag.vue'
+import { Icon } from '@/components/Icon'
+import { ElButton, TableV2FixedDir, ElSwitch } from 'element-plus'
+import { checkPermi } from '@/utils/permission'
+import { CommonStatusEnum } from '@/utils/constants'
+import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+
+defineOptions({ name: 'SystemMenu' })
+
+// 铏氭嫙鍒楄〃琛ㄦ牸
+const columns = [
+  {
+    key: 'name',
+    title: '鑿滃崟鍚嶇О',
+    dataKey: 'name',
+    width: 250,
+    fixed: TableV2FixedDir.LEFT
+  },
+  {
+    key: 'icon',
+    title: '鍥炬爣',
+    dataKey: 'icon',
+    width: 100,
+    align: 'center',
+    cellRenderer: ({ cellData: icon }) => <Icon icon={icon} />
+  },
+  {
+    key: 'sort',
+    title: '鎺掑簭',
+    dataKey: 'sort',
+    width: 60
+  },
+  {
+    key: 'permission',
+    title: '鏉冮檺鏍囪瘑',
+    dataKey: 'permission',
+    width: 300
+  },
+  {
+    key: 'component',
+    title: '缁勪欢璺緞',
+    dataKey: 'component',
+    width: 500
+  },
+  {
+    key: 'componentName',
+    title: '缁勪欢鍚嶇О',
+    dataKey: 'componentName',
+    width: 200
+  },
+  {
+    key: 'status',
+    title: '鐘舵��',
+    dataKey: 'status',
+    width: 60,
+    fixed: TableV2FixedDir.RIGHT,
+    cellRenderer: ({ rowData }) => {
+      // 妫�鏌ユ潈闄�
+      if (!checkPermi(['system:menu:update'])) {
+        return <DictTag type={DICT_TYPE.COMMON_STATUS} value={rowData.status} />
+      }
+
+      // 濡傛灉鏈夋潈闄愶紝娓叉煋 ElSwitch
+      return (
+        <ElSwitch
+          v-model={rowData.status}
+          active-value={CommonStatusEnum.ENABLE}
+          inactive-value={CommonStatusEnum.DISABLE}
+          loading={menuStatusUpdating[rowData.id]}
+          class="ml-4px"
+          onChange={(val) => handleStatusChanged(rowData, val)}
+        />
+      )
+    }
+  },
+  {
+    key: 'operations',
+    title: '鎿嶄綔',
+    align: 'center',
+    width: 160,
+    fixed: TableV2FixedDir.RIGHT,
+    cellRenderer: ({ rowData }) => {
+      // 瀹氫箟鎸夐挳鍒楄〃
+      const buttons: InstanceType<typeof ElButton>[] = []
+
+      // 妫�鏌ユ潈闄愬苟娣诲姞鎸夐挳
+      if (checkPermi(['system:menu:update'])) {
+        buttons.push(
+          <ElButton key="edit" link type="primary" onClick={() => openForm('update', rowData.id)}>
+            淇敼
+          </ElButton>
+        )
+      }
+      if (checkPermi(['system:menu:create'])) {
+        buttons.push(
+          <ElButton
+            key="create"
+            link
+            type="primary"
+            onClick={() => openForm('create', undefined, rowData.id)}
+          >
+            鏂板
+          </ElButton>
+        )
+      }
+      if (checkPermi(['system:menu:delete'])) {
+        buttons.push(
+          <ElButton key="delete" link type="danger" onClick={() => handleDelete(rowData.id)}>
+            鍒犻櫎
+          </ElButton>
+        )
+      }
+      // 濡傛灉娌℃湁鏉冮檺锛岃繑鍥� null
+      if (buttons.length === 0) {
+        return null
+      }
+      // 娓叉煋鎸夐挳鍒楄〃
+      return <>{buttons}</>
+    }
+  }
+]
+
+const { wsCache } = useCache()
+const { t } = useI18n() // 鍥介檯鍖�
+const message = useMessage() // 娑堟伅寮圭獥
+
+const loading = ref(true) // 鍒楄〃鐨勫姞杞戒腑
+const list = ref<any[]>([]) // 鍒楄〃鐨勬暟鎹�
+const queryParams = reactive({
+  name: undefined,
+  status: undefined
+})
+const queryFormRef = ref() // 鎼滅储鐨勮〃鍗�
+const isExpandAll = ref(false) // 鏄惁灞曞紑锛岄粯璁ゅ叏閮ㄦ姌鍙�
+const refreshTable = ref(true) // 閲嶆柊娓叉煋琛ㄦ牸鐘舵��
+
+// 娣诲姞灞曞紑琛屾帶鍒�
+const expandedRowKeys = ref<number[]>([])
+
+/** 鏌ヨ鍒楄〃 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await MenuApi.getMenuList(queryParams)
+    list.value = handleTree(data)
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  getList()
+}
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 娣诲姞/淇敼鎿嶄綔 */
+const formRef = ref()
+const openForm = (type: string, id?: number, parentId?: number) => {
+  formRef.value.open(type, id, parentId)
+}
+
+/** 灞曞紑/鎶樺彔鎿嶄綔 */
+const toggleExpandAll = () => {
+  if (!isExpandAll.value) {
+    // 灞曞紑鎵�鏈�
+    expandedRowKeys.value = list.value.map((item) => item.id)
+  } else {
+    // 鎶樺彔鎵�鏈�
+    expandedRowKeys.value = []
+  }
+  isExpandAll.value = !isExpandAll.value
+}
+
+/** 鍒锋柊鑿滃崟缂撳瓨鎸夐挳鎿嶄綔 */
+const refreshMenu = async () => {
+  try {
+    await message.confirm('鍗冲皢鏇存柊缂撳瓨鍒锋柊娴忚鍣紒', '鍒锋柊鑿滃崟缂撳瓨')
+    // 娓呯┖锛屼粠鑰岃Е鍙戝埛鏂�
+    wsCache.delete(CACHE_KEY.USER)
+    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
+    // 鍒锋柊娴忚鍣�
+    location.reload()
+  } catch {}
+}
+
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+const handleDelete = async (id: number) => {
+  try {
+    // 鍒犻櫎鐨勪簩娆$‘璁�
+    await message.delConfirm()
+    // 鍙戣捣鍒犻櫎
+    await MenuApi.deleteMenu(id)
+    message.success(t('common.delSuccess'))
+    // 鍒锋柊鍒楄〃
+    await getList()
+  } catch {}
+}
+
+/** 寮�鍚�/鍏抽棴鑿滃崟鐨勭姸鎬� */
+const menuStatusUpdating = ref({}) // 鑿滃崟鐘舵�佹洿鏂颁腑鐨� menu 鏄犲皠銆俴ey锛氳彍鍗曠紪鍙凤紝value锛氭槸鍚︽洿鏂颁腑
+const handleStatusChanged = async (menu: MenuVO, val: number) => {
+  // 1. 鏍囪 menu.id 鏇存柊涓�
+  menuStatusUpdating.value[menu.id] = true
+  try {
+    // 2. 鍙戣捣鏇存柊鐘舵��
+    menu.status = val
+    await MenuApi.updateMenu(menu)
+  } finally {
+    // 3. 鏍囪 menu.id 鏇存柊瀹屾垚
+    menuStatusUpdating.value[menu.id] = false
+  }
+}
+
+/** 鍒濆鍖� **/
+onMounted(() => {
+  getList()
+})
+</script>

--
Gitblit v1.8.0