kuaifan 1c90a11211 feat: 系统功能应用统一管理内置功能入口
- 新增 SystemFunctions 模块与 api/apps/functions 接口,读取 system-functions 应用的安装状态和开关字段
- 工作报告、日历、我的收藏、最近打开、签到打卡的入口(应用中心、头像菜单、左侧导航)统一受开关控制,卸载后全部隐藏
- 导出签到数据随签到打卡一起隐藏;日历停用后访问日历页跳回仪表盘
- 头像菜单按分组计算分割线,隐藏菜单项后不留多余分割线
- 新增迁移:无配置时自动安装并全部启用,已有配置不覆盖
- 同步 ai-kb 与 api-map
2026-09-30 12:44:18 +00:00

138 lines
4.3 KiB
JavaScript
Vendored
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Vue from 'vue'
// 无角标时的共享只读对象,避免 badge getter 每次调用都新建对象
const EMPTY_BADGE = Object.freeze({count: 0, dot: false})
/**
* 应用菜单角标(插件 / 自定义微应用)
*
* 结构:map[app_id][menu_key] = { count, dot }
* - menu_key 为空串表示该应用的第一个菜单
* - 初始值由 updateMicroAppsStatus 通过 hydrateMap 填充(apps/badge/list 一次返回插件 + 自定义全部角标)
* - 运行时由 websocket 消息 appBadge 经 set 增量更新
*/
export default {
namespaced: true,
state: () => ({
map: {},
}),
mutations: {
/**
* 增量设置单个菜单角标
* @param state
* @param appid
* @param menu_key
* @param count
* @param dot
*/
set(state, {appid, menu_key, count, dot}) {
if (!appid) {
return
}
const key = menu_key || ''
if (!state.map[appid]) {
Vue.set(state.map, appid, {})
}
Vue.set(state.map[appid], key, {
count: Number(count) || 0,
dot: !!dot,
})
},
/**
* 由后端角标快照整体初始化(apps/badge/list 返回,含插件 + 自定义微应用)
* @param state
* @param map 形如 { app_id: { 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 = next
},
/**
* 清除某应用某菜单的角标
*/
clearMenu(state, {appid, menu_key}) {
const key = menu_key || ''
if (appid && state.map[appid]) {
Vue.delete(state.map[appid], key)
}
},
/**
* 清除某应用全部角标(卸载/更新时)
*/
clearApp(state, appid) {
if (appid && state.map[appid]) {
Vue.delete(state.map, appid)
}
},
},
getters: {
/**
* 取单个菜单角标
* @returns {function(appid, menuKey): {count, dot}}
*/
badge: (state) => (appid, menuKey) => {
return (state.map[appid] && state.map[appid][menuKey || '']) || EMPTY_BADGE
},
/**
* 所有应用角标数字之和
*/
totalCount: (state) => {
let total = 0
Object.keys(state.map).forEach(appid => {
Object.keys(state.map[appid]).forEach(key => {
total += Number(state.map[appid][key].count) || 0
})
})
return total
},
/**
* 是否存在任一红点
*/
anyDot: (state) => {
return Object.keys(state.map).some(appid => {
return Object.keys(state.map[appid]).some(key => !!state.map[appid][key].dot)
})
},
/**
* 父『应用』入口聚合数字:工作报告未读(功能启用时)+ 所有插件角标数字之和
*/
applicationCount: (state, getters, rootState, rootGetters) => {
const reportCount = rootGetters.systemFunctionEnabled('report') ? (Number(rootState.reportUnreadNumber) || 0) : 0
return reportCount + getters.totalCount
},
/**
* 父『应用』入口聚合红点:总数为 0 时,任一插件存在红点则显示
*/
applicationDot: (state, getters) => getters.anyDot,
},
}