王昱 4933930afd feat: 调整机器人webhook事件
- 可取消接收消息事件
- 打开机器人会话窗口时推送webhook消息,相同机器人消息缓存1分钟
2025-11-06 04:08:39 +00:00

5183 lines
177 KiB
JavaScript
Vendored
Raw Blame History

This file contains ambiguous Unicode characters

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

import * as openpgp from 'openpgp_hi/lightweight';
import {initLanguage, languageList, languageName} from "../language";
import {$callData, $urlSafe, SSEClient} from '../utils'
import {isLocalHost} from "../components/Replace/utils";
import emitter from "./events";
import axios from "axios";
const dialogDraftState = { timer: {}, subTemp: null }
export default {
/**
* 预加载
* @param state
*/
preload({state}) {
window.addEventListener('resize', () => {
const windowWidth = $A(window).width(),
windowHeight = $A(window).height(),
windowOrientation = $A.screenOrientation()
state.windowTouch = "ontouchend" in document
state.windowWidth = windowWidth
state.windowHeight = windowHeight
state.windowOrientation = windowOrientation
state.windowLandscape = windowOrientation === 'landscape'
state.windowPortrait = windowOrientation === 'portrait'
state.windowIsFullScreen = $A.isFullScreen()
state.formOptions = {
class: windowWidth > 576 ? '' : 'form-label-weight-bold',
labelPosition: windowWidth > 576 ? 'right' : 'top',
labelWidth: windowWidth > 576 ? 'auto' : '',
}
$A.eeuiAppSendMessage({
action: 'windowSize',
width: windowWidth,
height: windowHeight,
});
})
window.addEventListener('scroll', () => {
state.windowScrollY = window.scrollY
})
window.addEventListener('message', ({data}) => {
data = $A.jsonParse(data);
if (data.action === 'eeuiAppSendMessage') {
const items = $A.isArray(data.data) ? data.data : [data.data];
items.forEach(item => {
$A.eeuiAppSendMessage(item);
})
}
})
window.addEventListener('fullscreenchange', () => {
if (document.fullscreenElement) {
$A("body").addClass("fullscreen-mode")
} else {
$A("body").removeClass("fullscreen-mode")
}
});
window.visualViewport?.addEventListener('resize', () => {
state.viewportHeight = window.visualViewport.height || 0;
});
},
/**
* 初始化
* @param state
* @param dispatch
* @returns {Promise<unknown>}
*/
init({state, dispatch}) {
return new Promise(async resolve => {
// 语言、主题、用户信息
const urlParams = $A.urlParameterAll()
const paramMap = {
language: '__system:languageName__',
theme: '__system:themeConf__',
userid: '__system:userId__',
token: '__system:userToken__'
};
const paramUser = {
userid: 0,
token: null
}
Object.entries(paramMap).forEach(([param, key]) => {
if (urlParams[param]) {
window.localStorage.setItem(key, urlParams[param]);
param === 'userid' && (paramUser.userid = $A.runNum(urlParams[param]))
param === 'token' && (paramUser.token = urlParams[param])
}
});
if (Object.keys(paramMap).some(param => urlParams[param])) {
const newUrl = $A.removeURLParameter(window.location.href, Object.keys(paramMap));
window.history.replaceState(null, '', newUrl);
}
// 处理用户身份信息
if (paramUser.userid > 0 && paramUser.token) {
const userInfo = await $A.IDBJson('userInfo')
await $A.IDBSet("userInfo", Object.assign(userInfo, paramUser));
}
// 清理缓存、读取缓存
let action = null
const clearCache = await $A.IDBString("clearCache")
if (clearCache) {
if (clearCache === "handle") {
action = "handleClearCache"
}
await $A.IDBRemove("clearCache")
await $A.IDBSet("cacheVersion", "clear")
}
const cacheVersion = await $A.IDBString("cacheVersion")
if (cacheVersion && cacheVersion !== state.cacheVersion) {
await dispatch("handleClearCache")
} else {
await dispatch("handleReadCache")
}
// 主题皮肤
await dispatch("synchTheme")
// Keyboard
await dispatch("handleKeyboard")
// 客户端ID
if (!state.clientId) {
state.clientId = $A.randomString(6)
await $A.IDBSet("clientId", state.clientId)
}
// 获取apiKey
dispatch("call", {
url: "users/key/client",
data: {client_id: state.clientId},
encrypt: false,
}).then(({data}) => {
state.apiKeyData = data;
})
// 获取系统设置
dispatch("systemSetting")
// 载入静态资源
await $A.loadScriptS([
// 基础包
'js/jsencrypt.min.js',
'js/scroll-into-view.min.js',
// 加载语言包
`language/web/key.js`,
`language/web/${languageName}.js`,
`language/iview/${languageName}.js`,
])
// 初始化语言
initLanguage()
resolve(action)
})
},
/**
* 获取安全区域
* @param state
* @returns {Promise<unknown>}
*/
safeAreaInsets({state}) {
return new Promise(resolve => {
if (!state.isFirstPage) {
return resolve(null)
}
$A.eeuiAppGetSafeAreaInsets().then(async data => {
data.top = data.top || state.safeAreaSize?.data?.top || 0
data.bottom = data.bottom || state.safeAreaSize?.data?.bottom || 0
const proportion = data.height / window.outerHeight
state.safeAreaSize = {
top: Math.round(data.top / proportion * 100) / 100,
bottom: Math.round(data.bottom / proportion * 100) / 100,
data
}
resolve(state.safeAreaSize)
}).catch(e => {
console.warn(e)
resolve(null)
})
})
},
/**
* 访问接口
* @param state
* @param dispatch
* @param params // {url,data,method,timeout,header,spinner,websocket,encrypt, before,complete,success,error,after}
* @returns {Promise<unknown>}
*/
call({state, dispatch}, params) {
if (!$A.isJson(params)) params = {url: params}
const header = {
'Content-Type': 'application/json',
'language': languageName,
'token': state.userToken,
'fd': $A.getSessionStorageString("userWsFd"),
'version': window.systemInfo.version || "0.0.1",
'platform': $A.Platform,
}
if (!state.userToken && state.meetingWindow?.meetingSharekey) {
header.sharekey = state.meetingWindow.meetingSharekey;
}
if ($A.isJson(params.header)) {
params.header = Object.assign(header, params.header)
} else {
params.header = header
}
if (state.systemConfig.e2e_message === 'open'
&& params.encrypt === undefined
&& $A.inArray(params.url, [
'users/login',
'users/editpass',
'users/operation',
'users/delete/account',
'system/license',
'users/bot/*',
'dialog/msg/*',
], true)) {
params.encrypt = true
}
if (params.encrypt) {
const userAgent = window.navigator.userAgent;
if (window.systemInfo.debug === "yes"
|| /Windows NT 5.1|Windows XP/.test(userAgent)
|| userAgent.indexOf("Windows NT 6.0") !== -1
|| userAgent.indexOf("Windows NT 6.1") !== -1
|| userAgent.indexOf("Windows NT 6.2") !== -1) {
params.encrypt = false // 是 Windows Xp, Vista, 7, 8 系统,不支持加密
}
}
params.url = $A.apiUrl(params.url)
params.data = $A.newDateString(params.data)
//
const cloneParams = $A.cloneJSON(params)
return new Promise(async (resolve, reject) => {
// 判断服务器地址
if (/^https?:\/\/public\//.test(params.url)) {
reject({ret: -1, data: {}, msg: "No server address"})
return
}
// 加密传输
const encrypt = []
if (params.encrypt === true) {
if (params.data) {
// 有数据才加密
if (state.apiKeyData.type === 'pgp') {
// PGP加密
encrypt.push(`encrypt_type=${state.apiKeyData.type};encrypt_id=${state.apiKeyData.id}`)
params.method = "post" // 加密传输时强制使用post
params.data = {encrypted: await dispatch("pgpEncryptApi", params.data)}
}
}
encrypt.push("client_type=pgp;client_key=" + (await dispatch("pgpGetLocalKey")).publicKeyB64)
}
if (encrypt.length > 0) {
params.header.encrypt = encrypt.join(";")
}
// 数据转换
if (params.method === "post") {
params.data = JSON.stringify(params.data)
}
// 等待效果Spinner
if (params.spinner === true || (typeof params.spinner === "number" && params.spinner > 0)) {
const {before, complete} = params
params.before = () => {
dispatch("showSpinner", typeof params.spinner === "number" ? params.spinner : 0)
typeof before === "function" && before()
}
params.complete = () => {
dispatch("hiddenSpinner")
typeof complete === "function" && complete()
}
}
// 请求成功
params.success = async (result, status, xhr) => {
// 数据校验
if (!$A.isJson(result)) {
console.log(result, status, xhr)
reject({ret: -1, data: {}, msg: $A.L('返回参数错误')})
return
}
// 数据解密
if (params.encrypt === true && result.encrypted) {
result = await dispatch("pgpDecryptApi", result.encrypted)
}
const {ret, data, msg} = result
// 身份判断(身份丢失)
if (ret === -1) {
state.userId = 0
if (params.checkAuth !== false) {
state.ajaxAuthException = msg || $A.L('请登录后继续...')
reject(Object.assign(result, {msg: false}))
return
}
}
// 身份判断(需要昵称)
if (ret === -2 && params.checkNick !== false) {
dispatch("userEditInput", 'nickname').then(() => {
dispatch("call", cloneParams).then(resolve).catch(reject)
}).catch(err => {
reject({ret: -1, data, msg: err || $A.L('请设置昵称!')})
})
return
}
// 身份判断(需要联系电话)
if (ret === -3 && params.checkTel !== false) {
dispatch("userEditInput", 'tel').then(() => {
dispatch("call", cloneParams).then(resolve).catch(reject)
}).catch(err => {
reject({ret: -1, data, msg: err || $A.L('请设置联系电话!')})
})
return
}
// 返回数据
if (ret === 1) {
resolve({data, msg, xhr})
return
}
// 错误处理
reject({ret, data, msg: msg || $A.L('未知错误')})
if (ret === -4001) {
dispatch("forgetProject", {id: data.project_id})
} else if (ret === -4002) {
data.force === 1 && (state.taskArchiveView = 0)
dispatch("forgetTask", {id: data.task_id})
} else if (ret === -4003) {
dispatch("forgetDialog", {id: data.dialog_id})
} else if (ret === -4004) {
dispatch("getTaskForParent", data.task_id).catch(() => {})
}
}
// 请求失败
params.error = async (xhr, status) => {
const reason = {ret: -1, data: {}, msg: $A.L('请求失败,请稍后重试。')}
const isNetworkException = window.navigator.onLine === false || (status === 0 && xhr.readyState === 4)
// 网络异常
if (isNetworkException) {
// 重试一次
if (cloneParams.method !== "post" && cloneParams.networkFailureRetry !== false) {
await new Promise(resolve => setTimeout(resolve, 1000));
dispatch("call", Object.assign(cloneParams, {networkFailureRetry: false})).then(resolve).catch(reject)
return
}
// 异常提示
reason.ret = -1001
reason.msg = params.checkNetwork !== false ? false : $A.L('网络异常,请稍后重试。')
if (params.checkNetwork !== false && $A.Ready !== false) {
state.ajaxNetworkException = $A.L("网络连接失败,请检查网络设置。")
}
}
// 异常处理
reject(reason)
console.error(xhr, status);
}
// 发起请求
$A.ajaxc(params)
})
},
/**
* 取消请求
* @param state
* @param requestId
* @returns {Promise<unknown>}
*/
callCancel({state}, requestId) {
return new Promise((resolve, reject) => {
if ($A.ajaxcCancel(requestId)) {
resolve()
} else {
reject()
}
})
},
/**
* 获取系统设置
* @param dispatch
* @param state
* @returns {Promise<unknown>}
*/
systemSetting({dispatch, state}) {
return new Promise((resolve, reject) => {
switch (state.systemConfig.__state) {
case "success":
resolve(state.systemConfig)
break
case "loading":
setTimeout(_ => {
dispatch("systemSetting").then(resolve).catch(reject)
}, 100)
break
default:
state.systemConfig.__state = "loading"
dispatch("call", {
url: "system/setting",
}).then(({data}) => {
state.systemConfig = Object.assign(data, {
timezoneDifference: $A.updateTimezone(data.server_timezone),
__state: "success",
})
resolve(state.systemConfig)
}).catch(_ => {
state.systemConfig.__state = "error"
reject()
});
break
}
})
},
/**
* 下载文件
* @param state
* @param data
*/
downUrl({state}, data) {
if (!data) {
return
}
let url = data;
let addToken = true
if ($A.isJson(data)) {
url = data.url
addToken = !!data.token
}
if (addToken) {
let params = {
token: state.userToken
};
if ($A.isJson(data)) {
url = data.url;
params = data.params || {};
}
url = $A.urlAddParams(url, params);
}
if ($A.Electron) {
$A.Electron.request({
action: 'openDownloadWindow',
language: languageName,
theme: state.themeName,
});
$A.Electron.request({
action: 'createDownload',
url
});
} else if ($A.isEEUIApp) {
$A.eeuiAppOpenWeb(url);
} else {
window.open(url)
}
},
/**
* 显示文件(打开文件所在位置)
* @param state
* @param getters
* @param dispatch
* @param data
*/
filePos({state, getters, dispatch}, data) {
if ($A.isSubElectron) {
$A.syncDispatch("filePos", data)
$A.Electron.sendMessage('mainWindowActive');
return
}
dispatch('openTask', 0)
if (!getters.isMessengerPage || state.windowPortrait) {
// 如果 当前不是消息页面 或 是竖屏 则关闭对话窗口
dispatch("openDialog", 0);
}
$A.goForward({name: 'manage-file', params: data});
},
/**
* 切换面板变量
* @param commit
* @param state
* @param data
* @param data|{key, project_id}
*/
toggleProjectParameter({commit, state}, data) {
$A.syncDispatch("toggleProjectParameter", data)
//
let key = data;
let value = null;
let project_id = state.projectId;
if ($A.isJson(data)) {
key = data.key;
value = data.value;
project_id = data.project_id;
}
if (project_id) {
let index = state.cacheProjectParameter.findIndex(item => item.project_id == project_id)
if (index === -1) {
commit("project/parameter/push", $A.projectParameterTemplate(project_id))
index = state.cacheProjectParameter.findIndex(item => item.project_id == project_id)
}
const cache = state.cacheProjectParameter[index];
if (!$A.isJson(key)) {
key = {[key]: value || !cache[key]};
}
commit("project/parameter/splice", {index, data: Object.assign(cache, key)})
}
},
/**
* 设置主题
* @param state
* @param dispatch
* @param mode
* @returns {Promise<unknown>}
*/
setTheme({state, dispatch}, mode) {
return new Promise(function (resolve) {
if (mode === undefined) {
resolve(false)
return;
}
if (!$A.dark.utils.supportMode()) {
if ($A.isEEUIApp) {
$A.modalWarning("仅Android设置支持主题功能");
} else {
$A.modalWarning("仅客户端或Chrome浏览器支持主题功能");
}
resolve(false)
return;
}
dispatch("synchTheme", mode)
resolve(true)
});
},
/**
* 同步主题
* @param state
* @param dispatch
* @param mode
*/
synchTheme({state, dispatch}, mode = undefined) {
if (typeof mode === "undefined") {
mode = state.themeConf
} else {
state.themeConf = mode
}
switch (mode) {
case 'dark':
$A.dark.enableDarkMode()
break;
case 'light':
$A.dark.disableDarkMode()
break;
default:
state.themeConf = "auto"
$A.dark.autoDarkMode()
break;
}
state.themeName = $A.dark.isDarkEnabled() ? 'dark' : 'light'
window.localStorage.setItem("__system:themeConf__", state.themeConf)
//
if ($A.isEEUIApp) {
$A.eeuiAppSendMessage({
action: 'updateTheme',
themeName: state.themeName,
themeDefault: {
theme: {
dark: '#131313',
light: '#f8f8f8'
},
nav: {
dark: '#cdcdcd',
light: '#232323'
}
}
});
} else if ($A.isElectron) {
$A.Electron.sendMessage('setStore', {
key: 'themeConf',
value: state.themeConf
});
}
},
/**
* 获取基本数据(项目、对话、仪表盘任务、会员基本信息)
* @param state
* @param dispatch
* @param timeout
*/
getBasicData({state, dispatch}, timeout) {
if (typeof timeout === "number") {
window.__getBasicDataTimer && clearTimeout(window.__getBasicDataTimer)
if (timeout > -1) {
window.__getBasicDataTimer = setTimeout(_ => dispatch("getBasicData", null), timeout)
}
return
}
//
const tmpKey = state.userId + $A.dayjs().unix()
if (window.__getBasicDataKey === tmpKey) {
return
}
window.__getBasicDataKey = tmpKey
//
dispatch("getDialogAuto").catch(() => {});
dispatch("getDialogTodo", 0).catch(() => {});
dispatch("getTaskPriority", 1000);
dispatch("getReportUnread", 1000);
dispatch("getApproveUnread", 1000);
dispatch("getProjectByQueue");
dispatch("getTaskForDashboard");
dispatch("dialogMsgRead");
dispatch("updateMicroAppsStatus");
//
const allIds = Object.values(state.userAvatar).map(({userid}) => userid);
[...new Set(allIds)].some(userid => dispatch("getUserBasic", {userid}))
},
/**
* 获取未读工作报告数量
* @param state
* @param dispatch
* @param timeout
*/
getReportUnread({state, dispatch}, timeout) {
window.__getReportUnread && clearTimeout(window.__getReportUnread)
window.__getReportUnread = setTimeout(() => {
if (state.userId === 0) {
state.reportUnreadNumber = 0;
} else {
dispatch("call", {
url: 'report/unread',
}).then(({data}) => {
state.reportUnreadNumber = data.total || 0;
}).catch(_ => {});
}
}, typeof timeout === "number" ? timeout : 1000)
},
/**
* 获取审批待办未读数量
* @param state
* @param dispatch
* @param timeout
*/
getApproveUnread({state, dispatch}, timeout) {
window.__getApproveUnread && clearTimeout(window.__getApproveUnread)
window.__getApproveUnread = setTimeout(() => {
if (state.userId === 0) {
state.approveUnreadNumber = 0;
} else {
dispatch("call", {
url: 'approve/process/doto'
}).then(({data}) => {
state.approveUnreadNumber = data.total || 0;
}).catch(({msg}) => {
if( msg.indexOf("404 not found") !== -1){
$A.modalInfo({
title: '版本过低',
content: '服务器版本过低,请升级服务器。',
})
}
});
}
}, typeof timeout === "number" ? timeout : 1000)
},
/**
* 获取/更新会员信息
* @param dispatch
* @returns {Promise<unknown>}
*/
getUserInfo({dispatch}) {
return new Promise(function (resolve, reject) {
dispatch("call", {
url: 'users/info',
}).then(result => {
dispatch("saveUserInfo", result.data);
resolve(result)
}).catch(e => {
console.warn(e);
reject(e)
});
});
},
/**
* 获取会员扩展信息
* @param state
* @param dispatch
* @param userid
* @returns {Promise<unknown>}
*/
getUserExtra({state, dispatch}, userid) {
return new Promise(async (resolve, reject) => {
if (!userid) {
reject({msg: "userid missing"});
return;
}
const cacheMap = state.cacheUserExtra || {};
const cacheItem = cacheMap[`${userid}`];
const now = Date.now();
if (cacheItem && cacheItem.data && (now - cacheItem.updatedAt) < 30000) {
resolve(cacheItem.data);
return;
}
try {
const {data} = await dispatch("call", {
url: 'users/extra',
data: {userid},
});
state.cacheUserExtra = Object.assign({}, cacheMap, {
[`${userid}`]: {
data,
updatedAt: Date.now()
}
});
resolve(data);
} catch (error) {
reject(error);
}
});
},
/**
* 缓存会员扩展信息
* @param state
* @param payload {userid, data}
*/
saveUserExtra({state}, payload) {
const userid = $A.runNum(payload?.userid);
if (!userid || !$A.isJson(payload?.data)) {
return;
}
const cacheMap = state.cacheUserExtra || {};
const current = cacheMap[`${userid}`]?.data || {};
state.cacheUserExtra = Object.assign({}, cacheMap, {
[`${userid}`]: {
data: Object.assign({}, current, payload.data),
updatedAt: Date.now()
}
});
},
/**
* 更新会员信息
* @param state
* @param dispatch
* @param info
* @returns {Promise<unknown>}
*/
saveUserInfoBase({state, dispatch}, info) {
return new Promise(async resolve => {
const userInfo = $A.cloneJSON(info);
userInfo.userid = $A.runNum(userInfo.userid);
userInfo.token = userInfo.userid > 0 ? (userInfo.token || state.userToken) : '';
state.userInfo = userInfo;
state.userId = userInfo.userid;
state.userToken = userInfo.token;
state.userIsAdmin = $A.inArray('admin', userInfo.identity);
if ($A.isSubElectron || ($A.isEEUIApp && !state.isFirstPage)) {
// 子窗口Electron、不是第一个页面App 不保存
} else {
await $A.IDBSet("userInfo", state.userInfo);
}
//
$A.eeuiAppSendMessage({
action: 'userChatList',
language: $A.eeuiAppConvertLanguage(),
url: $A.mainUrl('api/users/share/list') + `?token=${state.userToken}`
});
$A.eeuiAppSendMessage({
action:"userUploadUrl",
dirUrl: $A.mainUrl('api/file/content/upload') + `?token=${state.userToken}`,
chatUrl: $A.mainUrl('api/dialog/msg/sendfiles') + `?token=${state.userToken}`,
});
//
resolve()
})
},
/**
* 更新会员信息
* @param commit
* @param state
* @param dispatch
* @param info
* @returns {Promise<unknown>}
*/
saveUserInfo({commit, state, dispatch}, info) {
return new Promise(async resolve => {
await dispatch("saveUserInfoBase", info);
//
dispatch("getBasicData", null);
if (state.userId > 0) {
commit("user/save", state.cacheUserBasic.filter(({userid}) => userid !== state.userId))
dispatch("saveUserBasic", state.userInfo);
}
resolve()
});
},
/**
* 获取用户基础信息
* @param state
* @param dispatch
* @param data {userid}
*/
getUserBasic({state, dispatch}, data) {
if (state.loadUserBasic === true) {
data && state.cacheUserWait.push(data);
return;
}
//
let time = $A.dayjs().unix();
let list = $A.cloneJSON(state.cacheUserWait);
if (data && data.userid) {
list.push(data)
}
state.cacheUserWait = [];
//
let array = [];
let timeout = 0;
list.some((item) => {
let temp = state.cacheUserBasic.find(({userid}) => userid == item.userid);
if (temp && time - temp._time <= 30) {
setTimeout(() => {
emitter.emit('userActive', {type: 'cache', data: temp});
}, timeout += 5);
return false;
}
array.push(item);
});
if (array.length === 0) {
return;
} else if (array.length > 30) {
state.cacheUserWait = array.slice(30)
array = array.slice(0, 30)
}
//
state.loadUserBasic = true;
dispatch("call", {
url: 'users/basic',
data: {
userid: [...new Set(array.map(({userid}) => userid))]
},
checkAuth: false
}).then(result => {
time = $A.dayjs().unix();
array.forEach(value => {
let data = result.data.find(({userid}) => userid == value.userid) || Object.assign(value, {email: ""});
data._time = time;
dispatch("saveUserBasic", data);
});
state.loadUserBasic = false;
dispatch("getUserBasic");
}).catch(e => {
console.warn(e);
state.loadUserBasic = false;
dispatch("getUserBasic");
});
},
/**
* 获取用户基础信息(缓存没有则请求网络)
* @param state
* @param dispatch
* @param userid
* @returns {Promise<unknown>}
*/
getUserData({state, dispatch}, userid) {
return new Promise(async (resolve, reject) => {
let tempUser = state.cacheUserBasic.find(item => item.userid == userid);
if (!tempUser) {
try {
const {data} = await dispatch("call", {
url: 'users/basic',
data: {
userid: [userid]
},
checkAuth: false
});
tempUser = data.find(item => item.userid == userid);
} catch (_) {}
}
if (tempUser) {
resolve($A.cloneJSON(tempUser));
} else {
reject();
}
})
},
/**
* 保存用户基础信息
* @param commit
* @param state
* @param data
*/
saveUserBasic({commit, state}, data) {
$A.syncDispatch("saveUserBasic", data)
//
const index = state.cacheUserBasic.findIndex(({userid}) => userid == data.userid);
if (index > -1) {
data = Object.assign({}, state.cacheUserBasic[index], data)
commit("user/splice", {index, data})
} else {
commit("user/push", data)
}
emitter.emit('userActive', {type: 'cache', data});
},
/**
* 修改机器人信息
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
editUserBot({dispatch}, data) {
return new Promise((resolve, reject) => {
let dialogId = 0
if (data.dialog_id) {
dialogId = data.dialog_id;
delete data.dialog_id;
}
dispatch("call", {
url: 'users/bot/edit',
data,
method: 'post'
}).then(({data, msg}) => {
dispatch("saveUserBasic", {
userid: data.id,
nickname: data.name,
userimg: data.avatar,
});
if (dialogId) {
dispatch("saveDialog", {
id: dialogId,
name: data.name
});
}
resolve({data, msg})
}).catch(reject);
})
},
/**
* 设置用户信息
* @param dispatch
* @param type
* @returns {Promise<unknown>}
*/
userEditInput({dispatch}, type) {
return new Promise(function (userResolve, userReject) {
let desc = '';
if (type === 'nickname') {
desc = '昵称';
} else if (type === 'tel') {
desc = '联系电话';
} else {
userReject('参数错误')
return
}
setTimeout(_ => {
$A.modalInput({
title: `设置${desc}`,
placeholder: `请输入您的${desc}`,
okText: "保存",
onOk: (value) => {
if (!value) {
return `请输入${desc}`
}
return new Promise((inResolve, inReject) => {
dispatch("call", {
url: 'users/editdata',
data: {
[type]: value,
},
checkNick: false,
checkTel: false,
}).then(() => {
dispatch('getUserInfo').finally(_ => {
inResolve()
userResolve()
});
}).catch(({msg}) => {
inReject(msg)
});
})
},
onCancel: _ => userReject
});
}, 100)
});
},
/**
* 获取部门列表
* @param dispatch
* @returns {Promise<unknown>}
*/
getDepartmentList({dispatch}) {
return new Promise((resolve, reject) => {
const generateList = (data, parent_id = 0, level = 0, chains = []) => {
let result = [];
data.some(item => {
if (item.parent_id == parent_id) {
const newItem = Object.assign({}, item, {
chains: chains.concat([item.name]),
level: level + 1
});
result.push(newItem);
// 递归获取子部门
const children = generateList(data, item.id, level + 1, chains.concat([item.name]));
result = result.concat(children);
}
});
return result;
};
dispatch("call", {
url: 'users/department/list',
}).then(({data}) => {
resolve(generateList(data, 0, 1));
}).catch(reject);
});
},
/**
* 登出(打开登录页面)
* @param state
* @param dispatch
* @param appendFrom
* @returns {Promise<unknown>}
*/
logout({state, dispatch}, appendFrom = true) {
return new Promise(async resolve => {
try {
await dispatch("call", {
url: "users/logout",
timeout: 6000
})
} catch (e) {
console.log(e);
}
dispatch("handleClearCache", {}).then(() => {
let from = ["/", "/login"].includes(window.location.pathname) ? "" : encodeURIComponent(window.location.href);
if (appendFrom === false) {
from = null;
}
$A.goForward({name: 'login', query: from ? {from: from} : {}}, true);
resolve();
});
})
},
/**
* 处理快捷键配置
* @param state
* @param newData
* @returns {Promise<unknown>}
*/
handleKeyboard({state}, newData) {
return new Promise(resolve => {
if (!window.localStorage.getItem("__system:keyboardConf__")) {
window.localStorage.setItem("__system:keyboardConf__", window.localStorage.getItem("__keyboard:data__"))
window.localStorage.removeItem("__keyboard:data__")
}
const data = $A.isJson(newData) ? newData : ($A.jsonParse(window.localStorage.getItem("__system:keyboardConf__")) || {})
data.screenshot_key = (data.screenshot_key || "").trim().toLowerCase()
data.send_button_app = data.send_button_app || 'enter' // button, enter 移动端发送按钮,默认 enter (键盘回车发送)
data.send_button_desktop = data.send_button_desktop || 'enter' // button, enter 桌面端发送按钮,默认 enter (键盘回车发送)
window.localStorage.setItem("__system:keyboardConf__", $A.jsonStringify(data))
state.cacheKeyboard = data
resolve(data)
})
},
/**
* 清除缓存
* @param state
* @param dispatch
* @param userData
* @returns {Promise<unknown>}
*/
handleClearCache({state, dispatch}, userData) {
return new Promise(async resolve => {
// localStorage
const keys = ['themeConf', 'languageName', 'keyboardConf'];
const savedData = keys.reduce((acc, key) => ({
...acc,
[key]: window.localStorage.getItem(`__system:${key}__`)
}), {});
window.localStorage.clear();
keys.forEach(key =>
window.localStorage.setItem(`__system:${key}__`, savedData[key])
);
// localForage
const cacheItems = {
clientId: await $A.IDBString("clientId"),
cacheServerUrl: await $A.IDBString("cacheServerUrl"),
cacheCalendarView: await $A.IDBString("cacheCalendarView"),
cacheProjectParameter: await $A.IDBArray("cacheProjectParameter"),
cacheLoginEmail: await $A.IDBString("cacheLoginEmail"),
cacheFileSort: await $A.IDBJson("cacheFileSort"),
cacheTranslationLanguage: await $A.IDBString("cacheTranslationLanguage"),
cacheTranscriptionLanguage: await $A.IDBString("cacheTranscriptionLanguage"),
cacheTranslations: await $A.IDBArray("cacheTranslations"),
cacheEmojis: await $A.IDBArray("cacheEmojis"),
userInfo: await $A.IDBJson("userInfo"),
mcpServerStatus: await $A.IDBJson("mcpServerStatus"),
cacheVersion: state.cacheVersion,
};
await $A.IDBClear();
await Promise.all(
Object.entries(cacheItems).map(([key, value]) =>
$A.IDBSet(key, value)
)
);
// userInfo
await dispatch("saveUserInfoBase", $A.isJson(userData) ? userData : cacheItems.userInfo)
// readCache
await dispatch("handleReadCache")
// Reset auth exception flag after successful login flow
state.ajaxAuthException = null
resolve()
});
},
/**
* 读取缓存
* @param state
* @param dispatch
* @returns {Promise<unknown>}
*/
handleReadCache({state}) {
return new Promise(async resolve => {
// 定义需要获取的数据映射
const dataMap = {
string: [
'clientId',
'cacheServerUrl',
'cacheCalendarView',
'cacheTranslationLanguage',
'cacheTranscriptionLanguage'
],
array: [
'cacheUserBasic',
'cacheProjects',
'cacheColumns',
'cacheTasks',
'cacheProjectParameter',
'cacheTranslations',
'dialogMsgs',
'dialogDrafts',
'dialogQuotes',
'fileLists',
'callAt',
'cacheEmojis',
'cacheDialogs',
'microAppsIds',
'microAppsMenus',
],
json: [
'userInfo',
'taskRelatedCache',
'dialogCommonCountCache',
'mcpServerStatus'
]
};
// 批量获取数据
const data = await Promise.all([
...dataMap.string.map(key => $A.IDBString(key)),
...dataMap.array.map(key => $A.IDBArray(key)),
...dataMap.json.map(key => $A.IDBJson(key))
]);
// 更新 state
[...dataMap.string, ...dataMap.array, ...dataMap.json].forEach((key, index) => {
state[key] = data[index];
});
// 特殊处理 cacheDialogs
state.cacheDialogs = state.cacheDialogs.map(item => ({
...item,
loading: false,
}));
// 特殊处理 dialogDrafts
state.dialogDrafts = state.dialogDrafts.filter(item => !!item.content).map(item => ({
...item,
tag: !!item.content,
}));
// TranslationLanguage 检查
if (typeof languageList[state.cacheTranslationLanguage] === "undefined") {
state.cacheTranslationLanguage = languageName;
}
// TranscriptionLanguage 检查
if (typeof languageList[state.cacheTranscriptionLanguage] === "undefined") {
state.cacheTranscriptionLanguage = '';
}
// 处理用户信息
if (state.userInfo.userid) {
state.userId = state.userInfo.userid = $A.runNum(state.userInfo.userid);
state.userToken = state.userInfo.token;
state.userIsAdmin = $A.inArray("admin", state.userInfo.identity);
}
// 处理 ServerUrl
if (state.cacheServerUrl) {
window.systemInfo.apiUrl = state.cacheServerUrl
}
resolve();
})
},
/**
* Electron 页面卸载触发
*/
onBeforeUnload() {
if ($A.isSubElectron && dialogDraftState.subTemp) {
$A.syncDispatch("saveDialogDraft", dialogDraftState.subTemp)
dialogDraftState.subTemp = null;
}
},
/**
* 滚动到底部(将 el 底部对齐到网页底部)
* @param state
* @param el
*/
scrollBottom({state}, el) {
if (!el) {
return
}
const rect = el.getBoundingClientRect();
if (!rect) {
return;
}
window.scrollTo({
top: rect.bottom + state.safeAreaSize.bottom,
behavior: 'smooth'
});
},
/** *****************************************************************************************/
/** *************************************** 新窗口打开 ****************************************/
/** *****************************************************************************************/
/**
* 链接添加用户身份
* @param state
* @param url
* @returns {Promise<unknown>}
*/
userUrl({state}, url) {
return new Promise(resolve => {
// 如果是访问:服务器域名 且 当前是本地文件,则将服务器域名替换成本地路径
if ($A.getDomain(url) == $A.getDomain($A.mainUrl()) && isLocalHost(window.location)) {
try {
const remoteURL = new URL(url)
if (/^\/(single|meeting)\//.test(remoteURL.pathname)) {
// 判断将服务器域名替换成本地路径
const localURL = new URL(window.location)
localURL.hash = remoteURL.pathname + remoteURL.search
return resolve(localURL.toString())
}
} catch (e) {
// 解析失败则不做任何处理
}
}
// 基本参数
const params = {
language: languageName,
theme: state.themeConf,
userid: state.userId,
}
// 如果是访问:服务器域名 或 本地文件,则添加 token 参数
if ($A.getDomain(url) == $A.getDomain($A.mainUrl()) || isLocalHost(url)) {
params.token = state.userToken
}
resolve($A.urlAddParams(url, params))
})
},
/**
* 打开地图选位置App
* @param dispatch
* @param objects {{type: string, key: string, point: string, radius: number}}
* @returns {Promise<unknown>}
*/
openAppMapPage({dispatch}, objects) {
return new Promise(resolve => {
const title = $A.L("定位签到")
const channel = $A.randomString(6)
const params = {
title,
label: $A.L("选择附近地点"),
placeholder: $A.L("搜索地点"),
noresult: $A.L("附近没有找到地点"),
errtip: $A.L("定位失败"),
selectclose: "true",
channel,
}
$A.eeuiAppSetVariate(`location::${channel}`, "");
const url = $A.urlAddParams(window.location.origin + '/tools/map/index.html', Object.assign(params, objects || {}))
dispatch('openAppChildPage', {
pageType: 'app',
pageTitle: title,
url: 'web.js',
params: {
titleFixed: true,
hiddenDone: true,
url
},
callback: ({status}) => {
if (status === 'pause') {
const data = $A.jsonParse($A.eeuiAppGetVariate(`location::${channel}`));
if (data.point) {
$A.eeuiAppSetVariate(`location::${channel}`, "");
if (data.distance > objects.radius) {
$A.modalError(`你选择的位置「${data.title}」不在签到范围内`)
return
}
resolve(data);
}
}
}
})
})
},
/**
* 打开子窗口App
* @param dispatch
* @param objects
*/
async openAppChildPage({dispatch}, objects) {
objects.params.url = await dispatch("userUrl", objects.params.url)
if (typeof objects.params.allowAccess === "undefined") {
// 如果是本地文件,则允许跨域
objects.params.allowAccess = isLocalHost(objects.params.url)
}
if (typeof objects.params.showProgress === "undefined") {
// 如果不是本地文件,则显示进度条
objects.params.showProgress = !isLocalHost(objects.params.url)
}
$A.eeuiAppOpenPage(objects)
},
/**
* 打开子窗口(客户端)
* @param dispatch
* @param params
*/
async openChildWindow({dispatch}, params) {
params.path = await dispatch("userUrl", params.path)
$A.Electron.sendMessage('openChildWindow', params)
},
/**
* 打开新标签窗口(客户端)
* @param dispatch
* @param url
*/
async openWebTabWindow({dispatch}, url) {
const params = {url}
if ($A.getDomain(url) == $A.getDomain($A.mainUrl())) {
params.url = await dispatch("userUrl", url)
} else {
params.webPreferences = {contextIsolation: false}
}
$A.Electron.sendMessage('openWebTabWindow', params)
},
/** *****************************************************************************************/
/** ************************************** 文件 **********************************************/
/** *****************************************************************************************/
/**
* 保存文件数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
saveFile({commit, state, dispatch}, data) {
$A.syncDispatch("saveFile", data)
//
if ($A.isArray(data)) {
data.forEach((file) => {
dispatch("saveFile", file);
});
} else if ($A.isJson(data)) {
let base = {_load: false, _edit: false};
const index = state.fileLists.findIndex(({id}) => id == data.id);
if (index > -1) {
commit("file/splice", {index, data: Object.assign(base, state.fileLists[index], data)})
} else {
commit("file/push", Object.assign(base, data))
}
}
},
/**
* 忘记文件数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
forgetFile({commit, state, dispatch}, data) {
$A.syncDispatch("forgetFile", data)
//
const ids = $A.isArray(data.id) ? data.id : [data.id];
ids.some(id => {
commit("file/save", state.fileLists.filter(file => file.id != id))
state.fileLists.some(file => {
if (file.pid == id) {
dispatch("forgetFile", file);
}
});
})
},
/**
* 获取压缩进度
* @param state
* @param dispatch
* @param data
*/
packProgress({state, dispatch}, data) {
$A.syncDispatch("packProgress", data)
//
const index = state.filePackLists.findIndex(({name}) => name == data.name);
if (index > -1) {
state.filePackLists[index].progress = data.progress;
} else {
state.filePackLists.push(data);
}
},
/**
* 获取文件
* @param commit
* @param state
* @param dispatch
* @param pid
* @returns {Promise<unknown>}
*/
getFiles({commit, state, dispatch}, pid) {
return new Promise(function (resolve, reject) {
dispatch("call", {
url: 'file/lists',
data: {
pid
},
}).then((result) => {
const ids = result.data.map(({id}) => id)
commit("file/save", state.fileLists.filter((item) => item.pid != pid || ids.includes(item.id)));
//
dispatch("saveFile", result.data);
resolve(result)
}).catch(e => {
console.warn(e);
reject(e)
});
});
},
/**
* 搜索文件
* @param state
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
searchFiles({state, dispatch}, data) {
if (!$A.isJson(data)) {
data = {key: data}
}
return new Promise(function (resolve, reject) {
dispatch("call", {
url: 'file/search',
data,
}).then((result) => {
dispatch("saveFile", result.data);
resolve(result)
}).catch(e => {
console.warn(e);
reject(e)
});
});
},
/** *****************************************************************************************/
/** ************************************** 项目 **********************************************/
/** *****************************************************************************************/
/**
* 保存项目数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
saveProject({commit, state, dispatch}, data) {
$A.syncDispatch("saveProject", data)
//
if ($A.isArray(data)) {
data.forEach((project) => {
dispatch("saveProject", project)
});
} else if ($A.isJson(data)) {
if (typeof data.project_column !== "undefined") {
dispatch("saveColumn", data.project_column)
delete data.project_column;
}
const index = state.cacheProjects.findIndex(({id}) => id == data.id);
if (index > -1) {
commit("project/splice", {index, data: Object.assign({}, state.cacheProjects[index], data)})
} else {
if (typeof data.project_user === "undefined") {
data.project_user = []
}
commit("project/push", data)
state.projectTotal++
}
//
state.cacheDialogs.some(dialog => {
if (dialog.type == 'group' && dialog.group_type == 'project' && dialog.group_info && dialog.group_info.id == data.id) {
if (data.name !== undefined) {
dialog.name = data.name
}
for (let key in dialog.group_info) {
if (!dialog.group_info.hasOwnProperty(key) || data[key] === undefined) continue;
dialog.group_info[key] = data[key];
}
}
})
}
},
/**
* 忘记项目数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
forgetProject({commit, state, dispatch}, data) {
$A.syncDispatch("forgetProject", data)
//
const ids = $A.isArray(data.id) ? data.id : [data.id];
ids.some(id => {
const index = state.cacheProjects.findIndex(project => project.id == id);
if (index > -1) {
dispatch("forgetTask", {id: state.cacheTasks.filter(item => item.project_id == data.id).map(item => item.id)})
dispatch("forgetColumn", {id: state.cacheColumns.filter(item => item.project_id == data.id).map(item => item.id)})
commit("project/splice", {index})
state.projectTotal = Math.max(0, state.projectTotal - 1)
}
})
if (ids.includes(state.projectId)) {
const project = $A.cloneJSON(state.cacheProjects).sort((a, b) => {
if (a.top_at || b.top_at) {
return $A.sortDay(b.top_at, a.top_at);
}
return b.id - a.id;
}).find(({id}) => id && id != data.id);
if (project) {
$A.goForward({name: 'manage-project', params: {projectId: project.id}});
} else {
$A.goForward({name: 'manage-dashboard'});
}
}
},
/**
* 获取项目
* @param state
* @param dispatch
* @param requestData
* @returns {Promise<unknown>}
*/
getProjects({state, dispatch}, requestData) {
return new Promise(function (resolve, reject) {
if (state.userId === 0) {
state.cacheProjects = [];
reject({msg: 'Parameter error'});
return;
}
const callData = $callData('projects', requestData, state)
//
setTimeout(() => {
state.loadProjects++;
}, 2000)
dispatch("call", {
url: 'project/lists',
data: callData.get()
}).then(({data}) => {
dispatch("saveProject", data.data);
callData.save(data).then(ids => dispatch("forgetProject", {id: ids}))
state.projectTotal = data.total_all;
//
resolve(data)
}).catch(e => {
console.warn(e);
reject(e)
}).finally(_ => {
state.loadProjects--;
});
});
},
/**
* 获取项目(队列)
* @param dispatch
* @param timeout
*/
getProjectByQueue({dispatch}, timeout = null) {
window.__getProjectByQueueTimer && clearTimeout(window.__getProjectByQueueTimer)
if (typeof timeout === "number") {
window.__getProjectByQueueTimer = setTimeout(_ => dispatch("getProjectByQueue", null), timeout)
return
}
dispatch("getProjects").catch(() => {});
},
/**
* 获取单个项目
* @param state
* @param dispatch
* @param project_id
* @returns {Promise<unknown>}
*/
getProjectOne({state, dispatch}, project_id) {
return new Promise(function (resolve, reject) {
if ($A.runNum(project_id) === 0) {
reject({msg: 'Parameter error'});
return;
}
state.projectLoad++;
dispatch("call", {
url: 'project/one',
data: {
project_id,
},
}).then(result => {
setTimeout(() => {
state.projectLoad--;
}, 10)
dispatch("saveProject", result.data);
resolve(result)
}).catch(e => {
console.warn(e);
state.projectLoad--;
reject(e)
});
});
},
/**
* 归档项目
* @param state
* @param dispatch
* @param project_id
*/
archivedProject({state, dispatch}, project_id) {
return new Promise(function (resolve, reject) {
if ($A.runNum(project_id) === 0) {
reject({msg: 'Parameter error'});
return;
}
dispatch("call", {
url: 'project/archived',
data: {
project_id,
},
}).then(result => {
dispatch("forgetProject", {id: project_id})
resolve(result)
}).catch(e => {
console.warn(e);
dispatch("getProjectOne", project_id).catch(() => {})
reject(e)
});
});
},
/**
* 删除项目
* @param state
* @param dispatch
* @param project_id
*/
removeProject({state, dispatch}, project_id) {
return new Promise(function (resolve, reject) {
if ($A.runNum(project_id) === 0) {
reject({msg: 'Parameter error'});
return;
}
dispatch("call", {
url: 'project/remove',
data: {
project_id,
},
}).then(result => {
dispatch("forgetProject", {id: project_id})
resolve(result)
}).catch(e => {
console.warn(e);
dispatch("getProjectOne", project_id).catch(() => {})
reject(e)
});
});
},
/**
* 退出项目
* @param state
* @param dispatch
* @param project_id
*/
exitProject({state, dispatch}, project_id) {
return new Promise(function (resolve, reject) {
if ($A.runNum(project_id) === 0) {
reject({msg: 'Parameter error'});
return;
}
dispatch("call", {
url: 'project/exit',
data: {
project_id,
},
}).then(result => {
dispatch("forgetProject", {id: project_id})
resolve(result)
}).catch(e => {
console.warn(e);
dispatch("getProjectOne", project_id).catch(() => {})
reject(e)
});
});
},
/** *****************************************************************************************/
/** ************************************** 列表 **********************************************/
/** *****************************************************************************************/
/**
* 保存列表数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
saveColumn({commit, state, dispatch}, data) {
$A.syncDispatch("saveColumn", data)
//
if ($A.isArray(data)) {
data.forEach((column) => {
dispatch("saveColumn", column)
});
} else if ($A.isJson(data)) {
const index = state.cacheColumns.findIndex(({id}) => id == data.id);
if (index > -1) {
commit("project/column/splice", {index, data: Object.assign({}, state.cacheColumns[index], data)})
} else {
commit("project/column/push", data)
}
}
},
/**
* 忘记列表数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
forgetColumn({commit, state, dispatch}, data) {
$A.syncDispatch("forgetColumn", data)
//
const ids = $A.isArray(data.id) ? data.id : [data.id];
const project_ids = [];
ids.some(id => {
const index = state.cacheColumns.findIndex(column => column.id == id);
if (index > -1) {
dispatch("forgetTask", {id: state.cacheTasks.filter(item => item.column_id == data.id).map(item => item.id)})
project_ids.push(state.cacheColumns[index].project_id)
commit("project/column/splice", {index})
}
})
Array.from(new Set(project_ids)).some(id => dispatch("getProjectOne", id).catch(() => {}))
},
/**
* 获取列表
* @param commit
* @param state
* @param dispatch
* @param project_id
* @returns {Promise<unknown>}
*/
getColumns({commit, state, dispatch}, project_id) {
return new Promise(function (resolve, reject) {
if (state.userId === 0) {
state.cacheColumns = [];
reject({msg: 'Parameter error'})
return;
}
state.projectLoad++;
dispatch("call", {
url: 'project/column/lists',
data: {
project_id
}
}).then(({data}) => {
state.projectLoad--;
//
const ids = data.data.map(({id}) => id)
commit("project/column/save", state.cacheColumns.filter((item) => item.project_id != project_id || ids.includes(item.id)))
//
dispatch("saveColumn", data.data);
resolve(data.data)
// 判断只有1列的时候默认版面为表格模式
if (state.cacheColumns.filter(item => item.project_id == project_id).length === 1) {
const cache = state.cacheProjectParameter.find(item => item.project_id == project_id) || {};
if (typeof cache.menuInit === "undefined" || cache.menuInit === false) {
dispatch("toggleProjectParameter", {
project_id,
key: {
menuInit: true,
menuType: 'table',
}
});
}
}
}).catch(e => {
console.warn(e);
state.projectLoad--;
reject(e);
});
})
},
/**
* 删除列表
* @param state
* @param dispatch
* @param column_id
*/
removeColumn({state, dispatch}, column_id) {
return new Promise(function (resolve, reject) {
if ($A.runNum(column_id) === 0) {
reject({msg: 'Parameter error'});
return;
}
dispatch("call", {
url: 'project/column/remove',
data: {
column_id,
},
}).then(result => {
dispatch("forgetColumn", {id: column_id})
resolve(result)
}).catch(e => {
console.warn(e);
reject(e);
});
});
},
/** *****************************************************************************************/
/** ************************************** 任务 **********************************************/
/** *****************************************************************************************/
/**
* 保存任务数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
saveTask({commit, state, dispatch}, data) {
$A.syncDispatch("saveTask", data)
//
if ($A.isArray(data)) {
data.forEach((task) => {
dispatch("saveTask", task)
});
} else if ($A.isJson(data)) {
data._time = $A.dayjs().unix();
//
if (data.flow_item_name && data.flow_item_name.indexOf("|") !== -1) {
const flowInfo = $A.convertWorkflow(data.flow_item_name)
data.flow_item_status = flowInfo.status;
data.flow_item_name = flowInfo.name;
data.flow_item_color = flowInfo.color;
}
//
if (typeof data.archived_at !== "undefined") {
state.cacheTasks.filter(task => task.parent_id == data.id).some(task => {
dispatch("saveTask", Object.assign(task, {
archived_at: data.archived_at,
archived_userid: data.archived_userid
}))
})
}
//
let updateMarking = {};
if (typeof data.update_marking !== "undefined") {
updateMarking = $A.isJson(data.update_marking) ? data.update_marking : {};
delete data.update_marking;
}
//
const index = state.cacheTasks.findIndex(({id}) => id == data.id);
if (index > -1) {
commit("task/splice", {index, data: Object.assign({}, state.cacheTasks[index], data)});
} else {
commit("task/push", data);
}
//
if (updateMarking.is_update_maintask === true || (data.parent_id > 0 && state.cacheTasks.findIndex(({id}) => id == data.parent_id) === -1)) {
dispatch("getTaskOne", data.parent_id).catch(() => {})
}
if (updateMarking.is_update_project === true) {
dispatch("getProjectOne", data.project_id).catch(() => {})
}
if (updateMarking.is_update_content === true) {
dispatch("getTaskContent", data.id);
}
if (updateMarking.is_update_subtask === true) {
dispatch("getTaskForParent", data.id).catch(() => {})
}
//
state.cacheDialogs.some(dialog => {
if (dialog.name === undefined || dialog.dialog_delete === 1) {
return false;
}
if (dialog.type == 'group' && dialog.group_type == 'task' && dialog.group_info && dialog.group_info.id == data.id) {
if (data.name !== undefined) {
dialog.name = data.name
}
for (let key in dialog.group_info) {
if (!dialog.group_info.hasOwnProperty(key) || data[key] === undefined) continue;
dialog.group_info[key] = data[key];
}
}
})
}
},
/**
* 忘记任务数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
forgetTask({commit, state, dispatch}, data) {
$A.syncDispatch("forgetTask", data)
//
const ids = ($A.isArray(data.id) ? data.id : [data.id]).filter(id => id != state.taskArchiveView);
const parent_ids = [];
const project_ids = [];
ids.some(id => {
const index = state.cacheTasks.findIndex(task => task.id == id);
if (index > -1) {
if (state.cacheTasks[index].parent_id) {
parent_ids.push(state.cacheTasks[index].parent_id)
}
project_ids.push(state.cacheTasks[index].project_id)
commit("task/splice", {index})
}
state.cacheTasks.filter(task => task.parent_id == id).some(childTask => {
let cIndex = state.cacheTasks.findIndex(task => task.id == childTask.id);
if (cIndex > -1) {
project_ids.push(childTask.project_id)
commit("task/splice", {index: cIndex})
}
})
})
Array.from(new Set(parent_ids)).some(id => dispatch("getTaskOne", id).catch(() => {}))
Array.from(new Set(project_ids)).some(id => dispatch("getProjectOne", id).catch(() => {}))
//
if (ids.includes(state.taskId)) {
state.taskId = 0;
}
},
/**
* 更新任务“今日任务”、“过期任务”
* @param state
* @param dispatch
*/
todayAndOverdue({state, dispatch}) {
const now = $A.daytz();
const today = now.format("YYYY-MM-DD");
state.cacheTasks.some(task => {
if (!task.end_at) {
return false;
}
const data = {};
const endAt = $A.dayjs(task.end_at)
if (!task.today && endAt.format("YYYY-MM-DD") == today) {
data.today = true
}
if (!task.overdue && endAt < now) {
data.overdue = true;
}
if (Object.keys(data).length > 0) {
dispatch("saveTask", Object.assign(task, data));
}
})
},
/**
* 增加任务消息数量
* @param state
* @param commit
* @param data
*/
increaseTaskMsgNum({state, commit}, data) {
$A.syncDispatch("increaseTaskMsgNum", data)
//
const index = state.cacheTasks.findIndex(item => item.dialog_id === data.id);
if (index !== -1) {
const newData = $A.cloneJSON(state.cacheTasks[index])
newData.msg_num++;
commit("task/splice", {index, data: newData})
}
},
/**
* 新增回复数量
* @param state
* @param commit
* @param data
*/
increaseMsgReplyNum({state, commit}, data) {
$A.syncDispatch("increaseMsgReplyNum", data)
//
const index = state.dialogMsgs.findIndex(m => m.id == data.id)
if (index !== -1) {
const newData = $A.cloneJSON(state.dialogMsgs[index])
newData.reply_num++
commit("message/splice", {index, data: newData})
}
},
/**
* 减少回复数量
* @param state
* @param commit
* @param data
*/
decrementMsgReplyNum({state, commit}, data) {
$A.syncDispatch("decrementMsgReplyNum", data)
//
const index = state.dialogMsgs.findIndex(m => m.id == data.id)
if (index !== -1) {
const newData = $A.cloneJSON(state.dialogMsgs[index])
newData.reply_num--
commit("message/splice", {index, data: newData})
}
},
/**
* 获取任务
* @param state
* @param dispatch
* @param requestData
* @returns {Promise<unknown>}
*/
getTasks({state, dispatch}, requestData) {
if (requestData === null) {
requestData = {}
}
const callData = $callData('tasks', requestData, state)
//
return new Promise(function (resolve, reject) {
if (state.userId === 0) {
state.cacheTasks = [];
reject({msg: 'Parameter error'});
return;
}
if (requestData.project_id) {
state.projectLoad++;
}
//
dispatch("call", {
url: 'project/task/lists',
data: callData.get()
}).then(({data}) => {
if (requestData.project_id) {
state.projectLoad--;
}
dispatch("saveTask", data.data);
callData.save(data).then(ids => dispatch("forgetTask", {id: ids}))
//
if (data.next_page_url) {
requestData.page = data.current_page + 1
if (data.current_page % 30 === 0) {
$A.modalConfirm({
content: "数据已超过" + data.to + "条,是否继续加载?",
onOk: () => {
dispatch("getTasks", requestData).then(resolve).catch(reject)
},
onCancel: () => {
resolve()
}
});
} else {
dispatch("getTasks", requestData).then(resolve).catch(reject)
}
} else {
resolve()
}
}).catch(e => {
console.warn(e);
reject(e)
if (requestData.project_id) {
state.projectLoad--;
}
});
});
},
/**
* 获取单个任务
* @param state
* @param dispatch
* @param data Number|JSONObject{task_id, ?archived_at}
* @returns {Promise<unknown>}
*/
getTaskOne({state, dispatch}, data) {
return new Promise(function (resolve, reject) {
if (/^\d+$/.test(data)) {
data = {task_id: data}
}
if ($A.runNum(data.task_id) === 0) {
reject({msg: 'Parameter error'});
return;
}
//
if ($A.isArray(state.taskOneLoad[data.task_id])) {
state.taskOneLoad[data.task_id].push({resolve, reject})
return;
}
state.taskOneLoad[data.task_id] = []
//
dispatch("call", {
url: 'project/task/one',
data,
}).then(result => {
dispatch("saveTask", result.data);
resolve(result)
state.taskOneLoad[data.task_id].some(item => {
item.resolve(result)
})
}).catch(e => {
console.warn(e);
reject(e)
state.taskOneLoad[data.task_id].some(item => {
item.reject(e)
})
}).finally(_ => {
delete state.taskOneLoad[data.task_id]
});
});
},
/**
* 获取任务的子任务数据
* @param state
* @param dispatch
* @param taskId
*/
getTaskSubData({state, dispatch}, taskId) {
if (!taskId) {
return;
}
const parentTask = state.cacheTasks.find(({id}) => id == taskId);
if (!parentTask) {
return;
}
dispatch("call", {
url: 'project/task/subdata',
data: {
task_id: taskId
},
}).then(({data}) => {
dispatch("saveTask", Object.assign(parentTask, data))
}).catch(e => {
console.warn(e);
});
},
/**
* 获取Dashboard相关任务
* @param state
* @param dispatch
* @param timeout
*/
getTaskForDashboard({state, dispatch}, timeout) {
window.__getTaskForDashboard && clearTimeout(window.__getTaskForDashboard)
if (typeof timeout === "number") {
if (timeout > -1) {
window.__getTaskForDashboard = setTimeout(_ => dispatch("getTaskForDashboard", null), timeout)
}
return;
}
//
if (state.loadDashboardTasks === true) {
return;
}
state.loadDashboardTasks = true;
//
dispatch("getTasks", null).finally(_ => {
state.loadDashboardTasks = false;
})
},
/**
* 获取项目任务
* @param state
* @param dispatch
* @param project_id
* @returns {Promise<unknown>}
*/
getTaskForProject({state, dispatch}, project_id) {
return new Promise(function (resolve, reject) {
dispatch("getTasks", {project_id}).then(resolve).catch(reject)
})
},
/**
* 获取子任务
* @param state
* @param dispatch
* @param parent_id
* @returns {Promise<unknown>}
*/
getTaskForParent({state, dispatch}, parent_id) {
return new Promise(function (resolve, reject) {
dispatch("getTasks", {parent_id}).then(resolve).catch(reject)
})
},
/**
* 删除任务
* @param state
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
removeTask({state, dispatch}, data) {
return new Promise(function (resolve, reject) {
if ($A.runNum(data.task_id) === 0) {
reject({msg: 'Parameter error'});
return;
}
dispatch("setLoad", {
key: `task-${data.task_id}`,
delay: 300
})
dispatch("call", {
url: 'project/task/remove',
data,
}).then(result => {
state.taskArchiveView = 0;
dispatch("forgetTask", {id: data.task_id})
resolve(result)
}).catch(e => {
console.warn(e);
dispatch("getTaskOne", data.task_id).catch(() => {})
reject(e)
}).finally(_ => {
dispatch("cancelLoad", `task-${data.task_id}`)
});
});
},
/**
* 归档(还原)任务
* @param state
* @param dispatch
* @param data Number|JSONObject{task_id, ?archived_at}
* @returns {Promise<unknown>}
*/
archivedTask({state, dispatch}, data) {
return new Promise(function (resolve, reject) {
if (/^\d+$/.test(data)) {
data = {task_id: data}
}
if ($A.runNum(data.task_id) === 0) {
reject({msg: 'Parameter error'});
return;
}
dispatch("setLoad", {
key: `task-${data.task_id}`,
delay: 300
})
dispatch("call", {
url: 'project/task/archived',
data,
}).then(result => {
dispatch("saveTask", result.data)
resolve(result)
}).catch(e => {
console.warn(e);
dispatch("getTaskOne", data.task_id).catch(() => {})
reject(e)
}).finally(_ => {
dispatch("cancelLoad", `task-${data.task_id}`)
});
});
},
/**
* 子任务升级为主任务
* @param dispatch
* @param data Number|JSONObject{task_id}
* @returns {Promise<unknown>}
*/
taskConvertToMain({dispatch}, data) {
return new Promise(function (resolve, reject) {
if (/^\d+$/.test(data)) {
data = {task_id: data}
}
if ($A.runNum(data.task_id) === 0) {
reject({msg: 'Parameter error'});
return;
}
dispatch("setLoad", {
key: `task-${data.task_id}`,
delay: 300
})
dispatch("call", {
url: 'project/task/upgrade',
data,
}).then(result => {
const {task, parent} = result.data || {};
if (task) {
dispatch("saveTask", task);
}
if (parent) {
dispatch("saveTask", parent);
}
resolve(result)
}).catch(e => {
console.warn(e);
dispatch("getTaskOne", data.task_id).catch(() => {})
reject(e)
}).finally(_ => {
dispatch("cancelLoad", `task-${data.task_id}`)
});
});
},
/**
* 获取任务详细描述
* @param state
* @param dispatch
* @param task_id
*/
getTaskContent({state, dispatch}, task_id) {
if ($A.runNum(task_id) === 0) {
return;
}
dispatch("setLoad", {
key: `task-${task_id}`,
delay: 1200
})
dispatch("call", {
url: 'project/task/content',
data: {
task_id,
},
}).then(result => {
dispatch("saveTaskContent", result.data)
}).catch(e => {
console.warn(e);
}).finally(_ => {
dispatch("cancelLoad", `task-${task_id}`)
});
},
/**
* 更新任务详情
* @param commit
* @param state
* @param dispatch
* @param data
*/
saveTaskContent({commit, state, dispatch}, data) {
$A.syncDispatch("saveTaskContent", data)
//
if ($A.isArray(data)) {
data.forEach(item => {
dispatch("saveTaskContent", item)
});
} else if ($A.isJson(data)) {
const index = state.taskContents.findIndex(({task_id}) => task_id == data.task_id);
if (index > -1) {
commit("task/content/splice", {index, data: Object.assign({}, state.taskContents[index], data)})
} else {
commit("task/content/push", data)
}
}
},
/**
* 获取任务文件
* @param state
* @param dispatch
* @param task_id
*/
getTaskFiles({state, dispatch}, task_id) {
if ($A.runNum(task_id) === 0) {
return;
}
dispatch("call", {
url: 'project/task/files',
data: {
task_id,
},
}).then(result => {
result.data.forEach((data) => {
const index = state.taskFiles.findIndex(({id}) => id == data.id)
if (index > -1) {
state.taskFiles.splice(index, 1, data)
} else {
state.taskFiles.push(data)
}
})
dispatch("saveTask", {
id: task_id,
file_num: result.data.length
});
}).catch(e => {
console.warn(e);
});
},
/**
* 忘记任务文件
* @param state
* @param dispatch
* @param file_id
*/
forgetTaskFile({state, dispatch}, file_id) {
const ids = $A.isArray(file_id) ? file_id : [file_id];
ids.some(id => {
const index = state.taskFiles.findIndex(file => file.id == id)
if (index > -1) {
state.taskFiles.splice(index, 1)
}
})
},
/**
* 打开任务详情页
* @param state
* @param dispatch
* @param task
*/
openTask({state, dispatch}, task) {
let task_id = task;
if ($A.isJson(task)) {
if (task.parent_id > 0) {
task_id = task.parent_id;
} else {
task_id = task.id;
}
}
if ($A.isSubElectron) {
if (task_id > 0) {
$A.Electron.sendMessage('updateChildWindow', {
name: `task-${task_id}`,
path: `/single/task/${task_id}`,
});
} else {
$A.Electron.sendMessage('windowClose');
}
return
}
if (state.taskId > 0) {
emitter.emit('handleMoveTop', 'taskModal'); // 已打开任务时将任务窗口置顶
}
state.taskArchiveView = task_id;
state.taskId = task_id;
if (task_id > 0) {
dispatch("getTaskOne", {
task_id,
archived: 'all'
}).then(() => {
dispatch("getTaskContent", task_id);
dispatch("getTaskFiles", task_id);
dispatch("getTaskForParent", task_id).catch(() => {});
dispatch("saveTaskBrowse", task_id);
}).catch(({msg}) => {
$A.modalWarning({
content: msg,
onOk: () => {
state.taskId = 0;
}
});
});
} else {
state.taskOperation = {};
}
},
/**
* 添加任务
* @param state
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
taskAdd({state, dispatch}, data) {
return new Promise(function (resolve, reject) {
const post = $A.cloneJSON($A.newDateString(data));
if ($A.isArray(post.column_id)) post.column_id = post.column_id.find((val) => val)
//
dispatch("call", {
url: 'project/task/add',
data: post,
spinner: 600,
method: 'post',
}).then(result => {
if (result.data.is_visible === 1) {
dispatch("addTaskSuccess", result.data)
}
state.taskLatestId = result.data.id
resolve(result)
}).catch(e => {
console.warn(e);
reject(e);
});
});
},
/**
* 获取任务关联列表
* @param state
* @param dispatch
* @param commit
* @param taskId
* @returns {Promise<unknown>}
*/
getTaskRelated({state, commit, dispatch}, taskId) {
taskId = parseInt(taskId, 10);
if (!taskId) {
return Promise.resolve([]);
}
return new Promise((resolve, reject) => {
dispatch("call", {
url: 'project/task/related',
data: {task_id: taskId},
}).then(({data}) => {
const list = (data.list || []).map(item => ({
...item,
mention: !!item.mention,
mentioned_by: !!item.mentioned_by,
}));
commit('task/related/save', {
taskId,
list,
updatedAt: Date.now(),
});
resolve(list);
}).catch(reject);
});
},
/**
* 添加子任务
* @param dispatch
* @param data {task_id, name}
* @returns {Promise<unknown>}
*/
taskAddSub({dispatch}, data) {
return new Promise(function (resolve, reject) {
dispatch("call", {
url: 'project/task/addsub',
data: data,
spinner: 600,
}).then(result => {
dispatch("addTaskSuccess", result.data)
resolve(result)
}).catch(e => {
console.warn(e);
reject(e);
});
});
},
/**
* 添加任务成功
* @param dispatch
* @param task
*/
addTaskSuccess({dispatch}, task) {
if (typeof task.new_column !== "undefined") {
dispatch("saveColumn", task.new_column)
delete task.new_column
}
dispatch("saveTask", task)
dispatch("getTaskSubData", task.parent_id)
dispatch("getProjectOne", task.project_id).catch(() => {})
},
/**
* 更新任务
* @param state
* @param dispatch
* @param data {task_id, ?}
* @returns {Promise<unknown>}
*/
taskUpdate({state, dispatch}, data) {
return new Promise(function (resolve, reject) {
dispatch("taskBeforeUpdate", data).then(({post}) => {
dispatch("setLoad", {
key: `task-${post.task_id}`,
delay: 300
})
dispatch("call", {
url: 'project/task/update',
data: post,
method: 'post',
}).then(result => {
dispatch("saveTask", result.data)
dispatch("getTaskSubData", result.data.parent_id)
resolve(result)
}).catch(e => {
console.warn(e);
dispatch("getTaskOne", post.task_id).catch(() => {})
reject(e)
}).finally(_ => {
dispatch("cancelLoad", `task-${post.task_id}`)
});
}).catch(reject)
});
},
/**
* 更新任务之前判断
* @param state
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
taskBeforeUpdate({state, dispatch}, data) {
return new Promise(function (resolve, reject) {
let post = $A.cloneJSON($A.newDateString(data));
let title = "温馨提示";
let content = null;
// 修改时间前置判断
if (typeof post.times !== "undefined") {
if (data.times[0] === false) {
content = "你确定要取消任务时间吗?"
}
const currentTask = state.cacheTasks.find(({id}) => id == post.task_id);
title = currentTask.parent_id > 0 ? "更新子任务" : "更新主任务"
if (currentTask) {
if (currentTask.parent_id > 0) {
// 修改子任务,判断主任务
if (post.times[0]) {
state.cacheTasks.some(parentTask => {
if (parentTask.id != currentTask.parent_id) {
return false;
}
if (!parentTask.end_at) {
content = "主任务没有设置时间,设置子任务将同步设置主任务"
return true;
}
let n1 = $A.dayjs(post.times[0]).unix(),
n2 = $A.dayjs(post.times[1]).unix(),
o1 = $A.dayjs(parentTask.start_at).unix(),
o2 = $A.dayjs(parentTask.end_at).unix();
if (n1 < o1) {
content = "新设置的子任务开始时间在主任务时间之外,修改后将同步修改主任务" // 子任务开始时间 < 主任务开始时间
return true;
}
if (n2 > o2) {
content = "新设置的子任务结束时间在主任务时间之外,修改后将同步修改主任务" // 子任务结束时间 > 主任务结束时间
return true;
}
})
}
} else {
// 修改主任务,判断子任务
state.cacheTasks.some(subTask => {
if (subTask.parent_id != currentTask.id) {
return false;
}
if (!subTask.end_at) {
return false;
}
let n1 = $A.dayjs(post.times[0]).unix(),
n2 = $A.dayjs(post.times[1]).unix(),
c1 = $A.dayjs(currentTask.start_at).unix(),
c2 = $A.dayjs(currentTask.end_at).unix(),
o1 = $A.dayjs(subTask.start_at).unix(),
o2 = $A.dayjs(subTask.end_at).unix();
if (c1 == o1 && c2 == o2) {
return false;
}
if (!post.times[0]) {
content = `子任务(${subTask.name})已设置时间,清除主任务时间后将同步清除子任务的时间`
return true;
}
if (n1 > o1) {
content = `新设置的开始时间在子任务(${subTask.name})时间之内,修改后将同步修改子任务` // 主任务开始时间 > 子任务开始时间
return true;
}
if (n2 < o2) {
content = `新设置的结束时间在子任务(${subTask.name})时间之内,修改后将同步修改子任务` // 主任务结束时间 < 子任务结束时间
return true;
}
})
}
}
}
//
if (content === null) {
resolve({
confirm: false,
post
});
return
}
$A.modalConfirm({
title,
content,
onOk: () => {
resolve({
confirm: true,
post
});
},
onCancel: () => {
reject({msg: false})
}
});
});
},
/**
* 获取任务流程信息
* @param state
* @param dispatch
* @param task_id
* @param project_id
* @returns {Promise<unknown>}
*/
getTaskFlow({state, dispatch}, {task_id, project_id}) {
return new Promise(function (resolve, reject) {
dispatch("call", {
url: 'project/task/flow',
data: {
task_id: task_id,
project_id: project_id || 0
},
}).then(result => {
let task = state.cacheTasks.find(({id}) => id == task_id)
let {data} = result
data.turns.some(item => {
const index = state.taskFlowItems.findIndex(({id}) => id == item.id);
if (index > -1) {
state.taskFlowItems.splice(index, 1, item);
} else {
state.taskFlowItems.push(item);
}
if (task
&& task.flow_item_id == item.id
&& task.flow_item_name != item.name) {
state.cacheTasks.filter(({flow_item_id})=> flow_item_id == item.id).some(task => {
dispatch("saveTask", {
id: task.id,
flow_item_name: `${item.status}|${item.name}|${item.color}`,
})
})
}
})
//
delete data.turns;
const index = state.taskFlows.findIndex(({task_id}) => task_id == data.task_id);
if (index > -1) {
state.taskFlows.splice(index, 1, data);
} else {
state.taskFlows.push(data);
}
resolve(result)
}).catch(e => {
console.warn(e);
reject(e);
});
});
},
/**
* 获取任务优先级预设数据
* @param state
* @param dispatch
* @param timeout
*/
getTaskPriority({state, dispatch}, timeout) {
window.__getTaskPriority && clearTimeout(window.__getTaskPriority)
window.__getTaskPriority = setTimeout(() => {
dispatch("call", {
url: 'system/priority',
}).then(result => {
state.taskPriority = result.data;
}).catch(e => {
console.warn(e);
});
}, typeof timeout === "number" ? timeout : 1000)
},
/**
* 获取添加项目列表预设数据
* @param state
* @param dispatch
* @returns {Promise<unknown>}
*/
getColumnTemplate({state, dispatch}) {
return new Promise(function (resolve, reject) {
dispatch("call", {
url: 'system/column/template',
}).then(result => {
state.columnTemplate = result.data;
resolve(result)
}).catch(e => {
console.warn(e);
reject(e);
});
});
},
/**
* 保存完成任务临时表
* @param state
* @param task_id
*/
saveTaskCompleteTemp({state}, task_id) {
if (/^\d+$/.test(task_id) && !state.taskCompleteTemps.includes(task_id)) {
state.taskCompleteTemps.push(task_id)
}
},
/**
* 忘记完成任务临时表
* @param state
* @param task_id 任务ID 或 true标识忘记全部
*/
forgetTaskCompleteTemp({state}, task_id) {
if (task_id === true) {
state.taskCompleteTemps = [];
} else if (/^\d+$/.test(task_id)) {
state.taskCompleteTemps = state.taskCompleteTemps.filter(id => id != task_id);
}
},
/**
* 保存任务浏览记录
* @param dispatch
* @param task_id
*/
saveTaskBrowse({dispatch}, task_id) {
// 直接调用API保存到远程不维护本地缓存
dispatch('call', {
url: 'users/task/browse_save',
data: {
task_id: task_id
},
}).catch(error => {
console.warn('保存任务浏览历史失败:', error);
});
},
/**
* 获取任务浏览历史
* @param dispatch
* @param limit
*/
getTaskBrowseHistory({dispatch}, limit = 20) {
return dispatch('call', {
url: 'users/task/browse',
data: {
limit: limit
},
method: 'get',
});
},
/**
* 获取最近浏览历史
* @param dispatch
* @param params
* @returns {Promise<unknown>}
*/
getRecentBrowseHistory({dispatch}, params = {}) {
return dispatch('call', {
url: 'users/recent/browse',
data: params,
method: 'get',
});
},
/**
* 删除最近浏览记录
* @param dispatch
* @param id
* @returns {Promise<unknown>}
*/
removeRecentBrowseRecord({dispatch}, id) {
return dispatch('call', {
url: 'users/recent/delete',
data: {id},
method: 'post',
});
},
/**
* 任务默认时间
* @param state
* @param dispatch
* @param array
* @returns {Promise<unknown>}
*/
taskDefaultTime({state, dispatch}, array) {
return new Promise(async resolve => {
if ($A.isArray(array) && array.length === 2) {
if (/\s+(00:00|23:59)$/.test(array[0]) && /\s+(00:00|23:59)$/.test(array[1])) {
array[0] = await dispatch("taskDefaultStartTime", array[0])
array[1] = await dispatch("taskDefaultEndTime", array[1])
}
}
resolve(array)
});
},
/**
* 任务默认开始时间
* @param state
* @param value
* @returns {Promise<unknown>}
*/
taskDefaultStartTime({state}, value) {
return new Promise(resolve => {
if (/(\s|^)([0-2]\d):([0-5]\d)(:\d{1,2})*$/.test(value)) {
value = value.replace(/(\s|^)([0-2]\d):([0-5]\d)(:\d{1,2})*$/, "$1" + state.systemConfig.task_default_time[0])
}
resolve(value)
});
},
/**
* 任务默认结束时间
* @param state
* @param value
* @returns {Promise<unknown>}
*/
taskDefaultEndTime({state}, value) {
return new Promise(resolve => {
if (/(\s|^)([0-2]\d):([0-5]\d)(:\d{1,2})*$/.test(value)) {
value = value.replace(/(\s|^)([0-2]\d):([0-5]\d)(:\d{1,2})*$/, "$1" + state.systemConfig.task_default_time[1])
}
resolve(value)
});
},
/**
* 更新任务模板
* @param state
* @param dispatch
* @param projectId
* @returns {Promise<void>}
*/
async updateTaskTemplates({state, dispatch}, projectId) {
const {data} = await dispatch("call", {
url: 'project/task/template_list',
data: {
project_id: projectId
},
})
state.taskTemplates = state.taskTemplates.filter(template => template.project_id !== projectId).concat(data || [])
},
/** *****************************************************************************************/
/** ************************************** 收藏 **********************************************/
/** *****************************************************************************************/
/**
* 检查收藏状态
* @param dispatch
* @param {object} params {type: 'task|project|file|message', id: number}
*/
checkFavoriteStatus({dispatch}, {type, id}) {
return dispatch('call', {
url: 'users/favorite/check',
data: {
type: type,
id: id
},
method: 'get',
});
},
/**
* 切换收藏状态
* @param dispatch
* @param {object} params {type: 'task|project|file|message', id: number}
*/
toggleFavorite({dispatch}, {type, id}) {
return new Promise((resolve, reject) => {
dispatch('call', {
url: 'users/favorite/toggle',
data: {
type: type,
id: id
},
method: 'post',
}).then(result => {
resolve(result)
//
const {data, msg} = result
if (!data.favorited) {
$A.messageSuccess(msg);
return
}
$A.Message.success({
duration: 5,
render: h => {
return h('span', [
h('span', $A.L(msg)),
h('a', {
style: {
marginLeft: '8px'
},
on: {
click: () => {
const currentRemark = data && typeof data.remark === 'string' ? data.remark : '';
$A.modalInput({
title: $A.L('修改备注'),
placeholder: $A.L('请输入修改备注'),
okText: $A.L('保存'),
value: currentRemark,
onOk: (inputValue) => {
const remark = typeof inputValue === 'string' ? inputValue.trim() : '';
if (!remark) {
return $A.L('请输入修改备注');
}
return new Promise((resolveRemark, rejectRemark) => {
dispatch('call', {
url: 'users/favorite/remark',
data: {
type,
id,
remark,
},
method: 'post',
}).then(({msg}) => {
$A.messageSuccess(msg || $A.L('操作成功'));
resolveRemark();
}).catch(({msg}) => {
rejectRemark(msg || $A.L('操作失败'));
});
});
}
});
}
}
}, $A.L('修改备注')),
])
}
});
}).catch(({msg}) => {
$A.modalError(msg || this.$L('操作失败'));
reject()
});
});
},
/**
* 批量检查收藏状态
* @param dispatch
* @param {object} params {type: 'task|project|file|message', items: array}
*/
checkFavoritesStatus({dispatch}, {type, items}) {
if (!Array.isArray(items) || items.length === 0) {
return Promise.resolve([]);
}
// 批量检查收藏状态
const promises = items.map(item => {
return dispatch('checkFavoriteStatus', {type, id: item.id})
.then(({data}) => ({
id: item.id,
favorited: data.favorited || false
}))
.catch(() => ({
id: item.id,
favorited: false
}));
});
return Promise.all(promises);
},
/** *****************************************************************************************/
/** ************************************** 会话 **********************************************/
/** *****************************************************************************************/
/**
* 更新会话数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
saveDialog({commit, state, dispatch}, data) {
$A.syncDispatch("saveDialog", data)
//
if ($A.isArray(data)) {
data.forEach((dialog) => {
dispatch("saveDialog", dialog)
});
} else if ($A.isJson(data)) {
data.id = parseInt(data.id)
const index = state.cacheDialogs.findIndex(({id}) => id == data.id);
let lastForce = false
if (typeof data.last_force !== "undefined") {
lastForce = true
delete data.last_force
}
if (index > -1) {
const original = state.cacheDialogs[index]
const nowTime = data.user_ms
const originalTime = original.user_ms || 0
if (nowTime < originalTime) {
typeof data.unread !== "undefined" && delete data.unread
typeof data.unread_one !== "undefined" && delete data.unread_one
typeof data.mention !== "undefined" && delete data.mention
typeof data.mention_ids !== "undefined" && delete data.mention_ids
}
if (data.unread_one) {
if (state.dialogMsgs.find(m => m.id == data.unread_one)?.read_at) {
delete data.unread_one
}
}
if (data.mention_ids) {
data.mention_ids = data.mention_ids.filter(id => {
return !state.dialogMsgs.find(m => m.id == id)?.read_at
})
}
if (!lastForce
&& data.last_at
&& original.last_at
&& $A.dayjs(data.last_at) < $A.dayjs(original.last_at)) {
delete data.last_at
delete data.last_msg
}
commit("dialog/splice", {index, data: Object.assign({}, original, data)})
} else {
commit("dialog/push", data)
}
}
},
/**
* 更新会话最后消息
* @param state
* @param dispatch
* @param data
*/
updateDialogLastMsg({state, dispatch}, data) {
$A.syncDispatch("updateDialogLastMsg", data)
//
if ($A.isArray(data)) {
data.forEach((msg) => {
dispatch("updateDialogLastMsg", msg)
});
} else if ($A.isJson(data)) {
const index = state.cacheDialogs.findIndex(({id}) => id == data.dialog_id);
if (index > -1) {
const updateData = {
id: data.dialog_id,
last_msg: data,
last_at: data.created_at || $A.daytz().format("YYYY-MM-DD HH:mm:ss")
}
if (data.mtype == 'tag') {
updateData.has_tag = true;
}
if (data.mtype == 'todo') {
updateData.has_todo = true;
}
if (data.mtype == 'image') {
updateData.has_image = true;
}
if (data.mtype == 'file') {
updateData.has_file = true;
}
if (data.link) {
updateData.has_link = true;
}
dispatch("saveDialog", updateData);
} else {
dispatch("getDialogOne", data.dialog_id).catch(() => {})
}
}
},
/**
* 获取会话列表(避免重复获取)
* @param state
* @param dispatch
* @returns {Promise<unknown>}
*/
getDialogAuto({state, dispatch}) {
return new Promise(function (resolve, reject) {
if (state.loadDialogAuto) {
reject({msg: 'Loading'});
return
}
setTimeout(_ => {
state.loadDialogs++;
}, 2000)
state.loadDialogAuto = true
dispatch("getDialogs")
.then(resolve)
.catch(reject)
.finally(_ => {
state.loadDialogs--;
state.loadDialogAuto = false
})
})
},
/**
* 获取会话列表
* @param state
* @param dispatch
* @param requestData
* @returns {Promise<unknown>}
*/
getDialogs({state, dispatch}, requestData) {
return new Promise(function (resolve, reject) {
if (state.userId === 0) {
state.cacheDialogs = [];
reject({msg: 'Parameter error'});
return;
}
if (!$A.isJson(requestData)) {
requestData = {}
}
if (typeof requestData.page === "undefined") {
requestData.page = 1
}
if (typeof requestData.pagesize === "undefined") {
requestData.pagesize = 20
}
const callData = $callData('dialogs', requestData, state)
//
dispatch("call", {
url: 'dialog/lists',
data: callData.get()
}).then(({data}) => {
dispatch("saveDialog", data.data);
callData.save(data).then(ids => dispatch("forgetDialog", {id: ids}))
//
if (data.current_page === 1) {
dispatch("getDialogLatestMsgs", data.data.map(({id}) => id))
}
//
if (data.next_page_url && data.current_page < 5) {
requestData.page++
dispatch("getDialogs", requestData).then(resolve).catch(reject)
} else {
resolve()
dispatch("getDialogBeyonds")
}
}).catch(e => {
console.warn(e);
reject(e)
});
});
},
/**
* 获取超期未读会话
* @param state
* @param dispatch
* @returns {Promise<unknown>}
*/
async getDialogBeyonds({state, dispatch}) {
const key = await $A.IDBString("dialogBeyond")
const val = $A.daytz().format("YYYY-MM-DD HH")
if (key == val) {
return // 一小时取一次
}
await $A.IDBSet("dialogBeyond", val)
//
const filter = (func) => {
return state.cacheDialogs
.filter(func)
.sort((a, b) => {
return $A.sortDay(a.last_at, b.last_at);
})
.find(({id}) => id > 0)
}
const unreadDialog = filter(({unread, last_at}) => {
return unread > 0 && last_at
});
const todoDialog = filter(({todo_num, last_at}) => {
return todo_num > 0 && last_at
});
//
dispatch("call", {
url: 'dialog/beyond',
data: {
unread_at: unreadDialog ? unreadDialog.last_at : $A.dayjs().unix(),
todo_at: todoDialog ? todoDialog.last_at : $A.dayjs().unix()
}
}).then(({data}) => {
dispatch("saveDialog", data);
});
},
/**
* 获取单个会话信息
* @param state
* @param dispatch
* @param dialogId
* @returns {Promise<unknown>}
*/
getDialogOne({state, dispatch}, dialogId) {
return new Promise(function (resolve, reject) {
if ($A.runNum(dialogId) === 0) {
reject({msg: 'Parameter error'});
return;
}
dispatch("call", {
url: 'dialog/one',
data: {
dialog_id: dialogId,
},
}).then(result => {
dispatch("saveDialog", result.data);
resolve(result);
}).catch(e => {
console.warn(e);
reject(e);
});
});
},
/**
* 获取会话待办
* @param commit
* @param state
* @param dispatch
* @param dialogId
*/
getDialogTodo({commit, state, dispatch}, dialogId) {
dispatch("call", {
url: 'dialog/todo',
data: {
dialog_id: dialogId,
},
}).then(({data}) => {
if ($A.arrayLength(data) > 0) {
if (dialogId > 0) {
dispatch("saveDialog", {
id: dialogId,
todo_num: $A.arrayLength(data)
});
commit("dialog/todo/save", state.dialogTodos.filter(item => item.dialog_id != dialogId))
}
dispatch("saveDialogTodo", data)
} else {
if (dialogId > 0) {
dispatch("saveDialog", {
id: dialogId,
todo_num: 0
});
}
}
}).catch(console.warn);
},
/**
* 获取会话消息置顶
* @param state
* @param dispatch
* @param dialogId
*/
getDialogMsgTop({state, dispatch}, dialogId) {
dispatch("call", {
url: 'dialog/msg/topinfo',
data: {
dialog_id: dialogId,
},
}).then(({data}) => {
if ($A.isJson(data)) {
dispatch("saveDialogMsgTop", data)
}
}).catch(console.warn);
},
/**
* 打开会话
* @param state
* @param dispatch
* @param dialogId
* @returns {Promise<unknown>}
*/
openDialog({state, dispatch}, dialogId) {
return new Promise(async (resolve, reject) => {
let singleWindow,
searchMsgId,
dialogMsgId;
if ($A.isJson(dialogId)) {
singleWindow = dialogId.single;
searchMsgId = dialogId.search_msg_id;
dialogMsgId = dialogId.dialog_msg_id;
dialogId = dialogId.dialog_id;
}
singleWindow = typeof singleWindow === "boolean" ? singleWindow : $A.isSubElectron;
searchMsgId = /^\d+$/.test(searchMsgId) ? parseInt(searchMsgId) : 0;
dialogMsgId = /^\d+$/.test(dialogMsgId) ? parseInt(dialogMsgId) : 0;
dialogId = /^\d+$/.test(dialogId) ? parseInt(dialogId) : 0;
//
if (dialogId > 0 && state.cacheDialogs.findIndex(item => item.id == dialogId) === -1) {
dispatch("showSpinner", 300)
try {
await dispatch("getDialogOne", dialogId)
} catch (e) {
reject(e);
return;
} finally {
dispatch("hiddenSpinner")
}
}
//
if ($A.Electron && singleWindow) {
dispatch('openDialogNewWindow', dialogId);
resolve()
return
}
//
if (state.dialogModalShow) {
// 已打开对话时将对话窗口置顶
emitter.emit('handleMoveTop', 'dialogModal');
} else if (state.dialogId === dialogId) {
// 如果对话窗口未打开则清除当前对话ID避免类此已经在消息中打开项目对话时无法在其他地方打开项目对话
state.dialogId = 0;
}
//
requestAnimationFrame(_ => {
state.dialogSearchMsgId = searchMsgId;
state.dialogMsgId = dialogMsgId;
state.dialogId = dialogId;
resolve()
})
})
},
/**
* 打开会话(打开机器人会话推送 webhook
* @param state
* @param dispatch
* @param dialogId
* @returns {Promise<unknown>}
*/
openDialogWebhook({state, dispatch}, dialogId) {
return new Promise((resolve, reject) => {
const dialog = state.cacheDialogs.find(item => {
if (item.type !== 'user') {
return false
}
return item.id === dialogId
});
if (dialog && dialog.bot === 1) {
dispatch("call", {
url: 'dialog/open/webhook',
data: {
dialog_id: dialogId,
},
}).catch(e => {
console.warn(e);
reject(e);
})
}
});
},
/**
* 打开会话通过会员ID打开个人会话
* @param state
* @param dispatch
* @param userid
*/
openDialogUserid({state, dispatch}, userid) {
return new Promise((resolve, reject) => {
const dialog = state.cacheDialogs.find(item => {
if (item.type !== 'user' || !item.dialog_user) {
return false
}
return item.dialog_user.userid === userid
});
if (dialog) {
return dispatch("openDialog", dialog.id).then(resolve).catch(reject)
}
dispatch("call", {
url: 'dialog/open/user',
data: {
userid,
},
spinner: 600
}).then(async ({data}) => {
dispatch("saveDialog", data);
dispatch("openDialog", data.id).then(resolve).catch(reject)
}).catch(e => {
console.warn(e);
reject(e);
})
});
},
/**
* 打开会话(客户端新窗口)
* @param state
* @param dispatch
* @param dialogId
* @returns {Promise<void>}
*/
openDialogNewWindow({state, dispatch}, dialogId) {
const dialogData = state.cacheDialogs.find(({id}) => id === dialogId) || {}
dispatch('openChildWindow', {
name: `dialog-${dialogId}`,
path: `/single/dialog/${dialogId}`,
force: false,
config: {
title: dialogData.name,
parent: null,
width: Math.min(window.screen.availWidth, 1024),
height: Math.min(window.screen.availHeight, 768),
},
});
},
/**
* 忘记对话数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
forgetDialog({commit, state, dispatch}, data) {
$A.syncDispatch("forgetDialog", data)
//
const ids = $A.isArray(data.id) ? data.id : [data.id];
ids.some(id => {
if ($A.isJson(id)) {
id = id.id
}
const index = state.cacheDialogs.findIndex(dialog => dialog.id == id);
if (index > -1) {
dispatch("forgetDialogMsg", {id: state.dialogMsgs.filter(item => item.dialog_id == data.id).map(item => item.id)})
commit("dialog/splice", {index})
}
})
if (ids.includes(state.dialogId)) {
state.dialogId = 0
}
},
/**
* 保存正在会话
* @param commit
* @param state
* @param dispatch
* @param data {uid, dialog_id}
*/
saveInDialog({commit, state, dispatch}, data) {
$A.syncDispatch("saveInDialog", data)
//
const index = state.dialogIns.findIndex(item => item.uid == data.uid);
if (index > -1) {
commit("dialog/in/splice", {index, data: Object.assign({}, state.dialogIns[index], data)});
} else {
commit("dialog/in/push", data);
}
// 会话消息总数量大于5000时只保留最近打开的50个会话
const msg_max = 5000
const retain_num = 500
commit('dialog/history/save', state.dialogHistory.filter(id => id != data.dialog_id))
commit('dialog/history/push', data.dialog_id)
if (state.dialogMsgs.length > msg_max && state.dialogHistory.length > retain_num) {
const historys = state.dialogHistory.slice().reverse()
const newIds = []
const delIds = []
historys.forEach(id => {
if (newIds.length < retain_num || state.dialogIns.findIndex(item => item.dialog_id == id) > -1) {
newIds.push(id)
} else {
delIds.push(id)
}
})
if (delIds.length > 0) {
commit("message/save", state.dialogMsgs.filter(item => !delIds.includes(item.dialog_id)))
}
commit('dialog/history/save', newIds)
}
},
/**
* 忘记正在会话
* @param state
* @param commit
* @param data
*/
forgetInDialog({state, commit}, data) {
$A.syncDispatch("forgetInDialog", data)
//
const index = state.dialogIns.findIndex(item => item.uid == data.uid);
if (index > -1) {
commit("dialog/in/splice", {index})
}
},
/**
* 关闭对话
* @param state
* @param commit
* @param data
*/
closeDialog({state, commit}, data) {
$A.syncDispatch("closeDialog", data)
// 判断参数
if (!/^\d+$/.test(data.id)) {
return
}
// 更新草稿标签
commit('draft/tag', data.id)
// 关闭会话后删除会话超限消息
const msgs = state.dialogMsgs.filter(item => item.dialog_id == data.id)
if (msgs.length > state.dialogMsgKeep) {
const delIds = msgs.sort((a, b) => b.id - a.id).splice(state.dialogMsgKeep).map(item => item.id)
commit("message/save", state.dialogMsgs.filter(item => !delIds.includes(item.id)))
}
},
/**
* 清理会话本地缓存
* @param state
* @param commit
* @param data
*/
clearDialogMsgs({state, commit}, data) {
$A.syncDispatch("clearDialogMsgs", data)
// 判断参数
if (!/^\d+$/.test(data.id)) {
return
}
// 清理会话本地缓存
commit("message/save", state.dialogMsgs.filter(item => item.dialog_id != data.id))
},
/**
* 保存待办数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
saveDialogTodo({commit, state, dispatch}, data) {
$A.syncDispatch("saveDialogTodo", data)
//
if ($A.isArray(data)) {
data.forEach(item => {
dispatch("saveDialogTodo", item)
});
} else if ($A.isJson(data)) {
const index = state.dialogTodos.findIndex(item => item.id == data.id);
if (index > -1) {
commit('dialog/todo/splice', {index, data: Object.assign({}, state.dialogTodos[index], data)});
} else {
commit('dialog/todo/push', data)
}
}
},
/**
* 忘记待办数据
* @param state
* @param commit
* @param data
*/
forgetDialogTodoForMsgId({state, commit}, data) {
$A.syncDispatch("forgetDialogTodoForMsgId", data)
//
const index = state.dialogTodos.findIndex(item => item.msg_id == data.id);
if (index > -1) {
commit('dialog/todo/splice', {index})
}
},
/**
* 保存置顶数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
saveDialogMsgTop({commit, state, dispatch}, data) {
$A.syncDispatch("saveDialogMsgTop", data)
//
if ($A.isArray(data)) {
data.forEach(item => {
dispatch("saveDialogMsgTop", item)
});
} else if ($A.isJson(data)) {
commit('dialog/msg/top/save', state.dialogMsgTops.filter(item => item.dialog_id != data.dialog_id))
const index = state.dialogMsgTops.findIndex(item => item.id == data.id);
if (index > -1) {
commit('dialog/msg/top/splice', {index, data: Object.assign({}, state.dialogMsgTops[index], data)});
} else {
commit('dialog/msg/top/push', data)
}
}
},
/**
* 忘记消息置顶数据
* @param state
* @param commit
* @param data
*/
forgetDialogMsgTopForMsgId({state, commit}, data) {
$A.syncDispatch("forgetDialogMsgTopForMsgId", data)
//
const index = state.dialogMsgTops.findIndex(item => item.msg_id == data.id);
if (index > -1) {
commit('dialog/msg/top/splice', {index})
}
},
/**
* 保存草稿
* @param commit
* @param id
* @param content
* @param immediate
*/
saveDialogDraft({commit}, {id, content, immediate = false}) {
if ($A.isSubElectron) {
dialogDraftState.subTemp = {id, content, immediate: true}
return
}
$A.syncDispatch("saveDialogDraft", {id, content, immediate})
// 清除已有的计时器
if (dialogDraftState.timer[id]) {
clearTimeout(dialogDraftState.timer[id])
delete dialogDraftState.timer[id]
}
// 创建新的计时器
dialogDraftState.timer[id] = setTimeout(() => {
commit('draft/set', {id, content})
delete dialogDraftState.timer[id]
}, (immediate || !content) ? 0 : 600)
},
/**
* 保存引用
* @param commit
* @param data {id, type, content}
*/
saveDialogQuote({commit}, data) {
commit('quote/set', data)
},
/**
* 移除引用
* @param commit
* @param id
*/
removeDialogQuote({commit}, id) {
commit('quote/remove', id)
},
/** *****************************************************************************************/
/** ************************************** 消息 **********************************************/
/** *****************************************************************************************/
/**
* 更新消息数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
saveDialogMsg({commit, state, dispatch}, data) {
$A.syncDispatch("saveDialogMsg", data)
//
if ($A.isArray(data)) {
data.forEach((msg) => {
dispatch("saveDialogMsg", msg)
});
return
}
//
if (data.type == "notice") {
data.estimateSize = 42;
}
const index = state.dialogMsgs.findIndex(({id}) => id == data.id);
if (index > -1) {
const original = state.dialogMsgs[index]
if (original.read_at) {
delete data.read_at
}
data = Object.assign({}, original, data)
commit("message/splice", {index, data})
} else {
commit("message/push", data)
}
//
const dialog = state.cacheDialogs.find(({id}) => id == data.dialog_id);
if (dialog) {
let isUpdate = false
if (!data.read_at
&& data.userid != state.userId
&& !state.dialogIns.find(({dialog_id}) => dialog_id == dialog.id)) {
if (dialog.unread_one) {
dialog.unread_one = Math.min(dialog.unread_one, data.id)
} else {
dialog.unread_one = data.id
}
isUpdate = true
}
if (dialog.last_msg && dialog.last_msg.id == data.id) {
dialog.last_msg = Object.assign({}, dialog.last_msg, data)
isUpdate = true
}
if (isUpdate) {
dispatch("saveDialog", dialog)
}
}
},
/**
* 忘记消息数据
* @param commit
* @param state
* @param dispatch
* @param data
*/
forgetDialogMsg({commit, state, dispatch}, data) {
$A.syncDispatch("forgetDialogMsg", data)
//
const ids = $A.isArray(data.id) ? data.id : [data.id];
ids.some(id => {
const index = state.dialogMsgs.findIndex(item => item.id == id);
if (index > -1) {
const msgData = state.dialogMsgs[index]
dispatch("decrementMsgReplyNum", {id: msgData.reply_id});
dispatch("audioStop", $A.getObject(msgData, 'msg.path'));
commit("message/splice", {index})
}
})
dispatch("forgetDialogTodoForMsgId", data)
dispatch("forgetDialogMsgTopForMsgId", data)
},
/**
* 获取会话消息
* @param commit
* @param state
* @param dispatch
* @param getters
* @param data {dialog_id, msg_id, ?msg_type, ?position_id, ?prev_id, ?next_id, ?save_before, ?save_after, ?clear_before, ?spinner}
* @returns {Promise<unknown>}
*/
getDialogMsgs({commit, state, dispatch, getters}, data) {
return new Promise((resolve, reject) => {
let saveBefore = _ => {}
let saveAfter = _ => {}
let clearBefore = false
let spinner = false
if (typeof data.save_before !== "undefined") {
saveBefore = typeof data.save_before === "function" ? data.save_before : _ => {}
delete data.save_before
}
if (typeof data.save_after !== "undefined") {
saveAfter = typeof data.save_after === "function" ? data.save_after : _ => {}
delete data.save_after
}
if (typeof data.clear_before !== "undefined") {
clearBefore = typeof data.clear_before === "boolean" ? data.clear_before : false
delete data.clear_before
}
if (typeof data.spinner !== "undefined") {
spinner = data.spinner
delete data.spinner
}
//
const loadKey = `msg::${data.dialog_id}-${data.msg_id}-${data.msg_type || ''}`
if (getters.isLoad(loadKey)) {
reject({msg: 'Loading'});
return
}
dispatch("setLoad", loadKey)
//
if (clearBefore) {
commit("message/save", state.dialogMsgs.filter(({dialog_id}) => dialog_id !== data.dialog_id))
}
//
data.pagesize = 25;
//
dispatch("call", {
url: 'dialog/msg/list',
data,
spinner,
complete: _ => dispatch("cancelLoad", loadKey)
}).then(result => {
saveBefore()
const resData = result.data;
if ($A.isJson(resData.dialog)) {
const ids = resData.list.map(({id}) => id)
commit("message/save", state.dialogMsgs.filter(item => {
return item.dialog_id != data.dialog_id || ids.includes(item.id) || $A.dayjs(item.created_at).unix() >= resData.time
}))
dispatch("saveDialog", resData.dialog)
}
if ($A.isArray(resData.todo)) {
commit("dialog/todo/save", state.dialogTodos.filter(item => item.dialog_id != data.dialog_id))
dispatch("saveDialogTodo", resData.todo)
}
if ($A.isJson(resData.top)) {
dispatch("saveDialogMsgTop", resData.top)
}
//
dispatch("saveDialogMsg", resData.list)
resolve(result)
saveAfter()
}).catch(e => {
console.warn(e);
reject(e)
}).finally(_ => {
// 将原数据清除,避免死循环
if (data.prev_id) {
const prevMsg = state.dialogMsgs.find(({prev_id}) => prev_id == data.prev_id)
if (prevMsg) {
prevMsg.prev_id = 0
}
}
if (data.next_id) {
const nextMsg = state.dialogMsgs.find(({next_id}) => next_id == data.next_id)
if (nextMsg) {
nextMsg.next_id = 0
}
}
});
});
},
/**
* 获取最新消息
* @param state
* @param dispatch
* @param dialogIds
* @returns {Promise<unknown>}
*/
getDialogLatestMsgs({state, dispatch}, dialogIds = []) {
return new Promise(function (resolve, reject) {
if (state.userId === 0) {
reject({msg: 'Parameter error'});
return;
}
if (!$A.isArray(dialogIds)) {
reject({msg: 'Parameter is not array'});
return
}
if (dialogIds.length === 0) {
resolve()
return
}
//
const wait = dialogIds.slice(5)
const dialogs = dialogIds.slice(0, 5)
dispatch("call", {
method: 'post',
url: 'dialog/msg/latest',
data: {
dialogs: dialogs.map(id => {
return {
id,
latest_id: state.dialogMsgs.sort((a, b) => {
return b.id - a.id
}).find(({dialog_id}) => dialog_id == id)?.id || 0
}
}),
take: state.dialogMsgKeep
},
}).then(({data}) => {
dispatch("saveDialogMsg", data.data);
if (wait.length > 0) {
dispatch("getDialogLatestMsgs", wait).then(resolve).catch(reject)
} else {
resolve()
}
}).catch(e => {
reject(e)
});
})
},
/**
* 发送已阅消息
* @param state
* @param dispatch
* @param data
*/
dialogMsgRead({state, dispatch}, data) {
if ($A.isJson(data)) {
if (data.userid == state.userId) return;
if (data.read_at) return;
data.read_at = $A.daytz().format("YYYY-MM-DD HH:mm:ss");
state.readWaitData[data.id] = state.readWaitData[data.id] || 0
//
const dialog = state.cacheDialogs.find(({id}) => id == data.dialog_id);
if (dialog) {
let mark = false
if (data.id == dialog.unread_one) {
dialog.unread_one = 0
mark = true
}
if ($A.isArray(dialog.mention_ids)) {
const index = dialog.mention_ids.findIndex(id => id == data.id)
if (index > -1) {
dialog.mention_ids.splice(index, 1)
mark = true
}
}
if (mark) {
dispatch("saveDialog", dialog)
state.readWaitData[data.id] = data.dialog_id
}
}
}
clearTimeout(state.readTimeout);
state.readTimeout = setTimeout(_ => {
state.readTimeout = null;
//
if (state.userId === 0) {
data && (data.read_at = null);
return;
}
const entries = Object.entries(state.readWaitData);
if (entries.length === 0) {
data && (data.read_at = null);
return
}
const ids = Object.fromEntries(entries.slice(0, 100));
state.readWaitData = Object.fromEntries(entries.slice(100));
//
dispatch("call", {
method: 'post',
url: 'dialog/msg/read',
data: {
id: ids
}
}).then(({data}) => {
Object.entries(ids)
.filter(([_, dialogId]) => /^\d+$/.test(dialogId))
.forEach(([msdId, dialogId]) => {
state.dialogMsgs
.filter(item => item.dialog_id == dialogId && item.id >= msdId)
.forEach(item => {
item.read_at = $A.daytz().format("YYYY-MM-DD HH:mm:ss");
});
});
dispatch("saveDialog", data)
}).catch(_ => {
Object.keys(ids)
.forEach(id => {
const msg = state.dialogMsgs.find(item => item.id == id);
if (msg) msg.read_at = null;
});
state.readWaitData = Object.assign(state.readWaitData, ids);
}).finally(_ => {
state.readLoadNum++
});
}, 50);
},
/**
* 消息去除点
* @param state
* @param dispatch
* @param data
*/
dialogMsgDot({state, dispatch}, data) {
if (!$A.isJson(data)) {
return;
}
if (!data.dot) {
return;
}
data.dot = 0;
//
dispatch("call", {
url: 'dialog/msg/dot',
data: {
id: data.id
}
}).then(({data}) => {
dispatch("saveDialog", data)
});
},
/**
* 标记已读、未读
* @param state
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
dialogMsgMark({state, dispatch}, data) {
return new Promise((resolve, reject) => {
dispatch("call", {
url: 'dialog/msg/mark',
data,
}).then(result => {
if (typeof data.after_msg_id !== "undefined") {
state.dialogMsgs.some(item => {
if (item.dialog_id == data.dialog_id && item.id >= data.after_msg_id) {
item.read_at = $A.daytz().format("YYYY-MM-DD HH:mm:ss")
}
})
}
dispatch("saveDialog", result.data)
resolve(result)
}).catch(e => {
reject(e)
})
})
},
/**
* 消息流订阅
* @param state
* @param dispatch
* @param streamUrl
*/
streamMsgSubscribe({state, dispatch}, streamUrl) {
if (!/^https?:\/\//i.test(streamUrl)) {
streamUrl = $A.mainUrl(streamUrl.substring(1))
}
if (state.dialogSseList.find(item => item.streamUrl == streamUrl)) {
return
}
const sse = new SSEClient(streamUrl)
sse.subscribe(['append', 'replace', 'done'], (type, e) => {
switch (type) {
case 'append':
case 'replace':
const data = $A.jsonParse(e.data);
dispatch("streamMsgData", {
type,
id: e.lastEventId,
text: data.content
})
break;
case 'done':
const index = state.dialogSseList.findIndex(item => sse === item.sse)
if (index > -1) {
state.dialogSseList.splice(index, 1)
}
sse.unsunscribe()
break;
}
})
state.dialogSseList.push({sse, streamUrl, time: $A.dayjs().unix()})
if (state.dialogSseList.length > 10) {
state.dialogSseList.shift().sse.close()
}
},
/**
* 消息流数据
* @param state
* @param data
*/
streamMsgData({state}, data) {
$A.syncDispatch("streamMsgData", data)
emitter.emit('streamMsgData', data);
},
/**
* 保存翻译
* @param state
* @param data {key, content, language}
*/
saveTranslation({state}, data) {
if (!$A.isJson(data)) {
return
}
const translation = state.cacheTranslations.find(item => item.key == data.key && item.language == data.language)
if (translation) {
translation.content = data.content
} else {
const label = languageList[data.language] || data.language
state.cacheTranslations.push(Object.assign(data, {label}))
}
$A.IDBSave("cacheTranslations", state.cacheTranslations.slice(-200))
},
/**
* 删除翻译
* @param state
* @param key
*/
removeTranslation({state}, key) {
state.cacheTranslations = state.cacheTranslations.filter(item => item.key != key)
$A.IDBSave("cacheTranslations", state.cacheTranslations.slice(-200))
},
/**
* 设置翻译语言
* @param state
* @param language
*/
setTranslationLanguage({state}, language) {
state.cacheTranslationLanguage = language
$A.IDBSave('cacheTranslationLanguage', language);
},
/**
* 设置语音转文字语言
* @param state
* @param language
*/
setTranscriptionLanguage({state}, language) {
state.cacheTranscriptionLanguage = language
$A.IDBSave('cacheTranscriptionLanguage', language);
},
/** *****************************************************************************************/
/** ************************************* loads *********************************************/
/** *****************************************************************************************/
/**
* 设置等待
* @param state
* @param dispatch
* @param key
*/
setLoad({state, dispatch}, key) {
if ($A.isJson(key)) {
setTimeout(_ => {
dispatch("setLoad", key.key)
}, key.delay || 0)
return;
}
const load = state.loads.find(item => item.key == key)
if (!load) {
state.loads.push({key, num: 1})
} else {
load.num++;
}
},
/**
* 取消等待
* @param state
* @param key
*/
cancelLoad({state}, key) {
const load = state.loads.find(item => item.key == key)
if (!load) {
state.loads.push({key, num: -1})
} else {
load.num--;
}
},
/**
* 显示全局浮窗加载器
* @param state
* @param delay
*/
showSpinner({state}, delay) {
const id = $A.randomString(6)
state.floatSpinnerTimer.push({
id,
timer: setTimeout(_ => {
state.floatSpinnerTimer = state.floatSpinnerTimer.filter(item => item.id !== id)
state.floatSpinnerLoad++
}, typeof delay === "number" ? delay : 0)
})
},
/**
* 隐藏全局浮窗加载器
* @param state
* @param dispatch
* @param delay
*/
hiddenSpinner({state, dispatch}, delay) {
if (typeof delay === "number") {
setTimeout(_ => {
dispatch("hiddenSpinner")
}, delay)
return
}
const item = state.floatSpinnerTimer.shift()
if (item) {
clearTimeout(item.timer)
} else {
state.floatSpinnerLoad--
}
},
/**
* 预览图片
* @param state
* @param data {{index: number | string, list: array} | string}
*/
previewImage({state}, data) {
if (!$A.isJson(data)) {
data = {index: 0, list: [data]}
}
data.list = data.list.map(item => {
if ($A.isJson(item)) {
item.src = $A.thumbRestore(item.src)
} else {
item = $A.thumbRestore(item)
}
return item
})
if (typeof data.index === "string") {
const current = $A.thumbRestore(data.index)
data.index = Math.max(0, data.list.findIndex(item => {
return $A.isJson(item) ? item.src == current : item == current
}))
}
state.previewImageIndex = data.index;
state.previewImageList = data.list;
},
/**
* 播放音频
* @param state
* @param dispatch
* @param src
*/
audioPlay({state, dispatch}, src) {
const old = document.getElementById("__audio_play_element__")
if (old) {
// 删除已存在
old.pause()
old.src = ""
old.parentNode.removeChild(old);
}
if (!src || src === state.audioPlaying) {
// 空地址或跟现在播放的地址一致时仅停止
state.audioPlaying = null
return
}
//
const audio = document.createElement("audio")
audio.id = state.audioPlayId = "__audio_play_element__"
audio.controls = false
audio.loop = false
audio.volume = 1
audio.src = state.audioPlaying = src
audio.onended = _ => {
dispatch("audioStop", audio.src)
}
document.body.appendChild(audio)
audio.play().then(_ => {})
},
/**
* 停止播放音频
* @param state
* @param src
*/
audioStop({state}, src) {
const old = document.getElementById("__audio_play_element__")
if (!old) {
return
}
if (old.src === src || src === true) {
old.pause()
old.src = ""
old.parentNode.removeChild(old);
state.audioPlaying = null
}
},
/** *****************************************************************************************/
/** *********************************** websocket *******************************************/
/** *****************************************************************************************/
/**
* 初始化 websocket
* @param state
* @param dispatch
*/
websocketConnection({state, dispatch}) {
clearTimeout(state.wsTimeout);
if (state.ws) {
state.ws.close();
state.ws = null;
}
if (state.userId === 0) {
return;
}
//
if (typeof window.wsInfo === "undefined") {
window.wsInfo = {
msgCount: 0,
repeatCount: 0,
lastTime: 0,
lastData: null,
}
}
//
let url = $A.mainUrl('ws');
url = url.replace("https://", "wss://");
url = url.replace("http://", "ws://");
url += `?action=web&token=${state.userToken}&language=${languageName}`;
//
const wgLog = $A.openLog;
const wsRandom = $A.randomString(16);
state.wsRandom = wsRandom;
//
state.ws = new WebSocket(url);
state.ws.onopen = async (e) => {
wgLog && console.log("[WS] Open", e, $A.daytz().format("YYYY-MM-DD HH:mm:ss"))
state.wsOpenNum++;
//
if (window.systemInfo.debug === "yes" || state.systemConfig.e2e_message !== 'open') {
return // 测试环境不发送加密信息
}
dispatch("websocketSend", {
type: 'encrypt',
data: {
type: 'pgp',
key: (await dispatch("pgpGetLocalKey")).publicKeyB64
}
})
};
state.ws.onclose = async (e) => {
wgLog && console.log("[WS] Close", e, $A.daytz().format("YYYY-MM-DD HH:mm:ss"))
state.ws = null;
//
clearTimeout(state.wsTimeout);
state.wsTimeout = setTimeout(() => {
wsRandom === state.wsRandom && dispatch('websocketConnection');
}, 3000);
};
state.ws.onerror = async (e) => {
wgLog && console.log("[WS] Error", e, $A.daytz().format("YYYY-MM-DD HH:mm:ss"))
state.ws = null;
//
clearTimeout(state.wsTimeout);
state.wsTimeout = setTimeout(() => {
wsRandom === state.wsRandom && dispatch('websocketConnection');
}, 3000);
};
state.ws.onmessage = async (e) => {
if ($A.inArray(state.routeName, ['preload', '404'])) {
wgLog && console.log("[WS] Preload", e);
return;
}
//
if ($A.dayjs().unix() - window.wsInfo.lastTime < 3 && window.wsInfo.lastData === e.data) {
console.log("[WS] Repeat", e, window.wsInfo.repeatCount);
window.wsInfo.repeatCount++
return
}
window.wsInfo.msgCount++
window.wsInfo.lastTime = $A.dayjs().unix()
window.wsInfo.lastData = e.data
//
wgLog && console.log("[WS] Message", e);
let result = $A.jsonParse(e.data);
if (result.type === "encrypt" && result.encrypted) {
result = await dispatch("pgpDecryptApi", result.encrypted)
}
const msgDetail = $A.formatMsgBasic(result);
const {type, msgId} = msgDetail;
switch (type) {
case "open":
$A.setSessionStorage("userWsFd", msgDetail.data.fd)
break
case "receipt":
typeof state.wsCall[msgId] === "function" && state.wsCall[msgId](msgDetail.body, true);
delete state.wsCall[msgId];
break
case "line":
emitter.emit('userActive', {type: 'line', data: msgDetail.data});
break
case "msgStream":
if ($A.isSubElectron) {
return
}
dispatch("streamMsgSubscribe", msgDetail.stream_url);
break
default:
msgId && dispatch("websocketSend", {type: 'receipt', msgId}).catch(_ => {});
emitter.emit('websocketMsg', msgDetail);
if ($A.isSubElectron) {
return
}
switch (type) {
/**
* 聊天会话消息
*/
case "dialog": // 更新会话
(function (msg) {
const {mode, silence, data} = msg;
const {dialog_id} = data;
switch (mode) {
case 'delete':
// 删除消息
dispatch("forgetDialogMsg", data)
//
const dialog = state.cacheDialogs.find(({id}) => id == dialog_id);
if (dialog) {
// 更新最后消息
const newData = {
id: dialog_id,
last_msg: data.last_msg,
last_at: data.last_msg ? data.last_msg.created_at : $A.daytz().format("YYYY-MM-DD HH:mm:ss"),
last_force: true,
}
if (data.update_read) {
// 更新未读数量
dispatch("call", {
url: 'dialog/msg/unread',
data: {dialog_id}
}).then(({data}) => {
dispatch("saveDialog", Object.assign(newData, data))
}).catch(() => {});
} else {
dispatch("saveDialog", newData)
}
}
break;
case 'add':
case 'chat':
const isAdd = mode === "add";
if (!state.dialogMsgs.find(({id}) => id == data.id)) {
// 新增任务消息数量
dispatch("increaseTaskMsgNum", {id: data.dialog_id});
// 新增回复数量
dispatch("increaseMsgReplyNum", {id: data.reply_id});
//
if (isAdd) {
if (data.userid !== state.userId) {
// 更新对话新增未读数
const dialog = state.cacheDialogs.find(({id}) => id == dialog_id);
if (dialog) {
const newData = {
id: dialog_id,
unread: dialog.unread + 1,
mention: dialog.mention,
user_at: data.user_at,
user_ms: data.user_ms,
}
if (data.mention) {
newData.mention++;
}
dispatch("saveDialog", newData)
}
}
emitter.emit('dialogMsgPush', {silence, data});
}
}
const saveMsg = (data, count) => {
if (count > 5 || state.dialogMsgs.find(({id}) => id == data.id)) {
// 更新消息列表
dispatch("saveDialogMsg", data)
// 更新最后消息
isAdd && dispatch("updateDialogLastMsg", data);
return;
}
setTimeout(() => saveMsg(data, count + 1), 50);
}
saveMsg(data, 0);
break;
case 'update':
case 'readed':
const updateMsg = (data, count) => {
if (state.dialogMsgs.find(({id}) => id == data.id)) {
dispatch("saveDialogMsg", data)
// 更新待办
if (typeof data.todo !== "undefined") {
dispatch("getDialogTodo", dialog_id)
}
return;
}
if (count <= 5) {
setTimeout(_ => {
updateMsg(data, ++count)
}, 500);
}
}
updateMsg(data, 0);
break;
case 'groupAdd':
case 'groupJoin':
case 'groupRestore':
// 群组添加、加入、恢复
dispatch("getDialogOne", data.id).catch(() => {})
break;
case 'groupUpdate':
// 群组更新
if (state.cacheDialogs.find(({id}) => id == data.id)) {
dispatch("saveDialog", data)
}
break;
case 'groupExit':
case 'groupDelete':
// 群组退出、解散
dispatch("forgetDialog", data)
break;
case 'updateTopMsg':
// 更新置顶
dispatch("saveDialog", {
id: data.dialog_id,
top_msg_id: data.top_msg_id,
top_userid: data.top_userid
})
dispatch("getDialogMsgTop", dialog_id)
break;
}
})(msgDetail);
break;
/**
* 项目消息
*/
case "project":
(function (msg) {
const {action, data} = msg;
switch (action) {
case 'add':
case 'update':
case 'recovery':
dispatch("saveProject", data)
break;
case 'detail':
dispatch("getProjectOne", data.id).catch(() => {})
dispatch("getTaskForProject", data.id).catch(() => {})
break;
case 'delete':
case 'archived':
dispatch("forgetProject", data);
break;
case 'sort':
dispatch("getTaskForProject", data.id).catch(() => {})
break;
}
})(msgDetail);
break;
/**
* 任务列表消息
*/
case "projectColumn":
(function (msg) {
const {action, data} = msg;
switch (action) {
case 'add':
case 'update':
case 'recovery':
dispatch("saveColumn", data)
break;
case 'delete':
dispatch("forgetColumn", data)
break;
}
})(msgDetail);
break;
/**
* 任务消息
*/
case "projectTask":
(function (msg) {
const {action, data} = msg;
switch (action) {
case 'add':
case 'restore': // 恢复(删除)
dispatch("addTaskSuccess", data)
break;
case 'update':
case 'archived': // 归档
case 'recovery': // 恢复(归档)
dispatch("saveTask", data)
break;
case 'dialog':
dispatch("saveTask", data)
dispatch("getDialogOne", data.dialog_id).catch(() => {})
break;
case 'upload':
dispatch("getTaskFiles", data.task_id)
break;
case 'filedelete':
dispatch("forgetTaskFile", data.id)
break;
case 'delete':
dispatch("forgetTask", data)
break;
case 'relation':
emitter.emit('taskRelationUpdate', data.id)
break;
}
})(msgDetail);
break;
/**
* 文件消息
*/
case "file":
(function (msg) {
const {action, data} = msg;
switch (action) {
case 'add':
case 'update':
dispatch("saveFile", data);
break;
case 'delete':
dispatch("forgetFile", data);
break;
case 'compress':
dispatch("packProgress", data);
break;
}
})(msgDetail);
break;
/**
* 工作报告
*/
case "report":
(function ({action}) {
if (action == 'unreadUpdate') {
dispatch("getReportUnread", 1000)
}
})(msgDetail);
break;
/**
* 流程审批
*/
case "approve":
(function ({action}) {
if (action == 'unread') {
dispatch("getApproveUnread", 1000)
}
})(msgDetail);
break;
}
break
}
}
},
/**
* 发送 websocket 消息
* @param state
* @param params {type, data, callback}
* @returns {Promise<unknown>}
*/
websocketSend({state}, params) {
return new Promise((resolve, reject) => {
if (!$A.isJson(params)) {
reject()
return
}
const {type, data, callback} = params
let msgId = undefined
if (!state.ws) {
typeof callback === "function" && callback(null, false)
reject()
return
}
if (typeof callback === "function") {
msgId = type + '_' + $A.randomString(3)
state.wsCall[msgId] = callback
}
try {
state.ws?.send(JSON.stringify({type, msgId, data}))
resolve()
} catch (e) {
typeof callback === "function" && callback(null, false)
reject(e)
}
})
},
/**
* 记录 websocket 访问状态
* @param state
* @param dispatch
* @param path
*/
websocketPath({state, dispatch}, path) {
clearTimeout(state.wsPathTimeout);
state.wsPathValue = path;
state.wsPathTimeout = setTimeout(() => {
if (state.wsPathValue == path) {
dispatch("websocketSend", {type: 'path', data: {path}}).catch(_ => {});
}
}, 1000);
},
/**
* 关闭 websocket
* @param state
*/
websocketClose({state}) {
if (state.ws) {
state.ws.close();
state.ws = null;
}
},
/** *****************************************************************************************/
/** *************************************** pgp *********************************************/
/** *****************************************************************************************/
/**
* 创建密钥对
* @param state
* @returns {Promise<unknown>}
*/
pgpGenerate({state}) {
return new Promise(async resolve => {
const data = await openpgp.generateKey({
type: 'ecc',
curve: 'curve25519',
passphrase: state.clientId,
userIDs: [{name: 'doo', email: 'admin@admin.com'}],
})
data.publicKeyB64 = $urlSafe(data.publicKey.replace(/\s*-----(BEGIN|END) PGP PUBLIC KEY BLOCK-----\s*/g, ''))
resolve(data)
})
},
/**
* 获取密钥对(不存在自动创建)
* @param state
* @param dispatch
* @returns {Promise<unknown>}
*/
pgpGetLocalKey({state, dispatch}) {
return new Promise(async resolve => {
// 已存在
if (state.localKeyPair.privateKey) {
return resolve(state.localKeyPair)
}
// 避免重复生成
while (state.localKeyLock === true) {
await new Promise(r => setTimeout(r, 100));
}
if (state.localKeyPair.privateKey) {
return resolve(state.localKeyPair)
}
// 生成密钥对
state.localKeyLock = true
state.localKeyPair = await dispatch("pgpGenerate")
state.localKeyLock = false
resolve(state.localKeyPair)
})
},
/**
* 加密
* @param state
* @param dispatch
* @param data {message:any, ?publicKey:string}
* @returns {Promise<unknown>}
*/
pgpEncrypt({state, dispatch}, data) {
return new Promise(async resolve => {
if (!$A.isJson(data)) {
data = {message: data}
}
const message = data.message || data.text
const publicKeyArmored = data.publicKey || data.key || (await dispatch("pgpGetLocalKey")).publicKey
const encryptionKeys = await openpgp.readKey({armoredKey: publicKeyArmored})
//
const encrypted = await openpgp.encrypt({
message: await openpgp.createMessage({text: message}),
encryptionKeys,
})
resolve(encrypted)
})
},
/**
* 解密
* @param state
* @param dispatch
* @param data {encrypted:any, ?privateKey:string, ?passphrase:string}
* @returns {Promise<unknown>}
*/
pgpDecrypt({state, dispatch}, data) {
return new Promise(async resolve => {
if (!$A.isJson(data)) {
data = {encrypted: data}
}
const encrypted = data.encrypted || data.text
const privateKeyArmored = data.privateKey || data.key || (await dispatch("pgpGetLocalKey")).privateKey
const decryptionKeys = await openpgp.decryptKey({
privateKey: await openpgp.readPrivateKey({armoredKey: privateKeyArmored}),
passphrase: data.passphrase || state.clientId
})
//
const {data: decryptData} = await openpgp.decrypt({
message: await openpgp.readMessage({armoredMessage: encrypted}),
decryptionKeys
})
resolve(decryptData)
})
},
/**
* API加密
* @param state
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
pgpEncryptApi({state, dispatch}, data) {
return new Promise(resolve => {
data = $A.jsonStringify(data)
dispatch("pgpEncrypt", {
message: data,
publicKey: state.apiKeyData.key
}).then(data => {
resolve(data.replace(/\s*-----(BEGIN|END) PGP MESSAGE-----\s*/g, ''))
})
})
},
/**
* API解密
* @param state
* @param dispatch
* @param data
* @returns {Promise<unknown>}
*/
pgpDecryptApi({state, dispatch}, data) {
return new Promise(resolve => {
dispatch("pgpDecrypt", {
encrypted: "-----BEGIN PGP MESSAGE-----\n\n" + data + "\n-----END PGP MESSAGE-----"
}).then(data => {
resolve($A.jsonParse(data))
})
})
},
/** *****************************************************************************************/
/** *************************************** meeting *********************************************/
/** *****************************************************************************************/
/**
* 关闭会议窗口
* @param state
* @param type
*/
closeMeetingWindow({state}, type) {
state.meetingWindow = {
show: false,
type: type,
meetingid: 0
};
},
/**
* 显示会议窗口
* @param state
* @param data
*/
showMeetingWindow({state}, data) {
state.meetingWindow = Object.assign(data, {
show: data.type !== 'direct',
});
},
/** *****************************************************************************************/
/** ************************************ App Store ******************************************/
/** *****************************************************************************************/
/**
* 打开微应用
* @param state
* @param data
* - id 应用ID必须
* - name 应用名称(必须)
* - url 应用地址(必须)
* - url_type 地址类型(可选)
* - background 背景颜色(可选)
* - capsule 应用胶囊配置(可选)
* - transparent 是否透明模式 (true/false),默认 false
* - disable_scope_css 是否禁用样式隔离 (true/false),默认 false
* - auto_dark_theme 是否自动适配深色主题 (true/false),默认 true
* - keep_alive 是否开启微应用保活 (true/false),默认 true
* - immersive 是否开启沉浸式模式 (true/false),默认 false
* - props 传递参数
* 更多说明详见 https://appstore.dootask.com/development/manual
*/
async openMicroApp({state}, data) {
if (!data || !$A.isJson(data)) {
return
}
if (!data.id || !data.name || !data.url) {
return
}
const serverLocation = new URL($A.mainUrl(''))
data.url = data.url
.replace(/^\/+/, '')
.replace(/^\:(\d+)/ig, (_, port) => {
return serverLocation.protocol + '//' + serverLocation.hostname + ':' + port;
})
.replace(/\{window[._]location[._](\w+)}/ig, (_, property) => {
if (property in serverLocation) {
return serverLocation[property];
}
})
.replace(/\{system_base_url}/g, serverLocation.origin)
const config = {
id: data.id,
name: data.name,
url: $A.mainUrl(data.url),
url_type: data.url_type || 'inline',
background: data.background || null,
capsule: $A.isJson(data.capsule) ? data.capsule : {},
transparent: typeof data.transparent == 'boolean' ? data.transparent : false,
disable_scope_css: typeof data.disable_scope_css == 'boolean' ? data.disable_scope_css : false,
auto_dark_theme: typeof data.auto_dark_theme == 'boolean' ? data.auto_dark_theme : true,
keep_alive: typeof data.keep_alive == 'boolean' ? data.keep_alive : true,
immersive: typeof data.immersive == 'boolean' ? data.immersive : false,
props: $A.isJson(data.props) ? data.props : {},
}
if (!state.microAppsIds.includes(config.id)) {
$A.modalWarning(`应用「${config.id}」未安装`);
return;
}
config.url = config.url
.replace(/\{user_id}/g, state.userId)
.replace(/\{user_nickname}/g, encodeURIComponent(state.userInfo.nickname))
.replace(/\{user_email}/g, encodeURIComponent(state.userInfo.email))
.replace(/\{user_avatar}/g, encodeURIComponent(state.userInfo.userimg))
.replace(/\{user_token}/g, encodeURIComponent(state.userToken))
.replace(/\{system_theme}/g, state.themeName)
.replace(/\{system_lang}/g, languageName);
emitter.emit('observeMicroApp:open', config);
},
/**
* 微应用是否已安装
* @param state
* @param appName
* @returns {Promise<unknown>}
*/
isMicroAppInstalled({state}, appName) {
return new Promise(resolve => {
if (!appName) {
resolve(false)
return
}
resolve(!!state.microAppsIds.includes(appName))
})
},
/**
* 更新微应用状态(已安装应用、菜单项)
* @param commit
* @param dispatch
*/
async updateMicroAppsStatus({commit, state}) {
const {data: {code, data}} = await axios.get($A.mainUrl('appstore/api/v1/internal/installed'), {
headers: {
Token: state.userToken,
Language: languageName,
}
})
if (code === 200) {
commit("microApps/data", data|| [])
}
},
/** *****************************************************************************************/
/** *********************************** MCP Server ******************************************/
/** *****************************************************************************************/
/**
* 切换 MCP 服务器状态
* @param state
* @param commit
*/
async toggleMcpServer({state, commit}) {
if (state.mcpServerStatus.running === 'running') {
// 停止 MCP 服务器
commit('mcp/server/status', {running: 'stopped'});
} else {
// 启动 MCP 服务器
commit('mcp/server/status', {running: 'running'});
}
}
}