no message

This commit is contained in:
kuaifan 2025-05-15 19:23:34 +08:00
parent 0c34df290e
commit ef696391d8
11 changed files with 127 additions and 99 deletions

View File

@ -81,43 +81,46 @@ class AppsController extends AbstractController
} }
/** /**
* @api {get} api/apps/stats 04. 获取应用状况 * @api {get} api/apps/status 04. 获取应用状态
* *
* @apiDescription 获取应用状况,包括已安装的应用和应用入口点 * @apiDescription 获取应用状态,包括已安装的应用和应用菜单
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup apps * @apiGroup apps
* @apiName stats * @apiName status
* *
* @apiSuccess {Number} ret 返回状态码1正确、0错误 * @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述) * @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 应用入口点信息 * @apiSuccess {Object} data 应用和菜单信息
* @apiSuccess {Array} data.installed 已安装应用列表
* @apiSuccess {Array} data.menus 应用菜单列表
*/ */
public function stats() public function status()
{ {
User::auth(); User::auth();
// 获取已安装应用列表 // 获取已安装应用列表
$res = Apps::appList();
if (Base::isError($res)) {
return $res;
}
$installedName = ['appstore']; $installedName = ['appstore'];
foreach ($res['data'] as $app) { $appList = Apps::appList();
if ($app['config']['status'] == 'installed') { if (Base::isSuccess($appList)) {
$installedName[] = $app['name']; $installedName = array_merge(
} $installedName,
array_column(
array_filter($appList['data'], fn($app) => $app['config']['status'] === 'installed'),
'name'
)
);
} }
// 获取应用入口点 // 获取应用菜单
$res = Apps::getAppEntryPoints(); $menusData = [];
if (Base::isError($res)) { $res = Apps::getAppMenuItems();
return $res; if (Base::isSuccess($res)) {
$menusData = $res['data'];
} }
$entriesData = $res['data'];
return Base::retSuccess('success', [ return Base::retSuccess('success', [
'installed' => $installedName, 'installed' => $installedName,
'entries' => $entriesData, 'menus' => $menusData,
]); ]);
} }

View File

