mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-11 02:12:53 +00:00
perf: 优化桌面端通知图标
This commit is contained in:
parent
694f9a37a5
commit
07a290dbf9
9
cmd
9
cmd
@ -168,15 +168,12 @@ run_electron() {
|
||||
rm -rf "./electron/public"
|
||||
fi
|
||||
#
|
||||
BUILD_FRONTEND="build"
|
||||
if [ "$argv" == "dev" ]; then
|
||||
switch_debug "$argv"
|
||||
else
|
||||
mkdir -p ./electron/public
|
||||
cp ./electron/index.html ./electron/public/index.html
|
||||
npx vite build -- fromcmd electronBuild
|
||||
echo ""
|
||||
BUILD_FRONTEND="dev"
|
||||
fi
|
||||
node ./electron/build.js $argv
|
||||
env BUILD_FRONTEND=$BUILD_FRONTEND node ./electron/build.js $argv
|
||||
}
|
||||
|
||||
run_exec() {
|
||||
|
||||
1
electron/.gitignore
vendored
1
electron/.gitignore
vendored
@ -6,6 +6,7 @@ package-lock.json
|
||||
build/
|
||||
dist/
|
||||
updater/*
|
||||
cache/*
|
||||
|
||||
.devload
|
||||
.native
|
||||
|
||||
18
electron/build.js
vendored
18
electron/build.js
vendored
@ -11,7 +11,7 @@ const utils = require('./utils');
|
||||
const config = require('../package.json')
|
||||
const env = require('dotenv').config({ path: './.env' })
|
||||
const argv = process.argv;
|
||||
const {APPLEID, APPLEIDPASS, GITHUB_TOKEN, GITHUB_REPOSITORY, PUBLISH_KEY} = process.env;
|
||||
const {BUILD_FRONTEND, APPLEID, APPLEIDPASS, GITHUB_TOKEN, GITHUB_REPOSITORY, PUBLISH_KEY} = process.env;
|
||||
|
||||
const electronDir = path.resolve(__dirname, "public");
|
||||
const nativeCachePath = path.resolve(__dirname, ".native");
|
||||
@ -21,6 +21,9 @@ const packageBakFile = path.resolve(__dirname, "package-bak.json");
|
||||
const platforms = ["build-mac", "build-win"];
|
||||
const architectures = ["arm64", "x64"];
|
||||
|
||||
let buildChecked = false,
|
||||
updaterChecked = false;
|
||||
|
||||
/**
|
||||
* 检测并下载更新器
|
||||
*/
|
||||
@ -459,6 +462,13 @@ function genericPublish({url, key, version, output}) {
|
||||
* @param data
|
||||
*/
|
||||
async function startBuild(data) {
|
||||
if (BUILD_FRONTEND === 'build' && !buildChecked) {
|
||||
buildChecked = true
|
||||
fs.mkdirSync(electronDir, { recursive: true });
|
||||
fse.copySync(path.resolve(__dirname, "index.html"), path.resolve(electronDir, "index.html"))
|
||||
child_process.spawnSync("npx", ["vite", "build", "--", "fromcmd", "electronBuild"], {stdio: "inherit"});
|
||||
}
|
||||
//
|
||||
const {platform, archs, publish, release, notarize} = data.configure
|
||||
// system info
|
||||
const systemInfo = {
|
||||
@ -493,7 +503,10 @@ async function startBuild(data) {
|
||||
// drawio
|
||||
cloneDrawio(systemInfo)
|
||||
// updater
|
||||
await detectAndDownloadUpdater()
|
||||
if (!updaterChecked) {
|
||||
updaterChecked = true
|
||||
await detectAndDownloadUpdater()
|
||||
}
|
||||
}
|
||||
// language
|
||||
fse.copySync(path.resolve(__dirname, "../public/language"), path.resolve(electronDir, "language"))
|
||||
@ -569,6 +582,7 @@ async function startBuild(data) {
|
||||
if (appName === "public") appName = "DooTask"
|
||||
appConfig.name = data.name;
|
||||
appConfig.version = config.version;
|
||||
appConfig.appId = data.id;
|
||||
appConfig.build.appId = data.id;
|
||||
appConfig.build.artifactName = appName + "-v${version}-${os}-${arch}.${ext}";
|
||||
appConfig.build.nsis.artifactName = appName + "-v${version}-${os}-${arch}.${ext}";
|
||||
|
||||
55
electron/electron.js
vendored
55
electron/electron.js
vendored
@ -20,7 +20,8 @@ const isMac = process.platform === 'darwin'
|
||||
const isWin = process.platform === 'win32'
|
||||
const allowedUrls = /^(?:https?|mailto|tel|callto):/i;
|
||||
const allowedCalls = /^(?:mailto|tel|callto):/i;
|
||||
let updaterLockFile = path.join(os.tmpdir(), '.dootask_updater.lock');
|
||||
const cacheDir = path.join(os.tmpdir(), 'dootask-cache')
|
||||
let updaterLockFile = path.join(cacheDir, '.dootask_updater.lock');
|
||||
let enableStoreBkp = true;
|
||||
let dialogOpen = false;
|
||||
let enablePlugins = false;
|
||||
@ -53,6 +54,10 @@ if (fs.existsSync(devloadCachePath)) {
|
||||
devloadUrl = fs.readFileSync(devloadCachePath, 'utf8')
|
||||
}
|
||||
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
fs.mkdirSync(cacheDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 在最开始就注册协议为特权协议
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
@ -85,7 +90,7 @@ function createProtocol() {
|
||||
}
|
||||
|
||||
const data = await fs.promises.readFile(filePath)
|
||||
const mimeType = getMimeType(filePath)
|
||||
const mimeType = utils.getMimeType(filePath)
|
||||
|
||||
return new Response(data, {
|
||||
headers: {
|
||||
@ -99,24 +104,6 @@ function createProtocol() {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* MIME类型判断
|
||||
* @param filePath
|
||||
* @returns {*|string}
|
||||
*/
|
||||
function getMimeType(filePath) {
|
||||
const ext = path.extname(filePath).toLowerCase()
|
||||
const mimeTypes = {
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
'.gif': 'image/gif',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.webp': 'image/webp'
|
||||
}
|
||||
return mimeTypes[ext] || 'application/octet-stream'
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建主窗口
|
||||
*/
|
||||
@ -201,7 +188,7 @@ function createUpdaterWindow(updateTitle) {
|
||||
} else {
|
||||
updaterPath = path.join(process.resourcesPath, 'updater', 'updater');
|
||||
}
|
||||
|
||||
|
||||
// 检查updater应用是否存在
|
||||
if (!fs.existsSync(updaterPath)) {
|
||||
console.log('Updater not found:', updaterPath);
|
||||
@ -222,7 +209,7 @@ function createUpdaterWindow(updateTitle) {
|
||||
try {
|
||||
spawn('chmod', ['+x', updaterPath], {stdio: 'inherit'});
|
||||
} catch (e) {
|
||||
console.log('Failed to set executable permission:', e);
|
||||
console.log('Failed to set executable permission:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -728,6 +715,7 @@ if (!getTheLock) {
|
||||
})
|
||||
app.on('ready', () => {
|
||||
isReady = true
|
||||
isWin && app.setAppUserModelId(config.appId)
|
||||
// SameSite
|
||||
utils.useCookie()
|
||||
// 创建协议
|
||||
@ -756,7 +744,7 @@ if (!getTheLock) {
|
||||
mainTray.setContextMenu(trayMenu)
|
||||
}
|
||||
}
|
||||
// 删除updater锁文件(如果存在)
|
||||
// 删除updater锁文件(如果存在)
|
||||
if (fs.existsSync(updaterLockFile)) {
|
||||
try {
|
||||
fs.unlinkSync(updaterLockFile);
|
||||
@ -764,10 +752,6 @@ if (!getTheLock) {
|
||||
//忽略错误
|
||||
}
|
||||
}
|
||||
//
|
||||
if (process.platform === 'win32') {
|
||||
app.setAppUserModelId(config.name)
|
||||
}
|
||||
// 截图对象
|
||||
screenshotObj = new Screenshots({
|
||||
singleWindow: true,
|
||||
@ -1150,7 +1134,7 @@ ipcMain.on('copyImageAt', (event, args) => {
|
||||
try {
|
||||
event.sender.copyImageAt(args.x, args.y);
|
||||
} catch (e) {
|
||||
// log.error(e)
|
||||
// loger.error(e)
|
||||
}
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
@ -1212,14 +1196,7 @@ ipcMain.on('closeScreenshot', (event) => {
|
||||
* 通知
|
||||
*/
|
||||
ipcMain.on('openNotification', (event, args) => {
|
||||
const notifiy = new Notification(args);
|
||||
notifiy.addListener('click', _ => {
|
||||
mainWindow.webContents.send("clickNotification", args)
|
||||
})
|
||||
notifiy.addListener('reply', (event, reply) => {
|
||||
mainWindow.webContents.send("replyNotification", Object.assign(args, {reply}))
|
||||
})
|
||||
notifiy.show()
|
||||
utils.showNotification(args, mainWindow)
|
||||
event.returnValue = "ok"
|
||||
})
|
||||
|
||||
@ -1306,13 +1283,11 @@ ipcMain.on('updateQuitAndInstall', (event, args) => {
|
||||
// 启动更新子窗口
|
||||
createUpdaterWindow(args.updateTitle)
|
||||
|
||||
// 隐藏主窗口
|
||||
mainWindow.hide()
|
||||
|
||||
// 退出并安装更新
|
||||
setTimeout(_ => {
|
||||
mainWindow.hide()
|
||||
autoUpdater.quitAndInstall(true, true)
|
||||
}, 300)
|
||||
}, 600)
|
||||
})
|
||||
|
||||
//================================================================
|
||||
|
||||
211
electron/utils.js
vendored
211
electron/utils.js
vendored
@ -1,8 +1,14 @@
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const path = require('path')
|
||||
const dayjs = require("dayjs");
|
||||
const {shell, dialog, session} = require("electron");
|
||||
const http = require('http')
|
||||
const https = require('https')
|
||||
const crypto = require('crypto')
|
||||
const {shell, dialog, session, Notification} = require("electron");
|
||||
const loger = require("electron-log");
|
||||
|
||||
module.exports = {
|
||||
const utils = {
|
||||
/**
|
||||
* 时间对象
|
||||
* @param v
|
||||
@ -160,7 +166,7 @@ module.exports = {
|
||||
leftDelete(string, find, lower = false) {
|
||||
string += "";
|
||||
find += "";
|
||||
if (this.leftExists(string, find, lower)) {
|
||||
if (utils.leftExists(string, find, lower)) {
|
||||
string = string.substring(find.length)
|
||||
}
|
||||
return string ? string : '';
|
||||
@ -185,33 +191,33 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* 打开文件
|
||||
* @param path
|
||||
* @param filePath
|
||||
*/
|
||||
openFile(path) {
|
||||
if (!fs.existsSync(path)) {
|
||||
openFile(filePath) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return
|
||||
}
|
||||
shell.openPath(path).then(() => {
|
||||
shell.openPath(filePath).then(() => {
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除文件夹及文件
|
||||
* @param path
|
||||
* @param filePath
|
||||
*/
|
||||
deleteFile(path) {
|
||||
deleteFile(filePath) {
|
||||
let files = [];
|
||||
if (fs.existsSync(path)) {
|
||||
files = fs.readdirSync(path);
|
||||
files.forEach(function (file, index) {
|
||||
let curPath = path + "/" + file;
|
||||
if (fs.existsSync(filePath)) {
|
||||
files = fs.readdirSync(filePath);
|
||||
files.forEach(function (file) {
|
||||
let curPath = filePath + "/" + file;
|
||||
if (fs.statSync(curPath).isDirectory()) {
|
||||
deleteFile(curPath);
|
||||
utils.deleteFile(curPath);
|
||||
} else {
|
||||
fs.unlinkSync(curPath);
|
||||
}
|
||||
});
|
||||
fs.rmdirSync(path);
|
||||
fs.rmdirSync(filePath);
|
||||
}
|
||||
},
|
||||
|
||||
@ -225,14 +231,14 @@ module.exports = {
|
||||
let rs = fs.createReadStream(srcPath)
|
||||
rs.on('error', function (err) {
|
||||
if (err) {
|
||||
console.log('read error', srcPath)
|
||||
loger.log('read error', srcPath)
|
||||
}
|
||||
cb && cb(err)
|
||||
})
|
||||
let ws = fs.createWriteStream(tarPath)
|
||||
ws.on('error', function (err) {
|
||||
if (err) {
|
||||
console.log('write error', tarPath)
|
||||
loger.log('write error', tarPath)
|
||||
}
|
||||
cb && cb(err)
|
||||
})
|
||||
@ -296,7 +302,7 @@ module.exports = {
|
||||
const contents = app.webContents
|
||||
if (contents != null) {
|
||||
contents.executeJavaScript('if(typeof window.__onBeforeUnload === \'function\'){window.__onBeforeUnload()}', true).then(options => {
|
||||
if (this.isJson(options)) {
|
||||
if (utils.isJson(options)) {
|
||||
let choice = dialog.showMessageBoxSync(app, options)
|
||||
if (choice === 1) {
|
||||
contents.executeJavaScript('if(typeof window.__removeBeforeUnload === \'function\'){window.__removeBeforeUnload()}', true).catch(() => {});
|
||||
@ -414,7 +420,7 @@ module.exports = {
|
||||
* electron15 后,解决跨域cookie无法携带,
|
||||
*/
|
||||
useCookie() {
|
||||
const filter = {urls: ['https://*/*']};
|
||||
const filter = {urls: ['https://*/*', 'http://*/*']};
|
||||
session.defaultSession.webRequest.onHeadersReceived(filter, (details, callback) => {
|
||||
if (details.responseHeaders && details.responseHeaders['Set-Cookie']) {
|
||||
for (let i = 0; i < details.responseHeaders['Set-Cookie'].length; i++) {
|
||||
@ -436,5 +442,170 @@ module.exports = {
|
||||
} else {
|
||||
return input.meta
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* MIME类型判断
|
||||
* @param filePath
|
||||
* @returns {*|string}
|
||||
*/
|
||||
getMimeType(filePath) {
|
||||
const ext = path.extname(filePath).toLowerCase()
|
||||
const mimeTypes = {
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
'.gif': 'image/gif',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.webp': 'image/webp'
|
||||
}
|
||||
return mimeTypes[ext] || 'application/octet-stream'
|
||||
},
|
||||
|
||||
/**
|
||||
* 显示系统通知
|
||||
* @param {Object} args - 通知参数
|
||||
* @param {string} args.title - 通知标题
|
||||
* @param {string} args.body - 通知内容
|
||||
* @param {string} [args.icon] - 通知图标路径或URL
|
||||
* @param {Electron.BrowserWindow} [window] - 主窗口实例
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async showNotification(args, window = null) {
|
||||
try {
|
||||
// 如果是网络图片,进行缓存处理(仅Windows)
|
||||
if (process.platform === 'win32' && args.icon && /^https?:\/\//i.test(args.icon)) {
|
||||
args.icon = await utils.getCachedImage(args.icon);
|
||||
}
|
||||
|
||||
const notifiy = new Notification(args);
|
||||
notifiy.addListener('click', _ => {
|
||||
if (window && window.webContents) {
|
||||
window.webContents.send("clickNotification", args)
|
||||
if (!window.isVisible()) {
|
||||
window.show();
|
||||
}
|
||||
window.focus();
|
||||
}
|
||||
})
|
||||
notifiy.addListener('reply', (event, reply) => {
|
||||
if (window && window.webContents) {
|
||||
window.webContents.send("replyNotification", Object.assign(args, {reply}))
|
||||
}
|
||||
})
|
||||
notifiy.show()
|
||||
} catch (error) {
|
||||
loger.error('显示通知失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取缓存的图片路径
|
||||
* @param {string} imageUrl - 图片URL
|
||||
* @returns {Promise<string>} 缓存的图片路径
|
||||
*/
|
||||
async getCachedImage(imageUrl) {
|
||||
// 生成图片URL的唯一标识
|
||||
const urlHash = crypto.createHash('md5').update(imageUrl).digest('hex');
|
||||
const cacheDir = path.join(os.tmpdir(), 'dootask-cache', 'images');
|
||||
const cachePath = path.join(cacheDir, `${urlHash}.png`);
|
||||
|
||||
try {
|
||||
// 确保缓存目录存在
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
fs.mkdirSync(cacheDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 检查缓存是否存在
|
||||
if (!fs.existsSync(cachePath)) {
|
||||
await utils.downloadImage(imageUrl, cachePath);
|
||||
}
|
||||
|
||||
return cachePath;
|
||||
} catch (error) {
|
||||
loger.error('处理缓存图片失败:', error);
|
||||
return ''; // 返回空字符串,通知将使用默认图标
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 下载图片
|
||||
* @param {string} url - 图片URL
|
||||
* @param {string} filePath - 保存路径
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
downloadImage(url, filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const file = fs.createWriteStream(filePath);
|
||||
|
||||
// 根据协议选择http或https
|
||||
const protocol = url.startsWith('https') ? https : http;
|
||||
|
||||
const request = protocol.get(url, {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
||||
}
|
||||
}, (response) => {
|
||||
// 处理重定向
|
||||
if (response.statusCode === 301 || response.statusCode === 302) {
|
||||
file.close();
|
||||
fs.unlink(filePath, () => {});
|
||||
return utils.downloadImage(response.headers.location, filePath)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
}
|
||||
|
||||
// 检查内容类型
|
||||
const contentType = response.headers['content-type'];
|
||||
if (!contentType || !contentType.startsWith('image/')) {
|
||||
file.close();
|
||||
fs.unlink(filePath, () => {});
|
||||
reject(new Error(`非图片类型: ${contentType}`));
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
file.close();
|
||||
fs.unlink(filePath, () => {});
|
||||
reject(new Error(`下载失败,状态码: ${response.statusCode}`));
|
||||
return;
|
||||
}
|
||||
|
||||
let downloadedBytes = 0;
|
||||
response.on('data', (chunk) => {
|
||||
downloadedBytes += chunk.length;
|
||||
});
|
||||
|
||||
response.pipe(file);
|
||||
|
||||
file.on('finish', () => {
|
||||
// 检查文件大小
|
||||
if (downloadedBytes === 0) {
|
||||
file.close();
|
||||
fs.unlink(filePath, () => {});
|
||||
reject(new Error('下载的文件大小为0'));
|
||||
return;
|
||||
}
|
||||
file.close();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
request.on('error', (err) => {
|
||||
file.close();
|
||||
fs.unlink(filePath, () => {});
|
||||
reject(err);
|
||||
});
|
||||
|
||||
// 设置超时
|
||||
request.setTimeout(30000, () => {
|
||||
request.destroy();
|
||||
file.close();
|
||||
fs.unlink(filePath, () => {});
|
||||
reject(new Error('下载超时'));
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = utils;
|
||||
|
||||
@ -1075,32 +1075,26 @@ export default {
|
||||
const body = $A.getMsgSimpleDesc(data);
|
||||
this.__notificationId = id;
|
||||
//
|
||||
const notificationFuncA = (title) => {
|
||||
if (dialog_type === 'group') {
|
||||
let tempUser = this.cacheUserBasic.find(item => item.userid == userid);
|
||||
if (tempUser) {
|
||||
notificationFuncB(`${title} (${tempUser.nickname})`)
|
||||
} else {
|
||||
this.$store.dispatch("call", {
|
||||
const notificationFuncA = async (title) => {
|
||||
let tempUser = this.cacheUserBasic.find(item => item.userid == userid);
|
||||
if (!tempUser) {
|
||||
try {
|
||||
const {data} = await this.$store.dispatch("call", {
|
||||
url: 'users/basic',
|
||||
data: {
|
||||
userid: [userid]
|
||||
},
|
||||
skipAuthError: true
|
||||
}).then(({data}) => {
|
||||
tempUser = data.find(item => item.userid == userid);
|
||||
if (tempUser) {
|
||||
notificationFuncB(`${title} (${tempUser.nickname})`)
|
||||
}
|
||||
}).catch(_ => {
|
||||
notificationFuncB(title)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notificationFuncB(title)
|
||||
tempUser = data.find(item => item.userid == userid);
|
||||
} catch (_) {}
|
||||
}
|
||||
if (dialog_type === 'group' && tempUser) {
|
||||
title = `${title} (${tempUser.nickname})`
|
||||
}
|
||||
notificationFuncB(title, tempUser?.userimg)
|
||||
}
|
||||
const notificationFuncB = (title) => {
|
||||
const notificationFuncB = (title, userimg) => {
|
||||
if (this.__notificationId === id) {
|
||||
this.__notificationId = null
|
||||
if (this.$isEEUiApp) {
|
||||
@ -1115,7 +1109,7 @@ export default {
|
||||
})
|
||||
} else if (this.$Electron) {
|
||||
this.$Electron.sendMessage('openNotification', {
|
||||
icon: $A.originUrl('images/logo.png'),
|
||||
icon: userimg || $A.originUrl('images/logo.png'),
|
||||
title,
|
||||
body,
|
||||
data,
|
||||
@ -1125,7 +1119,7 @@ export default {
|
||||
})
|
||||
} else {
|
||||
this.notificationManage.replaceOptions({
|
||||
icon: $A.originUrl('images/logo.png'),
|
||||
icon: userimg || $A.originUrl('images/logo.png'),
|
||||
body: body,
|
||||
data: data,
|
||||
tag: "dialog",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user