Преглед изворни кода

Merge branch 'master' into release

kailong321200875 пре 1 година
родитељ
комит
3cc292aa4a

+ 2 - 2
.env.base

@@ -2,10 +2,10 @@
 NODE_ENV=development
 
 # 接口前缀
-VITE_API_BASE_PATH=base
+VITE_API_BASE_PATH=
 
 # 打包路径
 VITE_BASE_PATH=/
 
 # 标题
-VITE_APP_TITLE=ElementAdmin
+VITE_APP_TITLE=ElementAdmin

+ 1 - 1
.env.dev

@@ -2,7 +2,7 @@
 NODE_ENV=production
 
 # 接口前缀
-VITE_API_BASE_PATH=dev
+VITE_API_BASE_PATH=
 
 # 打包路径
 VITE_BASE_PATH=/dist-dev/

+ 1 - 1
.env.gitee

@@ -2,7 +2,7 @@
 NODE_ENV=production
 
 # 接口前缀
-VITE_API_BASE_PATH=pro
+VITE_API_BASE_PATH=
 
 # 打包路径
 VITE_BASE_PATH=/vue-element-plus-admin/

+ 1 - 1
.env.pro

@@ -2,7 +2,7 @@
 NODE_ENV=production
 
 # 接口前缀
-VITE_API_BASE_PATH=pro
+VITE_API_BASE_PATH=
 
 # 打包路径
 VITE_BASE_PATH=/

+ 1 - 1
.env.test

@@ -2,7 +2,7 @@
 NODE_ENV=production
 
 # 接口前缀
-VITE_API_BASE_PATH=test
+VITE_API_BASE_PATH=
 
 # 打包路径
 VITE_BASE_PATH=/dist-test/

+ 2 - 1
.eslintrc.js

@@ -65,6 +65,7 @@ module.exports = defineConfig({
       }
     ],
     'vue/multi-word-component-names': 'off',
-    'vue/no-v-html': 'off'
+    'vue/no-v-html': 'off',
+    'vue/require-toggle-inside-transition': 'off'
   }
 })

+ 2 - 2
.vscode/settings.json

@@ -1,11 +1,11 @@
 {
   "typescript.tsdk": "node_modules/typescript/lib",
-  "prettier.enable": false,
+  "prettier.enable": true,
   "editor.codeActionsOnSave": {
     "source.fixAll.eslint": true
   },
   "[vue]": {
-    "editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
   },
   "i18n-ally.localesPaths": ["src/locales"],
   "i18n-ally.keystyle": "nested",

+ 0 - 2
mock/department/index.ts

@@ -124,8 +124,6 @@ export default [
             email: '@EMAIL',
             // 创建时间
             createTime: '@datetime',
-            // 角色
-            role: '@first',
             // 用户id
             id: toAnyString()
           })

+ 104 - 22
mock/menu/index.ts