@ -344,41 +344,41 @@ class Apps
} }
/** /**
* 获取应用的入口点配置 * 获取应用的菜单配置
* *
* @param string|null $appName 应用名称为null时获取所有已安装应用的入口点 * @param string|null $appName 应用名称为null时获取所有已安装应用的菜单
* @return array * @return array
*/ */
public static function getAppEntryPoints(?string $appName = null): array public static function getAppMenuItems(?string $appName = null): array
{ {
if ($appName !== null) { if ($appName !== null) {
return self::entryGetSingle($appName); return self::menuGetSingle($appName);
} }
return self::entryGetAll(); return self::menuGetAll();
} }
/** /**
* 获取单个应用的入口点配置 * 获取单个应用的菜单配置
* *
* @param string $appName 应用名称 * @param string $appName 应用名称
* @return array * @return array
*/ */
private static function entryGetSingle(string $appName): array private static function menuGetSingle(string $appName): array
{ {
$baseDir = base_path('docker/appstore/apps/' . $appName); $baseDir = base_path('docker/appstore/apps/' . $appName);
$entryPoints = []; $menuItems = [];
if (!file_exists($baseDir . '/config.yml')) { if (!file_exists($baseDir . '/config.yml')) {
return Base::retSuccess("success", $entryPoints); return Base::retSuccess("success", $menuItems);
} }
try { try {
$configData = Yaml::parseFile($baseDir . '/config.yml'); $configData = Yaml::parseFile($baseDir . '/config.yml');
if (isset($configData['entry_points']) && is_array($configData['entry_points'])) { if (isset($configData['menu_items']) && is_array($configData['menu_items'])) {
foreach ($configData['entry_points'] as $entry) { foreach ($configData['menu_items'] as $menu) {
$normalizedEntry = self::entryNormalize($entry, $appName); $normalizedMenu = self::menuNormalize($menu, $appName);
if ($normalizedEntry) { if ($normalizedMenu) {
$entryPoints[] = $normalizedEntry; $menuItems[] = $normalizedMenu;
} }
} }
} }
@ -386,21 +386,21 @@ class Apps
return Base::retError('配置文件解析失败:' . $e->getMessage()); return Base::retError('配置文件解析失败:' . $e->getMessage());
} }
return Base::retSuccess("success", $entryPoints); return Base::retSuccess("success", $menuItems);
} }
/** /**
* 获取所有已安装应用的入口点配置 * 获取所有已安装应用的菜单配置
* *
* @return array * @return array
*/ */
private static function entryGetAll(): array private static function menuGetAll(): array
{ {
$allEntryPoints = []; $allMenuItems = [];
$baseDir = base_path('docker/appstore/apps'); $baseDir = base_path('docker/appstore/apps');
if (!is_dir($baseDir)) { if (!is_dir($baseDir)) {
return Base::retSuccess("success", $allEntryPoints); return Base::retSuccess("success", $allMenuItems);
} }
$dirs = scandir($baseDir); $dirs = scandir($baseDir);
@ -413,48 +413,48 @@ class Apps
continue; continue;
} }
$appEntryPoints = self::entryGetSingle($dir); $appMenuItems = self::menuGetSingle($dir);
if (Base::isSuccess($appEntryPoints)) { if (Base::isSuccess($appMenuItems)) {
$allEntryPoints = array_merge($allEntryPoints, $appEntryPoints['data']); $allMenuItems = array_merge($allMenuItems, $appMenuItems['data']);
} }
} }
return Base::retSuccess("success", $allEntryPoints); return Base::retSuccess("success", $allMenuItems);
} }
/** /**
* 标准化入口点配置 * 标准化菜单配置
* *
* @param array $entry 原始入口点配置 * @param array $menu 原始菜单配置
* @param string $appName 应用名称 * @param string $appName 应用名称
* @return array|null 标准化后的入口点配置配置无效时返回null * @return array|null 标准化后的菜单配置配置无效时返回null
*/ */
private static function entryNormalize(array $entry, string $appName): ?array private static function menuNormalize(array $menu, string $appName): ?array
{ {
// 检查必需的字段 // 检查必需的字段
if (!isset($entry['location']) || !isset($entry['url'])) { if (!isset($menu['location']) || !isset($menu['url'])) {
return null; return null;
} }
// 基础配置 // 基础配置
$normalizedEntry = [ $normalizedMenu = [
'app_name' => $appName, 'app_name' => $appName,
'location' => $entry['location'], 'location' => $menu['location'],
'url' => $entry['url'], 'url' => $menu['url'],
'key' => $entry['key'] ?? substr(md5($entry['url']), 0, 16), 'key' => $menu['key'] ?? substr(md5($menu['url']), 0, 16),
'icon' => self::processAppIcon($appName, [$entry['icon'] ?? '']), 'icon' => self::processAppIcon($appName, [$menu['icon'] ?? '']),
'label' => self::getMultiLanguageField($entry['label'] ?? ''), 'label' => self::getMultiLanguageField($menu['label'] ?? ''),
]; ];
// 处理可选的UI配置 // 处理可选的UI配置
$optionalConfigs = ['transparent', 'keepAlive']; $optionalConfigs = ['transparent', 'keepAlive'];
foreach ($optionalConfigs as $config) { foreach ($optionalConfigs as $config) {
if (isset($entry[$config])) { if (isset($menu[$config])) {
$normalizedEntry[$config] = $entry[$config]; $normalizedMenu[$config] = $menu[$config];
} }
} }
return $normalizedEntry; return $normalizedMenu;
} }
/** /**
@ -830,7 +830,7 @@ class Apps
// 处理应用名称 // 处理应用名称
$appName = Base::camel2snake(Base::cn2pinyin($configData['name'], '_')); $appName = Base::camel2snake(Base::cn2pinyin($configData['name'], '_'));
if (in_array($appName, self::$protectedServiceNames)) { if (in_array($appName, self::$protectedServiceNames)) {
return Base::retError('服务名称 "' . $name . '" 被保护,不能使用'); return Base::retError('服务名称 "' . $appName . '" 被保护,不能使用');
} }
$targetDir = base_path('docker/appstore/apps/' . $appName); $targetDir = base_path('docker/appstore/apps/' . $appName);
$targetConfigFile = $targetDir . '/config.json'; $targetConfigFile = $targetDir . '/config.json';

View File

@ -424,7 +424,7 @@ export default {
microApp.forceSetData(name, {type: 'beforeClose'}, array => { microApp.forceSetData(name, {type: 'beforeClose'}, array => {
if (!array?.find(item => item === true)) { if (!array?.find(item => item === true)) {
if (name === 'appstore') { if (name === 'appstore') {
this.$store.dispatch("updateMicroAppsStats"); this.$store.dispatch("updateMicroAppsStatus");
} }
if ($A.isSubElectron) { if ($A.isSubElectron) {
$A.Electron.sendMessage('windowDestroy'); $A.Electron.sendMessage('windowDestroy');

View File

@ -120,6 +120,10 @@
<div class="menu-title">{{$L('应用')}}</div> <div class="menu-title">{{$L('应用')}}</div>
<Badge class="menu-badge" :overflow-count="999" :text="String((reportUnreadNumber + approveUnreadNumber) || '')"/> <Badge class="menu-badge" :overflow-count="999" :text="String((reportUnreadNumber + approveUnreadNumber) || '')"/>
</li> </li>
<li v-for="(item, key) in filterMicroAppsMenusMain" :key="key" @click="onTabbarClick('microApp', item)">
<div class="apply-icon no-dark-content" :style="{backgroundImage: `url(${item.icon})`}"></div>
<div class="menu-title">{{item.label}}</div>
</li>
</ul> </ul>
</div> </div>
<div ref="menuProject" class="menu-project"> <div ref="menuProject" class="menu-project">
@ -515,7 +519,7 @@ export default {
'longpressData', 'longpressData',
]), ]),
...mapGetters(['dashboardTask']), ...mapGetters(['dashboardTask', "filterMicroAppsMenusMain"]),
/** /**
* page className * page className
@ -1167,7 +1171,7 @@ export default {
}) })
}, },
onTabbarClick(act) { onTabbarClick(act, params = '') {
switch (act) { switch (act) {
case 'createGroup': case 'createGroup':
this.onAddMenu('group') this.onAddMenu('group')
@ -1183,6 +1187,9 @@ export default {
case 'workReport': case 'workReport':
this.settingRoute(act) this.settingRoute(act)
break; break;
case 'microApp':
this.$store.dispatch("openMicroApp", params);
break;
case 'appstore': case 'appstore':
this.$store.dispatch("openMicroApp", { this.$store.dispatch("openMicroApp", {
name: 'appstore', name: 'appstore',

View File

@ -15,7 +15,7 @@
{{ t == 'base' ? $L('常用') : $L('管理员') }} {{ t == 'base' ? $L('常用') : $L('管理员') }}
</div> </div>
<Row :gutter="16"> <Row :gutter="16">
<Col v-for="item in (t == 'base' ? filterMicroAppsEntries : filterMicroAppsEntriesAdmin)" :key="item.key" <Col v-for="(item, key) in (t == 'base' ? filterMicroAppsMenus : filterMicroAppsMenusAdmin)" :key="key"
:xs="{ span: 6 }" :xs="{ span: 6 }"
:sm="{ span: 6 }" :sm="{ span: 6 }"
:lg="{ span: 6 }" :lg="{ span: 6 }"
@ -26,7 +26,7 @@
<div class="logo"> <div class="logo">
<div class="apply-icon no-dark-content" :style="{backgroundImage: `url(${item.icon})`}"></div> <div class="apply-icon no-dark-content" :style="{backgroundImage: `url(${item.icon})`}"></div>
</div> </div>
<p>{{ $L(item.label) }}</p> <p>{{ item.label }}</p>
</div> </div>
</div> </div>
</Col> </Col>
@ -364,6 +364,7 @@ export default {
}, },
activated() { activated() {
this.initList() this.initList()
this.$store.dispatch("updateMicroAppsStatus")
}, },
computed: { computed: {
...mapState([ ...mapState([
@ -378,8 +379,8 @@ export default {
'routeLoading', 'routeLoading',
]), ]),
...mapGetters([ ...mapGetters([
'filterMicroAppsEntries', 'filterMicroAppsMenus',
'filterMicroAppsEntriesAdmin', 'filterMicroAppsMenusAdmin',
]), ]),
isExistAdminList() { isExistAdminList() {
return this.applyList.map(h => h.type).indexOf('admin') !== -1; return this.applyList.map(h => h.type).indexOf('admin') !== -1;
@ -449,11 +450,8 @@ export default {
return item.value == type && num > 0 return item.value == type && num > 0
}, },
// //
applyClick(item, area = '') { applyClick(item, params = '') {
switch (item.value) { switch (item.value) {
case 'microApp':
this.$store.dispatch("openMicroApp", area);
return
case 'approve': case 'approve':
case 'calendar': case 'calendar':
case 'file': case 'file':
@ -461,27 +459,27 @@ export default {
this.goForward({ name: 'manage-' + item.value }); this.goForward({ name: 'manage-' + item.value });
break; break;
case 'report': case 'report':
emitter.emit('openReport', area == 'badge' ? 'receive' : 'my'); emitter.emit('openReport', params == 'badge' ? 'receive' : 'my');
break; break;
case 'mybot': case 'mybot':
this.getMybot(); this.getMybot();
this.mybotShow = true; this.mybotShow = true;
break; break;
case 'mybot-chat': case 'mybot-chat':
this.chatMybot(area.id); this.chatMybot(params.id);
break; break;
case 'mybot-add': case 'mybot-add':
this.addMybot(area); this.addMybot(params);
break; break;
case 'mybot-del': case 'mybot-del':
this.delMybot(area); this.delMybot(params);
break; break;
case 'robot': case 'robot':
this.getAITags(); this.getAITags();
this.aibotShow = true; this.aibotShow = true;
break; break;
case 'robot-setting': case 'robot-setting':
this.aibotTabAction = area; this.aibotTabAction = params;
this.aibotSettingShow = true; this.aibotSettingShow = true;
break; break;
case 'signin': case 'signin':
@ -510,7 +508,7 @@ export default {
break; break;
} }
this.$emit("on-click", item.value) this.$emit("on-click", item.value, params);
}, },
// //
getMybot() { getMybot() {

View File

@ -2619,7 +2619,7 @@ export default {
}); });
}, },
openOkr(id) { openOkrDetails(id) {
if (!id) { if (!id) {
return; return;
} }
@ -3150,7 +3150,7 @@ export default {
break; break;
case 'okr': case 'okr':
this.openOkr(this.dialogData.link_id) this.openOkrDetails(this.dialogData.link_id)
break; break;
default: default:
@ -3758,7 +3758,7 @@ export default {
this.$store.dispatch("openTask", $A.runNum(target.getAttribute("data-id"))); this.$store.dispatch("openTask", $A.runNum(target.getAttribute("data-id")));
} }
if (target.classList.contains('mention') && target.classList.contains('okr')) { if (target.classList.contains('mention') && target.classList.contains('okr')) {
this.openOkr($A.runNum(target.getAttribute("data-id"))); this.openOkrDetails($A.runNum(target.getAttribute("data-id")));
} }
break; break;

View File

@ -623,7 +623,7 @@ export default {
dispatch("getProjectByQueue"); dispatch("getProjectByQueue");
dispatch("getTaskForDashboard"); dispatch("getTaskForDashboard");
dispatch("dialogMsgRead"); dispatch("dialogMsgRead");
dispatch("updateMicroAppsStats"); dispatch("updateMicroAppsStatus");
// //
const allIds = Object.values(state.userAvatar).map(({userid}) => userid); const allIds = Object.values(state.userAvatar).map(({userid}) => userid);
[...new Set(allIds)].some(userid => dispatch("getUserBasic", {userid})) [...new Set(allIds)].some(userid => dispatch("getUserBasic", {userid}))
@ -1116,6 +1116,8 @@ export default {
'callAt', 'callAt',
'cacheEmojis', 'cacheEmojis',
'cacheDialogs', 'cacheDialogs',
'microAppsInstalled',
'microAppsMenus',
], ],
json: [ json: [
'userInfo' 'userInfo'
@ -4640,17 +4642,16 @@ export default {
/** *****************************************************************************************/ /** *****************************************************************************************/
/** /**
* 更新微应用状已安装入口菜单 * 更新微应用状已安装应用菜单项
* @param state * @param commit
* @param dispatch * @param dispatch
* @param appName
*/ */
updateMicroAppsStats({state, dispatch}) { updateMicroAppsStatus({commit, dispatch}) {
dispatch("call", { dispatch("call", {
url: 'apps/stats', url: 'apps/status',
}).then(({data}) => { }).then(({data}) => {
state.microAppsInstalled = data.installed commit("microApps/installed", data.installed)
state.microAppsEntries = data.entries commit("microApps/menu", data.menus)
}) })
}, },

