├── README.md
├── composer.json
├── migrations
└── database
│ └── 2024_04_06_164639_HPlusAdmin.php
└── src
├── Admin.php
├── Command
└── AdminInstall.php
├── ConfigProvider.php
├── Controller
├── AbstractAdminController.php
├── MenuController.php
├── SettingController.php
├── System
│ ├── AdminRolesController.php
│ └── AdminUsersController.php
└── UserController.php
├── Exception
└── ApiAmisException.php
├── Model
├── AdminApi.php
├── AdminCodeGenerator.php
├── AdminMenu.php
├── AdminPage.php
├── AdminPermission.php
├── AdminRelationship.php
├── AdminRole.php
├── AdminSetting.php
├── AdminUser.php
├── BaseModel.php
├── Extension.php
└── PersonalAccessToken.php
├── Service
├── AdminApiService.php
├── AdminBaseService.php
├── AdminCodeGeneratorService.php
├── AdminMenuService.php
├── AdminPageService.php
├── AdminPermissionService.php
├── AdminRelationshipService.php
├── AdminRoleService.php
├── AdminSettingService.php
└── AdminUserService.php
├── Support
├── Core
│ ├── Asset.php
│ ├── Database.php
│ ├── Hash.php
│ └── Menu.php
└── helpers.php
└── Traits
├── AssetsTrait.php
├── HasApiBase.php
├── HasCheckActionTrait.php
├── HasElementTrait.php
├── HasIconifyPickerTrait.php
├── HasQueryPathTrait.php
└── HasUploadTrait.php
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
HPlus Admin V3
5 |
拥抱后端开发的未来
6 |
基于最新技术栈Hyperf、PHP 8.1、Swoole 5打造的高性能后台管理框架,整合amis前端技术,为快速开发而生。
7 |
8 | [GitHub](https://github.com/hyperf-plus/admin) | [文档](您的文档地址) | [在线体验](您的演示地址) | [社区支持](您的社区支持链接)
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | ## 🚀 介绍
28 |
29 | HPlus Admin V3是一个革命性的后台管理框架,专为追求高性能、灵活性和前沿Web开发实践的开发者设计。该框架基于PHP 8.1、Swoole 5和Hyperf构建,提供了前所未有的性能提升和开发效率。HPlus Admin V3的一个重要特性是深度整合了amis前端框架,通过JSON配置即可快速构建出美观、功能丰富的界面,极大简化了前端的开发工作量。此外,HPlus Admin V3吸收了Laravel的开发理念,结合了Laravel在ORM、中间件、服务容器等方面的优势,为Hyperf提供了更加丰富的开发体验和生态支持。
30 |
31 | ## 💡 特点
32 |
33 | - **前沿技术栈:** 构建于PHP 8.1、Swoole 5和Hyperf之上,拥抱异步编程和协程的最新进展,为您的应用提供了前所未有的性能提升。
34 |
35 | - **与amis无缝集成:** HPlus Admin V3深度整合amis,让开发者能够通过JSON配置轻松构建出美观、功能丰富的前端界面。amis的这一特性极大地降低了前端开发的复杂性,使得无需深入了解复杂的前端框架或编写大量的JavaScript代码,就能快速开发出响应式、交云互动的Web应用。
36 |
37 | - **快速开发:** 提供了丰富的模板和构建工具,支持快速生成CRUD操作界面,大大加快了开发速度,使得从概念到产品的时间更短。
38 |
39 | - **高度可扩展:** 支持自定义插件和模块,开发者可以根据业务需求灵活添加功能,或者整合第三方服务,满足多变的业务场景。
40 |
41 | - **安全性:** 结合Hyperf的安全机制和Swoole的稳定性,提供了强大的安全保障,帮助您的应用抵御网络攻击,保护用户数据。
42 |
43 | 通过整合amis,HPlus Admin V3不仅仅是一个后台管理框架,它更是一个强大的全栈解决方案,使得从数据后端到前端界面的开发变得前所未有地简单和高效。无论是复杂的数据处理还是丰富的用户交互,HPlus Admin V3都能帮助您快速实现,加速产品的迭代和上市。
44 |
45 | ### 🔥 功能丰富的组件库
46 |
47 | - **丰富的组件库:** amis提供了150+的组件库,涵盖了从基础的表单元素到复杂的数据展示组件,甚至包括图表、对话框、标签页等高级组件。这些组件的丰富性保证了几乎所有类型的前端页面需求都能得到满足。
48 |
49 | - **高度可定制化:** 尽管amis的组件通过JSON配置即可使用,但它们也支持高度的定制化。开发者可以根据自己的需求调整组件的样式、行为和交互方式,甚至可以扩展自定义组件,以实现特定的功能。
50 |
51 | - **快速响应式布局:** amis的组件设计遵循响应式布局原则,能够自动适配不同大小的屏幕。无论是在PC端还是移动设备上,都能保证用户界面的美观和用户体验。
52 |
53 | - **声明式UI构建:** 通过JSON配置即可声明式地构建用户界面,这种方式使得界面的构建变得简单明了,大大减少了前端代码的编写量。开发者可以通过可视化工具直接生成这些JSON配置,实现所见即所得的界面开发。
54 |
55 |
56 | ## 🚀 快速开始
57 |
58 | ### 环境要求
59 |
60 | 确保您的服务器满足以下条件:
61 |
62 | - PHP >= 8.1
63 | - Swoole >= 5.0
64 | - Hyperf >= 3.0
65 |
66 | ### 安装步骤
67 | 【开发中暂未发布】
68 | 1. 创建Hyperf项目:
69 |
70 | ```bash
71 | composer create-project hyperf/hyperf-skeleton your-project-name
72 | ```
73 |
74 | 2. 安装HPlus Admin V3包:
75 |
76 | ```bash
77 | cd your-project-name
78 | composer require hyperf-plus/admin
79 | ```
80 |
81 | 3. 发布配置文件和资源:
82 |
83 | ```bash
84 | php bin/hyperf.php vendor:publish hyperf-plus/admin
85 | ```
86 |
87 | 4. 初始化数据库:
88 |
89 | ```bash
90 | php bin/hyperf.php admin:install
91 | ```
92 |
93 | 5. 启动项目:
94 |
95 | ```bash
96 | php bin/hyperf.php start
97 | ```
98 |
99 | 现在您可以通过访问`http://localhost:9501/admin`来使用HPlus Admin V3了,默认账号和密码均为`admin`。
100 |
101 | ## 🤝 贡献
102 |
103 | 欢迎各位开发者对HPlus Admin V3的贡献,无论是通过提交Pull Request来修复bug或添加新特性,还是通过提供意见和反馈来帮助我们改进项目。如果您喜欢这个项目,不妨给我们一个Star,您的支持是我们前进的最大动力!
104 |
105 | ## 🔒 许可证
106 |
107 | HPlus Admin V3遵循MIT许可证发布。详细内容请参见[LICENSE](LICENSE)文件。
108 |
109 | 感谢您对HPlus Admin V3的关注,期待您的加入和贡献!
110 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hyperf-plus/admin",
3 | "description": "开箱即用的Hyperf后台扩展,amis最强后台管理系统",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "毛自豪",
8 | "email": "4213509@qq.com"
9 | }
10 | ],
11 | "homepage": "https://github.com/lphkxd/hyperf-admin",
12 | "keywords": [
13 | "hyperf",
14 | "Admin",
15 | "amis"
16 | ],
17 | "require": {
18 | "php": ">=8.1",
19 | "ext-swoole": ">=5",
20 | "96qbhy/hyperf-auth": "^3.1",
21 | "hyperf-plus/helper": "^3.0",
22 | "hyperf-plus/route": "^3.1",
23 | "hyperf-plus/swagger": "^3.1",
24 | "hyperf-plus/validate": "^3.1",
25 | "hyperf-plus/ui": "^3.1"
26 | },
27 | "autoload": {
28 | "psr-4": {
29 | "HPlus\\Admin\\": "src/"
30 | },
31 | "files": [
32 | "../ui/src/Support/helpers.php"
33 | ]
34 | },
35 | "autoload-dev": {
36 | "psr-4": {
37 | "HPlus\\Admin\\Tests\\": "tests"
38 | }
39 | },
40 | "extra": {
41 | "hyperf": {
42 | "config": "\\HPlus\\Admin\\ConfigProvider"
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/migrations/database/2024_04_06_164639_HPlusAdmin.php:
--------------------------------------------------------------------------------
1 | up();
23 | }
24 |
25 | /**
26 | * Reverse the migrations.
27 | */
28 | public function down(): void
29 | {
30 | Database::make()->down();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Admin.php:
--------------------------------------------------------------------------------
1 | get(AdminSettingService::class);
58 | }
59 |
60 | /**
61 | * @return Menu|mixed
62 | * @throws ContainerExceptionInterface
63 | * @throws NotFoundExceptionInterface
64 | */
65 | public static function menu()
66 | {
67 | return ApplicationContext::getContainer()->get(Menu::class);
68 | }
69 |
70 | public static function user()
71 | {
72 | return auth('admin')->user();
73 | }
74 |
75 | public static function adminMenuModel()
76 | {
77 | return config('admin.database.menu_model', AdminMenu::class);
78 | }
79 |
80 | public static function adminUserModel()
81 | {
82 | return config('admin.database.user_model', AdminUser::class);
83 | }
84 |
85 | public static function adminRoleModel()
86 | {
87 | return config('admin.database.role_model', AdminRole::class);
88 | }
89 |
90 | public static function adminPermissionModel()
91 | {
92 | return config('admin.database.permission_model', AdminPermission::class);
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Command/AdminInstall.php:
--------------------------------------------------------------------------------
1 | setDescription('安装Admin后台');
37 | }
38 |
39 | public function handle()
40 | {
41 | $this->call('migrate', ['--path' => BASE_PATH . '/vendor/hyperf-plus/admin/migrations/database', '--realpath' => true]);
42 | if (config('admin.models.admin_user', AdminUser::class)::query()->count() == 0) {
43 | Database::make()->fillInitialData();
44 | }
45 | $this->info('success');
46 | }
47 |
48 | private function publishAssets()
49 | {
50 | $source = __DIR__ . '/../../admin-views/dist/';
51 | $target = BASE_PATH . '/public/admin';
52 |
53 | $this->copyDirectory($source, $target);
54 | }
55 |
56 | private function copyDirectory(string $source, string $target)
57 | {
58 | if (! is_dir($source)) {
59 | return;
60 | }
61 |
62 | if (! is_dir($target)) {
63 | mkdir($target, 0755, true);
64 | }
65 |
66 | # 复制目录
67 |
68 | $iterator = new RecursiveIteratorIterator(
69 | new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
70 | RecursiveIteratorIterator::SELF_FIRST
71 | );
72 |
73 | foreach ($iterator as $item) {
74 | if ($item->isDir()) {
75 | $dirPath = $target . DIRECTORY_SEPARATOR . $iterator->getSubPathName();
76 | if (! is_dir($dirPath)) {
77 | mkdir($dirPath);
78 | }
79 | } else {
80 | copy($item->getPathname(), $target . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/ConfigProvider.php:
--------------------------------------------------------------------------------
1 | [
26 | 'scan' => [
27 | 'paths' => [
28 | __DIR__ . '/Command',
29 | __DIR__ . '/Controller',
30 | __DIR__ . '/Service',
31 | ],
32 | ],
33 | ],
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Controller/AbstractAdminController.php:
--------------------------------------------------------------------------------
1 | service = make($this->serviceName);
73 | } else {
74 | $this->service = $this->defaultService($prefix);
75 | }
76 | $prefix = str_replace('/_', '/', $prefix);
77 | $this->queryPath = $prefix;
78 | }
79 |
80 | /**
81 | * 获取当前登录用户.
82 | */
83 | public function user()
84 | {
85 | return Admin::user();
86 | }
87 |
88 | public function getPrimaryValue($request): mixed
89 | {
90 | $primaryKey = $this->service->primaryKey();
91 |
92 | return $request->{$primaryKey};
93 | }
94 |
95 | /**
96 | * 获取新增页面.
97 | */
98 | #[GetApi(summary: '列表')]
99 | #[PostApi(summary: '创建数据')]
100 | public function _self_path()
101 | {
102 |
103 | if ($this->request->getMethod() == 'POST') {
104 | return $this->store();
105 | }
106 |
107 | if ($this->actionOfGetData()) {
108 | return $this->service->list($this->request->all());
109 | }
110 |
111 | if ($this->actionOfExport()) {
112 | return $this->export();
113 | }
114 |
115 | return $this->grid();
116 | }
117 |
118 | /**
119 | * 获取新增页面.
120 | */
121 | #[GetApi(summary: '创建数据')]
122 | public function create()
123 | {
124 | $this->isCreate = true;
125 | $form = amis()->Card()
126 | ->className('base-form')
127 | ->header(['title' => __('admin.create')])
128 | ->toolbar([$this->backButton()])
129 | ->body($this->form(false)->api($this->getStorePath()));
130 |
131 | return $this->basePage()->body($form);
132 | }
133 |
134 | public function store()
135 | {
136 | $response = fn ($result) => $this->autoResponse($result, __('admin.save'));
137 |
138 | if ($this->actionOfQuickEdit()) {
139 | return $response($this->service->quickEdit($this->request->all()));
140 | }
141 |
142 | if ($this->actionOfQuickEditItem()) {
143 | return $response($this->service->quickEditItem($this->request->all()));
144 | }
145 | return $response($this->service->store($this->request->all()));
146 | }
147 |
148 | #[GetApi(summary: '获取单条数据', path: '{id:\\d+}')]
149 | #[Path(name: '主键id', key: 'id')]
150 | public function edit($id)
151 | {
152 | $this->isEdit = true;
153 |
154 | if ($this->actionOfGetData()) {
155 | return $this->service->getEditData($id);
156 | }
157 |
158 | $form = amis()->Card()
159 | ->className('base-form')
160 | ->header(['title' => __('admin.edit')])
161 | ->toolbar([$this->backButton()])
162 | ->body(
163 | $this->form(true)->api($this->getUpdatePath())->initApi($this->getEditGetDataPath())
164 | );
165 |
166 | return $this->basePage()->body($form);
167 | }
168 |
169 | #[PutApi(summary: '保存单条数据', path: '{id:\\d+}')]
170 | #[Path(key: 'id', name: '主键id')]
171 | public function put($id)
172 | {
173 | $data = $this->request->all();
174 | if (empty($id)) {
175 | throw new ApiAmisException('缺少更新条件,请先选择数据!');
176 | }
177 | $result = $this->service->update($id, $data);
178 | return $this->autoResponse($result, __('admin.save'));
179 | }
180 |
181 | #[DeleteApi(summary: '删除数据', path: '{ids}')]
182 | public function destroy($ids)
183 | {
184 | $rows = $this->service->delete($ids);
185 | return $this->autoResponse($rows, __('admin.delete'));
186 | }
187 |
188 | /**
189 | * 根据传入的条件, 返回消息响应.
190 | * @param mixed $flag
191 | * @param mixed $text
192 | */
193 | protected function autoResponse($flag, $text = '')
194 | {
195 | if (! $text) {
196 | $text = __('admin.actions');
197 | }
198 |
199 | if ($flag) {
200 | return Admin::responseData([], $text . __('admin.successfully'));
201 | }
202 |
203 | return Admin::responseError($this->service->getError() ?? $text . __('admin.failed'));
204 | }
205 |
206 | private function defaultService(string $prefix)
207 | {
208 | # todo 根据路由自动挂载service
209 | $service = new class() extends AdminBaseService {
210 | };
211 | $service->setModelName($prefix);
212 | return $service;
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/src/Controller/MenuController.php:
--------------------------------------------------------------------------------
1 | baseDetail()->body([]);
33 | }
34 |
35 | /**
36 | * 保存排序.
37 | */
38 | public function saveOrder()
39 | {
40 | return $this->autoResponse($this->service->reorder($this->request->post('ids')));
41 | }
42 |
43 | protected function grid(): Page
44 | {
45 | $crud = $this->baseCRUD()
46 | ->draggable()
47 | ->saveOrderApi([
48 | 'url' => '/system/admin_menus/save_order',
49 | 'data' => ['ids' => '${ids}'],
50 | ])
51 | ->loadDataOnce()
52 | ->syncLocation(false)
53 | ->footerToolbar([])
54 | ->headerToolbar([$this->createButton(true, 'lg'), ...$this->baseHeaderToolBar()])
55 | ->filterTogglable(false)
56 | ->footerToolbar(['statistics'])
57 | ->bulkActions([$this->bulkDeleteButton()->reload('window')])
58 | ->columns([
59 | amis()->TableColumn('id', 'ID')->sortable(),
60 | amis()->TableColumn('title', __('admin.admin_menu.title')),
61 | amis()->TableColumn('icon', __('admin.admin_menu.icon'))
62 | ->type('flex')
63 | ->justify('start')
64 | ->items([
65 | amis()->SvgIcon()->icon('${icon}')->className('mr-2 text-lg'),
66 | '${icon}',
67 | ]),
68 | amis()->TableColumn('url', __('admin.admin_menu.url')),
69 | amis()->TableColumn('order', __('admin.admin_menu.order'))->quickEdit(
70 | amis()->NumberControl()->min(0)->saveImmediately(true)
71 | ),
72 | amis()->TableColumn('visible', __('admin.admin_menu.visible'))->quickEdit(
73 | amis()->SwitchControl()->mode('inline')->saveImmediately(true)
74 | ),
75 | amis()->TableColumn('is_home', __('admin.admin_menu.is_home'))->quickEdit(
76 | amis()->SwitchControl()->mode('inline')->saveImmediately(true)
77 | ),
78 | $this->rowActions([
79 | $this->rowEditButton(true, 'lg'),
80 | $this->rowDeleteButton(),
81 | ]),
82 | ]);
83 |
84 | return $this->baseList($crud);
85 | }
86 |
87 | protected function form(): Form
88 | {
89 | return $this->baseForm()->body([
90 | amis()->GroupControl()->body([
91 | amis()->TextControl('title', __('admin.admin_menu.title'))->required(),
92 | $this->iconifyPicker('icon', __('admin.admin_menu.icon')),
93 | ]),
94 | amis()->GroupControl()->body([
95 | amis()->TreeSelectControl('parent_id', __('admin.admin_menu.parent_id'))
96 | ->labelField('title')
97 | ->valueField('id')
98 | ->showIcon(false)
99 | ->value(0)
100 | ->source('/system/admin_menus?_action=getData'),
101 | amis()->TextControl('component', __('admin.admin_menu.component'))
102 | ->description(__('admin.admin_menu.component_desc'))
103 | ->value('amis'),
104 | ]),
105 | amis()->TextControl('url', __('admin.admin_menu.url'))
106 | ->required()
107 | ->validateOnChange()
108 | ->validations(['matchRegexp' => '/^(http(s)?\:\/)?(\/)+/'])
109 | ->validationErrors(['matchRegexp' => __('admin.need_start_with_slash')])
110 | ->placeholder('eg: /admin_menus'),
111 | amis()->NumberControl('order', __('admin.admin_menu.order'))
112 | ->required()
113 | ->displayMode('enhance')
114 | ->description(__('admin.order_asc'))
115 | ->min(0)
116 | ->value(0),
117 | // amis()->ListControl('url_type', __('admin.admin_menu.type'))
118 | // ->options(Admin::adminMenuModel()::getType())
119 | // ->value(Admin::adminMenuModel()::TYPE_ROUTE),
120 | amis()->SwitchControl('visible', __('admin.admin_menu.visible'))
121 | ->onText(__('admin.admin_menu.show'))
122 | ->offText(__('admin.admin_menu.hide'))
123 | ->value(1),
124 | amis()->SwitchControl('is_home', __('admin.admin_menu.is_home'))
125 | ->onText(__('admin.yes'))
126 | ->offText(__('admin.no'))
127 | ->description(__('admin.admin_menu.is_home_description'))
128 | ->value(0),
129 | amis()->SwitchControl('is_full', __('admin.admin_menu.is_full'))
130 | ->onText(__('admin.yes'))
131 | ->offText(__('admin.no'))
132 | ->description(__('admin.admin_menu.is_full_description'))
133 | ->value(0),
134 | ])->onEvent([
135 | 'submitSucc' => [
136 | 'actions' => [
137 | 'actionType' => 'custom',
138 | 'script' => 'window.location.reload()',
139 | ],
140 | ],
141 | ]);
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/Controller/SettingController.php:
--------------------------------------------------------------------------------
1 | UI::getNav(),
32 | 'assets' => UI::getAssets(),
33 | 'app_name' => Admin::config('admin.name'),
34 | 'locale' => Admin::config('app.locale'),
35 | 'layout' => Admin::config('admin.layout'),
36 | 'logo' => Admin::config('admin.logo'),
37 |
38 | 'login_captcha' => Admin::config('admin.auth.login_captcha'),
39 | 'show_development_tools' => Admin::config('admin.show_development_tools'),
40 | 'system_theme_setting' => Admin::setting()->get('system_theme_setting'),
41 | 'enabled_extensions' => Extension::query()->where('is_enabled', 1)->pluck('name')?->toArray(),
42 | ];
43 | }
44 |
45 | #[PostApi(path: 'config', summary: '获取系配置统')]
46 | public function update()
47 | {
48 | $data = $this->request->all();
49 | foreach ($data as $key => $value) {
50 | if ($key == 'system_theme_setting') {
51 | $data[$key] = $value;
52 | } else {
53 | unset($data[$key]);
54 | }
55 | }
56 | Admin::setting()->setMany($data);
57 | return Admin::responseMessage(__('admin.save_success'));
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Controller/System/AdminRolesController.php:
--------------------------------------------------------------------------------
1 | baseCRUD()
22 | ->headerToolbar([
23 | $this->createButton(true),
24 | ...$this->baseHeaderToolBar(),
25 | ])
26 | ->filterTogglable(false)
27 | ->itemCheckableOn('${slug !== "administrator"}')
28 | ->columns([
29 | amis()->TableColumn()->label('ID')->name('id')->sortable(),
30 | amis()->TableColumn()->label(__('admin.admin_role.name'))->name('name'),
31 | amis()->TableColumn()->label(__('admin.admin_role.slug'))->name('slug')->type('tag'),
32 | amis()->TableColumn()
33 | ->label(__('admin.created_at'))
34 | ->name('created_at')
35 | ->type('datetime')
36 | ->sortable(true),
37 | amis()->TableColumn()
38 | ->label(__('admin.updated_at'))
39 | ->name('updated_at')
40 | ->type('datetime')
41 | ->sortable(true),
42 | $this->rowActions([
43 | $this->setPermission(),
44 | $this->rowEditButton(true),
45 | $this->rowDeleteButton()->hiddenOn('${slug == "administrator"}'),
46 | ]),
47 | ]);
48 |
49 | return $this->baseList($crud)->css([
50 | '.tree-full' => [
51 | 'overflow' => 'hidden !important',
52 | ],
53 | '.cxd-TreeControl > .cxd-Tree' => [
54 | 'height' => '100% !important',
55 | 'max-height' => '100% !important',
56 | ],
57 | ]);
58 | }
59 |
60 | protected function setPermission()
61 | {
62 | return amis()->DrawerAction()
63 | ->label(__('admin.admin_role.set_permissions'))
64 | ->icon('fa-solid fa-gear')
65 | ->level('link')
66 | ->drawer(
67 | amis()->Drawer()
68 | ->title(__('admin.admin_role.set_permissions'))
69 | ->resizable()
70 | ->closeOnOutside()
71 | ->closeOnEsc()
72 | ->body([
73 | amis()->Form()
74 | ->api(admin_url('system/admin_roles/save_permissions'))
75 | ->initApi($this->getEditGetDataPath())
76 | ->mode('normal')
77 | ->data(['id' => '${id}'])
78 | ->body([
79 | amis()->TreeControl()
80 | ->name('permissions')
81 | ->label()
82 | ->multiple()
83 | ->heightAuto()
84 | ->options(AdminPermissionService::make()->getTree())
85 | ->searchable()
86 | ->cascade()
87 | ->joinValues(false)
88 | ->extractValue()
89 | ->size('full')
90 | ->className('h-full b-none')
91 | ->inputClassName('h-full tree-full')
92 | ->labelField('name')
93 | ->valueField('id'),
94 | ]),
95 | ])
96 | );
97 | }
98 |
99 | #[PostApi(summary: '保存权限')]
100 | public function savePermissions()
101 | {
102 | $data = $this->request->all();
103 |
104 | $result = $this->service->savePermissions($data[$this->service->primaryKey()], $data['permissions'] ?? []);
105 |
106 | return $this->autoResponse($result, __('admin.save'));
107 | }
108 |
109 | protected function form(): Form
110 | {
111 | return $this->baseForm()->body([
112 | amis()->TextControl()->label(__('admin.admin_role.name'))->name('name')->required(),
113 | amis()->TextControl()
114 | ->label(__('admin.admin_role.slug'))
115 | ->name('slug')
116 | ->description(__('admin.admin_role.slug_description'))
117 | ->required(),
118 | ]);
119 | }
120 |
121 | public function detail(): Form
122 | {
123 | return $this->baseDetail()->body([]);
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Controller/System/AdminUsersController.php:
--------------------------------------------------------------------------------
1 | baseCRUD()
32 | ->headerToolbar([
33 | $this->createButton(true),
34 | ...$this->baseHeaderToolBar(),
35 | ])
36 | ->filter($this->baseFilter()->body(
37 | amis()->TextControl('keyword', __('admin.keyword'))
38 | ->size('md')
39 | ->placeholder(__('admin.admin_user.search_username'))
40 | ))
41 | ->itemCheckableOn('${!administrator}')
42 | ->columns([
43 | amis()->TableColumn('id', 'ID')->sortable(),
44 | amis()->TableColumn('avatar', __('admin.admin_user.avatar'))->type('avatar')->src('${avatar}'),
45 | amis()->TableColumn('username', __('admin.username')),
46 | amis()->TableColumn('name', __('admin.admin_user.name')),
47 | amis()->TableColumn('roles', __('admin.admin_user.roles'))->type('each')->items(
48 | amis()->Tag()->label('${name}')->className('my-1')
49 | ),
50 | amis()->TableColumn('enabled', __('admin.extensions.card.status'))->quickEdit(
51 | amis()->SwitchControl()->mode('inline')->disabledOn('${id == 1}')->saveImmediately(true)
52 | ),
53 | amis()->TableColumn('created_at', __('admin.created_at'))->type('datetime')->sortable(true),
54 | $this->rowActions([
55 | $this->rowEditButton(true),
56 | $this->rowDeleteButton()->hiddenOn('${administrator}'),
57 | ]),
58 | ]);
59 |
60 | return $this->baseList($crud);
61 | }
62 |
63 | public function form(): Form
64 | {
65 | return $this->baseForm()->body([
66 | amis()->ImageControl('avatar', __('admin.admin_user.avatar'))->receiver($this->uploadImagePath()),
67 | amis()->TextControl('username', __('admin.username'))->required(),
68 | amis()->TextControl('name', __('admin.admin_user.name'))->required(),
69 | amis()->TextControl('password', __('admin.password'))->type('input-password'),
70 | amis()->TextControl('confirm_password', __('admin.confirm_password'))->type('input-password'),
71 | amis()->SelectControl('roles', __('admin.admin_user.roles'))
72 | ->searchable()
73 | ->multiple()
74 | ->labelField('name')
75 | ->valueField('id')
76 | ->joinValues(false)
77 | ->extractValue()
78 | ->options(AdminRoleService::make()->query()->get(['id', 'name'])),
79 | amis()->SwitchControl('enabled', __('admin.extensions.card.status'))
80 | ->onText(__('admin.extensions.enable'))
81 | ->offText(__('admin.extensions.disable'))
82 | ->disabledOn('${id == 1}')
83 | ->value(1),
84 | ]);
85 | }
86 |
87 | public function detail(): Form
88 | {
89 | return $this->baseDetail()->body([]);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Controller/UserController.php:
--------------------------------------------------------------------------------
1 | baseCRUD()
32 | ->headerToolbar([
33 | $this->createButton(true),
34 | ...$this->baseHeaderToolBar(),
35 | ])
36 | ->filter($this->baseFilter()->body(
37 | amis()->TextControl('keyword', __('admin.keyword'))
38 | ->size('md')
39 | ->placeholder(__('admin.admin_user.search_username'))
40 | ))
41 | ->columns([
42 | amis()->TableColumn('id', 'ID')->sortable(),
43 | amis()->TableColumn('avatar', __('admin.admin_user.avatar'))->type('avatar')->src('${avatar}'),
44 | amis()->TableColumn('username', __('admin.username')),
45 | amis()->TableColumn('name', __('admin.admin_user.name')),
46 | amis()->TableColumn('roles', __('admin.admin_user.roles'))->type('each')->items(
47 | amis()->Tag()->label('${name}')->className('my-1')
48 | ),
49 | amis()->TableColumn('enabled', __('admin.extensions.card.status'))->quickEdit(
50 | amis()->SwitchControl()->mode('inline')->disabledOn('${id == 1}')->saveImmediately(true)
51 | ),
52 | amis()->TableColumn('created_at', __('admin.created_at'))->type('datetime')->sortable(true),
53 | $this->rowActions([
54 | $this->rowEditButton(true),
55 | $this->rowDeleteButton()->hiddenOn('${administrator}'),
56 | ]),
57 | ]);
58 |
59 | return $this->baseList($crud);
60 | }
61 |
62 | public function form(): Form
63 | {
64 | return $this->baseForm()->body([
65 | // amis()->ImageControl('avatar', __('admin.admin_user.avatar'))->receiver($this->uploadImagePath()),
66 | amis()->TextControl('username', __('admin.username'))->required(),
67 | amis()->TextControl('name', __('admin.admin_user.name'))->required(),
68 | amis()->TextControl('password', __('admin.password'))->type('input-password'),
69 | amis()->TextControl('confirm_password', __('admin.confirm_password'))->type('input-password'),
70 | // amis()->SelectControl('roles', __('admin.admin_user.roles'))
71 | // ->searchable()
72 | // ->multiple()
73 | // ->labelField('name')
74 | // ->valueField('id')
75 | // ->joinValues(false)
76 | // ->extractValue()
77 | // ->options(AdminRoleService::make()->query()->get(['id', 'name'])),
78 | amis()->SwitchControl('enabled', __('admin.extensions.card.status'))
79 | ->onText(__('admin.extensions.enable'))
80 | ->offText(__('admin.extensions.disable'))
81 | ->disabledOn('${id == 1}')
82 | ->value(1),
83 | ]);
84 | }
85 |
86 | #[GetApi(summary: '验证码')]
87 | public function captcha()
88 | {
89 | return [];
90 | }
91 |
92 | #[GetApi(summary: '验证码')]
93 | public function example()
94 | {
95 | throw new UnauthorizedException('用户名或密码错误!');
96 | }
97 |
98 | #[GetApi(summary: '菜单列表')]
99 | public function menus()
100 | {
101 | return Admin::responseData(Admin::menu()->all());
102 | }
103 |
104 | #[GetApi(summary: '用户信息', path: 'current-user')]
105 | public function info($id = 0)
106 | {
107 | return [
108 | 'name' => '超级管理员',
109 | 'avatar' => 'http://demo.owladmin.com/admin-assets/default-avatar.png',
110 | 'menus' => [
111 | 'type' => 'dropdown-button',
112 | 'hideCaret' => true,
113 | 'trigger' => 'hover',
114 | 'label' => '超级管理员',
115 | 'className' => 'h-full w-full',
116 | 'btnClassName' => 'navbar-user w-full',
117 | 'menuClassName' => 'min-w-0 p-2',
118 | 'icon' => 'http://demo.owladmin.com/admin-assets/default-avatar.png',
119 | 'buttons' => [
120 | [
121 | 'type' => 'button',
122 | 'iconClassName' => 'pr-2',
123 | 'icon' => 'fa fa-user-gear',
124 | 'label' => '个人设置',
125 | 'onClick' => 'window.location.hash = "#/user_setting"',
126 | ],
127 | [
128 | 'type' => 'button',
129 | 'iconClassName' => 'pr-2',
130 | 'label' => '退出登录',
131 | 'icon' => 'fa-solid fa-right-from-bracket',
132 | 'onClick' => 'window.$owl.logout()',
133 | ],
134 | ],
135 | ],
136 | ];
137 | }
138 |
139 | #[PostApi(summary: '登录')]
140 | public function login()
141 | {
142 | return Admin::responseData([
143 | 'token' => auth('admin')->login(AdminUser::first()),
144 | ]);
145 | throw new UnauthorizedException('用户名或密码错误!');
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/Exception/ApiAmisException.php:
--------------------------------------------------------------------------------
1 | message = $message;
25 | $this->code = $code;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Model/AdminApi.php:
--------------------------------------------------------------------------------
1 | 'json',
30 | ];
31 |
32 | public function templateTitle(): Attribute
33 | {
34 | return Attribute::get(function () {
35 | if (! (new ReflectionClass($this->template))->isSubclassOf(AdminBaseApi::class)) {
36 | return '';
37 | }
38 |
39 | return app($this->template)->getTitle();
40 | });
41 | }
42 |
43 | public function method(): Attribute
44 | {
45 | return Attribute::get(function () {
46 | if (! (new ReflectionClass($this->template))->isSubclassOf(AdminBaseApi::class)) {
47 | return 'any';
48 | }
49 |
50 | $method = app($this->template)->getMethod();
51 |
52 | return in_array($method, self::METHODS) ? $method : 'any';
53 | });
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Model/AdminCodeGenerator.php:
--------------------------------------------------------------------------------
1 | 'array',
23 | 'needs' => 'array',
24 | 'menu_info' => 'array',
25 | 'page_info' => 'array',
26 | 'save_path' => 'array',
27 | ];
28 | }
29 |
--------------------------------------------------------------------------------
/src/Model/AdminMenu.php:
--------------------------------------------------------------------------------
1 | __('admin.admin_menu.route'),
35 | self::TYPE_LINK => __('admin.admin_menu.link'),
36 | self::TYPE_IFRAME => __('admin.admin_menu.iframe'),
37 | self::TYPE_PAGE => __('admin.admin_menu.page'),
38 | ];
39 | }
40 |
41 | /**
42 | * 父级菜单.
43 | */
44 | public function parent(): BelongsTo
45 | {
46 | return $this->belongsTo(self::class, 'parent_id');
47 | }
48 |
49 | public function getTitleAttribute($value)
50 | {
51 | $transKey = ($this->extension ? $this->extension . '::' : '') . "menu.{$value}";
52 | $translate = __($transKey);
53 |
54 | return $translate == $transKey ? $value : $translate;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Model/AdminPage.php:
--------------------------------------------------------------------------------
1 | 'json',
23 | ];
24 | }
25 |
--------------------------------------------------------------------------------
/src/Model/AdminPermission.php:
--------------------------------------------------------------------------------
1 | 'array',
36 | 'http_path' => 'array',
37 | ];
38 |
39 | protected array $fillable = ['sign'];
40 |
41 | public function menus(): BelongsToMany
42 | {
43 | return $this->belongsToMany(AdminMenu::class, 'admin_permission_menu', 'permission_id', 'menu_id')
44 | ->withTimestamps();
45 | }
46 |
47 | public function shouldPassThrough(Request $request): bool
48 | {
49 | if (empty($this->http_method) && empty($this->http_path)) {
50 | return true;
51 | }
52 | $method = $this->http_method;
53 | $matches = array_map(function ($path) use ($method) {
54 | $path = trim(Admin::config('admin.route.prefix'), '/') . $path;
55 | if (Str::contains($path, ':')) {
56 | [$method, $path] = explode(':', $path);
57 | $method = explode(',', $method);
58 | }
59 | return compact('method', 'path');
60 | }, $this->http_path);
61 | foreach ($matches as $match) {
62 | if ($this->matchRequest($match, $request)) {
63 | return true;
64 | }
65 | }
66 | return false;
67 | }
68 |
69 | protected function matchRequest(array $match, Request $request): bool
70 | {
71 | if ($match['path'] == '/') {
72 | $path = '/';
73 | } else {
74 | $path = trim($match['path'], '/');
75 | }
76 | if (! $request->is($path)) {
77 | return false;
78 | }
79 | $method = collect($match['method'])->filter()->map(function ($method) {
80 | return strtoupper($method);
81 | });
82 | return $method->isEmpty() || $method->contains($request->method());
83 | }
84 |
85 | public function deleting(Deleting $event)
86 | {
87 | $event->getMethod()->menus()->detach();
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/Model/AdminRelationship.php:
--------------------------------------------------------------------------------
1 | 'hasOne',
53 | self::TYPE_HAS_MANY => 'hasMany',
54 | self::TYPE_BELONGS_TO => 'belongsTo',
55 | self::TYPE_BELONGS_TO_MANY => 'belongsToMany',
56 | self::TYPE_HAS_ONE_THROUGH => 'hasOneThrough',
57 | self::TYPE_HAS_MANY_THROUGH => 'hasManyThrough',
58 | self::TYPE_MORPH_ONE => 'morphOne',
59 | self::TYPE_MORPH_MANY => 'morphMany',
60 | self::TYPE_MORPH_TO_MANY => 'morphToMany',
61 | ];
62 |
63 | public const TYPE_LABEL_MAP = [
64 | self::TYPE_HAS_ONE => '一对一',
65 | self::TYPE_HAS_MANY => '一对多',
66 | self::TYPE_BELONGS_TO => '一对多(反向)/属于',
67 | self::TYPE_BELONGS_TO_MANY => '多对多',
68 | self::TYPE_HAS_ONE_THROUGH => '远程一对一',
69 | self::TYPE_HAS_MANY_THROUGH => '远程一对多',
70 | self::TYPE_MORPH_ONE => '一对一(多态)',
71 | self::TYPE_MORPH_MANY => '一对多(多态)',
72 | self::TYPE_MORPH_TO_MANY => '多对多(多态)',
73 | ];
74 |
75 | protected $casts = [
76 | 'args' => 'json',
77 | 'extra' => 'json',
78 | ];
79 |
80 | public static function typeOptions()
81 | {
82 | return collect(self::TYPE_MAP)->map(function ($item, $index) {
83 | return [
84 | 'label' => config('app.locale') == 'zh_CN' ? self::TYPE_LABEL_MAP[$index] : self::TYPE_MAP[$index],
85 | 'method' => $item,
86 | 'value' => $index,
87 | ];
88 | })->values();
89 | }
90 |
91 | public function method(): Attribute
92 | {
93 | return Attribute::get(fn () => self::TYPE_MAP[$this->type]);
94 | }
95 |
96 | public function buildArgs()
97 | {
98 | $reflection = new ReflectionClass($this->model);
99 | $params = $reflection->getMethod($this->method)->getParameters();
100 |
101 | $args = [];
102 |
103 | foreach ($params as $item) {
104 | $_value = data_get($this->args, $item->getName());
105 | $args[] = [
106 | 'name' => $item->getName(),
107 | 'value' => filled($_value) ? $_value : $item->getDefaultValue(),
108 | ];
109 | }
110 |
111 | return $args;
112 | }
113 |
114 | public function getPreviewCode()
115 | {
116 | $className = Str::of($this->model)->explode('\\')->pop();
117 | $args = collect($this->buildArgs())
118 | ->pluck('value')
119 | ->map(fn ($item) => is_null($item) ? 'null' : (is_string($item) ? "'{$item}'" : $item))
120 | ->implode(', ');
121 |
122 | return <<title}() {
128 | return \$this->{$this->method}({$args});
129 | }
130 | }
131 | PHP;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/Model/AdminRole.php:
--------------------------------------------------------------------------------
1 | belongsToMany(AdminPermission::class, 'admin_role_permissions', 'role_id', 'permission_id')
26 | ->withTimestamps();
27 | }
28 |
29 | public function deleting(Deleting $event)
30 | {
31 | $event->getModel()->permissions()->detach();
32 | }
33 |
34 | public function users()
35 | {
36 | return $this->belongsToMany(AdminUser::class, 'admin_role_users', 'role_id', 'user_id')->withTimestamps();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Model/AdminSetting.php:
--------------------------------------------------------------------------------
1 | 'json',
25 | ];
26 | }
27 |
--------------------------------------------------------------------------------
/src/Model/AdminUser.php:
--------------------------------------------------------------------------------
1 | setConnection(config('admin.database.connection'));
31 |
32 | parent::__construct($attributes);
33 | }
34 |
35 | public function roles()
36 | {
37 | return $this->belongsToMany(AdminRole::class, 'admin_role_users', 'user_id', 'role_id')->withTimestamps();
38 | }
39 |
40 | // public function getAvatarAttribute(): Attribute
41 | // {
42 | // $storage = \Illuminate\Support\Facades\Storage::disk(Admin::config('admin.upload.disk'));
43 | //
44 | // return Attribute::make(
45 | // get: fn($value) => $value ? admin_resource_full_path($value) : url(Admin::config('admin.default_avatar')),
46 | // set: fn($value) => str_replace($storage->url(''), '', $value)
47 | // );
48 | // }
49 |
50 | public function deleting(Deleting $event)
51 | {
52 | $event->getModel()->roles()->detach();
53 | }
54 |
55 | public function allPermissions(): Collection
56 | {
57 | return $this->roles()->with('permissions')->get()->pluck('permissions')->flatten();
58 | }
59 |
60 | public function can($abilities, $arguments = []): bool
61 | {
62 | if (empty($abilities)) {
63 | return true;
64 | }
65 |
66 | if ($this->isAdministrator()) {
67 | return true;
68 | }
69 |
70 | return $this->roles->pluck('permissions')->flatten()->pluck('slug')->contains($abilities);
71 | }
72 |
73 | public function isAdministrator(): bool
74 | {
75 | return $this->isRole('administrator');
76 | }
77 |
78 | public function isRole(string $role): bool
79 | {
80 | return $this->roles->pluck('slug')->contains($role);
81 | }
82 |
83 | public function inRoles(array $roles = []): bool
84 | {
85 | return $this->roles->pluck('slug')->intersect($roles)->isNotEmpty();
86 | }
87 |
88 | public function visible(array $roles = []): bool
89 | {
90 | if ($this->isAdministrator()) {
91 | return true;
92 | }
93 | if (empty($roles)) {
94 | return false;
95 | }
96 | $roles = array_column($roles, 'slug');
97 |
98 | return $this->inRoles($roles);
99 | }
100 |
101 | public function getAdministratorAttribute()
102 | {
103 | return $this->isAdministrator();
104 | }
105 |
106 | public function getId()
107 | {
108 | return $this->id;
109 | }
110 |
111 | public static function retrieveById($key): ?Authenticatable
112 | {
113 | return self::find($key);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/Model/BaseModel.php:
--------------------------------------------------------------------------------
1 | setConnection(config('admin.database.connection'));
24 | parent::__construct($attributes);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Model/Extension.php:
--------------------------------------------------------------------------------
1 | 'json',
21 | ];
22 |
23 | protected ?string $table = 'admin_extensions';
24 | }
25 |
--------------------------------------------------------------------------------
/src/Model/PersonalAccessToken.php:
--------------------------------------------------------------------------------
1 | setConnection(Admin::config('admin.database.connection'));
23 |
24 | parent::__construct($attributes);
25 | }
26 |
27 | public static function findToken($token)
28 | {
29 | $expiration = config('admin.auth.token_expiration');
30 |
31 | if (! str_contains($token, '|')) {
32 | return static::where('token', hash('sha256', $token))
33 | ->when($expiration, fn ($q) => $q->where('created_at', '>=', now()->subMinutes($expiration)))
34 | ->first();
35 | }
36 |
37 | [$id, $token] = explode('|', $token, 2);
38 |
39 | $instance = static::when($expiration, fn ($q) => $q->where('created_at', '>=', now()->subMinutes($expiration)))
40 | ->find($id);
41 |
42 | if ($instance) {
43 | return hash_equals($instance->token, hash('sha256', $token)) ? $instance : null;
44 | }
45 |
46 | return null;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Service/AdminApiService.php:
--------------------------------------------------------------------------------
1 | query()
30 | ->where('path', $data['path'])
31 | ->when($primaryKey, fn ($q) => $q->where('id', '<>', $primaryKey))
32 | ->exists();
33 |
34 | $routes = AdminCodeGenerator::query()->get()->map(function ($item) {
35 | return $item->menu_info['enabled'] ? ltrim($item->menu_info['route'], '/') : '';
36 | })->filter()->toArray();
37 |
38 | admin_abort_if($exists || in_array(ltrim($data['path'], '/'), $routes), __('admin.apis.path_exists'));
39 | }
40 |
41 | public function saved($model, $isEdit = false)
42 | {
43 | RouteGenerator::refresh();
44 | }
45 |
46 | public function deleted($ids)
47 | {
48 | RouteGenerator::refresh();
49 | }
50 |
51 | public function getApiByPath($path)
52 | {
53 | $api = $this->query()->where('path', $path)->first();
54 |
55 | if (! $api && str_starts_with($path, '/')) {
56 | $api = $this->query()->where('path', ltrim($path, '/'))->first();
57 | }
58 |
59 | return $api;
60 | }
61 |
62 | public function getApiByTemplate($template)
63 | {
64 | return $this->query()->where('template', $template)->first();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Service/AdminBaseService.php:
--------------------------------------------------------------------------------
1 | modelName ?? \Hyperf\DbConnection\Model\Model::class);
52 | }
53 |
54 | public function setModelName($modelName)
55 | {
56 | $this->modelName = $modelName;
57 | }
58 |
59 | public function getTableColumns()
60 | {
61 | if (!$this->tableColumn) {
62 | $this->tableColumn = (new \Hyperf\Database\Schema\Schema)->connection($this->getModel()->getConnectionName())->getSchemaBuilder()->getColumnListing($this->getModel()->getTable());
63 | // $this->tableColumn = Schema::getColumnListing($this->getModel()->getTable());
64 | }
65 | return $this->tableColumn;
66 | }
67 |
68 | /**
69 | * @return Model
70 | */
71 | public function query()
72 | {
73 | return $this->modelName::query();
74 | }
75 |
76 | public function primaryKey()
77 | {
78 | return $this->getModel()->getKeyName();
79 | }
80 |
81 | public function update($primaryKey, array $data)
82 | {
83 | $this->saving($data, $primaryKey);
84 | $columns = $this->getTableColumns();
85 | $model = $this->query()->where($this->primaryKey(), $primaryKey)->first();
86 | if (empty($model)) {
87 | throw new ApiAmisException('数据不存在!');
88 | }
89 | foreach ($data as $k => $v) {
90 | if (!in_array($k, $columns)) {
91 | continue;
92 | }
93 | $model->setAttribute($k, $v);
94 | }
95 | $result = $model->save();
96 | if ($result) {
97 | $this->saved($model, true);
98 | }
99 | return $result;
100 | }
101 |
102 | /**
103 | * 详情 获取数据.
104 | * @param mixed $id
105 | */
106 | public function getDetail($id): null|Builder|Collection|Model
107 | {
108 | $query = $this->query();
109 |
110 | $this->addRelations($query, 'detail');
111 |
112 | return $query->find($id);
113 | }
114 |
115 | /**
116 | * 编辑 获取数据.
117 | * @param mixed $id
118 | */
119 | public function getEditData($id): null|array|Builder|Collection|Model
120 | {
121 | $model = $this->getModel();
122 |
123 | $hidden = collect([$model->getCreatedAtColumn(), $model->getUpdatedAtColumn()])
124 | ->filter(fn($item) => $item !== null)
125 | ->toArray();
126 |
127 | $query = $this->query();
128 |
129 | $this->addRelations($query, 'edit');
130 |
131 | return $query->find($id)->makeHidden($hidden);
132 | }
133 |
134 | /**
135 | * 列表 获取查询.
136 | *
137 | * @return Builder
138 | */
139 | public function listQuery()
140 | {
141 | $query = $this->query();
142 |
143 | // 处理排序
144 | $this->sortable($query);
145 |
146 | // 自动加载 TableColumn 内的关联关系
147 | $this->loadRelations($query);
148 |
149 | // 处理查询
150 | $this->searchable($query);
151 |
152 | // 追加关联关系
153 | $this->addRelations($query);
154 |
155 | return $query;
156 | }
157 |
158 | /**
159 | * 添加关联关系.
160 | *
161 | * 预留钩子, 方便处理只需要添加 [关联] 的情况
162 | *
163 | * @param string $scene 场景: list, detail, edit
164 | * @param mixed $query
165 | */
166 | public function addRelations($query, string $scene = 'list')
167 | {
168 | }
169 |
170 | /**
171 | * 根据 tableColumn 定义的列, 自动加载关联关系.
172 | * @param mixed $query
173 | */
174 | public function loadRelations($query)
175 | {
176 | // 根据当前控制器取出他的page定义的 column字段,如果字段包含 .证明是从关联关系里取,查询模型是否有这个关联关系,有了自动加with
177 | // $pageDefinition = Route::getCurrentRoute()->getController()?->list();
178 | // if (!$pageDefinition instanceof Page) {
179 | // return;
180 | // }
181 | // $columns = $pageDefinition->toArray()['body']->amisSchema['columns'] ?? [];
182 | // $withRelations = [];
183 | // foreach ($columns as $column) {
184 | // // 直接跳过不是 TableColumn 实例跳出
185 | // if (!$column instanceof TableColumn) continue;
186 | // $field = $column->amisSchema['name'];
187 | // // 如果字段名包含点,尝试解析关联关系
188 | // $splitFieldName = explode('.', $field);
189 | // if (count($splitFieldName) > 1) {
190 | // $relationName = $splitFieldName[0];
191 | // // 检查模型上是否存在该方法,如果存在则加入到关联数组中
192 | // if (method_exists($this->modelName, $relationName)) {
193 | // // 避免重复添加相同的关联关系
194 | // $withRelations[$relationName] = $relationName;
195 | // }
196 | // }
197 | // }
198 | // $query->with(array_values($withRelations));
199 | }
200 |
201 | /**
202 | * 排序.
203 | * @param mixed $query
204 | */
205 | public function sortable($query)
206 | {
207 | $orderBy = $this->request->query('orderBy');
208 | $orderDir = $this->request->query('orderDir', null);
209 | if ($orderBy && $orderDir) {
210 | $query->orderBy($orderBy, $orderDir ?? 'asc');
211 | } else {
212 | $query->orderByDesc($this->sortColumn());
213 | }
214 | }
215 |
216 | /**
217 | * 搜索.
218 | * @param mixed $query
219 | */
220 | public function searchable($query)
221 | {
222 | $params = $this->request->query();
223 | collect(array_keys($params))
224 | ->intersect($this->getTableColumns())
225 | ->map(function ($field) use ($query, $params) {
226 | $value = $params[$field] ?? null;
227 | $query->when($value, function ($query) use ($field, $value) {
228 | $query->where($field, 'like', '%' . $value . '%');
229 | });
230 | });
231 | }
232 |
233 | /**
234 | * 列表 排序字段.
235 | *
236 | * @return string
237 | */
238 | public function sortColumn()
239 | {
240 | if (in_array('sort', $this->getTableColumns())) {
241 | return 'sort';
242 | }
243 |
244 | if (in_array($this->getModel()->getKeyName(), $this->getTableColumns())) {
245 | return $this->getModel()->getKeyName();
246 | }
247 | return Arr::first($this->getTableColumns());
248 | }
249 |
250 | /**
251 | * 列表 获取数据.
252 | *
253 | * @return array
254 | */
255 | public function list()
256 | {
257 | $query = $this->listQuery();
258 |
259 | $list = $query->paginate((int)$this->request->input('perPage', 20));
260 | $items = $list->items();
261 | $total = $list->total();
262 |
263 | return compact('items', 'total');
264 | }
265 |
266 | /**
267 | * 新增.
268 | *
269 | * @param mixed $data
270 | * @return bool
271 | */
272 | public function store($data)
273 | {
274 | $this->saving($data);
275 |
276 | $columns = $this->getTableColumns();
277 | $model = $this->getModel();
278 |
279 | foreach ($data as $k => $v) {
280 | if (!in_array($k, $columns)) {
281 | continue;
282 | }
283 |
284 | $model->setAttribute($k, $v);
285 | }
286 |
287 | $result = $model->save();
288 |
289 | if ($result) {
290 | $this->saved($model);
291 | }
292 |
293 | return $result;
294 | }
295 |
296 | /**
297 | * 删除.
298 | *
299 | * @return mixed
300 | */
301 | public function delete(string $ids)
302 | {
303 | $result = $this->query()->whereIn($this->primaryKey(), explode(',', $ids))->delete();
304 |
305 | if ($result) {
306 | $this->deleted($ids);
307 | }
308 |
309 | return $result;
310 | }
311 |
312 | /**
313 | * 快速编辑.
314 | *
315 | * @param mixed $data
316 | * @return true
317 | */
318 | public function quickEdit($data)
319 | {
320 | $rowsDiff = data_get($data, 'rowsDiff', []);
321 |
322 | foreach ($rowsDiff as $item) {
323 | $this->update(Arr::pull($item, $this->primaryKey()), $item);
324 | }
325 |
326 | return true;
327 | }
328 |
329 | /**
330 | * 快速编辑单条
331 | *
332 | * @param mixed $data
333 | * @return bool
334 | */
335 | public function quickEditItem($data)
336 | {
337 | return $this->update(Arr::pull($data, $this->primaryKey()), $data);
338 | }
339 |
340 | /**
341 | * saving 钩子 (执行于新增/修改前).
342 | *
343 | * 可以通过判断 $primaryKey 是否存在来判断是新增还是修改
344 | * @param mixed $data
345 | * @param mixed $primaryKey
346 | */
347 | public function saving(&$data, $primaryKey = '')
348 | {
349 | }
350 |
351 | /**
352 | * saved 钩子 (执行于新增/修改后).
353 | *
354 | * 可以通过 $isEdit 来判断是新增还是修改
355 | * @param mixed $model
356 | * @param mixed $isEdit
357 | */
358 | public function saved($model, $isEdit = false)
359 | {
360 | }
361 |
362 | /**
363 | * deleted 钩子 (执行于删除后).
364 | * @param mixed $ids
365 | */
366 | public function deleted($ids)
367 | {
368 | }
369 |
370 | /**
371 | * 重新排序菜单.
372 | *
373 | * @param mixed $ids
374 | * @return false|int
375 | */
376 | public function reorder($ids)
377 | {
378 | if (check_blank($ids)) {
379 | return false;
380 | }
381 |
382 | $ids = json_decode('[' . str_replace('[', ',[', $ids) . ']');
383 |
384 | $list = collect($this->refreshOrder($ids))->transform(fn($i) => $i * 10)->all();
385 |
386 | $sql = 'update ' . $this->getModel()->getTable() . ' set `order` = case id ';
387 |
388 | foreach ($list as $k => $v) {
389 | $sql .= " when {$k} then {$v} ";
390 | }
391 |
392 | return Db::update($sql . ' else `order` end');
393 | }
394 |
395 | public function refreshOrder($list)
396 | {
397 | $result = collect($list)->filter(fn($i) => !is_array($i))->values()->flip()->toArray();
398 |
399 | collect($list)->filter(fn($i) => is_array($i))->each(function ($item) use (&$result) {
400 | $result = $this->refreshOrder($item) + $result;
401 | });
402 |
403 | return $result;
404 | }
405 | }
406 |
--------------------------------------------------------------------------------
/src/Service/AdminCodeGeneratorService.php:
--------------------------------------------------------------------------------
1 | when($keyword, function ($query) use ($keyword) {
32 | $query->where(function ($q) use ($keyword) {
33 | $q->where('table_name', 'like', "%{$keyword}%")->orWhere('title', 'like', "%{$keyword}%");
34 | });
35 | });
36 | }
37 |
38 | public function store($data)
39 | {
40 | amis_abort_if(
41 | $this->query()->where('table_name', $data['table_name'])->exists(),
42 | __('admin.code_generators.exists_table')
43 | );
44 |
45 | return parent::store($this->filterData($data));
46 | }
47 |
48 | public function update($primaryKey, $data)
49 | {
50 | $exists = $this->query()
51 | ->where('table_name', $data['table_name'])
52 | ->where($this->primaryKey(), '<>', $primaryKey)
53 | ->exists();
54 |
55 | amis_abort_if($exists, __('admin.code_generators.exists_table'));
56 |
57 | return parent::update($primaryKey, $this->filterData($data));
58 | }
59 |
60 | public function filterData($data)
61 | {
62 | admin_abort_if(! data_get($data, 'columns'), __('admin.required', ['attribute' => __('admin.code_generators.column_info')]));
63 |
64 | $data['columns'] = collect($data['columns'])
65 | ->map(fn ($item) => Arr::except($item, ['component_options']))
66 | ->toArray();
67 |
68 | return Arr::except($data, ['table_info', 'table_primary_keys']);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Service/AdminMenuService.php:
--------------------------------------------------------------------------------
1 | modelName = config('admin.models.admin_menu', AdminMenu::class);
31 | }
32 |
33 | public function getTree()
34 | {
35 | $list = $this->query()->orderBy('order')->get()->toArray();
36 |
37 | return array2tree($list);
38 | }
39 |
40 | public function parentIsChild($id, $parent_id)
41 | {
42 | $parent = $this->query()->find($parent_id);
43 |
44 | do {
45 | if ($parent->parent_id == $id) {
46 | return true;
47 | }
48 | // 如果没有parent 则为顶级菜单 退出循环
49 | $parent = $parent->parent;
50 | } while ($parent);
51 |
52 | return false;
53 | }
54 |
55 | public function update($primaryKey, $data)
56 | {
57 | $columns = $this->getTableColumns();
58 |
59 | $parent_id = Arr::get($data, 'parent_id');
60 | if ($parent_id != 0) {
61 | amis_abort_if($this->parentIsChild($primaryKey, $parent_id), __('admin.admin_menu.parent_id_not_allow'));
62 | }
63 |
64 | $model = $this->query()->whereKey($primaryKey)->first();
65 |
66 | $data['id'] = $primaryKey;
67 |
68 | return $this->saveData($data, $columns, $model);
69 | }
70 |
71 | public function store($data)
72 | {
73 | $columns = $this->getTableColumns();
74 |
75 | $model = $this->getModel();
76 |
77 | return $this->saveData($data, $columns, $model);
78 | }
79 |
80 | public function changeHomePage($excludeId = 0)
81 | {
82 | $this->query()->when($excludeId, fn ($query) => $query->where('id', '<>', $excludeId))->update(['is_home' => 0]);
83 | }
84 |
85 | public function list()
86 | {
87 | return ['items' => $this->getTree()];
88 | }
89 |
90 | /**
91 | * 重新排序菜单.
92 | *
93 | * @param mixed $ids
94 | * @return false|int
95 | */
96 | public function reorder($ids)
97 | {
98 | if (check_blank($ids)) {
99 | return false;
100 | }
101 |
102 | $ids = json_decode('[' . str_replace('[', ',[', $ids) . ']');
103 |
104 | $list = collect($this->refreshOrder($ids))->transform(fn ($i) => $i * 10)->all();
105 |
106 | $sql = 'update ' . $this->getModel()->getTable() . ' set `order` = case id ';
107 |
108 | foreach ($list as $k => $v) {
109 | $sql .= " when {$k} then {$v} ";
110 | }
111 |
112 | return Db::update($sql . ' else `order` end');
113 | }
114 |
115 | public function refreshOrder($list)
116 | {
117 | $result = collect($list)->filter(fn ($i) => ! is_array($i))->values()->flip()->toArray();
118 |
119 | collect($list)->filter(fn ($i) => is_array($i))->each(function ($item) use (&$result) {
120 | $result = $this->refreshOrder($item) + $result;
121 | });
122 |
123 | return $result;
124 | }
125 |
126 | /**
127 | * @param mixed $data
128 | * @return bool
129 | */
130 | protected function saveData($data, array $columns, AdminMenu $model)
131 | {
132 | $urlExists = $this->query()
133 | ->where('url', data_get($data, 'url'))
134 | ->when(data_get($data, 'id'), fn ($q) => $q->where('id', '<>', data_get($data, 'id')))
135 | ->exists();
136 |
137 | admin_abort_if($urlExists, __('admin.admin_menu.url_exists'));
138 |
139 | foreach ($data as $k => $v) {
140 | if (! in_array($k, $columns)) {
141 | continue;
142 | }
143 |
144 | $v = $k == 'parent_id' ? intval($v) : $v;
145 |
146 | $model->setAttribute($k, $v);
147 |
148 | if ($k == 'is_home' && $v == 1) {
149 | $this->changeHomePage($model->getKey());
150 | }
151 | }
152 |
153 | return $model->save();
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/Service/AdminPageService.php:
--------------------------------------------------------------------------------
1 | query()
36 | ->where('sign', $data['sign'])
37 | ->when($primaryKey, fn ($q) => $q->where('id', '<>', $primaryKey))
38 | ->exists();
39 |
40 | admin_abort_if($exists, __('admin.pages.sign_exists'));
41 | }
42 |
43 | public function saved($model, $isEdit = false)
44 | {
45 | if ($isEdit) {
46 | cache()->delete($this->cacheKeyPrefix . $model->sign);
47 | }
48 | }
49 |
50 | public function delete(string $ids)
51 | {
52 | $this->query()->whereIn('id', explode(',', $ids))->get()->map(function ($item) {
53 | cache()->delete($this->cacheKeyPrefix . $item->sign);
54 | });
55 |
56 | return parent::delete($ids);
57 | }
58 |
59 | public function getEditData($id): \Hyperf\Database\Model\Collection|Builder|\Hyperf\Database\Model\Model|array|null
60 | {
61 | $data = parent::getEditData($id);
62 |
63 | $data->setAttribute('page', ['schema' => $data->schema]);
64 | $data->setAttribute('schema', '');
65 |
66 | return $data;
67 | }
68 |
69 | /**
70 | * 获取页面结构.
71 | *
72 | * @param mixed $sign
73 | * @return mixed
74 | */
75 | public function get($sign)
76 | {
77 | return cache_has_set($this->cacheKeyPrefix . $sign, function () use ($sign) {
78 | return $this->query()->where('sign', $sign)->value('schema');
79 | });
80 | }
81 |
82 | public function options()
83 | {
84 | return $this->query()->get(['sign as value', 'title as label']);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Service/AdminPermissionService.php:
--------------------------------------------------------------------------------
1 | modelName = Admin::adminPermissionModel();
31 | }
32 |
33 | public function getTree()
34 | {
35 | $list = $this->query()->orderBy('order')->get()->toArray();
36 |
37 | return array2tree($list);
38 | }
39 |
40 | public function parentIsChild($id, $parent_id)
41 | {
42 | $parent = $this->query()->find($parent_id);
43 |
44 | do {
45 | if ($parent->parent_id == $id) {
46 | return true;
47 | }
48 | // 如果没有parent 则为顶级 退出循环
49 | $parent = $parent->parent;
50 | } while ($parent);
51 |
52 | return false;
53 | }
54 |
55 | public function getEditData($id): \Hyperf\Database\Model\Collection|Builder|\Hyperf\Database\Model\Model|array|null
56 | {
57 | $permission = parent::getEditData($id);
58 |
59 | $permission->load(['menus']);
60 |
61 | return $permission;
62 | }
63 |
64 | public function store($data)
65 | {
66 | $this->checkRepeated($data);
67 |
68 | $columns = $this->getTableColumns();
69 |
70 | $model = $this->getModel();
71 |
72 | return $this->saveData($data, $columns, $model);
73 | }
74 |
75 | public function update($primaryKey, $data)
76 | {
77 | $this->checkRepeated($data, $primaryKey);
78 |
79 | $columns = $this->getTableColumns();
80 |
81 | $parent_id = Arr::get($data, 'parent_id');
82 | if ($parent_id != 0) {
83 | amis_abort_if($this->parentIsChild($primaryKey, $parent_id), __('admin.admin_permission.parent_id_not_allow'));
84 | }
85 |
86 | $model = $this->query()->whereKey($primaryKey)->first();
87 |
88 | return $this->saveData($data, $columns, $model);
89 | }
90 |
91 | public function checkRepeated($data, $id = 0)
92 | {
93 | $query = $this->query()->when($id, fn ($query) => $query->where('id', '<>', $id));
94 |
95 | amis_abort_if($query->clone()->where('name', $data['name'])
96 | ->exists(), __('admin.admin_permission.name_already_exists'));
97 |
98 | amis_abort_if($query->clone()->where('slug', $data['slug'])
99 | ->exists(), __('admin.admin_permission.slug_already_exists'));
100 | }
101 |
102 | public function list()
103 | {
104 | return ['items' => $this->getTree()];
105 | }
106 |
107 | /**
108 | * @param mixed $data
109 | * @return bool
110 | */
111 | protected function saveData($data, array $columns, AdminPermission $model)
112 | {
113 | $menus = Arr::pull($data, 'menus');
114 |
115 | foreach ($data as $k => $v) {
116 | if (! in_array($k, $columns)) {
117 | continue;
118 | }
119 |
120 | $model->setAttribute($k, $v);
121 | }
122 |
123 | if ($model->save()) {
124 | $model->menus()->sync(Arr::has($menus, '0.id') ? Arr::pluck($menus, 'id') : $menus);
125 |
126 | return true;
127 | }
128 |
129 | return false;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/Service/AdminRelationshipService.php:
--------------------------------------------------------------------------------
1 | transform(function ($item) {
39 | $item->setAttribute('preview_code', $item->getPreviewCode());
40 | });
41 |
42 | return $list;
43 | }
44 |
45 | public function getAll()
46 | {
47 | return cache()->rememberForever($this->cacheKey, function () {
48 | return self::query()->get();
49 | });
50 | }
51 |
52 | public function saving(&$data, $primaryKey = '')
53 | {
54 | $exists = self::query()
55 | ->where('model', $data['model'])
56 | ->where('title', $data['title'])
57 | ->when($primaryKey, fn ($q) => $q->where('id', '<>', $primaryKey))
58 | ->exists();
59 |
60 | admin_abort_if($exists, __('admin.relationships.rel_name_exists'));
61 |
62 | $methodExists = method_exists($data['model'], $data['title']);
63 |
64 | admin_abort_if($methodExists, __('admin.relationships.rel_name_exists'));
65 | }
66 |
67 | public function saved($model, $isEdit = false)
68 | {
69 | cache()->forget($this->cacheKey);
70 | }
71 |
72 | public function deleted($ids)
73 | {
74 | cache()->forget($this->cacheKey);
75 | }
76 |
77 | public function allModels()
78 | {
79 | $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(app_path('Models')));
80 | $phpFiles = new RegexIterator($iterator, '/^.+\.php$/i', RegexIterator::GET_MATCH);
81 |
82 | foreach ($phpFiles as $phpFile) {
83 | $filePath = $phpFile[0];
84 | require_once $filePath;
85 | }
86 |
87 | $modelDirClass = collect(get_declared_classes())
88 | ->filter(fn ($i) => Str::startsWith($i, 'App\\Models'))
89 | ->toArray();
90 |
91 | $composer = require base_path('/vendor/autoload.php');
92 | $classMap = $composer->getClassMap();
93 | $tables = Database::getTables();
94 |
95 | $models = collect($classMap)
96 | ->keys()
97 | ->filter(fn ($item) => str_contains($item, 'Models\\'))
98 | ->filter(fn ($item) => @class_exists($item))
99 | ->filter(fn ($item) => (new ReflectionClass($item))->isSubclassOf(Model::class))
100 | ->merge($modelDirClass)
101 | ->unique()
102 | ->filter(fn ($item) => in_array(app($item)->getTable(), $tables))
103 | ->values()
104 | ->map(fn ($item) => [
105 | 'label' => Str::of($item)->explode('\\')->pop(),
106 | 'table' => app($item)->getTable(),
107 | 'value' => $item,
108 | ]);
109 |
110 | return compact('tables', 'models');
111 | }
112 |
113 | public function generateModel($table)
114 | {
115 | $className = Str::of($table)->studly()->singular()->value();
116 |
117 | $template = <<put($path, $template);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/Service/AdminRoleService.php:
--------------------------------------------------------------------------------
1 | modelName = Admin::adminRoleModel();
34 | }
35 |
36 | public function getEditData($id): null|array|Builder|Collection|Model
37 | {
38 | $permission = parent::getEditData($id);
39 |
40 | $permission->load(['permissions']);
41 |
42 | return $permission;
43 | }
44 |
45 | public function store($data)
46 | {
47 | $this->checkRepeated($data);
48 |
49 | $columns = $this->getTableColumns();
50 |
51 | $model = $this->getModel();
52 |
53 | foreach ($data as $k => $v) {
54 | if (! in_array($k, $columns)) {
55 | continue;
56 | }
57 |
58 | $model->setAttribute($k, $v);
59 | }
60 |
61 | return $model->save();
62 | }
63 |
64 | public function update($primaryKey, $data)
65 | {
66 | $this->checkRepeated($data, $primaryKey);
67 |
68 | $columns = $this->getTableColumns();
69 |
70 | $model = $this->query()->whereKey($primaryKey)->first();
71 |
72 | foreach ($data as $k => $v) {
73 | if (! in_array($k, $columns)) {
74 | continue;
75 | }
76 |
77 | $model->setAttribute($k, $v);
78 | }
79 |
80 | return $model->save();
81 | }
82 |
83 | /**
84 | * @param mixed $data
85 | * @param mixed $id
86 | * @throws ApiAmisException
87 | */
88 | public function checkRepeated($data, $id = 0)
89 | {
90 | $query = $this->query()->when($id, fn ($query) => $query->where('id', '<>', $id));
91 |
92 | amis_abort_if($query->clone()
93 | ->where('name', $data['name'])
94 | ->exists(), __('admin.admin_role.name_already_exists'));
95 |
96 | amis_abort_if($query->clone()
97 | ->where('slug', $data['slug'])
98 | ->exists(), __('admin.admin_role.slug_already_exists'));
99 | }
100 |
101 | public function savePermissions($primaryKey, $permissions)
102 | {
103 | $model = $this->query()->whereKey($primaryKey)->first();
104 |
105 | return $model->permissions()->sync(
106 | Arr::has($permissions, '0.id') ? Arr::pluck($permissions, 'id') : $permissions
107 | );
108 | }
109 |
110 | public function delete(string $ids)
111 | {
112 | $_ids = explode(',', $ids);
113 | $exists = $this->query()
114 | ->whereIn($this->primaryKey(), $_ids)
115 | ->where('slug', 'administrator')
116 | ->exists();
117 |
118 | admin_abort_if($exists, __('admin.admin_role.cannot_delete'));
119 |
120 | $used = $this->query()
121 | ->whereIn($this->primaryKey(), $_ids)
122 | ->has('users')
123 | ->exists();
124 |
125 | admin_abort_if($used, __('admin.admin_role.used'));
126 |
127 | return parent::delete($ids);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/Service/AdminSettingService.php:
--------------------------------------------------------------------------------
1 | query()->firstOrNew(['key' => $key]);
40 | $setting->values = $value;
41 | $this->clearCache($key);
42 | $setting->save();
43 | return true;
44 | }
45 |
46 | /**
47 | * 批量保存设置.
48 | *
49 | * @return bool
50 | */
51 | public function setMany(array $data)
52 | {
53 | Db::beginTransaction();
54 | try {
55 | foreach ($data as $key => $value) {
56 | $this->set($key, $value);
57 | }
58 | Db::commit();
59 | } catch (Exception $e) {
60 | Db::rollBack();
61 | throw new $e();
62 | }
63 |
64 | return true;
65 | }
66 |
67 | /**
68 | * 批量保存设置项并返回后台响应格式数据.
69 | *
70 | * @throws Exception
71 | */
72 | public function adminSetMany(array $data)
73 | {
74 | $prefix = __('admin.save');
75 | $this->setMany($data);
76 | return Admin::response()->successMessage($prefix . __('admin.successfully'));
77 | }
78 |
79 | /**
80 | * 以数组形式返回所有设置.
81 | *
82 | * @return array
83 | */
84 | public function all()
85 | {
86 | return $this->query()->pluck('values', 'key')->toArray();
87 | }
88 |
89 | /**
90 | * 获取设置项.
91 | *
92 | * @param string $key 设置项key
93 | * @param null|mixed $default 默认值
94 | * @param bool $fresh 是否直接从数据库获取
95 | *
96 | * @return null|mixed
97 | */
98 | public function get(string $key, mixed $default = null, bool $fresh = false)
99 | {
100 | if ($fresh) {
101 | return $this->query()->where('key', $key)->value('values') ?? $default;
102 | }
103 |
104 | $value = cache_has_set($this->getCacheKey($key), function () use ($key) {
105 | return $this->query()->where('key', $key)->value('values');
106 | });
107 |
108 | return $value ?? $default;
109 | }
110 |
111 | /**
112 | * 获取设置项中的某个值
113 | *
114 | * @param string $key 设置项key
115 | * @param string $path 通过点号分隔的路径, 同Arr::get()
116 | * @param null|mixed $default
117 | *
118 | * @return null|array|ArrayAccess|mixed
119 | */
120 | public function arrayGet(string $key, string $path, $default = null)
121 | {
122 | $value = $this->get($key);
123 |
124 | if (is_array($value)) {
125 | return Arr::get($value, $path, $default);
126 | }
127 |
128 | return $default;
129 | }
130 |
131 | /**
132 | * 清除指定设置项.
133 | *
134 | * @return bool
135 | */
136 | public function del(string $key)
137 | {
138 | if ($this->query()->where('key', $key)->delete()) {
139 | $this->clearCache($key);
140 |
141 | return true;
142 | }
143 |
144 | return false;
145 | }
146 |
147 | /**
148 | * 清除指定设置项的缓存.
149 | * @param mixed $key
150 | */
151 | public function clearCache($key)
152 | {
153 | cache()->delete($this->getCacheKey($key));
154 | }
155 |
156 | public function getCacheKey($key)
157 | {
158 | return $this->cacheKeyPrefix . $key;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/Service/AdminUserService.php:
--------------------------------------------------------------------------------
1 | modelName = Admin::adminUserModel();
37 | }
38 |
39 | public function getEditData($id): null|array|Builder|Collection|Model
40 | {
41 | $adminUser = parent::getEditData($id)->makeHidden('password');
42 |
43 | $adminUser->load('roles');
44 |
45 | return $adminUser;
46 | }
47 |
48 | /**
49 | * @param mixed $data
50 | * @throws ApiAmisException
51 | */
52 | public function store($data)
53 | {
54 | $this->checkUsernameUnique($data['username']);
55 |
56 | admin_abort_if(! data_get($data, 'password'), __('admin.required', ['attribute' => __('admin.password')]));
57 |
58 | $this->passwordHandler($data);
59 |
60 | $columns = $this->getTableColumns();
61 |
62 | $model = $this->getModel();
63 |
64 | return $this->saveData($data, $columns, $model);
65 | }
66 |
67 | public function update($primaryKey, $data)
68 | {
69 | $this->checkUsernameUnique($data['username'], $primaryKey);
70 | $this->passwordHandler($data);
71 |
72 | $columns = $this->getTableColumns();
73 |
74 | $model = $this->query()->whereKey($primaryKey)->first();
75 |
76 | return $this->saveData($data, $columns, $model);
77 | }
78 |
79 | public function checkUsernameUnique($username, $id = 0)
80 | {
81 | $exists = $this->query()
82 | ->where('username', $username)
83 | ->when($id, fn ($query) => $query->where('id', '<>', $id))
84 | ->exists();
85 |
86 | admin_abort_if($exists, __('admin.admin_user.username_already_exists'));
87 | }
88 |
89 | public function updateUserSetting($primaryKey, $data): bool
90 | {
91 | $this->passwordHandler($data, $primaryKey);
92 |
93 | return parent::update($primaryKey, $data);
94 | }
95 |
96 | public function passwordHandler(&$data, $id = null)
97 | {
98 | $password = Arr::get($data, 'password');
99 |
100 | if ($password) {
101 | admin_abort_if($password !== Arr::get($data, 'confirm_password'), __('admin.admin_user.password_confirmation'));
102 | /** @var Hash $hash */
103 | $hash = make(Hash::class);
104 | if ($id) {
105 | admin_abort_if(! Arr::get($data, 'old_password'), __('admin.admin_user.old_password_required'));
106 |
107 | $oldPassword = $this->query()->where('id', $id)->value('password');
108 |
109 | admin_abort_if(! $hash->check($data['old_password'], $oldPassword), __('admin.admin_user.old_password_error'));
110 | }
111 | $data['password'] = $hash->make($password);
112 | unset($data['confirm_password'], $data['old_password']);
113 | }
114 | }
115 |
116 | public function list()
117 | {
118 | $keyword = $params['keyword'] ?? null;
119 |
120 | $query = $this->query()
121 | ->with('roles')
122 | ->select(['id', 'name', 'username', 'avatar', 'enabled', 'created_at'])
123 | ->when($keyword, function ($query) use ($keyword) {
124 | $query->where('username', 'like', "%{$keyword}%")->orWhere('name', 'like', "%{$keyword}%");
125 | });
126 |
127 | $this->sortable($query);
128 |
129 | $list = $query->paginate((int)$this->request->query('perPage', 20));
130 | $items = $list->items();
131 | $total = $list->total();
132 |
133 | return compact('items', 'total');
134 | }
135 |
136 | public function delete(string $ids)
137 | {
138 | $exists = $this->query()
139 | ->whereIn($this->primaryKey(), explode(',', $ids))
140 | ->whereHas('roles', fn ($q) => $q->where('slug', 'administrator'))
141 | ->exists();
142 |
143 | admin_abort_if($exists, __('admin.admin_user.cannot_delete'));
144 |
145 | return parent::delete($ids);
146 | }
147 |
148 | /**
149 | * @param mixed $data
150 | * @return bool
151 | */
152 | protected function saveData($data, array $columns, AdminUser $model)
153 | {
154 | $roles = Arr::pull($data, 'roles');
155 |
156 | foreach ($data as $k => $v) {
157 | if (! in_array($k, $columns)) {
158 | continue;
159 | }
160 |
161 | $model->setAttribute($k, $v);
162 | }
163 |
164 | if ($model->save()) {
165 | $model->roles()->sync(Arr::has($roles, '0.id') ? Arr::pluck($roles, 'id') : $roles);
166 |
167 | return true;
168 | }
169 |
170 | return false;
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/Support/Core/Asset.php:
--------------------------------------------------------------------------------
1 | assetsHandler('js', $js);
32 | }
33 |
34 | public function css($css = null)
35 | {
36 | return $this->assetsHandler('css', $css);
37 | }
38 |
39 | public function scripts($scripts = null)
40 | {
41 | return $this->assetsHandler('scripts', $scripts);
42 | }
43 |
44 | public function styles($styles = null)
45 | {
46 | return $this->assetsHandler('styles', $styles);
47 | }
48 |
49 | public function appendNav($appendNav = null)
50 | {
51 | if (is_null($appendNav)) {
52 | return $this->appendNav;
53 | }
54 |
55 | $this->appendNav = $appendNav;
56 |
57 | return $this;
58 | }
59 |
60 | public function prependNav($prependNav = null)
61 | {
62 | if (is_null($prependNav)) {
63 | return $this->prependNav;
64 | }
65 |
66 | $this->prependNav = $prependNav;
67 |
68 | return $this;
69 | }
70 |
71 | private function assetsHandler($type, $assets)
72 | {
73 | if (is_null($assets)) {
74 | return $this->{$type};
75 | }
76 |
77 | if (is_array($assets)) {
78 | $this->{$type} = array_merge($this->{$type}, $assets);
79 | } else {
80 | $this->{$type}[] = $assets;
81 | }
82 |
83 | return $this;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Support/Core/Database.php:
--------------------------------------------------------------------------------
1 | moduleName = $moduleName;
30 | }
31 |
32 | public static function make($moduleName = null)
33 | {
34 | return new self($moduleName);
35 | }
36 |
37 | public function tableName($name)
38 | {
39 | return $this->moduleName . $name;
40 | }
41 |
42 | public function create($tableName, $callback)
43 | {
44 | Schema::create($this->tableName($tableName), $callback);
45 | }
46 |
47 | public function dropIfExists($tableName)
48 | {
49 | Schema::dropIfExists($this->tableName($tableName));
50 | }
51 |
52 | public function initSchema()
53 | {
54 | $this->down();
55 | $this->up();
56 | }
57 |
58 | public function up()
59 | {
60 | $this->create('admin_users', function (Blueprint $table) {
61 | $table->id();
62 | $table->string('username', 120)->unique();
63 | $table->string('password', 80);
64 | $table->tinyInteger('enabled')->default(1);
65 | $table->string('name')->default('');
66 | $table->string('avatar')->nullable();
67 | $table->string('remember_token', 100)->nullable();
68 | $table->timestamps();
69 | });
70 |
71 | $this->create('admin_roles', function (Blueprint $table) {
72 | $table->id();
73 | $table->string('name', 50)->unique();
74 | $table->string('slug', 50)->unique();
75 | $table->timestamps();
76 | });
77 |
78 | $this->create('admin_permissions', function (Blueprint $table) {
79 | $table->id();
80 | $table->string('name', 50)->unique();
81 | $table->string('slug', 50)->unique();
82 | $table->text('http_method')->nullable();
83 | $table->text('http_path')->nullable();
84 | $table->integer('order')->default(0);
85 | $table->integer('parent_id')->default(0);
86 | $table->timestamps();
87 | });
88 |
89 | $this->create('admin_menus', function (Blueprint $table) {
90 | $table->id();
91 | $table->integer('parent_id')->default(0);
92 | $table->integer('order')->default(0);
93 | $table->string('title', 100)->comment('菜单名称');
94 | $table->string('icon', 100)->nullable()->comment('菜单图标');
95 | $table->string('url')->nullable()->comment('菜单路由');
96 | $table->tinyInteger('url_type')->default(1)->comment('路由类型(1:路由,2:外链,3:iframe)');
97 | $table->tinyInteger('visible')->default(1)->comment('是否可见');
98 | $table->tinyInteger('is_home')->default(0)->comment('是否为首页');
99 | $table->tinyInteger('keep_alive')->nullable()->comment('页面缓存');
100 | $table->string('iframe_url')->nullable()->comment('iframe_url');
101 | $table->string('component')->nullable()->comment('菜单组件');
102 | $table->tinyInteger('is_full')->default(0)->comment('是否是完整页面');
103 | $table->string('extension')->nullable()->comment('扩展');
104 |
105 | $table->timestamps();
106 | });
107 |
108 | $this->create('admin_role_users', function (Blueprint $table) {
109 | $table->integer('role_id');
110 | $table->integer('user_id');
111 | $table->index(['role_id', 'user_id']);
112 | $table->timestamps();
113 | });
114 |
115 | $this->create('admin_role_permissions', function (Blueprint $table) {
116 | $table->integer('role_id');
117 | $table->integer('permission_id');
118 | $table->index(['role_id', 'permission_id']);
119 | $table->timestamps();
120 | });
121 |
122 | $this->create('admin_permission_menu', function (Blueprint $table) {
123 | $table->integer('permission_id');
124 | $table->integer('menu_id');
125 | $table->index(['permission_id', 'menu_id']);
126 | $table->timestamps();
127 | });
128 |
129 | // 如果是模块,跳过下面的表
130 | if ($this->moduleName) {
131 | return;
132 | }
133 |
134 | $this->create('admin_code_generators', function (Blueprint $table) {
135 | $table->id();
136 | $table->string('title')->default('')->comment('名称');
137 | $table->string('table_name')->default('')->comment('表名');
138 | $table->string('primary_key')->default('id')->comment('主键名');
139 | $table->string('model_name')->default('')->comment('模型名');
140 | $table->string('controller_name')->default('')->comment('控制器名');
141 | $table->string('service_name')->default('')->comment('服务名');
142 | $table->longText('columns')->comment('字段信息');
143 | $table->tinyInteger('need_timestamps')->default(0)->comment('是否需要时间戳');
144 | $table->tinyInteger('soft_delete')->default(0)->comment('是否需要软删除');
145 | $table->text('needs')->nullable()->comment('需要生成的代码');
146 | $table->text('menu_info')->nullable()->comment('菜单信息');
147 | $table->text('page_info')->nullable()->comment('页面信息');
148 | $table->text('save_path')->nullable()->comment('保存位置');
149 | $table->timestamps();
150 | });
151 |
152 | $this->create('admin_settings', function (Blueprint $table) {
153 | $table->string('key')->default('');
154 | $table->longText('values')->nullable();
155 | $table->timestamps();
156 | });
157 |
158 | $this->create('admin_extensions', function (Blueprint $table) {
159 | $table->id();
160 | $table->string('name', 100)->unique();
161 | $table->tinyInteger('is_enabled')->default(0);
162 | $table->timestamps();
163 | });
164 |
165 | $this->create('admin_pages', function (Blueprint $table) {
166 | $table->id();
167 | $table->string('title')->comment('页面名称');
168 | $table->string('sign')->comment('页面标识');
169 | $table->longText('schema')->comment('页面结构');
170 | $table->timestamps();
171 | });
172 |
173 | $this->create('admin_relationships', function (Blueprint $table) {
174 | $table->id();
175 | $table->string('model')->comment('模型');
176 | $table->string('title')->comment('关联名称');
177 | $table->string('type')->comment('关联类型');
178 | $table->string('remark')->comment('关联名称')->nullable();
179 | $table->text('args')->comment('关联参数')->nullable();
180 | $table->text('extra')->comment('额外参数')->nullable();
181 | $table->timestamps();
182 | });
183 |
184 | $this->create('admin_apis', function (Blueprint $table) {
185 | $table->id();
186 | $table->string('title')->comment('接口名称');
187 | $table->string('path')->comment('接口路径');
188 | $table->string('template')->comment('接口模板');
189 | $table->tinyInteger('enabled')->default(1)->comment('是否启用');
190 | $table->longText('args')->comment('接口参数')->nullable();
191 | $table->timestamps();
192 | });
193 | }
194 |
195 | public function down()
196 | {
197 | $this->dropIfExists('admin_users');
198 | $this->dropIfExists('admin_roles');
199 | $this->dropIfExists('admin_permissions');
200 | $this->dropIfExists('admin_menus');
201 | $this->dropIfExists('admin_role_users');
202 | $this->dropIfExists('admin_role_permissions');
203 | $this->dropIfExists('admin_permission_menu');
204 |
205 | // 如果是模块,跳过下面的表
206 | if ($this->moduleName) {
207 | return;
208 | }
209 |
210 | $this->dropIfExists('admin_code_generators');
211 | $this->dropIfExists('admin_settings');
212 | $this->dropIfExists('admin_extensions');
213 | $this->dropIfExists('admin_pages');
214 | $this->dropIfExists('admin_relationships');
215 | $this->dropIfExists('admin_apis');
216 | }
217 |
218 | /**
219 | * 填充初始数据.
220 | */
221 | public function fillInitialData()
222 | {
223 | $data = function ($data) {
224 | foreach ($data as $k => $v) {
225 | if (is_array($v)) {
226 | $data[$k] = "['" . implode("','", $v) . "']";
227 | }
228 | }
229 | $now = date('Y-m-d H:i:s');
230 |
231 | return array_merge($data, ['created_at' => $now, 'updated_at' => $now]);
232 | };
233 |
234 | $adminUser = Db::table($this->tableName('admin_users'));
235 | $adminMenu = Db::table($this->tableName('admin_menus'));
236 | $adminPermission = Db::table($this->tableName('admin_permissions'));
237 | $adminRole = Db::table($this->tableName('admin_roles'));
238 |
239 | // 创建初始用户
240 | $adminUser->truncate();
241 | $adminUser->insert($data([
242 | 'username' => 'admin',
243 | 'password' => $this->bcrypt('admin'),
244 | 'name' => 'Administrator',
245 | ]));
246 |
247 | // 创建初始角色
248 | $adminRole->truncate();
249 | $adminRole->insert($data([
250 | 'name' => 'Administrator',
251 | 'slug' => 'administrator',
252 | ]));
253 |
254 | // 用户 - 角色绑定
255 | Db::table($this->tableName('admin_role_users'))->truncate();
256 | Db::table($this->tableName('admin_role_users'))->insert($data([
257 | 'role_id' => 1,
258 | 'user_id' => 1,
259 | ]));
260 |
261 | // 创建初始权限
262 | $adminPermission->truncate();
263 | $adminPermission->insert([
264 | $data(['name' => '首页', 'slug' => 'home', 'http_path' => ['/home*'], 'parent_id' => 0]),
265 | $data(['name' => '系统', 'slug' => 'system', 'http_path' => '', 'parent_id' => 0]),
266 | $data(['name' => '管理员', 'slug' => 'admin_users', 'http_path' => ['/admin_users*'], 'parent_id' => 2]),
267 | $data(['name' => '角色', 'slug' => 'roles', 'http_path' => ['/roles*'], 'parent_id' => 2]),
268 | $data(['name' => '权限', 'slug' => 'permissions', 'http_path' => ['/permissions*'], 'parent_id' => 2]),
269 | $data(['name' => '菜单', 'slug' => 'menus', 'http_path' => ['/menus*'], 'parent_id' => 2]),
270 | $data(['name' => '设置', 'slug' => 'settings', 'http_path' => ['/settings*'], 'parent_id' => 2]),
271 | ]);
272 |
273 | // 角色 - 权限绑定
274 | Db::table($this->tableName('admin_role_permissions'))->truncate();
275 | $permissionIds = Db::table($this->tableName('admin_permissions'))->orderBy('id')->pluck('id');
276 | foreach ($permissionIds as $id) {
277 | Db::table($this->tableName('admin_role_permissions'))->insert($data([
278 | 'role_id' => 1,
279 | 'permission_id' => $id,
280 | ]));
281 | }
282 |
283 | // 创建初始菜单
284 | $adminMenu->truncate();
285 | $adminMenu->insert([
286 | $data([
287 | 'parent_id' => 0,
288 | 'title' => 'dashboard',
289 | 'icon' => 'mdi:chart-line',
290 | 'url' => '/home/dashboard',
291 | 'is_home' => 1,
292 | ]),
293 | $data([
294 | 'parent_id' => 0,
295 | 'title' => 'admin_system',
296 | 'icon' => 'material-symbols:settings-outline',
297 | 'url' => '/system',
298 | 'is_home' => 0,
299 | ]),
300 | $data([
301 | 'parent_id' => 2,
302 | 'title' => 'admin_users',
303 | 'icon' => 'ph:user-gear',
304 | 'url' => '/system/admin_users',
305 | 'is_home' => 0,
306 | ]),
307 | $data([
308 | 'parent_id' => 2,
309 | 'title' => 'admin_roles',
310 | 'icon' => 'carbon:user-role',
311 | 'url' => '/system/admin_roles',
312 | 'is_home' => 0,
313 | ]),
314 | $data([
315 | 'parent_id' => 2,
316 | 'title' => 'admin_permission',
317 | 'icon' => 'fluent-mdl2:permissions',
318 | 'url' => '/system/admin_permissions',
319 | 'is_home' => 0,
320 | ]),
321 | $data([
322 | 'parent_id' => 2,
323 | 'title' => 'admin_menu',
324 | 'icon' => 'ant-design:menu-unfold-outlined',
325 | 'url' => '/system/admin_menus',
326 | 'is_home' => 0,
327 | ]),
328 | $data([
329 | 'parent_id' => 2,
330 | 'title' => 'admin_setting',
331 | 'icon' => 'akar-icons:settings-horizontal',
332 | 'url' => '/system/settings',
333 | 'is_home' => 0,
334 | ]),
335 | ]);
336 |
337 | // 权限 - 菜单绑定
338 | Db::table($this->tableName('admin_permission_menu'))->truncate();
339 | $menus = $adminMenu->get();
340 | foreach ($menus as $menu) {
341 | $_list = [];
342 | $_list[] = $data(['permission_id' => $menu->id, 'menu_id' => $menu->id]);
343 |
344 | if ($menu->parent_id != 0) {
345 | $_list[] = $data(['permission_id' => $menu->parent_id, 'menu_id' => $menu->id]);
346 | }
347 |
348 | Db::table($this->tableName('admin_permission_menu'))->insert($_list);
349 | }
350 | }
351 |
352 | public static function getTables()
353 | {
354 | try {
355 | return collect(json_decode(json_encode(Schema::getAllTables()), true))
356 | ->map(fn ($i) => config('database.default') == 'sqlite' ? $i['name'] : array_shift($i))
357 | ->toArray();
358 | } catch (Throwable $e) {
359 | }
360 |
361 | // laravel 11+
362 | return array_column(Schema::getAllTables(), 'name');
363 | }
364 |
365 | private function bcrypt(string $string)
366 | {
367 | return $string;
368 | }
369 | }
370 |
--------------------------------------------------------------------------------
/src/Support/Core/Hash.php:
--------------------------------------------------------------------------------
1 | rounds = $options['rounds'] ?? $this->rounds;
32 | $this->verifyAlgorithm = $options['verify'] ?? $this->verifyAlgorithm;
33 | }
34 |
35 | /**
36 | * Hash the given value.
37 | *
38 | * @param string $value
39 | * @param array $options
40 | * @return string
41 | *
42 | * @throws \RuntimeException
43 | */
44 | public function make($value, array $options = [])
45 | {
46 | $hash = password_hash($value, PASSWORD_BCRYPT, [
47 | 'cost' => $this->cost($options),
48 | ]);
49 |
50 | if ($hash === false) {
51 | throw new \RuntimeException('Bcrypt hashing not supported.');
52 | }
53 |
54 | return $hash;
55 | }
56 |
57 | /**
58 | * Check the given plain value against a hash.
59 | *
60 | * @param string $value
61 | * @param string $hashedValue
62 | * @param array $options
63 | * @return bool
64 | *
65 | * @throws \RuntimeException
66 | */
67 | public function check($value, $hashedValue, array $options = [])
68 | {
69 | if ($this->verifyAlgorithm && !$this->isUsingCorrectAlgorithm($hashedValue)) {
70 | throw new RuntimeException('This password does not use the Bcrypt algorithm.');
71 | }
72 |
73 | if (is_null($hashedValue) || strlen($hashedValue) === 0) {
74 | return false;
75 | }
76 |
77 | return password_verify($value, $hashedValue);
78 | }
79 |
80 | /**
81 | * Check if the given hash has been hashed using the given options.
82 | *
83 | * @param string $hashedValue
84 | * @param array $options
85 | * @return bool
86 | */
87 | public function needsRehash($hashedValue, array $options = [])
88 | {
89 | return password_needs_rehash($hashedValue, PASSWORD_BCRYPT, [
90 | 'cost' => $this->cost($options),
91 | ]);
92 | }
93 |
94 | /**
95 | * Verifies that the configuration is less than or equal to what is configured.
96 | *
97 | * @internal
98 | */
99 | public function verifyConfiguration($value)
100 | {
101 | return $this->isUsingCorrectAlgorithm($value) && $this->isUsingValidOptions($value);
102 | }
103 |
104 | /**
105 | * Verify the hashed value's algorithm.
106 | *
107 | * @param string $hashedValue
108 | * @return bool
109 | */
110 | protected function isUsingCorrectAlgorithm($hashedValue)
111 | {
112 | return $this->info($hashedValue)['algoName'] === 'bcrypt';
113 | }
114 | /**
115 | * Get information about the given hashed value.
116 | *
117 | * @param string $hashedValue
118 | * @return array
119 | */
120 | public function info($hashedValue)
121 | {
122 | return password_get_info($hashedValue);
123 | }
124 |
125 |
126 | /**
127 | * Verify the hashed value's options.
128 | *
129 | * @param string $hashedValue
130 | * @return bool
131 | */
132 | protected function isUsingValidOptions($hashedValue)
133 | {
134 | ['options' => $options] = $this->info($hashedValue);
135 |
136 | if (!is_int($options['cost'] ?? null)) {
137 | return false;
138 | }
139 |
140 | if ($options['cost'] > $this->rounds) {
141 | return false;
142 | }
143 |
144 | return true;
145 | }
146 |
147 | /**
148 | * Set the default password work factor.
149 | *
150 | * @param int $rounds
151 | * @return $this
152 | */
153 | public function setRounds($rounds)
154 | {
155 | $this->rounds = (int)$rounds;
156 |
157 | return $this;
158 | }
159 |
160 | /**
161 | * Extract the cost value from the options array.
162 | *
163 | * @param array $options
164 | * @return int
165 | */
166 | protected function cost(array $options = [])
167 | {
168 | return $options['rounds'] ?? $this->rounds;
169 | }
170 | }
--------------------------------------------------------------------------------
/src/Support/Core/Menu.php:
--------------------------------------------------------------------------------
1 | userMenus();
31 | array_map(function ($item) use ($menus) {
32 | $menus->push($this->formatItem($item));
33 | }, $this->menus);
34 | $menus->sortBy('order')
35 | ->values()
36 | ->toArray();
37 | return array_merge($this->list2Menu($menus), $this->extra());
38 | }
39 |
40 | public function userMenus()
41 | {
42 | if (!Admin::config('admin.auth.enable')) {
43 | return collect([]);
44 | }
45 |
46 | /** @var AdminUser $user */
47 | $user = Admin::user();
48 |
49 | if ($user->isAdministrator() || Admin::config('admin.auth.permission') === false) {
50 | $list = AdminMenuService::make()->query()->orderBy('order')->get();
51 | } else {
52 | $user->load('roles.permissions.menus');
53 | $list = $user->roles
54 | ->pluck('permissions')
55 | ->flatten()
56 | ->pluck('menus')
57 | ->flatten()
58 | ->unique('id')
59 | ->sortBy('order');
60 | }
61 |
62 | return $list;
63 | }
64 |
65 | public function list2Menu($list, $parentId = 0, $parentName = ''): array
66 | {
67 | $data = [];
68 | foreach ($list as $key => $item) {
69 | if ($item['parent_id'] == $parentId) {
70 | $_component = match ($item['url_type']) {
71 | Admin::adminMenuModel()::TYPE_IFRAME => 'iframe',
72 | Admin::adminMenuModel()::TYPE_PAGE => 'amis',
73 | default => data_get($item, 'component') ?? 'amis'
74 | };
75 |
76 | $idStr = "[{$item['id']}]";
77 | $_temp = [
78 | 'name' => $parentName ? $parentName . '-' . $idStr : $idStr,
79 | 'path' => $item['url'],
80 | 'component' => $_component,
81 | 'is_home' => $item['is_home'],
82 | 'iframe_url' => $item['iframe_url'] ?? '',
83 | 'url_type' => $item['url_type'] ?? Admin::adminMenuModel()::TYPE_ROUTE,
84 | 'keep_alive' => $item['keep_alive'] ?? 0,
85 | 'is_full' => $item['is_full'] ?? 0,
86 | 'is_link' => $item['url_type'] == Admin::adminMenuModel()::TYPE_LINK,
87 | 'page_sign' => $item['url_type'] == Admin::adminMenuModel()::TYPE_PAGE ? data_get($item, 'component') : '',
88 | 'meta' => [
89 | 'title' => $item['title'],
90 | 'icon' => $item['icon'] ?? '-',
91 | 'hide' => $item['visible'] == 0,
92 | 'order' => $item['order'],
93 | ],
94 | ];
95 |
96 | $children = $this->list2Menu($list, (int)$item['id'], $_temp['name']);
97 |
98 | if (!empty($children)) {
99 | $_temp['component'] = 'amis';
100 | $_temp['children'] = $children;
101 | }
102 |
103 | $data[] = $_temp;
104 | if (!in_array($_temp['path'], Admin::config('admin.route.without_extra_routes')) && $item['url_type'] != Admin::adminMenuModel()::TYPE_PAGE) {
105 | array_push($data, ...$this->generateRoute($_temp));
106 | }
107 | unset($list[$key]);
108 | }
109 | }
110 | return $data;
111 | }
112 |
113 | public function generateRoute($item): array
114 | {
115 | $url = $item['path'] ?? '';
116 | $url = preg_replace('/\?.*/', '', $url);
117 |
118 | if (!$url || array_key_exists('children', $item)) {
119 | return [];
120 | }
121 |
122 | $menu = fn($action, $path) => [
123 | 'name' => $item['name'] . '-' . $action,
124 | 'path' => $url . $path,
125 | 'component' => $item['url_type'] == 3 ? 'iframe' : (data_get($item, 'component') ?? 'amis'),
126 | 'meta' => [
127 | 'hide' => true,
128 | 'icon' => Arr::get($item, 'meta.icon'),
129 | 'title' => Arr::get($item, 'meta.title') . ' - ' . __('admin.' . $action),
130 | ],
131 | ];
132 |
133 | return [
134 | $menu('create', '/create'),
135 | $menu('show', '/:id'),
136 | $menu('edit', '/:id/edit'),
137 | ];
138 | }
139 |
140 | public function add($menus)
141 | {
142 | $this->menus = array_merge($this->menus, $menus);
143 |
144 | return $this;
145 | }
146 |
147 | public function formatItem($item)
148 | {
149 | return array_merge([
150 | 'title' => '',
151 | 'url' => '',
152 | 'url_type' => 1,
153 | 'icon' => '',
154 | 'parent_id' => 0,
155 | 'id' => 999,
156 | 'is_home' => 0,
157 | 'visible' => 1,
158 | 'order' => 99,
159 | ], $item);
160 | }
161 |
162 | /**
163 | * 额外菜单.
164 | *
165 | * @return array|array[]
166 | */
167 | public function extra()
168 | {
169 | $extraMenus = [];
170 |
171 | if (Admin::config('admin.auth.enable')) {
172 | $extraMenus[] = [
173 | 'name' => 'user_setting',
174 | 'path' => '/user_setting',
175 | 'component' => 'amis',
176 | 'meta' => [
177 | 'hide' => true,
178 | 'title' => __('admin.user_setting'),
179 | 'icon' => 'material-symbols:manage-accounts',
180 | 'singleLayout' => 'basic',
181 | ],
182 | ];
183 | }
184 |
185 | if (Admin::config('admin.show_development_tools')) {
186 | $extraMenus = array_merge($extraMenus, $this->devToolMenus());
187 | }
188 |
189 | return $extraMenus;
190 | }
191 |
192 | /**
193 | * 开发者工具菜单.
194 | *
195 | * @return array[]
196 | */
197 | public function devToolMenus()
198 | {
199 | return [
200 | [
201 | 'name' => 'dev_tools',
202 | 'path' => '/dev_tools',
203 | 'component' => 'amis',
204 | 'meta' => [
205 | 'title' => __('admin.developer'),
206 | 'icon' => 'fluent:window-dev-tools-20-regular',
207 | ],
208 | 'children' => [
209 | [
210 | 'name' => 'dev_tools_extensions',
211 | 'path' => '/dev_tools/extensions',
212 | 'component' => 'amis',
213 | 'meta' => [
214 | 'title' => __('admin.extensions.menu'),
215 | 'icon' => 'ion:extension-puzzle-outline',
216 | ],
217 | ],
218 | [
219 | 'name' => 'dev_tools_pages',
220 | 'path' => '/dev_tools/pages',
221 | 'component' => 'amis',
222 | 'meta' => [
223 | 'title' => __('admin.pages.menu'),
224 | 'icon' => 'iconoir:multiple-pages',
225 | ],
226 | ],
227 | [
228 | 'name' => 'dev_tools_relationships',
229 | 'path' => '/dev_tools/relationships',
230 | 'component' => 'amis',
231 | 'meta' => [
232 | 'title' => __('admin.relationships.menu'),
233 | 'icon' => 'ant-design:node-index-outlined',
234 | ],
235 | ],
236 | [
237 | 'name' => 'dev_tools_apis',
238 | 'path' => '/dev_tools/apis',
239 | 'component' => 'amis',
240 | 'meta' => [
241 | 'title' => __('admin.apis.menu'),
242 | 'icon' => 'ant-design:api-outlined',
243 | ],
244 | ],
245 | [
246 | 'name' => 'dev_tools_code_generator',
247 | 'path' => '/dev_tools/code_generator',
248 | 'component' => 'amis',
249 | 'meta' => [
250 | 'title' => __('admin.code_generator'),
251 | 'icon' => 'ic:baseline-code',
252 | ],
253 | ],
254 | [
255 | 'name' => 'dev_tools_editor',
256 | 'path' => '/dev_tools/editor',
257 | 'component' => 'editor',
258 | 'meta' => [
259 | 'title' => __('admin.visual_editor'),
260 | 'icon' => 'mdi:monitor-edit',
261 | ],
262 | ],
263 | ],
264 | ],
265 | ];
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/src/Support/helpers.php:
--------------------------------------------------------------------------------
1 | $item) {
28 | if ($item['parent_id'] == $parentId) {
29 | $children = array2tree($list, (int) $item['id']);
30 | ! empty($children) && $item['children'] = $children;
31 | $data[] = $item;
32 | unset($list[$key]);
33 | }
34 | }
35 | return $data;
36 | }
37 | }
38 |
39 | if (! function_exists('admin_abort_if')) {
40 | /**
41 | * 生成树状数据.
42 | *
43 | * @param mixed $error
44 | *
45 | * @return array
46 | */
47 | function admin_abort_if($error, string $message = '')
48 | {
49 | if ($error) {
50 | throw new ApiAmisException($message);
51 | }
52 | }
53 | }
54 |
55 | if (! function_exists('amis_abort_if')) {
56 | /**
57 | * 生成树状数据.
58 | *
59 | * @param mixed $error
60 | *
61 | * @return array
62 | */
63 | function amis_abort_if($error, string $message = '')
64 | {
65 | if ($error) {
66 | throw new ApiAmisException($message);
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Traits/AssetsTrait.php:
--------------------------------------------------------------------------------
1 | get(Asset::class);
26 | }
27 |
28 | /**
29 | * 加载 js 文件.
30 | *
31 | * @param null|mixed $js
32 | * @return Asset
33 | */
34 | public static function js($js = null)
35 | {
36 | return static::asset()->js($js);
37 | }
38 |
39 | /**
40 | * 加载 css 文件.
41 | *
42 | * @param null|mixed $css
43 | * @return Asset
44 | */
45 | public static function css($css = null)
46 | {
47 | return static::asset()->css($css);
48 | }
49 |
50 | /**
51 | * 加载 js 脚本.
52 | *
53 | * @param null|mixed $scripts
54 | * @return Asset
55 | */
56 | public static function scripts($scripts = null)
57 | {
58 | return static::asset()->scripts($scripts);
59 | }
60 |
61 | /**
62 | * 加载样式表.
63 | *
64 | * @param null|mixed $styles
65 | * @return Asset
66 | */
67 | public static function styles($styles = null)
68 | {
69 | return static::asset()->styles($styles);
70 | }
71 |
72 | public static function getAssets()
73 | {
74 | return [
75 | 'js' => static::asset()->js(),
76 | 'css' => static::asset()->css(),
77 | 'scripts' => static::asset()->scripts(),
78 | 'styles' => static::asset()->styles(),
79 | ];
80 | }
81 |
82 | /**
83 | * 在后面添加 Nav.
84 | *
85 | * @param null|mixed $appendNav
86 | * @return Asset
87 | */
88 | public static function appendNav($appendNav = null)
89 | {
90 | return static::asset()->appendNav($appendNav);
91 | }
92 |
93 | /**
94 | * 在前面添加 Nav.
95 | *
96 | * @param null|mixed $prependNav
97 | * @return Asset
98 | */
99 | public static function prependNav($prependNav = null)
100 | {
101 | return static::asset()->prependNav($prependNav);
102 | }
103 |
104 | public static function getNav()
105 | {
106 | return [
107 | 'appendNav' => static::asset()->appendNav(),
108 | 'prependNav' => static::asset()->prependNav(),
109 | ];
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Traits/HasApiBase.php:
--------------------------------------------------------------------------------
1 | request->query('_action') == 'getData';
25 | }
26 |
27 | /**
28 | * 是否为导出数据请求
29 | *
30 | * @return bool
31 | */
32 | public function actionOfExport()
33 | {
34 | return $this->request->query('_action') == 'export';
35 | }
36 |
37 | /**
38 | * 是否为快速编辑数据请求
39 | *
40 | * @return bool
41 | */
42 | public function actionOfQuickEdit()
43 | {
44 | return $this->request->query('_action') == 'quickEdit';
45 | }
46 |
47 | /**
48 | * 是否为快速编辑数据请求
49 | *
50 | * @return bool
51 | */
52 | public function actionOfQuickEditItem()
53 | {
54 | return $this->request->query('_action') == 'quickEditItem';
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Traits/HasElementTrait.php:
--------------------------------------------------------------------------------
1 | Page()->className('m:overflow-auto');
37 | }
38 |
39 | /**
40 | * 返回列表按钮.
41 | */
42 | protected function backButton(): OtherAction
43 | {
44 | $path = str_replace(config('admin.route.prefix'), '', $this->request->path());
45 | $script = sprintf('window.$owl.hasOwnProperty(\'closeTabByPath\') && window.$owl.closeTabByPath(\'%s\')', $path);
46 | return amis()->OtherAction()
47 | ->label(__('admin.back'))
48 | ->icon('fa-solid fa-chevron-left')
49 | ->level('primary')
50 | ->onClick('window.history.back();' . $script);
51 | }
52 |
53 | /**
54 | * 批量删除按钮.
55 | */
56 | protected function bulkDeleteButton(): AjaxAction
57 | {
58 | return amis()->AjaxAction()
59 | ->api($this->getBulkDeletePath())
60 | ->icon('fa-solid fa-trash-can')
61 | ->label(__('admin.delete'))
62 | ->confirmText(__('admin.confirm_delete'));
63 | }
64 |
65 | /**
66 | * 新增按钮.
67 | */
68 | protected function createButton(bool $dialog = false, string $dialogSize = ''): DialogAction|LinkAction
69 | {
70 | if ($dialog) {
71 | $form = $this->form(false)->api($this->getStorePath())->onEvent([]);
72 | $button = amis()->DialogAction()->dialog(
73 | amis()->Dialog()->title(__('admin.create'))->body($form)->size($dialogSize)
74 | );
75 | } else {
76 | $button = amis()->LinkAction()->link($this->getCreatePath());
77 | }
78 |
79 | return $button->label(__('admin.create'))->icon('fa fa-add')->level('primary');
80 | }
81 |
82 | /**
83 | * 行编辑按钮.
84 | */
85 | protected function rowEditButton(bool $dialog = false, string $dialogSize = ''): DialogAction|LinkAction
86 | {
87 | if ($dialog) {
88 | $form = $this->form(true)
89 | ->api($this->getUpdatePath())
90 | ->initApi($this->getEditGetDataPath())
91 | ->redirect('')
92 | ->onEvent([]);
93 |
94 | $button = amis()->DialogAction()->dialog(
95 | amis()->Dialog()->title(__('admin.edit'))->body($form)->size($dialogSize)
96 | );
97 | } else {
98 | $button = amis()->LinkAction()->link($this->getEditPath());
99 | }
100 |
101 | return $button->label(__('admin.edit'))->icon('fa-regular fa-pen-to-square')->level('link');
102 | }
103 |
104 | /**
105 | * 行详情按钮.
106 | */
107 | protected function rowShowButton(bool $dialog = false, string $dialogSize = ''): DialogAction|LinkAction
108 | {
109 | if ($dialog) {
110 | $button = amis()->DialogAction()->dialog(
111 | amis()->Dialog()->title(__('admin.show'))->body($this->detail('$id'))->size($dialogSize)
112 | );
113 | } else {
114 | $button = amis()->LinkAction()->link($this->getShowPath());
115 | }
116 |
117 | return $button->label(__('admin.show'))->icon('fa-regular fa-eye')->level('link');
118 | }
119 |
120 | /**
121 | * 行删除按钮.
122 | */
123 | protected function rowDeleteButton(): AjaxAction
124 | {
125 | return amis()->AjaxAction()
126 | ->label(__('admin.delete'))
127 | ->icon('fa-regular fa-trash-can')
128 | ->level('link')
129 | ->confirmText(__('admin.confirm_delete'))
130 | ->api($this->getDeletePath());
131 | }
132 |
133 | /**
134 | * 操作列.
135 | *
136 | * @param bool $dialog
137 | */
138 | protected function rowActions(array|bool $dialog = false, string $dialogSize = ''): Operation
139 | {
140 | if (is_array($dialog)) {
141 | return amis()->Operation()->label(__('admin.actions'))->buttons($dialog);
142 | }
143 |
144 | return amis()->Operation()->label(__('admin.actions'))->buttons([
145 | $this->rowShowButton($dialog, $dialogSize),
146 | $this->rowEditButton($dialog, $dialogSize),
147 | $this->rowDeleteButton(),
148 | ]);
149 | }
150 |
151 | /**
152 | * 基础筛选器.
153 | */
154 | protected function baseFilter(): Form
155 | {
156 | return amis()->Form()
157 | ->panelClassName('base-filter')
158 | ->title('')
159 | ->actions([
160 | amis()->Button()->label(__('admin.reset'))->actionType('clear-and-submit'),
161 | amis('submit')->label(__('admin.search'))->level('primary'),
162 | ]);
163 | }
164 |
165 | /**
166 | * 基础筛选器 - 条件构造器.
167 | */
168 | protected function baseFilterConditionBuilder(): ConditionBuilderControl
169 | {
170 | return amis()->ConditionBuilderControl('filter_condition_builder');
171 | }
172 |
173 | protected function baseCRUD(): CRUDTable
174 | {
175 | $crud = amis()->CRUDTable()
176 | ->perPage(20)
177 | ->affixHeader(false)
178 | ->filterTogglable()
179 | ->filterDefaultVisible(false)
180 | ->api($this->getListGetDataPath())
181 | ->quickSaveApi($this->getQuickEditPath())
182 | ->quickSaveItemApi($this->getQuickEditItemPath())
183 | ->bulkActions([$this->bulkDeleteButton()])
184 | ->perPageAvailable([10, 20, 30, 50, 100, 200])
185 | ->footerToolbar(['switch-per-page', 'statistics', 'pagination'])
186 | ->headerToolbar([
187 | $this->createButton(),
188 | ...$this->baseHeaderToolBar(),
189 | ]);
190 |
191 | if (isset($this->service)) {
192 | $crud->set('primaryField', $this->service->primaryKey());
193 | }
194 |
195 | return $crud;
196 | }
197 |
198 | protected function baseHeaderToolBar()
199 | {
200 | return [
201 | 'bulkActions',
202 | amis('reload')->align('right'),
203 | amis('filter-toggler')->align('right'),
204 | ];
205 | }
206 |
207 | /**
208 | * 基础表单.
209 | */
210 | protected function baseForm(bool $back = true): Form
211 | {
212 | $path = str_replace(config('admin.route.prefix'), '', $this->request->path());
213 |
214 | $form = amis()->Form()->panelClassName('px-48 m:px-0')->title(' ')->mode('horizontal');
215 |
216 | if ($back) {
217 | $form->onEvent([
218 | 'submitSucc' => [
219 | 'actions' => [
220 | ['actionType' => 'custom', 'script' => 'window.history.back()'],
221 | [
222 | 'actionType' => 'custom',
223 | 'script' => sprintf('window.$owl.hasOwnProperty(\'closeTabByPath\') && window.$owl.closeTabByPath(\'%s\')', $path),
224 | ],
225 | ],
226 | ],
227 | ]);
228 | }
229 |
230 | return $form;
231 | }
232 |
233 | protected function baseDetail(): Form
234 | {
235 | return amis()->Form()
236 | ->panelClassName('px-48 m:px-0')
237 | ->title(' ')
238 | ->mode('horizontal')
239 | ->actions([])
240 | ->initApi($this->getShowGetDataPath());
241 | }
242 |
243 | /**
244 | * 基础列表.
245 | * @param mixed $crud
246 | */
247 | protected function baseList($crud): Page
248 | {
249 | return $this->basePage()->body($crud);
250 | }
251 |
252 | /**
253 | * 导出按钮.
254 | *
255 | * @param bool $disableSelectedItem
256 | */
257 | protected function exportAction($disableSelectedItem = false): Service
258 | {
259 | // 获取主键名称
260 | $primaryKey = $this->service->primaryKey();
261 | // 下载路径
262 | $downloadPath = admin_url('_download_export', true);
263 | // 导出接口地址
264 | $exportPath = $this->getExportPath();
265 | // 无数据提示
266 | $pageNoData = __('admin.export.page_no_data');
267 | // 选中行无数据提示
268 | $selectedNoData = __('admin.export.selected_rows_no_data');
269 | // 按钮点击事件
270 | $event = fn ($script) => ['click' => ['actions' => [['actionType' => 'custom', 'script' => $script]]]];
271 | // 导出处理动作
272 | $doAction = "doAction([{actionType:'setValue',componentId:'export-action',args:{value:{showExportLoading:true}}},{actionType:'ajax',args:{api:{url:url.toString(),method:'get'}}},{actionType:'setValue',componentId:'export-action',args:{value:{showExportLoading:false}}},{actionType:'custom',expression:'\${event.data.responseResult.responseStatus===0}',script:'window.open(\\'{$downloadPath}?path=\\'+event.data.responseResult.responseData.path)'}])";
273 | // 按钮
274 | $buttons = [
275 | // 导出全部
276 | amis()->VanillaAction()->label(__('admin.export.all'))->onEvent(
277 | $event("let data=event.data;let params=Object.keys(data).filter(key=>key!=='page' && key!=='__super').reduce((obj,key)=>{obj[key]=data[key];return obj;},{});let url=new URL('{$exportPath}',window.location.origin);Object.keys(params).forEach(key=>url.searchParams.append(key,params[key]));{$doAction}")
278 | ),
279 | // 导出本页
280 | amis()->VanillaAction()->label(__('admin.export.page'))->onEvent(
281 | $event("let ids=event.data.items.map(item=>item.{$primaryKey});if(ids.length===0){return doAction({actionType:'toast',args:{msgType:'warning',msg:'{$pageNoData}'}})};let url=new URL('{$exportPath}',window.location.origin);url.searchParams.append('_ids',ids.join(','));{$doAction}")
282 | ),
283 | ];
284 | // 导出选中项
285 | if (! $disableSelectedItem) {
286 | $buttons[] = amis()->VanillaAction()->label(__('admin.export.selected_rows'))->onEvent(
287 | $event("let ids=event.data.selectedItems.map(item=>item.{$primaryKey});if(ids.length===0){return doAction({actionType:'toast',args:{msgType:'warning',msg:'{$selectedNoData}'}})};let url=new URL('{$exportPath}',window.location.origin);url.searchParams.append('_ids',ids.join(','));{$doAction}")
288 | );
289 | }
290 |
291 | return amis()->Service()
292 | ->id('export-action')
293 | ->set('align', 'right')
294 | ->set('data', ['showExportLoading' => false])
295 | ->body(
296 | amis()->Spinner()->set('showOn', '${showExportLoading}')->overlay()->body(
297 | amis()->DropdownButton()
298 | ->label(__('admin.export.title'))
299 | ->set('icon', 'fa-solid fa-download')
300 | ->buttons($buttons)
301 | ->closeOnClick()
302 | ->align('right')
303 | )
304 | );
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/src/Traits/HasIconifyPickerTrait.php:
--------------------------------------------------------------------------------
1 | CRUDCards()
27 | ->perPage(40)
28 | ->loadDataOnce()
29 | ->set('columnsCount', 8)
30 | ->footerToolbar(['statistics', 'pagination'])
31 | ->api('/_iconify_search')
32 | ->filter(
33 | amis()->Form()->wrapWithPanel(false)->body([
34 | amis()->GroupControl()->className('pt-3 pb-3')->body([
35 | amis()->TextControl('query')
36 | ->size('md')
37 | ->value('${' . $name . ' || "home"}')
38 | ->clearable()
39 | ->required(),
40 | amis()->Button()
41 | ->label(__('admin.search'))
42 | ->level('primary')
43 | ->actionType('submit')
44 | ->icon('fa fa-search'),
45 | amis()->UrlAction()
46 | ->className('ml-2')
47 | ->icon('fa fa-external-link-alt')
48 | ->label('Icones')
49 | ->blank()
50 | ->url('https://icones.js.org/collection/all'),
51 | ]),
52 | ])
53 | )
54 | ->card(
55 | amis()->Card()->body([
56 | amis()->SvgIcon()->icon('${icon}')->className('text-2xl'),
57 | ])
58 | );
59 |
60 | return amis()->PickerControl($name, $label)
61 | ->pickerSchema($schema)
62 | ->source('/_iconify_search')
63 | ->size('lg')
64 | ->labelField('icon')
65 | ->valueField('icon');
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Traits/HasQueryPathTrait.php:
--------------------------------------------------------------------------------
1 | queryPath . '?_action=getData');
29 | }
30 |
31 | /**
32 | * 导出.
33 | *
34 | * @return string
35 | */
36 | public function getExportPath()
37 | {
38 | return admin_url($this->queryPath . '?_action=export', true);
39 | }
40 |
41 | /**
42 | * 删除.
43 | *
44 | * @return string
45 | */
46 | public function getDeletePath()
47 | {
48 | $primaryKey = isset($this->service) ? $this->service->primaryKey() : 'id';
49 |
50 | return 'delete:' . admin_url($this->queryPath . '/${' . $primaryKey . '}');
51 | }
52 |
53 | /**
54 | * 批量删除.
55 | *
56 | * @return string
57 | */
58 | public function getBulkDeletePath()
59 | {
60 | return 'delete:' . admin_url($this->queryPath . '/${ids}');
61 | }
62 |
63 | /**
64 | * 编辑页面.
65 | *
66 | * @return string
67 | */
68 | public function getEditPath()
69 | {
70 | return '/' . trim($this->queryPath, '/') . '/${' . $this->service->primaryKey() . '}';
71 | }
72 |
73 | /**
74 | * 编辑 获取数据.
75 | *
76 | * @return string
77 | */
78 | public function getEditGetDataPath()
79 | {
80 | $path = str_replace('/edit', '', $this->queryPath);
81 |
82 | $last = collect(explode('/', $path))->last();
83 |
84 | if (! is_numeric($last)) {
85 | $primaryKey = isset($this->service) ? $this->service->primaryKey() : 'id';
86 |
87 | $path .= '/${' . $primaryKey . '}';
88 | }
89 |
90 | return admin_url($path . '?_action=getData');
91 | }
92 |
93 | /**
94 | * 详情页面.
95 | *
96 | * @return string
97 | */
98 | public function getShowPath()
99 | {
100 | return '/' . trim($this->queryPath, '/') . '/${' . $this->service->primaryKey() . '}';
101 | }
102 |
103 | /**
104 | * 编辑保存.
105 | *
106 | * @return string
107 | */
108 | public function getUpdatePath()
109 | {
110 | $path = str_replace('/edit', '', $this->queryPath);
111 |
112 | $last = collect(explode('/', $path))->last();
113 |
114 | if (! is_numeric($last)) {
115 | $primaryKey = isset($this->service) ? $this->service->primaryKey() : 'id';
116 |
117 | $path .= '/${' . $primaryKey . '}';
118 | }
119 |
120 | return 'put:' . admin_url($path);
121 | }
122 |
123 | /**
124 | * 快速编辑.
125 | *
126 | * @return string
127 | */
128 | public function getQuickEditPath()
129 | {
130 | return $this->getStorePath() . '?_action=quickEdit';
131 | }
132 |
133 | public function getQuickEditItemPath()
134 | {
135 | return $this->getStorePath() . '?_action=quickEditItem';
136 | }
137 |
138 | /**
139 | * 详情 获取数据.
140 | *
141 | * @return string
142 | */
143 | public function getShowGetDataPath()
144 | {
145 | $path = $this->queryPath;
146 |
147 | $last = collect(explode('/', $this->queryPath))->last();
148 |
149 | if (! is_numeric($last)) {
150 | $path .= '/${' . $this->service->primaryKey() . '}';
151 | }
152 |
153 | return admin_url($path . '?_action=getData');
154 | }
155 |
156 | /**
157 | * 新增页面.
158 | *
159 | * @return string
160 | */
161 | public function getCreatePath()
162 | {
163 | return '/' . trim($this->queryPath, '/') . '/create';
164 | }
165 |
166 | /**
167 | * 新增 保存.
168 | *
169 | * @return string
170 | */
171 | public function getStorePath()
172 | {
173 | return 'post:' . admin_url(str_replace('/create', '', $this->queryPath));
174 | }
175 |
176 | /**
177 | * 列表.
178 | *
179 | * @return string
180 | */
181 | public function getListPath()
182 | {
183 | $path = $this->queryPath;
184 |
185 | if (Str::contains($this->queryPath, '/create')) {
186 | $path = str_replace('/create', '', $path);
187 | }
188 |
189 | if (Str::contains($this->queryPath, '/edit')) {
190 | $_path = explode('/', $path);
191 | array_pop($_path);
192 | array_pop($_path);
193 | $path = implode('/', $_path);
194 | }
195 |
196 | return '/' . trim($path, '/');
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/src/Traits/HasUploadTrait.php:
--------------------------------------------------------------------------------
1 |