宏通 hace 11 horas
commit
bb3a7ec980

+ 3
- 0
.gitattributes Ver fichero

@@ -0,0 +1,3 @@
1
+/dist export-ignore
2
+/.git export-ignore
3
+/.github export-ignore

+ 8
- 0
.idea/.gitignore Ver fichero

@@ -0,0 +1,8 @@
1
+# 默认忽略的文件
2
+/shelf/
3
+/workspace.xml
4
+# 基于编辑器的 HTTP 客户端请求
5
+/httpRequests/
6
+# Datasource local storage ignored files
7
+/dataSources/
8
+/dataSources.local.xml

+ 122
- 0
README.md Ver fichero

@@ -0,0 +1,122 @@
1
+# Dcat Employee Auth
2
+
3
+使用业务系统的 `employees` 表登录 dcat-admin,使用手机号和密码登录,并继续使用 dcat-admin 自带的角色与权限表。
4
+
5
+## 环境要求
6
+
7
+- PHP 8.1+
8
+- Laravel 9-12
9
+- dcat-admin 2.x
10
+- `employees` 表至少包含:
11
+  - `id`
12
+  - `employee_no`
13
+  - `email`
14
+  - `phone`
15
+  - `password`
16
+  - `name`
17
+  - `avatar`
18
+  - `status`
19
+  - `remember_token`
20
+
21
+## 安装
22
+
23
+```bash
24
+composer require returnee/dcat-employee-auth:^1.0
25
+php artisan vendor:publish --tag=dcat-employee-auth-config
26
+php artisan optimize:clear
27
+```
28
+
29
+如果扩展包位于项目本地目录:
30
+
31
+```json
32
+{
33
+    "repositories": [
34
+        {
35
+            "type": "path",
36
+            "url": "dcat-admin-extensions/dcat-employee-auth",
37
+            "options": {
38
+                "symlink": true
39
+            }
40
+        }
41
+    ]
42
+}
43
+```
44
+
45
+然后安装:
46
+
47
+```bash
48
+composer require returnee/dcat-employee-auth:^1.0
49
+```
50
+
51
+## 使用 ZIP 包安装
52
+
53
+将 `returnee-dcat-employee-auth-1.0.0.zip` 放入项目的 `packages` 目录,并配置:
54
+
55
+```json
56
+{
57
+    "repositories": [
58
+        {
59
+            "type": "artifact",
60
+            "url": "packages"
61
+        }
62
+    ]
63
+}
64
+```
65
+
66
+然后执行:
67
+
68
+```bash
69
+composer require returnee/dcat-employee-auth:^1.0
70
+```
71
+
72
+## 配置
73
+
74
+发布后的配置文件为 `config/dcat-employee-auth.php`。
75
+
76
+默认使用手机号登录:
77
+
78
+```php
79
+'login_columns' => [
80
+    'phone',
81
+],
82
+```
83
+
84
+只有 `status = 1` 的员工允许登录。
85
+
86
+## 授予后台管理员角色
87
+
88
+员工登录前,需要将员工ID关联到 dcat-admin 的角色表。可以执行:
89
+
90
+```bash
91
+php artisan dcat-employee-auth:grant-administrator E001
92
+```
93
+
94
+参数支持员工ID或手机号。
95
+
96
+扩展使用 dcat-admin 原有的 `admin_role_users` 表保存员工角色,`user_id` 对应 `employees.id`。
97
+
98
+## 自定义员工模型
99
+
100
+如需添加业务关系或修改访问器,可以继承扩展模型:
101
+
102
+```php
103
+namespace App\Models;
104
+
105
+use Returnee\DcatEmployeeAuth\Models\EmployeeAdministrator;
106
+
107
+class AdminEmployee extends EmployeeAdministrator
108
+{
109
+}
110
+```
111
+
112
+然后修改配置:
113
+
114
+```php
115
+'model' => App\Models\AdminEmployee::class,
116
+```
117
+
118
+## 禁用扩展
119
+
120
+```dotenv
121
+DCAT_EMPLOYEE_AUTH_ENABLED=false
122
+```

