mirror of
https://github.com/kuaifan/dootask.git
synced 2026-07-09 23:58:11 +00:00
fix(apps): 插件菜单角标补初始同步接口 badge/list
插件应用菜单来自 appstore installed(不含 count/dot),原先只有自定义微 应用经 microapp_menu 注入角标,导致插件角标只能靠实时 WS、刷新即丢。 - 新增 GET api/apps/badge/list(用户 token 鉴权)返回 userBadges 快照 (插件 + 自定义统一覆盖),前端 updateMicroAppsStatus 拉取后 hydrateMap - appBadges store hydrate -> hydrateMap(按角标 map 初始化,不再依赖 menu_items) - 删除已冗余的 Badge::attachMenuBadges(调用 + 方法定义)
This commit is contained in:
parent
420d46d5cc
commit
09439b555c
@ -13,6 +13,7 @@ use Request;
|
||||
* 动态路由(routes/web.php):
|
||||
* api/apps/badge/set -> badge__set() 应用密钥鉴权,绝对设置/清除角标
|
||||
* api/apps/badge/clear -> badge__clear() 当前用户 token 鉴权,清除自己的角标
|
||||
* api/apps/badge/list -> badge__list() 当前用户 token 鉴权,拉取自己全部角标(初始同步)
|
||||
*/
|
||||
class AppsController extends AbstractController
|
||||
{
|
||||
@ -71,4 +72,23 @@ class AppsController extends AbstractController
|
||||
trim(Request::input('menu_key', ''))
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/apps/badge/list 拉取自己全部角标
|
||||
*
|
||||
* @apiDescription 供前端初始同步:返回当前用户全部应用(插件 + 自定义微应用)的角标快照。
|
||||
* 数据结构 app_id => menu_key => {count, dot},与前端 store 的 map 结构一致。
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup apps
|
||||
* @apiName badge__list
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function badge__list()
|
||||
{
|
||||
$user = User::auth();
|
||||
return Base::retSuccess('success', Badge::userBadges((int)$user->userid));
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,6 @@ use Symfony\Component\Mailer\Transport;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use App\Models\UserCheckinRecord;
|
||||
use App\Module\Apps;
|
||||
use App\Module\Badge;
|
||||
use App\Module\BillMultipleExport;
|
||||
use LdapRecord\LdapRecordException;
|
||||
use Swoole\Coroutine;
|
||||
@ -787,7 +786,6 @@ class SystemController extends AbstractController
|
||||
}
|
||||
$setting = Setting::filterCustomMicroAppsForUser($setting, $user);
|
||||
$setting = Setting::formatCustomMicroAppsForResponse($setting);
|
||||
$setting = Badge::attachMenuBadges($setting, $user ? (int)$user->userid : 0);
|
||||
}
|
||||
return Base::retSuccess($type == 'save' ? '保存成功' : 'success', $setting);
|
||||
}
|
||||
|
||||
@ -273,33 +273,6 @@ class Badge
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为微应用菜单列表附带当前用户的角标 {count,dot},作为前端初始同步来源(零额外请求)。
|
||||
*
|
||||
* @param array $apps 微应用列表(每项含 id 与 menu_items)
|
||||
* @param int $userid
|
||||
* @return array
|
||||
*/
|
||||
public static function attachMenuBadges(array $apps, int $userid): array
|
||||
{
|
||||
$map = self::userBadges($userid);
|
||||
foreach ($apps as &$app) {
|
||||
$appId = (string)($app['id'] ?? '');
|
||||
if (empty($app['menu_items']) || !is_array($app['menu_items'])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($app['menu_items'] as &$menu) {
|
||||
$menuKey = (string)($menu['key'] ?? '');
|
||||
$badge = $map[$appId][$menuKey] ?? ['count' => 0, 'dot' => false];
|
||||
$menu['count'] = (int)$badge['count'];
|
||||
$menu['dot'] = (bool)$badge['dot'];
|
||||
}
|
||||
unset($menu);
|
||||
}
|
||||
unset($app);
|
||||
return $apps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向在线用户实时推送角标变更(仅投递,不补发离线)。
|
||||
*
|
||||
|
||||
9
resources/assets/js/store/actions.js
vendored
9
resources/assets/js/store/actions.js
vendored
@ -5376,8 +5376,13 @@ export default {
|
||||
// 忽略自定义菜单加载失败
|
||||
}
|
||||
commit("microApps/data", apps || [])
|
||||
// 用应用菜单返回的角标初始化(自定义应用由 microapp_menu 提供,插件应用由 appstore installed 提供)
|
||||
commit("appBadges/hydrate", apps || [])
|
||||
// 角标初始同步:一次拉取当前用户全部应用(插件 + 自定义微应用)的角标快照
|
||||
try {
|
||||
const {data: badgeMap} = await dispatch('call', {url: 'apps/badge/list'});
|
||||
commit("appBadges/hydrateMap", badgeMap || {})
|
||||
} catch (e) {
|
||||
// 忽略角标初始同步失败
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
43
resources/assets/js/store/modules/appBadges.js
vendored
43
resources/assets/js/store/modules/appBadges.js
vendored
@ -8,7 +8,7 @@ const EMPTY_BADGE = Object.freeze({count: 0, dot: false})
|
||||
*
|
||||
* 结构:map[app_id][menu_key] = { count, dot }
|
||||
* - menu_key 为空串表示该应用的第一个菜单
|
||||
* - 初始值由 updateMicroAppsStatus 通过 hydrate 填充(microapp_menu / appstore installed)
|
||||
* - 初始值由 updateMicroAppsStatus 通过 hydrateMap 填充(apps/badge/list 一次返回插件 + 自定义全部角标)
|
||||
* - 运行时由 websocket 消息 appBadge 经 set 增量更新
|
||||
*/
|
||||
export default {
|
||||
@ -42,29 +42,32 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* 由应用菜单列表整体初始化角标
|
||||
* 由后端角标快照整体初始化(apps/badge/list 返回,含插件 + 自定义微应用)
|
||||
* @param state
|
||||
* @param apps 形如 [{id, menu_items:[{key,count,dot}]}]
|
||||
* @param map 形如 { app_id: { menu_key: {count, dot} } }
|
||||
*/
|
||||
hydrate(state, apps) {
|
||||
const map = {}
|
||||
;(Array.isArray(apps) ? apps : []).forEach(app => {
|
||||
const appid = app && app.id
|
||||
if (!appid || !Array.isArray(app.menu_items)) {
|
||||
return
|
||||
}
|
||||
app.menu_items.forEach(menu => {
|
||||
const count = Number(menu && menu.count) || 0
|
||||
const dot = !!(menu && menu.dot)
|
||||
if (count > 0 || dot) {
|
||||
if (!map[appid]) {
|
||||
map[appid] = {}
|
||||
}
|
||||
map[appid][(menu && menu.key) || ''] = {count, dot}
|
||||
hydrateMap(state, map) {
|
||||
const next = {}
|
||||
if (map && typeof map === 'object') {
|
||||
Object.keys(map).forEach(appid => {
|
||||
const menus = map[appid]
|
||||
if (!appid || !menus || typeof menus !== 'object') {
|
||||
return
|
||||
}
|
||||
Object.keys(menus).forEach(key => {
|
||||
const item = menus[key] || {}
|
||||
const count = Number(item.count) || 0
|
||||
const dot = !!item.dot
|
||||
if (count > 0 || dot) {
|
||||
if (!next[appid]) {
|
||||
next[appid] = {}
|
||||
}
|
||||
next[appid][key || ''] = {count, dot}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
state.map = map
|
||||
}
|
||||
state.map = next
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
> 此文件由 `php artisan doc:api-map` 生成,勿手改。
|
||||
|
||||
接口总数:306
|
||||
接口总数:307
|
||||
|
||||
## 路由规则
|
||||
|
||||
@ -377,6 +377,7 @@ API 使用动态路由(见 `routes/web.php`),URL 段映射为控制器方
|
||||
| --- | --- | --- | --- |
|
||||
| api/apps/badge/set | badge__set() | post | 设置角标(应用密钥鉴权) |
|
||||
| api/apps/badge/clear | badge__clear() | post | 清除角标(当前用户 token 鉴权) |
|
||||
| api/apps/badge/list | badge__list() | get | 拉取自己全部角标 |
|
||||
|
||||
## test(TestController)
|
||||
|
||||
|
||||
@ -33,44 +33,44 @@ use App\Http\Controllers\Api\AppsController;
|
||||
*/
|
||||
Route::prefix('api')->middleware(['webapi'])->group(function () {
|
||||
// 会员
|
||||
Route::any('users/{method}', UsersController::class);
|
||||
Route::any('users/{method}/{action}', UsersController::class);
|
||||
Route::any('users/{method}', UsersController::class);
|
||||
Route::any('users/{method}/{action}', UsersController::class);
|
||||
// 项目
|
||||
Route::any('project/{method}', ProjectController::class);
|
||||
Route::any('project/{method}/{action}', ProjectController::class);
|
||||
Route::any('project/{method}', ProjectController::class);
|
||||
Route::any('project/{method}/{action}', ProjectController::class);
|
||||
// 系统
|
||||
Route::any('system/{method}', SystemController::class);
|
||||
Route::any('system/{method}/{action}', SystemController::class);
|
||||
Route::any('system/{method}', SystemController::class);
|
||||
Route::any('system/{method}/{action}', SystemController::class);
|
||||
// 在线授权
|
||||
Route::any('license/{method}', LicenseController::class);
|
||||
Route::any('license/{method}/{action}', LicenseController::class);
|
||||
Route::any('license/{method}', LicenseController::class);
|
||||
Route::any('license/{method}/{action}', LicenseController::class);
|
||||
// 对话
|
||||
Route::any('dialog/{method}', DialogController::class);
|
||||
Route::any('dialog/{method}/{action}', DialogController::class);
|
||||
Route::any('dialog/{method}', DialogController::class);
|
||||
Route::any('dialog/{method}/{action}', DialogController::class);
|
||||
// 文件
|
||||
Route::any('file/{method}', FileController::class);
|
||||
Route::any('file/{method}/{action}', FileController::class);
|
||||
Route::any('file/{method}', FileController::class);
|
||||
Route::any('file/{method}/{action}', FileController::class);
|
||||
// 报告
|
||||
Route::any('report/{method}', ReportController::class);
|
||||
Route::any('report/{method}/{action}', ReportController::class);
|
||||
Route::any('report/{method}', ReportController::class);
|
||||
Route::any('report/{method}/{action}', ReportController::class);
|
||||
// 公开接口
|
||||
Route::any('public/{method}', PublicController::class);
|
||||
Route::any('public/{method}/{action}', PublicController::class);
|
||||
Route::any('public/{method}', PublicController::class);
|
||||
Route::any('public/{method}/{action}', PublicController::class);
|
||||
// 助手
|
||||
Route::any('assistant/{method}', AssistantController::class);
|
||||
Route::any('assistant/{method}/{action}', AssistantController::class);
|
||||
Route::any('assistant/{method}', AssistantController::class);
|
||||
Route::any('assistant/{method}/{action}', AssistantController::class);
|
||||
// 投诉
|
||||
Route::any('complaint/{method}', ComplaintController::class);
|
||||
Route::any('complaint/{method}/{action}', ComplaintController::class);
|
||||
Route::any('complaint/{method}', ComplaintController::class);
|
||||
Route::any('complaint/{method}/{action}', ComplaintController::class);
|
||||
// 智能搜索
|
||||
Route::any('search/{method}', SearchController::class);
|
||||
Route::any('search/{method}/{action}', SearchController::class);
|
||||
Route::any('search/{method}', SearchController::class);
|
||||
Route::any('search/{method}/{action}', SearchController::class);
|
||||
// 应用相关
|
||||
Route::any('apps/{method}', AppsController::class);
|
||||
Route::any('apps/{method}/{action}', AppsController::class);
|
||||
Route::any('apps/{method}', AppsController::class);
|
||||
Route::any('apps/{method}/{action}', AppsController::class);
|
||||
// 测试
|
||||
Route::any('test/{method}', TestController::class);
|
||||
Route::any('test/{method}/{action}', TestController::class);
|
||||
Route::any('test/{method}', TestController::class);
|
||||
Route::any('test/{method}/{action}', TestController::class);
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user