mirror of
https://github.com/kuaifan/dootask.git
synced 2026-09-11 14:38:41 +00:00
feat(app): support hiding AI interfaces
This commit is contained in:
parent
9dfcdc7b07
commit
b05b0874f8
@ -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();
|
||||
// 指定人员名单仅管理员可见
|
||||
|
||||
@ -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'),
|
||||
|
||||
|
||||
@ -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 接口或删除会话、消息。
|
||||
|
||||
## 选哪端做什么
|
||||
- **创建复杂任务 / 编辑长文档** → 桌面端
|
||||
- **快速回消息 / 收推送 / 移动审批** → 移动端
|
||||
|
||||
@ -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}) {
|
||||
|
||||
@ -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 &&
|
||||
|
||||
@ -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+"
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
18
resources/assets/js/functions/web.js
vendored
18
resources/assets/js/functions/web.js
vendored
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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"></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) {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 || {};
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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"></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() {
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-actions">
|
||||
<div v-if="!aiConversationHidden" class="profile-actions">
|
||||
<Button @click="onOpenDialog"><i class="taskfont"></i>{{ $L("开始聊天") }}</Button>
|
||||
<Button @click="onCreateGroup"><i class="taskfont"></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>
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
@ -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() {
|
||||
|
||||
5
resources/assets/js/store/actions.js
vendored
5
resources/assets/js/store/actions.js
vendored
@ -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
|
||||
}
|
||||
|
||||
// 以「匹配到的应用菜单第一个 menu(microAppsMenus 中该应用的第一条记录)」作为基础数据,合并出本次打开的 data
|
||||
if (data.skip_base_menu !== true) {
|
||||
|
||||
42
resources/assets/js/store/getters.js
vendored
42
resources/assets/js/store/getters.js
vendored
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
4
types/dootask-globals.d.ts
vendored
4
types/dootask-globals.d.ts
vendored
@ -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;
|
||||
/** 返回对话未读数量(不含免打扰,但如果免打扰中有@则返回@数量) */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user