+ 31
- 0
composer.json Ver fichero

@@ -0,0 +1,31 @@
1
+{
2
+    "name": "returnee/dcat-employee-auth",
3
+    "description": "Use an employees table as the authentication source for Dcat Admin.",
4
+    "type": "library",
5
+    "license": "MIT",
6
+    "authors": [
7
+        {
8
+            "name": "Returnee Team",
9
+            "email": "dev@returnee.com"
10
+        }
11
+    ],
12
+    "require": {
13
+        "php": ">=8.1",
14
+        "e282486518/dcat-admin": "2.*",
15
+        "laravel/framework": "^9.0|^10.0|^11.0|^12.0"
16
+    },
17
+    "autoload": {
18
+        "psr-4": {
19
+            "Returnee\\DcatEmployeeAuth\\": "src/"
20
+        }
21
+    },
22
+    "extra": {
23
+        "laravel": {
24
+            "providers": [
25
+                "Returnee\\DcatEmployeeAuth\\EmployeeAuthServiceProvider"
26
+            ]
27
+        }
28
+    },
29
+    "minimum-stability": "stable",
30
+    "prefer-stable": true
31
+}

+ 43
- 0
config/dcat-employee-auth.php Ver fichero

@@ -0,0 +1,43 @@
1
+<?php
2
+
3
+use Returnee\DcatEmployeeAuth\Models\EmployeeAdministrator;
4
+
5
+return [
6
+    // 是否启用员工后台认证扩展。
7
+    'enabled' => env('DCAT_EMPLOYEE_AUTH_ENABLED', true),
8
+
9
+    // Dcat Admin 使用的认证守卫和用户提供者名称。
10
+    'guard' => env('DCAT_EMPLOYEE_AUTH_GUARD', 'admin'),
11
+    'provider' => env('DCAT_EMPLOYEE_AUTH_PROVIDER', 'admin'),
12
+
13
+    // 员工管理员模型、数据库连接及员工数据表。
14
+    'model' => EmployeeAdministrator::class,
15
+    'connection' => env('DCAT_EMPLOYEE_AUTH_CONNECTION'),
16
+    'table' => env('DCAT_EMPLOYEE_AUTH_TABLE', 'employees'),
17
+
18
+    // 登录表单字段、中文标签及允许用于登录的数据库字段。
19
+    'login_input' => 'account',
20
+    'login_label' => '手机号',
21
+    'login_columns' => [
22
+        'phone',
23
+    ],
24
+
25
+    // 项目员工表字段与扩展内部字段的映射关系。
26
+    'fields' => [
27
+        'employee_no' => 'employee_no',
28
+        'name' => 'name',
29
+        'avatar' => 'avatar',
30
+        'password' => 'password',
31
+        'status' => 'status',
32
+        'last_login_at' => 'last_login_at',
33
+        'last_login_ip' => 'last_login_ip',
34
+    ],
35
+
36
+    // 有效员工状态值;状态不匹配的员工无法登录。
37
+    'active_value' => 1,
38
+
39
+    // 登录页和密码输入限制。
40
+    'remember' => true,
41
+    'password_min_length' => 6,
42
+    'password_max_length' => 64,
43
+];

+ 144
- 0
resources/views/login.blade.php Ver fichero

