mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-11 18:42:54 +00:00
1251 lines
44 KiB
JavaScript
Executable File
Vendored
1251 lines
44 KiB
JavaScript
Executable File
Vendored
import {MarkdownPreview} from "../utils/markdown";
|
||
import {convertLocalResourcePath} from "../components/Replace/utils";
|
||
|
||
/**
|
||
* 页面专用
|
||
*/
|
||
(function (window) {
|
||
const $ = window.$A;
|
||
/**
|
||
* =============================================================================
|
||
* ******************************* web extra *******************************
|
||
* =============================================================================
|
||
*/
|
||
$.extend({
|
||
/**
|
||
* 接口地址
|
||
* @param str
|
||
* @returns {string|string|*}
|
||
*/
|
||
apiUrl(str) {
|
||
if (str == "privacy") {
|
||
const apiHome = $A.getDomain(window.systemInfo.apiUrl)
|
||
if (apiHome == "" || apiHome == "public") {
|
||
return "https://www.dootask.com/privacy.html"
|
||
}
|
||
str = "../privacy.html"
|
||
}
|
||
if (str.substring(0, 2) === "//" ||
|
||
str.substring(0, 7) === "http://" ||
|
||
str.substring(0, 8) === "https://" ||
|
||
str.substring(0, 6) === "ftp://" ||
|
||
str.substring(0, 1) === "/") {
|
||
return str;
|
||
}
|
||
if (typeof window.systemInfo.apiUrl === "string") {
|
||
str = window.systemInfo.apiUrl + str;
|
||
} else {
|
||
str = window.location.origin + "/api/" + str;
|
||
}
|
||
while (str.indexOf("/../") !== -1) {
|
||
str = str.replace(/\/(((?!\/).)*)\/\.\.\//, "/")
|
||
}
|
||
return str
|
||
},
|
||
|
||
/**
|
||
* 主页地址
|
||
* @param str
|
||
* @returns {string}
|
||
*/
|
||
mainUrl(str = null) {
|
||
if (!str) {
|
||
str = ""
|
||
}
|
||
if (str.substring(0, 2) === "//" ||
|
||
str.substring(0, 7) === "http://" ||
|
||
str.substring(0, 8) === "https://" ||
|
||
str.substring(0, 6) === "ftp://" ||
|
||
str.substring(0, 1) === "/") {
|
||
return str;
|
||
}
|
||
return $A.apiUrl(`../${str}`)
|
||
},
|
||
|
||
/**
|
||
* 服务地址
|
||
* @param str
|
||
* @returns {string}
|
||
*/
|
||
originUrl(str) {
|
||
if (str.substring(0, 2) === "//" ||
|
||
str.substring(0, 7) === "http://" ||
|
||
str.substring(0, 8) === "https://" ||
|
||
str.substring(0, 6) === "ftp://" ||
|
||
str.substring(0, 1) === "/") {
|
||
return str;
|
||
}
|
||
if (typeof window.systemInfo.origin === "string") {
|
||
str = window.systemInfo.origin + str;
|
||
} else {
|
||
str = window.location.origin + "/" + str;
|
||
}
|
||
while (str.indexOf("/../") !== -1) {
|
||
str = str.replace(/\/(((?!\/).)*)\/\.\.\//, "/")
|
||
}
|
||
return str
|
||
},
|
||
|
||
/**
|
||
* 预览文件地址
|
||
* @param name
|
||
* @param key
|
||
* @returns {*}
|
||
*/
|
||
onlinePreviewUrl(name, key) {
|
||
return $A.mainUrl(`online/preview/${name}?key=${key}&version=${window.systemInfo.version}&__=${$A.dayjs().valueOf()}`)
|
||
},
|
||
|
||
/**
|
||
* 项目配置模板
|
||
* @param project_id
|
||
* @returns {{showMy: boolean, showUndone: boolean, project_id, chat: boolean, showHelp: boolean, showCompleted: boolean, menuType: string, menuInit: boolean, completedTask: boolean}}
|
||
*/
|
||
projectParameterTemplate(project_id) {
|
||
return {
|
||
project_id,
|
||
menuInit: false,
|
||
menuType: 'column',
|
||
chat: false,
|
||
showMy: true,
|
||
showHelp: true,
|
||
showUndone: true,
|
||
showCompleted: false,
|
||
completedTask: false,
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 获取日期选择器的 shortcuts 模板参数
|
||
* @returns {(*)[]|[{text, value(): [Date,*]},{text, value(): [Date,*]},{text, value(): [*,*]},{text, value(): [*,*]},{text, value(): [Date,*]},null,null]|(Date|*)[]}
|
||
*/
|
||
timeOptionShortcuts() {
|
||
const startSecond = $A.daytz().startOf('day').toDate();
|
||
return [{
|
||
text: $A.L('今天'),
|
||
value() {
|
||
return [startSecond, $A.daytz().endOf('day').toDate()];
|
||
}
|
||
}, {
|
||
text: $A.L('明天'),
|
||
value() {
|
||
return [startSecond, $A.daytz().add(1, 'day').endOf('day').toDate()];
|
||
}
|
||
}, {
|
||
text: $A.L('本周'),
|
||
value() {
|
||
return [startSecond, $A.daytz().endOf('week').toDate()];
|
||
}
|
||
}, {
|
||
text: $A.L('本月'),
|
||
value() {
|
||
return [startSecond, $A.daytz().endOf('month').toDate()];
|
||
}
|
||
}, {
|
||
text: $A.L('3天'),
|
||
value() {
|
||
return [startSecond, $A.daytz().add(2, 'day').endOf('day').toDate()];
|
||
}
|
||
}, {
|
||
text: $A.L('5天'),
|
||
value() {
|
||
return [startSecond, $A.daytz().add(4, 'day').endOf('day').toDate()];
|
||
}
|
||
}, {
|
||
text: $A.L('7天'),
|
||
value() {
|
||
return [startSecond, $A.daytz().add(6, 'day').endOf('day').toDate()];
|
||
}
|
||
}];
|
||
},
|
||
|
||
/**
|
||
* 对话标签
|
||
* @param dialog
|
||
* @returns {*[]}
|
||
*/
|
||
dialogTags(dialog) {
|
||
let tags = [];
|
||
if (dialog.type == 'group') {
|
||
if (['project', 'task'].includes(dialog.group_type) && $A.isJson(dialog.group_info)) {
|
||
if (dialog.group_type == 'task' && dialog.group_info.complete_at) {
|
||
tags.push({
|
||
color: 'success',
|
||
text: '已完成'
|
||
})
|
||
}
|
||
if (dialog.group_info.deleted_at) {
|
||
tags.push({
|
||
color: 'red',
|
||
text: '已删除'
|
||
})
|
||
} else if (dialog.group_info.archived_at) {
|
||
tags.push({
|
||
color: 'default',
|
||
text: '已归档'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
return tags;
|
||
},
|
||
|
||
/**
|
||
* 对话完成
|
||
* @param dialog
|
||
* @returns {*[]}
|
||
*/
|
||
dialogCompleted(dialog) {
|
||
return this.dialogTags(dialog).find(({color}) => color == 'success');
|
||
},
|
||
|
||
/**
|
||
* 返回对话未读数量(不含免打扰,但如果免打扰中有@则返回@数量)
|
||
* @param dialog
|
||
* @returns {*|number}
|
||
*/
|
||
getDialogNum(dialog) {
|
||
if (!dialog) {
|
||
return 0
|
||
}
|
||
const unread = !dialog.silence ? dialog.unread : 0
|
||
return unread || dialog.mention || dialog.mark_unread || 0
|
||
},
|
||
|
||
/**
|
||
* 返回对话未读数量
|
||
* @param dialog
|
||
* @param containSilence 是否包含免打扰消息(true:包含, false:不包含)
|
||
* @returns {*|number}
|
||
*/
|
||
getDialogUnread(dialog, containSilence) {
|
||
if (!dialog) {
|
||
return 0
|
||
}
|
||
const unread = (containSilence || !dialog.silence) ? dialog.unread : 0
|
||
return unread || dialog.mark_unread || 0
|
||
},
|
||
|
||
/**
|
||
* 返回对话@提及未读数量
|
||
* @param dialog
|
||
* @returns {*|number}
|
||
*/
|
||
getDialogMention(dialog) {
|
||
return dialog?.mention || 0
|
||
},
|
||
|
||
/**
|
||
* 返回文本信息预览格式
|
||
* @param msgData
|
||
* @param imgClassName
|
||
* @returns {*}
|
||
*/
|
||
getMsgTextPreview({type, text}, imgClassName = null) {
|
||
if (!text) {
|
||
return '';
|
||
}
|
||
if (type === 'md') {
|
||
text = text.replace(/:::\s*reasoning[\s\S]*?:::/g, "");
|
||
if (/:::\s*reasoning\s+/.test(text)) {
|
||
return $A.L('思考中...')
|
||
}
|
||
let titleText = '';
|
||
const titleMatch = text.match(/^#{1,2}\s+(.+)/m);
|
||
if (titleMatch) {
|
||
titleText = titleMatch[1].trim();
|
||
}
|
||
if (titleText) {
|
||
text = titleText;
|
||
} else {
|
||
text = MarkdownPreview(text);
|
||
}
|
||
}
|
||
//
|
||
text = text.replace(/<img\s+class="emoticon"[^>]*?alt="(\S+)"[^>]*?>/g, "[$1]")
|
||
text = text.replace(/<img\s+class="emoticon"[^>]*?>/g, `[${$A.L('动画表情')}]`)
|
||
if (imgClassName) {
|
||
text = text.replace(/<img\s+class="browse"[^>]*?src="(\S+)"[^>]*?>/g, function (res, src) {
|
||
const item = $A.extractImageParameter(res);
|
||
if (item.width && item.height) {
|
||
const data = $A.imageRatioHandle({
|
||
src: item.src,
|
||
width: item.width,
|
||
height: item.height,
|
||
crops: {ratio: 2, percentage: '80x0'},
|
||
scaleSize: 40,
|
||
})
|
||
src = data.src
|
||
imgClassName = `${imgClassName}" style="width:${data.width}px;height:${data.height}px`
|
||
}
|
||
return `[image:${src}]`
|
||
})
|
||
} else {
|
||
text = text.replace(/<img\s+class="browse"[^>]*?>/g, `[${$A.L('图片')}]`)
|
||
}
|
||
text = text
|
||
.replace(/<\/p><p>/g, "</p> <p>")
|
||
.replace(/<[^>]+>/g, "")
|
||
.replace(/ /g, " ")
|
||
.replace(/"/g, "\"")
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/\s+/g, " ")
|
||
if (imgClassName) {
|
||
text = text.replace(/\[image:(.*?)\]/g, `<img class="${imgClassName}" src="$1">`)
|
||
text = text.replace(/\{\{RemoteURL\}\}/g, this.apiUrl('../'))
|
||
} else {
|
||
text = $A.cutString(text, 50)
|
||
}
|
||
return text
|
||
},
|
||
|
||
/**
|
||
* 消息格式化处理(将消息内的RemoteURL换成真实地址)
|
||
* @param data
|
||
*/
|
||
formatMsgBasic(data) {
|
||
if (!data) {
|
||
return data
|
||
}
|
||
if ($A.isJson(data)) {
|
||
for (let key in data) {
|
||
if (!data.hasOwnProperty(key)) continue;
|
||
data[key] = $A.formatMsgBasic(data[key]);
|
||
}
|
||
} else if ($A.isArray(data)) {
|
||
data.forEach((val, index) => {
|
||
data[index] = $A.formatMsgBasic(val);
|
||
});
|
||
} else if (typeof data === "string") {
|
||
data = data.replace(/\{\{RemoteURL\}\}/g, this.apiUrl('../'))
|
||
}
|
||
return data
|
||
},
|
||
|
||
/**
|
||
* 消息格式化处理
|
||
* @param text
|
||
* @param userid
|
||
* @returns {string|*}
|
||
*/
|
||
formatTextMsg(text, userid) {
|
||
if (!text) {
|
||
return ""
|
||
}
|
||
const atReg = new RegExp(`<span class="mention user" data-id="${userid}">`, "g")
|
||
text = text.trim().replace(/(\n\x20*){3,}/g, "\n\n");
|
||
text = text.replace(/ /g, ' ')
|
||
text = text.replace(/<p><\/p>/g, '<p><br/></p>')
|
||
text = text.replace(/\{\{RemoteURL\}\}/g, $A.mainUrl())
|
||
text = text.replace(atReg, `<span class="mention me" data-id="${userid}">`)
|
||
// 处理内容连接
|
||
if (/https?:\/\//.test(text)) {
|
||
text = text.split(/(<[^>]*>)/g).map(string => {
|
||
if (string && !/<[^>]*>/.test(string)) {
|
||
string = string.replace(/(^|[^'"])((https?:\/\/)((\w|=|\?|\.|\/|&|-|:|\+|%|;|#|@|,|!)+))/g, "$1<a href=\"$2\" target=\"_blank\">$2</a>")
|
||
}
|
||
return string;
|
||
}).join("")
|
||
}
|
||
// 处理图片显示尺寸
|
||
const items = $A.extractImageParameterAll(text);
|
||
items.some(item => {
|
||
if (item.src && item.width && item.height) {
|
||
const data = $A.imageRatioHandle({
|
||
src: item.src,
|
||
width: item.width,
|
||
height: item.height,
|
||
crops: {ratio: 5, percentage: '320x0'},
|
||
scaleSize: item.original.indexOf("emoticon") > -1 ? 150 : 220,
|
||
})
|
||
const value = item.original
|
||
.replace(/\s+width=/, ` original-width=`)
|
||
.replace(/\s+height=/, ` original-height=`)
|
||
.replace(/\s+src=(["'])([^'"]*)\1/i, ` style="width:${data.width}px;height:${data.height}px" src="${convertLocalResourcePath(data.src)}"`)
|
||
text = text.replace(item.original, value)
|
||
} else {
|
||
text = text.replace(item.original, `<div class="no-size-image-box">${item.original}</div>`);
|
||
}
|
||
})
|
||
return text;
|
||
},
|
||
|
||
/**
|
||
* 获取文本消息图片
|
||
* @param text
|
||
* @returns {*[]}
|
||
*/
|
||
getTextImagesInfo(text) {
|
||
const baseUrl = $A.mainUrl();
|
||
const array = text.match(new RegExp(`<img[^>]*?>`, "g"));
|
||
const list = [];
|
||
if (array) {
|
||
const srcReg = new RegExp("src=([\"'])([^'\"]*)\\1"),
|
||
widthReg = new RegExp("(original-)?width=\"(\\d+)\""),
|
||
heightReg = new RegExp("(original-)?height=\"(\\d+)\"")
|
||
array.some(res => {
|
||
const srcMatch = res.match(srcReg),
|
||
widthMatch = res.match(widthReg),
|
||
heightMatch = res.match(heightReg);
|
||
if (srcMatch) {
|
||
list.push({
|
||
src: srcMatch[2].replace(/\{\{RemoteURL\}\}/g, baseUrl),
|
||
width: widthMatch ? widthMatch[2] : -1,
|
||
height: heightMatch ? heightMatch[2] : -1,
|
||
})
|
||
}
|
||
})
|
||
}
|
||
return list;
|
||
},
|
||
|
||
/**
|
||
* 消息简单描述
|
||
* @param data
|
||
* @param imgClassName
|
||
* @returns {string|*}
|
||
*/
|
||
getMsgSimpleDesc(data, imgClassName = null) {
|
||
if (!$A.isJson(data)) {
|
||
return '';
|
||
}
|
||
switch (data.type) {
|
||
case 'text':
|
||
return $A.getMsgTextPreview(data.msg, imgClassName)
|
||
case 'longtext':
|
||
return data.msg.desc ? $A.cutString(data.msg.desc, 50) : ("[" + $A.L('长文本') + "]")
|
||
case 'vote':
|
||
return `[${$A.L('投票')}]` + $A.getMsgTextPreview(data.msg, imgClassName)
|
||
case 'word-chain':
|
||
return `[${$A.L('接龙')}]` + $A.getMsgTextPreview(data.msg, imgClassName)
|
||
case 'record':
|
||
return `[${$A.L('语音')}]`
|
||
case 'location':
|
||
return `[${$A.L('位置')}] ${$A.cutString(data.msg.title, 50)}`
|
||
case 'meeting':
|
||
return `[${$A.L('会议')}] ${$A.cutString(data.msg.name, 50)}`
|
||
case 'file':
|
||
return $A.fileMsgSimpleDesc(data.msg, imgClassName)
|
||
case 'tag':
|
||
return `[${$A.L(data.msg.action === 'remove' ? '取消标注' : '标注')}] ${$A.getMsgSimpleDesc(data.msg.data)}`
|
||
case 'top':
|
||
return `[${$A.L(data.msg.action === 'remove' ? '取消置顶' : '置顶')}] ${$A.getMsgSimpleDesc(data.msg.data)}`
|
||
case 'todo':
|
||
return `[${$A.L(data.msg.action === 'remove' ? '取消待办' : (data.msg.action === 'done' ? '完成' : '设待办'))}] ${$A.getMsgSimpleDesc(data.msg.data)}`
|
||
case 'notice':
|
||
const notice = data.msg.source === 'api' ? data.msg.notice : $A.L(data.msg.notice);
|
||
return $A.cutString(notice, 50)
|
||
case 'template':
|
||
return $A.templateMsgSimpleDesc(data.msg)
|
||
case 'preview':
|
||
return data.msg.preview
|
||
default:
|
||
return `[${$A.L('未知的消息')}]`
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 文件消息简单描述
|
||
* @param msg
|
||
* @param imgClassName
|
||
* @returns {string}
|
||
*/
|
||
fileMsgSimpleDesc(msg, imgClassName = null) {
|
||
if (msg.type == 'img') {
|
||
if (imgClassName) {
|
||
// 缩略图,主要用于回复消息预览
|
||
const data = $A.imageRatioHandle({
|
||
src: msg.thumb,
|
||
width: parseInt(msg.width),
|
||
height: parseInt(msg.height),
|
||
crops: {ratio: 2, percentage: '80x0'},
|
||
scaleSize: 40,
|
||
})
|
||
return `<img class="${imgClassName}" style="width:${data.width}px;height:${data.height}px" src="${data.src}">`
|
||
}
|
||
return `[${$A.L('图片')}]`
|
||
} else if (msg.ext == 'mp4') {
|
||
return `[${$A.L('视频')}]`
|
||
}
|
||
return `[${$A.L('文件')}] ${$A.cutString(msg.name, 50)}`
|
||
},
|
||
|
||
/**
|
||
* 模板消息简单描述
|
||
* @param msg
|
||
* @returns {string|*}
|
||
*/
|
||
templateMsgSimpleDesc(msg) {
|
||
if (msg.title_raw) {
|
||
return msg.title_raw
|
||
}
|
||
if (msg.type === 'task_list' && $A.arrayLength(msg.list) === 1) {
|
||
const title = msg.source === 'api' ? msg.title : $A.L(msg.title)
|
||
return title + ": " + $A.cutString(msg.list[0].name, 50)
|
||
}
|
||
if (msg.title) {
|
||
return msg.source === 'api' ? msg.title : $A.L(msg.title)
|
||
}
|
||
if (msg.type === 'content' && typeof msg.content === 'string' && msg.content !== '') {
|
||
const content = msg.source === 'api' ? msg.content : $A.L(msg.content)
|
||
return $A.cutString(content, 50)
|
||
}
|
||
return $A.L('未知的消息')
|
||
},
|
||
|
||
/**
|
||
* 获取文件标题
|
||
* @param file
|
||
* @returns {*}
|
||
*/
|
||
getFileName(file) {
|
||
let name = file.name || '';
|
||
let ext = file.ext || '';
|
||
if (ext != '') {
|
||
name += "." + ext;
|
||
}
|
||
return name;
|
||
},
|
||
|
||
/**
|
||
* 是否是doo服务器
|
||
* @returns {boolean}
|
||
*/
|
||
isDooServer() {
|
||
const u = $A.getDomain($A.mainUrl())
|
||
return /dootask\.com$/.test(u)
|
||
|| /hitosea\.com$/.test(u)
|
||
|| /^127\.0\.0\.1/.test(u)
|
||
|| /^(10)\./.test(u)
|
||
|| /^(172)\.(1[6-9]|2[0-9]|3[0-1])\./.test(u)
|
||
|| /^(192)\.(168)\./.test(u);
|
||
},
|
||
|
||
/**
|
||
* 缩略图还原
|
||
* @param url
|
||
* @returns {*|string}
|
||
*/
|
||
thumbRestore(url) {
|
||
return `${url}`
|
||
.replace(/_thumb\.(png|jpg|jpeg)$/, '')
|
||
.replace(/\/crop\/([^\/]+)$/, '')
|
||
},
|
||
|
||
/**
|
||
* 拖拽或粘贴的数据是否包含文件夹
|
||
* @param data
|
||
* @returns {boolean}
|
||
*/
|
||
dataHasFolder(data) {
|
||
const {items} = data;
|
||
if (items) {
|
||
for (const item of items) {
|
||
if (item.kind === "directory" || (item.kind === "file" && item.webkitGetAsEntry().isDirectory)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
},
|
||
|
||
/**
|
||
* 图片尺寸比例超出
|
||
* @param params {{src: *, width: (number|*), height: (number|*), crops: {ratio?: number, size?: string, percentage?: string, cover?: string, contain?: string}, scaleSize: (number)}}
|
||
* src, // 原图地址
|
||
* width, // 原图宽度
|
||
* height, // 原图高度
|
||
* crops, // 裁剪参数,如:{ratio:3, percentage:"80x0"}
|
||
* scaleSize, // 返回尺寸缩放最高尺寸
|
||
*/
|
||
imageRatioHandle(params) {
|
||
if (!$A.isJson(params.crops)) {
|
||
return params
|
||
}
|
||
|
||
if ($A.imageRatioJudge(params.src)) {
|
||
params.src = $A.thumbRestore(params.src) + "/crop/" + Object.keys(params.crops).map(key => {
|
||
return `${key}:${params.crops[key]}`
|
||
}).join(",")
|
||
|
||
const ratio = $A.imageRatioExceed(params.width, params.height, params.crops.ratio)
|
||
if (ratio > 0) {
|
||
if (params.width > params.height) {
|
||
params.width = params.height * ratio;
|
||
} else {
|
||
params.height = params.width * ratio;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (params.scaleSize) {
|
||
const scale = $A.scaleToScale(params.width, params.height, params.scaleSize);
|
||
params.width = scale.width;
|
||
params.height = scale.height;
|
||
}
|
||
|
||
return params;
|
||
},
|
||
|
||
/**
|
||
* 判断图片地址是否满足比例缩放
|
||
* @param url
|
||
* @returns {boolean}
|
||
*/
|
||
imageRatioJudge(url) {
|
||
if (!/\.(png|jpg|jpeg)$/.test(url)) {
|
||
return false
|
||
}
|
||
return $A.getDomain(url) == $A.getDomain($A.mainUrl());
|
||
},
|
||
|
||
/**
|
||
* 图片尺寸比例超出
|
||
* @param width
|
||
* @param height
|
||
* @param ratio
|
||
* @param float
|
||
* @returns {number}
|
||
*/
|
||
imageRatioExceed(width, height, ratio, float = 0.5) {
|
||
if (width && height && ratio) {
|
||
if (width / height > (ratio + float) || height / width > (ratio + float)) {
|
||
return ratio
|
||
}
|
||
}
|
||
return 0;
|
||
},
|
||
|
||
/**
|
||
* 去除html内容中无效的部分
|
||
* @param content
|
||
* @returns {string}
|
||
*/
|
||
filterInvalidLine(content) {
|
||
if (!content) return '';
|
||
return `${content}`
|
||
.replace(/^(<p>\s*<\/p>)+|(<p>\s*<\/p>)+$/gi, '')
|
||
.replace(/^(<p><br\/*><\/p>)+|(<p><br\/*><\/p>)+$/gi, '')
|
||
},
|
||
|
||
/**
|
||
* 加载 VConsole 日志组件
|
||
* @param key
|
||
*/
|
||
loadVConsole(key = undefined) {
|
||
if (typeof key === "string") {
|
||
switch (key) {
|
||
case 'log.o':
|
||
$A.IDBSet("logOpen", "open").then(_ => {
|
||
$A.loadVConsole()
|
||
});
|
||
return true;
|
||
case 'log.c':
|
||
$A.IDBSet("logOpen", "close").then(_ => {
|
||
$A.loadVConsole()
|
||
});
|
||
return true;
|
||
}
|
||
return false
|
||
}
|
||
$A.IDBString("logOpen").then(r => {
|
||
if (typeof window.vConsole !== "undefined") {
|
||
window.vConsole.destroy();
|
||
window.vConsole = null;
|
||
}
|
||
$A.openLog = r === "open"
|
||
if ($A.openLog) {
|
||
$A.loadScript('js/vconsole.min.js').then(_ => {
|
||
window.vConsole = new window.VConsole({
|
||
onReady: () => {
|
||
console.log('VConsole: onReady');
|
||
},
|
||
onClearLog: () => {
|
||
console.log('VConsole: onClearLog');
|
||
}
|
||
});
|
||
}).catch(_ => {
|
||
$A.modalError("VConsole 组件加载失败!");
|
||
})
|
||
}
|
||
})
|
||
},
|
||
|
||
/**
|
||
* 提取工作报告中的时间
|
||
* @param text
|
||
* @returns {*|string}
|
||
*/
|
||
reportExtractTime(text) {
|
||
const regex = /(?:.*?)(?:\[([^\[\]]*)\]\s*)?(?:\[([^\[\]]*)\]\s*)?$/;
|
||
const match = text.match(regex);
|
||
if (!match) return "";
|
||
const secondLast = `${match[1] || ""}`.replace(/^\s*\((.*)\)\s*$/, "$1");
|
||
const last = `${match[2] || ""}`.replace(/^\s*\((.*)\)\s*$/, "$1");
|
||
if (last && secondLast) {
|
||
return `${last} (${secondLast})`;
|
||
} else if (last) {
|
||
return last;
|
||
} else if (secondLast) {
|
||
return secondLast;
|
||
}
|
||
return "";
|
||
},
|
||
|
||
/**
|
||
* 根据十六进制颜色生成通用 CSS 变量样式
|
||
* @param {string} hexColor - 颜色值,格式如 "#RRGGBB"
|
||
* @param {number[]} levels - 需要生成的透明度等级数组(如 [10, 20, 70],代表 10%、20%、70%)
|
||
* @param {string} prefix - 生成的 CSS 变量前缀,默认为 'custom-color'
|
||
* @param {Object|null} styles - 可选的样式对象,如果未传入则会创建一个新的对象
|
||
* @returns {Object|null} 返回包含 CSS 变量的对象,若未传入颜色则返回 null
|
||
*/
|
||
generateColorVarStyle(hexColor, levels = [], prefix = 'custom-color', styles = null) {
|
||
if (typeof hexColor !== 'string' || !/^#([0-9a-fA-F]{6})$/.test(hexColor)) {
|
||
return styles;
|
||
}
|
||
// 解析十六进制颜色为 RGB
|
||
const r = parseInt(hexColor.substring(1, 3), 16);
|
||
const g = parseInt(hexColor.substring(3, 5), 16);
|
||
const b = parseInt(hexColor.substring(5, 7), 16);
|
||
|
||
// 初始化样式对象
|
||
if (!$A.isJson(styles)) {
|
||
styles = {};
|
||
}
|
||
|
||
// 遍历 levels,生成对应透明度的 rgba 变量
|
||
levels.forEach(level => {
|
||
// 只处理有效的数字
|
||
if (typeof level === 'number' && level >= 0 && level <= 100) {
|
||
const alpha = Math.round((level / 100) * 100) / 100; // 保留两位小数
|
||
styles[`--${prefix}-${level}`] = `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||
}
|
||
});
|
||
|
||
// 加上 100% 不透明度直接用 hexColor
|
||
styles[`--${prefix}-100`] = hexColor
|
||
|
||
return styles;
|
||
},
|
||
|
||
/**
|
||
* 转换工作流状态
|
||
* @param {string|{flow_item_name, complete_at}} item
|
||
* @returns {{status: null, name: string, color: null}}
|
||
*/
|
||
convertWorkflow(item) {
|
||
let status = null,
|
||
name = item,
|
||
color = null;
|
||
if ($A.isJson(item)) {
|
||
name = item.flow_item_name
|
||
if (name.indexOf("|") === -1) {
|
||
if (name.complete_at) {
|
||
name = $A.L('已完成');
|
||
} else {
|
||
name = $A.L('未完成');
|
||
}
|
||
}
|
||
}
|
||
if (name && name.indexOf("|") !== -1) {
|
||
const arr = `${name}||`.split("|")
|
||
status = arr[0]
|
||
name = arr[1]
|
||
color = arr[2]
|
||
}
|
||
return {status, name, color}
|
||
}
|
||
});
|
||
|
||
/**
|
||
* =============================================================================
|
||
* ***************************** iviewui assist ****************************
|
||
* =============================================================================
|
||
*/
|
||
$.extend({
|
||
// 弹窗
|
||
modalConfig(config) {
|
||
if (typeof config === "undefined") {
|
||
config = {content: "Undefined"};
|
||
} else if (typeof config === "string") {
|
||
config = {content: config};
|
||
}
|
||
config.title = config.title || (typeof config.render === 'undefined' ? $A.modalTranslation('温馨提示', config.language) : '');
|
||
config.content = config.content || '';
|
||
config.okText = config.okText || $A.modalTranslation('确定', config.language);
|
||
config.cancelText = config.cancelText || $A.modalTranslation('取消', config.language);
|
||
if (config.language !== false) {
|
||
delete config.language;
|
||
config.title = $A.L(config.title);
|
||
config.content = $A.L(config.content);
|
||
config.okText = $A.L(config.okText);
|
||
config.cancelText = $A.L(config.cancelText);
|
||
}
|
||
return config;
|
||
},
|
||
|
||
modalTranslation(title, language) {
|
||
if (language !== false) {
|
||
return title;
|
||
} else {
|
||
return $A.L(title)
|
||
}
|
||
},
|
||
|
||
modalInput(config, millisecond = 0) {
|
||
if (millisecond > 0) {
|
||
setTimeout(() => { $A.modalInput(config) }, millisecond);
|
||
return;
|
||
}
|
||
if (typeof config === "string") config = {title:config};
|
||
let inputId = "modalInput_" + $A.randomString(6);
|
||
let inputProps = {
|
||
type: config.type || "text",
|
||
value: config.value,
|
||
placeholder: $A.L(config.placeholder),
|
||
elementId: inputId,
|
||
}
|
||
if ($A.isJson(config.inputProps)) {
|
||
inputProps = Object.assign(inputProps, config.inputProps)
|
||
}
|
||
const onOk = () => {
|
||
return new Promise((resolve, reject) => {
|
||
if (!config.onOk) {
|
||
reject() // 没有返回:取消等待
|
||
return
|
||
}
|
||
const call = config.onOk(config.value);
|
||
if (!call) {
|
||
resolve() // 返回无内容:关闭弹窗
|
||
return
|
||
}
|
||
if (call.then) {
|
||
call.then(msg => {
|
||
msg && $A.messageSuccess(msg)
|
||
resolve()
|
||
}).catch(err => {
|
||
err && $A.messageError(err)
|
||
reject()
|
||
});
|
||
} else {
|
||
typeof call === "string" && $A.messageError(call)
|
||
reject()
|
||
}
|
||
})
|
||
};
|
||
const onCancel = () => {
|
||
if (typeof config.onCancel === "function") {
|
||
config.onCancel();
|
||
}
|
||
};
|
||
$A.eeuiAppKeyboardHide()
|
||
$A.Modal.confirm({
|
||
render: (h) => {
|
||
return h('div', [
|
||
h('div', {
|
||
style: {
|
||
fontSize: '16px',
|
||
fontWeight: '500',
|
||
marginBottom: '20px',
|
||
}
|
||
}, $A.L(config.title)),
|
||
h('Input', {
|
||
props: inputProps,
|
||
on: {
|
||
input: (val) => {
|
||
config.value = val;
|
||
},
|
||
'on-enter': (e) => {
|
||
$A(e.target).parents(".ivu-modal-body").find(".ivu-btn-primary").click();
|
||
}
|
||
}
|
||
})
|
||
])
|
||
},
|
||
onOk,
|
||
onCancel,
|
||
loading: true,
|
||
okText: $A.L(config.okText || '确定'),
|
||
cancelText: $A.L(config.cancelText || '取消'),
|
||
okType: config.okType || 'primary',
|
||
cancelType: config.cancelType || 'text',
|
||
});
|
||
setTimeout(() => {
|
||
document.getElementById(inputId) && document.getElementById(inputId).focus();
|
||
});
|
||
},
|
||
|
||
modalConfirm(config, millisecond = 0) {
|
||
if (config === false) {
|
||
return;
|
||
}
|
||
if (millisecond > 0) {
|
||
setTimeout(() => { $A.modalConfirm(config) }, millisecond);
|
||
return;
|
||
}
|
||
config = $A.modalConfig(config);
|
||
if (config.loading) {
|
||
const {onOk} = config;
|
||
config.onOk = () => {
|
||
return new Promise((resolve, reject) => {
|
||
if (!onOk) {
|
||
reject() // 没有返回:取消等待
|
||
return
|
||
}
|
||
const call = onOk();
|
||
if (!call) {
|
||
resolve() // 返回无内容:关闭弹窗
|
||
return
|
||
}
|
||
if (call.then) {
|
||
call.then(msg => {
|
||
msg && $A.messageSuccess(msg)
|
||
resolve()
|
||
}).catch(err => {
|
||
err && $A.messageError(err)
|
||
reject()
|
||
});
|
||
} else {
|
||
typeof call === "string" && $A.messageError(call)
|
||
reject()
|
||
}
|
||
})
|
||
}
|
||
}
|
||
$A.eeuiAppKeyboardHide()
|
||
$A.Modal.confirm($A.modalConfig(config));
|
||
},
|
||
|
||
modalSuccess(config, millisecond = 0) {
|
||
if (config === false) {
|
||
return;
|
||
}
|
||
if (millisecond > 0) {
|
||
setTimeout(() => { $A.modalSuccess(config) }, millisecond);
|
||
return;
|
||
}
|
||
$A.eeuiAppKeyboardHide()
|
||
$A.Modal.success($A.modalConfig(config));
|
||
},
|
||
|
||
modalInfo(config, millisecond = 0) {
|
||
if (config === false) {
|
||
return;
|
||
}
|
||
if (millisecond > 0) {
|
||
setTimeout(() => { $A.modalInfo(config) }, millisecond);
|
||
return;
|
||
}
|
||
$A.eeuiAppKeyboardHide()
|
||
$A.Modal.info($A.modalConfig(config));
|
||
},
|
||
|
||
modalWarning(config, millisecond = 0) {
|
||
if (config === false) {
|
||
return;
|
||
}
|
||
if ($A.isJson(config) && config.content === false) {
|
||
return;
|
||
}
|
||
if (millisecond > 0) {
|
||
setTimeout(() => { $A.modalWarning(config) }, millisecond);
|
||
return;
|
||
}
|
||
$A.eeuiAppKeyboardHide()
|
||
$A.Modal.warning($A.modalConfig(config));
|
||
},
|
||
|
||
modalError(config, millisecond = 0) {
|
||
if (config === false) {
|
||
return;
|
||
}
|
||
if ($A.isJson(config) && config.content === false) {
|
||
return;
|
||
}
|
||
if (millisecond > 0) {
|
||
setTimeout(() => { $A.modalError(config) }, millisecond);
|
||
return;
|
||
}
|
||
$A.eeuiAppKeyboardHide()
|
||
$A.Modal.error($A.modalConfig(config));
|
||
},
|
||
|
||
modalAlert(msg) {
|
||
if (msg === false) {
|
||
return;
|
||
}
|
||
$A.eeuiAppKeyboardHide()
|
||
alert($A.L(msg));
|
||
},
|
||
|
||
//提示
|
||
messageSuccess(msg) {
|
||
$A.Message.success($A.L(msg));
|
||
},
|
||
|
||
messageInfo(msg) {
|
||
$A.Message.info($A.L(msg));
|
||
},
|
||
|
||
messageWarning(msg) {
|
||
if (msg === false) {
|
||
return;
|
||
}
|
||
$A.Message.warning($A.L(msg));
|
||
},
|
||
|
||
messageError(msg) {
|
||
if (msg === false) {
|
||
return;
|
||
}
|
||
$A.Message.error($A.L(msg));
|
||
},
|
||
|
||
//通知
|
||
noticeConfig(config) {
|
||
if (typeof config === "undefined") {
|
||
config = {desc: "Undefined"};
|
||
} else if (typeof config === "string") {
|
||
config = {desc: config};
|
||
}
|
||
config.title = $A.L(config.title || (typeof config.render === 'undefined' ? '温馨提示' : ''));
|
||
config.desc = $A.L(config.desc || '');
|
||
return config;
|
||
},
|
||
|
||
noticeSuccess(config) {
|
||
if (config === false) {
|
||
return;
|
||
}
|
||
$A.Notice.success($A.noticeConfig(config));
|
||
},
|
||
|
||
noticeWarning(config) {
|
||
if (config === false) {
|
||
return;
|
||
}
|
||
$A.Notice.warning($A.noticeConfig(config));
|
||
},
|
||
|
||
noticeError(config) {
|
||
if (config === false) {
|
||
return;
|
||
}
|
||
if (typeof config === "string") {
|
||
config = {
|
||
desc: config,
|
||
duration: 6
|
||
};
|
||
}
|
||
$A.Notice.error($A.noticeConfig(config));
|
||
},
|
||
});
|
||
|
||
/**
|
||
* =============================================================================
|
||
* ********************************** dark *********************************
|
||
* =============================================================================
|
||
*/
|
||
|
||
$.extend({
|
||
dark: {
|
||
utils: {
|
||
supportMode() {
|
||
let ua = typeof window !== 'undefined' && window.navigator.userAgent.toLowerCase();
|
||
if (`${ua.match(/Chrome/i)}` === 'chrome') {
|
||
return 'chrome';
|
||
}
|
||
if (`${ua.match(/Webkit/i)}` === 'webkit') {
|
||
return 'webkit';
|
||
}
|
||
return null;
|
||
},
|
||
|
||
defaultFilter() {
|
||
return '-webkit-filter: invert(100%) hue-rotate(180deg) contrast(90%) !important; ' +
|
||
'filter: invert(100%) hue-rotate(180deg) contrast(90%) !important;';
|
||
},
|
||
|
||
reverseFilter() {
|
||
return '-webkit-filter: invert(100%) hue-rotate(180deg) contrast(100%) !important; ' +
|
||
'filter: invert(100%) hue-rotate(180deg) contrast(100%) !important;';
|
||
},
|
||
|
||
noneFilter() {
|
||
return '-webkit-filter: none !important; filter: none !important;';
|
||
},
|
||
|
||
addExtraStyle() {
|
||
try {
|
||
return '';
|
||
} catch (e) {
|
||
return '';
|
||
}
|
||
},
|
||
|
||
addStyle(id, tag, css) {
|
||
tag = tag || 'style';
|
||
let doc = document, styleDom = doc.getElementById(id);
|
||
if (styleDom) return;
|
||
let style = doc.createElement(tag);
|
||
style.rel = 'stylesheet';
|
||
style.id = id;
|
||
tag === 'style' ? style.innerHTML = css : style.href = css;
|
||
document.head.appendChild(style);
|
||
},
|
||
|
||
getClassList(node) {
|
||
return node.classList || [];
|
||
},
|
||
|
||
addClass(node, name) {
|
||
this.getClassList(node).add(name);
|
||
return this;
|
||
},
|
||
|
||
removeClass(node, name) {
|
||
this.getClassList(node).remove(name);
|
||
return this;
|
||
},
|
||
|
||
hasClass(node, name) {
|
||
return this.getClassList(node).contains(name);
|
||
},
|
||
|
||
hasElementById(eleId) {
|
||
return document.getElementById(eleId);
|
||
},
|
||
|
||
removeElementById(eleId) {
|
||
let ele = document.getElementById(eleId);
|
||
ele && ele.parentNode.removeChild(ele);
|
||
},
|
||
},
|
||
|
||
createDarkStyle() {
|
||
this.utils.addStyle('dark-mode-style', 'style', `
|
||
@media screen {
|
||
html {
|
||
${this.utils.defaultFilter()}
|
||
will-change: transform;
|
||
}
|
||
|
||
/* Default Reverse rule */
|
||
img,
|
||
video,
|
||
iframe,
|
||
canvas,
|
||
[style*="background:url"],
|
||
[style*="background: url"],
|
||
[style*="background-image:url"],
|
||
[style*="background-image: url"],
|
||
[background],
|
||
.emoji-original,
|
||
.no-dark-content,
|
||
.no-dark-before:before {
|
||
${this.utils.reverseFilter()}
|
||
will-change: transform;
|
||
}
|
||
|
||
.no-dark-content img,
|
||
.no-dark-content video,
|
||
.no-dark-content iframe,
|
||
.no-dark-content canvas,
|
||
.no-dark-content [style*="background:url"],
|
||
.no-dark-content [style*="background: url"],
|
||
.no-dark-content [style*="background-image:url"],
|
||
.no-dark-content [style*="background-image: url"],
|
||
.no-dark-content [background] {
|
||
${this.utils.noneFilter()}
|
||
}
|
||
|
||
.fullscreen-mode img,
|
||
.fullscreen-mode video,
|
||
.fullscreen-mode iframe,
|
||
.fullscreen-mode canvas {
|
||
${this.utils.noneFilter()}
|
||
}
|
||
|
||
/* Text contrast */
|
||
html {
|
||
text-shadow: 0 0 0 !important;
|
||
}
|
||
|
||
/* Full screen */
|
||
.no-filter,
|
||
:-webkit-full-screen,
|
||
:-webkit-full-screen *,
|
||
:-moz-full-screen,
|
||
:-moz-full-screen *,
|
||
:fullscreen,
|
||
:fullscreen * {
|
||
${this.utils.noneFilter()}
|
||
}
|
||
|
||
/* Page background */
|
||
html {
|
||
min-width: 100%;
|
||
min-height: 100%;
|
||
}
|
||
html[data-platform="desktop"] {
|
||
background-color: #0D0D0D;
|
||
}
|
||
.child-view {
|
||
background-color: #fff;
|
||
}
|
||
.page-login {
|
||
background-color: #f8f8f8;
|
||
}
|
||
${this.utils.addExtraStyle()}
|
||
}
|
||
|
||
@media print {
|
||
.no-print {
|
||
display: none !important;
|
||
}
|
||
}`);
|
||
},
|
||
|
||
enableDarkMode() {
|
||
if (!this.utils.supportMode()) {
|
||
return;
|
||
}
|
||
if (this.isDarkEnabled()) {
|
||
return
|
||
}
|
||
this.createDarkStyle();
|
||
this.utils.addClass(document.body, "dark-mode-reverse")
|
||
},
|
||
|
||
disableDarkMode() {
|
||
if (!this.isDarkEnabled()) {
|
||
return
|
||
}
|
||
this.utils.removeElementById('dark-mode-style');
|
||
this.utils.removeClass(document.body, "dark-mode-reverse")
|
||
},
|
||
|
||
autoDarkMode() {
|
||
let darkScheme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||
if ($A.isEEUIApp) {
|
||
darkScheme = $A.eeuiAppGetThemeName() === "dark"
|
||
}
|
||
if (darkScheme) {
|
||
this.enableDarkMode()
|
||
} else {
|
||
this.disableDarkMode()
|
||
}
|
||
},
|
||
|
||
isDarkEnabled() {
|
||
return this.utils.hasClass(document.body, "dark-mode-reverse")
|
||
},
|
||
}
|
||
});
|
||
|
||
window.$A = $;
|
||
})(window);
|