Эх сурвалжийг харах

feat: 完善导航菜单渲染

叶静 3 долоо хоног өмнө
parent
commit
dd3f8b0271

BIN
public/static/images/shop/avatar.png


+ 40 - 5
src/app/admin/api/index.js

@@ -29,14 +29,15 @@ export default {
     }),
   // 账号信息
   account: {
+    ...CRUD('user'),
     // 登录
     login: (data) =>
       request({
-        url: 'admin/index/login',
+        url: 'login/login',
         method: 'POST',
         data,
         options: {
-          showSuccessMessage: true,
+          showSuccessMessage: false,
         },
       }),
     // 登出
@@ -47,7 +48,17 @@ export default {
     // 个人信息
     info: () =>
       request({
-        url: 'admin/index/profile',
+        url: 'user/detail',
+        method: 'POST',
+        data,
+        options: {
+          showSuccessMessage: true,
+        },
+      }),
+    queryMenu: () =>
+      request({
+        url: 'login/queryMenu',
+        method: 'GET',
       }),
     // 菜单权限
     rules: () =>
@@ -79,6 +90,31 @@ export default {
     admin: {
       ...CRUD('admin/auth/admin'),
     },
+    // 用户管理
+    user: {
+      ...CRUD('user'),
+      // 用户列表
+      userList: (data) =>
+        request({
+          url: 'user/list',
+          method: 'POST',
+          data,
+        }),
+      // 用户状态切换
+      status: (id) =>
+        request({
+          url: '/user/status',
+          method: 'POST',
+          params: { id },
+        }),
+      // 修改密码
+      restpwd: (data) =>
+        request({
+          url: '/user/restpwd',
+          method: 'POST',
+          data,
+        }),
+    },
     // 角色管理
     role: {
       // 角色组
@@ -220,8 +256,7 @@ export default {
     // 文件上传
     upload: (params, data) =>
       request({
-        baseURL: import.meta.env.SHEEP_UPLOAD_BASE_URL,
-        url: '/file/upload',
+        url: '/operating/file/upload',
         method: 'POST',
         params,
         data,

+ 15 - 13
src/app/admin/store/account.js

@@ -32,8 +32,9 @@ const account = defineStore({
         this.setToken(response.data.token);
 
         // 如果返回了用户信息,也设置到 store 中
-        if (response.data.userInfo) {
-          this.info = response.data.userInfo;
+        if (response.data) {
+          this.info = response.data;
+          $storage.set('userInfo', response.data);
         }
       }
 
@@ -59,30 +60,31 @@ const account = defineStore({
      * @description 获取用户信息
      */
     async getInfo() {
-      const { code, data } = await admin.account.info();
-
       let lastLogin = $storage.get('lastLogin');
+      const userInfo = $storage.get('userInfo');
       if (lastLogin) {
         lastLogin.info = {
-          avatar: data.avatar,
-          nickname: data.nickname,
-          account: data.account,
+          avatar: userInfo.avatar,
+          nickname: userInfo.name,
+          account: userInfo.loginAccount,
         };
         $storage.set('lastLogin', lastLogin);
       }
-      this.info = data;
-      return data;
+      this.info = userInfo;
+      return userInfo;
     },
 
     /**
      * @description 获取用户菜单权限规则
      */
     async getRules() {
-      const { code, data } = await localAdmin.account.rules();
+      // const { code, data } = await localAdmin.account.rules();
+      const { code, data } = await admin.account.queryMenu();
+      // console.log(code, data);
       if (code == 200) {
-        this.menuRules = data.menu; // 设置菜单规则
-        this.pageRules = data.permission; // 设置页面权限
-        this.apiRules = data.permission; // 设置功能权限
+        this.menuRules = data; // 设置菜单规则
+        // this.pageRules = data.permission; // 设置页面权限
+        // this.apiRules = data.permission; // 设置功能权限
         return data;
       }
     },

+ 101 - 116
src/app/admin/views/auth/access/components/sa-access.vue

@@ -149,16 +149,18 @@
   watch(
     () => props.modelValue,
     (newValue) => {
+      // 如果正在进行权限选择操作,只更新checkedIds,不触发其他计算
+      if (isSelectingPermission) {
+        state.checkedIds = newValue || [];
+        return;
+      }
+
       state.checkedIds = newValue || [];
 
       // 当modelValue变化时,重新计算权限状态(无论数据是否为空都要计算)
-      let appItem = initAppItem();
       if (state.app.length > 0) {
+        let appItem = initAppItem();
         initCalculate(state.app, appItem);
-      }
-
-      // 如果不是权限选择操作导致的变化,才重置展开状态
-      if (!isSelectingPermission) {
         calculateShow(appItem);
       }
     },
@@ -169,16 +171,18 @@
   watch(
     () => state.app,
     (newApp) => {
+      // 如果正在进行权限选择操作,跳过所有处理
+      if (isSelectingPermission) {
+        return;
+      }
+
       // 无论数据是否为空都要初始化显示状态
       let appItem = initAppItem();
       if (newApp && newApp.length > 0) {
         initCalculate(newApp, appItem);
       }
 
-      // 如果不是权限选择操作导致的变化,才重置展开状态
-      if (!isSelectingPermission) {
-        calculateShow(appItem);
-      }
+      calculateShow(appItem);
     },
   );
 
@@ -189,19 +193,18 @@
       return [];
     }
 
-    console.log('🔄 transformPermissionData - 输入数据:', data);
-
     const result = data.map((item) => {
-      console.log('🔄 transformPermissionData - 处理项目:', item);
-
       const transformed = {
         id: item.id,
-        title: item.name, // 使用name作为title
+        title: item.name || item.label, // 优先使用name,备用label
         icon: item.logo || 'menu', // 使用logo作为icon,默认为menu
         children:
           item.children && Array.isArray(item.children)
             ? transformPermissionData(item.children)
             : [],
+        // 根据hasRelevance字段设置选中状态
+        checked: item.hasRelevance === '1',
+        indeterminate: false,
         // 保留原始数据以备后用
         _original: item,
       };
@@ -218,40 +221,45 @@
     state.loading = true;
 
     try {
-      // 调用新的权限树接口
-      const response = await admin.auth.access.getTree();
-
-      if (response.success && response.data !== null && response.data !== undefined) {
-        console.log('🔍 getData - 原始响应数据:', response.data);
-
-        // 转换数据格式(即使是空数组也要处理)
-        const transformedData = Array.isArray(response.data)
-          ? transformPermissionData(response.data)
-          : [];
+      let permissionData = [];
 
-        console.log('🔍 getData - 转换后数据:', transformedData);
+      // 根据type类型获取不同的数据
+      if (props.type === 'select' && props.role_id) {
+        // 权限选择模式且有角色ID,获取角色详情中的权限数据
+        console.log('🔍 获取角色权限数据, role_id:', props.role_id);
+        const roleResponse = await admin.auth.role.detail(props.role_id);
 
-        // 根据type类型处理数据
-        if (props.type === 'select') {
-          // 权限选择模式,使用完整的权限数据
-          state.app = transformedData;
-        } else {
-          // 其他模式(如list),也使用权限数据
-          state.app = transformedData;
+        if (roleResponse.code == 200 && roleResponse.data && roleResponse.data.permissions) {
+          console.log('🔍 getData - 角色权限数据:', roleResponse.data.permissions);
+          permissionData = roleResponse.data.permissions;
         }
+      } else {
+        // 其他模式,获取完整权限树
+        const response = await admin.auth.access.getTree();
 
-        // 初始化显示状态(无论数据是否为空都要初始化)
-        let appItem = initAppItem();
-        if (transformedData.length > 0) {
-          initCalculate(transformedData, appItem);
+        if (response.success && response.data !== null && response.data !== undefined) {
+          console.log('🔍 getData - 权限树数据:', response.data);
+          permissionData = response.data;
         }
-        calculateShow(appItem);
-      } else {
-        console.error('获取权限数据失败:', response.message || '数据为空');
-        state.app = [];
+      }
 
-        // 即使没有数据也要初始化显示状态
-        let appItem = initAppItem();
+      // 转换数据格式(即使是空数组也要处理)
+      const transformedData = Array.isArray(permissionData)
+        ? transformPermissionData(permissionData)
+        : [];
+
+      console.log('🔍 getData - 转换后数据:', transformedData);
+
+      state.app = transformedData;
+
+      // 初始化显示状态(无论数据是否为空都要初始化)
+      let appItem = initAppItem();
+      if (transformedData.length > 0) {
+        initCalculate(transformedData, appItem);
+      }
+
+      // 如果不是权限选择操作导致的变化,才重置展开状态
+      if (!isSelectingPermission) {
         calculateShow(appItem);
       }
     } catch (error) {
@@ -260,7 +268,9 @@
 
       // 异常情况下也要初始化显示状态
       let appItem = initAppItem();
-      calculateShow(appItem);
+      if (!isSelectingPermission) {
+        calculateShow(appItem);
+      }
     }
 
     state.loading = false;
@@ -535,39 +545,36 @@
         return checkedIdStr === itemIdStr || checkedIdNum === itemIdNum;
       });
 
-      if (isChecked) {
-        if (!isEmpty(item.children)) {
-          let arr = [];
-          flattenData(item.children, arr);
-          const allChildrenChecked = arr.every((k) => {
-            const kIdStr = String(k.id);
-            const kIdNum = Number(k.id);
-            return state.checkedIds.some((checkedId) => {
-              const checkedIdStr = String(checkedId);
-              const checkedIdNum = Number(checkedId);
-              return checkedIdStr === kIdStr || checkedIdNum === kIdNum;
-            });
-          });
+      // 先递归处理子项
+      if (!isEmpty(item.children)) {
+        initCalculate(item.children, item);
 
-          if (allChildrenChecked) {
-            item.checked = true;
-            item.indeterminate = false;
-          } else {
-            item.checked = false;
-            item.indeterminate = true;
-          }
-        } else {
+        // 检查子项的选中状态
+        const checkedChildren = item.children.filter((child) => child.checked);
+        const indeterminateChildren = item.children.filter((child) => child.indeterminate);
+
+        // 如果父级本身被明确选中,保持选中状态
+        if (isChecked) {
           item.checked = true;
           item.indeterminate = false;
+        } else if (checkedChildren.length === item.children.length) {
+          // 所有子项都选中
+          item.checked = true;
+          item.indeterminate = false;
+        } else if (checkedChildren.length > 0 || indeterminateChildren.length > 0) {
+          // 部分子项选中或有半选状态
+          item.checked = false;
+          item.indeterminate = true;
+        } else {
+          // 没有子项选中
+          item.checked = false;
+          item.indeterminate = false;
         }
       } else {
-        item.checked = false;
+        // 叶子节点,直接根据是否在选中列表中设置状态
+        item.checked = isChecked;
         item.indeterminate = false;
       }
-
-      if (!isEmpty(item.children)) {
-        initCalculate(item.children, item);
-      }
     });
   }
 
@@ -626,8 +633,9 @@
   }
 
   function handleSelect(checked, item) {
+    // 设置标志,阻止watch触发
+    isSelectingPermission = true;
     manualChecked = true;
-    isSelectingPermission = true; // 设置权限选择标志
 
     // 计算所有子元素
     doChecked(item, checked);
@@ -635,14 +643,17 @@
     // 计算父元素
     doCheckedParent(item);
 
-    state.newIds = [];
-    getCheckedIds(state.app);
-    emit('update:modelValue', state.newIds);
+    // 收集选中的权限ID
+    const newIds = [];
+    getCheckedIds(state.app, newIds);
 
-    // 延迟重置标志,确保watch执行完毕
-    nextTick(() => {
+    // 直接发送事件,不更新state.newIds避免触发watch
+    emit('update:modelValue', newIds);
+
+    // 延迟重置标志
+    setTimeout(() => {
       isSelectingPermission = false;
-    });
+    }, 50);
   }
 
   function doChecked(item, checked) {
@@ -767,20 +778,8 @@
     }
   }
 
-  watch(
-    () => state.app,
-    async () => {
-      if (manualChecked) {
-        state.newIds = [];
-        getCheckedIds(state.app);
-
-        emit('update:modelValue', state.newIds);
-      }
-    },
-    {
-      deep: true,
-    },
-  );
+  // 移除可能导致递归更新的watch监听器
+  // 权限选择的更新通过handleSelect函数直接处理
 
   watch(
     () => props.role_id,
@@ -792,13 +791,21 @@
     },
   );
 
-  function getCheckedIds(data) {
+  function getCheckedIds(data, targetArray = null) {
+    const idsArray = targetArray || state.newIds;
+
     data.forEach((i) => {
-      if (i.checked || i.indeterminate) {
-        state.newIds.push(i.id + '');
+      // 收集真正选中的权限(全选状态)
+      if (i.checked && !i.indeterminate) {
+        idsArray.push(i.id + '');
+      }
+      // 收集半选状态的父级权限(当子级被选中时,父级也要包含)
+      else if (i.indeterminate) {
+        idsArray.push(i.id + '');
       }
+
       if (!isEmpty(i.children)) {
-        getCheckedIds(i.children);
+        getCheckedIds(i.children, idsArray);
       }
     });
   }
@@ -908,8 +915,6 @@
             const indent = '  '.repeat(level);
 
             for (const item of items) {
-              console.log(`${indent}当前检查的item:`, item);
-
               // 现在可以安全使用_original字段了
               const originalData = item._original || {};
               const itemName = originalData.name || '';
@@ -918,12 +923,6 @@
               // 备用匹配:如果原始数据没有name,尝试使用转换后的title
               const fallbackName = item.title || '';
 
-              console.log(
-                `${indent}检查权限: 名称="${itemName}", 备用名称="${fallbackName}", 父ID="${itemParentId}"`,
-              );
-              console.log(`${indent}原始数据:`, originalData);
-              console.log(`${indent}item._original存在:`, !!item._original);
-
               // 匹配权限名称和父ID(优先使用原始数据的name字段,备用title字段)
               const nameMatches = itemName === name || (itemName === '' && fallbackName === name);
 
@@ -932,26 +931,12 @@
                 const isTopLevel = !parentId || parentId === '' || parentId === '0';
                 const itemIsTopLevel = !itemParentId || itemParentId === '' || itemParentId === '0';
 
-                console.log(
-                  `${indent}🔍 匹配检查: 查找="${name}" vs 权限="${itemName}" vs 备用="${fallbackName}"`,
-                );
-                console.log(`${indent}🔍 父ID检查: 查找="${parentId}" vs 权限="${itemParentId}"`);
-                console.log(
-                  `${indent}🔍 顶级检查: 查找顶级=${isTopLevel}, 权限顶级=${itemIsTopLevel}`,
-                );
-
                 // 🎯 修复:父ID匹配逻辑
                 const parentIdMatches =
                   (isTopLevel && itemIsTopLevel) || String(itemParentId) === String(parentId);
 
-                console.log(`${indent}🔍 父ID匹配结果: ${parentIdMatches}`);
-                console.log(
-                  `${indent}🔍 详细比较: "${String(itemParentId)}" === "${String(parentId)}" = ${String(itemParentId) === String(parentId)}`,
-                );
-
                 if (parentIdMatches) {
                   const matchedName = itemName || fallbackName;
-                  console.log(`${indent}✅ 找到匹配权限: ${matchedName}, ID: ${item.id}`);
                   return item.id;
                 } else {
                   console.log(`${indent}❌ 父ID不匹配,跳过此权限`);

+ 12 - 29
src/app/admin/views/auth/access/edit.vue

@@ -26,23 +26,24 @@
         </el-form-item> -->
         <el-form-item label="类型" prop="type">
           <el-radio-group v-model="form.model.type" @change="changeType">
-            <el-radio label="menu">菜单</el-radio>
-            <el-radio label="page">页面</el-radio>
-            <el-radio label="api">动作</el-radio>
+            <el-radio label="1">菜单</el-radio>
+            <el-radio label="3">页面</el-radio>
+            <el-radio label="4">动作</el-radio>
+            <!-- <el-radio label="3">页面</el-radio> -->
           </el-radio-group>
         </el-form-item>
         <el-form-item label="标题" prop="title">
           <el-input v-model="form.model.title"></el-input>
         </el-form-item>
         <el-form-item label="标识" prop="name">
-          <el-input v-model="form.model.name" placeholder="请输入前端排版标识"></el-input>
+          <el-input v-model="form.model.name" placeholder="请输入唯一标识"></el-input>
         </el-form-item>
         <el-form-item v-if="form.model.type != 'api'" label="图标" prop="icon">
           <sa-uploader v-model="form.model.icon" mode="input" fileType="image"></sa-uploader>
         </el-form-item>
-        <el-form-item v-if="form.model.type != 'api'" label="跳转地址" prop="url">
+        <!-- <el-form-item v-if="form.model.type != 'api'" label="跳转地址" prop="url">
           <el-input v-model="form.model.url" placeholder="请输入跳转地址"></el-input>
-        </el-form-item>
+        </el-form-item> -->
         <el-form-item label="权重" prop="weigh">
           <el-input v-model="form.model.weigh" placeholder="请输入权重" type="number"></el-input>
         </el-form-item>
@@ -122,6 +123,7 @@
   import admin from '@/app/admin/api';
   import SaDraggable from 'vuedraggable';
   import { cloneDeep } from 'lodash';
+  import { ElMessage } from 'element-plus';
 
   const apiCrud = [
     {
@@ -159,7 +161,7 @@
     model: {
       parent_id: props.modal.params.parent_id || '',
       title: '',
-      type: 'menu',
+      type: '1',
       name: '',
       icon: '',
       url: '',
@@ -186,7 +188,7 @@
     return {
       parent_id: data.parentId || null,
       title: data.name || '', // 使用name作为title
-      type: getFormType(data.type), // 转换type格式
+      type: data.type, // 转换type格式
       name: data.composingKey || '', // 使用composingKey作为标识
       icon: data.logo || '',
       url: data.url || '', // 跳转地址
@@ -196,16 +198,6 @@
     };
   }
 
-  // 转换type字段:数字转换为字符串
-  function getFormType(type) {
-    const typeMap = {
-      0: 'menu', // 菜单
-      1: 'page', // 页面
-      2: 'api', // 权限/动作
-    };
-    return typeMap[String(type)] || 'menu';
-  }
-
   // 转换status字段:数字转换为字符串
   function getFormStatus(status) {
     const statusMap = {
@@ -262,7 +254,7 @@
       eName: formData.title || '', // 使用title作为eName
       logo: formData.icon || '',
       composingKey: formData.name || '', // 使用name作为composingKey
-      type: getSubmitType(formData.type), // 转换type格式
+      type: formData.type, // 转换type格式
       status: getSubmitStatus(formData.status), // 转换status格式
       seq: formData.weigh || 0, // 使用weigh作为seq
       url: formData.url || '', // 跳转地址
@@ -275,16 +267,6 @@
     };
   }
 
-  // 转换type字段:字符串转换为数字
-  function getSubmitType(type) {
-    const typeMap = {
-      menu: 0, // 菜单
-      page: 1, // 页面
-      api: 2, // 动作
-    };
-    return typeMap[type] || 0;
-  }
-
   // 转换status字段:字符串转换为数字
   function getSubmitStatus(status) {
     const statusMap = {
@@ -307,6 +289,7 @@
         props.modal.params.type == 'add' || props.modal.params.type == 'append'
           ? await admin.auth.access.add(submitForm)
           : await admin.auth.access.edit(submitForm);
+      ElMessage.success(props.modal.params.type == 'add' ? '创建成功' : '更新成功');
       code == '200' && emit('modalCallBack', { event: 'confirm', data });
     });
   }

+ 105 - 78
src/app/admin/views/auth/admin/edit.vue

@@ -2,60 +2,53 @@
   <el-container>
     <el-main>
       <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
-        <el-form-item label="用户名" prop="username">
-          <el-input v-model="form.model.username" placeholder="请输入用户名"></el-input>
+        <el-form-item label="用户名" prop="account">
+          <el-input v-model="form.model.account" placeholder="请输入用户名"></el-input>
         </el-form-item>
-        <el-form-item label="手机号" prop="mobile">
-          <el-input v-model="form.model.mobile" placeholder="请输入手机号"></el-input>
-        </el-form-item>
-        <el-form-item label="授权角色" prop="role_id">
+        <el-form-item label="用户角色" prop="roleIds">
           <el-select
-            v-model="form.model.role_id"
-            placeholder="请选择授权角色"
+            v-model="form.model.roleIds"
+            placeholder="请选择用户角色"
+            multiple
             clearable
             style="width: 100%"
           >
             <el-option
-              v-for="item in role.select"
+              v-for="item in role.list"
               :key="item.id"
               :label="item.name"
               :value="item.id"
             ></el-option>
           </el-select>
         </el-form-item>
-        <el-form-item label="用户说明" prop="description">
+        <el-form-item v-if="modal.params.type === 'add'" label="密码" prop="pwd">
+          <el-input
+            v-model="form.model.pwd"
+            type="password"
+            placeholder="请输入密码"
+            show-password
+          ></el-input>
+        </el-form-item>
+        <el-form-item v-if="modal.params.type === 'add'" label="确认密码" prop="confirmPassword">
           <el-input
-            v-model="form.model.description"
-            type="textarea"
-            :rows="3"
-            placeholder="请输入用户说明"
+            v-model="form.model.confirmPassword"
+            type="password"
+            placeholder="请再次输入密码"
+            show-password
           ></el-input>
         </el-form-item>
       </el-form>
     </el-main>
     <el-footer class="sa-footer--submit">
-      <el-button
-        v-if="modal.params.type == 'add'"
-        v-auth="'admin.auth.admin.add'"
-        type="primary"
-        @click="confirm"
-        >保存</el-button
-      >
-      <el-button
-        v-if="modal.params.type == 'edit'"
-        v-auth="'admin.auth.admin.edit'"
-        type="primary"
-        @click="confirm"
-        >更新</el-button
-      >
+      <el-button v-if="modal.params.type == 'add'" type="primary" @click="confirm">保存</el-button>
+      <el-button v-if="modal.params.type == 'edit'" type="primary" @click="confirm">更新</el-button>
     </el-footer>
   </el-container>
 </template>
 <script setup>
   import { onMounted, reactive, ref, unref } from 'vue';
-  import { cloneDeep } from 'lodash';
-  import { adminMockData } from '@/sheep/mock/admin.js';
-  import { roleMockData } from '@/sheep/mock/role.js';
+  import { ElMessage } from 'element-plus';
+  import admin from '@/app/admin/api';
 
   const emit = defineEmits(['modalCallBack']);
   const props = defineProps({
@@ -68,57 +61,83 @@
   let formRef = ref(null);
   const form = reactive({
     model: {
-      username: '',
-      mobile: '',
-      role_id: null,
-      description: '',
-      role_name: '', // 用于显示角色名称
+      id: null,
+      account: '',
+      roleIds: [],
+      pwd: '',
+      confirmPassword: '',
     },
     rules: {
-      username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
-      mobile: [
-        { required: false, message: '请输入手机号', trigger: 'blur' },
-        { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' },
+      account: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
+      roleIds: [{ required: true, message: '请选择用户角色', trigger: 'change' }],
+      pwd: [
+        { required: true, message: '请输入密码', trigger: 'blur' },
+        { min: 6, message: '密码长度不能少于6位', trigger: 'blur' },
+      ],
+      confirmPassword: [
+        { required: true, message: '请再次输入密码', trigger: 'blur' },
+        {
+          validator: (_, value, callback) => {
+            if (value !== form.model.pwd) {
+              callback(new Error('两次输入的密码不一致'));
+            } else {
+              callback();
+            }
+          },
+          trigger: 'blur',
+        },
       ],
-      role_id: [{ required: true, message: '请选择授权角色', trigger: 'change' }],
-      description: [{ required: false, message: '请输入用户说明', trigger: 'blur' }],
     },
   });
   const loading = ref(false);
 
-  // 获取详情
-  async function getDetail(id) {
-    loading.value = true;
+  // 角色列表
+  const role = reactive({
+    list: [],
+  });
+
+  // 获取角色列表
+  async function getRoleList() {
     try {
-      const result = adminMockData.getDetail(id);
-      if (result.code === '200') {
-        form.model = { ...result.data };
+      const { code, data } = await admin.auth.role.roleList({
+        page: 1,
+        size: 100, // 获取所有角色
+      });
+      if (code == 200) {
+        role.list = data.list || [];
       }
     } catch (error) {
-      console.error('获取详情失败:', error);
+      console.error('获取角色列表失败:', error);
+      ElMessage.error('获取角色列表失败');
     }
-    loading.value = false;
   }
 
-  // 角色选择
-  const role = reactive({
-    select: [],
-  });
-
-  async function getRoleSelect() {
+  // 获取详情
+  async function getDetail(id) {
+    loading.value = true;
     try {
-      const result = roleMockData.getList();
-      if (result.code === '200') {
-        // 处理新的数据格式
-        const roleList = result.data.list || [];
-        role.select = roleList.map((item) => ({
-          id: item.id,
-          name: item.name,
-        }));
+      const { code, data } = await admin.auth.user.detail(id);
+      if (code == 200) {
+        form.model.id = data.id;
+        form.model.account = data.account || data.name;
+        // 处理角色数据:从userRoles数组中提取roleId
+        if (data.userRoles && Array.isArray(data.userRoles)) {
+          form.model.roleIds = data.userRoles.map((item) => item.roleId);
+        } else if (data.roleIds) {
+          // 兼容旧的roleIds字段格式
+          form.model.roleIds =
+            typeof data.roleIds === 'string'
+              ? data.roleIds.split(',').map((id) => parseInt(id))
+              : data.roleIds;
+        } else {
+          form.model.roleIds = [];
+        }
       }
     } catch (error) {
-      console.error('获取角色列表失败:', error);
+      console.error('获取详情失败:', error);
+      ElMessage.error('获取详情失败');
     }
+    loading.value = false;
   }
 
   // 表单关闭时提交
@@ -127,34 +146,42 @@
     unref(formRef).validate(async (valid) => {
       if (!valid) return;
 
-      let submitForm = cloneDeep(form.model);
+      try {
+        let submitData = {};
 
-      // 根据选择的角色ID设置角色名称
-      const selectedRole = role.select.find((r) => r.id === submitForm.role_id);
-      if (selectedRole) {
-        submitForm.role_name = selectedRole.name;
-      }
+        const isAdd = props.modal.params.type === 'add';
 
-      try {
-        const result =
-          props.modal.params.type == 'add'
-            ? adminMockData.add(submitForm)
-            : adminMockData.edit(props.modal.params.id, submitForm);
+        // 构建提交数据
+        submitData = {
+          account: form.model.account,
+          roleIds: form.model.roleIds.join(','),
+          ...(isAdd && { pwd: form.model.pwd }), // 新增时才包含密码
+          id: isAdd ? '' : props.modal.params.id, // 新增时id为空字符串,编辑时传递id
+        };
 
-        if (result.code === '200') {
+        // 调用对应接口
+        const { code } = isAdd
+          ? await admin.auth.user.add(submitData)
+          : await admin.auth.user.edit(submitData);
+
+        if (code == 200) {
+          ElMessage.success(isAdd ? '创建成功' : '更新成功');
           emit('modalCallBack', { event: 'confirm' });
         }
       } catch (error) {
         console.error('提交失败:', error);
+        ElMessage.error('提交失败');
       }
     });
   }
+
   async function init() {
-    getRoleSelect();
+    await getRoleList(); // 获取角色列表
     if (props.modal.params.id) {
       await getDetail(props.modal.params.id);
     }
   }
+
   onMounted(() => {
     init();
   });

+ 133 - 47
src/app/admin/views/auth/admin/index.vue

@@ -15,49 +15,80 @@
           height="100%"
           class="sa-table"
           :data="table.data"
-          @row-dblclick="editRow"
+          @sort-change="fieldFilter"
           row-key="id"
           stripe
         >
           <template #empty>
             <sa-empty />
           </template>
-          <el-table-column prop="username" label="用户名" min-width="120">
-            <template #default="scope">
-              <span class="sa-table-line-1">{{ scope.row.username || '-' }}</span>
-            </template>
+          <el-table-column prop="id" label="ID" min-width="100" sortable="custom">
           </el-table-column>
-          <el-table-column prop="mobile" label="手机号" min-width="140" align="center">
+          <el-table-column prop="name" label="用户名" min-width="150">
             <template #default="scope">
-              {{ scope.row.mobile || '-' }}
+              <span class="sa-table-line-1">{{ scope.row.name || '-' }}</span>
             </template>
           </el-table-column>
-          <el-table-column prop="role_name" label="授权角色" min-width="120" align="center">
+          <el-table-column label="状态" min-width="120" align="center">
             <template #default="scope">
-              <el-tag type="info">
-                {{ scope.row.role_name || '-' }}
+              <!-- ID为1的用户状态不可修改 -->
+              <el-tag
+                v-if="scope.row.id == 1"
+                :type="scope.row.status === '0' ? 'success' : 'danger'"
+              >
+                <span>{{ scope.row.status === '0' ? '启用' : '禁用' }}</span>
               </el-tag>
+              <!-- 其他用户可以修改状态 -->
+              <el-dropdown v-else popper-class="user-dropdown" @command="handleStatusCommand">
+                <el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
+                  <span>{{ scope.row.status === '0' ? '启用' : '禁用' }}</span>
+                  <el-icon class="sa-m-l-4">
+                    <ArrowDown />
+                  </el-icon>
+                </el-tag>
+                <template #dropdown>
+                  <el-dropdown-menu>
+                    <el-dropdown-item
+                      :command="{
+                        id: scope.row.id,
+                        type: 'enable',
+                      }"
+                    >
+                      <span class="status-up">启用</span>
+                    </el-dropdown-item>
+                    <el-dropdown-item
+                      :command="{
+                        id: scope.row.id,
+                        type: 'disable',
+                      }"
+                    >
+                      <span class="status-down">禁用</span>
+                    </el-dropdown-item>
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
             </template>
           </el-table-column>
-          <el-table-column prop="description" label="用户说明" min-width="150" align="center">
-            <template #default="scope">
-              {{ scope.row.description || '-' }}
-            </template>
-          </el-table-column>
-          <el-table-column fixed="right" label="操作" min-width="120">
+          <el-table-column fixed="right" label="操作">
             <template #default="scope">
-              <el-button link type="primary" @click="editRow(scope.row)">编辑</el-button>
-              <el-popconfirm
-                width="fit-content"
-                confirm-button-text="确认"
-                cancel-button-text="取消"
-                title="确认删除这条记录?"
-                @confirm="deleteApi(scope.row.id)"
-              >
-                <template #reference>
-                  <el-button link type="danger">删除</el-button>
-                </template>
-              </el-popconfirm>
+              <!-- 修改密码按钮:所有用户都可以 -->
+              <el-button link type="warning" @click="changePassword(scope.row)">修改密码</el-button>
+
+              <!-- ID为1的用户不能编辑和删除 -->
+              <template v-if="scope.row.id != 1">
+                <el-button link type="primary" @click="editRow(scope.row)">编辑</el-button>
+                <el-popconfirm
+                  width="fit-content"
+                  confirm-button-text="确认"
+                  cancel-button-text="取消"
+                  title="确认删除这条记录?"
+                  @confirm="deleteApi(scope.row.id)"
+                >
+                  <template #reference>
+                    <el-button link type="danger">删除</el-button>
+                  </template>
+                </el-popconfirm>
+              </template>
             </template>
           </el-table-column>
         </el-table>
@@ -77,35 +108,55 @@
 </script>
 <script setup>
   import { onMounted, reactive, ref } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import { ArrowDown } from '@element-plus/icons-vue';
   import { useModal, usePagination } from '@/sheep/hooks';
+  import admin from '@/app/admin/api';
   import AdminEdit from './edit.vue';
-  import { adminMockData } from '@/sheep/mock/admin.js';
+  import PasswordEdit from './password.vue';
 
   const { pageData } = usePagination();
 
   // 表格状态
   const table = reactive({
     data: [],
+    order: '',
+    sort: '',
   });
   const loading = ref(true);
 
   // 获取数据
-  async function getData() {
+  async function getData(page, searchParams = {}) {
+    if (page) pageData.page = page;
     loading.value = true;
+
     try {
-      const result = adminMockData.getList();
-      if (result.code === '200') {
-        // 适配新的数据格式
-        table.data = result.data.list || [];
-        pageData.total = result.data.total || 0;
-        pageData.page = result.data.pageNum || 1;
-        pageData.size = result.data.pageSize || 10;
+      const { code, data } = await admin.auth.user.userList({
+        page: pageData.page,
+        size: pageData.size,
+        order: table.order,
+        sort: table.sort,
+        ...searchParams,
+      });
+
+      if (code == 200) {
+        table.data = data.list;
+        pageData.page = data.pageNum;
+        pageData.size = data.pageSize;
+        pageData.total = data.total;
       }
     } catch (error) {
-      console.error('获取人员列表失败:', error);
-      table.data = [];
+      console.error('获取数据失败:', error);
+    } finally {
+      loading.value = false;
     }
-    loading.value = false;
+  }
+
+  // table 字段排序
+  function fieldFilter({ prop, order }) {
+    table.order = order == 'ascending' ? 'asc' : 'desc';
+    table.sort = prop;
+    getData();
   }
 
   // 新增人员
@@ -138,18 +189,42 @@
     );
   }
 
-  // 删除人员
-  async function deleteApi(id) {
+  // 修改密码
+  function changePassword(row) {
+    useModal(
+      PasswordEdit,
+      {
+        title: '修改密码',
+        type: 'password',
+        id: row.id,
+        name: row.name,
+      },
+      {
+        confirm: () => {
+          ElMessage.success('密码修改成功');
+        },
+      },
+    );
+  }
+
+  // 状态切换处理
+  async function handleStatusCommand(command) {
     try {
-      const result = adminMockData.delete(id);
-      if (result.code == '200') {
-        getData();
-      }
+      await admin.auth.user.status(command.id);
+      ElMessage.success('状态切换成功');
+      getData();
     } catch (error) {
-      console.error('删除失败:', error);
+      console.error('状态切换失败:', error);
+      ElMessage.error('状态切换失败');
     }
   }
 
+  // 删除人员
+  async function deleteApi(id) {
+    await admin.auth.user.delete({ id });
+    getData();
+  }
+
   onMounted(async () => {
     await getData();
   });
@@ -160,3 +235,14 @@
     color: var(--sa-font);
   }
 </style>
+
+<style lang="scss">
+  .user-dropdown {
+    .status-up {
+      color: var(--el-color-success);
+    }
+    .status-down {
+      color: var(--el-color-danger);
+    }
+  }
+</style>

+ 98 - 0
src/app/admin/views/auth/admin/password.vue

@@ -0,0 +1,98 @@
+<template>
+  <el-container>
+    <el-main>
+      <el-form :model="form.model" :rules="form.rules" ref="formRef" label-width="100px">
+        <el-form-item label="用户名称">
+          <el-input v-model="props.modal.params.name" disabled></el-input>
+        </el-form-item>
+        <el-form-item label="新密码" prop="password">
+          <el-input
+            v-model="form.model.password"
+            type="password"
+            placeholder="请输入新密码"
+            show-password
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="确认密码" prop="confirmPassword">
+          <el-input
+            v-model="form.model.confirmPassword"
+            type="password"
+            placeholder="请再次输入新密码"
+            show-password
+          ></el-input>
+        </el-form-item>
+      </el-form>
+    </el-main>
+    <el-footer class="sa-footer--submit">
+      <el-button type="primary" @click="confirm">确定修改</el-button>
+    </el-footer>
+  </el-container>
+</template>
+
+<script setup>
+  import { reactive, ref, unref } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import admin from '@/app/admin/api';
+
+  const emit = defineEmits(['modalCallBack']);
+  const props = defineProps({
+    modal: {
+      type: Object,
+    },
+  });
+
+  // 表单
+  const formRef = ref(null);
+  const form = reactive({
+    model: {
+      password: '',
+      confirmPassword: '',
+    },
+    rules: {
+      password: [
+        { required: true, message: '请输入新密码', trigger: 'blur' },
+        { min: 6, message: '密码长度不能少于6位', trigger: 'blur' },
+      ],
+      confirmPassword: [
+        { required: true, message: '请再次输入新密码', trigger: 'blur' },
+        {
+          validator: (rule, value, callback) => {
+            if (value !== form.model.password) {
+              callback(new Error('两次输入的密码不一致'));
+            } else {
+              callback();
+            }
+          },
+          trigger: 'blur',
+        },
+      ],
+    },
+  });
+
+  // 提交修改
+  function confirm() {
+    unref(formRef).validate(async (valid) => {
+      if (!valid) return;
+
+      try {
+        const { code } = await admin.auth.user.restpwd({
+          pwd: form.model.password,
+          userId: props.modal.params.id,
+        });
+
+        if (code == 200) {
+          emit('modalCallBack', { event: 'confirm' });
+        }
+      } catch (error) {
+        console.error('修改密码失败:', error);
+        ElMessage.error('修改密码失败');
+      }
+    });
+  }
+</script>
+
+<style lang="scss" scoped>
+  .el-form {
+    max-width: 400px;
+  }
+</style>

+ 39 - 36
src/app/admin/views/auth/role/edit.vue

@@ -5,22 +5,13 @@
         <el-form-item label="角色名称" prop="name">
           <el-input v-model="form.model.name" placeholder="请输入角色名称"></el-input>
         </el-form-item>
-        <el-form-item label="角色说明">
-          <el-input
-            type="textarea"
-            maxlength="200"
-            show-word-limit
-            v-model="form.model.description"
-            placeholder="请输入角色说明"
-          ></el-input>
-        </el-form-item>
         <el-form-item label="权限信息">
           <sa-access
             type="select"
             :isChangeParentId="isChangeParentId"
-            :role_id="form.model.parent_id"
+            :role_id="props.modal.params.type === 'edit' ? props.modal.params.id : null"
             :multiple="true"
-            v-model="form.model.rules"
+            v-model="form.model.permissionIds"
           >
           </sa-access>
         </el-form-item>
@@ -50,11 +41,9 @@
   const formRef = ref(null);
   const form = reactive({
     model: {
-      parent_id: '',
+      id: null,
       name: '',
-      description: '',
-      status: 'normal',
-      rules: [],
+      permissionIds: [], // 权限ID数组,用于sa-access组件
     },
     rules: {
       name: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
@@ -68,14 +57,32 @@
     try {
       const { code, data } = await admin.auth.role.detail(id);
       if (code == 200) {
-        form.model = { ...data };
+        // 设置基本信息
+        form.model.id = data.id;
+        form.model.name = data.name;
+        form.model.status = data.status || 'normal';
 
-        // 确保rules是数组格式,用于sa-access组件
-        if (!form.model.rules || !Array.isArray(form.model.rules)) {
-          form.model.rules = [];
+        // 处理权限数据:从permissions数组中提取已选中的权限ID
+        if (data.permissions && Array.isArray(data.permissions)) {
+          const selectedIds = [];
+          const extractSelectedIds = (permissions) => {
+            permissions.forEach((permission) => {
+              if (permission.hasRelevance === '1') {
+                selectedIds.push(permission.id.toString());
+              }
+              if (permission.children && permission.children.length > 0) {
+                extractSelectedIds(permission.children);
+              }
+            });
+          };
+          extractSelectedIds(data.permissions);
+          form.model.permissionIds = selectedIds;
+        } else {
+          form.model.permissionIds = [];
         }
 
         console.log('角色详情数据:', form.model);
+        console.log('已选中的权限ID:', form.model.permissionIds);
       }
     } catch (error) {
       console.error('获取详情失败:', error);
@@ -93,28 +100,24 @@
         let submitForm = cloneDeep(form.model);
         console.log('提交的表单数据:', submitForm);
 
-        // 处理权限数据,从rules数组中提取权限信息
-        if (submitForm.rules && submitForm.rules.length > 0) {
-          const permissions = [];
-          const extractPermissions = (rules) => {
-            rules.forEach((rule) => {
-              if (rule.title && rule.checked) {
-                permissions.push(rule.title);
-              }
-              if (rule.children && rule.children.length > 0) {
-                extractPermissions(rule.children);
-              }
-            });
-          };
-          extractPermissions(submitForm.rules);
-          submitForm.permissions = permissions;
-          submitForm.permissions_text = permissions.join('、');
+        // 处理权限数据:将权限ID数组转换为逗号分隔的字符串
+        if (submitForm.permissionIds && Array.isArray(submitForm.permissionIds)) {
+          submitForm.permissionIds = submitForm.permissionIds.join(',');
+        } else {
+          submitForm.permissionIds = '';
         }
 
+        // 新增时不需要传递id字段
+        if (props.modal.params.type === 'add') {
+          delete submitForm.id;
+        }
+
+        console.log('最终提交数据:', submitForm);
+
         const { code } =
           props.modal.params.type == 'add'
             ? await admin.auth.role.add(submitForm)
-            : await admin.auth.role.edit(props.modal.params.id, submitForm);
+            : await admin.auth.role.edit(submitForm);
 
         if (code == 200) {
           ElMessage.success(props.modal.params.type == 'add' ? '创建成功' : '更新成功');

+ 1 - 1
src/app/admin/views/auth/role/index.vue

@@ -137,7 +137,7 @@
 
   // 删除api 单独批量可以直接调用
   async function deleteApi(id) {
-    await admin.auth.role.delete(id);
+    await admin.auth.role.delete({ id });
     getData();
   }
 

+ 36 - 19
src/app/admin/views/index/profile/index.vue

@@ -14,14 +14,20 @@
                     size="64"
                     radius="32"
                   ></sa-image>
+                  <img
+                    v-else
+                    src="/static/images/shop/avatar.png"
+                    style="width: 64px; height: 64px; border-radius: 32px; object-fit: cover"
+                    alt="默认头像"
+                  />
                 </div>
                 <div>
                   <div class="name sa-m-b-8">
-                    {{ state.account.account }}
+                    {{ state.account.loginAccount || state.account.name }}
                     <span class="id sa-m-l-8">#{{ state.account.id }}</span>
                   </div>
-                  <div v-if="state.account.role_id">
-                    {{ state.account.role.name }}
+                  <div v-if="state.account.roleName">
+                    {{ state.account.roleName }}
                   </div>
                 </div>
               </div>
@@ -31,32 +37,43 @@
             </div>
             <div class="detail">
               <div class="detail-item">
-                <div class="label sa-m-b-8">用户称</div>
+                <div class="label sa-m-b-8">用户称</div>
                 <div class="sa-flex">
-                  <el-input v-model="state.account.nickname"></el-input>
+                  <el-input
+                    readonly
+                    v-model="state.account.name"
+                    placeholder="请输入用户名称"
+                  ></el-input>
                 </div>
               </div>
               <div class="detail-item">
-                <div class="label sa-m-b-8">电子邮件</div>
-                <el-input v-model="state.account.email" placeholder="未认证电子邮箱"></el-input>
-              </div>
-              <div class="detail-item">
-                <div class="label sa-m-b-8">手机号码</div>
-                <el-input v-model="state.account.mobile"></el-input>
+                <div class="label sa-m-b-8">登录账号</div>
+                <el-input
+                  readonly
+                  v-model="state.account.loginAccount"
+                  placeholder="请输入登录账号"
+                ></el-input>
               </div>
               <div class="detail-item">
-                <div class="label sa-m-b-8">更改密码</div>
+                <div class="label sa-m-b-8">角色</div>
                 <el-input
-                  v-model="state.account.password"
-                  placeholder="不修改密码请留空"
+                  readonly
+                  v-model="state.account.roleName"
+                  placeholder="roleName"
                 ></el-input>
               </div>
+              <!-- <div class="detail-item">
+                <div class="label sa-m-b-8">电子邮件</div>
+                <el-input v-model="state.account.email" placeholder="请输入电子邮箱"></el-input>
+              </div>
+              <div class="detail-item">
+                <div class="label sa-m-b-8">手机号码</div>
+                <el-input v-model="state.account.phoneNo" placeholder="请输入手机号码"></el-input>
+              </div>
               <div class="detail-item sa-flex">
-                <el-button v-auth="'admin.index.update'" type="primary" @click="updateAccount"
-                  >确定修改</el-button
-                >
+                <el-button type="primary" @click="updateAccount">确定修改</el-button>
                 <el-button @click="getInfo">重置</el-button>
-              </div>
+              </div> -->
             </div>
           </div>
           <div class="login">
@@ -85,7 +102,7 @@
     state.account = await accountStore.getInfo();
   }
   function updateAccount() {
-    adminApi.account.update(state.account);
+    adminApi.account.edit(state.account);
   }
   function logout() {
     ElMessageBox.confirm('您确定要退出登录吗?', '退出登录', {

+ 6 - 6
src/sheep/hooks/useApp.js

@@ -44,11 +44,11 @@ export function useApp() {
 
   // 获取应用动态菜单
   function getAppByName(name) {
-    return appMenu.value.find((item) => item.name == name);
+    return appMenu.value.find((item) => item.composingKey == name);
   }
 
   function getAppMenuByName(name) {
-    let app = appMenu.value.find((app) => app.name == name);
+    let app = appMenu.value.find((app) => app.composingKey == name);
     if (app && app.children) {
       return app.children;
     }
@@ -68,15 +68,15 @@ export function useApp() {
   const changeApp = (name) => {
     let selectedApp = getAppByName(name);
 
-    //1.如果是页面 则直接打开
-    if (selectedApp.type == 'page') {
+    //1.如果是页面 则直接打开 (type: '3' 表示页面)
+    if (selectedApp.type == '3') {
       appStore.changeApp(name);
 
       router.push({ name });
     }
 
-    //2.如果是菜单 找菜单组件然后弹出
-    if (selectedApp.type == 'menu') {
+    //2.如果是菜单或叶子菜单 找菜单组件然后弹出 (type: '1' 表示菜单, type: '2' 表示叶子菜单)
+    if (selectedApp.type == '1' || selectedApp.type == '2') {
       // 反选
       if (appName.value == name) {
         appStore.menuCollapse(true);

+ 24 - 17
src/sheep/layouts/menu/style/qianniu/index.vue

@@ -22,7 +22,7 @@
             popper-class="el-menu-tooltip"
           >
             <div class="el-menu-tooltip__trigger" @click="toView('admin.profile')">
-              <sa-icon :icon="account?.avatar" size="32" />
+              <img src="/static/images/shop/avatar.png" class="mr-4px w-40px h-40px rounded-full" />
             </div>
           </el-tooltip>
         </div>
@@ -83,7 +83,7 @@
 
   // 获取商城管理菜单数据
   const shopApp = computed(() => {
-    return appMenu.value?.find((app) => app.name === 'shop.admin') || { children: [] };
+    return appMenu.value?.find((app) => app.composingKey === 'shop.admin') || { children: [] };
   });
 
   const shop = reactive({
@@ -93,11 +93,11 @@
 
   function initShop(data, parent) {
     data?.forEach((i) => {
-      if (i?.name == route.name) {
+      if (i?.composingKey == route.name) {
         if (isEmpty(parent)) {
-          shop.name = i.name;
+          shop.name = i.composingKey;
         } else {
-          shop.name = parent.name;
+          shop.name = parent.composingKey;
           shop.childrenMenu = parent.children || [];
         }
       }
@@ -107,24 +107,24 @@
     });
   }
 
-  function onSelect(name) {
-    const menu = shopApp.value?.children?.find((m) => m.name == name);
+  function onSelect(composingKey) {
+    const menu = shopApp.value?.children?.find((m) => m.composingKey == composingKey);
     if (!menu || isEmpty(menu.children)) {
       shop.childrenMenu = [];
-      toView(name);
+      toView(composingKey);
     } else {
       shop.childrenMenu = menu.children;
     }
   }
 
-  async function toView(name) {
+  async function toView(composingKey) {
     let item = {};
     if (shopApp.value?.children) {
       loopType(shopApp.value.children);
     }
     function loopType(data) {
       data?.forEach((i) => {
-        if (i.name == name) {
+        if (i.composingKey == composingKey) {
           item = i;
         }
         if (!isEmpty(i.children)) {
@@ -132,18 +132,25 @@
         }
       });
     }
-    //todo-jj
+    // 根据新数据结构处理不同类型的菜单项
+    // type: '1' 菜单, '2' 叶子菜单, '3' 页面, '4' 动作
     if (item.type == 'modal') {
+      // 保留原有的 modal 类型处理
       let params = {
-        title: item.title,
+        title: item.label,
       };
-      if (item.params) params = { ...params, ...fromQuery(item.params) };
-      useModal(name, params);
+      if (item.url) params = { ...params, ...fromQuery(item.url) };
+      useModal(composingKey, params);
+    } else if (item.type == '4') {
+      // type: '4' 动作类型,可能需要特殊处理
+      // 这里可以根据具体需求添加动作处理逻辑
+      console.log('执行动作:', item.label, composingKey);
     } else {
-      if (name != route.name) {
+      // type: '1' 菜单, '2' 叶子菜单, '3' 页面 - 都进行路由跳转
+      if (composingKey != route.name) {
         router.push({
-          name: name,
-          query: item.params ? fromQuery(item.params) : {},
+          name: composingKey,
+          query: item.url ? fromQuery(item.url) : {},
         });
         appStore.menuCollapse(appBrowser.value.isMini);
       }

+ 3 - 3
src/sheep/layouts/menu/style/qianniu/menu-item.vue

@@ -1,8 +1,8 @@
 <template>
-  <el-menu-item :index="menuItem?.name" :style="itemStyle()">
-    <sa-icon class="sa-m-r-8" size="16" :icon="menuItem?.icon" />
+  <el-menu-item :index="menuItem?.composingKey" :style="itemStyle()">
+    <sa-icon class="sa-m-r-8" size="16" :icon="menuItem?.logo" />
     <template #title>
-      <span class="sa-line-1">{{ menuItem?.title }}</span>
+      <span class="sa-line-1">{{ menuItem?.label }}</span>
     </template>
   </el-menu-item>
 </template>

+ 43 - 4
src/sheep/layouts/taskbar/index.vue

@@ -42,10 +42,12 @@
       <el-dropdown @command="handleCommand" trigger="click">
         <div class="user-info sa-flex sa-row-center">
           <el-avatar :size="32" :src="userInfo.avatar" class="user-avatar">
-            {{ (userInfo.nickname || 'admin').charAt(0).toUpperCase() }}
+            {{ (userInfo.name || '暂无信息').charAt(0).toUpperCase() }}
           </el-avatar>
-          <span class="user-name mx-8px">{{ userInfo.nickname || 'admin' }}</span>
-          <el-tag type="info" plain size="small" class="mr-10px">管理员</el-tag>
+          <span class="user-name mx-8px">{{ userInfo.name || '暂无信息' }}</span>
+          <el-tag type="info" plain size="small" class="mr-10px">{{
+            userInfo.roleName || '暂无信息'
+          }}</el-tag>
           <el-icon class="dropdown-icon"><ArrowDown /></el-icon>
         </div>
         <template #dropdown>
@@ -54,6 +56,10 @@
               <el-icon><User /></el-icon>
               个人中心
             </el-dropdown-item>
+            <el-dropdown-item command="changePassword">
+              <el-icon><Lock /></el-icon>
+              修改密码
+            </el-dropdown-item>
             <el-dropdown-item command="logout" divided>
               <el-icon><SwitchButton /></el-icon>
               退出登录
@@ -80,8 +86,10 @@
   import sheep from '@/sheep';
   import { useRouter } from 'vue-router';
   import { closeTaskbar, closeOtherTaskbar } from '@/sheep/hooks/useTaskbar';
+  import { useModal } from '@/sheep/hooks';
   import { ElMessageBox, ElMessage } from 'element-plus';
-  import { ArrowDown, User, SwitchButton } from '@element-plus/icons-vue';
+  import { ArrowDown, User, SwitchButton, Lock } from '@element-plus/icons-vue';
+  import PasswordEdit from '@/app/admin/views/auth/admin/password.vue';
 
   const router = useRouter();
   const selectedCM = ref(0);
@@ -122,12 +130,43 @@
       case 'profile':
         router.push('/admin/profile');
         break;
+      case 'changePassword':
+        changePassword();
+        break;
       case 'logout':
         logout();
         break;
     }
   }
 
+  // 修改密码
+  function changePassword() {
+    // 检查用户ID是否为1
+    if (userInfo.value.id == 1) {
+      ElMessage.warning('超级管理员无法修改密码');
+      return;
+    }
+
+    useModal(
+      PasswordEdit,
+      {
+        title: '修改密码',
+        type: 'password',
+        id: userInfo.value.id,
+        name: userInfo.value.name,
+      },
+      {
+        confirm: () => {
+          // 密码修改成功后,调用注销方法退出登录
+          ElMessage.success('密码修改成功,请重新登录');
+          setTimeout(() => {
+            accountStore.logout();
+          }, 500); // 延迟1.5秒后退出登录,让用户看到成功提示
+        },
+      },
+    );
+  }
+
   // 退出登录
   function logout() {
     ElMessageBox.confirm('您确定要退出登录吗?', '退出登录', {

+ 18 - 5
src/sheep/local-data/admin.js

@@ -27,20 +27,33 @@ const loginConfigData = {
   },
 };
 
-// 菜单权限数据 - 二级菜单结构
+// 菜单权限数据 - 更新为新的数据结构
 const menuRulesData = {
   code: '200',
   msg: '获取成功',
   data: {
     menu: [
       {
+        id: 223,
+        name: 'shop.admin',
+        title: '商城管理',
+        label: '商城管理',
+        logo: 'ant-design:appstore-filled',
+        type: '1',
+        composingKey: 'shop.admin',
+        url: '',
+        seq: 0,
+        parentId: '',
+        createUserId: '',
+        updateUserId: 'sys',
+        createTime: '2025-07-23T03:41:25.000+0000',
+        updateTime: '2025-07-23T05:43:19.000+0000',
+        hasRelevance: null,
+        isAction: '0',
+        // 保持兼容性的旧字段
         type_text: '菜单',
         status_text: '显示',
-        id: 1,
         parent_id: 0,
-        name: 'shop.admin',
-        title: '商城管理',
-        type: 'menu',
         icon: '/static/img/shop/logo.png',
         params: '',
         weigh: 0,

+ 2 - 2
src/sheep/request/crud.js

@@ -28,7 +28,7 @@ export const ADD = (url, data) =>
     method: 'POST',
     data,
     options: {
-      showSuccessMessage: true,
+      showSuccessMessage: false,
     },
   });
 
@@ -39,7 +39,7 @@ export const EDIT = (url, data) =>
     method: 'POST',
     data,
     options: {
-      showSuccessMessage: true,
+      showSuccessMessage: false,
     },
   });
 

+ 32 - 18
src/sheep/request/index.js

@@ -17,6 +17,25 @@ const options = {
   // ...
 };
 
+/**
+ * @description 认证失败的错误码列表
+ */
+const AUTH_FAILURE_CODES = ['401'];
+
+/**
+ * @description 处理认证失败的统一逻辑
+ */
+const handleAuthFailure = (errorData) => {
+  const errorMessage = '未授权,请重新登录';
+  if (document.getElementsByClassName('el-message-box').length === 0) {
+    ElMessageBox.alert('认证失败,请重新登录', '认证失败', {
+      confirmButtonText: '重新登陆',
+    }).then(() => {
+      $store('account').logout(true);
+    });
+  }
+};
+
 /**
  * @description axios请求基础配置 可直接使用访问自定义请求
  */
@@ -37,10 +56,8 @@ export const request = axios.create({
 request.interceptors.request.use(
   (config) => {
     $store('app').setRequestCounter(1);
-    // 测试token
-    const token = `Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0eXBlIjoiYWRtaW4iLCJ1aWQiOjEsImF1ZCI6IiIsImV4cCI6MTc1MTk0NDQ3MiwiaWF0IjoxNzUxODU4MDcyLCJpc3MiOiIiLCJqdGkiOiI2YTIxMzk4NzYzOTliYzY5NTViY2Y5ZTcxOGQ5ODZjYyIsIm5iZiI6MTc1MTg1ODA3Miwic3ViIjoiIn0.-1wdpM_1uZq33YpaDUamtF-MY4c5FhZOItMp5CUTPvg`;
-    // const token = $storage.get('token');
-    if (token) config.headers['Authorization'] = token;
+    const token = $storage.get('token');
+    if (token) config.headers['Accesstoken'] = token;
     return config;
   },
   (error) => {
@@ -56,19 +73,24 @@ request.interceptors.response.use(
   (response) => {
     $store('app').setRequestCounter(-1);
     // 自动刷新令牌
-    if (response.headers.authorization) {
-      $store('account').setToken(response.headers.authorization);
+    if (response.headers.Accesstoken) {
+      $store('account').setToken(response.headers.Accesstoken);
     }
     // 导出&下载文件
     if (response.config.responseType === 'blob' && response.data.size > 0) {
       return Promise.resolve(response);
     }
     if (response.data.code !== '200') {
+      // 处理认证失败
+      if (AUTH_FAILURE_CODES.includes(response.data.code)) {
+        handleAuthFailure(response.data);
+        return Promise.reject(response);
+      }
       if (response.config.options.showErrorMessage)
-        ElMessage.error(response.data.msg || '操作失败');
+        ElMessage.error(response.data.message || '操作失败');
     } else {
       response.config.options.showSuccessMessage &&
-        ElMessage.success(response.data.msg || '操作成功');
+        ElMessage.success(response.data.message || '操作成功');
     }
     return Promise.resolve(response.data);
   },
@@ -81,16 +103,8 @@ request.interceptors.response.use(
           errorMessage = '请求错误';
           break;
         case 401:
-          errorMessage = '未授权,请重新登录';
-          if (document.getElementsByClassName('el-message-box').length === 0) {
-            ElMessageBox.alert(error.response.data.msg || errorMessage, '认证失败', {
-              confirmButtonText: '重新登陆',
-            }).then(() => {
-              $store('account').logout(true);
-            });
-          }
+          handleAuthFailure(error.response.data);
           return Promise.reject(error.response);
-          break;
         case 403:
           errorMessage = '拒绝访问';
           break;
@@ -122,7 +136,7 @@ request.interceptors.response.use(
           errorMessage = 'HTTP版本不受支持';
           break;
       }
-      ElMessage.error(error.response.data.msg || errorMessage);
+      ElMessage.error(error.response.data.message || errorMessage);
     } else {
       ElMessage.error(errorMessage);
     }

+ 12 - 11
src/sheep/views/login/index.vue

@@ -29,10 +29,10 @@
                   </template>
                 </el-input>
               </el-form-item>
-              <el-form-item prop="password">
+              <el-form-item prop="pwd">
                 <el-input
-                  :class="form.data.password ? 'is-focus' : ''"
-                  v-model="form.data.password"
+                  :class="form.data.pwd ? 'is-focus' : ''"
+                  v-model="form.data.pwd"
                   :type="showPwd ? 'password' : 'text'"
                   autocomplete="new-password"
                 >
@@ -82,10 +82,10 @@
                   {{ login.last.info.nickname }}
                 </div>
               </el-form-item>
-              <el-form-item prop="password">
+              <el-form-item prop="pwd">
                 <el-input
-                  :class="form.data.password ? 'is-focus' : ''"
-                  v-model="form.data.password"
+                  :class="form.data.pwd ? 'is-focus' : ''"
+                  v-model="form.data.pwd"
                   :type="showPwd ? 'password' : 'text'"
                   autocomplete="new-password"
                 >
@@ -124,12 +124,13 @@
   import storage from '@/sheep/utils/storage';
   import sheep from '@/sheep';
   import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
+  import { ElMessage } from 'element-plus';
 
   const accountStore = sheep.$store('account');
   const appStore = sheep.$store('app');
   const routeQuery = useRoute().query;
   if (routeQuery.token) {
-    accountStore.setToken(`Bearer ${routeQuery.token}`);
+    accountStore.setToken(`${routeQuery.token}`);
   }
   const appName = computed(() => appStore.info.name);
 
@@ -141,11 +142,11 @@
   const form = reactive({
     data: {
       account: '',
-      password: '',
+      pwd: '',
     },
     rules: {
       account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
-      password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
+      pwd: [{ required: true, message: '请输入密码', trigger: 'blur' }],
     },
   });
 
@@ -157,9 +158,9 @@
       loginLoading.value = true;
       let submit = form.data;
       const { code } = await accountStore.login(submit);
-      console.log(error);
       loginLoading.value = false;
       if (code == '200') {
+        ElMessage.success('登录成功');
         storage.set('lastLogin', login.last);
         await appStore.appLoad();
         router.push('/');
@@ -188,7 +189,7 @@
     login.type = type;
     if (type == 'input') {
       form.data.account = '';
-      form.data.password = '';
+      form.data.pwd = '';
       initLogin();
     }
   }