@@ -0,0 +1,144 @@
1
+<style>
2
+    .login-box {
3
+        margin-top: -10rem;
4
+        padding: 5px;
5
+    }
6
+    .login-card-body {
7
+        padding: 1.5rem 1.8rem 1.6rem;
8
+    }
9
+    .card, .card-body {
10
+        border-radius: .25rem
11
+    }
12
+    .login-btn {
13
+        padding-left: 2rem!important;
14
+        padding-right: 1.5rem!important;
15
+    }
16
+    .content {
17
+        overflow-x: hidden;
18
+    }
19
+    .form-group .control-label {
20
+        text-align: left;
21
+    }
22
+</style>
23
+
24
+@php
25
+    // 登录字段名称和显示标签均可由扩展配置覆盖。
26
+    $loginInput = config('dcat-employee-auth.login_input', 'account');
27
+    $loginLabel = config('dcat-employee-auth.login_label', '手机号');
28
+@endphp
29
+
30
+<div class="login-page bg-40">
31
+    <div class="login-box">
32
+        <div class="login-logo mb-2">
33
+            {{ config('admin.name') }}
34
+        </div>
35
+        <div class="card">
36
+            <div class="card-body login-card-body shadow-100">
37
+                <p class="login-box-msg mt-1 mb-1">{{ __('admin.welcome_back') }}</p>
38
+
39
+                <form id="login-form" method="POST" action="{{ admin_url('auth/login') }}">
40
+                    <input type="hidden" name="_token" value="{{ csrf_token() }}"/>
41
+
42
+                    <fieldset class="form-label-group form-group position-relative has-icon-left">
43
+                        <input
44
+                            id="{{ $loginInput }}"
45
+                            type="text"
46
+                            class="form-control {{ $errors->has($loginInput) ? 'is-invalid' : '' }}"
47
+                            name="{{ $loginInput }}"
48
+                            placeholder="{{ $loginLabel }}"
49
+                            value="{{ old($loginInput) }}"
50
+                            required
51
+                            autofocus
52
+                        >
53
+
54
+                        <div class="form-control-position">
55
+                            <i class="feather icon-user"></i>
56
+                        </div>
57
+
58
+                        <label for="{{ $loginInput }}">{{ $loginLabel }}</label>
59
+
60
+                        <div class="help-block with-errors"></div>
61
+                        @if($errors->has($loginInput))
62
+                            <span class="invalid-feedback text-danger" role="alert">
63
+                                @foreach($errors->get($loginInput) as $message)
64
+                                    <span class="control-label">
65
+                                        <i class="feather icon-x-circle"></i> {{ $message }}
66
+                                    </span><br>
67
+                                @endforeach
68
+                            </span>
69
+                        @endif
70
+                    </fieldset>
71
+
72
+                    <fieldset class="form-label-group form-group position-relative has-icon-left">
73
+                        <input
74
+                            minlength="{{ config('dcat-employee-auth.password_min_length', 6) }}"
75
+                            maxlength="{{ config('dcat-employee-auth.password_max_length', 64) }}"
76
+                            id="password"
77
+                            type="password"
78
+                            class="form-control {{ $errors->has('password') ? 'is-invalid' : '' }}"
79
+                            name="password"
80
+                            placeholder="{{ trans('admin.password') }}"
81
+                            required
82
+                            autocomplete="current-password"
83
+                        >
84
+
85
+                        <div class="form-control-position">
86
+                            <i class="feather icon-lock"></i>
87
+                        </div>
88
+                        <label for="password">{{ trans('admin.password') }}</label>
89
+
90
+                        <div class="help-block with-errors"></div>
91
+                        @if($errors->has('password'))
92
+                            <span class="invalid-feedback text-danger" role="alert">
93
+                                @foreach($errors->get('password') as $message)
94
+                                    <span class="control-label">
95
+                                        <i class="feather icon-x-circle"></i> {{ $message }}
96
+                                    </span><br>
97
+                                @endforeach
98
+                            </span>
99
+                        @endif
100
+                    </fieldset>
101
+
102
+                    <div class="form-group d-flex justify-content-between align-items-center">
103
+                        <div class="text-left">
104
+                            @if(config('admin.auth.remember'))
105
+                                <fieldset class="checkbox">
106
+                                    <div class="vs-checkbox-con vs-checkbox-primary">
107
+                                        <input
108
+                                            id="remember"
109
+                                            name="remember"
110
+                                            value="1"
111
+                                            type="checkbox"
112
+                                            {{ old('remember') ? 'checked' : '' }}
113
+                                        >
114
+                                        <span class="vs-checkbox">
115
+                                            <span class="vs-checkbox--check">
116
+                                                <i class="vs-icon feather icon-check"></i>
117
+                                            </span>
118
+                                        </span>
119
+                                        <span>{{ trans('admin.remember_me') }}</span>
120
+                                    </div>
121
+                                </fieldset>
122
+                            @endif
123
+                        </div>
124
+                    </div>
125
+
126
+                    <button type="submit" class="btn btn-primary float-right login-btn">
127
+                        {{ __('admin.login') }}
128
+                        &nbsp;
129
+                        <i class="feather icon-arrow-right"></i>
130
+                    </button>
131
+                </form>
132
+            </div>
133
+        </div>
134
+    </div>
135
+</div>
136
+
137
+<script>
138
+Dcat.ready(function () {
139
+    // 使用 Dcat 自带表单验证与 AJAX 登录处理。
140
+    $('#login-form').form({
141
+        validate: true,
142
+    });
143
+});
144
+</script>