View File

@ -279,35 +279,35 @@ export default {
}, },
/** /**
* 获取应用菜单入口 * 获取应用菜单
* 过滤出location为application的菜单项 * 过滤出location为application的菜单项
* *
* @param {Object} state * @param {Object} state
* @returns {Array} * @returns {Array}
*/ */
filterMicroAppsEntries: (state) => { filterMicroAppsMenus: (state) => {
return state.microAppsEntries.filter(item => item.location === 'application') return state.microAppsMenus.filter(item => item.location === 'application')
}, },
/** /**
* 获取应用管理菜单入口 * 获取应用管理菜单
* 过滤出location为application/admin的菜单项 * 过滤出location为application/admin的菜单项
* *
* @param {Object} state * @param {Object} state
* @returns {Array} * @returns {Array}
*/ */
filterMicroAppsEntriesAdmin: (state) => { filterMicroAppsMenusAdmin: (state) => {
return state.microAppsEntries.filter(item => item.location === 'application/admin') return state.microAppsMenus.filter(item => item.location === 'application/admin')
}, },
/** /**
* 获取主导航菜单入口 * 获取主导航菜单
* 过滤出location为main/menu的菜单项 * 过滤出location为main/menu的菜单项
* *
* @param {Object} state * @param {Object} state
* @returns {Array} * @returns {Array}
*/ */
filterMicroAppsEntriesMain: (state) => { filterMicroAppsMenusMain: (state) => {
return state.microAppsEntries.filter(item => item.location === 'main/menu') return state.microAppsMenus.filter(item => item.location === 'main/menu')
} }
} }

