更新
This commit is contained in:
@@ -139,6 +139,7 @@ let rendererConfig = {
|
|||||||
options: options,
|
options: options,
|
||||||
},
|
},
|
||||||
process,
|
process,
|
||||||
|
isWeb: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
minify: {
|
minify: {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ process.env.BABEL_ENV = 'web'
|
|||||||
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
|
const config = require('../config')
|
||||||
|
const env = process.env.NODE_ENV === 'production' ? config.build.env : config.dev.env
|
||||||
|
|
||||||
const MinifyPlugin = require("babel-minify-webpack-plugin")
|
const MinifyPlugin = require("babel-minify-webpack-plugin")
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||||
@@ -107,6 +109,7 @@ let webConfig = {
|
|||||||
options: options,
|
options: options,
|
||||||
},
|
},
|
||||||
process,
|
process,
|
||||||
|
isWeb: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
minify: {
|
minify: {
|
||||||
@@ -117,7 +120,9 @@ let webConfig = {
|
|||||||
nodeModules: false
|
nodeModules: false
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env.IS_WEB': 'true'
|
'process.env.IS_WEB': '"true"',
|
||||||
|
'process.env.BASE_API': env.BASE_API || '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
|
||||||
|
'process.env.VUE_APP_SOCKET_API': env.VUE_APP_SOCKET_API || '"ws://127.0.0.1:10010"'
|
||||||
}),
|
}),
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
new webpack.NoEmitOnErrorsPlugin()
|
new webpack.NoEmitOnErrorsPlugin()
|
||||||
@@ -129,11 +134,24 @@ let webConfig = {
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': path.join(__dirname, '../src/renderer'),
|
'@': path.join(__dirname, '../src/renderer'),
|
||||||
|
'@/platform': path.join(__dirname, '../src/renderer/platform/web.js'),
|
||||||
'vue$': 'vue/dist/vue.esm.js'
|
'vue$': 'vue/dist/vue.esm.js'
|
||||||
},
|
},
|
||||||
extensions: ['.js', '.vue', '.json', '.css']
|
extensions: ['.js', '.vue', '.json', '.css']
|
||||||
},
|
},
|
||||||
target: 'web'
|
node: {
|
||||||
|
fs: 'empty',
|
||||||
|
path: 'empty',
|
||||||
|
child_process: 'empty'
|
||||||
|
},
|
||||||
|
target: 'web',
|
||||||
|
devServer: process.env.NODE_ENV !== 'production' ? {
|
||||||
|
port: 9081,
|
||||||
|
hot: true,
|
||||||
|
open: true,
|
||||||
|
contentBase: path.join(__dirname, '../'),
|
||||||
|
publicPath: '/'
|
||||||
|
} : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,226 @@
|
|||||||
|
# 宝塔部署 Web 端说明
|
||||||
|
|
||||||
|
## 一、构建前配置
|
||||||
|
|
||||||
|
### 1. 配置后端 API 地址
|
||||||
|
|
||||||
|
编辑 `config/prod.env.js`,将 `BASE_API` 改为实际的后端接口地址:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
module.exports = {
|
||||||
|
NODE_ENV: '"production"',
|
||||||
|
BASE_API: '"https://your-api-domain.com/api"', // 改为实际后端地址
|
||||||
|
VUE_APP_SOCKET_API: '"ws://127.0.0.1:10010"' // WebSocket 默认地址(用户可在登录页修改)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意:**
|
||||||
|
- `BASE_API` 是 HTTP 接口地址(登录、数据请求等)
|
||||||
|
- `VUE_APP_SOCKET_API` 是 WebSocket 默认地址,用户可在登录页修改,所以这里可以保持默认或设为公网地址
|
||||||
|
|
||||||
|
### 2. 执行构建
|
||||||
|
|
||||||
|
在项目根目录执行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build:web
|
||||||
|
```
|
||||||
|
|
||||||
|
构建完成后,产物在 `dist/web/` 目录下,包含:
|
||||||
|
- `index.html`(入口文件)
|
||||||
|
- `web.js`(主 JS 文件,包含所有代码和样式)
|
||||||
|
- `1.js` ~ `7.js`(代码分割的 chunk 文件)
|
||||||
|
- `fonts/`(字体文件)
|
||||||
|
- `imgs/`(图片资源)
|
||||||
|
|
||||||
|
**注意**:CSS 样式被打包进 `web.js` 中,运行时动态注入,无需单独引用 CSS 文件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、宝塔部署步骤
|
||||||
|
|
||||||
|
### 1. 创建网站
|
||||||
|
|
||||||
|
1. 登录宝塔面板
|
||||||
|
2. 点击「网站」→「添加站点」
|
||||||
|
3. 填写:
|
||||||
|
- **域名**:例如 `your-domain.com` 或 `192.168.1.100`(内网 IP)
|
||||||
|
- **根目录**:例如 `/www/wwwroot/your-domain.com`
|
||||||
|
- **PHP 版本**:选择「纯静态」或任意版本(前端不需要 PHP)
|
||||||
|
|
||||||
|
### 2. 上传文件
|
||||||
|
|
||||||
|
将 `dist/web/` 目录下的**所有文件**上传到网站根目录:
|
||||||
|
|
||||||
|
```
|
||||||
|
/www/wwwroot/your-domain.com/
|
||||||
|
├── index.html
|
||||||
|
├── web.js
|
||||||
|
├── styles.css
|
||||||
|
└── static/
|
||||||
|
├── imgs/
|
||||||
|
└── fonts/
|
||||||
|
```
|
||||||
|
|
||||||
|
**上传方式:**
|
||||||
|
- 方式一:在宝塔「文件」中直接上传压缩包并解压
|
||||||
|
- 方式二:使用 FTP/SFTP 工具上传
|
||||||
|
- 方式三:使用宝塔「终端」执行 `scp` 或 `rsync` 命令
|
||||||
|
|
||||||
|
### 3. 配置 Nginx(重要)
|
||||||
|
|
||||||
|
由于是 Vue Router 的 hash 模式(`#/login`、`#/manage`),通常不需要特殊配置。但建议添加以下配置以确保正常访问:
|
||||||
|
|
||||||
|
在宝塔「网站」→ 选择站点 →「设置」→「配置文件」中,在 `server` 块内添加:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
root /www/wwwroot/your-domain.com;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# 前端路由支持(hash 模式通常不需要,但加上更稳妥)
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 静态资源缓存
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# 禁止访问隐藏文件
|
||||||
|
location ~ /\. {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 配置 HTTPS(可选但推荐)
|
||||||
|
|
||||||
|
如果使用域名且需要 HTTPS:
|
||||||
|
|
||||||
|
1. 在宝塔「网站」→ 选择站点 →「SSL」→「Let's Encrypt」申请免费证书
|
||||||
|
2. 开启「强制 HTTPS」
|
||||||
|
3. **注意**:如果后端 API 或 WebSocket 使用 HTTPS/WSS,确保:
|
||||||
|
- `BASE_API` 使用 `https://`
|
||||||
|
- WebSocket 地址使用 `wss://`(或在登录页填写 `wss://` 地址)
|
||||||
|
|
||||||
|
### 5. 配置跨域(如需要)
|
||||||
|
|
||||||
|
如果后端 API 与前端不在同一域名,后端需要配置 CORS:
|
||||||
|
|
||||||
|
- 允许的源:`https://your-domain.com`(或 `*` 用于开发)
|
||||||
|
- 允许的请求头:`Content-Type, X-Token`(根据实际后端要求)
|
||||||
|
- 允许的方法:`GET, POST, PUT, DELETE` 等
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、WebSocket 配置
|
||||||
|
|
||||||
|
### 情况 1:WebSocket 服务在服务器本机
|
||||||
|
|
||||||
|
如果 WebSocket 服务运行在**宝塔服务器本机**(例如 `ws://127.0.0.1:10010`):
|
||||||
|
|
||||||
|
- 用户访问网页时,浏览器会尝试连接**用户电脑**的 `127.0.0.1`,**无法连接**
|
||||||
|
- **解决方案**:
|
||||||
|
1. 在登录页填写 `ws://服务器公网IP:10010` 或 `ws://服务器域名:10010`
|
||||||
|
2. 或配置 Nginx 反向代理 WebSocket(见下方)
|
||||||
|
|
||||||
|
### 情况 2:使用 Nginx 反向代理 WebSocket(推荐)
|
||||||
|
|
||||||
|
在宝塔「网站」→ 选择站点 →「设置」→「配置文件」中添加:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# WebSocket 代理(假设后端 WebSocket 在 127.0.0.1:10010)
|
||||||
|
location /ws {
|
||||||
|
proxy_pass http://127.0.0.1:10010;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在登录页填写:`wss://your-domain.com/ws`(HTTPS 用 `wss://`,HTTP 用 `ws://`)
|
||||||
|
|
||||||
|
### 情况 3:WebSocket 服务在公网服务器
|
||||||
|
|
||||||
|
如果 WebSocket 服务部署在**公网服务器**(例如 `wss://ws-api.xxx.com`):
|
||||||
|
|
||||||
|
- 在登录页填写该地址即可
|
||||||
|
- 或在构建时设置 `VUE_APP_SOCKET_API` 为 `wss://ws-api.xxx.com`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、访问测试
|
||||||
|
|
||||||
|
1. 打开浏览器访问:`http://your-domain.com` 或 `https://your-domain.com`
|
||||||
|
2. 应能看到登录页
|
||||||
|
3. 点击「设置 WebSocket 服务地址」,填写实际可访问的 WebSocket 地址
|
||||||
|
4. 输入账号密码登录
|
||||||
|
5. 检查:
|
||||||
|
- 登录是否成功
|
||||||
|
- WebSocket 是否连接成功(查看浏览器控制台 Network → WS)
|
||||||
|
- 各功能页面是否正常加载
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、常见问题
|
||||||
|
|
||||||
|
### 1. 页面空白
|
||||||
|
|
||||||
|
- **检查**:浏览器控制台是否有 JS 错误
|
||||||
|
- **解决**:确认 `index.html` 中引用的 `web.js`、`styles.css` 路径正确;检查 Nginx 配置是否正确
|
||||||
|
|
||||||
|
### 2. 接口请求失败(404 或 CORS)
|
||||||
|
|
||||||
|
- **检查**:`BASE_API` 是否正确;后端是否允许跨域
|
||||||
|
- **解决**:修改 `config/prod.env.js` 中的 `BASE_API` 后重新构建;后端配置 CORS
|
||||||
|
|
||||||
|
### 3. WebSocket 连接失败
|
||||||
|
|
||||||
|
- **检查**:浏览器控制台 Network → WS,查看连接地址和错误信息
|
||||||
|
- **解决**:
|
||||||
|
- 确认 WebSocket 服务已启动
|
||||||
|
- 在登录页填写正确的地址(本机用 `ws://127.0.0.1:10010`,服务器用 `ws://服务器IP:10010`,HTTPS 用 `wss://`)
|
||||||
|
- 如使用 Nginx 代理,确认代理配置正确
|
||||||
|
|
||||||
|
### 4. 静态资源 404
|
||||||
|
|
||||||
|
- **检查**:`static/` 目录是否上传完整
|
||||||
|
- **解决**:重新上传 `dist/web/static/` 目录
|
||||||
|
|
||||||
|
### 5. 路由跳转异常
|
||||||
|
|
||||||
|
- **检查**:Nginx 配置中是否有 `try_files $uri $uri/ /index.html;`
|
||||||
|
- **解决**:添加该配置后重启 Nginx
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、更新部署
|
||||||
|
|
||||||
|
当代码更新后:
|
||||||
|
|
||||||
|
1. 修改 `config/prod.env.js`(如需要)
|
||||||
|
2. 执行 `npm run build:web`
|
||||||
|
3. 将 `dist/web/` 下的文件**覆盖上传**到宝塔网站根目录
|
||||||
|
4. 清除浏览器缓存或强制刷新(Ctrl+F5)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、总结
|
||||||
|
|
||||||
|
- **构建产物**:`dist/web/` 目录
|
||||||
|
- **上传位置**:宝塔网站根目录
|
||||||
|
- **配置要点**:
|
||||||
|
- `BASE_API`:后端 HTTP 接口地址
|
||||||
|
- WebSocket:用户可在登录页配置,或通过 Nginx 反向代理
|
||||||
|
- **访问方式**:`http(s)://your-domain.com`
|
||||||
|
|
||||||
|
部署完成后,用户访问网站即可使用,首次登录时记得在登录页配置 WebSocket 服务地址。
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# WebSocket 与网页端部署说明
|
||||||
|
|
||||||
|
## 问题说明
|
||||||
|
|
||||||
|
程序通过 **WebSocket** 与后端服务通信(设备状态、作业列表、系统配置等)。该服务在**桌面端**场景下是 Windows 本机运行的服务(默认 `ws://127.0.0.1:10010`)。
|
||||||
|
|
||||||
|
**网页端打包部署后能否连接,取决于“浏览器连的是谁”:**
|
||||||
|
|
||||||
|
- 网页运行在**用户浏览器**里,`new WebSocket("ws://127.0.0.1:10010")` 会连接的是**当前用户所在设备**的 127.0.0.1,而不是你放网页的那台服务器。
|
||||||
|
- 因此:
|
||||||
|
- **用户和 Windows 服务在同一台电脑**:例如在本机浏览器打开部署在本机的页面,用 `ws://127.0.0.1:10010` 可以连到本机服务,**可以连接**。
|
||||||
|
- **用户和 Windows 服务不在同一台电脑**:例如网页部署在服务器 A,用户在手机或另一台电脑 B 打开,此时 127.0.0.1 指向的是 B,连不到 A 上的服务,**无法连接**。
|
||||||
|
|
||||||
|
所以:**把程序打包放到网页端后,只有在“能访问到 WebSocket 服务”的情况下才可以连接**;默认写死 127.0.0.1 时,只适合“网页和服务在同一台机器”或“用户本机也跑了该服务”的场景。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已做改造:可配置 WebSocket 地址
|
||||||
|
|
||||||
|
为支持不同部署方式,已做两点改动:
|
||||||
|
|
||||||
|
### 1. 连接时优先读“用户配置”
|
||||||
|
|
||||||
|
- 连接 WebSocket 时**优先使用**本地存储中的 **`WS_SOCKET_API`**(localStorage)。
|
||||||
|
- 若未配置,再使用构建时注入的 **`process.env.VUE_APP_SOCKET_API`**(默认 `ws://127.0.0.1:10010`)。
|
||||||
|
|
||||||
|
因此同一套网页包可以:
|
||||||
|
|
||||||
|
- 部署在服务器上,由用户在登录页填写“实际要连的 WebSocket 地址”;
|
||||||
|
- 或由部署方在构建时通过环境变量写死一个公网/内网地址。
|
||||||
|
|
||||||
|
### 2. 登录页可填写并保存地址
|
||||||
|
|
||||||
|
- 在**登录页**增加 **“设置 WebSocket 服务地址”**(可展开/收起)。
|
||||||
|
- 用户可输入例如:
|
||||||
|
- `ws://127.0.0.1:10010`(本机服务)
|
||||||
|
- `ws://192.168.1.100:10010`(内网某台机器上的服务)
|
||||||
|
- `wss://your-domain.com/ws`(通过域名 + HTTPS 的 WSS 服务)
|
||||||
|
- 失焦或保存后写入 **localStorage**,下次进入或刷新后,连接会使用该地址。
|
||||||
|
|
||||||
|
这样**网页端打包部署后**,只要用户(或管理员)在登录页把地址改成“实际可访问的 WebSocket 服务地址”,就可以正常连接。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常见部署方式与是否可连接
|
||||||
|
|
||||||
|
| 部署方式 | 网页访问方式 | WebSocket 地址建议 | 能否连接 |
|
||||||
|
|----------|--------------|--------------------|----------|
|
||||||
|
| 网页和 Windows 服务都在**同一台电脑** | 本机浏览器打开(如 http://localhost:9081 或 file://) | `ws://127.0.0.1:10010`(默认即可) | 可以 |
|
||||||
|
| 网页在**服务器**,服务仍在**用户本机** | 用户在本机浏览器打开网站 | 在登录页设为 `ws://127.0.0.1:10010`(本机须已运行该服务) | 可以(仅限本机也跑服务时) |
|
||||||
|
| 网页在**服务器**,服务在**内网另一台机器** | 用户在内网任意电脑打开网站 | 在登录页设为 `ws://内网机器IP:10010`(或该机域名) | 可以(需网络可达、无策略拦截) |
|
||||||
|
| 网页在**公网**,服务在**公网服务器** | 任意设备打开网站 | 构建时设 `VUE_APP_SOCKET_API=wss://api.xxx.com/ws`,或在登录页填写该地址 | 可以(需后端提供 wss 与跨域策略) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用步骤(网页端部署后)
|
||||||
|
|
||||||
|
1. 打开登录页,点击 **“设置 WebSocket 服务地址”**。
|
||||||
|
2. 填入实际可访问的地址,例如:
|
||||||
|
- 本机:`ws://127.0.0.1:10010`
|
||||||
|
- 内网机器:`ws://192.168.1.100:10010`
|
||||||
|
- 公网:`wss://your-api.com/ws`
|
||||||
|
3. 失焦后会自动保存到当前浏览器;之后登录或刷新页面,会使用该地址连接。
|
||||||
|
4. 若更换环境(换电脑/换网络),在同一浏览器重新打开登录页,修改并保存新地址即可。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 构建时写死地址(可选)
|
||||||
|
|
||||||
|
若部署环境固定(例如始终连同一台服务器),可在构建网页时注入默认地址,减少用户手动填写:
|
||||||
|
|
||||||
|
- 开发/测试:在 `config/dev.env.js` 中设置 `VUE_APP_SOCKET_API`。
|
||||||
|
- 生产:在 `config/prod.env.js` 或 CI 中设置 `VUE_APP_SOCKET_API`(如 `wss://api.xxx.com/ws`)。
|
||||||
|
|
||||||
|
未在登录页保存过地址时,会使用该默认值;保存过后以登录页配置为准。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
- **默认 `ws://127.0.0.1:10010` 只适合“浏览器所在机器 = 运行 WebSocket 服务的机器”的情况。**
|
||||||
|
- **网页端打包部署后**:通过**登录页可配置 WebSocket 地址**并写入 localStorage,连接时**优先使用该配置**,因此只要用户填写的地址在浏览器侧可达(本机/内网/公网),就可以正常连接。
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
# DiscWorker 网页端功能清单与实现情况
|
||||||
|
|
||||||
|
本文档列出全部功能,并标明:**无改动**(桌面/网页一致)、**有改动**(为兼容网页做了适配)、**网页端无法实现**(仅桌面端或需后端扩展)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、全局与基础设施
|
||||||
|
|
||||||
|
| 功能 | 实现情况 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 路由(/、/login、/manage、/set、/user、/404) | 无改动 | 使用 Vue Router,hash 模式,网页端可直接部署 |
|
||||||
|
| 登录鉴权(permission.js) | 无改动 | 基于 token 与 store,与运行环境无关 |
|
||||||
|
| 路由守卫(白名单、未登录跳转登录) | 无改动 | 纯前端逻辑 |
|
||||||
|
| HTTP 请求(request.js、BASE_API) | 有改动 | 网页构建通过 DefinePlugin 注入 BASE_API,需配置后端地址 |
|
||||||
|
| 多语言(vue-i18n) | 无改动 | 纯前端 |
|
||||||
|
| Element UI、主题与尺寸 | 无改动 | 纯前端 |
|
||||||
|
| Vuex Store(user、chat、app 等) | 有改动 | store 仅桌面端条件引入 vuex-electron,网页端不引入 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、登录页(/login)
|
||||||
|
|
||||||
|
| 功能 | 实现情况 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 用户名/密码输入、记住账号 | 无改动 | 纯表单 + localStorage |
|
||||||
|
| 登录请求(/user/login) | 无改动 | 走 axios + BASE_API,网页端需后端可用 |
|
||||||
|
| 新手引导(第一步) | 无改动 | 纯前端弹窗与步骤 |
|
||||||
|
| **WebSocket 服务地址设置** | **有改动** | 登录页可展开「设置 WebSocket 服务地址」,填写后写入 localStorage(key: `WS_SOCKET_API`),连接时优先使用该地址,便于网页端部署后连接本机/内网/公网服务。详见 [WEBSOCKET_DEPLOY.md](./WEBSOCKET_DEPLOY.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、首页 / 看板(/)
|
||||||
|
|
||||||
|
### 3.1 顶部与状态
|
||||||
|
|
||||||
|
| 功能 | 实现情况 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 回到主界面、系统设置入口 | 无改动 | 路由跳转 |
|
||||||
|
| 服务启停开关(打开/关闭服务) | **网页端无法实现** | 依赖桌面端执行 `control.sh`,网页端显示「网页端」占位与 tooltip「仅桌面端支持启停服务」 |
|
||||||
|
| 完成刻录数/废弃刻录数/错误数 | 无改动 | 来自 WebSocket 的 task_list,与端无关 |
|
||||||
|
| 试用版天数、前往激活 | 有改动 | 「前往激活」仅桌面端显示(依赖 runCmd 执行 regist.sh);网页端不显示该按钮 |
|
||||||
|
| 用户信息组件 | 无改动 | 展示 store 中的用户信息 |
|
||||||
|
|
||||||
|
### 3.2 设备与作业
|
||||||
|
|
||||||
|
| 功能 | 实现情况 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 设备状态展示(图示、状态文案) | 无改动 | 数据来自 WebSocket printer_info |
|
||||||
|
| 运行选项(重启设备、清除错误、设置光盘桶、刷新光盘桶等) | 无改动 | 通过 WebSocket 下发,网页端在已连接服务时可用 |
|
||||||
|
| 作业列表(表格、重试/取消) | 无改动 | WebSocket task_list + websocketsend |
|
||||||
|
| 新建作业、打开作业 | 有改动 | **打开作业**:桌面端用系统对话框选 .dwk 后读文件;网页端用 `<input type="file">` 选文件并 FileReader 读内容,再解析填入 workAdd,**可正常使用**。新建作业为打开弹窗,无改动 |
|
||||||
|
| 保存作业(workAdd 内) | 有改动 | 桌面端:系统保存对话框 + 写入本地路径;网页端:无保存对话框,直接触发浏览器下载 .dwk 文件,**可正常使用** |
|
||||||
|
| 动态/日志(最新日志、全部日志) | 无改动 | 数据来自 WebSocket log,与端无关 |
|
||||||
|
|
||||||
|
### 3.3 帮助与其它
|
||||||
|
|
||||||
|
| 功能 | 实现情况 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 帮助(打开帮助文档) | 有改动 | 桌面端:IPC 打开本机 PDF;网页端:`window.open(HELP_PDF_URL)`,需部署或配置帮助 PDF 地址 |
|
||||||
|
| 关于我们弹窗 | 无改动 | 纯前端 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、作业弹窗(WorkAdd 组件)
|
||||||
|
|
||||||
|
### 4.1 作业配置
|
||||||
|
|
||||||
|
| 功能 | 实现情况 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 任务名称、CD 类型、刻录类型、标签、UDF、关联任务等表单 | 无改动 | 纯表单与校验 |
|
||||||
|
| 模板下拉(从 User Templates 加载) | 有改动 | 桌面端:读本地「User Templates」目录;网页端:readdir 不可用,模板列表为空,**网页端无法使用本地模板列表**;若需网页端模板,需后端提供列表接口 |
|
||||||
|
| 选择模板(加载 .soon 文件) | 有改动 | 仅桌面端可用(依赖本地路径 + readFile);网页端无模板列表,该入口不适用 |
|
||||||
|
| 「···」选择作业文件(.soon) | 有改动 | 桌面端:系统对话框选文件;网页端:可用平台 showOpenFileDialog(input file),**单文件选择可用**;多选仍仅桌面端 |
|
||||||
|
| 新建标签(打开 SoonDesign) | **网页端无法实现** | 依赖桌面端 exec('soondesign'),网页端按钮改为灰色 + tooltip「仅桌面端支持」 |
|
||||||
|
|
||||||
|
### 4.2 文件与路径
|
||||||
|
|
||||||
|
| 功能 | 实现情况 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 添加文件(单/多选) | 有改动 | 桌面端:系统对话框 + 本地路径 + stat;网页端:input file 单文件,以「web://」虚拟路径加入列表,**可添加单文件** |
|
||||||
|
| 添加文件夹 | **网页端无法实现** | 依赖系统选择目录,网页端 showOpenDirectoryDialog 拒绝,提示「仅桌面端支持」 |
|
||||||
|
| 拖放添加文件/文件夹 | 有改动 | 桌面端:支持路径与 calcSize;网页端:仅支持 File 对象(无 path),用 name 与 size,不计算目录大小,**可拖放文件** |
|
||||||
|
| 选择路径(归档路径) | **网页端无法实现** | 依赖系统选择目录,网页端不支持 |
|
||||||
|
| 保存作业为 .dwk | 见「三、首页」保存作业 | 网页端为下载 |
|
||||||
|
|
||||||
|
### 4.3 提交与打包
|
||||||
|
|
||||||
|
| 功能 | 实现情况 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 提交任务(刻录/打印) | 有改动 | 桌面端:提交本地路径 + mtime 等,由本机服务访问本地文件;网页端:当前提交的为「web://」虚拟路径,**后端无法按路径读文件,故网页端提交任务需后端提供文件上传接口**,否则仅桌面端可完整使用 |
|
||||||
|
| 复制文件到某路径(file-back 复制) | **网页端无法实现** | 依赖 fs.cp,仅桌面端;网页端点击会提示「仅桌面端支持」 |
|
||||||
|
| 压缩为 zip/加密 zip(file-back) | **网页端无法实现** | 依赖 fs stream + archiver,仅桌面端;网页端会提示「仅桌面端支持」 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、系统管理(/manage)
|
||||||
|
|
||||||
|
| 功能 | 实现情况 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 设置光驱(手动设置光驱、设置光盘桶类型) | 无改动 | 全部通过 WebSocket 与后端/设备通信,网页端在连接服务后**可正常使用** |
|
||||||
|
| 校准设备、维护设备 | 无改动 | WebSocket 下发指令,网页端可用 |
|
||||||
|
| 刷新时间、执行 | 无改动 | WebSocket,网页端可用 |
|
||||||
|
| 清除任务(服务重启时是否删除已完成/已取消任务) | 无改动 | WebSocket/配置,网页端可用 |
|
||||||
|
| 新手引导开关 | 无改动 | 本地存储,网页端可用 |
|
||||||
|
| 光盘类型列表(getCurrentOSDiscTypes) | 有改动 | fileSize 在网页端返回固定 1024 与 web 类型,manage 仅读配置列表,**网页端可正常使用** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、系统配置(/set)
|
||||||
|
|
||||||
|
| 功能 | 实现情况 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| LogLevel、AutoRetryTimes、PrintQuality、CacheDir、SkipSr0Check 等 | 无改动 | 通过 WebSocket 获取/提交系统配置,网页端**可正常使用** |
|
||||||
|
| 保存 | 无改动 | websocketsend,网页端可用 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、用户管理(/user)
|
||||||
|
|
||||||
|
| 功能 | 实现情况 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 用户列表展示(当前为静态示例数据) | 无改动 | 纯前端表格,若后续接真实接口则与端无关 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、其它
|
||||||
|
|
||||||
|
| 功能 | 实现情况 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 404 页 | 无改动 | 纯静态 |
|
||||||
|
| TabBar 导航 | 无改动 | 路由跳转 |
|
||||||
|
| WebSocket 连接(chat 模块) | 有改动 | 网页端通过 VUE_APP_SOCKET_API 连接,需后端或本机服务提供 WebSocket;**网页端需能访问该地址**(同源或 CORS/代理) |
|
||||||
|
| 文件大小显示(filterSize、fileSize) | 有改动 | fileSize 在网页端 getOS 返回 'web'、getBaseSize 固定 1024,**显示正常** |
|
||||||
|
| 右键菜单(show-context-menu) | 有改动 | 桌面端 IPC;网页端 no-op,无影响 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、总结表
|
||||||
|
|
||||||
|
| 类型 | 数量 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| **无改动** | 绝大多数 | 登录、配置、设备指令、作业列表、重试/取消、日志、系统管理、系统配置等均与运行环境无关或仅依赖 WebSocket/HTTP |
|
||||||
|
| **有改动且网页端可用** | 若干 | 打开作业(选文件 + 读内容)、保存作业(下载)、帮助(新标签打开 PDF)、添加单文件、拖放文件、服务开关/激活 UI 占位与提示 |
|
||||||
|
| **网页端无法实现** | 少量 | 服务启停(执行 control.sh)、前往激活(执行 regist.sh)、新建标签(打开 SoonDesign)、选择目录、复制到本地路径、本地 zip 打包;提交任务在无上传接口时仅桌面端完整可用 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、网页端使用前提
|
||||||
|
|
||||||
|
1. **后端与 WebSocket**:配置 `BASE_API`、`VUE_APP_SOCKET_API`,确保登录接口与 WebSocket 可访问(同源或代理/CORS)。
|
||||||
|
2. **帮助文档**:如需帮助入口,部署帮助 PDF 并配置 `HELP_PDF_URL`(或使用默认 `/help/User Manual.pdf`)。
|
||||||
|
3. **提交任务**:若要在网页端完整使用「提交刻录/打印任务」,需后端提供文件上传接口,前端改为先上传再提交任务参数;当前实现仍为提交路径,仅桌面端可被本机服务访问。
|
||||||
|
|
||||||
|
按当前实现,网页端可正常完成:登录、看板查看、设备状态与指令、作业列表与重试/取消、打开/保存作业(选文件与下载)、系统管理、系统配置、日志查看等;仅上述「网页端无法实现」项在网页端为禁用或提示。
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
"build:dir": "node .electron-vue/build.js && electron-builder --dir",
|
"build:dir": "node .electron-vue/build.js && electron-builder --dir",
|
||||||
"build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
|
"build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
|
||||||
"build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js",
|
"build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js",
|
||||||
|
"dev:web": "cross-env NODE_ENV=development node node_modules/webpack-dev-server/bin/webpack-dev-server.js --config .electron-vue/webpack.web.config.js --open",
|
||||||
"electron:linux": "vue-cli-service electron:build -l --x64",
|
"electron:linux": "vue-cli-service electron:build -l --x64",
|
||||||
"electron:arm": "vue-cli-service electron:build -l --arm64",
|
"electron:arm": "vue-cli-service electron:build -l --arm64",
|
||||||
"dev": "node .electron-vue/dev-runner.js",
|
"dev": "node .electron-vue/dev-runner.js",
|
||||||
|
|||||||
+4
-4
@@ -3,8 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>DiscWorker V1.01</title>
|
<title>DiscWorker V1.01</title>
|
||||||
<% if (htmlWebpackPlugin.options.nodeModules) { %>
|
<% if (typeof isWeb === 'undefined' ? htmlWebpackPlugin.options.nodeModules : !isWeb && htmlWebpackPlugin.options.nodeModules) { %>
|
||||||
<!-- Add `node_modules/` to global paths so `require` works properly in development -->
|
<!-- Add `node_modules/` to global paths so `require` works properly in development (Electron only) -->
|
||||||
<script>
|
<script>
|
||||||
require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>')
|
require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>')
|
||||||
</script>
|
</script>
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- Set `__static` path to static files in production -->
|
<!-- Set `__static` path to static files in production (Electron only; do not inject in web build) -->
|
||||||
<% if (!process.browser) { %>
|
<% if (typeof isWeb === 'undefined' ? !process.browser : !isWeb) { %>
|
||||||
<script>
|
<script>
|
||||||
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
const fs = require("fs");
|
const IS_WEB = process.env.IS_WEB === 'true';
|
||||||
const path = require("path");
|
let fs, path, stat, readdir;
|
||||||
// 使用promisify方法来promise化指定方法
|
if (!IS_WEB) {
|
||||||
const { promisify } = require("util");
|
fs = require("fs");
|
||||||
const stat = promisify(fs.stat);
|
path = require("path");
|
||||||
const readdir = promisify(fs.readdir);
|
const { promisify } = require("util");
|
||||||
|
stat = promisify(fs.stat);
|
||||||
|
readdir = promisify(fs.readdir);
|
||||||
|
}
|
||||||
|
|
||||||
// 异步
|
// 异步(网页端不计算目录大小,直接 callback 0)
|
||||||
export async function calcSize(dirPath, callback) {
|
export async function calcSize(dirPath, callback) {
|
||||||
|
if (IS_WEB || !stat) {
|
||||||
|
callback(null, 0, dirPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let fileSize = 0;
|
let fileSize = 0;
|
||||||
let error = null;
|
let error = null;
|
||||||
async function calc(dirPath) {
|
async function calc(dirPath) {
|
||||||
@@ -14,9 +21,7 @@ export async function calcSize(dirPath, callback) {
|
|||||||
const statObj = await stat(dirPath);
|
const statObj = await stat(dirPath);
|
||||||
if (statObj.isDirectory()) {
|
if (statObj.isDirectory()) {
|
||||||
const files = await readdir(dirPath);
|
const files = await readdir(dirPath);
|
||||||
let dirs = files.map((item) => {
|
let dirs = files.map((item) => path.join(dirPath, item));
|
||||||
return path.join(dirPath, item);
|
|
||||||
});
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
async function next() {
|
async function next() {
|
||||||
if (index < dirs.length) {
|
if (index < dirs.length) {
|
||||||
@@ -38,7 +43,9 @@ export async function calcSize(dirPath, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getFileName(name) {
|
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) {
|
export function getExtension(name) {
|
||||||
return name.substring(name.lastIndexOf(".") + 1);
|
return name.substring(name.lastIndexOf(".") + 1);
|
||||||
@@ -52,7 +59,8 @@ export function bytesToSize(bytes) {
|
|||||||
return (bytes / Math.pow(k, i)).toPrecision(3) + " " + sizes[i];
|
return (bytes / Math.pow(k, i)).toPrecision(3) + " " + sizes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFolder(path) {
|
export function isFolder(filePath) {
|
||||||
let _stat = fs.lstatSync(path);
|
if (IS_WEB || !fs) return false;
|
||||||
|
let _stat = fs.lstatSync(filePath);
|
||||||
return _stat.isDirectory();
|
return _stat.isDirectory();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,15 +52,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
const { dialog } = require("@electron/remote");
|
import platform from "@/platform";
|
||||||
const fs = require("fs");
|
|
||||||
import fileEmpty from "./fileEmpty";
|
import fileEmpty from "./fileEmpty";
|
||||||
import fileList from "./fileList";
|
import fileList from "./fileList";
|
||||||
import progressdialog from "./progressdialog";
|
import progressdialog from "./progressdialog";
|
||||||
import archiverdialog from "./archiverdialog";
|
import archiverdialog from "./archiverdialog";
|
||||||
import { calcSize, getFileName, isFolder } from "./calc";
|
import { calcSize, getFileName, isFolder } from "./calc";
|
||||||
import { copy } from "./copy";
|
const copyFn = process.env.IS_WEB !== 'true' ? require("./copy").copy : null;
|
||||||
import { zip } from "./archiver";
|
const zipFn = process.env.IS_WEB !== 'true' ? require("./archiver").zip : null;
|
||||||
export default {
|
export default {
|
||||||
name: "Files",
|
name: "Files",
|
||||||
props: {
|
props: {
|
||||||
@@ -105,15 +104,15 @@ export default {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// e.stopPropagation();
|
// e.stopPropagation();
|
||||||
for (const f of e.dataTransfer.files) {
|
for (const f of e.dataTransfer.files) {
|
||||||
|
const pathKey = f.path || ('web://' + (f.name || 'file') + '_' + Date.now() + Math.random());
|
||||||
const isFolder = _this.dropFolderCheck(f);
|
const isFolder = _this.dropFolderCheck(f);
|
||||||
_this.insertList({
|
_this.insertList({
|
||||||
name: getFileName(f.path),
|
name: getFileName(f.path || f.name),
|
||||||
path: f.path,
|
path: pathKey,
|
||||||
size: isFolder ? -1 : f.size,
|
size: isFolder ? -1 : f.size,
|
||||||
folder: isFolder,
|
folder: isFolder,
|
||||||
});
|
});
|
||||||
|
if (isFolder && platform.hasNativeFs && platform.hasNativeFs()) {
|
||||||
if (isFolder) {
|
|
||||||
calcSize(f.path, _this.folderCalcCallback);
|
calcSize(f.path, _this.folderCalcCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,45 +139,29 @@ export default {
|
|||||||
},
|
},
|
||||||
addFile() {
|
addFile() {
|
||||||
const _this = this;
|
const _this = this;
|
||||||
dialog
|
platform.showOpenFileDialog({ properties: ["multiSelections"] }).then((rel) => {
|
||||||
.showOpenDialog({
|
if (rel.file && !(platform.hasNativeFs && platform.hasNativeFs())) {
|
||||||
properties: ["multiSelections"],
|
const pathKey = 'web://' + rel.file.name + '_' + Date.now();
|
||||||
})
|
_this.insertList({ name: getFileName(rel.file.name), path: pathKey, size: rel.file.size || 0, folder: false });
|
||||||
.then(async (res) => {
|
return;
|
||||||
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,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
(rel.filePaths || []).forEach((item) => {
|
||||||
|
platform.stat(item).then((res) => {
|
||||||
|
_this.insertList({ name: getFileName(item), path: item, size: res.size, folder: false });
|
||||||
|
}).catch(() => {});
|
||||||
});
|
});
|
||||||
|
}).catch(() => {});
|
||||||
},
|
},
|
||||||
addFolder() {
|
addFolder() {
|
||||||
const _this = this;
|
const _this = this;
|
||||||
dialog
|
platform.showOpenDirectoryDialog({ properties: ["openDirectory", "multiSelections"] }).then((res) => {
|
||||||
.showOpenDialog({
|
(res.filePaths || []).forEach((item) => {
|
||||||
properties: ["openDirectory", "multiSelections"],
|
const result = _this.insertList({ name: getFileName(item), path: item, size: -1, folder: true });
|
||||||
})
|
if (result && platform.hasNativeFs && platform.hasNativeFs()) {
|
||||||
.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);
|
calcSize(item, _this.folderCalcCallback);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}).catch(() => {});
|
||||||
},
|
},
|
||||||
folderCalcCallback(err, res, path) {
|
folderCalcCallback(err, res, path) {
|
||||||
if (this.filesList[path]) {
|
if (this.filesList[path]) {
|
||||||
@@ -194,17 +177,9 @@ export default {
|
|||||||
this.allNumber--;
|
this.allNumber--;
|
||||||
},
|
},
|
||||||
dropFolderCheck(f) {
|
dropFolderCheck(f) {
|
||||||
//T是文件夹 F不是文件夹
|
if (f.size != 0 && f.size != 4096) return false;
|
||||||
//拖放无法从参数判断是否为文件夹,需要额外处理
|
if (f.type != "") return false;
|
||||||
if (f.size != 0 && f.size != 4096) {
|
return isFolder(f.path || f.name || '');
|
||||||
//返回大小不是0,则不是文件夹
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (f.type != "") {
|
|
||||||
//如果type不是空,则不是文件夹
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return isFolder(f.path);
|
|
||||||
},
|
},
|
||||||
sizeChange(size) {
|
sizeChange(size) {
|
||||||
this.allSize = this.allSize + size;
|
this.allSize = this.allSize + size;
|
||||||
@@ -255,18 +230,13 @@ export default {
|
|||||||
if (this.isCopy) {
|
if (this.isCopy) {
|
||||||
this.overNumber = 0;
|
this.overNumber = 0;
|
||||||
this.changeProgressvisible(true);
|
this.changeProgressvisible(true);
|
||||||
|
if (!copyFn) {
|
||||||
|
this.$message && this.$message({ message: '仅桌面端支持', type: 'warning' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (let i in this.filesList) {
|
for (let i in this.filesList) {
|
||||||
// const path = "D:\\copytest\\1\\" + this.filesList[i].name;
|
|
||||||
const path = this.copyPath + this.filesList[i].name;
|
const path = this.copyPath + this.filesList[i].name;
|
||||||
console.log(this.copyPath);
|
copyFn(i, path, this.filesList[i].folder, this.fileBack, this.filesList[i]);
|
||||||
console.log(path);
|
|
||||||
copy(
|
|
||||||
i,
|
|
||||||
path,
|
|
||||||
this.filesList[i].folder,
|
|
||||||
this.fileBack,
|
|
||||||
this.filesList[i]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//2023-04-24修改为所有都只上传文件路径,不需要压缩
|
//2023-04-24修改为所有都只上传文件路径,不需要压缩
|
||||||
@@ -281,18 +251,24 @@ export default {
|
|||||||
} else if (file_form == 1) {
|
} else if (file_form == 1) {
|
||||||
//电子光盘
|
//电子光盘
|
||||||
} else if (file_form == 2) {
|
} else if (file_form == 2) {
|
||||||
//zip
|
if (!zipFn) {
|
||||||
|
this.$message && this.$message({ message: '仅桌面端支持', type: 'warning' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.archiverIsover = false;
|
this.archiverIsover = false;
|
||||||
this.archiverIsfalse = false;
|
this.archiverIsfalse = false;
|
||||||
this.zip_path = "D:/archivertest/1.zip";
|
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) {
|
} else if (file_form == 3) {
|
||||||
//加密zip
|
if (!zipFn) {
|
||||||
|
this.$message && this.$message({ message: '仅桌面端支持', type: 'warning' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.archiverIsover = false;
|
this.archiverIsover = false;
|
||||||
this.archiverIsfalse = false;
|
this.archiverIsfalse = false;
|
||||||
this.zip_path = "D:/archivertest/2.zip";
|
this.zip_path = "D:/archivertest/2.zip";
|
||||||
let password = "123456";
|
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) {
|
} else if (file_form == 4) {
|
||||||
//u盘
|
//u盘
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,8 +52,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
const { dialog } = require("@electron/remote");
|
import platform from "@/platform";
|
||||||
const fs = require("fs");
|
|
||||||
import fileEmpty from "./fileEmpty";
|
import fileEmpty from "./fileEmpty";
|
||||||
import fileList from "./fileList";
|
import fileList from "./fileList";
|
||||||
import progressdialog from "./progressdialog";
|
import progressdialog from "./progressdialog";
|
||||||
@@ -109,15 +108,15 @@ export default {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// e.stopPropagation();
|
// e.stopPropagation();
|
||||||
for (const f of e.dataTransfer.files) {
|
for (const f of e.dataTransfer.files) {
|
||||||
|
const pathKey = f.path || ('web://' + (f.name || 'file') + '_' + Date.now() + Math.random());
|
||||||
const isFolder = _this.dropFolderCheck(f);
|
const isFolder = _this.dropFolderCheck(f);
|
||||||
_this.insertList({
|
_this.insertList({
|
||||||
name: getFileName(f.path),
|
name: getFileName(f.path || f.name),
|
||||||
path: f.path,
|
path: pathKey,
|
||||||
size: isFolder ? -1 : f.size,
|
size: isFolder ? -1 : f.size,
|
||||||
folder: isFolder,
|
folder: isFolder,
|
||||||
});
|
});
|
||||||
|
if (isFolder && platform.hasNativeFs && platform.hasNativeFs()) {
|
||||||
if (isFolder) {
|
|
||||||
calcSize(f.path, _this.folderCalcCallback);
|
calcSize(f.path, _this.folderCalcCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,65 +143,51 @@ export default {
|
|||||||
},
|
},
|
||||||
addFile() {
|
addFile() {
|
||||||
const _this = this;
|
const _this = this;
|
||||||
console.log(_this.copyType)
|
const filters = _this.copyType == 2 ? [{ name: '镜像文件', extensions: ['ISO', 'IMG'] }] : [{ name: '所有文件', extensions: ['*'] }];
|
||||||
const properties = _this.copyType == 2 ? [] : ["multiSelections"];
|
platform.showOpenFileDialog({ filters }).then((rel) => {
|
||||||
const filters = _this.copyType == 2 ? [{ name: '镜像文件', extensions: ['ISO', 'IMG'] }] : [{ name: '所有文件', extensions: ['*'] }]
|
if (rel.file && !platform.hasNativeFs()) {
|
||||||
dialog
|
const pathKey = 'web://' + rel.file.name + '_' + Date.now();
|
||||||
.showOpenDialog({
|
_this.insertList({ name: getFileName(rel.file.name), path: pathKey, size: rel.file.size || 0, folder: false });
|
||||||
filters,
|
return;
|
||||||
properties
|
}
|
||||||
})
|
const paths = rel.filePaths || [];
|
||||||
.then(async (rel) => {
|
paths.forEach((item) => {
|
||||||
console.log(rel);
|
try {
|
||||||
for (const item of rel.filePaths) {
|
const stats = platform.statSync(item);
|
||||||
const stats = fs.statSync(item);
|
|
||||||
if (stats.isFile()) {
|
if (stats.isFile()) {
|
||||||
await fs.stat(item, function (err, res) {
|
platform.stat(item).then((res) => {
|
||||||
if (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (_this.copyType == 2) {
|
if (_this.copyType == 2) {
|
||||||
_this.filesList = {}
|
_this.filesList = {};
|
||||||
_this.allNumber = 1;
|
_this.allNumber = 1;
|
||||||
_this.filesList[item] = {
|
_this.$set(_this.filesList, item, { name: getFileName(item), path: item, size: res.size, folder: false });
|
||||||
name: getFileName(item),
|
_this.sizeChange(res.size);
|
||||||
path: item,
|
return;
|
||||||
size: res.size,
|
|
||||||
folder: false,
|
|
||||||
};
|
|
||||||
calcSize(item, _this.folderCalcCallback);
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
_this.insertList({
|
_this.insertList({ name: getFileName(item), path: item, size: res.size, folder: false });
|
||||||
name: getFileName(item),
|
|
||||||
path: item,
|
|
||||||
size: res.size,
|
|
||||||
folder: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}).catch(() => {});
|
||||||
},
|
},
|
||||||
addFolder() {
|
addFolder() {
|
||||||
const _this = this;
|
const _this = this;
|
||||||
dialog
|
platform.showOpenDirectoryDialog({ properties: ["openDirectory", "multiSelections"] }).then((res) => {
|
||||||
.showOpenDialog({
|
const filePaths = res.filePaths || [];
|
||||||
properties: ["openDirectory", "multiSelections"],
|
filePaths.forEach((item) => {
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
for (const item of res.filePaths) {
|
|
||||||
const result = _this.insertList({
|
const result = _this.insertList({
|
||||||
name: getFileName(item),
|
name: getFileName(item),
|
||||||
path: item,
|
path: item,
|
||||||
size: -1,
|
size: -1,
|
||||||
folder: true,
|
folder: true,
|
||||||
});
|
});
|
||||||
if (result) {
|
if (result && platform.hasNativeFs && platform.hasNativeFs()) {
|
||||||
calcSize(item, _this.folderCalcCallback);
|
calcSize(item, _this.folderCalcCallback);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}).catch(() => {});
|
||||||
},
|
},
|
||||||
folderCalcCallback(err, res, path) {
|
folderCalcCallback(err, res, path) {
|
||||||
if (this.filesList[path]) {
|
if (this.filesList[path]) {
|
||||||
|
|||||||
@@ -163,7 +163,8 @@
|
|||||||
<div @click="openFile" class="work_right_top_file">···</div>
|
<div @click="openFile" class="work_right_top_file">···</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</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>
|
||||||
<div class="wook_soon" :class="{ wook_soon1: !showList }">
|
<div class="wook_soon" :class="{ wook_soon1: !showList }">
|
||||||
<div class="flex_box flex_row_center wook_soon_top">
|
<div class="flex_box flex_row_center wook_soon_top">
|
||||||
@@ -324,18 +325,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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'
|
import { mapGetters } from 'vuex'
|
||||||
const dayjs = require('dayjs')
|
const dayjs = require('dayjs')
|
||||||
|
import platform from '@/platform'
|
||||||
import fileEmpty from './files/fileEmpty'
|
import fileEmpty from './files/fileEmpty'
|
||||||
import files from './files/file'
|
import files from './files/file'
|
||||||
|
|
||||||
const exePath = !app.isPackaged ? process.cwd() : path.dirname(process.execPath)
|
|
||||||
export default {
|
export default {
|
||||||
name: 'UserInfo',
|
name: 'UserInfo',
|
||||||
components: { fileEmpty, files },
|
components: { fileEmpty, files },
|
||||||
@@ -600,32 +595,18 @@ export default {
|
|||||||
that.csvForm.req_info.uuid = that.form.task_uuid
|
that.csvForm.req_info.uuid = that.form.task_uuid
|
||||||
}
|
}
|
||||||
if (that.saveWorkList.json_file) {
|
if (that.saveWorkList.json_file) {
|
||||||
fs.readFile(that.saveWorkList.json_file, 'utf8', (err, data) => {
|
platform.readFile(that.saveWorkList.json_file, 'utf8').then((data) => {
|
||||||
if (err) {
|
const name = (that.saveWorkList.json_file || '').split(/[/\\]/).pop() || 'file'
|
||||||
console.error('读取文件时出错:', err)
|
that.fileLists[0] = new File([data], name, { type: '' })
|
||||||
return
|
}).catch((err) => { console.error('读取文件时出错:', err) })
|
||||||
}
|
|
||||||
let name = that.saveWorkList.json_file.split('\\')[1]
|
|
||||||
that.fileLists[0] = new File([data], name, {
|
|
||||||
type: ''
|
|
||||||
})
|
|
||||||
// that.readFile(data)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if (that.saveWorkList.udf_file) {
|
if (that.saveWorkList.udf_file) {
|
||||||
that.csvIsExist = true
|
that.csvIsExist = true
|
||||||
console.log(that.saveWorkList.udf_file, that.csvIsExist)
|
platform.readFile(that.saveWorkList.udf_file, 'utf8').then((data) => {
|
||||||
fs.readFile(that.saveWorkList.udf_file, 'utf8', (err, data) => {
|
const name = (that.saveWorkList.udf_file || '').split(/[/\\]/).pop() || 'file'
|
||||||
if (err) {
|
|
||||||
console.error('读取文件时出错:', err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let name = that.saveWorkList.udf_file.split('\\')[1]
|
|
||||||
that.file_name = name
|
that.file_name = name
|
||||||
that.fileLists[0] = new File([data], name, {
|
that.fileLists[0] = new File([data], name, { type: '' })
|
||||||
type: ''
|
}).catch((err) => { console.error('读取文件时出错:', err) })
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -634,6 +615,9 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['name', 'roles']),
|
...mapGetters(['name', 'roles']),
|
||||||
|
hasRunCmd() {
|
||||||
|
return platform.hasRunCmd && platform.hasRunCmd()
|
||||||
|
},
|
||||||
file_percent() {
|
file_percent() {
|
||||||
let disk = this.cd_types.find((item) => item.value === this.form.cd_type)
|
let disk = this.cd_types.find((item) => item.value === this.form.cd_type)
|
||||||
if (!disk) {
|
if (!disk) {
|
||||||
@@ -668,8 +652,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
// 右键事件
|
// 右键事件
|
||||||
showContextMenu() {
|
showContextMenu() {
|
||||||
console.log(123)
|
platform.showContextMenu()
|
||||||
ipcRenderer.send('show-context-menu');
|
|
||||||
},
|
},
|
||||||
// 进度条处理
|
// 进度条处理
|
||||||
format(percentage) {
|
format(percentage) {
|
||||||
@@ -691,15 +674,13 @@ export default {
|
|||||||
sizeChange(size) {
|
sizeChange(size) {
|
||||||
this.totalSize = size
|
this.totalSize = size
|
||||||
},
|
},
|
||||||
// 选择路径
|
// 选择路径(仅桌面端支持)
|
||||||
selectPath() {
|
selectPath() {
|
||||||
dialog
|
platform.showOpenDirectoryDialog({ properties: ['openDirectory'] })
|
||||||
.showOpenDialog({
|
|
||||||
properties: ["openDirectory"],
|
|
||||||
})
|
|
||||||
.then((res) => {
|
.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() { },
|
async upload_over() { },
|
||||||
// 保存
|
// 保存
|
||||||
@@ -735,21 +716,24 @@ export default {
|
|||||||
save.soonImg = that.soonImg
|
save.soonImg = that.soonImg
|
||||||
save.soonList = that.soonList
|
save.soonList = that.soonList
|
||||||
const v = JSON.stringify(save)
|
const v = JSON.stringify(save)
|
||||||
dialog
|
platform
|
||||||
.showSaveDialog({
|
.showSaveFileDialog({
|
||||||
title: 'Save',
|
title: 'Save',
|
||||||
filters: [{ name: 'Soon Work', extensions: ['dwk'] }]
|
filters: [{ name: 'Soon Work', extensions: ['dwk'] }]
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.filePath == "") { return; }
|
let filePath = result.filePath || ''
|
||||||
if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) {
|
if (!filePath) return
|
||||||
result.filePath += '.dwk';
|
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) => {
|
.catch((err) => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
@@ -777,11 +761,16 @@ export default {
|
|||||||
for (let key in files) {
|
for (let key in files) {
|
||||||
let file = { ...files[key] }
|
let file = { ...files[key] }
|
||||||
try {
|
try {
|
||||||
const fileStats = fs.statSync(files[key].path)
|
const fileStats = platform.statSync(files[key].path)
|
||||||
file.mtime = fileStats.mtimeMs
|
file.mtime = fileStats.mtimeMs
|
||||||
fileList.push(file)
|
fileList.push(file)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (platform.hasNativeFs && platform.hasNativeFs()) {
|
||||||
console.error('Error reading file:', error)
|
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)
|
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() {
|
getTemplates() {
|
||||||
let that = this
|
const that = this
|
||||||
const filePath = path.join(exePath, 'User Templates');
|
const filePath = platform.pathJoin(platform.getAppRoot(), 'User Templates')
|
||||||
fs.readdir(filePath, (err, files) => {
|
platform.readdir(filePath).then((files) => {
|
||||||
if (err) {
|
const fileList = (files || []).map((file) => ({
|
||||||
console.log(err)
|
|
||||||
} else {
|
|
||||||
console.log(files)
|
|
||||||
const fileList = files.map((file) => {
|
|
||||||
return {
|
|
||||||
label: file,
|
label: file,
|
||||||
value: path.join('User Templates', file)
|
value: platform.pathJoin('User Templates', file)
|
||||||
}
|
}))
|
||||||
})
|
|
||||||
that.templates = fileList
|
that.templates = fileList
|
||||||
}
|
}).catch(() => {
|
||||||
|
that.templates = []
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 选择模板
|
// 选择模板(仅桌面端)
|
||||||
changeTemplate(e) {
|
changeTemplate(e) {
|
||||||
let that = this
|
const that = this
|
||||||
const fullPath = path.join(exePath, e)
|
const fullPath = platform.pathJoin(platform.getAppRoot(), e)
|
||||||
that.form.json_file = fullPath
|
that.form.json_file = fullPath
|
||||||
fs.readFile(fullPath, 'utf8', (err, data) => {
|
platform.readFile(fullPath, 'utf8').then((data) => {
|
||||||
if (err) {
|
const name = platform.pathBasename(fullPath)
|
||||||
console.error('读取文件时出错:', err)
|
that.fileLists[0] = new File([data], name, { type: '' })
|
||||||
return
|
|
||||||
}
|
|
||||||
let name = path.basename(fullPath)
|
|
||||||
that.fileLists[0] = new File([data], name, {
|
|
||||||
type: ''
|
|
||||||
})
|
|
||||||
that.readFile(data)
|
that.readFile(data)
|
||||||
})
|
}).catch((err) => { console.error('读取文件时出错:', err) })
|
||||||
},
|
},
|
||||||
// 读取文件信息
|
// 读取文件信息
|
||||||
readFile(data) {
|
readFile(data) {
|
||||||
@@ -1054,19 +1032,10 @@ export default {
|
|||||||
that.file_name = null
|
that.file_name = null
|
||||||
that.file_name3 = '添加图片文件'
|
that.file_name3 = '添加图片文件'
|
||||||
},
|
},
|
||||||
// 打开标签程序
|
// 打开标签程序(仅桌面端)
|
||||||
openDesign() {
|
openDesign() {
|
||||||
// 启动exe程序
|
platform.runCmd('soondesign').catch((err) => {
|
||||||
console.log('启动soondesign')
|
console.error('执行的错误:', err)
|
||||||
// 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}`)
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 文件上传处理
|
// 文件上传处理
|
||||||
@@ -1502,6 +1471,12 @@ export default {
|
|||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.work_right_top_add_disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-color: #ddd;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wook_soon {
|
.wook_soon {
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import 'element-ui/lib/theme-chalk/index.css'
|
|||||||
import App from './App'
|
import App from './App'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import store from './store'
|
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 i18n from './lang' // internationalization
|
||||||
import './permission' // permission control
|
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.http = Vue.prototype.$http = axios
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
// 设置公共方法
|
// 设置公共方法(runCmd 来自 platform,网页端为“仅桌面端支持”的 Promise.reject)
|
||||||
Vue.prototype.$accAdd = accAdd
|
Vue.prototype.$accAdd = accAdd
|
||||||
Vue.prototype.$accSub = accSub
|
Vue.prototype.$accSub = accSub
|
||||||
Vue.prototype.$accMul = accMul
|
Vue.prototype.$accMul = accMul
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import getters from './getters'
|
import getters from './getters'
|
||||||
|
|
||||||
import { createPersistedState, createSharedMutations } from 'vuex-electron'
|
|
||||||
|
|
||||||
import modules from './modules'
|
import modules from './modules'
|
||||||
|
|
||||||
|
// 仅桌面端引入 vuex-electron,避免网页构建报错
|
||||||
|
if (process.env.IS_WEB !== 'true') {
|
||||||
|
require('vuex-electron')
|
||||||
|
}
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
modules,
|
modules,
|
||||||
getters,
|
getters,
|
||||||
plugins: [
|
plugins: [],
|
||||||
// createPersistedState(),
|
|
||||||
// createSharedMutations()
|
|
||||||
],
|
|
||||||
strict: process.env.NODE_ENV !== 'production'
|
strict: process.env.NODE_ENV !== 'production'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,17 @@
|
|||||||
let timeouter = null
|
let timeouter = null
|
||||||
let websock = 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
|
// 状态JSON
|
||||||
// 设备状态
|
// 设备状态
|
||||||
const PrinterStatus = {
|
const PrinterStatus = {
|
||||||
@@ -348,7 +359,8 @@ const actions = {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
commit('setData', { name: 'connecting', data: true })
|
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) {
|
websock.onmessage = function (res) {
|
||||||
dispatch('websocketonmessage', res)
|
dispatch('websocketonmessage', res)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
* 处理不同操作系统的文件大小差异
|
* 处理不同操作系统的文件大小差异
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 检测操作系统
|
// 检测操作系统(网页端无 process.platform,返回 'web')
|
||||||
export function getOS() {
|
export function getOS() {
|
||||||
|
if (typeof process === 'undefined' || process.platform === undefined) return 'web'
|
||||||
const platform = process.platform
|
const platform = process.platform
|
||||||
if (platform === 'win32') return 'windows'
|
if (platform === 'win32') return 'windows'
|
||||||
if (platform === 'darwin') return 'mac'
|
if (platform === 'darwin') return 'mac'
|
||||||
@@ -20,7 +21,8 @@ export function getBaseSize() {
|
|||||||
const baseSizes = {
|
const baseSizes = {
|
||||||
windows: 1024, // Windows 使用 1024 进制 (二进制)
|
windows: 1024, // Windows 使用 1024 进制 (二进制)
|
||||||
mac: 1024, // macOS 使用 1024 进制 (十进制)
|
mac: 1024, // macOS 使用 1024 进制 (十进制)
|
||||||
linux: 1024 // Linux 使用 1024 进制 (十进制,遵循 SI 标准)
|
linux: 1024, // Linux 使用 1024 进制 (十进制,遵循 SI 标准)
|
||||||
|
web: 1024 // 网页端固定 1024
|
||||||
}
|
}
|
||||||
|
|
||||||
return baseSizes[os] || 1024
|
return baseSizes[os] || 1024
|
||||||
|
|||||||
@@ -477,18 +477,4 @@ export const pow1024 = (num) => {
|
|||||||
return Math.pow(baseSize, num)
|
return Math.pow(baseSize, num)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行脚本
|
// runCmd 已迁移至 @/platform,由 main.js 挂载到 Vue.prototype.$runCmd
|
||||||
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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
<div class="top_left_line"></div>
|
<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"
|
<el-popover v-if="guideStep" :placement="guideStep[4].placement" width="250" trigger="manual"
|
||||||
v-model="guideStep[4].show">
|
v-model="guideStep[4].show">
|
||||||
<div class="guide_box">
|
<div class="guide_box">
|
||||||
@@ -75,10 +75,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div slot="reference" class="flex_box flex_row_center top_left_status_box"
|
<div slot="reference" class="flex_box flex_row_center top_left_status_box"
|
||||||
:class="{ 'guide_body': beginStep && currentStep == 4 }">
|
: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>
|
inactive-color="#aaa"> </el-switch>
|
||||||
|
<span v-else class="top_left_status_text">网页端</span>
|
||||||
</div>
|
</div>
|
||||||
</el-popover>
|
</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-tooltip>
|
||||||
<el-popover v-if="guideStep" :placement="guideStep[5].placement" width="250" trigger="manual"
|
<el-popover v-if="guideStep" :placement="guideStep[5].placement" width="250" trigger="manual"
|
||||||
v-model="guideStep[5].show">
|
v-model="guideStep[5].show">
|
||||||
@@ -117,7 +123,7 @@
|
|||||||
<div v-if="printer_info.test_use === 255" class="top_tip"></div>
|
<div v-if="printer_info.test_use === 255" class="top_tip"></div>
|
||||||
<template v-else-if="printer_info.test_use > -1">
|
<template v-else-if="printer_info.test_use > -1">
|
||||||
<div class="top_tip">试用版 {{ printer_info.test_use }}天</div>
|
<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>
|
</template>
|
||||||
<div v-else-if="printer_info.test_use == -1" class="top_tip1">正在验证注册信息</div>
|
<div v-else-if="printer_info.test_use == -1" class="top_tip1">正在验证注册信息</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -522,16 +528,10 @@
|
|||||||
<script>
|
<script>
|
||||||
let that
|
let that
|
||||||
let outTimer
|
let outTimer
|
||||||
let fs = require('fs')
|
|
||||||
let path = require('path')
|
|
||||||
const { ipcRenderer } = require("electron");
|
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
const { app, dialog } = require('@electron/remote')
|
|
||||||
import UserInfo from '@/components/userInfo/userInfo.vue'
|
import UserInfo from '@/components/userInfo/userInfo.vue'
|
||||||
import WorkAdd from '@/components/workAdd/workAdd.vue'
|
import WorkAdd from '@/components/workAdd/workAdd.vue'
|
||||||
const exePath = !app.isPackaged ? process.cwd() : path.dirname(process.execPath)
|
import platform from '@/platform'
|
||||||
const shPath = path.join(exePath, 'CardsoonServer', 'control.sh')
|
|
||||||
const activePath = path.join(exePath, 'CardsoonServer', 'regist.sh')
|
|
||||||
export default {
|
export default {
|
||||||
name: 'dashboard',
|
name: 'dashboard',
|
||||||
components: { UserInfo, WorkAdd },
|
components: { UserInfo, WorkAdd },
|
||||||
@@ -876,6 +876,19 @@ export default {
|
|||||||
},
|
},
|
||||||
workStatus() {
|
workStatus() {
|
||||||
return this.isConnect && this.serviceStatus
|
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() {
|
mounted() {
|
||||||
@@ -1250,23 +1263,30 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
that.isNew = false
|
that.isNew = false
|
||||||
dialog
|
platform
|
||||||
.showOpenDialog({
|
.showOpenFileDialog({
|
||||||
title: '打开作业文件',
|
title: '打开作业文件',
|
||||||
filters: [{ name: 'Soon Work', extensions: ['dwk'] }]
|
filters: [{ name: 'Soon Work', extensions: ['dwk'] }]
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
fs.readFile(res.filePaths[0], (err, data) => {
|
const pathOrName = (res.filePaths && res.filePaths[0]) || res.path
|
||||||
let fName = res.filePaths[0].trim()
|
const contentP = res.content != null
|
||||||
let fileName = fName.substring(fName.lastIndexOf('\\') + 1)
|
? 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 = JSON.parse(data)
|
||||||
that.saveWorkList.task_name = fileName
|
that.saveWorkList.task_name = fileName
|
||||||
that.workShow = true
|
that.workShow = true
|
||||||
that.$nextTick(() => {
|
that.$nextTick(() => {
|
||||||
that.$refs.workAdd.show()
|
that.$refs.workAdd.show()
|
||||||
})
|
})
|
||||||
|
}).catch((err) => {
|
||||||
|
that.$message({ message: err && err.message ? err.message : '打开失败', type: 'error' })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.catch(() => {})
|
||||||
},
|
},
|
||||||
// 显示日志
|
// 显示日志
|
||||||
noticeChange() {
|
noticeChange() {
|
||||||
@@ -1486,7 +1506,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
help() {
|
help() {
|
||||||
ipcRenderer.send("open-help-file");
|
platform.openHelp()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1562,6 +1582,10 @@ $red: #ff0000;
|
|||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
zoom: 1.3;
|
zoom: 1.3;
|
||||||
}
|
}
|
||||||
|
.top_left_status_text {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.top_left_option {
|
.top_left_option {
|
||||||
|
|||||||
@@ -29,6 +29,18 @@
|
|||||||
<el-button class="login_btn" type="primary" :loading="loading" @click.native.prevent="handleLogin">{{
|
<el-button class="login_btn" type="primary" :loading="loading" @click.native.prevent="handleLogin">{{
|
||||||
$t('login.logIn') }}</el-button>
|
$t('login.logIn') }}</el-button>
|
||||||
</el-form-item>
|
</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>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,10 +95,25 @@ export default {
|
|||||||
},
|
},
|
||||||
isChecked: true,
|
isChecked: true,
|
||||||
loading: false,
|
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: {
|
methods: {
|
||||||
|
saveSocketApi() {
|
||||||
|
try {
|
||||||
|
const v = (this.socketApi || '').trim()
|
||||||
|
if (v) localStorage.setItem('WS_SOCKET_API', v)
|
||||||
|
} catch (e) {}
|
||||||
|
},
|
||||||
showPwd() {
|
showPwd() {
|
||||||
if (this.pwdType === 'password') {
|
if (this.pwdType === 'password') {
|
||||||
this.pwdType = ''
|
this.pwdType = ''
|
||||||
@@ -260,6 +287,20 @@ $light_gray: #eee;
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: #00BCC3;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user