+ 71
- 0
src/Auth/EmployeeUserProvider.php Ver fichero

@@ -0,0 +1,71 @@
1
+<?php
2
+
3
+namespace Returnee\DcatEmployeeAuth\Auth;
4
+
5
+use Illuminate\Auth\EloquentUserProvider;
6
+use Illuminate\Contracts\Hashing\Hasher as HasherContract;
7
+
8
+/**
9
+ * 员工账号 Eloquent 用户提供者。
10
+ *
11
+ * 根据配置的登录字段查找员工,并自动过滤禁用或离职账号。
12
+ */
13
+class EmployeeUserProvider extends EloquentUserProvider
14
+{
15
+    public function __construct(
16
+        HasherContract $hasher,
17
+        string $model,
18
+        private readonly array $employeeConfig
19
+    ) {
20
+        parent::__construct($hasher, $model);
21
+    }
22
+
23
+    /**
24
+     * 根据登录凭证查询员工。
25
+     *
26
+     * 默认只允许使用手机号登录,也可通过 login_columns 配置其他字段。
27
+     */
28
+    public function retrieveByCredentials(#[\SensitiveParameter] array $credentials)
29
+    {
30
+        $input = $this->employeeConfig['login_input'] ?? 'account';
31
+        $account = $credentials[$input] ?? null;
32
+
33
+        if ($account === null || $account === '') {
34
+            return null;
35
+        }
36
+
37
+        $columns = array_values(array_filter(
38
+            $this->employeeConfig['login_columns'] ?? ['phone']
39
+        ));
40
+
41
+        if ($columns === []) {
42
+            return null;
43
+        }
44
+
45
+        return $this->newModelQuery()
46
+            ->where(function ($query) use ($account, $columns) {
47
+                // 多个登录字段之间使用 OR,例如手机号或邮箱。
48
+                foreach ($columns as $index => $column) {
49
+                    $method = $index === 0 ? 'where' : 'orWhere';
50
+                    $query->{$method}($column, $account);
51
+                }
52
+            })
53
+            ->first();
54
+    }
55
+
56
+    protected function newModelQuery($model = null)
57
+    {
58
+        $query = parent::newModelQuery($model);
59
+        $statusColumn = $this->employeeConfig['fields']['status'] ?? null;
60
+
61
+        if ($statusColumn) {
62
+            // 所有认证查询统一限制为有效员工,避免已禁用账号继续登录。
63
+            $query->where(
64
+                $statusColumn,
65
+                $this->employeeConfig['active_value'] ?? 1
66
+            );
67
+        }
68
+
69
+        return $query;
70
+    }
71
+}

+ 58
- 0
src/Console/GrantAdministratorCommand.php Ver fichero

@@ -0,0 +1,58 @@
1
+<?php
2
+
3
+namespace Returnee\DcatEmployeeAuth\Console;
4
+
5
+use Dcat\Admin\Models\Role;
6
+use Illuminate\Console\Command;
7
+
8
+/**
9
+ * 为指定员工授予 Dcat Admin 超级管理员角色。
10
+ */
11
+class GrantAdministratorCommand extends Command
12
+{
13
+    protected $signature = 'dcat-employee-auth:grant-administrator
14
+                            {account : 员工ID或登录账号}';
15
+
16
+    protected $description = '为员工授予 Dcat Admin 超级管理员角色';
17
+
18
+    /**
19
+     * 根据员工 ID 或配置的登录字段查找员工并绑定 administrator 角色。
20
+     */
21
+    public function handle(): int
22
+    {
23
+        $model = config('dcat-employee-auth.model');
24
+        $account = $this->argument('account');
25
+        $columns = config('dcat-employee-auth.login_columns', []);
26
+
27
+        $employee = $model::query()
28
+            ->where(function ($query) use ($account, $columns) {
29
+                // 员工主键和所有可登录字段都可以作为命令参数。
30
+                $query->whereKey($account);
31
+
32
+                foreach ($columns as $column) {
33
+                    $query->orWhere($column, $account);
34
+                }
35
+            })
36
+            ->first();
37
+
38
+        if (! $employee) {
39
+            $this->error('未找到对应员工。');
40
+
41
+            return self::FAILURE;
42
+        }
43
+
44
+        $role = Role::query()->where('slug', Role::ADMINISTRATOR)->first();
45
+
46
+        if (! $role) {
47
+            $this->error('未找到 administrator 角色,请先安装 Dcat Admin 数据表。');
48
+
49
+            return self::FAILURE;
50
+        }
51
+
52
+        $employee->roles()->syncWithoutDetaching([$role->getKey()]);
53
+
54
+        $this->info("已为员工 {$employee->getKey()} 授予超级管理员角色。");
55
+
56
+        return self::SUCCESS;
57
+    }
58
+}

+ 94
- 0
src/EmployeeAuthServiceProvider.php Ver fichero

@@ -0,0 +1,94 @@
1
+<?php
2
+
3
+namespace Returnee\DcatEmployeeAuth;
4
+
5
+use Illuminate\Support\Facades\Auth;
6
+use Illuminate\Support\ServiceProvider;
7
+use Returnee\DcatEmployeeAuth\Auth\EmployeeUserProvider;
8
+use Returnee\DcatEmployeeAuth\Console\GrantAdministratorCommand;
9
+use Returnee\DcatEmployeeAuth\Http\Controllers\AuthController;
10
+
11
+/**
12
+ * 员工后台认证扩展服务提供者。
13
+ *
14
+ * 负责将 employees 表接入 Laravel Auth 与 Dcat Admin,并注册扩展配置、
15
+ * 登录视图和超级管理员授权命令。
16
+ */
17
+class EmployeeAuthServiceProvider extends ServiceProvider
18
+{
19
+    /**
20
+     * 合并扩展配置并替换 Dcat Admin 的认证实现。
21
+     */
22
+    public function register(): void
23
+    {
24
+        $this->mergeConfigFrom(
25
+            __DIR__.'/../config/dcat-employee-auth.php',
26
+            'dcat-employee-auth'
27
+        );
28
+
29
+        if (! config('dcat-employee-auth.enabled', true)) {
30
+            return;
31
+        }
32
+
33
+        $this->registerUserProvider();
34
+        $this->configureAuthentication();
35
+    }
36
+
37
+    /**
38
+     * 发布配置、加载视图并注册 Artisan 命令。
39
+     */
40
+    public function boot(): void
41
+    {
42
+        $this->publishes([
43
+            __DIR__.'/../config/dcat-employee-auth.php' => config_path('dcat-employee-auth.php'),
44
+        ], 'dcat-employee-auth-config');
45
+
46
+        $this->loadViewsFrom(__DIR__.'/../resources/views', 'dcat-employee-auth');
47
+
48
+        if ($this->app->runningInConsole()) {
49
+            $this->commands([
50
+                GrantAdministratorCommand::class,
51
+            ]);
52
+        }
53
+    }
54
+
55
+    private function registerUserProvider(): void
56
+    {
57
+        // 注册自定义用户提供者,让 Laravel 从员工表中查找登录账号。
58
+        Auth::provider('dcat_employee', function ($app, array $config) {
59
+            return new EmployeeUserProvider(
60
+                $app['hash'],
61
+                $config['model'],
62
+                config('dcat-employee-auth')
63
+            );
64
+        });
65
+    }
66
+
67
+    private function configureAuthentication(): void
68
+    {
69
+        $guard = config('dcat-employee-auth.guard', 'admin');
70
+        $provider = config('dcat-employee-auth.provider', 'admin');
71
+        $model = config('dcat-employee-auth.model');
72
+
73
+        $guardConfig = [
74
+            'driver' => 'session',
75
+            'provider' => $provider,
76
+        ];
77
+        $providerConfig = [
78
+            'driver' => 'dcat_employee',
79
+            'model' => $model,
80
+        ];
81
+
82
+        // 同时覆盖 Dcat 和 Laravel 的认证配置,保证中间件与 Admin::user() 使用同一员工模型。
83
+        config([
84
+            'admin.auth.controller' => AuthController::class,
85
+            'admin.auth.guard' => $guard,
86
+            "admin.auth.guards.{$guard}" => $guardConfig,
87
+            "admin.auth.providers.{$provider}" => $providerConfig,
88
+            'admin.auth.remember' => config('dcat-employee-auth.remember', true),
89
+            'admin.database.users_model' => $model,
90
+            "auth.guards.{$guard}" => $guardConfig,
91
+            "auth.providers.{$provider}" => $providerConfig,
92
+        ]);
93
+    }
94
+}

+ 60
- 0
src/Http/Controllers/AuthController.php Ver fichero

@@ -0,0 +1,60 @@
1
+<?php
2
+
3
+namespace Returnee\DcatEmployeeAuth\Http\Controllers;
4
+
5
+use Dcat\Admin\Http\Controllers\AuthController as BaseAuthController;
6
+use Illuminate\Http\Request;
7
+
8
+/**
9
+ * 员工后台登录控制器。
10
+ *
11
+ * 复用 Dcat 登录流程,仅替换登录视图、账号字段并记录最后登录信息。
12
+ */
13
+class AuthController extends BaseAuthController
14
+{
15
+    /** @var string 员工登录页面视图 */
16
+    protected $view = 'dcat-employee-auth::login';
17
+
18
+    /**
19
+     * 返回登录表单使用的账号字段名。
20
+     */
21
+    protected function username()
22
+    {
23
+        return config('dcat-employee-auth.login_input', 'account');
24
+    }
25
+
26
+    protected function sendLoginResponse(Request $request)
27
+    {
28
+        // 登录成功后先记录时间和 IP,再交由 Dcat 生成标准响应。
29
+        $this->updateLoginInformation($request);
30
+
31
+        return parent::sendLoginResponse($request);
32
+    }
33
+
34
+    private function updateLoginInformation(Request $request): void
35
+    {
36
+        $user = $this->guard()->user();
37
+
38
+        if (! $user) {
39
+            return;
40
+        }
41
+
42
+        $values = [];
43
+        $lastLoginAt = config('dcat-employee-auth.fields.last_login_at');
44
+        $lastLoginIp = config('dcat-employee-auth.fields.last_login_ip');
45
+
46
+        if ($lastLoginAt) {
47
+            $values[$lastLoginAt] = now();
48
+        }
49
+
50
+        if ($lastLoginIp) {
51
+            $values[$lastLoginIp] = $request->ip();
52
+        }
53
+
54
+        if ($values !== []) {
55
+            // 直接更新数据库,并同步刷新当前内存模型中的字段值。
56
+            $user->newQuery()->whereKey($user->getKey())->update($values);
57
+            $user->forceFill($values);
58
+        }
59
+    }
60
+}

+ 123
- 0
src/Models/EmployeeAdministrator.php Ver fichero

@@ -0,0 +1,123 @@
1
+<?php
2
+
3
+namespace Returnee\DcatEmployeeAuth\Models;
4
+
5
+use Dcat\Admin\Models\Administrator;
6
+use Dcat\Admin\Support\Helper;
7
+use Illuminate\Support\Facades\Storage;
8
+use Illuminate\Support\Facades\URL;
9
+
10
+/**
11
+ * Dcat Admin 员工管理员模型。
12
+ *
13
+ * 复用 Dcat Administrator 的角色和权限能力,但将认证数据源切换到 employees 表。
14
+ */
15
+class EmployeeAdministrator extends Administrator
16
+{
17
+    /**
18
+     * 设置员工模型使用的数据库连接和数据表。
19
+     */
20
+    protected function init()
21
+    {
22
+        $connection = config('dcat-employee-auth.connection')
23
+            ?: config('admin.database.connection')
24
+            ?: config('database.default');
25
+
26
+        $this->setConnection($connection);
27
+        $this->setTable(config('dcat-employee-auth.table', 'employees'));
28
+    }
29
+
30
+    public function getUsernameAttribute($value): mixed
31
+    {
32
+        // Dcat 内部使用 username,此处映射为员工工号。
33
+        $column = config('dcat-employee-auth.fields.employee_no', 'employee_no');
34
+
35
+        return $this->attributes[$column] ?? $value;
36
+    }
37
+
38
+    public function getNameAttribute($value): mixed
39
+    {
40
+        // 将 Dcat 展示名称映射到员工姓名字段。
41
+        $column = config('dcat-employee-auth.fields.name', 'name');
42
+
43
+        return $this->attributes[$column] ?? $value;
44
+    }
45
+
46
+    public function getAuthPasswordName(): string
47
+    {
48
+        // 支持项目自定义员工密码字段名。
49
+        return config('dcat-employee-auth.fields.password', 'password');
50
+    }
51
+
52
+    /**
53
+     * 获取员工头像地址,不存在时使用 Dcat 默认头像。
54
+     */
55
+    public function getAvatar()
56
+    {
57
+        $column = config('dcat-employee-auth.fields.avatar', 'avatar');
58
+        $avatar = $this->getAttribute($column);
59
+
60
+        if ($avatar) {
61
+            if (! URL::isValidUrl($avatar)) {
62
+                $avatar = Storage::disk(config('admin.upload.disk'))->url($avatar);
63
+            }
64
+
65
+            return $avatar;
66
+        }
67
+
68
+        return admin_asset(config('admin.default_avatar') ?: '@admin/images/default-avatar.jpg');
69
+    }
70
+
71
+    /**
72
+     * 判断当前员工是否可以看到指定菜单。
73
+     *
74
+     * 超级管理员始终可见;普通角色的父菜单只有在至少一个子菜单可访问时才显示,
75
+     * 避免出现没有任何可用功能的空目录。
76
+     */
77
+    public function canSeeMenu($menu): bool
78
+    {
79
+        if ($this->isAdministrator()) {
80
+            return true;
81
+        }
82
+
83
+        $children = Helper::array(data_get($menu, 'children', []));
84
+
85
+        if (! $children) {
86
+            return true;
87
+        }
88
+
89
+        foreach ($children as $child) {
90
+            if ($this->canAccessMenuItem($child) && $this->canSeeMenu($child)) {
91
+                return true;
92
+            }
93
+        }
94
+
95
+        return false;
96
+    }
97
+
98
+    private function canAccessMenuItem($menu): bool
99
+    {
100
+        // 菜单可以直接绑定角色,也可以通过权限节点间接授权。
101
+        $roles = collect(Helper::array(data_get($menu, 'roles', [])))
102
+            ->pluck('slug')
103
+            ->filter()
104
+            ->all();
105
+        $permissions = collect(Helper::array(data_get($menu, 'permissions', [])))
106
+            ->flatMap(fn ($permission) => [
107
+                data_get($permission, 'id'),
108
+                data_get($permission, 'slug'),
109
+            ])
110
+            ->filter()
111
+            ->all();
112
+        $permissionIds = Helper::array(data_get($menu, 'permission_id', []));
113
+
114
+        if (! $roles && ! $permissions && ! $permissionIds) {
115
+            // 未配置权限约束的菜单按 Dcat 默认行为允许显示。
116
+            return true;
117
+        }
118
+
119
+        return $this->visible($roles)
120
+            || collect(array_merge($permissions, $permissionIds))
121
+                ->contains(fn ($permission) => $this->can($permission));
122
+    }
123
+}