no message

This commit is contained in:
kuaifan 2022-01-10 23:54:48 +08:00
parent 79a94d25bd
commit 1d20f529a0
7 changed files with 364 additions and 235 deletions

112
electron/build.js vendored
View File

@ -3,103 +3,11 @@ const fse = require('fs-extra');
const path = require('path')
const inquirer = require('inquirer');
const child_process = require('child_process');
const utils = require('./utils');
const config = require('../package.json')
const argv = process.argv;
const env = require('dotenv').config({ path: './.env' })
/**
* 删除文件夹及文件
* @param path
*/
function deleteFile(path) {
let files = [];
if (fs.existsSync(path)) {
files = fs.readdirSync(path);
files.forEach(function (file, index) {
let curPath = path + "/" + file;
if (fs.statSync(curPath).isDirectory()) {
deleteFile(curPath);
} else {
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
}
/**
* 复制文件
* @param srcPath
* @param tarPath
* @param cb
*/
function copyFile(srcPath, tarPath, cb) {
let rs = fs.createReadStream(srcPath)
rs.on('error', function (err) {
if (err) {
console.log('read error', srcPath)
}
cb && cb(err)
})
let ws = fs.createWriteStream(tarPath)
ws.on('error', function (err) {
if (err) {
console.log('write error', tarPath)
}
cb && cb(err)
})
ws.on('close', function (ex) {
cb && cb(ex)
})
rs.pipe(ws)
}
/**
* 给地址加上前后
* @param str
* @returns {string}
*/
function formatUrl(str) {
let url;
if (str.substring(0, 7) === "http://" ||
str.substring(0, 8) === "https://") {
url = str.trim();
} else {
url = "http://" + str.trim();
}
if (url.substring(url.length - 1) != "/") {
url += "/"
}
return url;
}
/**
* 正则提取域名
* @param weburl
* @returns {string|string}
*/
function getDomain(weburl) {
let urlReg = /http(s)?:\/\/([^\/]+)/i;
let domain = weburl.match(urlReg);
return ((domain != null && domain.length > 0) ? domain[2] : "");
}
/**
* 右边是否包含
* @param string
* @param find
* @returns {boolean}
*/
function rightExists(string, find) {
string += "";
find += "";
return (string.substring(string.length - find.length) === find);
}
/** ***************************************************************************************************/
/** ***************************************************************************************************/
/** ***************************************************************************************************/
const electronDir = path.resolve(__dirname, "public");
const nativeCachePath = path.resolve(__dirname, ".native");
const devloadCachePath = path.resolve(__dirname, ".devload");
@ -108,7 +16,7 @@ const packageBakFile = path.resolve(__dirname, "package-bak.json");
const platform = ["build-mac", "build-mac-arm", "build-win"];
// 生成配置、编译应用
function start(data, publish) {
function startBuild(data, publish) {
console.log("Name: " + data.name);
console.log("AppId: " + data.id);
console.log("Version: " + config.version);
@ -117,10 +25,10 @@ function start(data, publish) {
title: data.name,
version: config.version,
origin: "./",
apiUrl: formatUrl(data.url) + "api/",
apiUrl: utils.formatUrl(data.url) + "api/",
}
fs.writeFileSync(electronDir + "/config.js", "window.systemInformation = " + JSON.stringify(systemInfo, null, 2), 'utf8');
fs.writeFileSync(nativeCachePath, formatUrl(data.url));
fs.writeFileSync(nativeCachePath, utils.formatUrl(data.url));
fs.writeFileSync(devloadCachePath, "", 'utf8');
// index.html
let indexFile = path.resolve(electronDir, "index.html");
@ -134,8 +42,8 @@ function start(data, publish) {
econfig.name = data.name;
econfig.version = config.version;
econfig.build.appId = data.id;
econfig.build.artifactName = getDomain(data.url) + "-v${version}-${os}-${arch}.${ext}";
econfig.build.nsis.artifactName = getDomain(data.url) + "-v${version}-${os}-${arch}.${ext}";
econfig.build.artifactName = utils.getDomain(data.url) + "-v${version}-${os}-${arch}.${ext}";
econfig.build.nsis.artifactName = utils.getDomain(data.url) + "-v${version}-${os}-${arch}.${ext}";
econfig.build.pkg.mustClose = [data.id];
fs.writeFileSync(packageFile, JSON.stringify(econfig, null, 2), 'utf8');
// build
@ -146,7 +54,7 @@ function start(data, publish) {
if (["dev"].includes(argv[2])) {
// 开发模式
fs.writeFileSync(devloadCachePath, formatUrl("127.0.0.1:" + env.parsed.APP_PORT), 'utf8');
fs.writeFileSync(devloadCachePath, utils.formatUrl("127.0.0.1:" + env.parsed.APP_PORT), 'utf8');
child_process.spawn("npx", ["mix", "watch", "--hot", "--", "--env", "--electron"], {stdio: "inherit"});
child_process.spawn("npm", ["run", "start-quiet"], {stdio: "inherit", cwd: "electron"});
} else if (platform.includes(argv[2])) {
@ -154,7 +62,7 @@ if (["dev"].includes(argv[2])) {
config.app.sites.forEach((data) => {
if (data.name && data.id && data.url) {
data.platform = argv[2];
start(data, true)
startBuild(data, true)
}
})
} else {
@ -171,7 +79,7 @@ if (["dev"].includes(argv[2])) {
return undefined;
},
validate: function (value) {
if (!rightExists(value, "/")) {
if (!utils.rightExists(value, "/")) {
return '网址必须以 "/" 结尾';
}
return value !== ''
@ -198,7 +106,7 @@ if (["dev"].includes(argv[2])) {
];
inquirer.prompt(questions).then(answers => {
answers.platform.forEach(platform => {
start({
startBuild({
"name": config.name,
"id": config.app.id,
"url": answers.website,

77
electron/main.js vendored
View File

@ -4,19 +4,24 @@ const path = require('path')
const XLSX = require('xlsx');
const {app, BrowserWindow, ipcMain, dialog} = require('electron')
const utils = require('./utils');
const config = require('./package.json');
const log = require("electron-log");
let mainWindow = null,
subWindow = [],
downloadList = [],
willQuitApp = false,
inheritClose = false,
devloadUrl = "",
devloadCachePath = path.resolve(__dirname, ".devload"),
devloadUrl = "";
downloadList = [],
downloadCacheFile = path.join(app.getPath('cache'), config.name + '.downloadCache');
if (fs.existsSync(devloadCachePath)) {
devloadUrl = fs.readFileSync(devloadCachePath, 'utf8')
}
if (fs.existsSync(downloadCacheFile)) {
downloadList = utils.jsonParse(fs.readFileSync(downloadCacheFile, 'utf8'), [])
}
function createMainWindow() {
mainWindow = new BrowserWindow({
@ -64,7 +69,7 @@ function createMainWindow() {
})
mainWindow.webContents.session.on('will-download', (event, item) => {
item.setSavePath(path.join(app.getPath('temp'), item.getFilename()));
item.setSavePath(path.join(app.getPath('cache'), item.getFilename()));
item.on('done', (event, state) => {
try {
const info = {
@ -87,15 +92,14 @@ function createMainWindow() {
download.info = info
}
})
fs.writeFileSync(downloadCacheFile, utils.jsonStringify(downloadList), 'utf8');
} else {
// 下载失败
info.chain.some(url => {
downloadList = downloadList.filter(item => item.url != url)
})
}
} catch (e) {
//
}
} catch (e) { }
})
})
}
@ -184,12 +188,19 @@ app.on('before-quit', () => {
willQuitApp = true
})
/**
* 继承关闭窗口事件
*/
ipcMain.on('inheritClose', (event) => {
inheritClose = true
event.returnValue = "ok"
})
ipcMain.on('downloadURL', (event, args) => {
/**
* 下载文件
* @param args {url}
*/
ipcMain.on('downloadFile', (event, args) => {
const download = downloadList.find(({url}) => url == args.url);
if (download) {
if (download.status == "completed") {
@ -212,21 +223,35 @@ ipcMain.on('downloadURL', (event, args) => {
event.returnValue = "ok"
})
/**
* 打开文件
* @param args {path}
*/
ipcMain.on('openFile', (event, args) => {
utils.openFile(args.path)
event.returnValue = "ok"
})
/**
* 退出客户端
*/
ipcMain.on('windowQuit', (event) => {
event.returnValue = "ok"
app.quit();
})
/**
* 创建路由窗口
* @param args {path, ?}
*/
ipcMain.on('windowRouter', (event, args) => {
createSubWindow(args)
event.returnValue = "ok"
})
/**
* 隐藏窗口mac隐藏其他关闭
*/
ipcMain.on('windowHidden', (event) => {
if (process.platform === 'darwin') {
app.hide();
@ -236,12 +261,19 @@ ipcMain.on('windowHidden', (event) => {
event.returnValue = "ok"
})
/**
* 关闭窗口
*/
ipcMain.on('windowClose', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
win.close()
event.returnValue = "ok"
})
/**
* 设置窗口尺寸
* @param args {width, height, autoZoom, minWidth, minHeight, maxWidth, maxHeight}
*/
ipcMain.on('windowSize', (event, args) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win) {
@ -277,23 +309,34 @@ ipcMain.on('windowSize', (event, args) => {
event.returnValue = "ok"
})
/**
* 设置窗口最小尺寸
* @param args {minWidth, minHeight}
*/
ipcMain.on('windowMinSize', (event, args) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win) {
win.setMinimumSize(args.width || win.getMinimumSize()[0], args.height || win.getMinimumSize()[1])
win.setMinimumSize(args.minWidth || win.getMinimumSize()[0], args.minHeight || win.getMinimumSize()[1])
}
event.returnValue = "ok"
})
/**
* 设置窗口最大尺寸
* @param args {maxWidth, maxHeight}
*/
ipcMain.on('windowMaxSize', (event, args) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win) {
win.setMaximumSize(args.width || win.getMaximumSize()[0], args.height || win.getMaximumSize()[1])
win.setMaximumSize(args.maxWidth || win.getMaximumSize()[0], args.maxHeight || win.getMaximumSize()[1])
}
event.returnValue = "ok"
})
ipcMain.on('windowCenter', (event, args) => {
/**
* 窗口居中
*/
ipcMain.on('windowCenter', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win) {
win.center();
@ -301,6 +344,9 @@ ipcMain.on('windowCenter', (event, args) => {
event.returnValue = "ok"
})
/**
* 窗口最大化或恢复
*/
ipcMain.on('windowMax', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win.isMaximized()) {
@ -311,6 +357,10 @@ ipcMain.on('windowMax', (event) => {
event.returnValue = "ok"
})
/**
* 给主窗口发送信息
* @param args {channel, data}
*/
ipcMain.on('sendForwardMain', (event, args) => {
if (mainWindow) {
mainWindow.webContents.send(args.channel, args.data)
@ -318,6 +368,10 @@ ipcMain.on('sendForwardMain', (event, args) => {
event.returnValue = "ok"
})
/**
* 设置Dock标记
* @param args
*/
ipcMain.on('setDockBadge', (event, args) => {
if(process.platform !== 'darwin'){
// Mac only
@ -331,6 +385,9 @@ ipcMain.on('setDockBadge', (event, args) => {
event.returnValue = "ok"
})
/**
* 保存sheets
*/
ipcMain.on('saveSheet', (event, data, filename, opts) => {
const EXTENSIONS = "xls|xlsx|xlsm|xlsb|xml|csv|txt|dif|sylk|slk|prn|ods|fods|htm|html".split("|");
dialog.showSaveDialog({

155
electron/utils.js vendored
View File

@ -2,18 +2,73 @@ const fs = require("fs");
const {shell} = require("electron");
module.exports = {
/**
* 是否数组
* @param obj
* @returns {boolean}
*/
isArray(obj) {
return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == '[object array]' && typeof obj.length == "number";
},
/**
* 是否数组对象
* @param obj
* @returns {boolean}
*/
isJson(obj) {
return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && typeof obj.length == "undefined";
},
/**
* 将一个 JSON 字符串转换为对象已try
* @param str
* @param defaultVal
* @returns {*}
*/
jsonParse(str, defaultVal = undefined) {
if (str === null) {
return defaultVal ? defaultVal : {};
}
if (typeof str === "object") {
return str;
}
try {
return JSON.parse(str.replace(/\n/g,"\\n").replace(/\r/g,"\\r"));
} catch (e) {
return defaultVal ? defaultVal : {};
}
},
/**
* JavaScript 值转换为 JSON 字符串已try
* @param json
* @param defaultVal
* @returns {string}
*/
jsonStringify(json, defaultVal = undefined) {
if (typeof json !== 'object') {
return json;
}
try{
return JSON.stringify(json);
}catch (e) {
return defaultVal ? defaultVal : "";
}
},
/**
* 随机数字
* @param str
* @param fixed
* @returns {number}
*/
runNum(str, fixed) {
runNum(str, fixed = null) {
let _s = Number(str);
if (_s + "" === "NaN") {
_s = 0;
}
if (/^[0-9]*[1-9][0-9]*$/.test(fixed)) {
if (fixed && /^[0-9]*[1-9][0-9]*$/.test(fixed)) {
_s = _s.toFixed(fixed);
let rs = _s.indexOf('.');
if (rs < 0) {
@ -43,7 +98,7 @@ module.exports = {
},
/**
* 字符串包含
* 字符串是否包含
* @param string
* @param find
* @param lower
@ -92,6 +147,23 @@ module.exports = {
return string ? string : '';
},
/**
* 字符串是否右边包含
* @param string
* @param find
* @param lower
* @returns {boolean}
*/
rightExists(string, find, lower = false) {
string += "";
find += "";
if (lower !== true) {
string = string.toLowerCase();
find = find.toLowerCase();
}
return (string.substring(string.length - find.length) === find);
},
/**
* 打开文件
* @param path
@ -103,4 +175,81 @@ module.exports = {
shell.openPath(path).then(() => {
})
},
/**
* 删除文件夹及文件
* @param path
*/
deleteFile(path) {
let files = [];
if (fs.existsSync(path)) {
files = fs.readdirSync(path);
files.forEach(function (file, index) {
let curPath = path + "/" + file;
if (fs.statSync(curPath).isDirectory()) {
deleteFile(curPath);
} else {
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
},
/**
* 复制文件
* @param srcPath
* @param tarPath
* @param cb
*/
copyFile(srcPath, tarPath, cb) {
let rs = fs.createReadStream(srcPath)
rs.on('error', function (err) {
if (err) {
console.log('read error', srcPath)
}
cb && cb(err)
})
let ws = fs.createWriteStream(tarPath)
ws.on('error', function (err) {
if (err) {
console.log('write error', tarPath)
}
cb && cb(err)
})
ws.on('close', function (ex) {
cb && cb(ex)
})
rs.pipe(ws)
},
/**
* 给地址加上前后
* @param str
* @returns {string}
*/
formatUrl(str) {
let url;
if (str.substring(0, 7) === "http://" ||
str.substring(0, 8) === "https://") {
url = str.trim();
} else {
url = "http://" + str.trim();
}
if (url.substring(url.length - 1) != "/") {
url += "/"
}
return url;
},
/**
* 正则提取域名
* @param weburl
* @returns {string|string}
*/
getDomain(weburl) {
let urlReg = /http(s)?:\/\/([^\/]+)/i;
let domain = weburl.match(urlReg);
return ((domain != null && domain.length > 0) ? domain[2] : "");
},
}

View File

@ -1,6 +1,6 @@
<template>
<div v-if="status && !$store.state.windowMax768" class="common-app-down">
<div v-if="$Electron" class="common-app-down-link" @click="installApplication">
<div v-if="showButton" class="common-app-down" :data-route="$route.name">
<div v-if="$Electron" class="common-app-down-link" @click="releasesNotification">
<Icon type="md-download"/> {{$L(repoTitle)}}
</div>
<a v-else class="common-app-down-link" :href="releases.html_url" target="_blank">
@ -36,51 +36,7 @@ export default {
this.$Electron.ipcRenderer.on('downloadDone', (event, args) => {
if (args.name == this.repoData.name) {
this.downInfo = args;
const h = this.$createElement;
window.__appNotification && window.__appNotification.close();
window.__appNotification = Notification({
title: this.$L("更新提示"),
duration: 0,
position: "bottom-right",
customClass: "common-app-down-notification",
onClose: () => {
this.status = 2;
},
message: h('span', [
h('span', [
h('span', this.$L('发现新版本') + ": "),
h('Tag', {
props: {
color: 'volcano'
}
}, this.releases.tag_name)
]),
h('MarkdownPreview', {
class: 'common-app-down-body',
props: {
initialValue: this.releases.body
}
}),
h('div', {
class: 'common-app-down-link',
on: {
click: () => {
this.installApplication();
}
},
}, [
h('Icon', {
props: {
type: 'md-download'
},
style: {
marginRight: '5px'
}
}),
h('span', this.$L('立即安装'))
]),
])
});
this.releasesNotification()
}
})
}
@ -88,74 +44,12 @@ export default {
computed: {
repoTitle() {
return this.status == 2 ? '更新客户端' : '客户端下载';
},
showButton() {
return this.status && !this.$store.state.windowMax768 && ['login', 'manage-dashboard'].includes(this.$route.name)
}
},
methods: {
getReleases() {
let cache = $A.getStorageJson("cacheAppdown");
if (cache.time && cache.time + 3600 > Math.round(new Date().getTime() / 1000)) {
this.releases = cache.data;
this.chackReleases()
return;
}
;(() => {
axios
.get("https://api.github.com/repos/" + this.repoName + "/releases/latest")
.then(({status, data}) => {
if (status === 200) {
$A.setStorage("cacheAppdown", {
time: Math.round(new Date().getTime() / 1000),
data: data
});
this.releases = data;
this.chackReleases()
}
});
})();
},
chackReleases() {
let hostName = window.location.hostname;
if (hostName == '127.0.0.1') {
hostName = "www.dootask.com"
}
if (this.$Electron) {
//
let match = (window.navigator.userAgent + "").match(/\s+(Main|Sub)TaskWindow\/(.*?)\/(.*?)\//)
if (!match) {
return;
}
let artifactName = null;
if (match[2] === 'darwin') {
artifactName = `${hostName}-${this.releases.tag_name}-mac-${match[3]}.pkg`;
} else if (match[2] === 'win32') {
artifactName = `${hostName}-${this.releases.tag_name}-win-${match[3]}.exe`;
} else {
return;
}
this.repoData = (this.releases.assets || []).find(({name}) => name == artifactName);
if (!this.repoData) {
return;
}
let currentVersion = window.systemInformation.version;
let latestVersion = $A.leftDelete(this.releases.tag_name.toLowerCase(), "v")
if (this.compareVersion(latestVersion, currentVersion) === 1) {
//
console.log("有新版本");
this.$Electron.ipcRenderer.send('downloadURL', {
url: this.repoData.browser_download_url
});
}
} else {
//
this.repoData = (this.releases.assets || []).find(({name}) => $A.strExists(name, hostName));
if (this.repoData) {
console.log("有客户端");
this.status = 1;
}
}
},
compareVersion(version1, version2) {
let pA = 0, pB = 0;
//
@ -200,6 +94,129 @@ export default {
return 0;
},
getReleases() {
if (this.status > 0) {
return;
}
//
let cache = $A.getStorageJson("cacheAppdown");
let timeout = 1800;
if (cache.time && cache.time + timeout > Math.round(new Date().getTime() / 1000)) {
this.releases = cache.data;
this.chackReleases()
return;
}
//
;(() => {
axios
.get("https://api.github.com/repos/" + this.repoName + "/releases/latest")
.then(({status, data}) => {
if (status === 200) {
$A.setStorage("cacheAppdown", {
time: Math.round(new Date().getTime() / 1000),
data: data
});
this.releases = data;
this.chackReleases();
setTimeout(this.getReleases, timeout)
}
});
})();
},
chackReleases() {
let hostName = window.location.hostname;
if (hostName == '127.0.0.1') {
hostName = "www.dootask.com"
}
if (this.$Electron) {
//
let match = (window.navigator.userAgent + "").match(/\s+(Main|Sub)TaskWindow\/(.*?)\/(.*?)\//)
if (!match) {
return;
}
let artifactName = null;
if (match[2] === 'darwin') {
artifactName = `${hostName}-${this.releases.tag_name}-mac-${match[3]}.pkg`;
} else if (match[2] === 'win32') {
artifactName = `${hostName}-${this.releases.tag_name}-win-${match[3]}.exe`;
} else {
return;
}
this.repoData = (this.releases.assets || []).find(({name}) => name == artifactName);
if (!this.repoData) {
return;
}
let currentVersion = window.systemInformation.version;
let latestVersion = $A.leftDelete(this.releases.tag_name.toLowerCase(), "v")
if (this.compareVersion(latestVersion, currentVersion) === 1) {
//
console.log("New version: " + latestVersion);
this.$Electron.ipcRenderer.send('downloadFile', {
url: this.repoData.browser_download_url
});
}
} else {
//
this.repoData = (this.releases.assets || []).find(({name}) => $A.strExists(name, hostName));
if (this.repoData) {
console.log("有客户端");
this.status = 1;
}
}
},
releasesNotification() {
if (this.downInfo.state != "completed") {
return;
}
const h = this.$createElement;
window.__appNotification && window.__appNotification.close();
window.__appNotification = Notification({
title: this.$L("更新提示"),
duration: 0,
position: "bottom-right",
customClass: "common-app-down-notification",
onClose: () => {
this.status = 2;
},
message: h('span', [
h('span', [
h('span', this.$L('发现新版本') + ": "),
h('Tag', {
props: {
color: 'volcano'
}
}, this.releases.tag_name)
]),
h('MarkdownPreview', {
class: 'common-app-down-body',
props: {
initialValue: this.releases.body
}
}),
h('div', {
class: 'common-app-down-link',
on: {
click: () => {
this.installApplication();
}
},
}, [
h('Icon', {
props: {
type: 'md-checkmark-circle-outline'
},
style: {
marginRight: '5px'
}
}),
h('span', this.$L('立即更新'))
]),
])
});
},
installApplication() {
if (!this.$Electron) {
return;

View File

@ -42,8 +42,8 @@
<div class="login-forgot">{{$L('忘记密码了?')}}<a href="javascript:void(0)" @click="forgotPassword">{{$L('重置密码')}}</a></div>
</div>
</div>
<div class="login-right-bottom">
<Button v-if="$Electron" icon="ios-globe-outline" type="primary" @click="onServerUrlInput">{{$L('自定义服务器')}}</Button>
<div v-if="$Electron" class="login-right-bottom">
<Button icon="ios-globe-outline" type="primary" @click="onServerUrlInput">{{$L('自定义服务器')}}</Button>
</div>
</div>
</template>

View File

@ -5,6 +5,10 @@
z-index: 1;
display: flex;
align-items: center;
transition: bottom 0.3s;
&[data-route=login] {
bottom: 75px;
}
}
.common-app-down-notification {

View File

@ -114,11 +114,5 @@
z-index: 1;
display: flex;
align-items: center;
.common-app-down {
position: static;
bottom: auto;
right: auto;
margin-left: 12px;
}
}
}