View File

@ -298,4 +298,15 @@ export default {
'menu/operation': function(state, data) { 'menu/operation': function(state, data) {
state.menuOperation = data || {} state.menuOperation = data || {}
}, },
// 微应用管理
'microApps/menu': function(state, data) {
state.microAppsMenus = data
$A.IDBSave("microAppsMenus", state.microAppsMenus)
},
'microApps/installed': function(state, data) {
state.microAppsInstalled = data
$A.IDBSave("microAppsInstalled", state.microAppsInstalled)
},
} }

View File

@ -261,7 +261,7 @@ export default {
// 长按数据 // 长按数据
longpressData: {type: '', data: null, element: null}, longpressData: {type: '', data: null, element: null},
// 微应用菜单入口 // 微应用数据
microAppsInstalled: [], microAppsInstalled: [],
microAppsEntries: [], microAppsMenus: [],
}; };

View File

@ -55,6 +55,14 @@
font-size: 20px; font-size: 20px;
margin-right: 10px; margin-right: 10px;
} }
.apply-icon {
width: 20px;
height: 20px;
background-repeat: no-repeat;
background-size: contain;
background-position: center center;
margin-right: 10px;
}
.menu-title { .menu-title {
flex: 1; flex: 1;
white-space: nowrap; white-space: nowrap;