This commit is contained in:
24kycj
2026-05-12 21:42:42 +08:00
parent 31c9d64ba9
commit 68d1968376
21 changed files with 1120 additions and 273 deletions
+21 -13
View File
@@ -1,12 +1,19 @@
const fs = require("fs");
const path = require("path");
// 使用promisify方法来promise化指定方法
const { promisify } = require("util");
const stat = promisify(fs.stat);
const readdir = promisify(fs.readdir);
const IS_WEB = process.env.IS_WEB === 'true';
let fs, path, stat, readdir;
if (!IS_WEB) {
fs = require("fs");
path = require("path");
const { promisify } = require("util");
stat = promisify(fs.stat);
readdir = promisify(fs.readdir);
}
// 异步
// 异步(网页端不计算目录大小,直接 callback 0)
export async function calcSize(dirPath, callback) {
if (IS_WEB || !stat) {
callback(null, 0, dirPath);
return;
}
let fileSize = 0;
let error = null;
async function calc(dirPath) {
@@ -14,9 +21,7 @@ export async function calcSize(dirPath, callback) {
const statObj = await stat(dirPath);
if (statObj.isDirectory()) {
const files = await readdir(dirPath);
let dirs = files.map((item) => {
return path.join(dirPath, item);
});
let dirs = files.map((item) => path.join(dirPath, item));
let index = 0;
async function next() {
if (index < dirs.length) {
@@ -38,7 +43,9 @@ export async function calcSize(dirPath, callback) {
}
export function getFileName(name) {
return name.substring(name.lastIndexOf("\\") + 1);
if (!name) return '';
const i = Math.max(name.lastIndexOf('\\'), name.lastIndexOf('/'));
return i < 0 ? name : name.substring(i + 1);
}
export function getExtension(name) {
return name.substring(name.lastIndexOf(".") + 1);
@@ -52,7 +59,8 @@ export function bytesToSize(bytes) {
return (bytes / Math.pow(k, i)).toPrecision(3) + " " + sizes[i];
}
export function isFolder(path) {
let _stat = fs.lstatSync(path);
export function isFolder(filePath) {
if (IS_WEB || !fs) return false;
let _stat = fs.lstatSync(filePath);
return _stat.isDirectory();
}
@@ -52,15 +52,14 @@
</div>
</template>
<script>
const { dialog } = require("@electron/remote");
const fs = require("fs");
import platform from "@/platform";
import fileEmpty from "./fileEmpty";
import fileList from "./fileList";
import progressdialog from "./progressdialog";
import archiverdialog from "./archiverdialog";
import { calcSize, getFileName, isFolder } from "./calc";
import { copy } from "./copy";
import { zip } from "./archiver";
const copyFn = process.env.IS_WEB !== 'true' ? require("./copy").copy : null;
const zipFn = process.env.IS_WEB !== 'true' ? require("./archiver").zip : null;
export default {
name: "Files",
props: {
@@ -105,15 +104,15 @@ export default {
e.preventDefault();
// e.stopPropagation();
for (const f of e.dataTransfer.files) {
const pathKey = f.path || ('web://' + (f.name || 'file') + '_' + Date.now() + Math.random());
const isFolder = _this.dropFolderCheck(f);
_this.insertList({
name: getFileName(f.path),
path: f.path,
name: getFileName(f.path || f.name),
path: pathKey,
size: isFolder ? -1 : f.size,
folder: isFolder,
});
if (isFolder) {
if (isFolder && platform.hasNativeFs && platform.hasNativeFs()) {
calcSize(f.path, _this.folderCalcCallback);
}
}
@@ -140,45 +139,29 @@ export default {
},
addFile() {
const _this = this;
dialog
.showOpenDialog({
properties: ["multiSelections"],
})
.then(async (res) => {
for (const item of res.filePaths) {
await fs.stat(item, function (err, res) {
if (err) {
return false;
}
_this.insertList({
name: getFileName(item),
path: item,
size: res.size,
folder: false,
});
});
}
platform.showOpenFileDialog({ properties: ["multiSelections"] }).then((rel) => {
if (rel.file && !(platform.hasNativeFs && platform.hasNativeFs())) {
const pathKey = 'web://' + rel.file.name + '_' + Date.now();
_this.insertList({ name: getFileName(rel.file.name), path: pathKey, size: rel.file.size || 0, folder: false });
return;
}
(rel.filePaths || []).forEach((item) => {
platform.stat(item).then((res) => {
_this.insertList({ name: getFileName(item), path: item, size: res.size, folder: false });
}).catch(() => {});
});
}).catch(() => {});
},
addFolder() {
const _this = this;
dialog
.showOpenDialog({
properties: ["openDirectory", "multiSelections"],
})
.then((res) => {
for (const item of res.filePaths) {
const result = _this.insertList({
name: getFileName(item),
path: item,
size: -1,
folder: true,
});
if (result) {
calcSize(item, _this.folderCalcCallback);
}
platform.showOpenDirectoryDialog({ properties: ["openDirectory", "multiSelections"] }).then((res) => {
(res.filePaths || []).forEach((item) => {
const result = _this.insertList({ name: getFileName(item), path: item, size: -1, folder: true });
if (result && platform.hasNativeFs && platform.hasNativeFs()) {
calcSize(item, _this.folderCalcCallback);
}
});
}).catch(() => {});
},
folderCalcCallback(err, res, path) {
if (this.filesList[path]) {
@@ -194,17 +177,9 @@ export default {
this.allNumber--;
},
dropFolderCheck(f) {
//T是文件夹 F不是文件夹
//拖放无法从参数判断是否为文件夹,需要额外处理
if (f.size != 0 && f.size != 4096) {
//返回大小不是0,则不是文件夹
return false;
}
if (f.type != "") {
//如果type不是空,则不是文件夹
return false;
}
return isFolder(f.path);
if (f.size != 0 && f.size != 4096) return false;
if (f.type != "") return false;
return isFolder(f.path || f.name || '');
},
sizeChange(size) {
this.allSize = this.allSize + size;
@@ -255,18 +230,13 @@ export default {
if (this.isCopy) {
this.overNumber = 0;
this.changeProgressvisible(true);
if (!copyFn) {
this.$message && this.$message({ message: '仅桌面端支持', type: 'warning' });
return;
}
for (let i in this.filesList) {
// const path = "D:\\copytest\\1\\" + this.filesList[i].name;
const path = this.copyPath + this.filesList[i].name;
console.log(this.copyPath);
console.log(path);
copy(
i,
path,
this.filesList[i].folder,
this.fileBack,
this.filesList[i]
);
copyFn(i, path, this.filesList[i].folder, this.fileBack, this.filesList[i]);
}
} else {
//2023-04-24修改为所有都只上传文件路径,不需要压缩
@@ -281,18 +251,24 @@ export default {
} else if (file_form == 1) {
//电子光盘
} else if (file_form == 2) {
//zip
if (!zipFn) {
this.$message && this.$message({ message: '仅桌面端支持', type: 'warning' });
return;
}
this.archiverIsover = false;
this.archiverIsfalse = false;
this.zip_path = "D:/archivertest/1.zip";
zip(this.filesList, this.zip_path, this.archiverBack, false);
zipFn(this.filesList, this.zip_path, this.archiverBack, false);
} else if (file_form == 3) {
//加密zip
if (!zipFn) {
this.$message && this.$message({ message: '仅桌面端支持', type: 'warning' });
return;
}
this.archiverIsover = false;
this.archiverIsfalse = false;
this.zip_path = "D:/archivertest/2.zip";
let password = "123456";
zip(this.filesList, this.zip_path, this.archiverBack, true, password);
zipFn(this.filesList, this.zip_path, this.archiverBack, true, password);
} else if (file_form == 4) {
//u盘
}
+37 -52
View File
@@ -52,8 +52,7 @@
</div>
</template>
<script>
const { dialog } = require("@electron/remote");
const fs = require("fs");
import platform from "@/platform";
import fileEmpty from "./fileEmpty";
import fileList from "./fileList";
import progressdialog from "./progressdialog";
@@ -109,15 +108,15 @@ export default {
e.preventDefault();
// e.stopPropagation();
for (const f of e.dataTransfer.files) {
const pathKey = f.path || ('web://' + (f.name || 'file') + '_' + Date.now() + Math.random());
const isFolder = _this.dropFolderCheck(f);
_this.insertList({
name: getFileName(f.path),
path: f.path,
name: getFileName(f.path || f.name),
path: pathKey,
size: isFolder ? -1 : f.size,
folder: isFolder,
});
if (isFolder) {
if (isFolder && platform.hasNativeFs && platform.hasNativeFs()) {
calcSize(f.path, _this.folderCalcCallback);
}
}
@@ -144,65 +143,51 @@ export default {
},
addFile() {
const _this = this;
console.log(_this.copyType)
const properties = _this.copyType == 2 ? [] : ["multiSelections"];
const filters = _this.copyType == 2 ? [{ name: '镜像文件', extensions: ['ISO', 'IMG'] }] : [{ name: '所有文件', extensions: ['*'] }]
dialog
.showOpenDialog({
filters,
properties
})
.then(async (rel) => {
console.log(rel);
for (const item of rel.filePaths) {
const stats = fs.statSync(item);
const filters = _this.copyType == 2 ? [{ name: '镜像文件', extensions: ['ISO', 'IMG'] }] : [{ name: '所有文件', extensions: ['*'] }];
platform.showOpenFileDialog({ filters }).then((rel) => {
if (rel.file && !platform.hasNativeFs()) {
const pathKey = 'web://' + rel.file.name + '_' + Date.now();
_this.insertList({ name: getFileName(rel.file.name), path: pathKey, size: rel.file.size || 0, folder: false });
return;
}
const paths = rel.filePaths || [];
paths.forEach((item) => {
try {
const stats = platform.statSync(item);
if (stats.isFile()) {
await fs.stat(item, function (err, res) {
if (err) {
return false;
}
platform.stat(item).then((res) => {
if (_this.copyType == 2) {
_this.filesList = {}
_this.filesList = {};
_this.allNumber = 1;
_this.filesList[item] = {
name: getFileName(item),
path: item,
size: res.size,
folder: false,
};
calcSize(item, _this.folderCalcCallback);
return
_this.$set(_this.filesList, item, { name: getFileName(item), path: item, size: res.size, folder: false });
_this.sizeChange(res.size);
return;
}
_this.insertList({
name: getFileName(item),
path: item,
size: res.size,
folder: false,
});
_this.insertList({ name: getFileName(item), path: item, size: res.size, folder: false });
});
}
} catch (e) {
console.error(e);
}
});
}).catch(() => {});
},
addFolder() {
const _this = this;
dialog
.showOpenDialog({
properties: ["openDirectory", "multiSelections"],
})
.then((res) => {
for (const item of res.filePaths) {
const result = _this.insertList({
name: getFileName(item),
path: item,
size: -1,
folder: true,
});
if (result) {
calcSize(item, _this.folderCalcCallback);
}
platform.showOpenDirectoryDialog({ properties: ["openDirectory", "multiSelections"] }).then((res) => {
const filePaths = res.filePaths || [];
filePaths.forEach((item) => {
const result = _this.insertList({
name: getFileName(item),
path: item,
size: -1,
folder: true,
});
if (result && platform.hasNativeFs && platform.hasNativeFs()) {
calcSize(item, _this.folderCalcCallback);
}
});
}).catch(() => {});
},
folderCalcCallback(err, res, path) {
if (this.filesList[path]) {
+67 -92
View File
@@ -163,7 +163,8 @@
<div @click="openFile" class="work_right_top_file">···</div>
</el-tooltip>
</div>
<div @click="openDesign" class="work_right_top_add">新建标签</div>
<div v-if="hasRunCmd" @click="openDesign" class="work_right_top_add">新建标签</div>
<el-tooltip v-else content="仅桌面端支持" placement="bottom"><span class="work_right_top_add work_right_top_add_disabled">新建标签</span></el-tooltip>
</div>
<div class="wook_soon" :class="{ wook_soon1: !showList }">
<div class="flex_box flex_row_center wook_soon_top">
@@ -324,18 +325,12 @@
</template>
<script>
let fs = require('fs')
let path = require('path')
const { app, dialog } = require('@electron/remote')
const { exec } = require('child_process')
const { ipcRenderer } = require('electron')
import { mapGetters } from 'vuex'
const dayjs = require('dayjs')
import platform from '@/platform'
import fileEmpty from './files/fileEmpty'
import files from './files/file'
const exePath = !app.isPackaged ? process.cwd() : path.dirname(process.execPath)
export default {
name: 'UserInfo',
components: { fileEmpty, files },
@@ -600,32 +595,18 @@ export default {
that.csvForm.req_info.uuid = that.form.task_uuid
}
if (that.saveWorkList.json_file) {
fs.readFile(that.saveWorkList.json_file, 'utf8', (err, data) => {
if (err) {
console.error('读取文件时出错:', err)
return
}
let name = that.saveWorkList.json_file.split('\\')[1]
that.fileLists[0] = new File([data], name, {
type: ''
})
// that.readFile(data)
})
platform.readFile(that.saveWorkList.json_file, 'utf8').then((data) => {
const name = (that.saveWorkList.json_file || '').split(/[/\\]/).pop() || 'file'
that.fileLists[0] = new File([data], name, { type: '' })
}).catch((err) => { console.error('读取文件时出错:', err) })
}
if (that.saveWorkList.udf_file) {
that.csvIsExist = true
console.log(that.saveWorkList.udf_file, that.csvIsExist)
fs.readFile(that.saveWorkList.udf_file, 'utf8', (err, data) => {
if (err) {
console.error('读取文件时出错:', err)
return
}
let name = that.saveWorkList.udf_file.split('\\')[1]
platform.readFile(that.saveWorkList.udf_file, 'utf8').then((data) => {
const name = (that.saveWorkList.udf_file || '').split(/[/\\]/).pop() || 'file'
that.file_name = name
that.fileLists[0] = new File([data], name, {
type: ''
})
})
that.fileLists[0] = new File([data], name, { type: '' })
}).catch((err) => { console.error('读取文件时出错:', err) })
}
}
})
@@ -634,6 +615,9 @@ export default {
},
computed: {
...mapGetters(['name', 'roles']),
hasRunCmd() {
return platform.hasRunCmd && platform.hasRunCmd()
},
file_percent() {
let disk = this.cd_types.find((item) => item.value === this.form.cd_type)
if (!disk) {
@@ -668,8 +652,7 @@ export default {
methods: {
// 右键事件
showContextMenu() {
console.log(123)
ipcRenderer.send('show-context-menu');
platform.showContextMenu()
},
// 进度条处理
format(percentage) {
@@ -691,15 +674,13 @@ export default {
sizeChange(size) {
this.totalSize = size
},
// 选择路径
// 选择路径(仅桌面端支持)
selectPath() {
dialog
.showOpenDialog({
properties: ["openDirectory"],
})
platform.showOpenDirectoryDialog({ properties: ['openDirectory'] })
.then((res) => {
if (res.filePaths[0]) this.form.archive_path = res.filePaths[0]
});
if (res.filePaths && res.filePaths[0]) this.form.archive_path = res.filePaths[0]
})
.catch(() => {})
},
async upload_over() { },
// 保存
@@ -735,21 +716,24 @@ export default {
save.soonImg = that.soonImg
save.soonList = that.soonList
const v = JSON.stringify(save)
dialog
.showSaveDialog({
platform
.showSaveFileDialog({
title: 'Save',
filters: [{ name: 'Soon Work', extensions: ['dwk'] }]
})
.then((result) => {
if (result.filePath == "") { return; }
if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) {
result.filePath += '.dwk';
let filePath = result.filePath || ''
if (!filePath) return
if (filePath.substring(filePath.length - 5).indexOf('.') == -1) {
filePath += '.dwk'
}
if (platform.hasNativeFs && platform.hasNativeFs()) {
platform.writeFileSync(filePath, v)
that.$notify({ message: '保存成功至' + filePath, type: 'success' })
} else {
platform.downloadFile(v, filePath)
that.$notify({ message: '已下载 ' + filePath, type: 'success' })
}
fs.writeFileSync(result.filePath, v)
that.$notify({
message: '保存成功至' + result.filePath,
type: 'success'
})
})
.catch((err) => {
console.log(err)
@@ -777,11 +761,16 @@ export default {
for (let key in files) {
let file = { ...files[key] }
try {
const fileStats = fs.statSync(files[key].path)
const fileStats = platform.statSync(files[key].path)
file.mtime = fileStats.mtimeMs
fileList.push(file)
} catch (error) {
console.error('Error reading file:', error)
if (platform.hasNativeFs && platform.hasNativeFs()) {
console.error('Error reading file:', error)
} else {
file.mtime = Date.now()
fileList.push(file)
}
}
}
let isISO = fileList.every(item => item.name.indexOf('.ISO') > -1 || item.name.indexOf('.IMG') > -1 || item.name.indexOf('.iso') > -1 || item.name.indexOf('.img') > -1)
@@ -981,41 +970,30 @@ export default {
})
}
},
// 获取模板文件列表
// 获取模板文件列表(仅桌面端有本地模板目录)
getTemplates() {
let that = this
const filePath = path.join(exePath, 'User Templates');
fs.readdir(filePath, (err, files) => {
if (err) {
console.log(err)
} else {
console.log(files)
const fileList = files.map((file) => {
return {
label: file,
value: path.join('User Templates', file)
}
})
that.templates = fileList
}
const that = this
const filePath = platform.pathJoin(platform.getAppRoot(), 'User Templates')
platform.readdir(filePath).then((files) => {
const fileList = (files || []).map((file) => ({
label: file,
value: platform.pathJoin('User Templates', file)
}))
that.templates = fileList
}).catch(() => {
that.templates = []
})
},
// 选择模板
// 选择模板(仅桌面端)
changeTemplate(e) {
let that = this
const fullPath = path.join(exePath, e)
const that = this
const fullPath = platform.pathJoin(platform.getAppRoot(), e)
that.form.json_file = fullPath
fs.readFile(fullPath, 'utf8', (err, data) => {
if (err) {
console.error('读取文件时出错:', err)
return
}
let name = path.basename(fullPath)
that.fileLists[0] = new File([data], name, {
type: ''
})
platform.readFile(fullPath, 'utf8').then((data) => {
const name = platform.pathBasename(fullPath)
that.fileLists[0] = new File([data], name, { type: '' })
that.readFile(data)
})
}).catch((err) => { console.error('读取文件时出错:', err) })
},
// 读取文件信息
readFile(data) {
@@ -1054,19 +1032,10 @@ export default {
that.file_name = null
that.file_name3 = '添加图片文件'
},
// 打开标签程序
// 打开标签程序(仅桌面端)
openDesign() {
// 启动exe程序
console.log('启动soondesign')
// exec('"D:\\Program Files\\Cardsoon\\SoonDesign\\SoonDesign.exe"', (error, stdout, stderr) => {
exec('soondesign', (error, stdout, stderr) => {
if (error) {
console.error(`执行的错误: ${error}`)
return
}
// console.log(`stdout: ${stdout}`)
// console.error(`stderr: ${stderr}`)
platform.runCmd('soondesign').catch((err) => {
console.error('执行的错误:', err)
})
},
// 文件上传处理
@@ -1502,6 +1471,12 @@ export default {
line-height: 40px;
text-align: center;
}
.work_right_top_add_disabled {
cursor: not-allowed;
background: #f5f5f5;
border-color: #ddd;
color: #999;
}
}
.wook_soon {
+3 -2
View File
@@ -9,7 +9,8 @@ import 'element-ui/lib/theme-chalk/index.css'
import App from './App'
import router from './router'
import store from './store'
import { accAdd, accSub, accMul, accDiv, filterSize, runCmd } from './utils'
import { accAdd, accSub, accMul, accDiv, filterSize } from './utils'
import { runCmd } from '@/platform'
import i18n from './lang' // internationalization
import './permission' // permission control
@@ -18,7 +19,7 @@ if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
Vue.http = Vue.prototype.$http = axios
Vue.config.productionTip = false
// 设置公共方法
// 设置公共方法runCmd 来自 platform,网页端为“仅桌面端支持”的 Promise.reject
Vue.prototype.$accAdd = accAdd
Vue.prototype.$accSub = accSub
Vue.prototype.$accMul = accMul
+138
View File
@@ -0,0 +1,138 @@
/**
* 桌面端平台实现:依赖 Node/Electron,仅在此文件中 require,且仅被 Electron 构建加载
*/
const fs = require('fs')
const path = require('path')
const { exec } = require('child_process')
const { ipcRenderer } = require('electron')
const { app, dialog } = require('@electron/remote')
export function getAppRoot() {
return !app.isPackaged ? process.cwd() : path.dirname(process.execPath)
}
export function getPlatform() {
const p = process.platform
if (p === 'win32') return 'windows'
if (p === 'darwin') return 'mac'
if (p === 'linux') return 'linux'
return 'unknown'
}
export function getBaseSize() {
const os = getPlatform()
const baseSizes = { windows: 1024, mac: 1024, linux: 1024 }
return baseSizes[os] || 1024
}
export function openHelp() {
ipcRenderer.send('open-help-file')
}
export function runCmd(cmd) {
return new Promise((resolve, reject) => {
exec(cmd, (err, stdout, stderr) => {
if (err) reject(err.message || err)
else if (stderr) reject(stderr)
else resolve(stdout)
})
})
}
export function showContextMenu() {
ipcRenderer.send('show-context-menu')
}
export function showOpenFileDialog(options = {}) {
return dialog.showOpenDialog(options).then(res => {
if (res.canceled || !res.filePaths || !res.filePaths[0]) {
return Promise.reject(new Error('取消选择'))
}
return { filePaths: res.filePaths, path: res.filePaths[0] }
})
}
export function showOpenDirectoryDialog(options = {}) {
const opts = { ...options, properties: ['openDirectory'].concat(options.properties || []) }
return dialog.showOpenDialog(opts).then(res => {
if (res.canceled || !res.filePaths || !res.filePaths[0]) {
return Promise.reject(new Error('取消选择'))
}
return { filePaths: res.filePaths, path: res.filePaths[0] }
})
}
export function showSaveFileDialog(options = {}) {
return dialog.showSaveDialog(options).then(result => {
if (result.canceled || result.filePath === '') {
return Promise.reject(new Error('取消保存'))
}
return { filePath: result.filePath }
})
}
export function readFile(filePath, encoding) {
return new Promise((resolve, reject) => {
fs.readFile(filePath, encoding || 'utf8', (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
export function readFileSync(filePath, encoding) {
return fs.readFileSync(filePath, encoding || 'utf8')
}
export function writeFile(filePath, content) {
return new Promise((resolve, reject) => {
fs.writeFile(filePath, content, err => {
if (err) reject(err)
else resolve()
})
})
}
export function writeFileSync(filePath, content) {
return fs.writeFileSync(filePath, content)
}
export function readdir(dirPath) {
return new Promise((resolve, reject) => {
fs.readdir(dirPath, (err, files) => {
if (err) reject(err)
else resolve(files)
})
})
}
export function stat(filePath) {
return new Promise((resolve, reject) => {
fs.stat(filePath, (err, stats) => {
if (err) reject(err)
else resolve(stats)
})
})
}
export function statSync(filePath) {
return fs.statSync(filePath)
}
export function hasNativeFs() {
return true
}
export function hasRunCmd() {
return true
}
/** 桌面端不需要触发下载,保存走 dialog + writeFile */
export function downloadFile(/* content, filename */) {
// no-op on desktop
}
/** path 工具:仅桌面端可用 */
export const pathJoin = path.join
export const pathDirname = path.dirname
export const pathBasename = path.basename
+31
View File
@@ -0,0 +1,31 @@
/**
* 平台抽象层入口:按构建环境导出桌面端或网页端实现,避免网页包中引入 Node/Electron
*/
const IS_WEB = process.env.IS_WEB === 'true'
const platform = IS_WEB ? require('./web.js') : require('./desktop.js')
export default platform
export const getAppRoot = platform.getAppRoot
export const getPlatform = platform.getPlatform
export const getBaseSize = platform.getBaseSize
export const openHelp = platform.openHelp
export const runCmd = platform.runCmd
export const showContextMenu = platform.showContextMenu
export const showOpenFileDialog = platform.showOpenFileDialog
export const showOpenDirectoryDialog = platform.showOpenDirectoryDialog
export const showSaveFileDialog = platform.showSaveFileDialog
export const downloadFile = platform.downloadFile
export const readFile = platform.readFile
export const readFileSync = platform.readFileSync
export const writeFile = platform.writeFile
export const writeFileSync = platform.writeFileSync
export const readdir = platform.readdir
export const stat = platform.stat
export const statSync = platform.statSync
export const hasNativeFs = platform.hasNativeFs
export const hasRunCmd = platform.hasRunCmd
export const pathJoin = platform.pathJoin || (() => '')
export const pathDirname = platform.pathDirname || (() => '')
export const pathBasename = platform.pathBasename || (p => p)
+187
View File
@@ -0,0 +1,187 @@
/**
* 网页端平台实现:不依赖 Node/Electron,使用浏览器 API 或占位
*/
// 可选:网页端帮助文档 URL,可由构建或运行时配置覆盖
const HELP_PDF_URL = typeof process !== 'undefined' && process.env.HELP_PDF_URL
? process.env.HELP_PDF_URL
: '/help/User Manual.pdf'
export function getAppRoot() {
return ''
}
export function getPlatform() {
return 'web'
}
export function getBaseSize() {
return 1024
}
export function openHelp() {
try {
window.open(HELP_PDF_URL, '_blank')
} catch (e) {
console.warn('openHelp:', e)
}
}
export function runCmd(/* cmd */) {
return Promise.reject(new Error('仅桌面端支持'))
}
export function showContextMenu() {
// no-op in web
}
/**
* 网页端:通过 input[type=file] 选文件,返回 { filePaths: [name], content }(无真实路径)
* @param {Object} options - { title?, filters: [{ name, extensions }] }
* @returns {Promise<{ filePaths: string[], content?: string }>}
*/
export function showOpenFileDialog(options = {}) {
return new Promise((resolve, reject) => {
const input = document.createElement('input')
input.type = 'file'
input.style.display = 'none'
const exts = (options.filters && options.filters[0] && options.filters[0].extensions)
? options.filters[0].extensions
: []
if (exts.length) {
input.accept = exts.map(e => '.' + e).join(',')
}
input.onchange = () => {
const file = input.files && input.files[0]
document.body.removeChild(input)
if (!file) {
reject(new Error('未选择文件'))
return
}
const reader = new FileReader()
reader.onload = () => {
resolve({
filePaths: [file.name],
path: file.name,
content: reader.result,
file
})
}
reader.onerror = () => reject(reader.error)
reader.readAsText(file, 'utf-8')
}
input.oncancel = () => {
document.body.removeChild(input)
reject(new Error('取消选择'))
}
document.body.appendChild(input)
input.click()
})
}
/**
* 网页端:无系统保存对话框,通过 downloadFile 触发浏览器下载
* @returns {Promise<{ filePath: string }>} - 仅返回默认文件名,实际保存用 downloadFile
*/
export function showSaveFileDialog() {
return Promise.resolve({ filePath: 'work.dwk' })
}
/** 网页端:选择目录仅桌面端支持 */
export function showOpenDirectoryDialog() {
return Promise.reject(new Error('仅桌面端支持选择目录'))
}
/**
* 网页端:触发浏览器下载
* @param {string} content - 文件内容
* @param {string} filename - 建议文件名
*/
export function downloadFile(content, filename) {
const blob = new Blob([content], { type: 'application/octet-stream' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename || 'download'
a.style.display = 'none'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
}
export function readFile(path, encoding) {
return Promise.reject(new Error('仅桌面端支持本地路径读取'))
}
export function readFileSync(path, encoding) {
throw new Error('仅桌面端支持')
}
export function writeFile(path, content) {
return Promise.reject(new Error('仅桌面端支持'))
}
export function writeFileSync(path, content) {
throw new Error('仅桌面端支持')
}
export function readdir(path) {
return Promise.reject(new Error('仅桌面端支持'))
}
export function stat(path) {
return Promise.reject(new Error('仅桌面端支持'))
}
export function statSync(path) {
throw new Error('仅桌面端支持')
}
/** 是否支持本地文件系统(路径读写、readdir 等) */
export function hasNativeFs() {
return false
}
/** 是否支持 runCmd / 脚本执行 */
export function hasRunCmd() {
return false
}
/** 简单路径拼接(仅用于显示或相对路径,无真实文件系统) */
export function pathJoin(...parts) {
return parts.filter(Boolean).join('/')
}
export function pathDirname(p) {
const i = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\'))
return i < 0 ? '' : p.slice(0, i)
}
export function pathBasename(p) {
const i = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\'))
return i < 0 ? p : p.slice(i + 1)
}
export default {
getAppRoot,
getPlatform,
getBaseSize,
openHelp,
runCmd,
showContextMenu,
showOpenFileDialog,
showOpenDirectoryDialog,
showSaveFileDialog,
downloadFile,
readFile,
readFileSync,
writeFile,
writeFileSync,
readdir,
stat,
statSync,
hasNativeFs,
hasRunCmd,
pathJoin,
pathDirname,
pathBasename
}
+6 -7
View File
@@ -1,19 +1,18 @@
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import { createPersistedState, createSharedMutations } from 'vuex-electron'
import modules from './modules'
// 仅桌面端引入 vuex-electron,避免网页构建报错
if (process.env.IS_WEB !== 'true') {
require('vuex-electron')
}
Vue.use(Vuex)
export default new Vuex.Store({
modules,
getters,
plugins: [
// createPersistedState(),
// createSharedMutations()
],
plugins: [],
strict: process.env.NODE_ENV !== 'production'
})
+13 -1
View File
@@ -2,6 +2,17 @@
let timeouter = null
let websock = null
// WebSocket 地址:优先使用用户配置(localStorage),便于网页端部署后连接远程或本机服务
const WS_SOCKET_API_KEY = 'WS_SOCKET_API'
function getSocketUrl() {
try {
const saved = localStorage.getItem(WS_SOCKET_API_KEY)
if (saved && saved.trim()) return saved.trim()
} catch (e) {}
const envUrl = typeof process !== 'undefined' && process.env && process.env.VUE_APP_SOCKET_API
return envUrl || 'ws://127.0.0.1:10010'
}
// 状态JSON
// 设备状态
const PrinterStatus = {
@@ -348,7 +359,8 @@ const actions = {
return
}
commit('setData', { name: 'connecting', data: true })
websock = new WebSocket(process.env.VUE_APP_SOCKET_API)
const socketUrl = getSocketUrl()
websock = new WebSocket(socketUrl)
websock.onmessage = function (res) {
dispatch('websocketonmessage', res)
}
+4 -2
View File
@@ -3,8 +3,9 @@
* 处理不同操作系统的文件大小差异
*/
// 检测操作系统
// 检测操作系统(网页端无 process.platform,返回 'web'
export function getOS() {
if (typeof process === 'undefined' || process.platform === undefined) return 'web'
const platform = process.platform
if (platform === 'win32') return 'windows'
if (platform === 'darwin') return 'mac'
@@ -20,7 +21,8 @@ export function getBaseSize() {
const baseSizes = {
windows: 1024, // Windows 使用 1024 进制 (二进制)
mac: 1024, // macOS 使用 1024 进制 (十进制)
linux: 1024 // Linux 使用 1024 进制 (十进制,遵循 SI 标准)
linux: 1024, // Linux 使用 1024 进制 (十进制,遵循 SI 标准)
web: 1024 // 网页端固定 1024
}
return baseSizes[os] || 1024
+1 -15
View File
@@ -477,18 +477,4 @@ export const pow1024 = (num) => {
return Math.pow(baseSize, num)
}
// 执行脚本
const { exec } = require('child_process');
export const runCmd = (cmd) => {
return new Promise((resolve, reject) => {
exec(cmd, (err, stdout, stderr) => {
if (err) {
reject(err)
}
if (stderr) {
reject(stderr)
}
resolve(stdout)
})
})
}
// runCmd 已迁移至 @/platform,由 main.js 挂载到 Vue.prototype.$runCmd
+40 -16
View File
@@ -57,7 +57,7 @@
</div>
</el-popover>
<div class="top_left_line"></div>
<el-tooltip :content="workStatus ? '关闭服务' : '打开服务'" placement="bottom">
<el-tooltip :content="hasRunCmd ? (workStatus ? '关闭服务' : '打开服务') : '仅桌面端支持启停服务'" placement="bottom">
<el-popover v-if="guideStep" :placement="guideStep[4].placement" width="250" trigger="manual"
v-model="guideStep[4].show">
<div class="guide_box">
@@ -75,10 +75,16 @@
</div>
<div slot="reference" class="flex_box flex_row_center top_left_status_box"
:class="{ 'guide_body': beginStep && currentStep == 4 }">
<el-switch @change="serviceChange" class="top_left_status" :value="workStatus" active-color="#009688"
<el-switch v-if="hasRunCmd" @change="serviceChange" class="top_left_status" :value="workStatus" active-color="#009688"
inactive-color="#aaa"> </el-switch>
<span v-else class="top_left_status_text">网页端</span>
</div>
</el-popover>
<div v-else slot="reference" class="flex_box flex_row_center top_left_status_box">
<el-switch v-if="hasRunCmd" @change="serviceChange" class="top_left_status" :value="workStatus" active-color="#009688"
inactive-color="#aaa"> </el-switch>
<span v-else class="top_left_status_text">网页端</span>
</div>
</el-tooltip>
<el-popover v-if="guideStep" :placement="guideStep[5].placement" width="250" trigger="manual"
v-model="guideStep[5].show">
@@ -117,7 +123,7 @@
<div v-if="printer_info.test_use === 255" class="top_tip"></div>
<template v-else-if="printer_info.test_use > -1">
<div class="top_tip">试用版 {{ printer_info.test_use }}</div>
<el-button @click="handleActive" type="text" class="top_tip_btn">前往激活</el-button>
<el-button v-if="hasRunCmd" @click="handleActive" type="text" class="top_tip_btn">前往激活</el-button>
</template>
<div v-else-if="printer_info.test_use == -1" class="top_tip1">正在验证注册信息</div>
</template>
@@ -522,16 +528,10 @@
<script>
let that
let outTimer
let fs = require('fs')
let path = require('path')
const { ipcRenderer } = require("electron");
import { mapGetters } from 'vuex'
const { app, dialog } = require('@electron/remote')
import UserInfo from '@/components/userInfo/userInfo.vue'
import WorkAdd from '@/components/workAdd/workAdd.vue'
const exePath = !app.isPackaged ? process.cwd() : path.dirname(process.execPath)
const shPath = path.join(exePath, 'CardsoonServer', 'control.sh')
const activePath = path.join(exePath, 'CardsoonServer', 'regist.sh')
import platform from '@/platform'
export default {
name: 'dashboard',
components: { UserInfo, WorkAdd },
@@ -876,6 +876,19 @@ export default {
},
workStatus() {
return this.isConnect && this.serviceStatus
},
hasRunCmd() {
return platform.hasRunCmd && platform.hasRunCmd()
},
shPath() {
return platform.hasRunCmd && platform.hasRunCmd()
? platform.pathJoin(platform.getAppRoot(), 'CardsoonServer', 'control.sh')
: ''
},
activePath() {
return platform.hasRunCmd && platform.hasRunCmd()
? platform.pathJoin(platform.getAppRoot(), 'CardsoonServer', 'regist.sh')
: ''
}
},
mounted() {
@@ -1250,23 +1263,30 @@ export default {
return
}
that.isNew = false
dialog
.showOpenDialog({
platform
.showOpenFileDialog({
title: '打开作业文件',
filters: [{ name: 'Soon Work', extensions: ['dwk'] }]
})
.then((res) => {
fs.readFile(res.filePaths[0], (err, data) => {
let fName = res.filePaths[0].trim()
let fileName = fName.substring(fName.lastIndexOf('\\') + 1)
const pathOrName = (res.filePaths && res.filePaths[0]) || res.path
const contentP = res.content != null
? Promise.resolve(res.content)
: platform.readFile(pathOrName, 'utf8')
contentP.then((data) => {
const sep = pathOrName.lastIndexOf('\\') >= 0 ? '\\' : '/'
const fileName = pathOrName.trim().split(sep).pop() || pathOrName
that.saveWorkList = JSON.parse(data)
that.saveWorkList.task_name = fileName
that.workShow = true
that.$nextTick(() => {
that.$refs.workAdd.show()
})
}).catch((err) => {
that.$message({ message: err && err.message ? err.message : '打开失败', type: 'error' })
})
})
.catch(() => {})
},
// 显示日志
noticeChange() {
@@ -1486,7 +1506,7 @@ export default {
}
},
help() {
ipcRenderer.send("open-help-file");
platform.openHelp()
},
}
}
@@ -1562,6 +1582,10 @@ $red: #ff0000;
padding: 0 16px;
zoom: 1.3;
}
.top_left_status_text {
font-size: 12px;
color: #999;
}
}
.top_left_option {
+42 -1
View File
@@ -29,6 +29,18 @@
<el-button class="login_btn" type="primary" :loading="loading" @click.native.prevent="handleLogin">{{
$t('login.logIn') }}</el-button>
</el-form-item>
<el-form-item>
<div class="login_ws_setting">
<el-button type="text" @click="showSocketSetting = !showSocketSetting">
{{ showSocketSetting ? '收起' : '设置 WebSocket 服务地址' }}
</el-button>
<div v-if="showSocketSetting" class="login_ws_input">
<el-input v-model="socketApi" placeholder="例如 ws://127.0.0.1:10010 或 ws://服务器IP:10010"
@blur="saveSocketApi" size="small" />
<span class="login_ws_tip">网页端部署后请填写实际服务地址保存后刷新或重新登录生效</span>
</div>
</div>
</el-form-item>
</div>
</el-form>
</div>
@@ -83,10 +95,25 @@ export default {
},
isChecked: true,
loading: false,
pwdType: 'password'
pwdType: 'password',
showSocketSetting: false,
socketApi: ''
}
},
mounted() {
try {
this.socketApi = localStorage.getItem('WS_SOCKET_API') || 'ws://127.0.0.1:10010'
} catch (e) {
this.socketApi = 'ws://127.0.0.1:10010'
}
},
methods: {
saveSocketApi() {
try {
const v = (this.socketApi || '').trim()
if (v) localStorage.setItem('WS_SOCKET_API', v)
} catch (e) {}
},
showPwd() {
if (this.pwdType === 'password') {
this.pwdType = ''
@@ -260,6 +287,20 @@ $light_gray: #eee;
font-size: 24px;
color: #00BCC3;
}
.login_ws_setting {
width: 100%;
.login_ws_input {
margin-top: 8px;
.el-input { width: 100%; }
}
.login_ws_tip {
display: block;
font-size: 12px;
color: #909399;
margin-top: 4px;
}
}
}
}