aexiaoliou преди 1 година
родител
ревизия
e3e76baf90

+ 32 - 2
api/app/admin/attr/Permission.php

@@ -6,15 +6,45 @@ use Attribute;
 #[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
 class Permission
 {
+    /**
+     * 权限值
+     */
     public $value;
 
     /**
+     * 仅对方法生效!是否忽略权限要求
+     */
+    public $ignore;
+
+    /**
+     * 继承controller的权限, 例如controller设置了权限值'admin', 方法设置了权限值'create', 那么实际权限值为'admin.create'
+     * 如果在controller上设为true,那么当方法没有设置该注解时,而且useMethodName设置为了true,会自动使用方法名作为继承值
+     * 如果在controller上设为false,只使用控制器的权限值
+     */
+    public $inherit;
+
+    /**
+     * 仅对controller生效!
+     * true: 权限值为空时使用方法名作为权限值
+     * false: 权限值为空时,使用控制器名小写作为权限值
+     * @see \InvalidArgumentException
+     */
+    public $useMethodName;
+
+    /**
      * __construct
      *
-     * @param string $value 需要的权限
+     * @param string $value 权限值
+     * @param bool $ignore 仅对方法生效!是否忽略权限要求
+     * @param bool $name 继承controller的权限, 例如controller设置了权限值'admin', 方法设置了权限值'create', 那么实际权限值为'admin.create'
+     * @param bool $useMethodName 仅对controller生效!true: 权限值为空时使用方法名作为权限值; false: 权限值为空时,使用控制器名小写作为权限值
+     * @see \InvalidArgumentException
      */
-    public function __construct($value)
+    public function __construct($value = '', $ignore = false, $inherit = true, $useMethodName = true)
     {
         $this->value = $value;
+        $this->ignore = $ignore;
+        $this->inherit = $inherit;
+        $this->useMethodName = $useMethodName;
     }
 }

+ 10 - 1
api/app/admin/controller/Admin.php

@@ -1,11 +1,14 @@
 <?php
 
 namespace app\admin\controller;
+use app\admin\attr\Permission;
 use app\common\model\Admin as AdminModel;
 use app\common\util\Result;
 
