From 4983fe8feb3259b16165e87798bb8afc5905026f Mon Sep 17 00:00:00 2001 From: kuaifan Date: Wed, 19 Nov 2025 07:54:47 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=BE=AE=E5=BA=94=E7=94=A8=E8=8F=9C=E5=8D=95=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81=E7=AE=A1=E7=90=86=E5=91=98?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=92=8C=E4=BF=9D=E5=AD=98=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/SystemController.php | 41 +++ app/Models/Setting.php | 207 ++++++++++++++ app/Module/Base.php | 2 +- .../assets/js/pages/manage/application.vue | 252 +++++++++++++++++- resources/assets/js/store/actions.js | 20 +- resources/assets/js/store/mutations.js | 4 +- resources/assets/sass/pages/page-apply.scss | 80 ++++++ 7 files changed, 597 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/Api/SystemController.php b/app/Http/Controllers/Api/SystemController.php index df5dd82f6..707338d0e 100755 --- a/app/Http/Controllers/Api/SystemController.php +++ b/app/Http/Controllers/Api/SystemController.php @@ -722,6 +722,47 @@ class SystemController extends AbstractController return Base::retSuccess($type == 'save' ? '保存成功' : 'success', $setting); } + /** + * @api {post} api/system/microapp_menu 自定义应用菜单 + * + * @apiDescription 获取或保存自定义微应用菜单,仅管理员可配置 + * @apiVersion 1.0.0 + * @apiGroup system + * @apiName microapp_menu + * + * @apiParam {String} type + * - get: 获取(默认) + * - save: 保存(限管理员) + * @apiParam {Array} list 菜单列表,格式:[{id,name,version,menu_items}] + * + * @apiSuccess {Number} ret 返回状态码(1正确、0错误) + * @apiSuccess {String} msg 返回信息(错误描述) + * @apiSuccess {Object} data 返回数据 + */ + public function microapp_menu() + { + $type = trim(Request::input('type')); + $user = User::auth(); + if ($type == 'save') { + User::auth('admin'); + $list = Request::input('list'); + if (empty($list) || !is_array($list)) { + $list = []; + } + $apps = Setting::normalizeCustomMicroApps($list); + $setting = Base::setting('microapp_menu', $apps); + $setting = Setting::formatCustomMicroAppsForResponse($setting); + } else { + $setting = Base::setting('microapp_menu'); + if (!is_array($setting)) { + $setting = []; + } + $setting = Setting::filterCustomMicroAppsForUser($setting, $user); + $setting = Setting::formatCustomMicroAppsForResponse($setting); + } + return Base::retSuccess($type == 'save' ? '保存成功' : 'success', $setting); + } + /** * @api {post} api/system/column/template 创建项目模板 * diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 8303aad29..eccf46a69 100644 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -164,6 +164,213 @@ class Setting extends AbstractModel return $array; } + /** + * 规范自定义微应用配置 + * @param array $list + * @return array + */ + public static function normalizeCustomMicroApps($list) + { + if (!is_array($list)) { + return []; + } + $apps = []; + foreach ($list as $item) { + $app = self::normalizeCustomMicroAppItem($item); + if ($app) { + $apps[] = $app; + } + } + return $apps; + } + + /** + * 根据用户身份过滤可见的自定义微应用 + * @param array $apps + * @param \App\Models\User|null $user + * @return array + */ + public static function filterCustomMicroAppsForUser(array $apps, $user) + { + if (empty($apps)) { + return []; + } + $isAdmin = $user ? $user->isAdmin() : false; + $userId = $user ? intval($user->userid) : 0; + $filtered = []; + foreach ($apps as $app) { + $visible = self::normalizeCustomMicroVisible($app['visible_to'] ?? ['admin']); + if (!self::isCustomMicroVisibleTo($visible, $isAdmin, $userId)) { + continue; + } + if (empty($app['menu_items']) || !is_array($app['menu_items'])) { + continue; + } + $menus = array_values(array_filter($app['menu_items'], function ($menu) use ($isAdmin, $userId) { + if (!isset($menu['visible_to'])) { + return true; + } + $visible = self::normalizeCustomMicroVisible($menu['visible_to']); + return self::isCustomMicroVisibleTo($visible, $isAdmin, $userId); + })); + if (empty($menus)) { + continue; + } + $app['menu_items'] = $menus; + $filtered[] = $app; + } + return $filtered; + } + + /** + * 将存储结构转换成 appstore 接口同款格式 + * @param array $apps + * @return array + */ + public static function formatCustomMicroAppsForResponse(array $apps) + { + return array_values(array_map(function ($app) { + unset($app['visible_to']); + if (!empty($app['menu_items']) && is_array($app['menu_items'])) { + $app['menu_items'] = array_values(array_map(function ($menu) { + $menu['keep_alive'] = isset($menu['keep_alive']) ? (bool)$menu['keep_alive'] : true; + $menu['disable_scope_css'] = (bool)($menu['disable_scope_css'] ?? false); + $menu['auto_dark_theme'] = isset($menu['auto_dark_theme']) ? (bool)$menu['auto_dark_theme'] : true; + $menu['transparent'] = (bool)($menu['transparent'] ?? false); + if (isset($menu['visible_to'])) { + unset($menu['visible_to']); + } + return $menu; + }, $app['menu_items'])); + } + return $app; + }, $apps)); + } + + /** + * 规范自定义微应用 + * @param array $item + * @return array|null + */ + protected static function normalizeCustomMicroAppItem($item) + { + if (!is_array($item)) { + return null; + } + $id = trim($item['id'] ?? ''); + if ($id === '') { + return null; + } + $name = Base::newTrim($item['name'] ?? ''); + $version = Base::newTrim($item['version'] ?? '') ?: 'custom'; + $menuItems = []; + if (isset($item['menu_items']) && is_array($item['menu_items'])) { + $menuItems = $item['menu_items']; + } elseif (isset($item['menu']) && is_array($item['menu'])) { + $menuItems = [$item['menu']]; + } + if (empty($menuItems)) { + return null; + } + $normalizedMenus = []; + foreach ($menuItems as $menu) { + $formattedMenu = self::normalizeCustomMicroMenuItem($menu, $name ?: $id); + if ($formattedMenu) { + $normalizedMenus[] = $formattedMenu; + } + } + if (empty($normalizedMenus)) { + return null; + } + return Base::newTrim([ + 'id' => $id, + 'name' => $name, + 'version' => $version, + 'menu_items' => $normalizedMenus, + 'visible_to' => self::normalizeCustomMicroVisible($item['visible_to'] ?? 'admin'), + ]); + } + + /** + * 规范自定义微应用菜单项 + * @param array $menu + * @param string $fallbackLabel + * @return array|null + */ + protected static function normalizeCustomMicroMenuItem($menu, $fallbackLabel = '') + { + if (!is_array($menu)) { + return null; + } + $url = trim($menu['url'] ?? ''); + if ($url === '') { + return null; + } + $location = trim($menu['location'] ?? 'application'); + $label = trim($menu['label'] ?? $fallbackLabel); + $urlType = strtolower(trim($menu['url_type'] ?? 'iframe')); + $payload = [ + 'location' => $location, + 'label' => $label, + 'icon' => Base::newTrim($menu['icon'] ?? ''), + 'url' => $url, + 'url_type' => $urlType, + 'keep_alive' => isset($menu['keep_alive']) ? (bool)$menu['keep_alive'] : true, + 'disable_scope_css' => (bool)($menu['disable_scope_css'] ?? false), + 'auto_dark_theme' => isset($menu['auto_dark_theme']) ? (bool)$menu['auto_dark_theme'] : true, + 'transparent' => (bool)($menu['transparent'] ?? false), + ]; + if (!empty($menu['background'])) { + $payload['background'] = Base::newTrim($menu['background']); + } + if (!empty($menu['capsule']) && is_array($menu['capsule'])) { + $payload['capsule'] = Base::newTrim($menu['capsule']); + } + return $payload; + } + + /** + * 规范自定义微应用可见范围 + * @param mixed $value + * @return array + */ + protected static function normalizeCustomMicroVisible($value) + { + if (is_array($value)) { + $list = array_filter(array_map('trim', $value)); + } else { + $list = array_filter(array_map('trim', explode(',', (string)$value))); + } + if (empty($list)) { + return ['admin']; + } + if (in_array('all', $list)) { + return ['all']; + } + return array_values($list); + } + + /** + * 判断自定义微应用是否可见 + * @param array $visible + * @param bool $isAdmin + * @param int $userId + * @return bool + */ + protected static function isCustomMicroVisibleTo(array $visible, bool $isAdmin, int $userId) + { + if (in_array('all', $visible)) { + return true; + } + if ($isAdmin && in_array('admin', $visible)) { + return true; + } + if ($userId > 0 && in_array((string)$userId, $visible, true)) { + return true; + } + return false; + } + /** * 验证邮箱地址(过滤忽略地址) * @param $array diff --git a/app/Module/Base.php b/app/Module/Base.php index ab0baeccb..172de773a 100755 --- a/app/Module/Base.php +++ b/app/Module/Base.php @@ -1301,7 +1301,7 @@ class Base /** * 获取或设置 * @param $setname // 配置名称 - * @param bool $array // 保存内容 + * @param bool|array $array // 保存内容 * @param bool $isUpdate // 保存内容为更新模式,默认否 * @return array */ diff --git a/resources/assets/js/pages/manage/application.vue b/resources/assets/js/pages/manage/application.vue index 5622a6311..dc4b2d090 100644 --- a/resources/assets/js/pages/manage/application.vue +++ b/resources/assets/js/pages/manage/application.vue @@ -15,6 +15,7 @@ {{ $L('调整排序') }} {{ $L('退出排序') }} + {{ $L('自定义应用菜单') }} @@ -111,6 +112,114 @@ + + + + {{ $L('仅管理员可配置,保存后会在应用列表中生成对应菜单。') }} + +
+ +
+
+
+ {{ $L('暂无自定义菜单,请点击下方按钮新增。') }} +
+ + +
+
+ {{ item.id || $L('未命名应用') }} +
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{ $L('保持激活状态 (keep_alive)') }} + {{ $L('禁用作用域样式') }} + {{ $L('透明背景') }} + {{ $L('自动暗黑模式') }} +
+
+
+
+
+ +
+
+ + +
+
+