mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-11 10:33:54 +00:00
1698 lines
64 KiB
Vue
1698 lines
64 KiB
Vue
<template>
|
||
<div class="page-manage" :class="pageClass">
|
||
<div ref="boxMenu" class="manage-box-menu">
|
||
<Dropdown
|
||
class="page-manage-menu-dropdown main-menu"
|
||
trigger="click"
|
||
@on-click="settingRoute"
|
||
@on-visible-change="menuVisibleChange">
|
||
<div :class="['manage-box-title', visibleMenu ? 'menu-visible' : '']">
|
||
<div class="manage-box-avatar">
|
||
<UserAvatar :userid="userId" :size="36"/>
|
||
</div>
|
||
<span>{{userInfo.nickname}}</span>
|
||
<Badge v-if="!!clientNewVersion" class="manage-box-top-report" dot/>
|
||
<div class="manage-box-arrow">
|
||
<Icon type="ios-arrow-up" />
|
||
<Icon type="ios-arrow-down" />
|
||
</div>
|
||
</div>
|
||
<DropdownMenu slot="list">
|
||
<template v-for="(item, index) in menu">
|
||
<!--最近打开的任务-->
|
||
<Dropdown
|
||
v-if="item.path === 'taskBrowse'"
|
||
:key="`taskBrowse-${index}`"
|
||
transfer
|
||
transfer-class-name="page-manage-menu-dropdown max-h-400"
|
||
placement="right-start">
|
||
<DropdownItem :divided="!!item.divided">
|
||
<div class="manage-menu-flex">
|
||
<div class="manage-menu-title">
|
||
{{$L(item.name)}}
|
||
</div>
|
||
<Icon type="ios-arrow-forward"></Icon>
|
||
</div>
|
||
</DropdownItem>
|
||
<DropdownMenu slot="list" v-if="taskBrowseLists.length > 0">
|
||
<template v-for="(item, key) in taskBrowseLists">
|
||
<DropdownItem
|
||
v-if="item.id > 0 && key < 10"
|
||
:key="`task-${key}`"
|
||
:style="$A.generateColorVarStyle(item.flow_item_color, [10], 'flow-item-custom-color')"
|
||
class="task-title"
|
||
@click.native="openTask(item)"
|
||
:name="item.name">
|
||
<span v-if="item.flow_item_name" :class="item.flow_item_status">{{item.flow_item_name}}</span>
|
||
<div class="task-title-text">{{ item.name }}</div>
|
||
</DropdownItem>
|
||
</template>
|
||
<DropdownItem
|
||
:key="'task-browse-view-more'"
|
||
class="task-title task-view-more"
|
||
@click.native="openRecent"
|
||
name="taskBrowseViewMore">
|
||
<div class="task-title-text">{{ $L('查看更多...') }}</div>
|
||
</DropdownItem>
|
||
</DropdownMenu>
|
||
<DropdownMenu v-else slot="list">
|
||
<DropdownItem style="color:darkgrey">{{ $L('暂无打开记录') }}</DropdownItem>
|
||
</DropdownMenu>
|
||
</Dropdown>
|
||
<!-- 团队管理 -->
|
||
<Dropdown
|
||
v-else-if="item.path === 'team'"
|
||
:key="`team-${index}`"
|
||
transfer
|
||
transfer-class-name="page-manage-menu-dropdown"
|
||
placement="right-start">
|
||
<DropdownItem :divided="!!item.divided">
|
||
<div class="manage-menu-flex">
|
||
<div class="manage-menu-title">
|
||
{{$L(item.name)}}
|
||
</div>
|
||
<Icon type="ios-arrow-forward"></Icon>
|
||
</div>
|
||
</DropdownItem>
|
||
<DropdownMenu slot="list">
|
||
<DropdownItem name="allUser">{{$L('团队管理')}}</DropdownItem>
|
||
<DropdownItem name="exportTask">{{$L('导出任务统计')}}</DropdownItem>
|
||
<DropdownItem name="exportOverdueTask">{{$L('导出超期任务')}}</DropdownItem>
|
||
<DropdownItem name="exportApprove">{{$L('导出审批数据')}}</DropdownItem>
|
||
<DropdownItem name="exportCheckin">{{$L('导出签到数据')}}</DropdownItem>
|
||
</DropdownMenu>
|
||
</Dropdown>
|
||
<!-- 其他菜单 -->
|
||
<DropdownItem
|
||
v-else-if="item.visible !== false"
|
||
:key="`menu-${index}`"
|
||
:divided="!!item.divided"
|
||
:name="item.path"
|
||
:style="item.style || {}">
|
||
<div class="manage-menu-flex">
|
||
<div class="manage-menu-title">
|
||
{{$L(item.name)}}
|
||
</div>
|
||
<Icon
|
||
v-if="item.selected === true"
|
||
type="md-checkmark" />
|
||
<Badge
|
||
v-if="item.path === 'version'"
|
||
class="manage-menu-report-badge"
|
||
:text="clientNewVersion"/>
|
||
<Badge
|
||
v-else-if="item.path === 'workReport' && reportUnreadNumber > 0"
|
||
class="manage-menu-report-badge"
|
||
:count="reportUnreadNumber"/>
|
||
<Badge
|
||
v-else-if="item.path === 'approve' && approveUnreadNumber > 0"
|
||
class="manage-menu-report-badge"
|
||
:count="approveUnreadNumber"/>
|
||
</div>
|
||
</DropdownItem>
|
||
</template>
|
||
</DropdownMenu>
|
||
</Dropdown>
|
||
<Scrollbar class-name="manage-item" @on-scroll="operateVisible = false">
|
||
<div class="menu-base">
|
||
<ul>
|
||
<li @click="toggleRoute('dashboard')" :class="classNameRoute('dashboard')">
|
||
<i class="taskfont"></i>
|
||
<div class="menu-title">{{$L('仪表盘')}}</div>
|
||
<Badge v-if="dashboardTask.overdue_count > 0" class="menu-badge" type="error" :overflow-count="999" :count="dashboardTask.overdue_count"/>
|
||
<Badge v-else-if="dashboardTask.today_count > 0" class="menu-badge" type="info" :overflow-count="999" :count="dashboardTask.today_count"/>
|
||
<Badge v-else-if="dashboardTask.todo_count > 0" class="menu-badge" type="primary" :overflow-count="999" :count="dashboardTask.todo_count"/>
|
||
</li>
|
||
<li @click="toggleRoute('calendar')" :class="classNameRoute('calendar')">
|
||
<i class="taskfont"></i>
|
||
<div class="menu-title">{{$L('日历')}}</div>
|
||
</li>
|
||
<li @click="toggleRoute('messenger')" :class="classNameRoute('messenger')">
|
||
<i class="taskfont"></i>
|
||
<div class="menu-title">{{$L('消息')}}</div>
|
||
<Badge class="menu-badge" :overflow-count="999" :text="msgUnreadMention"/>
|
||
</li>
|
||
<li @click="toggleRoute('file')" :class="classNameRoute('file')">
|
||
<i class="taskfont"></i>
|
||
<div class="menu-title">{{$L('文件')}}</div>
|
||
</li>
|
||
<li @click="toggleRoute('application')" :class="classNameRoute('application')">
|
||
<i class="taskfont"></i>
|
||
<div class="menu-title">{{$L('应用')}}</div>
|
||
<Badge class="menu-badge" :overflow-count="999" :text="String((reportUnreadNumber + approveUnreadNumber) || '')"/>
|
||
</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>
|
||
</div>
|
||
<div ref="menuProject" class="menu-project">
|
||
<Draggable
|
||
:list="projectDraggableList"
|
||
:animation="150"
|
||
:disabled="$isEEUIApp || windowTouch || !!projectKeyValue"
|
||
tag="ul"
|
||
item-key="id"
|
||
draggable="li:not(.pinned)"
|
||
handle=".project-h1"
|
||
v-longpress="handleLongpress"
|
||
@start="projectDragging = true"
|
||
@end="onProjectSortEnd">
|
||
<li
|
||
v-for="item in projectDraggableList"
|
||
:ref="`project_${item.id}`"
|
||
:key="item.id"
|
||
:class="[classNameProject(item), item.top_at ? 'pinned' : '']"
|
||
:data-id="item.id"
|
||
@pointerdown="handleOperation"
|
||
@click="toggleRoute('project', {projectId: item.id})">
|
||
<div class="project-h1">
|
||
<em @click.stop="toggleOpenMenu(item.id)"></em>
|
||
<div class="title" v-html="transformEmojiToHtml(item.name)"></div>
|
||
<div v-if="item.top_at" class="icon-top"></div>
|
||
<div v-if="item.task_my_num - item.task_my_complete > 0" class="num">{{item.task_my_num - item.task_my_complete}}</div>
|
||
</div>
|
||
<div class="project-h2">
|
||
<p>
|
||
<em>{{$L('我的')}}:</em>
|
||
<span>{{item.task_my_complete}}/{{item.task_my_num}}</span>
|
||
<Progress :percent="item.task_my_percent" :stroke-width="6" />
|
||
</p>
|
||
<p>
|
||
<em>{{$L('全部')}}:</em>
|
||
<span>{{item.task_complete}}/{{item.task_num}}</span>
|
||
<Progress :percent="item.task_percent" :stroke-width="6" />
|
||
</p>
|
||
</div>
|
||
</li>
|
||
<li v-if="projectKeyLoading > 0" class="loading"><Loading/></li>
|
||
</Draggable>
|
||
</div>
|
||
</Scrollbar>
|
||
<div
|
||
v-transfer-dom
|
||
:data-transfer="true"
|
||
class="operate-position"
|
||
:style="operateStyles"
|
||
v-show="operateVisible">
|
||
<Dropdown
|
||
trigger="custom"
|
||
:placement="windowLandscape ? 'bottom' : 'top'"
|
||
:visible="operateVisible"
|
||
@on-clickoutside="operateVisible = false"
|
||
transfer>
|
||
<div :style="{userSelect:operateVisible ? 'none' : 'auto', height: operateStyles.height}"></div>
|
||
<DropdownMenu slot="list">
|
||
<DropdownItem @click.native="handleTopClick">
|
||
{{ $L(operateItem.top_at ? '取消置顶' : '置顶该项目') }}
|
||
</DropdownItem>
|
||
<DropdownItem @click.native="handleChatClick">
|
||
{{ $L('项目讨论') }}
|
||
</DropdownItem>
|
||
</DropdownMenu>
|
||
</Dropdown>
|
||
</div>
|
||
<div
|
||
v-if="projectKeyValue || ((projectSearchShow || projectTotal > 20) && windowHeight > 600)"
|
||
class="manage-project-search">
|
||
<div class="search-pre">
|
||
<Loading v-if="projectKeyLoading > 0"/>
|
||
<Icon v-else type="ios-search" />
|
||
</div>
|
||
<Form class="search-form" action="javascript:void(0)" @submit.native.prevent="$A.eeuiAppKeyboardHide">
|
||
<Input type="search" v-model="projectKeyValue" :placeholder="$L(`共${projectTotal || cacheProjects.length}个项目,搜索...`)" clearable/>
|
||
</Form>
|
||
</div>
|
||
<ButtonGroup class="manage-box-new-group">
|
||
<Button class="manage-box-new" type="primary" icon="md-add" @click="onAddShow">{{$L('新建项目')}}</Button>
|
||
<Dropdown @on-click="onAddMenu" trigger="click">
|
||
<Button type="primary">
|
||
<Icon type="ios-arrow-down"></Icon>
|
||
</Button>
|
||
<DropdownMenu slot="list">
|
||
<DropdownItem name="project">{{$L('新建项目')}} ({{mateName}}+B)</DropdownItem>
|
||
<DropdownItem name="task">{{$L('新建任务')}} ({{mateName}}+K)</DropdownItem>
|
||
<DropdownItem name="group">{{$L('创建群组')}} ({{mateName}}+U)</DropdownItem>
|
||
<DropdownItem name="createMeeting">{{$L('新会议')}} ({{mateName}}+J)</DropdownItem>
|
||
<DropdownItem name="joinMeeting">{{$L('加入会议')}}</DropdownItem>
|
||
</DropdownMenu>
|
||
</Dropdown>
|
||
</ButtonGroup>
|
||
</div>
|
||
|
||
<div class="manage-box-main" :role="routeName">
|
||
<div class="manage-status-bar"><span></span></div>
|
||
<keep-alive>
|
||
<router-view class="manage-box-view" @on-click="onTabbarClick"></router-view>
|
||
</keep-alive>
|
||
<div class="manage-navigation-bar"><span></span></div>
|
||
</div>
|
||
|
||
<!--新建项目-->
|
||
<Modal
|
||
v-model="addShow"
|
||
:title="$L('新建项目')"
|
||
:mask-closable="false">
|
||
<Form
|
||
ref="addProject"
|
||
:model="addData"
|
||
:rules="addRule"
|
||
v-bind="formOptions"
|
||
@submit.native.prevent>
|
||
<FormItem prop="name" :label="$L('项目名称')">
|
||
<div class="page-manage-project-ai-wrapper">
|
||
<Input ref="projectName" type="text" v-model="addData.name"></Input>
|
||
<div
|
||
class="project-ai-button"
|
||
type="text"
|
||
@click="onProjectAI">
|
||
<i class="taskfont"></i>
|
||
</div>
|
||
</div>
|
||
</FormItem>
|
||
<FormItem v-if="addData.columns" :label="$L('任务列表')">
|
||
<TagInput v-model="addData.columns"/>
|
||
</FormItem>
|
||
<FormItem v-else :label="$L('项目模板')">
|
||
<Select :value="0" @on-change="selectChange" :placeholder="$L('请选择模板')">
|
||
<Option v-for="(item, index) in columns" :value="index" :key="index">{{ item.name }}</Option>
|
||
</Select>
|
||
</FormItem>
|
||
<FormItem prop="flow" :label="$L('开启工作流')">
|
||
<RadioGroup v-model="addData.flow">
|
||
<Radio label="open">{{$L('开启')}}</Radio>
|
||
<Radio label="close">{{$L('关闭')}}</Radio>
|
||
</RadioGroup>
|
||
</FormItem>
|
||
</Form>
|
||
<div slot="footer" class="adaption">
|
||
<Button type="default" @click="addShow=false">{{$L('取消')}}</Button>
|
||
<Button type="primary" :loading="loadIng > 0" @click="onAddProject">{{$L('添加')}}</Button>
|
||
</div>
|
||
</Modal>
|
||
|
||
<!--添加任务-->
|
||
<Modal
|
||
v-model="addTaskShow"
|
||
:mask-closable="false"
|
||
:styles="{
|
||
width: '90%',
|
||
maxWidth: '640px'
|
||
}"
|
||
footer-hide>
|
||
<TaskAdd ref="addTask" v-model="addTaskShow"/>
|
||
</Modal>
|
||
|
||
<!--创建群组-->
|
||
<Modal
|
||
v-model="createGroupShow"
|
||
:title="$L('创建群组')"
|
||
:mask-closable="false">
|
||
<Form :model="createGroupData" v-bind="formOptions" @submit.native.prevent>
|
||
<FormItem prop="avatar" :label="$L('群头像')">
|
||
<ImgUpload v-model="createGroupData.avatar" :num="1" :width="512" :height="512" whcut="cover"/>
|
||
</FormItem>
|
||
<FormItem prop="userids" :label="$L('群成员')">
|
||
<UserSelect v-model="createGroupData.userids" :uncancelable="createGroupData.uncancelable" :multiple-max="100" show-bot :title="$L('选择项目成员')"/>
|
||
</FormItem>
|
||
<FormItem prop="chat_name" :label="$L('群名称')">
|
||
<Input v-model="createGroupData.chat_name" :placeholder="$L('输入群名称(选填)')"/>
|
||
</FormItem>
|
||
</Form>
|
||
<div slot="footer" class="adaption">
|
||
<Button type="default" @click="createGroupShow=false">{{$L('取消')}}</Button>
|
||
<Button type="primary" :loading="createGroupLoad > 0" @click="submitCreateGroup">{{$L('创建')}}</Button>
|
||
</div>
|
||
</Modal>
|
||
|
||
<!--弹出 MCP 服务器信息-->
|
||
<MCPHelper v-model="mcpHelperShow"/>
|
||
|
||
<!--导出任务统计-->
|
||
<TaskExport v-model="exportTaskShow"/>
|
||
|
||
<!--导出签到数据-->
|
||
<CheckinExport v-model="exportCheckinShow"/>
|
||
|
||
<!--导出审批数据-->
|
||
<ApproveExport v-model="exportApproveShow"/>
|
||
|
||
<!--任务详情-->
|
||
<TaskModal ref="taskModal"/>
|
||
|
||
<!--聊天窗口-->
|
||
<DialogModal ref="dialogModal"/>
|
||
|
||
<!--搜索框-->
|
||
<SearchBox ref="searchBox"/>
|
||
|
||
<!--工作报告-->
|
||
<DrawerOverlay
|
||
v-model="workReportShow"
|
||
placement="right"
|
||
:size="1200">
|
||
<Report v-if="workReportShow" v-model="workReportTab" @on-read="$store.dispatch('getReportUnread', 1000)" />
|
||
</DrawerOverlay>
|
||
|
||
<!--我的收藏-->
|
||
<DrawerOverlay
|
||
v-model="favoriteShow"
|
||
placement="right"
|
||
:size="1200">
|
||
<FavoriteManagement v-if="favoriteShow" @on-close="favoriteShow = false"/>
|
||
</DrawerOverlay>
|
||
|
||
<!--最近打开-->
|
||
<DrawerOverlay
|
||
v-model="recentShow"
|
||
placement="right"
|
||
:size="1200">
|
||
<RecentManagement v-if="recentShow" @on-close="recentShow = false"/>
|
||
</DrawerOverlay>
|
||
|
||
<!--团队成员管理-->
|
||
<DrawerOverlay
|
||
v-model="allUserShow"
|
||
placement="right"
|
||
:size="1380">
|
||
<TeamManagement v-if="allUserShow" @on-close="allUserShow=false"/>
|
||
</DrawerOverlay>
|
||
|
||
<!--查看所有项目-->
|
||
<DrawerOverlay
|
||
v-model="allProjectShow"
|
||
placement="right"
|
||
:size="1200">
|
||
<ProjectManagement v-if="allProjectShow"/>
|
||
</DrawerOverlay>
|
||
|
||
<!--举报投诉管理-->
|
||
<DrawerOverlay
|
||
v-model="complaintShow"
|
||
placement="right"
|
||
:size="1200">
|
||
<ComplaintManagement v-if="complaintShow"/>
|
||
</DrawerOverlay>
|
||
|
||
<!--查看归档项目-->
|
||
<DrawerOverlay
|
||
v-model="archivedProjectShow"
|
||
placement="right"
|
||
:size="1200">
|
||
<ProjectArchived v-if="archivedProjectShow"/>
|
||
</DrawerOverlay>
|
||
|
||
<!--审批中心-->
|
||
<DrawerOverlay v-model="approveShow" placement="right" :size="1380" class-name="approve-drawer">
|
||
<Approve v-if="approveShow"/>
|
||
</DrawerOverlay>
|
||
|
||
<!--审批详情-->
|
||
<DrawerOverlay v-model="approveDetailsShow" placement="right" :size="600">
|
||
<ApproveDetails v-if="approveDetailsShow" :data="approveDetails"/>
|
||
</DrawerOverlay>
|
||
|
||
<!--移动端选项卡-->
|
||
<transition name="mobile-slide">
|
||
<MobileTabbar v-if="mobileTabbar" @on-click="onTabbarClick"/>
|
||
</transition>
|
||
|
||
<!--应用详情-->
|
||
<MicroApps/>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { mapState, mapGetters } from 'vuex'
|
||
import ProjectArchived from "./manage/components/ProjectArchived";
|
||
import TeamManagement from "./manage/components/TeamManagement";
|
||
import MCPHelper from "./manage/components/MCPHelper";
|
||
import FavoriteManagement from "./manage/components/FavoriteManagement";
|
||
import RecentManagement from "./manage/components/RecentManagement";
|
||
import ProjectManagement from "./manage/components/ProjectManagement";
|
||
import DrawerOverlay from "../components/DrawerOverlay";
|
||
import MobileTabbar from "../components/Mobile/Tabbar";
|
||
import TaskAdd from "./manage/components/TaskAdd";
|
||
import Report from "./manage/components/Report";
|
||
import longpress from "../directives/longpress";
|
||
import TransferDom from "../directives/transfer-dom";
|
||
import DialogModal from "./manage/components/DialogModal";
|
||
import TaskModal from "./manage/components/TaskModal";
|
||
import CheckinExport from "./manage/components/CheckinExport";
|
||
import TaskExport from "./manage/components/TaskExport";
|
||
import ApproveExport from "./manage/components/ApproveExport";
|
||
import ComplaintManagement from "./manage/components/ComplaintManagement";
|
||
import MicroApps from "../components/MicroApps";
|
||
import UserSelect from "../components/UserSelect.vue";
|
||
import ImgUpload from "../components/ImgUpload.vue";
|
||
import Approve from "./manage/approve/index.vue";
|
||
import ApproveDetails from "./manage/approve/details.vue";
|
||
import notificationKoro from "notification-koro1";
|
||
import emitter from "../store/events";
|
||
import SearchBox from "../components/SearchBox.vue";
|
||
import transformEmojiToHtml from "../utils/emoji";
|
||
import {languageName} from "../language";
|
||
import {AINormalizeJsonContent, PROJECT_AI_SYSTEM_PROMPT, withLanguagePreferencePrompt} from "../utils/ai";
|
||
import Draggable from 'vuedraggable'
|
||
|
||
export default {
|
||
components: {
|
||
Approve,
|
||
SearchBox,
|
||
ApproveDetails,
|
||
ImgUpload,
|
||
UserSelect,
|
||
TaskExport,
|
||
CheckinExport,
|
||
ApproveExport,
|
||
TaskModal,
|
||
DialogModal,
|
||
MobileTabbar,
|
||
TaskAdd,
|
||
Report,
|
||
DrawerOverlay,
|
||
ProjectManagement,
|
||
TeamManagement,
|
||
MCPHelper,
|
||
FavoriteManagement,
|
||
RecentManagement,
|
||
ProjectArchived,
|
||
MicroApps,
|
||
ComplaintManagement,
|
||
Draggable
|
||
},
|
||
directives: {longpress, TransferDom},
|
||
data() {
|
||
return {
|
||
loadIng: 0,
|
||
|
||
mateName: /macintosh|mac os x/i.test(navigator.userAgent) ? '⌘' : 'Ctrl',
|
||
|
||
addShow: false,
|
||
addData: {
|
||
name: '',
|
||
columns: '',
|
||
flow: 'open',
|
||
},
|
||
addRule: {
|
||
name: [
|
||
{ required: true, message: this.$L('请填写项目名称!'), trigger: 'change' },
|
||
{ type: 'string', min: 2, message: this.$L('项目名称至少2个字!'), trigger: 'change' }
|
||
]
|
||
},
|
||
|
||
addTaskShow: false,
|
||
|
||
createGroupShow: false,
|
||
createGroupData: {},
|
||
createGroupLoad: 0,
|
||
|
||
exportTaskShow: false,
|
||
exportCheckinShow: false,
|
||
exportApproveShow: false,
|
||
|
||
projectKeyValue: '',
|
||
projectKeyLoading: 0,
|
||
projectSearchShow: false,
|
||
|
||
projectDraggableList: [],
|
||
projectDragging: false,
|
||
|
||
openMenu: {},
|
||
visibleMenu: false,
|
||
|
||
allUserShow: false,
|
||
allProjectShow: false,
|
||
archivedProjectShow: false,
|
||
|
||
favoriteShow: false,
|
||
recentShow: false,
|
||
|
||
natificationReady: false,
|
||
notificationManage: null,
|
||
|
||
workReportShow: false,
|
||
workReportTab: "my",
|
||
|
||
operateStyles: {},
|
||
operateVisible: false,
|
||
operateItem: {},
|
||
|
||
complaintShow: false,
|
||
|
||
approveShow: false,
|
||
approveDetails: {id: 0},
|
||
approveDetailsShow: false,
|
||
|
||
taskBrowseLoading: false,
|
||
taskBrowseHistory: [],
|
||
|
||
mcpHelperShow: false,
|
||
}
|
||
},
|
||
|
||
mounted() {
|
||
this.notificationInit();
|
||
//
|
||
emitter.on('addTask', this.onAddTask);
|
||
emitter.on('createGroup', this.onCreateGroup);
|
||
emitter.on('dialogMsgPush', this.addDialogMsg);
|
||
emitter.on('approveDetails', this.openApproveDetails);
|
||
emitter.on('openReport', this.openReport);
|
||
emitter.on('openFavorite', this.openFavorite);
|
||
emitter.on('openRecent', this.openRecent);
|
||
emitter.on('openManageExport', this.openManageExport);
|
||
//
|
||
document.addEventListener('keydown', this.shortcutEvent);
|
||
},
|
||
|
||
activated() {
|
||
this.$store.dispatch("getUserInfo").catch(_ => {})
|
||
this.$store.dispatch("getTaskPriority", 1000)
|
||
this.$store.dispatch("getReportUnread", 1000)
|
||
this.$store.dispatch("getApproveUnread", 1000)
|
||
},
|
||
|
||
beforeDestroy() {
|
||
emitter.off('addTask', this.onAddTask);
|
||
emitter.off('createGroup', this.onCreateGroup);
|
||
emitter.off('dialogMsgPush', this.addDialogMsg);
|
||
emitter.off('approveDetails', this.openApproveDetails);
|
||
emitter.off('openReport', this.openReport);
|
||
emitter.off('openFavorite', this.openFavorite);
|
||
emitter.off('openRecent', this.openRecent);
|
||
emitter.off('openManageExport', this.openManageExport);
|
||
//
|
||
document.removeEventListener('keydown', this.shortcutEvent);
|
||
},
|
||
|
||
deactivated() {
|
||
this.addShow = false;
|
||
},
|
||
|
||
computed: {
|
||
...mapState([
|
||
'userInfo',
|
||
'userIsAdmin',
|
||
'cacheUserBasic',
|
||
'cacheTasks',
|
||
'cacheDialogs',
|
||
'cacheProjects',
|
||
'projectTotal',
|
||
'themeName',
|
||
'wsOpenNum',
|
||
'columnTemplate',
|
||
|
||
'clientNewVersion',
|
||
|
||
'reportUnreadNumber',
|
||
'approveUnreadNumber',
|
||
|
||
'dialogIns',
|
||
'formOptions',
|
||
'mobileTabbar',
|
||
'longpressData',
|
||
|
||
'mcpServerStatus'
|
||
]),
|
||
|
||
...mapGetters(['dashboardTask', "filterMicroAppsMenusMain"]),
|
||
|
||
/**
|
||
* page className
|
||
* @param mobileTabbar
|
||
* @param userId
|
||
* @returns {{"show-tabbar", "not-logged": boolean}}
|
||
*/
|
||
pageClass({mobileTabbar, userId}) {
|
||
return {
|
||
'show-tabbar': mobileTabbar,
|
||
'not-logged': userId <= 0
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 综合数(未读、提及、待办)
|
||
* @returns {string|string}
|
||
*/
|
||
msgUnreadMention() {
|
||
let num = 0; // 未读
|
||
let mention = 0; // 提及
|
||
this.cacheDialogs.some(dialog => {
|
||
num += $A.getDialogUnread(dialog, false);
|
||
mention += $A.getDialogMention(dialog);
|
||
})
|
||
if (num > 999) {
|
||
num = "999+"
|
||
}
|
||
if (mention > 999) {
|
||
mention = "999+"
|
||
}
|
||
const todoNum = this.msgTodoTotal // 待办
|
||
if (todoNum) {
|
||
if (mention) {
|
||
return `@${mention}·${todoNum}`
|
||
}
|
||
if (num) {
|
||
return `${num}·${todoNum}`
|
||
}
|
||
return todoNum;
|
||
}
|
||
if (num) {
|
||
if (mention) {
|
||
return `${num}·@${mention}`
|
||
}
|
||
return String(num)
|
||
}
|
||
if (mention) {
|
||
return `@${mention}`
|
||
}
|
||
return "";
|
||
},
|
||
|
||
/**
|
||
* 未读消息数
|
||
* @returns {number}
|
||
*/
|
||
msgAllUnread() {
|
||
let num = 0;
|
||
this.cacheDialogs.some(dialog => {
|
||
num += $A.getDialogNum(dialog);
|
||
})
|
||
return num;
|
||
},
|
||
|
||
/**
|
||
* 待办消息数
|
||
* @returns {string|null}
|
||
*/
|
||
msgTodoTotal() {
|
||
let todoNum = this.cacheDialogs.reduce((total, current) => total + (current.todo_num || 0), 0)
|
||
if (todoNum > 0) {
|
||
if (todoNum > 99) {
|
||
todoNum = "99+"
|
||
} else if (todoNum === 1) {
|
||
todoNum = ""
|
||
}
|
||
return `${this.$L("待办")}${todoNum}`
|
||
}
|
||
return null;
|
||
},
|
||
|
||
/**
|
||
* 未读消息 + 逾期任务
|
||
* @returns {number|*}
|
||
*/
|
||
unreadAndOverdue() {
|
||
if (this.userId > 0) {
|
||
return this.msgAllUnread + this.dashboardTask.overdue_count
|
||
} else {
|
||
return 0
|
||
}
|
||
},
|
||
|
||
menu() {
|
||
const {userIsAdmin} = this;
|
||
const array = [
|
||
{path: 'taskBrowse', name: '最近打开的任务'},
|
||
{path: 'favorite', name: '我的收藏'},
|
||
{path: 'download', name: '下载内容', visible: !!this.$Electron},
|
||
{path: 'mcpServer', name: '启用桌面 MCP 服务器', visible: !!this.$Electron, selected: this.mcpServerStatus.running === 'running'},
|
||
];
|
||
if (userIsAdmin) {
|
||
array.push(...[
|
||
{path: 'personal', name: '个人设置', divided: true},
|
||
{path: 'system', name: '系统设置'},
|
||
{path: 'license', name: 'License Key'},
|
||
|
||
{path: 'version', name: '更新版本', divided: true, visible: !!this.clientNewVersion},
|
||
|
||
{path: 'allProject', name: '所有项目', divided: true},
|
||
{path: 'archivedProject', name: '已归档的项目'},
|
||
|
||
{path: 'team', name: '团队管理', divided: true},
|
||
])
|
||
} else {
|
||
array.push(...[
|
||
{path: 'personal', name: '个人设置', divided: true},
|
||
{path: 'version', name: '更新版本', divided: true, visible: !!this.clientNewVersion},
|
||
|
||
{path: 'workReport', name: '工作报告', divided: true},
|
||
{path: 'archivedProject', name: '已归档的项目'},
|
||
])
|
||
}
|
||
array.push(...[
|
||
{path: 'clearCache', name: '清除缓存', divided: true},
|
||
{path: 'logout', name: '退出登录', style: {color: '#f40'}}
|
||
])
|
||
return array
|
||
},
|
||
|
||
columns() {
|
||
const array = $A.cloneJSON(this.columnTemplate);
|
||
array.unshift({
|
||
name: this.$L('空白模板'),
|
||
columns: [],
|
||
})
|
||
return array
|
||
},
|
||
|
||
projectLists() {
|
||
const {projectKeyValue, cacheProjects} = this;
|
||
const data = $A.cloneJSON(cacheProjects).sort((a, b) => {
|
||
// 置顶优先
|
||
if (a.top_at !== b.top_at && (a.top_at || b.top_at)) {
|
||
return $A.sortDay(b.top_at, a.top_at);
|
||
}
|
||
// 自定义排序
|
||
const as = typeof a.sort === 'number' ? a.sort : Number.MAX_SAFE_INTEGER;
|
||
const bs = typeof b.sort === 'number' ? b.sort : Number.MAX_SAFE_INTEGER;
|
||
if (as !== bs) return as - bs;
|
||
// 兜底:按ID倒序
|
||
return b.id - a.id;
|
||
});
|
||
if (projectKeyValue) {
|
||
return data.filter(item => $A.strExists(`${item.name} ${item.desc}`, projectKeyValue));
|
||
}
|
||
return data;
|
||
},
|
||
|
||
taskBrowseLists() {
|
||
// 直接使用组件内的响应式数据
|
||
return this.taskBrowseHistory.slice(0, 10); // 只显示前10个
|
||
},
|
||
},
|
||
|
||
watch: {
|
||
'$route' () {
|
||
this.chackPass();
|
||
},
|
||
|
||
userInfo() {
|
||
this.chackPass();
|
||
},
|
||
|
||
projectKeyValue(val) {
|
||
if (val == '') {
|
||
return;
|
||
}
|
||
setTimeout(() => {
|
||
if (this.projectKeyValue == val) {
|
||
this.searchProject();
|
||
}
|
||
}, 600);
|
||
},
|
||
|
||
wsOpenNum(num) {
|
||
if (num <= 1) return
|
||
this.$store.dispatch("getBasicData", 600)
|
||
},
|
||
|
||
workReportShow(show) {
|
||
if (!show) return
|
||
this.$store.dispatch("getReportUnread", 0)
|
||
},
|
||
|
||
windowActive(active) {
|
||
if (!active) return
|
||
this.$store.dispatch("getProjectByQueue", 600);
|
||
},
|
||
|
||
themeName: {
|
||
handler(theme) {
|
||
if (this.$Electron) {
|
||
$A.Electron.request({
|
||
action: 'updateDownloadWindow',
|
||
language: languageName,
|
||
theme,
|
||
});
|
||
}
|
||
},
|
||
immediate: true
|
||
},
|
||
|
||
'cacheProjects.length': {
|
||
handler() {
|
||
this.$nextTick(_ => {
|
||
const menuProject = this.$refs.menuProject
|
||
const lastEl = $A.last($A.getObject(menuProject, 'children.0.children'))
|
||
if (lastEl) {
|
||
const lastRect = lastEl.getBoundingClientRect()
|
||
const menuRect = menuProject.getBoundingClientRect()
|
||
if (lastRect.top > menuRect.top + menuRect.height) {
|
||
this.projectSearchShow = true
|
||
return
|
||
}
|
||
}
|
||
this.projectSearchShow = false
|
||
})
|
||
},
|
||
immediate: true
|
||
},
|
||
|
||
projectLists: {
|
||
handler(val) {
|
||
if (!this.projectDragging) {
|
||
this.projectDraggableList = $A.cloneJSON(val)
|
||
}
|
||
},
|
||
immediate: true
|
||
},
|
||
|
||
unreadAndOverdue: {
|
||
handler(val) {
|
||
if (this.$Electron) {
|
||
this.$Electron.sendMessage('setDockBadge', val);
|
||
}
|
||
},
|
||
immediate: true
|
||
},
|
||
|
||
mcpServerStatus: {
|
||
handler(data) {
|
||
if (!this.$Electron) {
|
||
return;
|
||
}
|
||
this.$Electron.sendMessage('mcpServerToggle', data);
|
||
},
|
||
immediate: true
|
||
}
|
||
},
|
||
|
||
methods: {
|
||
transformEmojiToHtml,
|
||
chackPass() {
|
||
if (this.userInfo.changepass === 1) {
|
||
this.goForward({name: 'manage-setting-password'});
|
||
}
|
||
},
|
||
|
||
async toggleRoute(path, params) {
|
||
const location = {name: 'manage-' + path, params: params || {}};
|
||
const fileFolderId = await $A.IDBInt("fileFolderId");
|
||
if (path === 'file' && fileFolderId > 0) {
|
||
location.params.folderId = fileFolderId
|
||
}
|
||
this.goForward(location);
|
||
},
|
||
|
||
toggleOpenMenu(id) {
|
||
this.$set(this.openMenu, id, !this.openMenu[id])
|
||
},
|
||
|
||
settingRoute(path) {
|
||
switch (path) {
|
||
case 'allUser':
|
||
this.allUserShow = true;
|
||
return;
|
||
case 'allProject':
|
||
this.allProjectShow = true;
|
||
return;
|
||
case 'archivedProject':
|
||
this.archivedProjectShow = true;
|
||
return;
|
||
case 'exportTask':
|
||
this.exportTaskShow = true;
|
||
return;
|
||
case 'exportOverdueTask':
|
||
this.exportOverdueTask();
|
||
return;
|
||
case 'exportCheckin':
|
||
this.exportCheckinShow = true;
|
||
return;
|
||
case 'exportApprove':
|
||
this.exportApproveShow = true;
|
||
return;
|
||
case 'workReport':
|
||
this.openReport(this.reportUnreadNumber > 0 ? 'receive' : 'my');
|
||
return;
|
||
case 'favorite':
|
||
this.openFavorite();
|
||
return;
|
||
case 'version':
|
||
emitter.emit('updateNotification', null);
|
||
return;
|
||
case 'clearCache':
|
||
$A.IDBSet("clearCache", "handle").then(_ => {
|
||
$A.reloadUrl()
|
||
});
|
||
return;
|
||
case 'approve':
|
||
if (this.menu.findIndex((m) => m.path == path) > -1) {
|
||
this.goForward({name: 'manage-approve'});
|
||
}
|
||
return;
|
||
case 'complaint':
|
||
this.complaintShow = true;
|
||
return;
|
||
case 'download':
|
||
$A.Electron.request({
|
||
action: 'openDownloadWindow',
|
||
language: languageName,
|
||
theme: this.themeName,
|
||
});
|
||
return;
|
||
case 'mcpServer':
|
||
this.mcpHelperShow = true;
|
||
if (this.mcpServerStatus.running !== 'running') {
|
||
this.$store.dispatch('toggleMcpServer');
|
||
}
|
||
return;
|
||
case 'logout':
|
||
$A.modalConfirm({
|
||
title: '退出登录',
|
||
content: '你确定要登出系统吗?',
|
||
loading: true,
|
||
onOk: () => {
|
||
return new Promise(async resolve => {
|
||
await this.$store.dispatch("logout", false)
|
||
resolve()
|
||
})
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
if (this.menu.findIndex((m) => m.path == path) > -1) {
|
||
this.toggleRoute('setting-' + path);
|
||
}
|
||
},
|
||
|
||
exportOverdueTask() {
|
||
$A.modalConfirm({
|
||
title: '导出任务',
|
||
content: '你确定要导出所有超期任务吗?',
|
||
loading: true,
|
||
onOk: () => {
|
||
return new Promise((resolve, reject) => {
|
||
this.$store.dispatch("call", {
|
||
url: 'project/task/exportoverdue',
|
||
}).then(() => {
|
||
resolve();
|
||
$A.modalSuccess('正在打包,请留意系统消息。');
|
||
}).catch(({msg}) => {
|
||
reject(msg);
|
||
});
|
||
})
|
||
},
|
||
});
|
||
},
|
||
|
||
menuVisibleChange(visible) {
|
||
this.visibleMenu = visible
|
||
// 当菜单展开时,获取最新的浏览历史
|
||
if (visible && !this.taskBrowseLoading) {
|
||
this.loadTaskBrowseHistory()
|
||
}
|
||
},
|
||
|
||
classNameRoute(path) {
|
||
let name = this.routeName
|
||
if (name == 'manage-approve') {
|
||
name = `manage-application`
|
||
}
|
||
return {
|
||
"active": name === `manage-${path}`,
|
||
};
|
||
},
|
||
|
||
classNameProject(item) {
|
||
return {
|
||
"active": this.routeName === 'manage-project' && this.$route.params.projectId == item.id,
|
||
"open-menu": this.openMenu[item.id] === true,
|
||
"operate": item.id == this.operateItem.id && this.operateVisible
|
||
};
|
||
},
|
||
|
||
onAddMenu(name) {
|
||
switch (name) {
|
||
case 'project':
|
||
this.onAddShow()
|
||
break;
|
||
|
||
case 'task':
|
||
this.onAddTask(0)
|
||
break;
|
||
|
||
case 'group':
|
||
this.onCreateGroup([this.userId])
|
||
break;
|
||
|
||
case 'createMeeting':
|
||
emitter.emit('addMeeting', {
|
||
type: 'create',
|
||
userids: [this.userId],
|
||
});
|
||
break;
|
||
|
||
case 'joinMeeting':
|
||
emitter.emit('addMeeting', {
|
||
type: 'join',
|
||
});
|
||
break;
|
||
}
|
||
},
|
||
|
||
onAddShow() {
|
||
this.$store.dispatch("getColumnTemplate").catch(() => {})
|
||
this.addShow = true;
|
||
this.$nextTick(() => {
|
||
this.$refs.projectName.focus();
|
||
})
|
||
},
|
||
|
||
onProjectAI() {
|
||
emitter.emit('openAIAssistant', {
|
||
placeholder: this.$L('请简要描述项目目标、范围或关键里程碑,AI 将生成名称和任务列表'),
|
||
onBeforeSend: this.handleProjectAIBeforeSend,
|
||
onRender: this.handleProjectAIRender,
|
||
onApply: this.handleProjectAIApply,
|
||
});
|
||
},
|
||
|
||
buildProjectAIContextData() {
|
||
const prompts = [];
|
||
const currentName = (this.addData.name || '').trim();
|
||
const currentColumns = this.normalizeAIColumns(this.addData.columns);
|
||
|
||
if (currentName || currentColumns.length > 0) {
|
||
prompts.push('## 当前项目草稿');
|
||
if (currentName) {
|
||
prompts.push(`已有名称:${currentName}`);
|
||
}
|
||
if (currentColumns.length > 0) {
|
||
prompts.push(`现有任务列表:${currentColumns.join('、')}`);
|
||
}
|
||
prompts.push('请在此基础上进行优化和补充。');
|
||
}
|
||
|
||
const rawTemplates = Array.isArray(this.columns) ? this.columns : [];
|
||
const templateExamples = rawTemplates
|
||
.filter((item, index) => index > 0 && item)
|
||
.map(item => {
|
||
const columns = this.normalizeAIColumns(item.columns);
|
||
if (columns.length === 0) {
|
||
return null;
|
||
}
|
||
return {
|
||
name: (item.name || '').trim(),
|
||
columns,
|
||
};
|
||
})
|
||
.filter(Boolean)
|
||
.slice(0, 6);
|
||
|
||
if (templateExamples.length > 0) {
|
||
prompts.push('## 常用模板示例');
|
||
templateExamples.forEach(example => {
|
||
const namePrefix = example.name ? `${example.name}:` : '';
|
||
prompts.push(`- ${namePrefix}${example.columns.join('、')}`);
|
||
});
|
||
prompts.push('可以借鉴以上结构,但要结合用户需求生成更贴合的方案。');
|
||
}
|
||
|
||
return prompts.join('\n').trim();
|
||
},
|
||
|
||
handleProjectAIBeforeSend(context = []) {
|
||
const prepared = [
|
||
['system', withLanguagePreferencePrompt(PROJECT_AI_SYSTEM_PROMPT)]
|
||
];
|
||
const contextPrompt = this.buildProjectAIContextData();
|
||
if (contextPrompt) {
|
||
let assistantContext = [
|
||
'以下是可用的上下文,请据此生成项目:',
|
||
contextPrompt,
|
||
].join('\n');
|
||
if ($A.getObject(context, [0,0]) === 'human') {
|
||
assistantContext += "\n----\n请根据以上信息,结合以下用户输入的内容生成项目名称和任务列表:++++";
|
||
}
|
||
prepared.push(['human', assistantContext]);
|
||
}
|
||
if (context.length > 0) {
|
||
prepared.push(...context);
|
||
}
|
||
return prepared;
|
||
},
|
||
|
||
handleProjectAIApply({rawOutput}) {
|
||
if (!rawOutput) {
|
||
$A.messageWarning('AI 未生成内容');
|
||
return;
|
||
}
|
||
const parsed = this.parseProjectAIContent(rawOutput);
|
||
if (!parsed) {
|
||
$A.modalError('AI 内容解析失败,请重试');
|
||
return;
|
||
}
|
||
if (parsed.name) {
|
||
this.$set(this.addData, 'name', parsed.name);
|
||
}
|
||
if (parsed.columns.length > 0) {
|
||
this.$set(this.addData, 'columns', parsed.columns.join(','));
|
||
}
|
||
this.$nextTick(() => {
|
||
if (this.$refs.projectName) {
|
||
this.$refs.projectName.focus();
|
||
}
|
||
});
|
||
},
|
||
|
||
normalizeAIColumns(value) {
|
||
if (!value) {
|
||
return [];
|
||
}
|
||
const normalize = (item) => {
|
||
if (!item) {
|
||
return '';
|
||
}
|
||
if (typeof item === 'string') {
|
||
return item.trim();
|
||
}
|
||
if (typeof item === 'object') {
|
||
const text = item.name || item.title || item.label || item.value || '';
|
||
return typeof text === 'string' ? text.trim() : '';
|
||
}
|
||
return String(item).trim();
|
||
};
|
||
if (Array.isArray(value)) {
|
||
return value.map(normalize).filter(Boolean);
|
||
}
|
||
if (typeof value === 'string') {
|
||
return value.split(/[\n\r,,;;|]/).map(item => item.trim()).filter(Boolean);
|
||
}
|
||
if (typeof value === 'object') {
|
||
if (Array.isArray(value.columns)) {
|
||
return this.normalizeAIColumns(value.columns);
|
||
}
|
||
if (typeof value.columns === 'string') {
|
||
return this.normalizeAIColumns(value.columns);
|
||
}
|
||
}
|
||
return [];
|
||
},
|
||
|
||
parseProjectAIContent(content) {
|
||
const payload = AINormalizeJsonContent(content);
|
||
if (!payload || typeof payload !== 'object') {
|
||
return null;
|
||
}
|
||
const nameSource = [payload.name, payload.title, payload.project_name].find(item => typeof item === 'string' && item.trim());
|
||
const columnsSource = payload.columns || payload.lists || payload.stages || payload.columns_list;
|
||
const columns = this.normalizeAIColumns(columnsSource);
|
||
if (!nameSource && columns.length === 0) {
|
||
return null;
|
||
}
|
||
return {
|
||
name: nameSource ? nameSource.trim() : '',
|
||
columns,
|
||
};
|
||
},
|
||
|
||
handleProjectAIRender({rawOutput}) {
|
||
if (!rawOutput) {
|
||
return '';
|
||
}
|
||
const parsed = this.parseProjectAIContent(rawOutput);
|
||
if (!parsed) {
|
||
return rawOutput;
|
||
}
|
||
const blocks = [];
|
||
if (parsed.name) {
|
||
blocks.push(`## ${parsed.name}`);
|
||
}
|
||
if (parsed.columns.length > 0) {
|
||
const lines = parsed.columns.map((column, index) => `${index + 1}. ${column}`);
|
||
blocks.push(lines.join('\n'));
|
||
}
|
||
return blocks.join('\n\n').trim() || rawOutput;
|
||
},
|
||
|
||
onAddProject() {
|
||
this.$refs.addProject.validate((valid) => {
|
||
if (valid) {
|
||
this.loadIng++;
|
||
this.$store.dispatch("call", {
|
||
url: 'project/add',
|
||
data: this.addData,
|
||
}).then(({data, msg}) => {
|
||
$A.messageSuccess(msg);
|
||
this.addShow = false;
|
||
this.$refs.addProject.resetFields();
|
||
this.$store.dispatch("saveProject", data);
|
||
this.toggleRoute('project', {projectId: data.id})
|
||
}).catch(({msg}) => {
|
||
$A.modalError(msg);
|
||
}).finally(_ => {
|
||
this.loadIng--;
|
||
});
|
||
}
|
||
});
|
||
},
|
||
|
||
searchProject() {
|
||
setTimeout(() => {
|
||
this.projectKeyLoading++;
|
||
}, 1000)
|
||
this.$store.dispatch("getProjects", {
|
||
keys: {
|
||
name: this.projectKeyValue
|
||
}
|
||
}).finally(_ => {
|
||
this.projectKeyLoading--;
|
||
});
|
||
},
|
||
|
||
selectChange(index) {
|
||
this.$nextTick(() => {
|
||
this.$set(this.addData, 'columns', this.columns[index].columns.join(','));
|
||
})
|
||
},
|
||
|
||
shortcutEvent(e) {
|
||
if (e.metaKey || e.ctrlKey) {
|
||
switch (e.keyCode) {
|
||
case 66: // B - 新建项目
|
||
e.preventDefault();
|
||
this.onAddShow()
|
||
break;
|
||
|
||
case 70:
|
||
case 191: // F、/ - 搜索
|
||
e.preventDefault();
|
||
this.$refs.searchBox.onShow();
|
||
break;
|
||
|
||
case 75:
|
||
case 78: // K、N - 新建任务
|
||
e.preventDefault();
|
||
this.onAddMenu('task')
|
||
break;
|
||
|
||
case 76: // L - 下载内容(+ alt)
|
||
if (e.altKey) {
|
||
e.preventDefault();
|
||
this.settingRoute('download')
|
||
}
|
||
break;
|
||
|
||
case 85: // U - 创建群组
|
||
this.onCreateGroup([this.userId])
|
||
break;
|
||
|
||
case 74: // J - 新会议
|
||
e.preventDefault();
|
||
this.onAddMenu('createMeeting')
|
||
break;
|
||
|
||
case 83: // S - 保存任务
|
||
if (this.$refs.taskModal.checkUpdate()) {
|
||
e.preventDefault();
|
||
}
|
||
break;
|
||
|
||
case 188: // , - 进入设置
|
||
e.preventDefault();
|
||
this.toggleRoute('setting')
|
||
break;
|
||
}
|
||
}
|
||
},
|
||
|
||
onProjectSortEnd() {
|
||
const nonPinnedItems = this.projectDraggableList.filter(item => !item.top_at)
|
||
this.$store.dispatch("call", {
|
||
url: 'project/user/sort',
|
||
data: {
|
||
list: nonPinnedItems.map(item => item.id)
|
||
},
|
||
method: 'post',
|
||
spinner: 2000
|
||
}).then(({msg}) => {
|
||
nonPinnedItems.forEach((item, index) => {
|
||
this.$store.dispatch("saveProject", {id: item.id, sort: index})
|
||
})
|
||
$A.messageSuccess(msg)
|
||
}).catch(({msg}) => {
|
||
this.projectDraggableList = $A.cloneJSON(this.projectLists)
|
||
$A.modalError(msg)
|
||
}).finally(() => {
|
||
this.projectDragging = false
|
||
})
|
||
},
|
||
|
||
onAddTask(params) {
|
||
this.addTaskShow = true
|
||
this.$nextTick(_ => {
|
||
let data = {
|
||
owner: [this.userId],
|
||
}
|
||
if ($A.isJson(params)) {
|
||
data = params
|
||
} else if (/^[1-9]\d*$/.test(params)) {
|
||
data.column_id = params
|
||
}
|
||
this.$refs.addTask.setData(data)
|
||
})
|
||
},
|
||
|
||
openTask(task) {
|
||
this.$store.dispatch("openTask", task)
|
||
},
|
||
|
||
onCreateGroup(userids) {
|
||
if (!$A.isArray(userids)) {
|
||
userids = []
|
||
}
|
||
this.createGroupData = {userids, uncancelable: [this.userId]}
|
||
this.createGroupShow = true
|
||
},
|
||
|
||
submitCreateGroup() {
|
||
this.createGroupLoad++;
|
||
this.$store.dispatch("call", {
|
||
url: 'dialog/group/add',
|
||
data: this.createGroupData
|
||
}).then(({data, msg}) => {
|
||
$A.messageSuccess(msg);
|
||
this.createGroupShow = false;
|
||
this.createGroupData = {};
|
||
this.$store.dispatch("saveDialog", data);
|
||
this.$store.dispatch('openDialog', data.id)
|
||
}).catch(({msg}) => {
|
||
$A.modalError(msg);
|
||
}).finally(_ => {
|
||
this.createGroupLoad--;
|
||
});
|
||
},
|
||
|
||
addDialogMsg({silence, data}) {
|
||
if (silence) {
|
||
return; // 静默消息不通知
|
||
}
|
||
if (!this.natificationReady && !this.$isEEUIApp) {
|
||
return; // 通知未准备好不通知
|
||
}
|
||
if (this.windowActive && data.dialog_id === $A.last(this.dialogIns)?.dialog_id) {
|
||
return; // 窗口激活且最后打开的会话是通知的会话时不通知
|
||
}
|
||
//
|
||
const {id, dialog_id, dialog_type, userid} = data;
|
||
if (userid == this.userId) {
|
||
return; // 自己的消息不通知
|
||
}
|
||
this.__notificationId = id;
|
||
//
|
||
const notificationFuncA = async (title, body) => {
|
||
const tempUser = await this.$store.dispatch("getUserData", userid).catch(_ => {});
|
||
if (dialog_type === 'group' && tempUser) {
|
||
body = tempUser.nickname + ': ' + body;
|
||
}
|
||
notificationFuncB(title, body, tempUser?.userimg)
|
||
}
|
||
const notificationFuncB = (title, body, userimg) => {
|
||
if (this.__notificationId === id) {
|
||
this.__notificationId = null
|
||
if (this.$isEEUIApp) {
|
||
emitter.emit('openMobileNotification', {
|
||
userid: userid,
|
||
title,
|
||
desc: body,
|
||
callback: () => {
|
||
this.$store.dispatch('openDialog', dialog_id)
|
||
}
|
||
});
|
||
} else if (this.$Electron) {
|
||
this.$Electron.sendMessage('openNotification', {
|
||
icon: userimg || $A.originUrl('images/logo.png'),
|
||
title,
|
||
body,
|
||
data,
|
||
tag: "dialog",
|
||
hasReply: true,
|
||
replyPlaceholder: this.$L('回复消息')
|
||
})
|
||
} else {
|
||
this.notificationManage.replaceOptions({
|
||
icon: userimg || $A.originUrl('images/logo.png'),
|
||
body: body,
|
||
data: data,
|
||
tag: "dialog",
|
||
// requireInteraction: true // true为通知不自动关闭
|
||
});
|
||
this.notificationManage.replaceTitle(title);
|
||
this.notificationManage.userAgreed();
|
||
}
|
||
}
|
||
}
|
||
const dialog = this.cacheDialogs.find((item) => item.id == dialog_id);
|
||
const summary = $A.getMsgSimpleDesc(data);
|
||
if (dialog) {
|
||
notificationFuncA(dialog.name, summary)
|
||
} else {
|
||
this.$store.dispatch("getDialogOne", dialog_id).then(({data}) => notificationFuncA(data.name, summary)).catch(() => {})
|
||
}
|
||
},
|
||
|
||
openApproveDetails(id) {
|
||
this.approveDetailsShow = true;
|
||
this.$nextTick(() => {
|
||
this.approveDetails = {id};
|
||
})
|
||
},
|
||
|
||
openReport(tab) {
|
||
this.workReportTab = tab;
|
||
this.workReportShow = true;
|
||
},
|
||
|
||
openFavorite() {
|
||
this.favoriteShow = true;
|
||
},
|
||
|
||
openRecent() {
|
||
this.recentShow = true;
|
||
},
|
||
|
||
openManageExport(type) {
|
||
switch (type) {
|
||
case 'task':
|
||
this.exportTaskShow = true;
|
||
break;
|
||
case 'overdue':
|
||
this.exportOverdueTask();
|
||
break;
|
||
case 'approve':
|
||
this.exportApproveShow = true;
|
||
break;
|
||
case 'checkin':
|
||
this.exportCheckinShow = true;
|
||
break;
|
||
}
|
||
},
|
||
|
||
handleLongpress(event) {
|
||
const {type, data, element} = this.longpressData;
|
||
this.$store.commit("longpress/clear")
|
||
//
|
||
if (type !== 'manage') {
|
||
return
|
||
}
|
||
const projectItem = this.projectLists.find(item => item.id == data.projectId)
|
||
if (!projectItem) {
|
||
return
|
||
}
|
||
this.operateVisible = false;
|
||
this.operateItem = $A.isJson(projectItem) ? projectItem : {};
|
||
requestAnimationFrame(() => {
|
||
const rect = element.getBoundingClientRect();
|
||
this.operateStyles = {
|
||
left: `${event.clientX}px`,
|
||
top: `${rect.top}px`,
|
||
height: `${rect.height}px`,
|
||
}
|
||
this.operateVisible = true;
|
||
})
|
||
},
|
||
|
||
handleOperation({currentTarget}) {
|
||
this.$store.commit("longpress/set", {
|
||
type: 'manage',
|
||
data: {
|
||
projectId: $A.getAttr(currentTarget, 'data-id')
|
||
},
|
||
element: currentTarget
|
||
})
|
||
},
|
||
|
||
handleTopClick() {
|
||
this.$store.dispatch("call", {
|
||
url: 'project/top',
|
||
data: {
|
||
project_id: this.operateItem.id,
|
||
},
|
||
}).then(({data}) => {
|
||
this.$store.dispatch("saveProject", data);
|
||
this.$nextTick(() => {
|
||
const active = this.$refs.menuProject.querySelector(".active")
|
||
if (active) {
|
||
$A.scrollIntoViewIfNeeded(active);
|
||
}
|
||
});
|
||
}).catch(({msg}) => {
|
||
$A.modalError(msg);
|
||
});
|
||
},
|
||
|
||
handleChatClick() {
|
||
this.$store.dispatch("openDialog", this.operateItem.dialog_id).catch(({msg}) => {
|
||
$A.modalError(msg || this.$L('打开会话失败'))
|
||
})
|
||
},
|
||
|
||
onTabbarClick(act, params = '') {
|
||
switch (act) {
|
||
case 'approve':
|
||
this.approveShow = true
|
||
break;
|
||
case 'createGroup':
|
||
this.onAddMenu('group')
|
||
break;
|
||
case 'addTask':
|
||
this.onAddTask(0)
|
||
break;
|
||
case 'addProject':
|
||
this.onAddShow()
|
||
break;
|
||
case 'allUser':
|
||
case 'complaint':
|
||
case 'workReport':
|
||
this.settingRoute(act)
|
||
break;
|
||
case 'microApp':
|
||
this.$store.dispatch("openMicroApp", params);
|
||
break;
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 初始化通知
|
||
*/
|
||
notificationInit() {
|
||
this.notificationManage = new notificationKoro(this.$L("打开通知成功"));
|
||
if (this.notificationManage.support) {
|
||
this.notificationManage.notificationEvent({
|
||
onclick: ({target}) => {
|
||
console.log("[Notification] A Click", target);
|
||
this.notificationManage.close();
|
||
this.notificationClick(target)
|
||
window.focus()
|
||
},
|
||
});
|
||
this.notificationPermission();
|
||
}
|
||
//
|
||
if (this.$Electron) {
|
||
this.$Electron.listener('clickNotification', target => {
|
||
console.log("[Notification] B Click", target);
|
||
this.$Electron.sendMessage('mainWindowActive')
|
||
this.notificationClick(target)
|
||
})
|
||
this.$Electron.listener('replyNotification', target => {
|
||
console.log("[Notification] B Reply", target);
|
||
this.notificationReply(target)
|
||
})
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 通知权限
|
||
*/
|
||
notificationPermission() {
|
||
const userSelectFn = msg => {
|
||
switch (msg) {
|
||
// 随时可以调用通知
|
||
case 'already granted':
|
||
case 'granted':
|
||
return this.natificationReady = true;
|
||
|
||
// 请求权限通知被关闭,再次调用
|
||
case 'close':
|
||
return this.notificationManage.initNotification(userSelectFn);
|
||
|
||
// 请求权限当前被拒绝 || 曾经被拒绝
|
||
case 'denied':
|
||
case 'already denied':
|
||
if (msg === "denied") {
|
||
console.log("您刚刚拒绝显示通知 请在设置中更改设置");
|
||
} else {
|
||
console.log("您曾级拒绝显示通知 请在设置中更改设置");
|
||
}
|
||
break;
|
||
}
|
||
};
|
||
this.notificationManage.initNotification(userSelectFn);
|
||
},
|
||
|
||
/**
|
||
* 点击通知(客户端)
|
||
* @param target
|
||
*/
|
||
notificationClick(target) {
|
||
const {tag, data} = target;
|
||
if (tag == 'dialog') {
|
||
if (!$A.isJson(data)) {
|
||
return;
|
||
}
|
||
this.$nextTick(_ => {
|
||
this.$store.dispatch('openDialog', data.dialog_id)
|
||
})
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 回复通知(客户端)
|
||
* @param target
|
||
*/
|
||
notificationReply(target) {
|
||
const {tag, data, reply} = target;
|
||
if (tag == 'dialog' && reply) {
|
||
this.$store.dispatch("call", {
|
||
url: 'dialog/msg/sendtext',
|
||
data: {
|
||
dialog_id: data.dialog_id,
|
||
text: reply,
|
||
},
|
||
method: 'post',
|
||
}).then(({data}) => {
|
||
this.$store.dispatch("saveDialogMsg", data);
|
||
this.$store.dispatch("increaseTaskMsgNum", {id: data.dialog_id});
|
||
this.$store.dispatch("increaseMsgReplyNum", {id: data.reply_id});
|
||
this.$store.dispatch("updateDialogLastMsg", data);
|
||
}).catch(({msg}) => {
|
||
$A.modalError(msg)
|
||
});
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 加载任务浏览历史
|
||
*/
|
||
loadTaskBrowseHistory() {
|
||
if (this.taskBrowseLoading) return
|
||
|
||
this.taskBrowseLoading = true
|
||
this.$store.dispatch("getTaskBrowseHistory", 20).then(({data}) => {
|
||
// 更新组件内的浏览历史数据
|
||
this.taskBrowseHistory = data || []
|
||
}).catch(error => {
|
||
console.warn('获取任务浏览历史失败:', error)
|
||
// 失败时保持当前数据不变
|
||
}).finally(() => {
|
||
this.taskBrowseLoading = false
|
||
})
|
||
},
|
||
}
|
||
}
|
||
</script>
|