Explorar o código

feat:完善用户管理及按钮权限

lizhi hai 4 meses
pai
achega
2c94980140

+ 11 - 0
protected/components/DB.php

@@ -173,6 +173,17 @@ class DB
         return true;
     }
 
+    public static function deleteById($tableName, $filters, $id)
+    {
+        $id = intval($id);
+        if ($id <= 0) {
+            return false;
+        }
+        $db = self::getDbCommand();
+        $db->delete(self::formTableName($tableName), "id=:id", [':id' => $id]);
+        return true;
+    }
+
     public static function formTableName($tableName)
     {
         return 'wx_' . trim($tableName, 'wx_');

+ 36 - 6
protected/controllers/UseradminController.php

@@ -26,7 +26,7 @@ class UseradminController extends Controller
             return (int)$item;
         }, $authIds);
         Helper::ok([
-            'userId' => $model->id,
+            'id' => $model->id,
             'username' => $model->username,
             'auth_ids' => $authIds,
             'buttons' => [],
@@ -59,14 +59,31 @@ class UseradminController extends Controller
         Helper::ok($data);
     }
 
+    /**
+     * 角色下拉列表获取
+     * @return void
+     */
+    public function actionGetRoleSelect()
+    {
+        $cri = DbCriteria::simpleCompare([])->setSelect('id, name');
+        $data = DB::getListWithCriteria('role', $cri);
+        Helper::ok($data['records']??[]);
+    }
+
     public function actionUserList()
     {
         $name = Helper::getPostString('name');
         $name = $name ? '%' . $name : null;
-        $cri = DbCriteria::simpleCompareWithPage(['username' => $name])
+        $filters = [
+            'username' => $name,
+            'u.id' => '!=1',
+            'role_id' => Helper::getPostInt('role_id')?:null,
+            'phone' => Helper::getPostString('phone')?:null,
+        ];
+        $cri = DbCriteria::simpleCompareWithPage($filters)
             ->setAlias('u')
             ->setDebugUntil('234', '-1')
-            ->setSelect('u.id, u.username, r.name as role_name, u.is_using, u.phone, u.create_date, u.avatar, u.update_date')
+            ->setSelect('u.id, u.username, r.name as role_name, u.is_using, u.sex, u.phone, u.create_date, u.avatar, u.update_date')
             ->setJoin('left join wx_role r on u.role_id = r.id');
         $data = DB::getListWithCriteria('useradmin', $cri);
         if (!empty($data['records'])) {
@@ -110,8 +127,8 @@ class UseradminController extends Controller
         if ($id > 0) {
             $cri->addCondition('id!=' . $id);
         }
-        if ($id = DB::getScalerWithCriteria('useradmin', $cri)) {
-            Helper::error('用户名已存在 ' . $id);
+        if ($fid = DB::getScalerWithCriteria('useradmin', $cri)) {
+            Helper::error('用户名已存在 ' . $fid);
         }
         $info = [
             'username' => $username,
@@ -127,7 +144,7 @@ class UseradminController extends Controller
                 Helper::error('请选择角色');
             }
             $info['password'] = md5($password);
-            $info['roe_id'] = $role_id;
+            $info['role_id'] = $role_id;
             DB::addData('useradmin', $info);
         } else {
             DB::updateById('useradmin', $info, $id);
@@ -137,7 +154,20 @@ class UseradminController extends Controller
 
     public function actionDeleteUser()
     {
+        $id = Helper::getPostInt('id');
+        if ($id < 1) {
+            Helper::error('参数错误');
+        }
+        DB::deleteById('useradmin', $id);
+    }
 
+    public function actionDeleteRole()
+    {
+        $id = Helper::getPostInt('id');
+        if ($id < 1) {
+            Helper::error('参数错误');
+        }
+        DB::deleteById('role', $id);
     }
 	
 	public function actionCheckpwd(){

+ 1 - 1
protected/include/DbCriteria.php

@@ -70,7 +70,7 @@ class DbCriteria extends \CDbCriteria
             $value = "$value";
         }
 
-        if (preg_match('/^(?:\s*(<>|<=|>=|<|>|=|%))?(.*)$/', $value, $matches)) {
+        if (preg_match('/^(?:\s*(<>|<=|>=|<|>|=|%|\!=))?(.*)$/', $value, $matches)) {
             $value = $matches[2];
             $op = $matches[1];
         } else {

+ 8 - 0
protected/include/Helper.php

@@ -471,4 +471,12 @@ class Helper
         return $ret;
     }
 
+    /**
+     * array_column 一样用法
+     */
+    public static function arrayColumn($array, $column_key, $index_key = null): array
+    {
+        return $array && $array['datas'] ? array_column($array['datas'], $column_key, $index_key) : [];
+    }
+
 }

+ 7 - 6
protected/include/Lewaimai/LewaimaiAdminPingtaiAuth.php

@@ -20,6 +20,7 @@ class LewaimaiAdminPingtaiAuth
      */
     public static array $noAuthCheckRouters = [
         'useradmin/info', // 用户信息
+        'useradmin/getroleselect', // 角色下拉列表
         'useradmin/setting', // 密码修改
         'useradmin/checkpwd' // 密码修改检测
     ];
@@ -67,12 +68,12 @@ class LewaimaiAdminPingtaiAuth
 
             // ===================   用户及角色管理  =======================
             'useradmin/rolelist' => 1102, // 角色列表
-            'useradmin/saveroleauth' => 110201,
-            'useradmin/editrole' => 110202,
-            'useradmin/deleterole' => 110203,
-            'useradmin/userlist' => 1101,
-            'useradmin/edituser' => 110101,
-            'useradmin/deleteuser' => 110102,
+            'useradmin/saveroleauth' => 110201, // 菜单权限
+            'useradmin/editrole' => 110202, // 编辑角色
+            'useradmin/deleterole' => 110203, // 删除角色
+            'useradmin/userlist' => 1101, // 用户列表
+            'useradmin/edituser' => 110101, // 编辑用户
+            'useradmin/deleteuser' => 110102, // 删除用户
 
         ];
 

+ 12 - 4
web/src/api/roleApi.ts

@@ -4,17 +4,25 @@ export class roleService {
   // 角色列表
   static roleList(params?: Api.Common.PaginatingSearchParams) {
     return request.post<Api.Role.RoleListResponse>({
-      url: 'useradmin/rolelist',
-      params
+      url: 'useradmin/roleList',
+      params,
+      showErrorMessage: false // 不显示错误消息
+    })
+  }
+  // 角色下拉列表
+  static roleSelectList() {
+    return request.post<Api.Common.SelectInfo[]>({
+      url: 'useradmin/getRoleSelect',
       // showErrorMessage: false // 不显示错误消息
     })
   }
+  // 角色权限设置
   static saveRoleAuth(id:number, leaf_ids: number[], half_Leaf_ids: number[]) {
     const params = {id, leaf_ids, half_Leaf_ids}
     return request.post<Api.Http.BaseResponse>({
       url: 'useradmin/saveRoleAuth',
-      params
-      // showErrorMessage: false // 不显示错误消息
+      params,
+      showErrorMessage: false // 不显示错误消息
     })
   }
 }

+ 4 - 3
web/src/api/usersApi.ts

@@ -25,13 +25,14 @@ export class UserService {
   static getUserList(params: Api.Common.PaginatingSearchParams) {
     return request.post<Api.User.UserListData>({
       url: 'useradmin/userlist',
-      params
+      params,
+      // showErrorMessage: false // 不显示错误消息
     })
   }
 
   // 编辑用户
   static editUser(params: Api.User.UserInfo) {
-    return request.post<Api.Http.BaseResponse>({
+    return request.post<any>({
       url: 'useradmin/edituser',
       params
     })
@@ -39,7 +40,7 @@ export class UserService {
 
   //删除用户
   static deleteUser(params: Api.Common.DeleteParams) {
-    return request.post<Api.Http.BaseResponse>({
+    return request.post<any>({
       url: 'useradmin/deleteuser',
       params
     })

+ 5 - 5
web/src/components/core/forms/art-button-more/index.vue

@@ -7,7 +7,7 @@
         <ElDropdownMenu>
           <template v-for="item in list" :key="item.key">
             <ElDropdownItem
-              v-if="!item.auth || hasAuth(item.auth)"
+              v-if="!item.authId || useUserStore().checkAuth(item.authId)"
               :disabled="item.disabled"
               @click="handleClick(item)"
             >
@@ -21,12 +21,10 @@
 </template>
 
 <script setup lang="ts">
-  import { useAuth } from '@/composables/useAuth'
+  import {useUserStore} from "@/store/modules/user";
 
   defineOptions({ name: 'ArtButtonMore' })
 
-  const { hasAuth } = useAuth()
-
   export interface ButtonMoreItem {
     /** 按钮标识,可用于点击事件 */
     key: string | number
@@ -36,6 +34,7 @@
     disabled?: boolean
     /** 权限标识 */
     auth?: string
+    authId?: number
   }
 
   interface Props {
@@ -43,6 +42,7 @@
     list: ButtonMoreItem[]
     /** 整体权限控制 */
     auth?: string
+    authId?: number
     /** 是否显示背景 */
     hasBackground?: boolean
   }
@@ -53,7 +53,7 @@
 
   // 检查是否有任何有权限的 item
   const hasAnyAuthItem = computed(() => {
-    return props.list.some((item) => !item.auth || hasAuth(item.auth))
+    return props.list.some((item) => !item.authId || useUserStore().checkAuth(item.authId))
   })
 
   const emit = defineEmits<{

+ 2 - 1
web/src/composables/useHeaderBar.ts

@@ -8,6 +8,7 @@ import { storeToRefs } from 'pinia'
 import { useSettingStore } from '@/store/modules/setting'
 import { headerBarConfig } from '@/config/headerBar'
 import { HeaderBarFeatureConfig } from '@/types'
+import {useUserStore} from "@/store/modules/user";
 
 /**
  * 顶部栏功能管理
@@ -88,7 +89,7 @@ export function useHeaderBar() {
 
   // 检查设置面板是否显示
   const shouldShowSettings = computed(() => {
-    return isFeatureEnabled('settings')
+    return isFeatureEnabled('settings') && useUserStore().isSuperAdmin
   })
 
   // 检查主题切换是否显示

+ 1 - 1
web/src/directives/auth.ts

@@ -20,7 +20,7 @@ function checkAuthPermission(el: HTMLElement, binding: AuthBinding): void {
   // const hasPermission = authList.some((item) => item.authMark === binding.value)
 
   // 如果没有权限,移除元素
-  if (useUserStore().checkAuth(binding.value)) {
+  if (!useUserStore().checkAuth(binding.value)) {
     removeElement(el)
   }
 }

+ 1 - 4
web/src/router/guards/beforeEach.ts

@@ -229,14 +229,11 @@ async function processBackendMenu(router: Router): Promise<void> {
   const menuList = asyncRoutes.map((route) => menuDataToRouter(route))
   const userStore = useUserStore()
   const authIds = userStore.info.auth_ids
-  console.log(`%c authIds == `, 'background:#41b883 ; padding:1px; color:#fff', authIds);
   if (!authIds) {
-    console.log(`%c userStore.info == `, 'background:#41b883 ; padding:1px; color:#fff', userStore.info);
     throw new Error('获取用户权限失败')
   }
 
-  const filteredMenuList = filterMenuByAuthIds(menuList, authIds)
-  console.log(`%c filteredMenuList == `, 'background:#41b883 ; padding:1px; color:#fff', filteredMenuList);
+  const filteredMenuList = userStore.isSuperAdmin() ? menuList : filterMenuByAuthIds(menuList, authIds)
   // 添加延时以提升用户体验
   await new Promise((resolve) => setTimeout(resolve, LOADING_DELAY))
 

+ 5 - 0
web/src/store/modules/user.ts

@@ -106,6 +106,10 @@ export const useUserStore = defineStore(
       return info.value.auth_ids?.includes(authId)
     }
 
+    const isSuperAdmin = () => {
+      return info.value.id == 1
+    }
+
     /**
      * 退出登录
      * 清空所有用户相关状态并跳转到登录页
@@ -138,6 +142,7 @@ export const useUserStore = defineStore(
     return {
       language,
       isLogin,
+      isSuperAdmin,
       isLock,
       lockPassword,
       info,

+ 5 - 0
web/src/typings/api.d.ts

@@ -39,6 +39,11 @@ declare namespace Api {
       reason?: string
     }
 
+    interface SelectInfo {
+      id: number
+      name: string
+    }
+
     /** 启用状态 */
     type EnableStatus = '1' | '2'
   }

+ 1 - 1
web/src/utils/sys/console.ts

@@ -10,4 +10,4 @@ const asciiArt = `
 \x1b[0m
 `
 
-console.log(asciiArt)
+// console.log(asciiArt)

+ 1 - 1
web/src/utils/table/tableUtils.ts

@@ -111,7 +111,7 @@ export const defaultResponseAdapter = <T>(response: unknown): ApiResponse<T> =>
 
   // 如果还是没有找到,使用兜底
   if (records.length === 0) {
-    console.warn('[tableUtils] 无法识别的响应格式:', response)
+    return { records: [], total: 0 }
   }
 
   const result: ApiResponse<T> = { records, total }

+ 4 - 4
web/src/views/system/role/index.vue

@@ -10,7 +10,7 @@
         <ElCol :xs="24" :sm="12" :lg="6">
           <ElFormItem>
             <ElButton v-ripple>搜索</ElButton>
-            <ElButton @click="showDialog('add')" v-ripple>新增角色</ElButton>
+            <ElButton @click="showDialog('add')" v-ripple v-auth="110202">新增角色</ElButton>
           </ElFormItem>
         </ElCol>
       </ElRow>
@@ -37,9 +37,9 @@
               <!-- 可以在 list 中添加 auth 属性来控制按钮的权限, auth 属性值为权限标识 -->
               <ArtButtonMore
                 :list="[
-                  { key: 'permission', label: '菜单权限' },
-                  { key: 'edit', label: '编辑角色' },
-                  { key: 'delete', label: '删除角色' }
+                  { key: 'permission', label: '菜单权限', authId: 110201 },
+                  { key: 'edit', label: '编辑角色', authId: 110202 },
+                  { key: 'delete', label: '删除角色', authId: 110203 }
                 ]"
                 @click="buttonMoreClick($event, scope.row)"
               />

+ 18 - 24
web/src/views/system/user/index.vue

@@ -5,13 +5,13 @@
 <template>
   <div class="user-page art-full-height">
     <!-- 搜索栏 -->
-    <UserSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams"></UserSearch>
+    <UserSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams" :role-list="roleList"></UserSearch>
 
     <ElCard class="art-table-card" shadow="never">
       <!-- 表格头部 -->
       <ArtTableHeader v-model:columns="columnChecks" @refresh="refreshData">
         <template #left>
-          <ElButton @click="showDialog('add')" v-ripple>新增用户</ElButton>
+          <ElButton @click="showDialog('add')" v-ripple v-auth="110101">新增用户</ElButton>
         </template>
       </ArtTableHeader>
 
@@ -66,10 +66,8 @@
   // 搜索表单
   const searchForm = ref({
     name: '',
-    level: 'vip',
-    date: '',
-    daterange: '',
-    status: ''
+    phone: '',
+    role_id: ''
   })
 
   // 用户状态配置
@@ -90,14 +88,12 @@
     )
   }
 
-  const roleList = ref<Api.Role.RoleInfo[]>([])
+  const roleList = ref<Api.Common.SelectInfo[]>([])
   const getRoleList = async () => {
-    const { records } = await roleService.roleList()
-    roleList.value = records
-  }
-  if (useUserStore().checkAuth(1102)) {
-    getRoleList()
+    const data = await roleService.roleSelectList()
+    roleList.value = data
   }
+  getRoleList()
 
   const {
     columns,
@@ -123,9 +119,11 @@
       // 排除 apiParams 中的属性
       excludeParams: ['daterange'],
       columnsFactory: () => [
-        { type: 'selection' }, // 勾选列
-        { prop: 'id', width: 40, label: 'ID' }, // 序号
+        // { type: 'selection' }, // 勾选列
+        // { prop: 'id', width: 40, label: 'ID' }, // 序号
         { prop: 'username', label: '用户名' },
+        { prop: 'role_name', label: '角色' },
+        { prop: 'phone', label: '手机号' },
         {
           prop: 'sex',
           label: '性别',
@@ -133,8 +131,6 @@
           // checked: false, // 隐藏列
           formatter: (row) => row.sex ? '女' : '男'
         },
-        { prop: 'phone', label: '手机号' },
-        { prop: 'role_name', label: '角色' },
         {
           prop: 'is_using',
           label: '状态',
@@ -193,12 +189,7 @@
    * @param params 参数
    */
   const handleSearch = (params: Record<string, any>) => {
-    // 处理日期区间参数,把 daterange 转换为 startTime 和 endTime
-    const { daterange, ...filtersParams } = params
-    const [startTime, endTime] = Array.isArray(daterange) ? daterange : [null, null]
-
-    // 搜索参数赋值
-    Object.assign(searchParams, { ...filtersParams, startTime, endTime })
+    Object.assign(searchParams, { ...params })
     getData()
   }
 
@@ -206,7 +197,6 @@
    * 显示用户弹窗
    */
   const showDialog = (type: Form.DialogType, row?: UserListItem): void => {
-    console.log('打开弹窗:', { type, row })
     dialogType.value = type
     currentUserData.value = row || {}
     nextTick(() => {
@@ -224,7 +214,7 @@
       cancelButtonText: '取消',
       type: 'error'
     }).then(() => {
-      ElMessage.success('注销成功')
+      UserService.deleteUser({id: row.id})
     })
   }
 
@@ -235,6 +225,10 @@
     try {
       dialogVisible.value = false
       currentUserData.value = {}
+      // 延迟更新 不然数据可能没更新
+      setTimeout(() => {
+        refreshData()
+      }, 1000)
     } catch (error) {
       console.error('提交失败:', error)
     }

+ 5 - 14
web/src/views/system/user/modules/user-dialog.vue

@@ -51,7 +51,7 @@
     visible: boolean
     type: string
     userData?: any
-    roleList: Api.Role.RoleInfo[]
+    roleList: Api.Common.SelectInfo[]
   }
 
   interface Emits {
@@ -105,7 +105,7 @@
     Object.assign(formData, {
       username: isEdit ? row.username || '' : '',
       phone: isEdit ? row.phone || '' : '',
-      gender: isEdit ? (row.sex ? '女' : '男') : '男',
+      sex: isEdit ? (row.sex ? '女' : '男') : '男',
       role_name: isEdit ? row.role_name || '' : ''
     })
   }
@@ -133,24 +133,15 @@
         let userData:Api.User.UserInfo = {
           id: props.userData.id,
           username: formData.username,
-          password: isEdit ? '' : formData.password,
+          password: formData.password,
           phone: formData.phone,
-          sex: formData.sex === '男' ? 0 : 1,
+          sex: parseInt(formData.sex),
           role_id: props.roleList.find((item) => item.name === formData.role_name)?.id || 0
         }
-        saveRoleAuth(userData)
+        UserService.editUser(userData)
         dialogVisible.value = false
         emit('submit')
       }
     })
   }
-
-  const saveRoleAuth = async (userData: Api.User.UserInfo) => {
-    const {code, msg} = await UserService.editUser(userData)
-    if (code !== 200) {
-      ElMessage.error(msg)
-    } else {
-      ElMessage.success(msg)
-    }
-  }
 </script>

+ 8 - 135
web/src/views/system/user/modules/user-search.vue

@@ -7,19 +7,15 @@
     @reset="handleReset"
     @search="handleSearch"
   >
-    <template #email>
-      <ElInput v-model="formData.email" placeholder="我是插槽渲染出来的组件" />
-    </template>
   </ArtSearchBar>
 </template>
 
 <script setup lang="ts">
   import { ref, computed, onMounted, h } from 'vue'
-  import ArtIconSelector from '@/components/core/base/art-icon-selector/index.vue'
-  import { IconTypeEnum } from '@/enums/appEnum'
 
   interface Props {
-    modelValue: Record<string, any>
+    modelValue: Record<string, any>,
+    roleList: Api.Common.SelectInfo[]
   }
   interface Emits {
     (e: 'update:modelValue', value: Record<string, any>): void
@@ -44,24 +40,6 @@
   // 动态 options
   const levelOptions = ref<{ label: string; value: string; disabled?: boolean }[]>([])
 
-  // 模拟接口返回用户等级
-  function fetchLevelOptions(): Promise<typeof levelOptions.value> {
-    return new Promise((resolve) => {
-      setTimeout(() => {
-        resolve([
-          { label: '普通用户', value: 'normal' },
-          { label: 'VIP用户', value: 'vip' },
-          { label: '高级VIP', value: 'svip' },
-          { label: '企业用户', value: 'enterprise', disabled: true }
-        ])
-      }, 1000)
-    })
-  }
-
-  onMounted(async () => {
-    levelOptions.value = await fetchLevelOptions()
-  })
-
   // 表单配置
   const formItems = computed(() => [
     { label: '用户名', key: 'name', type: 'input', placeholder: '请输入用户名', clearable: true },
@@ -72,118 +50,13 @@
       props: { placeholder: '请输入手机号', maxlength: '11' }
     },
     {
-      label: '用户等级',
-      key: 'level',
+      label: '角色',
+      key: 'role_id',
       type: 'select',
-      props: { placeholder: '请选择等级', options: levelOptions.value }
-    },
-    { label: '地址', key: 'address', type: 'input', placeholder: '请输入地址' },
-    {
-      label: '日期',
-      key: 'date',
-      type: 'datetime',
-      props: {
-        style: { width: '100%' },
-        placeholder: '请选择日期',
-        type: 'date',
-        valueFormat: 'YYYY-MM-DD',
-        shortcuts: [
-          { text: '今日', value: new Date() },
-          { text: '昨日', value: () => new Date(Date.now() - 86400000) },
-          { text: '一周前', value: () => new Date(Date.now() - 604800000) }
-        ]
-      }
-    },
-    {
-      label: '日期范围',
-      key: 'daterange',
-      type: 'datetime',
-      props: {
-        type: 'daterange',
-        valueFormat: 'YYYY-MM-DD',
-        rangeSeparator: '至',
-        startPlaceholder: '开始日期',
-        endPlaceholder: '结束日期'
-      }
-    },
-    {
-      label: '级联选择',
-      key: 'cascader',
-      type: 'cascader',
-      props: {
-        placeholder: '请选择级联选择器',
-        clearable: true,
-        style: { width: '100%' },
-        collapseTags: true,
-        maxCollapseTags: 1,
-        props: { multiple: true },
-        options: [
-          {
-            value: 'guide',
-            label: '指南',
-            children: [
-              {
-                value: 'disciplines',
-                label: '规范',
-                children: [
-                  { value: 'consistency', label: '一致性' },
-                  { value: 'feedback', label: '反馈' },
-                  { value: 'efficiency', label: '效率' },
-                  { value: 'controllability', label: '可控性' }
-                ]
-              }
-            ]
-          },
-          {
-            value: 'components',
-            label: '组件',
-            children: [
-              {
-                value: 'basic',
-                label: '基础组件',
-                children: [
-                  { value: 'button', label: '按钮' },
-                  { value: 'form', label: '表单' },
-                  { value: 'table', label: '表格' }
-                ]
-              }
-            ]
-          }
-        ]
-      }
-    },
-    { label: '插槽', key: 'email', type: 'input', placeholder: '请输入邮箱' },
-    {
-      label: '渲染组件',
-      key: 'iconSelector',
-      type: () => h(ArtIconSelector, { iconType: IconTypeEnum.UNICODE, width: '100%' }),
-      props: { placeholder: '请输入备注', type: 'textarea', rows: 4 }
-    },
-    {
-      label: '栅格示例',
-      key: 'checkboxgroup',
-      type: 'checkboxgroup',
-      span: 12,
-      props: {
-        options: [
-          { label: '选项1', value: 'option1' },
-          { label: '选项2', value: 'option2' },
-          { label: '选项3', value: 'option3' },
-          { label: '选项4', value: 'option4' },
-          { label: '选项5(disabled)', value: 'option5', disabled: true }
-        ]
-      }
-    },
-    {
-      label: '性别',
-      key: 'userGender',
-      type: 'radiogroup',
-      props: {
-        options: [
-          { label: '男', value: '1' },
-          { label: '女', value: '2' }
-        ]
-      }
+      props: { placeholder: '请选择角色', options: props.roleList.map(item => ({
+          label: item.name,
+          value: item.id
+        })) }
     }
   ])