@@ -24,6 +24,8 @@ export default [
               name: 'Dashboard',
               status: Mock.Random.integer(0, 1),
               id: 1,
+              type: 0,
+              parentId: undefined,
               title: '首页',
               meta: {
                 title: '首页',
@@ -37,10 +39,23 @@ export default [
                   name: 'Analysis',
                   status: Mock.Random.integer(0, 1),
                   id: 2,
+                  type: 1,
+                  parentId: 1,
                   title: '分析页',
+                  permissionList: [
+                    {
+                      label: '新增',
+                      value: 'add'
+                    },
+                    {
+                      label: '编辑',
+                      value: 'edit'
+                    }
+                  ],
                   meta: {
                     title: '分析页',
-                    noCache: true
+                    noCache: true,
+                    permission: ['add', 'edit']
                   }
                 },
                 {
@@ -49,7 +64,23 @@ export default [
                   name: 'Workplace',
                   status: Mock.Random.integer(0, 1),
                   id: 3,
+                  type: 1,
+                  parentId: 1,
                   title: '工作台',
+                  permissionList: [
+                    {
+                      label: '新增',
+                      value: 'add'
+                    },
+                    {
+                      label: '编辑',
+                      value: 'edit'
+                    },
+                    {
+                      label: '删除',
+                      value: 'delete'
+                    }
+                  ],
                   meta: {
                     title: '工作台',
                     noCache: true
@@ -60,7 +91,6 @@ export default [
             {
               path: '/external-link',
               component: '#',
-              title: '文档',
               meta: {
                 title: '文档',
                 icon: 'clarity:document-solid'
@@ -68,12 +98,17 @@ export default [
               name: 'ExternalLink',
               status: Mock.Random.integer(0, 1),
               id: 4,
+              type: 0,
+              parentId: undefined,
+              title: '文档',
               children: [
                 {
                   path: 'https://element-plus-admin-doc.cn/',
                   name: 'DocumentLink',
                   status: Mock.Random.integer(0, 1),
                   id: 5,
+                  type: 1,
+                  parentId: 4,
                   title: '文档',
                   meta: {
                     title: '文档'
@@ -88,6 +123,8 @@ export default [
               name: 'Level',
               status: Mock.Random.integer(0, 1),
               id: 6,
+              type: 0,
+              parentId: undefined,
               title: '菜单',
               meta: {
                 title: '菜单',
@@ -100,8 +137,10 @@ export default [
                   component: '##',
                   status: Mock.Random.integer(0, 1),
                   id: 7,
-                  redirect: '/level/menu1/menu1-1/menu1-1-1',
+                  type: 0,
+                  parentId: 6,
                   title: '菜单1',
+                  redirect: '/level/menu1/menu1-1/menu1-1-1',
                   meta: {
                     title: '菜单1'
                   },
@@ -112,8 +151,10 @@ export default [
                       component: '##',
                       status: Mock.Random.integer(0, 1),
                       id: 8,
-                      redirect: '/level/menu1/menu1-1/menu1-1-1',
+                      type: 0,
+                      parentId: 7,
                       title: '菜单1-1',
+                      redirect: '/level/menu1/menu1-1/menu1-1-1',
                       meta: {
                         title: '菜单1-1',
                         alwaysShow: true
@@ -125,7 +166,8 @@ export default [
                           component: 'views/Level/Menu111',
                           status: Mock.Random.integer(0, 1),
                           id: 9,
-                          permission: ['edit', 'add', 'delete'],
+                          type: 1,
+                          parentId: 8,
                           title: '菜单1-1-1',
                           meta: {
                             title: '菜单1-1-1'
@@ -139,7 +181,8 @@ export default [
                       component: 'views/Level/Menu12',
                       status: Mock.Random.integer(0, 1),
                       id: 10,
-                      permission: ['edit', 'add', 'delete'],
+                      type: 1,
+                      parentId: 7,
                       title: '菜单1-2',
                       meta: {
                         title: '菜单1-2'
@@ -153,7 +196,8 @@ export default [
                   component: 'views/Level/Menu2',
                   status: Mock.Random.integer(0, 1),
                   id: 11,
-                  permission: ['edit', 'add', 'delete'],
+                  type: 1,
+                  parentId: 6,
                   title: '菜单2',
                   meta: {
                     title: '菜单2'
@@ -168,6 +212,8 @@ export default [
               name: 'Example',
               status: Mock.Random.integer(0, 1),
               id: 12,
+              type: 0,
+              parentId: undefined,
               title: '综合示例',
               meta: {
                 title: '综合示例',
@@ -181,11 +227,29 @@ export default [
                   name: 'ExampleDialog',
                   status: Mock.Random.integer(0, 1),
                   id: 13,
+                  type: 1,
+                  parentId: 12,
                   title: '综合示例-弹窗',
-                  permission: ['edit', 'add', 'delete'],
+                  permissionList: [
+                    {
+                      label: '新增',
+                      value: 'add'
+                    },
+                    {
+                      label: '编辑',
+                      value: 'edit'
+                    },
+                    {
+                      label: '删除',
+                      value: 'delete'
+                    },
+                    {
+                      label: '查看',
+                      value: 'view'
+                    }
+                  ],
                   meta: {
-                    title: '综合示例-弹窗',
-                    permission: ['edit', 'add']
+                    title: '综合示例-弹窗'
                   }
                 },
                 {
@@ -194,11 +258,29 @@ export default [
                   name: 'ExamplePage',
                   status: Mock.Random.integer(0, 1),
                   id: 14,
-                  permission: ['edit', 'add', 'delete'],
+                  type: 1,
+                  parentId: 12,
                   title: '综合示例-页面',
+                  permissionList: [
+                    {
+                      label: '新增',
+                      value: 'edit'
+                    },
+                    {
+                      label: '编辑',
+                      value: 'edit'
+                    },
+                    {
+                      label: '删除',
+                      value: 'delete'
+                    },
+                    {
+                      label: '查看',
+                      value: 'view'
+                    }
+                  ],
                   meta: {
-                    title: '综合示例-页面',
-                    permission: ['edit', 'add']
+                    title: '综合示例-页面'
                   }
                 },
                 {
@@ -207,7 +289,8 @@ export default [
                   name: 'ExampleAdd',
                   status: Mock.Random.integer(0, 1),
                   id: 15,
-                  permission: ['edit', 'add', 'delete'],
+                  type: 1,
+                  parentId: 12,
                   title: '综合示例-新增',
                   meta: {
                     title: '综合示例-新增',
@@ -215,8 +298,7 @@ export default [
                     noCache: true,
                     hidden: true,
                     showMainRoute: true,
-                    activeMenu: '/example/example-page',
-                    permission: ['delete', 'add']
+                    activeMenu: '/example/example-page'
                   }
                 },
                 {
@@ -225,7 +307,8 @@ export default [
                   name: 'ExampleEdit',
                   status: Mock.Random.integer(0, 1),
                   id: 16,
-                  permission: ['edit', 'add', 'delete'],
+                  type: 1,
+                  parentId: 12,
                   title: '综合示例-编辑',
                   meta: {
                     title: '综合示例-编辑',
@@ -233,8 +316,7 @@ export default [
                     noCache: true,
                     hidden: true,
                     showMainRoute: true,
-                    activeMenu: '/example/example-page',
-                    permission: ['delete', 'add']
+                    activeMenu: '/example/example-page'
                   }
                 },
                 {
@@ -243,7 +325,8 @@ export default [
                   name: 'ExampleDetail',
                   status: Mock.Random.integer(0, 1),
                   id: 17,
-                  permission: ['edit', 'add', 'delete'],
+                  type: 1,
+                  parentId: 12,
                   title: '综合示例-详情',
                   meta: {
                     title: '综合示例-详情',
@@ -251,8 +334,7 @@ export default [
                     noCache: true,
                     hidden: true,
                     showMainRoute: true,
-                    activeMenu: '/example/example-page',
-                    permission: ['delete', 'edit']
+                    activeMenu: '/example/example-page'
                   }
                 }
               ]

+ 29 - 29
package.json

@@ -34,12 +34,12 @@
     "@wangeditor/editor-for-vue": "^5.1.10",
     "@zxcvbn-ts/core": "^3.0.4",
     "animate.css": "^4.1.1",
-    "axios": "^1.5.1",
+    "axios": "^1.6.0",
     "dayjs": "^1.11.10",
     "driver.js": "^1.3.0",
     "echarts": "^5.4.3",
     "echarts-wordcloud": "^2.1.0",
-    "element-plus": "^2.4.0",
+    "element-plus": "^2.4.2",
     "lodash-es": "^4.17.21",
     "mitt": "^3.0.1",
     "mockjs": "^1.1.0",
@@ -49,62 +49,62 @@
     "qrcode": "^1.5.3",
     "qs": "^6.11.2",
     "url": "^0.11.3",
-    "vue": "3.3.4",
-    "vue-i18n": "9.5.0",
+    "vue": "3.3.8",
+    "vue-i18n": "9.6.5",
     "vue-json-pretty": "^2.2.4",
     "vue-router": "^4.2.5",
     "vue-types": "^5.1.1"
   },
   "devDependencies": {
-    "@commitlint/cli": "^17.7.2",
-    "@commitlint/config-conventional": "^17.7.0",
-    "@iconify/json": "^2.2.128",
-    "@intlify/unplugin-vue-i18n": "^1.4.0",
+    "@commitlint/cli": "^18.2.0",
+    "@commitlint/config-conventional": "^18.1.0",
+    "@iconify/json": "^2.2.138",
+    "@intlify/unplugin-vue-i18n": "^1.5.0",
     "@purge-icons/generated": "^0.9.0",
-    "@types/fs-extra": "^11.0.2",
-    "@types/inquirer": "^9.0.4",
-    "@types/lodash-es": "^4.17.9",
-    "@types/node": "^20.8.6",
-    "@types/nprogress": "^0.2.1",
-    "@types/qrcode": "^1.5.2",
-    "@types/qs": "^6.9.8",
-    "@types/sortablejs": "^1.15.3",
-    "@typescript-eslint/eslint-plugin": "^6.7.5",
-    "@typescript-eslint/parser": "^6.7.5",
-    "@unocss/transformer-variant-group": "^0.56.5",
+    "@types/fs-extra": "^11.0.4",
+    "@types/inquirer": "^9.0.7",
+    "@types/lodash-es": "^4.17.11",
+    "@types/node": "^20.9.0",
+    "@types/nprogress": "^0.2.3",
+    "@types/qrcode": "^1.5.5",
+    "@types/qs": "^6.9.10",
+    "@types/sortablejs": "^1.15.5",
+    "@typescript-eslint/eslint-plugin": "^6.10.0",
+    "@typescript-eslint/parser": "^6.10.0",
+    "@unocss/transformer-variant-group": "^0.57.2",
     "@vitejs/plugin-legacy": "^4.1.1",
     "@vitejs/plugin-vue": "^4.4.0",
     "@vitejs/plugin-vue-jsx": "^3.0.2",
     "autoprefixer": "^10.4.16",
     "chalk": "^5.3.0",
     "consola": "^3.2.3",
-    "eslint": "^8.51.0",
+    "eslint": "^8.53.0",
     "eslint-config-prettier": "^9.0.0",
     "eslint-define-config": "^1.24.1",
     "eslint-plugin-prettier": "^5.0.1",
-    "eslint-plugin-vue": "^9.17.0",
+    "eslint-plugin-vue": "^9.18.1",
     "esno": "^0.17.0",
     "fs-extra": "^11.1.1",
     "husky": "^8.0.3",
-    "inquirer": "^9.2.11",
+    "inquirer": "^9.2.12",
     "less": "^4.2.0",
-    "lint-staged": "^14.0.1",
+    "lint-staged": "^15.0.2",
     "plop": "^4.0.0",
     "postcss": "^8.4.31",
     "postcss-html": "^1.5.0",
     "postcss-less": "^6.0.0",
     "prettier": "^3.0.3",
     "rimraf": "^5.0.5",
-    "rollup": "^4.0.2",
-    "stylelint": "^15.10.3",
+    "rollup": "^4.3.0",
+    "stylelint": "^15.11.0",
     "stylelint-config-html": "^1.1.0",
     "stylelint-config-recommended": "^13.0.0",
     "stylelint-config-standard": "^34.0.0",
     "stylelint-order": "^6.0.3",
-    "terser": "^5.21.0",
+    "terser": "^5.24.0",
     "typescript": "5.2.2",
-    "unocss": "^0.56.5",
-    "vite": "4.4.11",
+    "unocss": "^0.57.2",
+    "vite": "4.5.0",
     "vite-plugin-ejs": "^1.6.4",
     "vite-plugin-eslint": "^1.8.1",
     "vite-plugin-mock": "2.9.6",
@@ -112,7 +112,7 @@
     "vite-plugin-purge-icons": "^0.9.2",
     "vite-plugin-style-import": "2.0.0",
     "vite-plugin-svg-icons": "^2.0.1",
-    "vue-tsc": "^1.8.19"
+    "vue-tsc": "^1.8.22"
   },
   "engines": {
     "node": ">= 16.13.0"

+ 114 - 37
src/components/Waterfall/src/Waterfall.vue

@@ -20,10 +20,13 @@ const prop = defineProps({
     src: 'src',
     height: 'height'
   }),
+  cols: propTypes.number.def(undefined),
   loadingText: propTypes.string.def('加载中...'),
   loading: propTypes.bool.def(false),
   end: propTypes.bool.def(false),
-  endText: propTypes.string.def('没有更多了')
+  endText: propTypes.string.def('没有更多了'),
+  autoCenter: propTypes.bool.def(true),
+  layout: propTypes.oneOf(['javascript', 'flex']).def('flex')
 })
 
 const wrapEl = ref<HTMLDivElement>()
@@ -37,7 +40,7 @@ const wrapWidth = ref(0)
 const loadMore = ref<HTMLDivElement>()
 
 // 首先确定列数 = 页面宽度 / 图片宽度
-const cols = ref(0)
+const innerCols = ref(0)
 
 const filterData = ref<any[]>([])
 
@@ -49,11 +52,11 @@ const filterWaterfall = async () => {
 
   const container = unref(wrapEl) as HTMLElement
   if (!container) return
-  cols.value = Math.floor(container.clientWidth / (width + gap))
+  innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
 
   const length = data.length
   for (let i = 0; i < length; i++) {
-    if (i < unref(cols)) {
+    if (i < unref(innerCols)) {
       heights.value[i] = data[i][props.height as string]
       filterData.value.push({
         ...data[i],
@@ -66,7 +69,7 @@ const filterWaterfall = async () => {
       let minHeight = heights.value[0]
       let index = 0
       // 找出最小高度
-      for (let j = 1; j < unref(cols); j++) {
+      for (let j = 1; j < unref(innerCols); j++) {
         if (unref(heights)[j] < minHeight) {
           minHeight = unref(heights)[j]
           index = j
@@ -83,14 +86,42 @@ const filterWaterfall = async () => {
     }
   }
   wrapHeight.value = Math.max(...unref(heights))
-  wrapWidth.value = unref(cols) * (width + gap) - gap
+  wrapWidth.value = unref(innerCols) * (width + gap) - gap
 }
 
-watch(
-  () => prop.data,
-  async () => {
-    await nextTick()
+const flexWaterfall = async () => {
+  const { width, gap } = prop
+  const data = prop.data as any[]
+  await nextTick()
+
+  const container = unref(wrapEl) as HTMLElement
+  if (!container) return
+  innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
+
+  const length = data.length
+  // 根据列数,创建数组
+  const arr = new Array(unref(innerCols)).fill([])
+  // 循环data,依次插入到arr中
+  for (let i = 0; i < length; i++) {
+    const index = i % unref(innerCols)
+    arr[index] = [...arr[index], data[i]]
+  }
+  filterData.value = arr
+}
+
+const initLayout = () => {
+  const { layout } = prop
+  if (layout === 'javascript') {
     filterWaterfall()
+  } else if (layout === 'flex') {
+    flexWaterfall()
+  }
+}
+
+watch(
+  () => [prop.data, prop.cols],
+  () => {
+    initLayout()
   },
   {
     immediate: true
@@ -99,7 +130,7 @@ watch(
 
 onMounted(() => {
   if (unref(prop.reset)) {
-    useEventListener(window, 'resize', debounce(filterWaterfall, 300))
+    useEventListener(window, 'resize', debounce(initLayout, 300))
   }
   useIntersectionObserver(
     unref(loadMore),
@@ -117,41 +148,87 @@ onMounted(() => {
 
 <template>
   <div
-    :class="[prefixCls, 'flex', 'justify-center', 'items-center']"
+    :class="[
+      prefixCls,
+      'flex',
+      'items-center',
+      {
+        'justify-center': autoCenter
+      }
+    ]"
     ref="wrapEl"
     :style="{
-      height: `${wrapHeight + 40}px`
+      height: `${layout === 'javascript' ? wrapHeight + 40 : 'auto'}px`
     }"
   >
-    <div
-      class="relative"
-      :style="{
-        width: `${wrapWidth}px`,
-        height: `${wrapHeight + 40}px`
-      }"
-    >
-      <div
-        v-for="(item, $index) in filterData"
-        :class="[`${prefixCls}-item__${$index}`, 'absolute']"
-        :key="`water-${$index}`"
-        :style="{
-          width: `${width}px`,
-          height: `${item[props.height as string]}px`,
-          top: `${item.top}px`,
-          left: `${item.left}px`
-        }"
-      >
-        <img :src="item[props.src as string]" class="w-full h-full block" alt="" srcset="" />
+    <template v-if="layout === 'javascript'">
+      <div class="relative" :style="{ width: `${wrapWidth}px`, height: `${wrapHeight + 40}px` }">
+        <div
+          v-for="(item, $index) in filterData"
+          :class="[
+            `${prefixCls}-item__${$index}`,
+            {
+              absolute: layout === 'javascript'
+            }
+          ]"
+          :key="`water-${$index}`"
+          :style="{
+            width: `${width}px`,
+            height: `${item[props.height as string]}px`,
+            top: `${item.top}px`,
+            left: `${item.left}px`
+          }"
+        >
+          <img :src="item[props.src as string]" class="w-full h-full block" alt="" srcset="" />
+        </div>
+        <div
+          ref="loadMore"
+          class="h-40px flex justify-center absolute w-full"
+          :style="{
+            top: `${wrapHeight + gap}px`
+          }"
+        >
+          {{ end ? endText : loadingText }}
+        </div>
       </div>
+    </template>
+    <template v-else-if="layout === 'flex'">
       <div
-        ref="loadMore"
-        class="h-40px flex justify-center absolute w-full"
+        class="relative flex pb-40px"
         :style="{
-          top: `${wrapHeight + gap}px`
+          width: cols ? '100%' : 'auto'
         }"
       >
-        {{ end ? endText : loadingText }}
+        <div
+          v-for="(item, $index) in filterData"
+          :key="`waterWrap-${$index}`"
+          class="flex-1"
+          :style="{
+            marginRight: $index === filterData.length - 1 ? '0' : `${gap}px`
+          }"
+        >
+          <div
+            v-for="(child, i) in item"
+            :key="`waterWrap-${$index}-${i}`"
+            :style="{
+              marginBottom: `${gap}px`,
+              width: cols ? '100%' : `${width}px`,
+              height: cols ? 'auto' : `${child[props.height as string]}px`
+            }"
+          >
+            <img :src="child[props.src as string]" class="w-full h-full block" alt="" srcset="" />
+          </div>
+        </div>
+        <div
+          ref="loadMore"
+          class="h-40px flex justify-center absolute w-full items-center"
+          :style="{
+            bottom: 0
+          }"
+        >
+          {{ end ? endText : loadingText }}
+        </div>
       </div>
-    </div>
+    </template>
   </div>
 </template>

+ 0 - 17
src/config/axios/config.ts

@@ -8,23 +8,6 @@ import { ElMessage } from 'element-plus'
 import qs from 'qs'
 
 const config: AxiosConfig = {
-  /**
-   * api请求基础路径
-   */
-  baseUrl: {
-    // 开发环境接口前缀
-    base: '',
-
-    // 打包开发环境接口前缀
-    dev: '',
-
-    // 打包生产环境接口前缀
-    pro: '',
-
-    // 打包测试环境接口前缀
-    test: ''
-  },
-
   /**
    * 接口成功返回状态码
    */

+ 2 - 2
src/config/axios/service.ts

@@ -4,8 +4,8 @@ import config, { defaultRequestInterceptors, defaultResponseInterceptors } from
 import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './types'
 import { ElMessage } from 'element-plus'
 
-const { interceptors, baseUrl } = config
-export const PATH_URL = baseUrl[import.meta.env.VITE_API_BASE_PATH]
+const { interceptors } = config
+export const PATH_URL = import.meta.env.VITE_API_BASE_PATH
 
 const { requestInterceptors, responseInterceptors } = interceptors
 

+ 0 - 6
src/config/axios/types/index.ts

@@ -16,12 +16,6 @@ interface RequestInterceptors<T> {
   responseInterceptorsCatch?: (err: any) => any
 }
 interface AxiosConfig<T = AxiosResponse> {
-  baseUrl: {
-    base: string
-    dev: string
-    pro: string
-    test: string
-  }
   code: number
   defaultHeaders: AxiosHeaders
   timeout: number

+ 1 - 1
src/locales/zh-CN.ts

@@ -521,7 +521,7 @@ export default {
   menu: {
     menuName: '菜单名称',
     icon: '图标',
-    permission: '权限标识',
+    permission: '按钮权限',
     component: '组件',
     path: '路径',
     status: '状态',

+ 1 - 1
src/store/modules/app.ts

@@ -63,7 +63,7 @@ export const useAppStore = defineStore('app', {
 
       layout: getStorage('layout') || 'classic', // layout布局
       isDark: getStorage('isDark'), // 是否是暗黑模式
-      currentSize: getStorage('default') || 'default', // 组件尺寸
+      currentSize: getStorage('currentSize') || 'default', // 组件尺寸
       theme: getStorage('theme') || {
         // 主题色
         elColorPrimary: '#409eff',

+ 1 - 1
src/styles/index.less

@@ -4,4 +4,4 @@
 // 解决抽屉弹出时,body宽度变化的问题
 .el-popup-parent--hidden {
   width: 100% !important;
-}
+}

+ 1 - 5
src/views/Authorization/Department/Department.vue

@@ -166,11 +166,7 @@ const crudSchemas = reactive<CrudSchema[]>([
       hidden: true
     },
     form: {
-      component: 'DatePicker',
-      componentProps: {
-        type: 'datetime',
-        valueFormat: 'YYYY-MM-DD HH:mm:ss'
-      }
+      hidden: true
     }
   },
   {

+ 1 - 3
src/views/Authorization/Department/components/Write.vue

@@ -20,9 +20,7 @@ const props = defineProps({
 
 const rules = reactive({
   id: [required()],
-  status: [required()],
-  createTime: [required()],
-  remark: [required()]
+  status: [required()]
 })
 
 const { formRegister, formMethods } = useForm()

+ 7 - 1
src/views/Authorization/Menu/Menu.vue

@@ -35,7 +35,13 @@ const tableColumns = reactive<TableColumn[]>([
   },
   {
     field: 'meta.title',
-    label: t('menu.menuName')
+    label: t('menu.menuName'),
+    slots: {
+      default: (data: any) => {
+        const title = data.row.meta.title
+        return <>{title}</>
+      }
+    }
   },
   {
     field: 'meta.icon',

+ 67 - 0
src/views/Authorization/Menu/components/AddButtonPermission.vue

@@ -0,0 +1,67 @@
+<script setup lang="ts">
+import { FormSchema, Form } from '@/components/Form'
+import { ElDrawer, ElButton } from 'element-plus'
+import { reactive } from 'vue'
+import { useForm } from '@/hooks/web/useForm'
+import { useValidator } from '@/hooks/web/useValidator'
+
+const modelValue = defineModel<boolean>()
+
+const { required } = useValidator()
+
+const formSchema = reactive<FormSchema[]>([
+  {
+    field: 'label',
+    label: 'label',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  },
+  {
+    field: 'value',
+    label: 'value',
+    component: 'Input',
+    colProps: {
+      span: 24
+    }
+  }
+])
+
+const { formRegister, formMethods } = useForm()
+const { getFormData, getElFormExpose } = formMethods
+
+const emit = defineEmits(['confirm'])
+
+const rules = reactive({
+  label: [required()],
+  value: [required()]
+})
+
+const confirm = async () => {
+  const elFormExpose = await getElFormExpose()
+  if (!elFormExpose) return
+  const valid = await elFormExpose?.validate().catch((err) => {
+    console.log(err)
+  })
+  if (valid) {
+    const formData = await getFormData()
+    emit('confirm', formData)
+    modelValue.value = false
+  }
+}
+</script>
+
+<template>
+  <ElDrawer v-model="modelValue" title="新增按钮权限">
+    <template #default>
+      <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+    </template>
+    <template #footer>
+      <div>
+        <ElButton @click="() => (modelValue = false)">取消</ElButton>
+        <ElButton type="primary" @click="confirm">确认</ElButton>
+      </div>
+    </template>
+  </ElDrawer>
+</template>

+ 18 - 0
src/views/Authorization/Menu/components/Detail.vue

@@ -75,6 +75,24 @@ const detailSchema = ref<DescriptionsSchema[]>([
     field: 'meta.activeMenu',
     label: '高亮菜单'
   },
+  {
+    field: 'permissionList',
+    label: '按钮权限',
+    span: 24,
+    slots: {
+      default: (data: any) => (
+        <>
+          {data?.permissionList?.map((v) => {
+            return (
+              <ElTag class="mr-1" key={v.value}>
+                {v.label}
+              </ElTag>
+            )
+          })}
+        </>
+      )
+    }
+  },
   {
     field: 'menuState',
     label: '菜单状态',

+ 74 - 15
src/views/Authorization/Menu/components/Write.vue

@@ -1,11 +1,12 @@
 <script setup lang="tsx">
 import { Form, FormSchema } from '@/components/Form'
 import { useForm } from '@/hooks/web/useForm'
-import { PropType, reactive, watch } from 'vue'
+import { PropType, reactive, watch, ref, unref } from 'vue'
 import { useValidator } from '@/hooks/web/useValidator'
 import { useI18n } from '@/hooks/web/useI18n'
 import { getMenuListApi } from '@/api/menu'
 import { ElTag, ElButton } from 'element-plus'
+import AddButtonPermission from './AddButtonPermission.vue'
 
 const { t } = useI18n()
 
@@ -18,6 +19,16 @@ const props = defineProps({
   }
 })
 
+const handleClose = async (tag: any) => {
+  const formData = await getFormData()
+  // 删除对应的权限
+  setValues({
+    permissionList: formData?.permissionList?.filter((v: any) => v.value !== tag.value)
+  })
+}
+
+const showDrawer = ref(false)
+
 const formSchema = reactive<FormSchema[]>([
   {
     field: 'type',
@@ -50,7 +61,7 @@ const formSchema = reactive<FormSchema[]>([
               }
             ])
             setValues({
-              component: ''
+              component: unref(cacheComponent)
             })
           } else {
             setSchema([
@@ -104,7 +115,7 @@ const formSchema = reactive<FormSchema[]>([
             })
           } else if (formData.type === 1) {
             setValues({
-              component: ''
+              component: unref(cacheComponent) ?? ''
             })
           }
         }
@@ -127,7 +138,12 @@ const formSchema = reactive<FormSchema[]>([
     value: '#',
     componentProps: {
       disabled: true,
-      placeholder: '#为顶级目录,##为子目录'
+      placeholder: '#为顶级目录,##为子目录',
+      on: {
+        change: (val: string) => {
+          cacheComponent.value = val
+        }
+      }
     }
   },
   {
@@ -176,18 +192,16 @@ const formSchema = reactive<FormSchema[]>([
     },
     formItemProps: {
       slots: {
-        default: () => (
+        default: (data: any) => (
           <>
-            <ElTag class="mx-1" closable disableTransitions={false}>
-              新增
-            </ElTag>
-            <ElTag class="mx-1" closable disableTransitions={false}>
-              编辑
-            </ElTag>
-            <ElTag class="mx-1" closable disableTransitions={false}>
-              删除
-            </ElTag>
-            <ElButton type="primary" size="small" onClick={() => console.log('添加权限')}>
+            {data?.permissionList?.map((v) => {
+              return (
+                <ElTag class="mr-1" key={v.value} closable onClose={() => handleClose(v)}>
+                  {v.label}
+                </ElTag>
+              )
+            })}
+            <ElButton type="primary" size="small" onClick={() => (showDrawer.value = true)}>
               添加权限
             </ElButton>
           </>
@@ -252,10 +266,47 @@ const submit = async () => {
   }
 }
 
+const cacheComponent = ref('')
+
 watch(
   () => props.currentRow,
   (currentRow) => {
     if (!currentRow) return
+    cacheComponent.value = currentRow.type === 1 ? currentRow.component : ''
+    if (currentRow.parentId === 0) {
+      setSchema([
+        {
+          field: 'component',
+          path: 'componentProps.disabled',
+          value: true
+        }
+      ])
+    } else {
+      setSchema([
+        {
+          field: 'component',
+          path: 'componentProps.disabled',
+          value: false
+        }
+      ])
+    }
+    if (currentRow.type === 1) {
+      setSchema([
+        {
+          field: 'component',
+          path: 'componentProps.disabled',
+          value: false
+        }
+      ])
+    } else {
+      setSchema([
+        {
+          field: 'component',
+          path: 'componentProps.disabled',
+          value: true
+        }
+      ])
+    }
     setValues(currentRow)
   },
   {
@@ -267,8 +318,16 @@ watch(
 defineExpose({
   submit
 })
+
+const confirm = async (data: any) => {
+  const formData = await getFormData()
+  setValues({
+    permissionList: [...(formData?.permissionList || []), data]
+  })
+}
 </script>
 
 <template>
   <Form :rules="rules" @register="formRegister" :schema="formSchema" />
+  <AddButtonPermission v-model="showDrawer" @confirm="confirm" />
 </template>

+ 5 - 4
src/views/Authorization/Role/Role.vue

@@ -9,6 +9,7 @@ import { Search } from '@/components/Search'
 import { FormSchema } from '@/components/Form'
 import { ContentWrap } from '@/components/ContentWrap'
 import Write from './components/Write.vue'
+import Detail from './components/Detail.vue'
 import { Dialog } from '@/components/Dialog'
 
 const { t } = useI18n()
@@ -36,10 +37,6 @@ const tableColumns = reactive<TableColumn[]>([
     field: 'roleName',
     label: t('role.roleName')
   },
-  {
-    field: 'role',
-    label: t('role.role')
-  },
   {
     field: 'status',
     label: t('menu.status'),
@@ -75,6 +72,9 @@ const tableColumns = reactive<TableColumn[]>([
             <ElButton type="primary" onClick={() => action(row, 'edit')}>
               {t('exampleDemo.edit')}
             </ElButton>
+            <ElButton type="success" onClick={() => action(row, 'detail')}>
+              {t('exampleDemo.detail')}
+            </ElButton>
             <ElButton type="danger">{t('exampleDemo.del')}</ElButton>
           </>
         )
@@ -155,6 +155,7 @@ const save = async () => {
 
   <Dialog v-model="dialogVisible" :title="dialogTitle">
     <Write v-if="actionType !== 'detail'" ref="writeRef" :current-row="currentRow" />
+    <Detail v-else :current-row="currentRow" />
 
     <template #footer>
       <ElButton v-if="actionType !== 'detail'" type="primary" :loading="saveLoading" @click="save">

+ 106 - 0
src/views/Authorization/Role/components/Detail.vue

@@ -0,0 +1,106 @@
+<script setup lang="tsx">
+import { PropType, ref, unref, nextTick } from 'vue'
+import { Descriptions, DescriptionsSchema } from '@/components/Descriptions'
+import { ElTag, ElTree } from 'element-plus'
+import { findIndex } from '@/utils'
+import { getMenuListApi } from '@/api/menu'
+
+defineProps({
+  currentRow: {
+    type: Object as PropType<any>,
+    default: () => undefined
+  }
+})
+
+const filterPermissionName = (value: string) => {
+  const index = findIndex(unref(currentTreeData)?.permissionList || [], (item) => {
+    return item.value === value
+  })
+  return (unref(currentTreeData)?.permissionList || [])[index].label ?? ''
+}
+
+const renderTag = (enable?: boolean) => {
+  return <ElTag type={!enable ? 'danger' : 'success'}>{enable ? '启用' : '禁用'}</ElTag>
+}
+
+const treeRef = ref<typeof ElTree>()
+
+const currentTreeData = ref()
+const nodeClick = (treeData: any) => {
+  currentTreeData.value = treeData
+}
+
+const treeData = ref<any[]>([])
+const getMenuList = async () => {
+  const res = await getMenuListApi()
+  if (res) {
+    treeData.value = res.data.list
+    await nextTick()
+  }
+}
+getMenuList()
+
+const detailSchema = ref<DescriptionsSchema[]>([
+  {
+    field: 'roleName',
+    label: '角色名称'
+  },
+  {
+    field: 'status',
+    label: '状态',
+    slots: {
+      default: (data: any) => {
+        return renderTag(data.status)
+      }
+    }
+  },
+  {
+    field: 'remark',
+    label: '备注',
+    span: 24
+  },
+  {
+    field: 'permissionList',
+    label: '菜单分配',
+    span: 24,
+    slots: {
+      default: () => {
+        return (
+          <>
+            <div class="flex w-full">
+              <div class="flex-1">
+                <ElTree
+                  ref={treeRef}
+                  node-key="id"
+                  props={{ children: 'children', label: 'title' }}
+                  highlight-current
+                  expand-on-click-node={false}
+                  data={treeData.value}
+                  onNode-click={nodeClick}
+                >
+                  {{
+                    default: (data) => {
+                      return <span>{data?.data?.title}</span>
+                    }
+                  }}
+                </ElTree>
+              </div>
+              <div class="flex-1">
+                {unref(currentTreeData)
+                  ? unref(currentTreeData)?.meta?.permission?.map((v: string) => {
+                      return <ElTag class="ml-2 mt-2">{filterPermissionName(v)}</ElTag>
+                    })
+                  : null}
+              </div>
+            </div>
+          </>
+        )
+      }
+    }
+  }
+])
+</script>
+
+<template>
+  <Descriptions :schema="detailSchema" :data="currentRow || {}" />
+</template>

+ 3 - 8
src/views/Authorization/Role/components/Write.vue

@@ -28,11 +28,6 @@ const formSchema = ref<FormSchema[]>([
     label: t('role.roleName'),
     component: 'Input'
   },
-  {
-    field: 'role',
-    label: t('role.role'),
-    component: 'Input'
-  },
   {
     field: 'status',
     label: t('menu.status'),
@@ -81,10 +76,10 @@ const formSchema = ref<FormSchema[]>([
                   </ElTree>
                 </div>
                 <div class="flex-1">
-                  {unref(currentTreeData) && unref(currentTreeData)?.permission ? (
+                  {unref(currentTreeData) && unref(currentTreeData)?.permissionList ? (
                     <ElCheckboxGroup v-model={unref(currentTreeData).meta.permission}>
-                      {unref(currentTreeData)?.permission.map((v: string) => {
-                        return <ElCheckbox label={v} />
+                      {unref(currentTreeData)?.permissionList.map((v: any) => {
+                        return <ElCheckbox label={v.value}>{v.label}</ElCheckbox>
                       })}
                     </ElCheckboxGroup>
                   ) : null}

+ 28 - 2
src/views/Authorization/User/User.vue

@@ -11,6 +11,7 @@ import { Search } from '@/components/Search'
 import Write from './components/Write.vue'
 import Detail from './components/Detail.vue'
 import { Dialog } from '@/components/Dialog'
+import { getRoleListApi } from '@/api/role'
 import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
 
 const { t } = useI18n()
@@ -113,6 +114,22 @@ const crudSchemas = reactive<CrudSchema[]>([
     label: t('userDemo.role'),
     search: {
       hidden: true
+    },
+    form: {
+      component: 'Select',
+      value: [],
+      componentProps: {
+        multiple: true,
+        collapseTags: true,
+        maxCollapseTags: 1
+      },
+      optionApi: async () => {
+        const res = await getRoleListApi()
+        return res.data?.list?.map((v) => ({
+          label: v.roleName,
+          value: v.id
+        }))
+      }
     }
   },
   {
@@ -276,7 +293,7 @@ const save = async () => {
 
 <template>
   <div class="flex w-100% h-100%">
-    <ContentWrap class="flex-1">
+    <ContentWrap class="w-250px">
       <div class="flex justify-center items-center">
         <div class="flex-1">{{ t('userDemo.departmentList') }}</div>
         <ElInput
@@ -299,7 +316,16 @@ const save = async () => {
         }"
         :filter-node-method="filterNode"
         @current-change="currentChange"
-      />
+      >
+        <template #default="{ data }">
+          <div
+            :title="data.departmentName"
+            class="whitespace-nowrap overflow-ellipsis overflow-hidden"
+          >
+            {{ data.departmentName }}
+          </div>
+        </template>
+      </ElTree>
     </ContentWrap>
     <ContentWrap class="flex-[3] ml-20px">
       <Search

+ 1 - 4
src/views/Authorization/User/components/Write.vue

@@ -21,10 +21,7 @@ const props = defineProps({
 const rules = reactive({
   username: [required()],
   account: [required()],
-  'department.id': [required()],
-  role: [required()],
-  email: [required()],
-  createTime: [required()]
+  'department.id': [required()]
 })
 
 const { formRegister, formMethods } = useForm()

+ 2 - 1
src/views/Components/Waterfall.vue

@@ -19,7 +19,8 @@ const getList = () => {
         width,
         height,
         id: toAnyString(),
-        image_uri: Mock.Random.image(`${width}x${height}`)
+        // http更换为https
+        image_uri: Mock.Random.image(`${width}x${height}`).replace('http://', 'https://')
       })
     )
   }

+ 11 - 0
uno.config.ts

@@ -4,6 +4,17 @@ import transformerVariantGroup from '@unocss/transformer-variant-group'
 export default defineConfig({
   // ...UnoCSS options
   rules: [
+    [
+      /^overflow-ellipsis$/,
+      ([], { rawSelector }) => {
+        const selector = e(rawSelector)
+        return `
+${selector} {
+  text-overflow: ellipsis;
+}
+`
+      }
+    ],
     [
       /^custom-hover$/,
       ([], { rawSelector }) => {