瀏覽代碼

修改菜单管理的界面

695593266@qq.com 3 月之前
父節點
當前提交
29adc941a6

+ 6 - 0
src/views/material/BOMmanage/components/routingDialog.vue

@@ -233,6 +233,9 @@
       // 是否开启响应式布局
       styleResponsive() {
         return this.$store.state.theme.styleResponsive;
+      },
+      clientEnvironmentId() {
+        return this.$store.state.user.info.clientEnvironmentId;
       }
     },
     created() {
@@ -339,6 +342,9 @@
       /* 表格数据源 */
       async datasource({ page, limit, where, order }) {
         where.status=1
+        if (this.clientEnvironmentId == 9) {
+          where.approvalStatus = 2;
+        }
         const res = await route.list({
           ...where,
           ...order,

+ 420 - 200
src/views/system/menu/components/menu-edit.vue

@@ -4,13 +4,19 @@
     width="720px"
     :visible="visible"
     :close-on-click-modal="false"
-    custom-class="ele-dialog-form"
+    custom-class="ele-dialog-form menu-edit-dialog"
     :title="isUpdate ? '修改菜单' : '添加菜单'"
     @update:visible="updateVisible"
     :maxable="true"
   >
-    <el-form ref="form" :model="form" :rules="rules" label-width="100px">
-      <el-row>
+    <el-form
+      ref="form"
+      class="menu-edit-form"
+      :model="form"
+      :rules="rules"
+      label-width="100px"
+    >
+      <el-row :gutter="18">
         <el-col :span="12">
           <el-form-item label="上级菜单:">
             <ele-tree-select
@@ -114,24 +120,38 @@
             />
           </el-form-item>
         </el-col>
-        <el-col :span="12">
-          <el-form-item :rules="[
-      { required: form.source === 2 ? true : false, message: '字段必填', trigger: 'blur' }
-    ]" :label="form.source === 2 ? '报表ID:': '扩展属性:'"  :prop="form.source == 2 ? 'extend' : ''">
-            <el-input
-              clearable
-              v-model="form.extend"
-              :placeholder="form.source === 2 ? '请输入报表ID' : '请输入扩展属性'"
-            />
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
-          <el-form-item label="菜单类型:" prop="type">
-            <el-radio-group v-model="form.type" @change="onMenuTypeChange">
-              <el-radio :label="1">菜单</el-radio>
-              <el-radio :label="2">按钮</el-radio>
-            </el-radio-group>
-          </el-form-item>
+        <el-col :span="24" class="menu-type-extend-row">
+          <el-row :gutter="18">
+            <el-col :span="12">
+              <el-form-item
+                :rules="[
+                  {
+                    required: form.source === 2 ? true : false,
+                    message: '字段必填',
+                    trigger: 'blur'
+                  }
+                ]"
+                :label="form.source === 2 ? '报表ID:' : '扩展属性:'"
+                :prop="form.source == 2 ? 'extend' : ''"
+              >
+                <el-input
+                  clearable
+                  v-model="form.extend"
+                  :placeholder="
+                    form.source === 2 ? '请输入报表ID' : '请输入扩展属性'
+                  "
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="菜单类型:" prop="type">
+                <el-radio-group v-model="form.type" @change="onMenuTypeChange">
+                  <el-radio :label="1">菜单</el-radio>
+                  <el-radio :label="2">按钮</el-radio>
+                </el-radio-group>
+              </el-form-item>
+            </el-col>
+          </el-row>
         </el-col>
       </el-row>
       <!-- <div style="margin: 6px 0 28px 0">
@@ -139,199 +159,399 @@
       </div> -->
     </el-form>
     <template v-slot:footer>
-      <el-button @click="updateVisible(false)">取消</el-button>
-      <el-button type="primary" :loading="loading" @click="save">
-        保存
-      </el-button>
+      <div class="menu-edit-footer">
+        <el-button class="menu-edit-cancel" @click="updateVisible(false)">
+          取消
+        </el-button>
+        <el-button
+          class="menu-edit-save"
+          type="primary"
+          :loading="loading"
+          @click="save"
+        >
+          保存
+        </el-button>
+      </div>
     </template>
   </ele-modal>
 </template>
 
 <script>
-import { EleIconPicker } from 'ele-admin';
-import { saveOrUpdate } from '@/api/system/menu';
-import { secretLevelList } from '@/enum/dict';
-
-export default {
-  components: { EleIconPicker },
-  props: {
-    // 弹窗是否打开
-    visible: Boolean,
-    // 修改回显的数据
-    data: Object,
-    // 上级菜单id
-    parentId: [Number, String],
-    // 全部菜单数据
-    menuList: Array
-  },
-  data() {
-    const defaultForm = {
-      id: null,
-      parentId: '',
-      name: '',
-      type: 1,
-      source: 1,
-      icon: '',
-      url: '/',
-      component: '',
-      sort: null,
-      rootId: null,
-      permissionCode: 1,
-      secretLevel: 1,
-      extend: null, //报表ID
-    };
-    return {
-      defaultForm,
-      // 表单数据
-      form: { ...defaultForm },
-      // 表单验证规则
-      rules: {
-        name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
-        sort: [{ required: true, message: '请输入排序号', trigger: 'blur' }],
-        url: [{ required: true, message: '请输入菜单路由', trigger: 'blur' }],
-        component: [
-          { required: true, message: '请输入组件地址', trigger: 'blur' }
-        ],
-        type: [{ required: true, message: '请选择菜单类型', trigger: 'blur' }]
-      },
-      // 提交状态
-      loading: false,
-      // 是否是修改
-      isUpdate: false
-    };
-  },
-  computed: {
-    // pathPlaceholder() {
-    //   return this.form.openType === 2 ? '请输入外链地址' : '请输入路由地址';
-    // },
-    // componentPlaceholder() {
-    //   return this.form.openType === 1 ? '请输入内链地址' : '请输入组件路径';
-    // },
-    // // 是否开启响应式布局
-    // styleResponsive() {
-    //   return this.$store.state.theme.styleResponsive;
-    // }
-  },
-  methods: {
-    secretLevelList() {
-      return secretLevelList;
+  import { EleIconPicker } from 'ele-admin';
+  import { saveOrUpdate } from '@/api/system/menu';
+  import { secretLevelList } from '@/enum/dict';
+
+  export default {
+    components: { EleIconPicker },
+    props: {
+      // 弹窗是否打开
+      visible: Boolean,
+      // 修改回显的数据
+      data: Object,
+      // 上级菜单id
+      parentId: [Number, String],
+      // 全部菜单数据
+      menuList: Array
     },
-    sourceChange(e) {
-      if (e == 2) {
-        this.form.url =
-          '/reportForms/sourceLink';
-        this.form.component = '/reportForms/sourceLink'
-      } else {
-        this.form.url = '/';
-      }
+    data() {
+      const defaultForm = {
+        id: null,
+        parentId: '',
+        name: '',
+        type: 1,
+        source: 1,
+        icon: '',
+        url: '/',
+        component: '',
+        sort: null,
+        rootId: null,
+        permissionCode: 1,
+        secretLevel: 1,
+        extend: null //报表ID
+      };
+      return {
+        defaultForm,
+        // 表单数据
+        form: { ...defaultForm },
+        // 表单验证规则
+        rules: {
+          name: [
+            { required: true, message: '请输入菜单名称', trigger: 'blur' }
+          ],
+          sort: [{ required: true, message: '请输入排序号', trigger: 'blur' }],
+          url: [{ required: true, message: '请输入菜单路由', trigger: 'blur' }],
+          component: [
+            { required: true, message: '请输入组件地址', trigger: 'blur' }
+          ],
+          type: [{ required: true, message: '请选择菜单类型', trigger: 'blur' }]
+        },
+        // 提交状态
+        loading: false,
+        // 是否是修改
+        isUpdate: false
+      };
     },
-    /* 保存编辑 */
-    save() {
-      this.$refs.form.validate((valid) => {
-        if (!valid) {
-          return false;
+    computed: {
+      // pathPlaceholder() {
+      //   return this.form.openType === 2 ? '请输入外链地址' : '请输入路由地址';
+      // },
+      // componentPlaceholder() {
+      //   return this.form.openType === 1 ? '请输入内链地址' : '请输入组件路径';
+      // },
+      // // 是否开启响应式布局
+      // styleResponsive() {
+      //   return this.$store.state.theme.styleResponsive;
+      // }
+    },
+    methods: {
+      secretLevelList() {
+        return secretLevelList;
+      },
+      sourceChange(e) {
+        if (e == 2) {
+          this.form.url = '/reportForms/sourceLink';
+          this.form.component = '/reportForms/sourceLink';
+        } else {
+          this.form.url = '/';
         }
-        this.loading = true;
-        const data = {
-          ...this.form,
-          // menuType 对应的值与后端不一致在前端处理
-          // type: this.form.type === 2 ? 1 : 0,
-          parentId: this.form.parentId || 0
-        };
-        // const saveOrUpdate = this.isUpdate ? updateMenu : addMenu;
-        if (!this.isUpdate) {
-          delete data.id;
+      },
+      /* 保存编辑 */
+      save() {
+        this.$refs.form.validate((valid) => {
+          if (!valid) {
+            return false;
+          }
+          this.loading = true;
+          const data = {
+            ...this.form,
+            // menuType 对应的值与后端不一致在前端处理
+            // type: this.form.type === 2 ? 1 : 0,
+            parentId: this.form.parentId || 0
+          };
+          // const saveOrUpdate = this.isUpdate ? updateMenu : addMenu;
+          if (!this.isUpdate) {
+            delete data.id;
+          }
+          saveOrUpdate(data)
+            .then((res) => {
+              this.loading = false;
+              if (this.isUpdate) {
+                this.$message.success('菜单编辑成功');
+              } else {
+                this.$message.success('菜单新增成功');
+              }
+              this.updateVisible(false);
+              this.$emit('done');
+            })
+            .catch((e) => {
+              this.loading = false;
+              // this.$message.error(e.message);
+            });
+        });
+      },
+      /* 更新visible */
+      updateVisible(value) {
+        this.$emit('update:visible', value);
+      },
+      /* menuType选择改变 */
+      onMenuTypeChange() {
+        // if (this.form.menuType === 0) {
+        //   this.form.authority = null;
+        //   this.form.openType = 0;
+        //   this.form.component = null;
+        // } else if (this.form.menuType === 1) {
+        //   if (this.form.openType === 2) {
+        //     this.form.authority = null;
+        //   }
+        // } else {
+        //   this.form.openType = 0;
+        //   this.form.icon = null;
+        //   this.form.path = null;
+        //   this.form.component = null;
+        //   this.form.hide = 0;
+        // }
+      },
+      /* openType选择改变 */
+      onOpenTypeChange() {
+        if (this.form.openType === 2) {
+          this.form.component = null;
+          this.form.authority = null;
         }
-        saveOrUpdate(data)
-          .then((res) => {
-            this.loading = false;
-            if (this.isUpdate) {
-              this.$message.success('菜单编辑成功');
-            } else {
-              this.$message.success('菜单新增成功');
-            }
-            this.updateVisible(false);
-            this.$emit('done');
-          })
-          .catch((e) => {
-            this.loading = false;
-            // this.$message.error(e.message);
-          });
-      });
-    },
-    /* 更新visible */
-    updateVisible(value) {
-      this.$emit('update:visible', value);
-    },
-    /* menuType选择改变 */
-    onMenuTypeChange() {
-      // if (this.form.menuType === 0) {
-      //   this.form.authority = null;
-      //   this.form.openType = 0;
-      //   this.form.component = null;
-      // } else if (this.form.menuType === 1) {
-      //   if (this.form.openType === 2) {
-      //     this.form.authority = null;
-      //   }
-      // } else {
-      //   this.form.openType = 0;
-      //   this.form.icon = null;
-      //   this.form.path = null;
-      //   this.form.component = null;
-      //   this.form.hide = 0;
+      }
+      /* 判断是否是外链 */
+      // isUrl(url) {
+      //   return !!(
+      //     url &&
+      //     (url.startsWith('http://') ||
+      //       url.startsWith('https://') ||
+      //       url.startsWith('//'))
+      //   );
+      // },
+      /* 判断是否是目录 */
+      // isDirectory(d) {
+      //   return !!d.children?.length && !d.component;
       // }
     },
-    /* openType选择改变 */
-    onOpenTypeChange() {
-      if (this.form.openType === 2) {
-        this.form.component = null;
-        this.form.authority = null;
+    watch: {
+      visible(visible) {
+        if (visible) {
+          if (this.data) {
+            // const isInnerLink = this.isUrl(this.data.component) ? 1 : 0;
+            // menuType 对应的值与后端不一致在前端处理
+            // const menuType =
+            //   this.data.type === 1
+            //     ? 2
+            //     : this.isDirectory(this.data)
+            //     ? 0
+            //     : 1;
+
+            this.$util.assignObject(this.form, {
+              ...this.data,
+              // openType: this.isUrl(this.data.path) ? 2 : isInnerLink,
+              parentId: this.data.parentId == 0 ? '' : this.data.parentId ?? ''
+            });
+            this.isUpdate = true;
+            // this.sourceChange(this.form.source);
+          } else {
+            this.form.parentId = this.parentId ?? '';
+            this.isUpdate = false;
+          }
+        } else {
+          this.$refs.form.clearValidate();
+          this.form = { ...this.defaultForm };
+          // this.sourceChange(this.form.source);
+        }
       }
     }
-    /* 判断是否是外链 */
-    // isUrl(url) {
-    //   return !!(
-    //     url &&
-    //     (url.startsWith('http://') ||
-    //       url.startsWith('https://') ||
-    //       url.startsWith('//'))
-    //   );
-    // },
-    /* 判断是否是目录 */
-    // isDirectory(d) {
-    //   return !!d.children?.length && !d.component;
-    // }
-  },
-  watch: {
-    visible(visible) {
-      if (visible) {
-        if (this.data) {
-          // const isInnerLink = this.isUrl(this.data.component) ? 1 : 0;
-          // menuType 对应的值与后端不一致在前端处理
-          // const menuType =
-          //   this.data.type === 1
-          //     ? 2
-          //     : this.isDirectory(this.data)
-          //     ? 0
-          //     : 1;
-
-          this.$util.assignObject(this.form, {
-            ...this.data,
-            // openType: this.isUrl(this.data.path) ? 2 : isInnerLink,
-            parentId: this.data.parentId == 0 ? '' : this.data.parentId ?? ''
-          });
-          this.isUpdate = true;
-          // this.sourceChange(this.form.source);
-        } else {
-          this.form.parentId = this.parentId ?? '';
-          this.isUpdate = false;
+  };
+</script>
+
+<style lang="scss" scoped>
+  ::v-deep .menu-edit-dialog {
+    overflow: hidden;
+    border-radius: 8px;
+    box-shadow: 0 26px 70px rgba(15, 35, 66, 0.22);
+
+    .el-dialog__header {
+      position: relative;
+      padding: 18px 24px 16px;
+      border-bottom: 1px solid #e6edf7;
+      background: linear-gradient(
+          135deg,
+          rgba(22, 119, 255, 0.12),
+          rgba(0, 184, 169, 0.08)
+        ),
+        #ffffff;
+
+      &::before {
+        content: '';
+        position: absolute;
+        left: 0;
+        top: 0;
+        bottom: 0;
+        width: 4px;
+        background: linear-gradient(180deg, #1677ff, #00b8a9);
+      }
+    }
+
+    .el-dialog__title {
+      color: #10233f;
+      font-size: 17px;
+      font-weight: 800;
+    }
+
+    .el-dialog__body {
+      padding: 22px 24px 10px;
+      background: #fbfdff;
+    }
+
+    .el-dialog__footer {
+      padding: 14px 24px 20px;
+      border-top: 1px solid #e6edf7;
+      background: #ffffff;
+    }
+  }
+
+  .menu-edit-form {
+    .menu-type-extend-row {
+      clear: both;
+    }
+
+    ::v-deep .el-form-item {
+      margin-bottom: 18px;
+    }
+
+    ::v-deep .el-form-item__label {
+      color: #34465e;
+      font-weight: 700;
+    }
+
+    ::v-deep .el-input__inner,
+    ::v-deep .el-input-number .el-input__inner,
+    ::v-deep .el-select .el-input__inner {
+      height: 38px;
+      color: #26384d;
+      border-color: #d7e3f1;
+      border-radius: 7px;
+      background-color: #ffffff;
+      transition: border-color 0.18s ease, box-shadow 0.18s ease;
+    }
+
+    ::v-deep .el-input__inner:hover,
+    ::v-deep .el-input-number .el-input__inner:hover,
+    ::v-deep .el-select .el-input__inner:hover {
+      border-color: #a9c6ea;
+    }
+
+    ::v-deep .el-input__inner:focus,
+    ::v-deep .el-input-number .el-input__inner:focus,
+    ::v-deep .el-select .el-input__inner:focus {
+      border-color: #1677ff;
+      box-shadow: 0 0 0 3px rgba(22, 119, 255, 0.12);
+    }
+
+    ::v-deep .el-input.is-disabled .el-input__inner {
+      color: #8aa0b6;
+      background: #f3f6fa;
+      border-color: #e0e7f0;
+    }
+
+    ::v-deep .el-radio-group {
+      display: inline-flex;
+      flex-wrap: wrap;
+      gap: 8px;
+      padding: 4px;
+      border: 1px solid #dce8f5;
+      border-radius: 8px;
+      background: #f5f9ff;
+    }
+
+    ::v-deep .el-radio {
+      height: 30px;
+      margin-right: 0;
+      padding: 0 12px;
+      line-height: 30px;
+      border-radius: 6px;
+      transition: background-color 0.18s ease, color 0.18s ease;
+    }
+
+    ::v-deep .el-radio.is-checked {
+      color: #1677ff;
+      background: #ffffff;
+      box-shadow: 0 6px 14px rgba(22, 119, 255, 0.14);
+    }
+
+    ::v-deep .el-radio__label {
+      padding-left: 6px;
+      font-weight: 600;
+    }
+
+    ::v-deep .ele-tree-select,
+    ::v-deep .ele-icon-picker {
+      width: 100%;
+    }
+  }
+
+  .menu-edit-footer {
+    display: flex;
+    justify-content: flex-end;
+    gap: 10px;
+
+    .el-button + .el-button {
+      margin-left: 0;
+    }
+  }
+
+  .menu-edit-cancel,
+  .menu-edit-save {
+    min-width: 82px;
+    height: 38px;
+    border-radius: 7px;
+  }
+
+  .menu-edit-cancel {
+    color: #31506f;
+    border-color: #d7e3f1;
+
+    &:hover,
+    &:focus {
+      color: #1677ff;
+      border-color: rgba(22, 119, 255, 0.35);
+      background: #f4f9ff;
+    }
+  }
+
+  .menu-edit-save {
+    border: 0;
+    background: linear-gradient(135deg, #1677ff, #00a3ff);
+    box-shadow: 0 10px 22px rgba(22, 119, 255, 0.24);
+
+    &:hover,
+    &:focus {
+      background: linear-gradient(135deg, #0f67e8, #0093ef);
+    }
+  }
+
+  @media (max-width: 768px) {
+    ::v-deep .menu-edit-dialog {
+      width: calc(100vw - 24px) !important;
+      margin-top: 7vh !important;
+
+      .el-dialog__body {
+        padding: 18px 16px 6px;
+      }
+    }
+
+    .menu-edit-form {
+      ::v-deep .el-col-12 {
+        width: 100%;
+      }
+
+      .menu-type-extend-row {
+        ::v-deep .el-col-12 {
+          width: 100%;
         }
-      } else {
-        this.$refs.form.clearValidate();
-        this.form = { ...this.defaultForm };
-        // this.sourceChange(this.form.source);
       }
     }
   }
-};
-</script>
+</style>

+ 120 - 6
src/views/system/menu/components/menu-search.vue

@@ -2,24 +2,39 @@
 <template>
   <el-form
     label-width="77px"
-    class="ele-form-search"
+    class="ele-form-search menu-search-panel"
     @keyup.enter.native="search"
     @submit.native.prevent
   >
     <el-row :gutter="15">
       <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
         <el-form-item label="菜单名称:">
-          <el-input clearable v-model.trim="where.name" placeholder="请输入" />
+          <el-input
+            clearable
+            class="menu-search-input"
+            v-model.trim="where.name"
+            placeholder="请输入"
+          />
         </el-form-item>
       </el-col>
       <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
         <el-form-item label="菜单地址:">
-          <el-input clearable v-model.trim="where.url" placeholder="请输入" />
+          <el-input
+            clearable
+            class="menu-search-input"
+            v-model.trim="where.url"
+            placeholder="请输入"
+          />
         </el-form-item>
       </el-col>
      <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
         <el-form-item label="权限编码:">
-          <el-input clearable v-model.trim="where.permissionCode" placeholder="请输入" />
+          <el-input
+            clearable
+            class="menu-search-input"
+            v-model.trim="where.permissionCode"
+            placeholder="请输入"
+          />
         </el-form-item>
       </el-col> 
       <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
@@ -27,12 +42,12 @@
           <el-button
             type="primary"
             icon="el-icon-search"
-            class="ele-btn-icon"
+            class="ele-btn-icon menu-search-btn"
             @click="search"
           >
             查询
           </el-button>
-          <el-button @click="reset">重置</el-button>
+          <el-button class="menu-reset-btn" @click="reset">重置</el-button>
         </div>
       </el-col>
     </el-row>
@@ -72,3 +87,102 @@
     }
   };
 </script>
+
+<style lang="scss" scoped>
+  .menu-search-panel {
+    position: relative;
+    margin-bottom: 16px;
+    padding: 18px 18px 4px;
+    border: 1px solid #e0eaf5;
+    border-radius: 8px;
+    background:
+      linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(248, 251, 255, 0.96)),
+      repeating-linear-gradient(
+        90deg,
+        rgba(22, 119, 255, 0.04) 0,
+        rgba(22, 119, 255, 0.04) 1px,
+        transparent 1px,
+        transparent 28px
+      );
+    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
+
+    ::v-deep .el-form-item {
+      margin-bottom: 14px;
+    }
+
+    ::v-deep .el-form-item__label {
+      color: #34465e;
+      font-weight: 700;
+    }
+  }
+
+  .menu-search-input {
+    ::v-deep .el-input__inner {
+      height: 38px;
+      color: #26384d;
+      border-color: #d7e3f1;
+      border-radius: 7px;
+      background: rgba(255, 255, 255, 0.92);
+      transition: border-color 0.18s ease, box-shadow 0.18s ease;
+    }
+
+    ::v-deep .el-input__inner:hover {
+      border-color: #a9c6ea;
+    }
+
+    ::v-deep .el-input__inner:focus {
+      border-color: #1677ff;
+      box-shadow: 0 0 0 3px rgba(22, 119, 255, 0.12);
+    }
+  }
+
+  .ele-form-actions {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+
+    .el-button + .el-button {
+      margin-left: 0;
+    }
+  }
+
+  .menu-search-btn {
+    height: 38px;
+    padding: 0 18px;
+    border: 0;
+    border-radius: 7px;
+    background: linear-gradient(135deg, #1677ff, #00a3ff);
+    box-shadow: 0 9px 20px rgba(22, 119, 255, 0.24);
+
+    &:hover,
+    &:focus {
+      background: linear-gradient(135deg, #0f67e8, #0093ef);
+    }
+  }
+
+  .menu-reset-btn {
+    height: 38px;
+    padding: 0 16px;
+    color: #31506f;
+    border-color: #d7e3f1;
+    border-radius: 7px;
+    background: #ffffff;
+
+    &:hover,
+    &:focus {
+      color: #1677ff;
+      border-color: rgba(22, 119, 255, 0.35);
+      background: #f4f9ff;
+    }
+  }
+
+  @media (max-width: 768px) {
+    .menu-search-panel {
+      padding: 14px 14px 2px;
+    }
+
+    .ele-form-actions {
+      justify-content: flex-start;
+    }
+  }
+</style>

+ 383 - 58
src/views/system/menu/index.vue

@@ -1,10 +1,11 @@
 <template>
-  <div class="ele-body">
-    <el-card shadow="never">
+  <div class="ele-body system-menu-page">
+    <el-card shadow="never" class="menu-shell">
       <!-- 搜索表单 -->
       <menu-search @search="reload" />
       <!-- 数据表格 -->
       <ele-pro-table
+        class="menu-table"
         ref="table"
         row-key="id"
         :columns="columns"
@@ -18,27 +19,48 @@
       >
         <!-- 表头工具栏 -->
         <template v-slot:toolbar>
-          <el-button
-            size="small"
-            type="primary"
-            icon="el-icon-plus"
-            class="ele-btn-icon"
-            @click="openEdit()"
-            v-if="$hasPermission('sys:resources:save')"
-          >
-            添加
-          </el-button>
-          <el-button class="ele-btn-icon" size="small" @click="expandAll">
-            展开全部
-          </el-button>
-          <el-button class="ele-btn-icon" size="small" @click="foldAll">
-            折叠全部
-          </el-button>
+          <div class="menu-toolbar">
+            <el-button
+              size="small"
+              type="primary"
+              icon="el-icon-plus"
+              class="ele-btn-icon menu-primary-action"
+              @click="openEdit()"
+              v-if="$hasPermission('sys:resources:save')"
+            >
+              添加
+            </el-button>
+            <el-button
+              class="ele-btn-icon menu-ghost-action"
+              size="small"
+              @click="expandAll"
+            >
+              展开全部
+            </el-button>
+            <el-button
+              class="ele-btn-icon menu-ghost-action"
+              size="small"
+              @click="foldAll"
+            >
+              折叠全部
+            </el-button>
+          </div>
           <!-- <div class="el-button--primary">122222</div> -->
         </template>
         <!-- 标题列 -->
         <template v-slot:name="{ row }">
-          <i :class="row.icon"></i> {{ row.name }}
+          <span
+            class="menu-name-cell"
+            :class="{
+              'is-child': row.parentId && row.parentId != 0,
+              'is-parent': row.children && row.children.length
+            }"
+          >
+            <span v-if="row.icon" class="menu-icon-wrap">
+              <i :class="row.icon"></i>
+            </span>
+            <span class="menu-name-text">{{ row.name }}</span>
+          </span>
         </template>
         <!-- 标题列 -->
         <template v-slot:secretLevel="{ row }">
@@ -47,6 +69,7 @@
         <!-- 类型列 -->
         <template v-slot:type="{ row }">
           <el-tag
+            class="menu-type-tag"
             type="primary"
             size="mini"
             :disable-transitions="true"
@@ -55,6 +78,7 @@
             菜单
           </el-tag>
           <el-tag
+            class="menu-type-tag menu-type-tag-warning"
             type="warning"
             size="mini"
             :disable-transitions="true"
@@ -65,6 +89,7 @@
         </template>
         <template v-slot:isEnabled="{ row }">
           <el-switch
+            class="menu-status-switch"
             v-model="row.isEnabled"
             active-color="#13ce66"
             inactive-color="#ff4949"
@@ -76,36 +101,38 @@
         </template>
         <!-- 操作列 -->
         <template v-slot:action="{ row }">
-          <el-link
-            type="primary"
-            :underline="false"
-            icon="el-icon-plus"
-            @click="openEdit(null, row.id)"
-            v-if="$hasPermission('sys:resources:save')"
-          >
-            添加
-          </el-link>
-          <el-link
-            type="primary"
-            :underline="false"
-            icon="el-icon-edit"
-            @click="openEdit(row)"
-            v-if="$hasPermission('sys:resources:save')"
-          >
-            修改
-          </el-link>
-          <el-popconfirm
-            class="ele-action"
-            title="确定要删除吗?"
-            @confirm="remove(row)"
-            v-if="row.id != 99999 && $hasPermission('sys:resources:delete')"
-          >
-            <template v-slot:reference>
-              <el-link type="danger" :underline="false" icon="el-icon-delete">
-                删除
-              </el-link>
-            </template>
-          </el-popconfirm>
+          <div class="menu-actions">
+            <el-link
+              type="primary"
+              :underline="false"
+              icon="el-icon-plus"
+              @click="openEdit(null, row.id)"
+              v-if="$hasPermission('sys:resources:save')"
+            >
+              添加
+            </el-link>
+            <el-link
+              type="primary"
+              :underline="false"
+              icon="el-icon-edit"
+              @click="openEdit(row)"
+              v-if="$hasPermission('sys:resources:save')"
+            >
+              修改
+            </el-link>
+            <el-popconfirm
+              class="ele-action"
+              title="确定要删除吗?"
+              @confirm="remove(row)"
+              v-if="row.id != 99999 && $hasPermission('sys:resources:delete')"
+            >
+              <template v-slot:reference>
+                <el-link type="danger" :underline="false" icon="el-icon-delete">
+                  删除
+                </el-link>
+              </template>
+            </el-popconfirm>
+          </div>
         </template>
       </ele-pro-table>
     </el-card>
@@ -140,14 +167,14 @@
             type: 'index',
             width: 45,
             align: 'center',
-            showOverflowTooltip: true,
+            showOverflowTooltip: false,
             fixed: 'left'
           },
           {
             prop: 'name',
             label: '菜单名称',
             showOverflowTooltip: true,
-            minWidth: 110,
+            minWidth: 180,
             slot: 'name'
           },
           {
@@ -168,7 +195,7 @@
             prop: 'secretLevel',
             label: '密级',
             align: 'center',
-            showOverflowTooltip: true,
+            showOverflowTooltip: false,
             slot: 'secretLevel',
             minWidth: 110
           },
@@ -176,15 +203,15 @@
             prop: 'sort',
             label: '排序',
             align: 'center',
-            showOverflowTooltip: true,
+            showOverflowTooltip: false,
             width: 60
           },
           {
             prop: 'type',
             label: '类型',
             align: 'center',
-            showOverflowTooltip: true,
-            width: 60,
+            showOverflowTooltip: false,
+            width: 76,
             slot: 'type'
           },
           {
@@ -192,7 +219,7 @@
             label: '启用状态',
             align: 'center',
 
-            showOverflowTooltip: true,
+            showOverflowTooltip: false,
             slot: 'isEnabled',
 
             minWidth: 110
@@ -210,11 +237,11 @@
           {
             columnKey: 'action',
             label: '操作',
-            width: 190,
+            width: 250,
             align: 'center',
             resizable: false,
             slot: 'action',
-            showOverflowTooltip: true
+            showOverflowTooltip: false
           }
         ],
         // 当前编辑数据
@@ -319,3 +346,301 @@
     }
   };
 </script>
+
+<style lang="scss" scoped>
+  .system-menu-page {
+    min-height: calc(100vh - 84px);
+    padding: 16px;
+    background:
+      linear-gradient(135deg, rgba(35, 108, 255, 0.08), transparent 28%),
+      linear-gradient(315deg, rgba(0, 186, 173, 0.08), transparent 30%),
+      #f4f7fb;
+  }
+
+  .menu-shell {
+    position: relative;
+    overflow: hidden;
+    border: 1px solid rgba(47, 93, 153, 0.12);
+    border-radius: 8px;
+    box-shadow: 0 18px 42px rgba(19, 44, 82, 0.08);
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      height: 3px;
+      background: linear-gradient(90deg, #1677ff, #00b8a9 46%, #ffb020);
+    }
+
+    ::v-deep .el-card__body {
+      padding: 18px 20px 22px;
+    }
+  }
+
+  .menu-table {
+    ::v-deep .ele-table-tool,
+    ::v-deep .ele-pro-table-toolbar {
+      min-height: 56px;
+      padding: 12px 14px;
+      border: 1px solid #e6edf7;
+      border-bottom: 0;
+      border-radius: 8px 8px 0 0;
+      background: linear-gradient(180deg, #ffffff, #f8fbff);
+    }
+
+    ::v-deep .ele-table-tool {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: 12px;
+    }
+
+    ::v-deep .ele-table-tool-title,
+    ::v-deep .ele-table-tool-title-content {
+      min-width: 0;
+    }
+
+    ::v-deep .ele-table-tool .el-button {
+      border-radius: 7px;
+    }
+
+    ::v-deep .el-table {
+      color: #334155;
+      border-color: #e6edf7;
+      border-radius: 0 0 8px 8px;
+      overflow: hidden;
+    }
+
+    ::v-deep .el-table th {
+      height: 44px;
+      color: #10233f;
+      font-weight: 700;
+      background: #f2f7fd;
+      border-color: #e3ebf6;
+    }
+
+    ::v-deep .el-table td {
+      height: 42px;
+      border-color: #edf2f7;
+    }
+
+    ::v-deep .el-table__row {
+      transition: none;
+    }
+
+    ::v-deep .el-table__row:hover > td {
+      background: #f7fbff !important;
+      box-shadow: none;
+    }
+
+    ::v-deep .el-table__expand-icon {
+      color: #66809f;
+      transition: none;
+    }
+
+    ::v-deep .el-table__expand-icon:hover {
+      color: #1677ff;
+    }
+  }
+
+  .menu-toolbar {
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    gap: 10px;
+
+    .el-button + .el-button {
+      margin-left: 0;
+    }
+  }
+
+  .menu-primary-action {
+    border: 0;
+    background: linear-gradient(135deg, #1677ff, #00a3ff);
+    box-shadow: 0 10px 22px rgba(22, 119, 255, 0.24);
+
+    &:hover,
+    &:focus {
+      background: linear-gradient(135deg, #0f67e8, #0093ef);
+      box-shadow: 0 12px 26px rgba(22, 119, 255, 0.3);
+    }
+  }
+
+  .menu-ghost-action {
+    color: #254769;
+    border-color: #d7e3f1;
+    background: #ffffff;
+
+    &:hover,
+    &:focus {
+      color: #1677ff;
+      border-color: rgba(22, 119, 255, 0.35);
+      background: #f4f9ff;
+    }
+  }
+
+  .menu-name-cell {
+    display: inline-flex;
+    align-items: center;
+    max-width: 100%;
+    gap: 8px;
+    color: #26384d;
+    font-weight: 600;
+  }
+
+  .menu-name-cell.is-parent {
+    color: #10233f;
+    font-weight: 700;
+  }
+
+  .menu-name-cell.is-child {
+    position: relative;
+    gap: 7px;
+    color: #41566f;
+    font-weight: 600;
+
+    &::before {
+      content: '';
+      width: 5px;
+      height: 5px;
+      flex: 0 0 5px;
+      border-radius: 50%;
+      background: #8fb3d9;
+    }
+
+    .menu-icon-wrap {
+      width: 24px;
+      height: 24px;
+      flex-basis: 24px;
+      color: #3485ff;
+      border-color: #d5e7ff;
+      background: #f6faff;
+      box-shadow: none;
+    }
+
+    .menu-name-text {
+      padding: 3px 9px 3px 0;
+      border-radius: 0 6px 6px 0;
+      color: #354a62;
+      background: #f7fbff;
+    }
+  }
+
+  .menu-icon-wrap {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    width: 26px;
+    height: 26px;
+    flex: 0 0 26px;
+    color: #1677ff;
+    border: 1px solid rgba(22, 119, 255, 0.14);
+    border-radius: 7px;
+    background: #f6faff;
+  }
+
+  .menu-name-text {
+    min-width: 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .menu-type-tag {
+    height: 24px;
+    padding: 0 9px;
+    line-height: 22px;
+    font-weight: 600;
+    border-radius: 6px;
+    border-color: rgba(22, 119, 255, 0.24);
+    background: #edf6ff;
+  }
+
+  .menu-type-tag-warning {
+    border-color: rgba(255, 176, 32, 0.34);
+    background: #fff7e6;
+  }
+
+  .menu-status-switch {
+    height: 24px;
+    line-height: 24px;
+
+    ::v-deep .el-switch__core {
+      width: 42px !important;
+      height: 22px;
+      border: 1px solid rgba(15, 23, 42, 0.08);
+      border-radius: 999px;
+      background: #18c56e !important;
+      box-shadow: none;
+    }
+
+    ::v-deep .el-switch__core::after {
+      top: 2px;
+      left: 3px;
+      width: 16px;
+      height: 16px;
+      border: 1px solid rgba(15, 23, 42, 0.08);
+      box-shadow: none;
+    }
+  }
+
+  ::v-deep .menu-status-switch.is-checked .el-switch__core::after {
+    left: 21px;
+    margin-left: 0;
+  }
+
+  ::v-deep .menu-status-switch:not(.is-checked) .el-switch__core {
+    background: #94a3b8 !important;
+  }
+
+  .menu-actions {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    flex-wrap: nowrap;
+    gap: 6px;
+    white-space: nowrap;
+
+    ::v-deep .el-link {
+      height: 26px;
+      padding: 0 8px;
+      flex: 0 0 auto;
+      border-radius: 6px;
+      transition: none;
+    }
+
+    ::v-deep .el-link--primary {
+      color: #1677ff;
+      background: rgba(22, 119, 255, 0.08);
+    }
+
+    ::v-deep .el-link--primary:hover {
+      color: #0f5fd2;
+      background: rgba(22, 119, 255, 0.14);
+    }
+
+    ::v-deep .el-link--danger {
+      color: #f04438;
+      background: rgba(240, 68, 56, 0.08);
+    }
+
+    ::v-deep .el-link--danger:hover {
+      color: #d92d20;
+      background: rgba(240, 68, 56, 0.14);
+    }
+  }
+
+  @media (max-width: 768px) {
+    .system-menu-page {
+      padding: 10px;
+    }
+
+    .menu-shell {
+      ::v-deep .el-card__body {
+        padding: 14px;
+      }
+    }
+  }
+</style>