feat(app): support hiding AI interfaces

This commit is contained in:
kuaifan 2026-08-28 12:20:25 +00:00
parent 9dfcdc7b07
commit b05b0874f8
24 changed files with 177 additions and 79 deletions

View File

@ -156,6 +156,7 @@ class SystemController extends AbstractController
$setting['unclaimed_task_reminder_time'] = $setting['unclaimed_task_reminder_time'] ?: '';
$setting['task_ai_auto_analyze'] = $setting['task_ai_auto_analyze'] ?: 'open';
$setting['department_owner_project_view'] = $setting['department_owner_project_view'] ?: 'open';
$setting['app_ai_hidden'] = config('dootask.app_ai_hidden');
$setting['server_timezone'] = config('app.timezone');
$setting['server_version'] = Base::getVersion();
// 指定人员名单仅管理员可见

View File

@ -8,6 +8,9 @@ return [
// 许可证显示开关:设为 'hidden' 时隐藏系统许可证信息Doo::license
'system_license' => env('SYSTEM_LICENSE'),
// 移动 APP AI 界面开关:设为 true 时仅在移动 APP 隐藏 AI 相关界面
'app_ai_hidden' => filter_var(env('APP_AI_HIDDEN', false), FILTER_VALIDATE_BOOLEAN),
// 演示账号登录页展示的演示账号SystemController::demo
'demo_account' => env('DEMO_ACCOUNT'),

View File

@ -19,7 +19,8 @@ negative:
- 移动端缺失的复杂功能不会硬塞,大屏复杂操作仍建议在桌面端完成
- 桌面端独有的能力(截图、全局快捷键、托盘)由原生 OS 集成,移动端无对应概念
- 微应用 / 插件部分能在移动端打开(走 WebView),但触屏不一定可用
last_verified: v1.7.90
- 部署环境可通过 APP_AI_HIDDEN=true 隐藏移动 APP 的 AI 界面,但不会关闭 Web、Electron 或服务端 AI 接口
last_verified: v1.9.18
---
# 移动端与桌面端功能差异
@ -31,7 +32,7 @@ last_verified: v1.7.90
- 项目(成员、视图基本浏览)
- 文件(上传、下载、预览)
- 通知与日程
- AI 助手(基础对话)
- AI 助手(基础对话;部署环境未隐藏移动 APP AI 界面时)
## 移动端独有
- 底部 Tabbar 原生导航
@ -61,6 +62,9 @@ last_verified: v1.7.90
| 报表 / 仪表盘 | 完整图表 + 编辑 | 仅查看 |
| 会议 | 创建 / 主持 / 屏幕共享 | 主要参会,部分高级功能受限 |
## AI 界面部署开关
部署管理员可设置 `APP_AI_HIDDEN=true`,仅在 iOS / Android APP 隐藏 AI 助手、AI 微应用、AI 生成与分析入口,以及邮箱符合 `ai-*@bot.system` 的 AI 机器人单聊入口。任务、项目或普通群聊中已有的 AI 消息仍会显示并正常通知Web 和 Electron 客户端不受影响,该配置也不会禁用服务端 AI 接口或删除会话、消息。
## 选哪端做什么
- **创建复杂任务 / 编辑长文档** → 桌面端
- **快速回消息 / 收推送 / 移动审批** → 移动端

View File

@ -42,7 +42,7 @@
<UserDetail/>
<!--AI 助理-->
<AIAssistant/>
<AIAssistant v-if="aiVisible"/>
<!--返回效果-->
<MobileBack v-if="isFirstPage"/>
@ -100,7 +100,7 @@ import MobileNotification from "./components/Mobile/Notification.vue";
import MobileBack from "./components/Mobile/Back.vue";
import DropdownMenu from "./components/DropdownMenu";
import {ctrlPressed} from "./mixins/ctrlPressed";
import {mapState} from "vuex";
import {mapGetters, mapState} from "vuex";
import emitter from "./store/events";
import AIAssistant from "./components/AIAssistant";
import UserDetail from "./pages/manage/components/UserDetail.vue";
@ -152,6 +152,8 @@ export default {
},
computed: {
...mapGetters(['aiVisible']),
...mapState(['ws', 'themeConf', 'windowOrientation', 'safeAreaSize', 'isFirstPage', 'mobileTabbar', 'themeName']),
statusColor({routeName, windowLandscape}) {

View File

@ -24,7 +24,7 @@
</template>
<script>
import {mapState} from "vuex";
import {mapGetters} from "vuex";
import emitter from "../../store/events";
import {createOperationModule} from "./operation-module";
import {loadFloatButtonVisible} from "./float-button-preference";
@ -61,14 +61,10 @@ export default {
},
computed: {
...mapState(['microAppsIds']),
aiInstalled() {
return this.microAppsIds?.includes('ai');
},
...mapGetters(['aiVisible']),
visible() {
return this.aiInstalled &&
return this.aiVisible &&
this.userId > 0 &&
this.positionLoaded &&
this.visibilityLoaded &&

View File

@ -27,7 +27,7 @@
</template>
<script>
import {mapGetters, mapState} from "vuex";
import {mapGetters} from "vuex";
import NetworkException from "../NetworkException";
import emitter from "../../store/events";
@ -55,8 +55,7 @@ export default {
},
computed: {
...mapState(['cacheDialogs']),
...mapGetters(['dashboardTask']),
...mapGetters(['dashboardTask', 'visibleDialogs']),
// appBadges getter
...mapGetters('appBadges', {
@ -71,7 +70,7 @@ export default {
msgUnreadMention() {
let num = 0; //
let mention = 0; //
this.cacheDialogs.some(dialog => {
this.visibleDialogs.some(dialog => {
num += $A.getDialogUnread(dialog, false);
mention += $A.getDialogMention(dialog);
})
@ -109,7 +108,7 @@ export default {
*/
msgAllUnread() {
let num = 0;
this.cacheDialogs.some(dialog => {
this.visibleDialogs.some(dialog => {
num += $A.getDialogNum(dialog);
})
return num;
@ -120,7 +119,7 @@ export default {
* @returns {string|null}
*/
msgTodoTotal() {
let todoNum = this.cacheDialogs.reduce((total, current) => total + (current.todo_num || 0), 0)
let todoNum = this.visibleDialogs.reduce((total, current) => total + (current.todo_num || 0), 0)
if (todoNum > 0) {
if (todoNum > 999) {
todoNum = "999+"

View File

@ -91,7 +91,7 @@
</template>
<script>
import {mapState} from "vuex";
import {mapGetters, mapState} from "vuex";
import emitter from "../store/events";
import transformEmojiToHtml from "../utils/emoji";
import {SEARCH_AI_SYSTEM_PROMPT, withLanguagePreferencePrompt} from "../utils/ai";
@ -147,6 +147,8 @@ export default {
},
computed: {
...mapGetters(['aiVisible', 'appAiHidden']),
...mapState([
'themeName',
'keyboardShow',
@ -156,7 +158,7 @@ export default {
aiSearchAvailable() {
return this.microAppsIds
&& this.microAppsIds.includes('search')
&& this.microAppsIds.includes('ai')
&& this.aiVisible
},
isFullscreen({windowWidth}) {
@ -164,7 +166,18 @@ export default {
},
items({searchKey, searchResults, action}) {
return searchResults.filter(item => item.key === searchKey && (!action || item.type === action))
return searchResults.filter(item => {
if (item.key !== searchKey || (action && item.type !== action)) {
return false
}
if (!this.appAiHidden) {
return true
}
if (item.type === 'message') {
return !$A.isAiBotDialog(item.rawData)
}
return true
})
},
total() {

View File

@ -234,6 +234,11 @@ export default {
type: Boolean,
default: false
},
// AI
hideAiBot: {
type: Boolean,
default: false
},
//
showDisable: {
type: Boolean,
@ -403,9 +408,7 @@ export default {
}
},
computed: {
...mapState([
'cacheDialogs',
]),
...mapState(['cacheDialogs']),
isFullscreen({windowWidth}) {
return windowWidth < 576
@ -424,7 +427,7 @@ export default {
return recents
case 'contact':
return contacts
return this.hideAiBot ? contacts.filter(item => !$A.isAiBotUser(item)) : contacts
case 'project':
return projects
@ -595,6 +598,9 @@ export default {
searchRecent() {
this.recents = this.cacheDialogs.filter(dialog => {
if (this.hideAiBot && $A.isAiBotDialog(dialog)) {
return false
}
if (this.onlyGroup && dialog.type != 'group') {
return false
}

View File

@ -213,6 +213,24 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
return tags;
},
/**
* 是否为 AI 机器人账号
* @param user
* @returns {boolean}
*/
isAiBotUser(user) {
return !!user?.bot && /^ai-[^@]+@bot\.system$/.test(user.email || '')
},
/**
* 是否为 AI 机器人单聊
* @param dialog
* @returns {boolean}
*/
isAiBotDialog(dialog) {
return dialog?.type === 'user' && this.isAiBotUser(dialog)
},
/**
* 对话完成
* @param dialog

View File

@ -272,7 +272,7 @@
<Icon type="ios-arrow-down"></Icon>
</Button>
<DropdownMenu slot="list">
<DropdownItem v-if="aiInstalled" name="aiAssistant">{{$L('AI 助手')}} ({{mateName}}+I)</DropdownItem>
<DropdownItem v-if="aiVisible" name="aiAssistant">{{$L('AI 助手')}} ({{mateName}}+I)</DropdownItem>
<DropdownItem name="task">{{$L('新建任务')}} ({{mateName}}+K)</DropdownItem>
<DropdownItem name="project">{{$L('新建项目')}} ({{mateName}}+B)</DropdownItem>
<DropdownItem name="group">{{$L('创建群组')}} ({{mateName}}+U)</DropdownItem>
@ -313,6 +313,7 @@
<div class="page-manage-project-ai-wrapper">
<Input ref="projectName" type="text" v-model="addData.name"></Input>
<div
v-if="aiVisible"
class="project-ai-button"
type="text"
@click="onProjectAI">
@ -651,11 +652,10 @@ export default {
'departmentOwnerProjectsRefreshing',
'departmentOwnerProjectViewEnabled',
'mcpServerStatus',
'microAppsIds'
'mcpServerStatus'
]),
...mapGetters(['dashboardTask', "filterMicroAppsMenusMain"]),
...mapGetters(['aiVisible', 'dashboardTask', "filterMicroAppsMenusMain", 'visibleDialogs']),
// appBadges getter
...mapGetters('appBadges', {
@ -663,10 +663,6 @@ export default {
applicationBadgeDot: 'applicationDot',
}),
aiInstalled() {
return this.microAppsIds?.includes('ai');
},
departmentOwnerViewAvailable() {
return this.systemConfig.department_owner_project_view === 'open' && (this.userInfo.managed_departments || []).length > 0;
},
@ -701,7 +697,7 @@ export default {
msgUnreadMention() {
let num = 0; //
let mention = 0; //
this.cacheDialogs.some(dialog => {
this.visibleDialogs.some(dialog => {
num += $A.getDialogUnread(dialog, false);
mention += $A.getDialogMention(dialog);
})
@ -739,7 +735,7 @@ export default {
*/
msgAllUnread() {
let num = 0;
this.cacheDialogs.some(dialog => {
this.visibleDialogs.some(dialog => {
num += $A.getDialogNum(dialog);
})
return num;
@ -750,7 +746,7 @@ export default {
* @returns {string|null}
*/
msgTodoTotal() {
let todoNum = this.cacheDialogs.reduce((total, current) => total + (current.todo_num || 0), 0)
let todoNum = this.visibleDialogs.reduce((total, current) => total + (current.todo_num || 0), 0)
if (todoNum > 0) {
if (todoNum > 99) {
todoNum = "99+"
@ -1486,7 +1482,7 @@ export default {
break;
case 73: // I - AI
if (this.aiInstalled) {
if (this.aiVisible) {
e.preventDefault();
this.onOpenAIAssistant();
}

View File

@ -152,7 +152,7 @@
<em>{{$L('上传文件')}}</em>
</div>
</template>
<div v-if="dialogId > 0" class="chat-input-popover-item" @click="onToolbar('ai')">
<div v-if="dialogId > 0 && aiVisible" class="chat-input-popover-item" @click="onToolbar('ai')">
<i class="taskfont">&#xe8a1;</i>
<em>{{$L('AI 生成')}}</em>
</div>
@ -606,7 +606,7 @@ export default {
'viewportHeight',
]),
...mapGetters(['getDialogDraft', 'getDialogQuote']),
...mapGetters(['aiVisible', 'getDialogDraft', 'getDialogQuote']),
isEnterSend({cacheKeyboard}) {
if (this.$isEEUIApp) {

View File

@ -5,6 +5,7 @@
ref="forwardSelect"
:title="title"
:show-bot="showBot"
:hide-ai-bot="appAiHidden"
:multiple-max="userMaxSelect"
:before-submit="onSelectBefore"
:show-select-all="false"
@ -31,6 +32,7 @@
<script>
import UserSelect from "../../../../components/UserSelect.vue";
import ForwardConfirm from "./confirm.vue";
import {mapGetters} from "vuex";
export default {
name: "Forwarder",
@ -94,6 +96,10 @@ export default {
}
},
computed: {
...mapGetters(['appAiHidden']),
},
methods: {
onSelection() {
this.$refs.forwardSelect.onSelection()

View File

@ -435,7 +435,7 @@
</template>
</FormItem>
<FormItem :label="$L('AI任务分析')" prop="ai_auto_analyze">
<FormItem v-if="aiVisible" :label="$L('AI任务分析')" prop="ai_auto_analyze">
<RadioGroup v-model="settingData.ai_auto_analyze">
<Radio label="open" :disabled="systemConfig.task_ai_auto_analyze === 'close'">{{$L('开启')}}</Radio>
<Radio label="close">{{$L('关闭')}}</Radio>
@ -765,7 +765,7 @@ export default {
'systemConfig',
]),
...mapGetters(['projectData', 'transforTasks']),
...mapGetters(['aiVisible', 'projectData', 'transforTasks']),
tabTypeActive() {
return this.projectData.cacheParameter.menuType

View File

@ -43,7 +43,7 @@
</li>
</ul>
<div ref="reportContent" @click="onClick" class="report-content user-select-auto" v-html="currentDetail.content"></div>
<div v-if="currentDetail.id" class="report-ai-analysis">
<div v-if="currentDetail.id && aiVisible" class="report-ai-analysis">
<div class="analysis-header">
<div class="analysis-title">{{ $L("AI 分析") }}</div>
<Button
@ -72,7 +72,7 @@
<script>
const VMPreview = () => import('../../../components/VMEditor/preview');
import {mapState} from "vuex";
import {mapGetters, mapState} from "vuex";
import emitter from "../../../store/events";
import {extractPlainText} from "../../../utils/text";
import {REPORT_ANALYSIS_SYSTEM_PROMPT, withLanguagePreferencePrompt} from "../../../utils/ai";
@ -97,6 +97,8 @@ export default {
}
},
computed: {
...mapGetters(['aiVisible']),
...mapState(['formOptions', 'userInfo']),
currentDetail() {
return this.detail || this.data || {};

View File

@ -46,6 +46,7 @@
<div class="report-bottoms">
<Button type="primary" @click="handleSubmit" :loading="loadIng > 0" class="report-bottom">{{$L(id > 0 ? '修改' : '提交')}}</Button>
<Button
v-if="aiVisible"
type="default"
class="report-bottom"
@click="onOrganize">
@ -60,7 +61,7 @@
<script>
import UserSelect from "../../../components/UserSelect.vue";
import {mapState} from "vuex";
import {mapGetters, mapState} from "vuex";
import emitter from "../../../store/events";
import {MarkdownConver} from "../../../utils/markdown";
import {extractPlainText} from "../../../utils/text";
@ -112,6 +113,8 @@ export default {
},
},
computed: {
...mapGetters(['aiVisible']),
...mapState(['formOptions']),
},
methods: {

View File

@ -39,7 +39,7 @@
:placeholder="$L('任务描述')"
enterkeyhint="done"
@on-keydown="onKeydown"/>
<div class="ai-btn" @click="onAI">
<div v-if="aiVisible" class="ai-btn" @click="onAI">
<i class="taskfont">&#xe8a1;</i>
</div>
</div>
@ -201,7 +201,7 @@
</template>
<script>
import {mapState} from "vuex";
import {mapGetters, mapState} from "vuex";
import emitter from "../../../store/events";
import UserSelect from "../../../components/UserSelect.vue";
import TaskExistTips from "./TaskExistTips.vue";
@ -292,6 +292,8 @@ export default {
},
computed: {
...mapGetters(['aiVisible']),
...mapState(['cacheProjects', 'projectId', 'cacheColumns', 'taskPriority', 'taskTemplates', 'formOptions']),
taskTemplateShareEnabled() {

View File

@ -35,7 +35,7 @@
</div>
</div>
<div class="profile-actions">
<div v-if="!aiConversationHidden" class="profile-actions">
<Button @click="onOpenDialog"><i class="taskfont">&#xe6eb;</i>{{ $L("开始聊天") }}</Button>
<Button @click="onCreateGroup"><i class="taskfont">&#xe63f;</i>{{ $L("创建群组") }}</Button>
</div>
@ -131,7 +131,7 @@
<script>
import emitter from "../../../store/events";
import { mapState } from "vuex";
import { mapGetters, mapState } from "vuex";
import transformEmojiToHtml from "../../../utils/emoji";
import UserTagsModal from "./UserTagsModal.vue";
import CommonDialogModal from "./CommonDialogModal.vue";
@ -182,6 +182,12 @@ export default {
},
computed: {
...mapGetters(['appAiHidden']),
aiConversationHidden() {
return this.appAiHidden && $A.isAiBotUser(this.userData)
},
isFullscreen({ windowWidth }) {
return windowWidth < 576;
},
@ -464,4 +470,4 @@ export default {
},
},
};
</script>
</script>

View File

@ -380,7 +380,12 @@ export default {
'longpressData'
]),
...mapGetters(['getDialogDraft', 'tagDialogDraft']),
...mapGetters([
'appAiHidden',
'getDialogDraft',
'tagDialogDraft',
'visibleDialogs',
]),
contactAvatarSize() {
return this.windowPortrait ? 36 : 30
@ -389,19 +394,19 @@ export default {
dialogList() {
const {dialogActive, dialogSearchKey, dialogSearchList} = this
if (dialogSearchList.length > 0) {
return dialogSearchList.sort((a, b) => {
return dialogSearchList.filter(dialog => !this.appAiHidden || !$A.isAiBotDialog(dialog)).sort((a, b) => {
//
return (a.is_search === true ? 1 : 0) - (b.is_search === true ? 1 : 0)
})
}
if (dialogActive == '' && dialogSearchKey == '') {
return this.cacheDialogs.filter(dialog => this.filterDialog(dialog)).sort(this.dialogSort);
return this.visibleDialogs.filter(dialog => this.filterDialog(dialog)).sort(this.dialogSort);
}
if (dialogActive == 'mark' && !dialogSearchKey) {
const lists = [];
this.dialogMsgs.filter(h => h.tag).forEach(h => {
let dialog = $A.cloneJSON(this.cacheDialogs).find(p => p.id == h.dialog_id)
if (dialog) {
if (dialog && (!this.appAiHidden || !$A.isAiBotDialog(dialog))) {
dialog.last_msg = h;
dialog.search_msg_id = h.id;
lists.push(dialog);
@ -409,7 +414,7 @@ export default {
});
return lists;
}
const list = this.cacheDialogs.filter(dialog => {
const list = this.visibleDialogs.filter(dialog => {
if (!this.filterDialog(dialog)) {
return false;
}
@ -508,7 +513,7 @@ export default {
msgUnread() {
return function (type) {
let num = 0
this.cacheDialogs.some((dialog) => {
this.visibleDialogs.some((dialog) => {
switch (type) {
case 'project':
case 'task':
@ -839,6 +844,9 @@ export default {
},
filterDialog(dialog) {
if (this.appAiHidden && $A.isAiBotDialog(dialog)) {
return false
}
if ((dialog.id > 0 && dialog.id == this.dialogId) || dialog.top_at || dialog.todo_num > 0 || $A.getDialogNum(dialog) > 0) {
return true
}

View File

@ -158,7 +158,7 @@
:placeholder="$L('请选择提醒时间')"
transfer/>
</FormItem>
<FormItem :label="$L('AI任务分析')" prop="taskAiAutoAnalyze">
<FormItem v-if="aiVisible" :label="$L('AI任务分析')" prop="taskAiAutoAnalyze">
<RadioGroup v-model="formDatum.task_ai_auto_analyze">
<Radio label="open">{{$L('开启')}}</Radio>
<Radio label="close">{{$L('关闭')}}</Radio>
@ -354,7 +354,7 @@
</template>
<script>
import {mapState} from "vuex";
import {mapGetters, mapState} from "vuex";
import UserSelect from "../../../../components/UserSelect.vue";
import SystemColumnTemplate from "./SystemColumnTemplate.vue";
import SystemFileSetting from "./SystemFileSetting.vue";
@ -394,6 +394,8 @@ export default {
},
computed: {
...mapGetters(['aiVisible']),
...mapState(['formOptions']),
projectAddAll() {

View File

@ -46,7 +46,7 @@
</template>
<script>
import {mapState} from "vuex";
import {mapGetters, mapState} from "vuex";
import axios from "axios";
import MobileNavTitle from "../../../components/Mobile/NavTitle.vue";
import emitter from "../../../store/events";
@ -72,11 +72,9 @@ export default {
},
computed: {
...mapState(['userInfo', 'userIsAdmin', 'clientNewVersion', 'systemConfig', 'microAppsIds']),
...mapGetters(['aiVisible']),
aiInstalled() {
return this.microAppsIds?.includes('ai');
},
...mapState(['userInfo', 'userIsAdmin', 'clientNewVersion', 'systemConfig']),
showContent() {
return this.$route.path.match(/^\/manage\/setting\/\w+$/)
@ -95,7 +93,7 @@ export default {
menu.push({path: 'keyboard', name: '键盘设置'})
}
if (this.aiInstalled) {
if (this.aiVisible) {
menu.push({path: 'assistant', name: 'AI 助手设置'})
}
@ -147,14 +145,14 @@ export default {
handler(name) {
if (name === 'manage-setting' && this.windowLandscape) {
this.goForward({name: 'manage-setting-personal'}, true);
} else if (name === 'manage-setting-assistant' && !this.aiInstalled) {
} else if (name === 'manage-setting-assistant' && !this.aiVisible) {
this.goForward({name: 'manage-setting-personal'}, true);
}
},
immediate: true
},
microAppsIds() {
if (this.routeName === 'manage-setting-assistant' && !this.aiInstalled) {
aiVisible(visible) {
if (this.routeName === 'manage-setting-assistant' && !visible) {
this.goForward({name: 'manage-setting-personal'}, true);
}
},

View File

@ -12,7 +12,7 @@
{{mateName}}<div class="input-box-push">+</div>{{altName}}<div class="input-box-push">+</div>L
</div>
</FormItem>
<FormItem v-if="aiInstalled" :label="$L('AI 助手')">
<FormItem v-if="aiVisible" :label="$L('AI 助手')">
<div class="input-box">
{{mateName}}<div class="input-box-push">+</div>I
</div>
@ -75,15 +75,11 @@
}
</style>
<script>
import {mapState} from "vuex";
import {mapGetters} from "vuex";
export default {
computed: {
...mapState(['microAppsIds']),
aiInstalled() {
return this.microAppsIds?.includes('ai');
},
...mapGetters(['aiVisible']),
},
data() {

View File

@ -5401,7 +5401,7 @@ export default {
* - props 传递参数
* 更多说明详见 https://appstore.dootask.com/development/manual
*/
async openMicroApp({state}, data) {
async openMicroApp({state, getters}, data) {
// 参数基础校验:必须是对象且包含 id/name/url
if (!data || !$A.isJson(data)) {
return
@ -5419,6 +5419,9 @@ export default {
$A.modalWarning(`应用「${data.id}」未安装`)
return
}
if (microAppId === 'ai' && !getters.aiVisible) {
return
}
// 以「匹配到的应用菜单第一个 menumicroAppsMenus 中该应用的第一条记录)」作为基础数据,合并出本次打开的 data
if (data.skip_base_menu !== true) {

View File

@ -278,6 +278,36 @@ export default {
return quote || null
},
/**
* AI 界面是否可见
* 移动 APP 等待系统配置加载完成后再展示避免启动时闪现
*/
aiVisible(state, getters) {
const installed = state.microAppsIds?.includes('ai')
if (!$A.isEEUIApp) {
return installed
}
return installed && state.systemConfig.__state === 'success' && !getters.appAiHidden
},
/**
* 移动 APP 是否隐藏 AI 功能
*/
appAiHidden(state) {
return $A.isEEUIApp
&& (state.systemConfig.__state !== 'success' || state.systemConfig.app_ai_hidden === true)
},
/**
* 当前客户端可见的会话
*/
visibleDialogs(state, getters) {
if (!getters.appAiHidden) {
return state.cacheDialogs
}
return state.cacheDialogs.filter(dialog => !$A.isAiBotDialog(dialog))
},
/**
* 获取应用菜单
* 过滤出location为application的菜单项
@ -285,9 +315,9 @@ export default {
* @param {Object} state
* @returns {Array}
*/
filterMicroAppsMenus: (state) => {
filterMicroAppsMenus: (state, getters) => {
return state.microAppsMenus.filter(item => {
return item.location === 'application'
return item.location === 'application' && (item.id !== 'ai' || getters.aiVisible)
})
},
@ -298,9 +328,9 @@ export default {
* @param {Object} state
* @returns {Array}
*/
filterMicroAppsMenusAdmin: (state) => {
filterMicroAppsMenusAdmin: (state, getters) => {
return state.microAppsMenus.filter(item => {
return item.location === 'application/admin'
return item.location === 'application/admin' && (item.id !== 'ai' || getters.aiVisible)
})
},
@ -311,9 +341,9 @@ export default {
* @param {Object} state
* @returns {Array}
*/
filterMicroAppsMenusMain: (state) => {
filterMicroAppsMenusMain: (state, getters) => {
return state.microAppsMenus.filter(item => {
return item.location === 'main/menu'
return item.location === 'main/menu' && (item.id !== 'ai' || getters.aiVisible)
})
}
}

View File

@ -468,6 +468,10 @@ interface DooTaskGlobal {
timeOptionShortcuts(): Array<{ text: string; value(): [Date, Date] }>;
/** 对话标签(已完成/已删除/已归档) */
dialogTags(dialog: any): Array<{ color: string; text: string }>;
/** 是否为 AI 机器人账号 */
isAiBotUser(user: any): boolean;
/** 是否为 AI 机器人单聊 */
isAiBotDialog(dialog: any): boolean;
/** 对话是否完成(返回 success 标签) */
dialogCompleted(dialog: any): { color: string; text: string } | undefined;
/** 返回对话未读数量(不含免打扰,但如果免打扰中有@则返回@数量) */