+#[Permission('admin')]
 class Admin extends BaseAuthorized
 {
+    #[Permission(ignore: true)]
     public function init()
     {
         $roleList = \app\common\model\Role::field("id,name,valid,remark")->order("name asc ,id desc")->select();
@@ -15,12 +18,14 @@ class Admin extends BaseAuthorized
         return $this->success($result);
     }
 
+    #[Permission('read')]
     public function fullList()
     {
         $list = (new AdminModel)->select();
         return Result::rest($list);
     }
 
+    #[Permission('read')]
     public function list()
     {
         //第1段:校验输入
@@ -38,7 +43,7 @@ class Admin extends BaseAuthorized
         return $this->success($res["data"]);
     }
 
-
+    #[Permission('create')]
     public function add()
     {
         //第1段:校验输入
@@ -60,6 +65,7 @@ class Admin extends BaseAuthorized
         return $this->success($res["data"], "新增成功");
     }
 
+    #[Permission('update')]
     public function edit()
     {
         //第1段:校验输入
@@ -84,6 +90,7 @@ class Admin extends BaseAuthorized
     /**
      * 删除
      */
+    #[Permission('delete')]
     public function delete()
     {
         $param = request()->param();
@@ -98,6 +105,7 @@ class Admin extends BaseAuthorized
         return $this->success($res["data"]);
     }
 
+    #[Permission('read')]
     public function detail()
     {
         $param = request()->param();
@@ -112,6 +120,7 @@ class Admin extends BaseAuthorized
         return $this->success($admin);
     }
 
+    #[Permission('update')]
     public function resetPwd()
     {
         $param = request()->param();

+ 11 - 2
api/app/admin/controller/Project.php

@@ -15,7 +15,7 @@ class Project extends BaseAuthorized
     /*
      * 基础接口 
      */
-
+    #[Permission('read')]
     public function page()
     {
         $res = $this->ProjectService()->page();
@@ -43,8 +43,8 @@ class Project extends BaseAuthorized
     /*
     * 状态相关接口
     */
-
     #[Get('status/list')]
+    #[Permission(ignore: true)]
     public function listStatus()
     {
         $res = $this->ProjectStatusService()->list();
@@ -52,6 +52,7 @@ class Project extends BaseAuthorized
     }
 
     #[Post('status/create')]
+    #[Permission('status.create')]
     public function createStatus()
     {
         $res = $this->ProjectStatusService()->create();
@@ -59,6 +60,7 @@ class Project extends BaseAuthorized
     }
 
     #[Post('status/update')]
+    #[Permission('status.update')]
     public function updateStatus()
     {
         $res = $this->ProjectStatusService()->update();
@@ -70,6 +72,7 @@ class Project extends BaseAuthorized
     */
 
     #[Post('schedule/create')]
+    #[Permission('schedule.create')]
     public function createSchedule()
     {
         $res = $this->ProjectScheduleService()->create();
@@ -77,6 +80,7 @@ class Project extends BaseAuthorized
     }
 
     #[Post('schedule/update')]
+    #[Permission('schedule.update')]
     public function updateSchedule()
     {
         $res = $this->ProjectScheduleService()->update();
@@ -84,6 +88,7 @@ class Project extends BaseAuthorized
     }
 
     #[Post('schedule/delete')]
+    #[Permission('schedule.delete')]
     public function deleteSchedule()
     {
         $res = $this->ProjectScheduleService()->delete();
@@ -95,6 +100,7 @@ class Project extends BaseAuthorized
      * 合同相关接口
      */
     #[Post('contract/page')]
+    #[Permission('contract.read')]
     public function pageContranct()
     {
         $res = $this->ContractService()->page();
@@ -102,6 +108,7 @@ class Project extends BaseAuthorized
     }
 
     #[Post('contract/create')]
+    #[Permission('contract.read')]
     public function createContract()
     {
         $res = $this->ContractService()->create();
@@ -109,6 +116,7 @@ class Project extends BaseAuthorized
     }
 
     #[Post('contract/update')]
+    #[Permission('contract.update')]
     public function updateContract()
     {
         $res = $this->ContractService()->update();
@@ -116,6 +124,7 @@ class Project extends BaseAuthorized
     }
 
     #[Post('contract/delete')]
+    #[Permission('contract.delete')]
     public function deleteContract()
     {
         $res = $this->ContractService()->delete();

+ 2 - 0
api/app/admin/controller/Role.php

@@ -7,6 +7,7 @@ use app\common\util\Result;
 #[Permission('role')]
 class Role extends BaseAuthorized
 {
+    #[Permission('read')]
     public function list()
     {
         $res = $this->RoleService()->list();
@@ -25,6 +26,7 @@ class Role extends BaseAuthorized
         return Result::rest($res);
     }
 
+    #[Permission(ignore: true)]
     public function codes()
     {
         $res = $this->RoleService()->codes();

+ 84 - 6
api/app/admin/middleware/CheckPermissionAttr.php

@@ -5,34 +5,112 @@ namespace app\admin\middleware;
 use app\admin\attr\Permission;
 use app\common\exception\CatchException;
 use app\common\model\Admin;
+use app\common\model\Role;
 use think\Request;
 
 class CheckPermissionAttr
 {
     public function handle(Request $request, \Closure $next)
     {
-        return $next($request);
         // 通过依赖注入获取admin
         $admin = app(Admin::class);
         $role = $admin->role;
         $codes = $role->codes;
 
+        // 超级管理员可以做任何事
+        if (in_array(Role::CODE_SUPER_ADMIN, $codes)) {
+            return $next($request);
+        }
+
         // 获取权限注解
-        $controller = 'app\\admin\\controller\\'. $request->controller();
-        $ref = new \ReflectionClass($controller);
+        $controller = $request->controller();
+        $controllerNameSpace = 'app\\admin\\controller\\' . $controller;
+        $ref = new \ReflectionClass($controllerNameSpace);
         $attrs = $ref->getAttributes(Permission::class);
+        $methodName = $request->action();
+        $method = $ref->getMethod($methodName);
+        $methodAttrs = $method->getAttributes(Permission::class);
+
+        // 如果有具体的方法权限
+        if ($methodAttrs) {
+            foreach ($methodAttrs as $attrRaw) {
+                /**
+                 * @var Permission
+                 */
+                $attr = $attrRaw->newInstance();
+                // 忽略权限要求
+                if ($attr->ignore) {
+                    return $next($request);
+                }
 
-        // 检查权限
+                /*
+                 * 权限值设置
+                 */
+                $permission = $attr->value;
+                // 继承权限
+                if ($attr->inherit) {
+                    if (count($attrs) > 1) {
+                        throw new \InvalidArgumentException('使用了继承权限值,但是controller的权限Attribute不止一个');
+                    }
+                    $controllerAttr = $attrs[0]->newInstance();
+                    $controllerPermission = $controllerAttr->value;
+                    // 是否使用控制器名
+                    // 尽管在方法上没有权限Attr的话,不会进入foreach循环中,但是还是有可能传一个空的permission进来
+                    if (!$permission) {
+                        if ($controllerAttr->useMethodName) {
+                            $permission = "$controllerPermission.$methodName";
+                        } else {
+                            $permission = $controllerPermission;
+                        }
+                    }
+                } else {
+                    // 不继承权限,直接使用$permission, 但是检查权限值是否为空
+                    if (!$permission) {
+                        if (count($attrs) > 1) {
+                            throw new \InvalidArgumentException('没有使用继承,而且权限值为空,尝试使用controller权限值规则,但是controller的权限Attribute不止一个');
+                        }
+                        $controllerAttr = $attrs[0]->newInstance();
+                        $controllerPermission = $controllerAttr->value;
+                        // 使用控制器attr规则
+                        // 使用方法名
+                        if ($controllerAttr->useMethodName) {
+                            $permission = $methodName;
+                        } else {
+                            // 使用控制器名
+                            $permission = strtolower($controller);
+                        }
+                    }
+                    // 直接使用$permission
+                }
+                /*
+                * 检查权限
+                */
+                if (!in_array($permission, $codes)) {
+                    throw new CatchException("未具有权限$permission, 禁止访问", 403);
+                }
+                // 成功
+                return $next($request);
+            }
+        }
+
+        // 只有控制器权限
         /**
          * @var \ReflectionAttribute $attrRaw 
          */
-        foreach($attrs as $attrRaw) {
+        foreach ($attrs as $attrRaw) {
             /**
              * @var Permission
              */
             $attr = $attrRaw->newInstance();
             $permission = $attr->value;
-            if (false && !in_array($permission, $codes)) {
+            // 自动使用方法名作为权限值
+            if ($attr->useMethodName) {
+                $permission = "$permission.$methodName";
+            } elseif (!$permission) {
+                // 没有标注要什么权限而且useMethodName设为了false,使用controller的小写作为权限值
+                $permission = strtolower($controller);
+            }
+            if (!in_array($permission, $codes)) {
                 throw new CatchException("未具有权限$permission, 禁止访问", 403);
             }
         }

+ 4 - 0
api/app/common/model/Role.php

@@ -2,6 +2,10 @@
 
 namespace app\common\model;
 
+/**
+ * 
+ * @property array<string> $codes 权限列表
+ */
 class Role extends Base
 {
     const CODE_SUPER_ADMIN = 'super_admin';

+ 11 - 6
api/app/common/service/RoleService.php

@@ -32,10 +32,10 @@ class RoleService extends Service
     public function codes()
     {
         // 从缓存获取之前生成好的值
-        $cache = Cache::get(self::CODE_CACHE_KEY);
-        if ($cache) {
-            return $cache;
-        }
+        // $cache = Cache::get(self::CODE_CACHE_KEY);
+        // if ($cache) {
+        //     return $cache;
+        // }
 
         $crud = function($parent, $name, $create = true, $read = true, $update = true, $delete = true) {
             $codes = [];
@@ -78,6 +78,11 @@ class RoleService extends Service
                 'children' => $crud('admin', '人员管理')
             ],
             [
+                'id' => 'role',
+                'lable' => '角色管理',
+                'children' => $crud('role', '角色管理')
+            ],
+            [
                 'id' => 'project',
                 'lable' => '项目',
                 'children' => [
@@ -85,12 +90,12 @@ class RoleService extends Service
                     [
                         'id' => 'project.schedule',
                         'lable' => '进程',
-                        'children' => $crud('project.schedule', '进程')
+                        'children' => $crud('project.schedule', '进程', read: false)
                     ],
                     [
                         'id' => 'project.status',
                         'lable' => '状态',
-                        'children' => $crud('project.status', '状态', delete: false)
+                        'children' => $crud('project.status', '状态', read: false, delete: false)
                     ],
                     [
                         'id' => 'project.contract',

+ 10 - 10
h5/src/components/role/codes.vue

@@ -1,6 +1,6 @@
 <template>
     <el-dialog :modelValue="modelValue" @close="$emit('update:modelValue', false)" title="编辑权限">
-        <el-tree-v2 ref="tree" :data="codes" :props="treeProps" :default-checked-keys="role?.codes" show-checkbox :height="500">
+        <el-tree-v2 ref="tree" :data="codes" :props="treeProps" :default-checked-keys="role.codes" show-checkbox :height="500">
             <template #default="{ node }">
                 <span>{{ node.data.lable }}</span>
             </template>
@@ -17,12 +17,12 @@ import { update, codes as getCodes } from '/@/api/role'
 import type { Role } from '/@/api/role'
 import { ref, onMounted } from 'vue'
 import type { ElTreeV2 } from 'element-plus'
-import { clone } from 'lodash'
 import { ElMessage } from 'element-plus'
+import { throttle } from 'lodash';
 
 const { modelValue, role } = defineProps<{
     modelValue: boolean,
-    role?: Role
+    role: Role
 }>()
 const emit = defineEmits(['update:modelValue'])
 
@@ -42,17 +42,17 @@ onMounted(async () => {
     codes.value = (await getCodes()).data
 })
 
-const submitForm = async () => {
+const submitForm = throttle(async () => {
     const checkedCodes = tree.value?.getCheckedNodes(true).map(node => node.lable)
     const checkedKeys = tree.value?.getCheckedKeys()
 
-    const updateRole = clone(role) as Role
-    
-    updateRole.codes = checkedKeys
-    updateRole.codes_cn = checkedCodes
+    console.log("🚀 ~ file: codes.vue:50 ~ submitForm ~ role:", role)
+    role.codes = checkedKeys
+    role.codes_cn = checkedCodes
+    console.log("🚀 ~ file: codes.vue:52 ~ submitForm ~ role:", role)
 
     submitLoading.value = true
-    const result = await update(updateRole)
+    const result = await update(role)
     if (result.code != 0) {
         return
     }
@@ -60,6 +60,6 @@ const submitForm = async () => {
     ElMessage({ message: '更新成功' })
 
     emit('update:modelValue', false)
-}
+})
 
 </script>

+ 35 - 47
h5/src/views/admin/role/index.vue

@@ -21,11 +21,11 @@
     </div>
 </template>
 
-<script lang="ts" setup name="underlyingRoleManage">
+<script lang="tsx" setup name="underlyingRoleManage">
 import { ref, h } from 'vue'
 import { ElTag, ElMessage, ElMessageBox, ElButton, TableV2FixedDir } from 'element-plus'
 import type { Column } from 'element-plus'
-import { throttle } from 'lodash'
+import { clone, throttle } from 'lodash'
 import { init, update, newRole } from '/@/api/role'
 import type { Role } from '/@/api/role'
 import Codes from '/@/components/role/codes.vue'
@@ -71,11 +71,10 @@ const columns: Column<any>[] = [
         title: '权限值',
         dataKey: 'codes_cn',
         width: 400,
-        cellRenderer: ({ cellData: codes_cn }) => h(
-            'div',
-            { style: { 'overflow-y': 'auto', display: 'flex' } },
-            codes_cn?.map(cn => h(ElTag, { style: { 'margin-right': '5px' } }, cn)) || '暂无权限 ~ 点击权限以授权'
-        ),
+        cellRenderer: ({ cellData: codes_cn }) =>
+            <div style="overflow-y: auto; display: flex">
+                {codes_cn?.map(cn => <ElTag style="margin-right: 5px">{cn}</ElTag>)}
+            </div>
     },
     {
         key: 'remark',
@@ -88,10 +87,10 @@ const columns: Column<any>[] = [
         title: '是否启用',
         dataKey: 'valid',
         width: 100,
-        cellRenderer: ({ cellData: valid }) => h(
-            ElTag,
-            { effect: valid ? 'dark' : 'plain' }, valid ? '已启用' : '已禁用'
-        ),
+        cellRenderer: ({ cellData: valid }) =>
+            <ElTag effect={valid ? 'dark' : 'plain'}>
+                {valid ? '已启用' : '已禁用'}
+            </ElTag>
     },
     {
         key: 'op',
@@ -100,50 +99,39 @@ const columns: Column<any>[] = [
         fixed: TableV2FixedDir.RIGHT,
         flexGrow: 1,
         cellRenderer: ({ rowData: row }) =>
-            h('div', [
-                // 启用禁用
-                h(
-                    ElButton,
-                    {
-                        type: row.valid ? 'danger' : 'success',
-                        onClick: (throttle(() => {
-                            row.valid = !row.valid
-                            update(row)
-                        })),
-                    },
-                    row.valid ? '禁用' : '启用'
-                ),
-                // 打开权限表单
-                h(
-                    ElButton,
-                    {
-                        type: 'primary',
-                        onClick: throttle(() => {
-                            editRole.value = row
-                            editType.value = 'update'
-                            isShowCodesEditForm.value = true
-                        }),
-                    },
-                    '权限'
-                ),
-                h(
-                    ElButton,
-                    {
-                        type: 'primary',
-                        onClick: () => showEditDialog('update', row),
-                    },
-                    '编辑'
-                ),
-            ]),
+            <div>
+                <ElButton type={row.valid ? 'danger' : 'success'} onClick={() => updateValid(row)}>
+                    {row.valid ? '禁用' : '启用'}
+                </ElButton>
+                <ElButton type="primary" onClick={() => showPermissionDialog(row)}>
+                    权限
+                </ElButton>
+                <ElButton type="primary" onClick={() => showEditDialog('update', row)}>
+                    编辑
+                </ElButton>
+            </div>
     },
 ]
 
 const showEditDialog = throttle((type: 'create' | 'update' = 'create', role: Role = newRole()) => {
-    editRole.value = role
+    editRole.value = clone(role)
     editType.value = type
     isShowRoleEditForm.value = true
 })
 
+const showPermissionDialog = throttle((role) => {
+    editRole.value = clone(role)
+    editType.value = 'update'
+    isShowCodesEditForm.value = true
+})
+
+const updateValid = throttle((row: any) => {
+    row.valid = !row.valid
+    update(row)
+})
+
+
+
 /**
  * 刷新
  */

+ 4 - 1
h5/tsconfig.json

@@ -8,7 +8,7 @@
 		"module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
 		"lib": ["esnext", "dom", "dom.iterable", "scripthost"] /* Specify library files to be included in the compilation. */,
 		"allowJs": true,                       /* Allow javascript files to be compiled. */
-		// "checkJs": true,                       /* Report errors in .js files. */
+		"checkJs": true,                       /* Report errors in .js files. */
 		"jsx": "preserve" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
 		// "declaration": true /* Generates corresponding '.d.ts' file. */,
 		// "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
@@ -71,6 +71,9 @@
 		"noImplicitAny": false,
         "noEmit": true
 	},
+    "vueCompilerOptions": {
+        "target": 3.3
+    },
 	"include": ["src/**/*.ts", "src/**/*.vue", "src/**/*.tsx", "src/**/*.d.ts", "src/utils/request.ts"], // **Represents any directory, and * represents any file. Indicates that all files in the src directory will be compiled
 	"exclude": ["node_modules", "dist"] // Indicates the file directory that does not need to be compiled
 }