Compare commits

...

10 Commits

Author SHA1 Message Date
24kycj c727492da2 优化错误 2026-06-24 11:13:47 +08:00
24kycj dee6029642 文字调整 2026-06-23 18:38:42 +08:00
24kycj 68d1968376 更新 2026-05-12 21:42:42 +08:00
24kycj 31c9d64ba9 管理页面设置光盘桶 2026-02-05 16:14:59 +08:00
24kycj b277ef38b3 优化bug 2026-01-19 02:46:47 +08:00
24kycj 9c471a494c 优化字段生成 2025-12-09 17:32:42 +08:00
24kycj 03a5a18998 更新 2025-12-06 00:27:29 +08:00
24kycj f0a3b92dd3 优化代码 2025-11-28 03:15:24 +08:00
24kycj ba25ad48f0 调整引导与帮助等 2025-11-15 14:01:39 +08:00
24kycj 4a412ea668 更新 2025-10-30 00:29:38 +08:00
35 changed files with 3002 additions and 1068 deletions
+1
View File
@@ -65,6 +65,7 @@ function startRenderer () {
contentBase: path.join(__dirname, '../'),
quiet: true,
hot: true,
disableHostCheck: true,
before (app, ctx) {
app.use(hotMiddleware)
ctx.middleware.waitUntilValid(() => {
+1
View File
@@ -139,6 +139,7 @@ let rendererConfig = {
options: options,
},
process,
isWeb: false,
};
},
minify: {
+20 -2
View File
@@ -4,6 +4,8 @@ process.env.BABEL_ENV = 'web'
const path = require('path')
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 CopyWebpackPlugin = require('copy-webpack-plugin')
@@ -107,6 +109,7 @@ let webConfig = {
options: options,
},
process,
isWeb: true,
};
},
minify: {
@@ -117,7 +120,9 @@ let webConfig = {
nodeModules: false
}),
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.NoEmitOnErrorsPlugin()
@@ -129,11 +134,24 @@ let webConfig = {
resolve: {
alias: {
'@': path.join(__dirname, '../src/renderer'),
'@/platform': path.join(__dirname, '../src/renderer/platform/web.js'),
'vue$': 'vue/dist/vue.esm.js'
},
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
}
/**
+5 -1
View File
@@ -1 +1,5 @@
功能:统一相对路径导入 fileSize 工具,修复打包别名解析问题
- 将 '@/utils/fileSize' 全部改为相对路径导入,兼容打包环境
- 修复 store 与页面中别名解析失败导致的模块找不到问题
- 保持逻辑不变,仅调整导入路径,降低打包风险
+226
View File
@@ -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 配置
### 情况 1WebSocket 服务在服务器本机
如果 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://`
### 情况 3WebSocket 服务在公网服务器
如果 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 服务地址。
+82
View File
@@ -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,连接时**优先使用该配置**,因此只要用户填写的地址在浏览器侧可达(本机/内网/公网),就可以正常连接。
+154
View File
@@ -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 Storeuser、chat、app 等) | 有改动 | store 仅桌面端条件引入 vuex-electron,网页端不引入 |
---
## 二、登录页(/login
| 功能 | 实现情况 | 说明 |
|------|----------|------|
| 用户名/密码输入、记住账号 | 无改动 | 纯表单 + localStorage |
| 登录请求(/user/login | 无改动 | 走 axios + BASE_API,网页端需后端可用 |
| 新手引导(第一步) | 无改动 | 纯前端弹窗与步骤 |
| **WebSocket 服务地址设置** | **有改动** | 登录页可展开「设置 WebSocket 服务地址」,填写后写入 localStoragekey: `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) | 有改动 | 桌面端:系统对话框选文件;网页端:可用平台 showOpenFileDialoginput 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/加密 zipfile-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. **提交任务**:若要在网页端完整使用「提交刻录/打印任务」,需后端提供文件上传接口,前端改为先上传再提交任务参数;当前实现仍为提交路径,仅桌面端可被本机服务访问。
按当前实现,网页端可正常完成:登录、看板查看、设备状态与指令、作业列表与重试/取消、打开/保存作业(选文件与下载)、系统管理、系统配置、日志查看等;仅上述「网页端无法实现」项在网页端为禁用或提示。
Binary file not shown.
+5
View File
@@ -12,6 +12,7 @@
"build:dir": "node .electron-vue/build.js && electron-builder --dir",
"build:clean": "cross-env BUILD_TARGET=clean 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:arm": "vue-cli-service electron:build -l --arm64",
"dev": "node .electron-vue/dev-runner.js",
@@ -33,6 +34,10 @@
{
"from": "./cardsoonServer",
"to": "."
},
{
"from": "./help",
"to": "."
}
],
"directories": {
+4 -4
View File
@@ -3,8 +3,8 @@
<head>
<meta charset="utf-8">
<title>DiscWorker V1.01</title>
<% if (htmlWebpackPlugin.options.nodeModules) { %>
<!-- Add `node_modules/` to global paths so `require` works properly in development -->
<% if (typeof isWeb === 'undefined' ? htmlWebpackPlugin.options.nodeModules : !isWeb && htmlWebpackPlugin.options.nodeModules) { %>
<!-- Add `node_modules/` to global paths so `require` works properly in development (Electron only) -->
<script>
require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>')
</script>
@@ -12,8 +12,8 @@
</head>
<body>
<div id="app"></div>
<!-- Set `__static` path to static files in production -->
<% if (!process.browser) { %>
<!-- Set `__static` path to static files in production (Electron only; do not inject in web build) -->
<% if (typeof isWeb === 'undefined' ? !process.browser : !isWeb) { %>
<script>
if (process.env.NODE_ENV !== 'development') window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
</script>
+38 -1
View File
@@ -1,4 +1,6 @@
import { app, BrowserWindow, Menu } from 'electron'
import { app, BrowserWindow, Menu, ipcMain, shell } from 'electron'
import path from 'path'
import fs from 'fs'
import '../renderer/store'
/**
@@ -14,6 +16,18 @@ const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:9080`
: `file://${__dirname}/index.html`
// 获取应用根目录
// 开发环境:项目根目录(__dirname 指向 dist/electron
// 生产环境:可执行文件所在目录
const root = process.env.NODE_ENV === "development"
? path.resolve(__dirname, '../../')
: path.dirname(app.getPath("exe"))
// 日志函数
function writeLog(message) {
console.log(`[${new Date().toISOString()}] ${message}`)
}
function createWindow () {
/**
* Initial window options
@@ -75,3 +89,26 @@ app.on('ready', () => {
if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates()
})
*/
// IPC 处理:打开帮助文件
ipcMain.on('open-help-file', async event => {
const helpFilePath = path.join(root, "help", "User Manual.pdf");
if (!fs.existsSync(helpFilePath)) {
const errorMsg = `帮助文件不存在: ${helpFilePath}`;
writeLog(errorMsg);
if (event.reply) {
event.reply('open-help-file-error', errorMsg);
}
return;
}
try {
await shell.openPath(helpFilePath);
} catch (error) {
writeLog("打开帮助文件错误: " + error.message);
if (event.reply) {
event.reply('open-help-file-error', error.message);
}
}
});
+50
View File
@@ -228,4 +228,54 @@ export default {
color: #fff;
}
}
// 引导样式
.guide_body {
position: relative;
z-index: 200;
background-color: #fff;
padding: 0 10px;
border-radius: 4px;
border: 1px dashed #2e9bfb;
height: 100%;
}
.guide_box {
border: 1px dashed #2e9bfb;
padding: 12px;
.guide_title {
font-weight: bold;
font-size: 16px;
color: #000000;
line-height: 30px;
span {
font-size: 13px;
color: #999;
font-weight: 400;
}
}
.guide_desc {
font-size: 13px;
color: #000000;
line-height: 24px;
a {
color: #2e9bfb;
cursor: pointer;
}
}
.guide_btns {
padding: 10px 0 0;
display: flex;
align-items: center;
justify-content: end;
.guide_btn1 {
color: #999;
}
.guide_btn2 {
background-color: #2e9bfb;
border: none;
}
}
}
.el-popover {
padding: 0;
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

+104 -95
View File
@@ -8,8 +8,9 @@
</div>
<div class="top_left_line"></div>
<div class="flex_box flex_col_top tab_box">
<div @click="goPath(item)" v-for="item in tabs" :key="item.id" class="flex_box flex_row_center flex_col tab_item" :class="{'tab_item1': active===item.id}">
<div class="tab_name">{{item.name}}</div>
<div @click="goPath(item)" v-for="item in tabs" :key="item.id"
class="flex_box flex_row_center flex_col tab_item" :class="{ 'tab_item1': active === item.id }">
<div class="tab_name">{{ item.name }}</div>
<div class="tab_line"></div>
</div>
</div>
@@ -20,112 +21,120 @@
</template>
<script>
import { mapGetters } from 'vuex'
import UserInfo from '../userInfo/userInfo.vue';
export default {
name: 'TabBar',
components: { UserInfo },
props: {
active: {
type: Number,
default: 1
}
},
data () {
return {
tabs: [
{
id: 1,
name: '系统管理',
path: '/manage'
},
// {
// id: 2,
// name: '用户管理',
// path: '/user'
// },
{
id: 3,
name: '系统配置',
path: '/set'
}
]
}
},
computed: {
...mapGetters(['name', 'roles'])
},
methods: {
goHome() {
this.$router.go(-1)
},
goPath(e) {
console.log(e)
if (e.id === this.active) {
return
import { mapGetters } from 'vuex'
import UserInfo from '../userInfo/userInfo.vue';
export default {
name: 'TabBar',
components: { UserInfo },
props: {
active: {
type: Number,
default: 1
}
},
data() {
return {
tabs: [
{
id: 1,
name: '系统管理',
path: '/manage'
},
// {
// id: 2,
// name: '用户管理',
// path: '/user'
// },
{
id: 3,
name: '系统配置',
path: '/set'
}
this.$router.replace(e.path)
]
}
},
computed: {
...mapGetters(['name', 'roles'])
},
methods: {
goHome() {
this.$router.go(-1)
},
goPath(e) {
console.log(e)
if (e.id === this.active) {
return
}
this.$router.replace(e.path)
}
}
}
</script>
<style lang="scss" scoped>
.top_box {
height: 86px;
padding: 0 32px;
background-color: #ffffff;
border-bottom: 8px solid #F8F8F8;
.top_left {
.top_btn1 {
cursor: pointer;
padding: 0 16px;
height: 30px;
background: #00C325;
border-radius: 4px;
margin-right: 16px;
.top_box {
height: 86px;
padding: 0 32px;
background-color: #ffffff;
border-bottom: 8px solid #F8F8F8;
.top_left {
.top_btn1 {
cursor: pointer;
padding: 0 16px;
height: 30px;
background: #009688;
border-radius: 4px;
margin-right: 16px;
font-weight: 500;
font-size: 15px;
color: #FFFFFF;
img {
width: 18px;
height: 18px;
margin-right: 8px;
}
}
.top_left_line {
width: 2px;
height: 30px;
background: #E0E0E0;
margin: 0 16px;
}
.tab_box {
.tab_item {
padding: 20px 16px;
font-weight: 500;
font-size: 15px;
color: #FFFFFF;
img {
width: 18px;
height: 18px;
margin-right: 8px;
color: #000000;
line-height: 24px;
cursor: pointer;
.tab_name {
padding-bottom: 4px;
}
.tab_line {
display: none;
width: 24px;
height: 4px;
background: #009688;
border-radius: 4px;
}
}
.top_left_line {
width: 2px;
height: 30px;
background: #E0E0E0;
margin: 0 16px;
}
.tab_box {
.tab_item {
padding: 20px 16px;
font-weight: 500;
font-size: 15px;
color: #000000;
line-height: 24px;
cursor: pointer;
.tab_name {
padding-bottom: 4px;
}
.tab_line {
display: none;
width: 24px;
height: 4px;
background: #00C325;
border-radius: 4px;
}
}
.tab_item1 {
color: #00C325;
font-weight: bold;
.tab_line {
display: block;
}
.tab_item1 {
color: #009688;
font-weight: bold;
.tab_line {
display: block;
}
}
}
}
}
</style>
+34 -33
View File
@@ -1,49 +1,50 @@
<template>
<div>
<div class="flex_box top_right">
<div class="top_right_name">你好{{name}}</div>
<div class="top_right_name">你好{{ name }}</div>
<!-- <div @click="goOut" class="top_right_btn">登出</div> -->
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'UserInfo',
data () {
return {
}
},
computed: {
...mapGetters(['name', 'roles'])
},
methods: {
goOut() {
this.$store.dispatch('FedLogOut').then(() => {
location.reload()// In order to re-instantiate the vue-router object to avoid bugs
})
}
import { mapGetters } from 'vuex'
export default {
name: 'UserInfo',
data() {
return {
}
},
computed: {
...mapGetters(['name', 'roles'])
},
methods: {
goOut() {
this.$store.dispatch('FedLogOut').then(() => {
location.reload()// In order to re-instantiate the vue-router object to avoid bugs
})
}
}
}
</script>
<style lang="scss" scoped>
.top_right {
.top_right_name {
font-weight: 500;
font-size: 21px;
color: #000000;
padding: 0 24px;
}
.top_right_btn {
font-weight: 500;
font-size: 21px;
color: #00C325;
line-height: 50px;
padding-left: 24px;
cursor: pointer;
}
.top_right {
.top_right_name {
font-weight: 500;
font-size: 21px;
color: #000000;
padding: 0 24px;
}
.top_right_btn {
font-weight: 500;
font-size: 21px;
color: #009688;
line-height: 50px;
padding-left: 24px;
cursor: pointer;
}
}
</style>
+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]) {
+427 -284
View File
@@ -4,194 +4,196 @@
:close-on-click-modal="false" :show-close="false">
<div class="work_body" v-loading="submitLoading" element-loading-text="正在准备任务,您也可以直接右上角关闭作业窗口"
element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)">
<div v-if="currentStep && parseInt(currentStep) >= 8" class="bg_box"
style="position: absolute; top: 0; left: 0; z-index: 10; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5)">
</div>
<div @click="close" class="close_box">
<img src="@/assets/images/Close_icon.png" />
<i class="el-icon-close"></i>
</div>
<div class="work_box">
<div class="flex_box flex_row_between work_top">
<div class="flex_box">
<div class="flex_box work_top_item">
<div class="work_top_label">光盘类型</div>
<div class="work_top_val">
<el-select v-model="form.cd_type" placeholder="请选择">
<el-option v-for="item in cd_types" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<el-popover v-if="guideStep" :placement="guideStep[8].placement" width="250" trigger="manual"
v-model="guideStep[8].show">
<div class="guide_box">
<div class="guide_title">新手引导<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ guideStep[8].step }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">跳过引导</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1"
size="mini">上一步</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep ==
guideStep.length
- 1 ? '完成引导' : '下一步' }}</el-button>
</div>
</div>
<div class="flex_box work_top_item">
<div class="work_top_label">任务类型</div>
<div class="work_top_val">
<el-select v-model="form.copy_type" placeholder="请选择">
<el-option v-for="item in copyList" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<div class="flex_box" slot="reference" :class="{ 'guide_body': currentStep == 8 }">
<div class="flex_box work_top_item">
<div class="work_top_label">光盘类型</div>
<div class="work_top_val">
<el-select v-model="form.cd_type" placeholder="请选择">
<el-option v-for="item in cd_types" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
</div>
<div class="flex_box work_top_item">
<div class="work_top_label">任务类型</div>
<div class="work_top_val">
<el-select v-model="form.copy_type" placeholder="请选择">
<el-option v-for="item in copyList" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
</div>
</div>
</div>
<el-popover v-model="setShow" placement="bottom-end" trigger="click" @show="initSet">
<div class="set_box">
<div class="set_list">
<!-- <div class="flex_box set_item">
<div class="set_label">优先级</div>
<div class="set_val">
<el-select v-model="setForm.priority" placeholder="请选择">
<el-option v-for="item in priorityList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">目标工作站</div>
<div class="set_val">
<el-select v-model="setForm.stage" placeholder="请选择">
<el-option v-for="item in stageList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</div>
</div> -->
<div class="flex_box set_item">
<div class="set_label">刻录速率</div>
<div class="set_val">
<el-select v-model="setForm.speed" placeholder="请选择">
<el-option v-for="item in speedList" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">刻录完成后校验光盘</div>
<div class="set_val">
<el-switch v-model="setForm.isCheck" active-color="#07C160" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">刻录完成后关闭光盘</div>
<div class="set_val">
<el-switch v-model="setForm.isClose" active-color="#07C160" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">刻录完成后删除缓存</div>
<div class="set_val">
<el-switch v-model="setForm.isDel" active-color="#07C160" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">作业失败打印标签</div>
<div class="set_val">
<el-switch v-model="setForm.isPrint" active-color="#07C160" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">允许跨盘</div>
<div class="set_val">
<el-switch v-model="setForm.isArrow" active-color="#07C160" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">是否虚拟刻录</div>
<div class="set_val">
<el-switch v-model="setForm.is_sim" active-color="#07C160" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div v-if="setForm.isArrow" class="flex_box flex_col_top set_item">
<div class="set_label">分盘设置</div>
<div class="set_val">
<el-radio-group v-model="setForm.disk">
<el-radio :label="item.id" v-for="(item, index) in diskSet" :key="index">
<div class="set_disk_name">{{ item.name }}</div>
<div class="set_disk_desc">{{ item.desc }}</div>
</el-radio>
</el-radio-group>
</div>
</div>
</div>
<div class="flex_box flex_row_center set_btns">
<el-button @click="setCancel" class="set_btn1">取消</el-button>
<el-button @click="setSure" class="set_btn2" type="primary">确定</el-button>
</div>
</div>
<div class="flex_box work_top_set" slot="reference">
</el-popover>
<template v-if="!currentStep || currentStep < 8">
<div @click="openSetDialog" class="flex_box work_top_set">
<img src="@/assets/images/Setting_icon.png" />
<div>高级作业设置</div>
</div>
</el-popover>
</template>
<template v-else>
<el-popover v-if="guideStep" :placement="guideStep[11].placement" width="250" trigger="manual"
v-model="guideStep[11].show">
<div class="guide_box">
<div class="guide_title">新手引导<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ guideStep[11].step }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">跳过引导</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1"
size="mini">上一步</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep ==
guideStep.length
- 1 ? '完成引导' : '下一步' }}</el-button>
</div>
</div>
<div slot="reference" :class="{ 'guide_body': currentStep == 11 }">
<div @click="openSetDialog" class="flex_box work_top_set">
<img src="@/assets/images/Setting_icon.png" />
<div>高级作业设置</div>
</div>
</div>
</el-popover>
</template>
</div>
<div class="flex_box flex_col_top work_main">
<div class="work_left">
<div v-if="form.copy_type == 3 || form.copy_type == 4" class="flex_box flex_row_center work_no">
<img src="@/assets/images/ElBanCircle.png" />
</div>
<div class="flex_box flex_row_between work_left_top">
<div class="flex_box">
<div class="work_left_top_label">内容</div>
<div v-if="form.copy_type !== 2" @click="addFolder" class="flex_box work_left_top_btn">
<img src="@/assets/images/file_icon.png" />
<div>添加文件夹</div>
<el-popover v-if="guideStep" :placement="guideStep[9].placement" width="250" trigger="manual"
v-model="guideStep[9].show">
<div class="guide_box">
<div class="guide_title">新手引导<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div @click="addFile" class="flex_box work_left_top_btn">
<img src="@/assets/images/paper_icon.png" />
<div>添加文件</div>
<div class="guide_desc">{{ guideStep[9].step }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">跳过引导</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1"
size="mini">上一步</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep ==
guideStep.length
- 1 ? '完成引导' : '下一步' }}</el-button>
</div>
</div>
<div @contextmenu.prevent="showContextMenu" class="work_left_top_input">
<el-input placeholder="请输入卷标" v-model="form.label">
<template slot="prepend">卷标</template>
</el-input>
<div slot="reference" :class="{ 'guide_body': currentStep == 9 }">
<div v-if="form.copy_type == 3 || form.copy_type == 4" class="flex_box flex_row_center work_no">
<img src="@/assets/images/ElBanCircle.png" />
</div>
<div class="flex_box flex_row_between work_left_top">
<div class="flex_box">
<div class="work_left_top_label">内容</div>
<div v-if="form.copy_type !== 2" @click="addFolder" class="flex_box work_left_top_btn">
<img src="@/assets/images/file_icon.png" />
<div>添加文件夹</div>
</div>
<div @click="addFile" class="flex_box work_left_top_btn">
<img src="@/assets/images/paper_icon.png" />
<div>添加文件</div>
</div>
</div>
<div @contextmenu.prevent="showContextMenu" class="work_left_top_input">
<el-input placeholder="请输入卷标" v-model="form.label">
<template slot="prepend">卷标</template>
</el-input>
</div>
</div>
<div class="work_files">
<div class="work_files_box">
<!-- 文件列表 -->
<files ref="files" :copyType="form.copy_type" :onSizechange="sizeChange" :complete="upload_over">
</files>
</div>
<div class="work_files_progress">
<el-progress :text-inside="true" :percentage="file_percent" :format="format"
:color="customColors"></el-progress>
</div>
</div>
</div>
</div>
<div class="work_files">
<div class="work_files_box">
<!-- 文件列表 -->
<files ref="files" :copyType="form.copy_type" :onSizechange="sizeChange" :complete="upload_over"></files>
</div>
<div class="work_files_progress">
<el-progress :text-inside="true" :percentage="file_percent" :format="format"
:color="customColors"></el-progress>
</div>
</div>
</el-popover>
</div>
<div class="work_right">
<!-- <div v-if="form.copy_type == 3" class="flex_box flex_row_center work_no">
<img src="@/assets/images/ElBanCircle.png" />
</div> -->
<div class="flex_box work_right_top">
<div class="flex_box">
<div class="work_right_top_label">标签</div>
<el-select @change="changeTemplate" v-model="currentTemplate" placeholder="选择一个标签或点击创建标签">
<el-option v-for="item in templates" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<el-tooltip content="选择更多模版" placement="bottom">
<div @click="openFile" class="work_right_top_file">···</div>
</el-tooltip>
</div>
<div @click="openDesign" class="work_right_top_add">新建标签</div>
</div>
<div class="wook_soon" :class="{ wook_soon1: !showList }">
<div class="flex_box flex_row_center wook_soon_top">
<img class="wook_soon_img" :src="soonImg" />
<div @click="showList = !showList" class="flex_box flex_row_center wook_soon_btn"
:class="{ wook_soon_btn1: !showList }">
<img src="@/assets/images/Arrow_up_icon.png" />
<el-popover v-if="guideStep" :placement="guideStep[10].placement" width="250" trigger="manual"
v-model="guideStep[10].show">
<div class="guide_box">
<div class="guide_title">新手引导<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ guideStep[10].step }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">跳过引导</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1"
size="mini">上一步</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep ==
guideStep.length
- 1 ? '完成引导' : '下一步' }}</el-button>
</div>
</div>
<div class="wook_soon_list">
<div v-for="(item, index) in soonList" :key="index" class="flex_box wook_soon_item">
<div class="wook_soon_label">{{ item.name }}</div>
<div class="wook_soon_input">
<div v-if="item.type == 1">
<input type="file" accept="image/*" :ref="item.origin_name" :data-name="item.origin_name" />
</div>
<div v-if="item.type == 3 || item.type == 4 || item.type == 5">
<el-input clearable :disabled="csvIsExist" v-model="item.val" placeholder="请输入"></el-input>
<div slot="reference" :class="{ 'guide_body': currentStep == 10 }">
<!-- <div v-if="form.copy_type == 3" class="flex_box flex_row_center work_no">
<img src="@/assets/images/ElBanCircle.png" />
</div> -->
<div class="flex_box work_right_top">
<div class="flex_box">
<div class="work_right_top_label">标签</div>
<el-select @change="changeTemplate" v-model="currentTemplate" placeholder="选择一个标签或点击创建标签">
<el-option v-for="item in templates" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
<el-tooltip content="选择更多模版" placement="bottom">
<div @click="openFile" class="work_right_top_file">···</div>
</el-tooltip>
</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">
<img class="wook_soon_img" :src="soonImg" />
<div @click="showList = !showList" class="flex_box flex_row_center wook_soon_btn"
:class="{ wook_soon_btn1: !showList }">
<i class="el-icon-arrow-up"></i>
</div>
</div>
<div @click="openFile2" v-if="item.type == 3 || item.type == 4 || item.type == 5"
class="wook_soon_add">添加字段
<div class="wook_soon_list">
<div v-for="(item, index) in soonList" :key="index" class="flex_box wook_soon_item">
<div class="wook_soon_label">{{ item.name }}</div>
<div class="wook_soon_input">
<div v-if="item.type == 1">
<input type="file" accept="image/*" :ref="item.origin_name" :data-name="item.origin_name" />
</div>
<div v-if="item.type == 3 || item.type == 4 || item.type == 5">
<el-input clearable :disabled="csvIsExist" v-model="item.val" placeholder="请输入"></el-input>
</div>
</div>
<div @click="openFile2" v-if="item.type == 3 || item.type == 4 || item.type == 5"
class="wook_soon_add">
添加字段
</div>
</div>
</div>
</div>
</div>
</div>
</el-popover>
</div>
</div>
</div>
@@ -209,10 +211,26 @@
</el-input>
</div>
</div>
<div class="flex_box footer_btns">
<el-button type="primary" class="footer_save" @click="workSave" :loading="saveFlag">保存</el-button>
<el-button type="primary" class="footer_submit" @click="workSubmit" :loading="submitFlag">提交</el-button>
</div>
<el-popover v-if="guideStep" :placement="guideStep[12].placement" width="250" trigger="manual"
v-model="guideStep[12].show">
<div class="guide_box">
<div class="guide_title">新手引导<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ guideStep[12].step }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">跳过引导</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1"
size="mini">上一步</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep ==
guideStep.length
- 1 ? '完成引导' : '下一步' }}</el-button>
</div>
</div>
<div class="flex_box footer_btns" slot="reference" :class="{ 'guide_body': currentStep == 12 }">
<el-button type="primary" class="footer_save" @click="workSave" :loading="saveFlag">保存</el-button>
<el-button type="primary" class="footer_submit" @click="workSubmit" :loading="submitFlag">提交</el-button>
</div>
</el-popover>
</div>
</div>
<!-- 上传文件区域 -->
@@ -220,22 +238,99 @@
<input type="file" accept=".csv" @change="fileLoad2" ref="refFile2" style="display: none" />
<input type="file" accept="..zip,.rar" @change="fileLoad3" ref="refFile3" style="display: none" />
</el-dialog>
<!-- 高级作业设置弹窗 -->
<el-dialog title="高级作业设置" :visible.sync="setShow" width="600px" @open="initSet">
<div class="set_box">
<div class="set_list">
<!-- <div class="flex_box set_item">
<div class="set_label">优先级</div>
<div class="set_val">
<el-select v-model="setForm.priority" placeholder="请选择">
<el-option v-for="item in priorityList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">目标工作站</div>
<div class="set_val">
<el-select v-model="setForm.stage" placeholder="请选择">
<el-option v-for="item in stageList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</div>
</div> -->
<div class="flex_box set_item">
<div class="set_label">刻录速率</div>
<div class="set_val">
<el-select v-model="setForm.speed" placeholder="请选择">
<el-option v-for="item in speedList" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">刻录完成后校验光盘</div>
<div class="set_val">
<el-switch v-model="setForm.isCheck" active-color="#009688" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">刻录完成后关闭光盘</div>
<div class="set_val">
<el-switch v-model="setForm.isClose" active-color="#009688" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">刻录完成后删除缓存</div>
<div class="set_val">
<el-switch v-model="setForm.isDel" active-color="#009688" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">作业失败打印标签</div>
<div class="set_val">
<el-switch v-model="setForm.isPrint" active-color="#009688" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">允许跨盘</div>
<div class="set_val">
<el-switch v-model="setForm.isArrow" active-color="#009688" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div class="flex_box set_item">
<div class="set_label">是否虚拟刻录</div>
<div class="set_val">
<el-switch v-model="setForm.is_sim" active-color="#009688" inactive-color="#aaa"> </el-switch>
</div>
</div>
<div v-if="setForm.isArrow" class="flex_box flex_col_top set_item">
<div class="set_label">分盘设置</div>
<div class="set_val">
<el-radio-group v-model="setForm.disk">
<el-radio :label="item.id" v-for="(item, index) in diskSet" :key="index">
<div class="set_disk_name">{{ item.name }}</div>
<div class="set_disk_desc">{{ item.desc }}</div>
</el-radio>
</el-radio-group>
</div>
</div>
</div>
<div class="flex_box flex_row_center set_btns">
<el-button @click="setCancel" class="set_btn1">取消</el-button>
<el-button @click="setSure" class="set_btn2" type="primary">确定</el-button>
</div>
</div>
</el-dialog>
</div>
</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 },
@@ -425,6 +520,9 @@ export default {
}
],
cdList: [],
// 新手引导
guideStep: null,
currentStep: 0,
}
},
watch: {
@@ -461,7 +559,7 @@ export default {
req_info: {}
})
.then(() => {
})
return
}
@@ -492,33 +590,23 @@ export default {
that.form[key] = that.saveWorkList[key]
}
that.form.task_name = that.saveWorkList.task_name
// 恢复task_uuid后,同步更新csvForm.req_info.uuid
if (that.form.task_uuid) {
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) })
}
}
})
@@ -527,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) {
@@ -535,7 +626,7 @@ export default {
const diskSize = disk.size
let t = diskSize ? parseFloat(this.$accDiv(this.totalSize, diskSize)) * 100 : 0
return t > 100 ? 100 : t
}
},
},
mounted() {
let workForm = localStorage.getItem('workForm')
@@ -547,13 +638,21 @@ export default {
if (setForm) {
this.$store.dispatch('setDatas', { name: 'setForm', data: JSON.parse(setForm) })
}
// 新手引导
const guideStepStr = localStorage.getItem('guideStep')
this.guideStep = guideStepStr ? JSON.parse(guideStepStr) : null
this.currentStep = localStorage.getItem('currentStep') || -1
if (this.guideStep && this.guideStep[this.currentStep]) {
this.guideStep[this.currentStep].show = false
setTimeout(() => {
this.guideStep[this.currentStep].show = true
}, 200)
}
},
methods: {
// 右键事件
showContextMenu() {
console.log(123)
ipcRenderer.send('show-context-menu');
platform.showContextMenu()
},
// 进度条处理
format(percentage) {
@@ -575,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() { },
// 保存
@@ -619,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)
@@ -661,16 +761,21 @@ 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)
if (that.form.copy_type == 2) {
if (fileList.length != 1 || !isISO) {
if (fileList.length != 1 || !isISO) {
this.$notify({
message: '仅可上传一个类型为ISO/IMG的文件',
type: 'warning'
@@ -686,7 +791,7 @@ export default {
return
}
if (that.form.copy_type == 3) {
if(that.cdList.length > 0) {
if (that.cdList.length > 0) {
that.$store
.dispatch('chat/websocketsend', {
req_name: 'openCDDevByPath',
@@ -865,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) {
@@ -938,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)
})
},
// 文件上传处理
@@ -1021,16 +1106,28 @@ export default {
show() {
this.addShow = true
this.getTemplates()
this.form.task_uuid = this.genTaskUUID()
this.csvForm.req_info.uuid = this.form.task_uuid
this.form.label = dayjs().format('YYYY-MM-DD')
console.log(this.form.task_uuid)
// 只有新建作业时才生成新的task_uuid和label,打开已有文件时保留原有的值
if (this.isNew) {
this.form.task_uuid = this.genTaskUUID()
this.csvForm.req_info.uuid = this.form.task_uuid
this.form.label = dayjs().format('YYYY-MM-DD')
} else {
// 打开已有文件时,使用从文件加载的task_uuid和labelisNew watch会恢复)
// 使用$nextTick确保isNew watch先执行,恢复task_uuid
this.$nextTick(() => {
this.csvForm.req_info.uuid = this.form.task_uuid
})
}
},
close() {
this.addShow = false
this.$emit('close')
},
// 高级设置
openSetDialog() {
this.initSet()
this.setShow = true
},
setCancel() {
this.setShow = false
},
@@ -1043,7 +1140,44 @@ export default {
initSet() {
this.setForm = { ...this.$store.state.user.setForm }
console.log(this.$store.state.user.setForm)
}
},
// 退出新手引导
exitGuide() {
for (let key in this.guideStep) {
this.guideStep[key].show = false
}
this.currentStep = -1
localStorage.setItem('currentStep', this.currentStep)
localStorage.setItem('guideStep', JSON.stringify(this.guideStep))
this.close()
},
// 上一步
prevStep(e) {
this.guideStep[this.currentStep].show = false
this.currentStep = parseInt(this.currentStep) - 1
if (this.guideStep[this.currentStep]) {
this.guideStep[this.currentStep].show = true
localStorage.setItem('guideStep', JSON.stringify(this.guideStep))
localStorage.setItem('currentStep', this.currentStep)
if (this.currentStep == 7) {
this.close()
}
} else {
this.exitGuide()
}
},
// 下一步
nextStep(e) {
this.guideStep[this.currentStep].show = false
this.currentStep = parseInt(this.currentStep) + 1
if (this.guideStep[this.currentStep]) {
this.guideStep[this.currentStep].show = true
localStorage.setItem('guideStep', JSON.stringify(this.guideStep))
localStorage.setItem('currentStep', this.currentStep)
} else {
this.exitGuide()
}
},
}
}
</script>
@@ -1078,15 +1212,17 @@ export default {
.close_box {
position: absolute;
z-index: 99;
top: 0;
right: 0;
top: 24px;
right: 24px;
cursor: pointer;
padding: 24px 24px 0 0;
img {
width: 28px;
height: 28px;
}
width: 30px;
height: 30px;
color: #000;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
}
.work_box {
@@ -1142,11 +1278,13 @@ export default {
font-weight: 500;
font-size: 18px;
color: #333;
&:hover {
background: rgba(0, 195, 37, 0.32);
border: 1px solid rgba(0, 195, 37, 0.32);
color: #00c325;
color: #009688;
}
img {
width: 22px;
height: 22px;
@@ -1203,10 +1341,11 @@ export default {
font-size: 16px;
color: #333;
margin-right: 22px;
&:hover {
background: rgba(0, 195, 37, 0.32);
border: 1px solid rgba(0, 195, 37, 0.32);
color: #00c325;
color: #009688;
}
img {
@@ -1332,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 {
@@ -1356,15 +1501,12 @@ export default {
width: 56px;
height: 56px;
z-index: 9;
background: #ffffff;
background: #fff;
border-radius: 4px;
transition: all 0.5s;
cursor: pointer;
img {
width: 25px;
height: 16px;
}
color: #000;
font-size: 24px;
}
.wook_soon_btn1 {
@@ -1512,6 +1654,7 @@ export default {
border: 1px solid #999;
padding: 0;
margin-right: 22px;
&:hover {
background: #00bcc3;
color: #ffffff;
@@ -1521,7 +1664,7 @@ export default {
.footer_submit {
width: 166px;
height: 45px;
background: #00c325;
background: #009688;
border-radius: 4px;
font-weight: 500;
font-size: 19px;
@@ -1621,7 +1764,7 @@ export default {
.set_btn2 {
width: 120px;
height: 40px;
background: #00c325;
background: #009688;
border-radius: 4px;
font-weight: 500;
font-size: 16px;
+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'
})
+54 -27
View File
@@ -2,6 +2,21 @@
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'
}
function asArray(val) {
return Array.isArray(val) ? val : []
}
// 状态JSON
// 设备状态
const PrinterStatus = {
@@ -213,6 +228,7 @@ const defaultData = {
lack_ink: 0, // 缺墨警告
cover_flag: false, //是否盖盖
printer_name: '', //设备名称
printer_path: '', //设备路径
printer_status: 'I', //设备状态 数字转成字符为I
printer_tray: 'I', //设备托盘状态 数字转成字符为I
red_last: 0, //红色墨盒遗留
@@ -221,6 +237,8 @@ const defaultData = {
system_name: '', //主机名称
is_set_cddev: 0,
serial_no: '', //设备序列号
free_cache_space: 51587919872, //缓存空间
total_cache_space: 105152176128, //总缓存空间
test_use: '', //试用天数
// 左右光盘桶
strong_list: [
@@ -345,7 +363,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)
}
@@ -361,20 +380,28 @@ const actions = {
},
// 接收消息
websocketonmessage({ state, commit, dispatch }, e) {
const data = JSON.parse(e.data) // 转json对象
if (data.resp_name) {
let data
try {
data = JSON.parse(e.data)
} catch (err) {
console.warn('websocket parse error:', err)
return
}
if (!data.resp_name) return
try {
const resp = data.resp_info || {}
// 作业列表
if (data.resp_name === 'task_list') {
commit('setData', { name: 'task_list', data: data.resp_info.task_list })
commit('setData', { name: 'task_list', data: asArray(resp.task_list) })
// 设备信息
} else if (data.resp_name === 'printer_info') {
let info = data.resp_info
info.printer_status = String.fromCharCode(info.printer_status)
info.printer_tray = String.fromCharCode(info.printer_tray)
let strong_list = {}
for (let i = 0; i < info.strong_list.length; i++) {
strong_list[info.strong_list[i].strong_pos] = info.strong_list[i]
}
const info = { ...resp }
if (info.printer_status != null) info.printer_status = String.fromCharCode(info.printer_status)
if (info.printer_tray != null) info.printer_tray = String.fromCharCode(info.printer_tray)
const strong_list = {}
asArray(info.strong_list).forEach((item) => {
strong_list[item.strong_pos] = item
})
info.strongList = strong_list
// if (info.cover_flag && info.printer_name !== 'SE3') {
// commit('setData', { name: 'printer_error', data: { type: 'error', notify: '设备门盖被打开,请关闭门盖。' } })
@@ -385,7 +412,7 @@ const actions = {
commit('setData', { name: 'printer_info', data: info })
// 驱动信息
} else if (data.resp_name === 'cd_info') {
const list = data.resp_info.cd_list
const list = asArray(resp.cd_list)
let cds = []
let isBD = false
for (let i = 0; i < list.length; i++) {
@@ -414,13 +441,13 @@ const actions = {
commit('setData', { name: 'cd_list', data: cds })
// 手动设置驱动
} else if (data.resp_name === 'get_all_cdlist') {
commit('setData', { name: 'cdList', data: data.resp_info.cd_list || [] })
commit('setData', { name: 'cdList', data: asArray(resp.cd_list) })
// 系统配置
} else if (data.resp_name === 'get_system_config') {
commit('setData', { name: 'settings', data: data.resp_info })
commit('setData', { name: 'settings', data: resp })
// 新增日志
} else if (data.resp_name === 'add_log') {
let logs = data.resp_info.add_log
const logs = asArray(resp.add_log)
let loglist = []
let logLists = [...state.logList]
for (let i = 0; i < logs.length; i++) {
@@ -436,7 +463,7 @@ const actions = {
commit('setData', { name: 'logList', data: list })
// 全部日志
} else if (data.resp_name === 'all_log') {
let logs = data.resp_info.add_log
const logs = asArray(resp.add_log)
let loglist = []
for (let i = 0; i < logs.length; i++) {
let texts = logs[i].split('|')
@@ -449,14 +476,15 @@ const actions = {
commit('setData', { name: 'logList', data: loglist })
// 提交任务报错处理
} else if (data.resp_name === 'submit_task_res') {
let error = data.resp_info
let task_res = '0x' + error.task_res.toString(16)
commit('setData', { name: 'printer_error', data: { type: 'error', notify: '任务' + error.task_uuid + '提交失败:' + submitErros[task_res] } })
if (resp.task_res != null) {
const task_res = '0x' + resp.task_res.toString(16)
commit('setData', { name: 'printer_error', data: { type: 'error', notify: '任务' + resp.task_uuid + '提交失败:' + submitErros[task_res] } })
}
// 打印报错处理
} else if (data.resp_name === 'printer_error') {
let error = data.resp_info
if (error.error) {
let errors = error.error
const error = resp
const errors = asArray(error.error)
if (errors.length) {
let continues = [8, 9, 10, 11, 12, 17, 19]
let nojumps = [7, 8, 9, 10, 11, 12, 17, 19]
let isClears = [20, 21]
@@ -525,15 +553,14 @@ const actions = {
}, 1000);
// 执行错误提示
} else {
if (data.resp_info.res_code === 0) {
if (resp.res_code === 0) {
} else {
commit('setData', { name: 'printer_error', data: { type: 'error', notify: data.resp_info.res_msg || '操作失败' } })
commit('setData', { name: 'printer_error', data: { type: 'error', notify: resp.res_msg || '操作失败' } })
}
}
} else {
// 未知消息
console.log('报错信息: ' + JSON.stringify(data))
} catch (err) {
console.warn('websocket handle error:', data.resp_name, err)
}
},
// 心跳检测重连
+16 -14
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'
@@ -19,11 +20,12 @@ export function getBaseSize() {
// 不同操作系统的基础单位
const baseSizes = {
windows: 1024, // Windows 使用 1024 进制 (二进制)
mac: 1000, // macOS 使用 1000 进制 (十进制)
linux: 1000 // Linux 使用 1000 进制 (十进制,遵循 SI 标准)
mac: 1024, // macOS 使用 1024 进制 (十进制)
linux: 1024, // Linux 使用 1024 进制 (十进制,遵循 SI 标准)
web: 1024 // 网页端固定 1024
}
return baseSizes[os] || 1000
return baseSizes[os] || 1024
}
// 计算光盘类型大小
@@ -36,10 +38,10 @@ export function calculateDiscSize(sizeInGB, os = null) {
export function getBaseSizeForOS(os) {
const baseSizes = {
windows: 1024, // Windows 使用 1024 进制 (二进制)
mac: 1000, // macOS 使用 1000 进制 (十进制)
linux: 1000 // Linux 使用 1000 进制 (十进制,遵循 SI 标准)
mac: 1024, // macOS 使用 1024 进制 (十进制)
linux: 1024 // Linux 使用 1024 进制 (十进制,遵循 SI 标准)
}
return baseSizes[os] || 1000
return baseSizes[os] || 1024
}
// 光盘类型配置
@@ -50,37 +52,37 @@ export function getDiscTypes(os = null) {
{
value: 1,
label: 'CD 700MB',
size: 700 * Math.pow(baseSize, 2)
size: 700 * Math.pow(1000, 2)
},
{
value: 2,
label: 'DVD 4.7GB',
size: 4.7 * Math.pow(baseSize, 3)
size: 4.7 * Math.pow(1000, 3)
},
{
value: 3,
label: 'DVD_DL 8.5GB',
size: 8.5 * Math.pow(baseSize, 3)
size: 8.5 * Math.pow(1000, 3)
},
{
value: 4,
label: 'BD 25GB',
size: 25 * Math.pow(baseSize, 3)
size: 25 * Math.pow(1000, 3)
},
{
value: 5,
label: 'BD_DL 50GB',
size: 50 * Math.pow(baseSize, 3)
size: 50 * Math.pow(1000, 3)
},
{
value: 6,
label: 'BD_TL 100GB',
size: 100 * Math.pow(baseSize, 3)
size: 100 * Math.pow(1000, 3)
},
{
value: 7,
label: 'BD_QL 128GB',
size: 128 * Math.pow(baseSize, 3)
size: 128 * Math.pow(1000, 3)
}
]
}
+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
File diff suppressed because it is too large Load Diff
+104 -22
View File
@@ -2,21 +2,23 @@
<div class="login-container">
<img class="login_bg" src="@/assets/images/login_bg.jpg" />
<div class="flex_box form_box">
<el-form class="flex_box flex_col flex_row_center login-form" autoComplete="on" :model="loginForm" :rules="loginRules" ref="loginForm" label-position="left">
<el-form class="flex_box flex_col flex_row_center login-form" autoComplete="on" :model="loginForm"
:rules="loginRules" ref="loginForm" label-position="left">
<img class="login_logo" src="@/assets/images/logo.png" />
<div class="login_box">
<div class="login_title">用户登录</div>
<div class="login_label">用户名</div>
<el-form-item prop="username">
<el-input name="username" type="text" v-model="loginForm.username" autoComplete="on" placeholder="username" />
<el-input name="username" type="text" v-model="loginForm.username" autoComplete="on"
placeholder="username" />
</el-form-item>
<div class="login_label">密码</div>
<el-form-item prop="password">
<el-input name="password" :type="pwdType" @keyup.enter.native="handleLogin" v-model="loginForm.password" autoComplete="on"
placeholder="password"></el-input>
<span class="show-pwd" @click="showPwd">
</span>
<el-input name="password" :type="pwdType" @keyup.enter.native="handleLogin" v-model="loginForm.password"
autoComplete="on" placeholder="password"></el-input>
<span class="show-pwd" @click="showPwd">
</span>
</el-form-item>
<el-form-item>
<div class="login_check">
@@ -24,11 +26,40 @@
</div>
</el-form-item>
<el-form-item>
<el-button class="login_btn" type="primary" :loading="loading" @click.native.prevent="handleLogin">{{ $t('login.logIn') }}</el-button>
<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>
<el-popover v-if="guideStep" :placement="guideStep[0].placement" width="250" trigger="manual"
v-model="guideStep[0].show">
<div class="guide_box">
<div class="guide_title">新手引导<span>{{ parseInt(currentStep) + 1 }}/{{ guideStep.length }}</span>
</div>
<div class="guide_desc">{{ guideStep[0].step }}</div>
<div class="guide_btns">
<el-button @click="exitGuide" class="guide_btn1" size="mini" type="text">跳过引导</el-button>
<el-button v-if="parseInt(currentStep) > 0" @click="prevStep" class="guide_btn1" size="mini">上一步</el-button>
<el-button @click="nextStep" class="guide_btn2" size="mini" type="primary">{{ currentStep ==
guideStep.length
- 1 ? '完成引导' : '下一步' }}</el-button>
</div>
</div>
</el-popover>
</div>
</template>
@@ -64,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 = ''
@@ -81,10 +127,10 @@ export default {
this.loading = true
setToken('data.token')
if (localStorage.getItem('setForm')) {
this.$store.dispatch('setDatas', {name: 'setForm', data: localStorage.getItem('setForm')})
this.$store.dispatch('setDatas', { name: 'setForm', data: localStorage.getItem('setForm') })
}
this.$store.dispatch('setDatas', {name: 'token', data: 'data.token'})
this.$store.dispatch('setDatas', {name: 'oneLogin', data: true})
this.$store.dispatch('setDatas', { name: 'token', data: 'data.token' })
this.$store.dispatch('setDatas', { name: 'oneLogin', data: true })
setTimeout(() => {
this.loading = false
this.$router.push({ path: '/' })
@@ -106,8 +152,8 @@ export default {
</script>
<style rel="stylesheet/scss" lang="scss">
$bg:#F1F1F1;
$light_gray:#000000;
$bg: #F1F1F1;
$light_gray: #000000;
/* reset element-ui css */
.login-container {
@@ -119,6 +165,7 @@ $light_gray:#000000;
background: #F1F1F1;
border-radius: 30px;
color: #000;
input {
background: transparent;
border: 0px;
@@ -128,6 +175,7 @@ $light_gray:#000000;
height: 60px;
font-size: 24px;
border-radius: 30px;
&:-webkit-autofill {
border-radius: 30px;
-webkit-box-shadow: 0 0 0px 1000px $bg inset !important;
@@ -136,23 +184,25 @@ $light_gray:#000000;
}
}
}
</style>
<style rel="stylesheet/scss" lang="scss" scoped>
$bg:#F1F1F1;
$dark_gray:#889aa4;
$light_gray:#eee;
$bg: #F1F1F1;
$dark_gray: #889aa4;
$light_gray: #eee;
.login-container {
position: fixed;
height: 100%;
width: 100%;
.login_bg {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
}
.form_box {
position: absolute;
left: 0;
@@ -162,60 +212,72 @@ $light_gray:#eee;
height: 100%;
padding: 0 0 0 120px;
}
.login-form {
height: 100%;
.login_logo {
width: 844px;
height: 158px;
margin-bottom: 48px;
}
.login_box {
width: 450px;
background-color: #fff;
border-radius: 24px;
padding: 48px;
.login_title {
font-weight: 500;
font-size: 30px;
color: #00C325;
color: #009688;
line-height: 44px;
text-align: center;
margin-bottom: 48px;
}
.login_label {
font-size: 24px;
color: #B2B2B2;
line-height: 36px;
margin-bottom: 24px;
}
.login_check {
::v-deep {
.el-checkbox {
display: flex;
align-items: center;
}
.el-checkbox__inner {
width: 24px;
height: 24px;
}
.el-checkbox__input.is-checked .el-checkbox__inner {
background-color: #00C325;
border-color: #00C325;
background-color: #009688;
border-color: #009688;
}
.el-checkbox__inner::after {
width: 6px;
height: 14px;
left: 8px;
}
.el-checkbox__label {
font-size: 24px;
line-height: 24px;
}
.el-checkbox__input.is-checked+.el-checkbox__label {
color: #00C325;
color: #009688;
}
}
}
.login_btn {
width: 100%;
height: 60px;
@@ -225,28 +287,47 @@ $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;
}
}
}
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
width: 30px;
display: inline-block;
&_login {
font-size: 20px;
}
}
.title {
font-size: 26px;
font-weight: 400;
@@ -255,6 +336,7 @@ $light_gray:#eee;
text-align: center;
font-weight: bold;
}
.show-pwd {
position: absolute;
right: 10px;
+287 -68
View File
@@ -16,7 +16,8 @@
<div class="form_label">设置光驱</div>
<div class="form_val">
<div class="flex_box flex_wrap form_tabs">
<div @click="handleAction(item)" v-for="(item, index) in actionList" :key="index" class="form_tab">{{ item.name }}</div>
<div @click="handleAction(item)" v-for="(item, index) in actionList" :key="index" class="form_tab">{{
item.name }}</div>
</div>
</div>
</div>
@@ -24,7 +25,8 @@
<div class="form_label">校准设备</div>
<div class="form_val">
<div class="flex_box flex_wrap form_tabs">
<div @click="handleMove(item)" v-for="(item, index) in moveList" :key="index" class="form_tab">{{ item.name }}</div>
<div @click="handleMove(item)" v-for="(item, index) in moveList" :key="index" class="form_tab">{{
item.name }}</div>
</div>
</div>
</div>
@@ -32,7 +34,8 @@
<div class="form_label">维护设备</div>
<div class="form_val">
<div class="flex_box flex_wrap form_tabs">
<div @click="handleService(item)" v-for="(item, index) in serviceList" :key="index" class="form_tab">{{ item.name }}</div>
<div @click="handleService(item)" v-for="(item, index) in serviceList" :key="index" class="form_tab">{{
item.name }}</div>
</div>
</div>
</div>
@@ -72,6 +75,12 @@
<el-checkbox v-model="isDel">服务重启时是否删除已完成和已取消任务?</el-checkbox>
</div>
</div>
<div class="flex_box form_option">
<div class="form_label">新手引导</div>
<div class="form_val">
<el-checkbox v-model="hideStepEnabled" @change="handleGuideChange">是否开启新手引导功能</el-checkbox>
</div>
</div>
</div>
</div>
<!-- <div class="form_item">
@@ -111,7 +120,8 @@
<div class="cd_name">{{ item.dev_name }}({{ item.dev_path }})</div>
<div class="flex_box cd_r">
<el-select v-model="item.cdType" @change="cdChange(index, item.cdType)">
<el-option v-for="type in cdOptions" :key="type.value" :label="type.label" :value="type.value"> </el-option>
<el-option v-for="type in cdOptions" :key="type.value" :label="type.label" :value="type.value">
</el-option>
</el-select>
<el-button @click="cdOpen(index)" type="success">打开</el-button>
<el-button @click="cdClose(index)" type="danger">关闭</el-button>
@@ -124,6 +134,32 @@
</div>
</div>
</el-dialog>
<!-- 设置光盘桶类型 -->
<el-dialog center title="设置光盘桶类型" :visible.sync="setCDShow" width="600px">
<div class="cd_box">
<div class="cd_title">请选择左右光盘桶所对应的光盘类型</div>
<div class="cd_desc">修改后将影响作业可选择的光盘类型请谨慎操作</div>
<div class="cd_list">
<div class="flex_box flex_row_between cd_item" v-for="(item, index) in changeCD.req_info.strong_list" :key="index">
<div class="cd_name">光盘桶 {{ item.strong_pos }}</div>
<div class="flex_box cd_r">
<el-select v-model="item.strong_type" @change="(val) => handleSetCDChange(index, val)">
<el-option
v-for="(label, key) in CDTypes"
:key="key"
:label="label"
:value="parseInt(key)">
</el-option>
</el-select>
</div>
</div>
</div>
<div class="flex_box flex_row_center cd_footer">
<el-button @click="handleSetCDCancel" class="cd_footer_btn1">取消</el-button>
<el-button @click="handleSetCDSubmit" class="cd_footer_btn2" type="primary">确定</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
@@ -170,6 +206,10 @@ export default {
// }
],
serviceList: [
{
id: 7,
name: '设置光盘桶'
},
{
id: 4,
name: '刷新光盘桶'
@@ -198,6 +238,7 @@ export default {
refreshTime: 10,
sleepTime: 300000,
isDel: true,
hideStepEnabled: false, // 新手引导开关状态
langVal: 'zhCN',
langList: [
{
@@ -228,7 +269,18 @@ export default {
value: 3,
label: '下光驱'
}
]
],
// 设置光盘桶
setCDShow: false,
CDTypes: {},
changeCD: {
req_name: 'setCDStrong',
req_type: 2,
req_info: {
strong_list: []
}
},
default_cd: []
}
},
computed: {
@@ -260,7 +312,7 @@ export default {
handler(val) {
let data = { ...val }
this.printer_info = { ...data }
if (val.printer_name && val.printer_name === 'SE3') {
if (val.printer_name && val.printer_name.indexOf('SE3') > -1) {
this.moveList = [
{
id: 4,
@@ -341,6 +393,10 @@ export default {
}
]
this.serviceList = [
{
id: 7,
name: '设置光盘桶'
},
{
id: 4,
name: '刷新光盘桶'
@@ -370,6 +426,13 @@ export default {
},
deep: true,
immediate: true
},
'$store.state.chat.CDTypes': {
handler(val) {
this.CDTypes = { ...val }
},
deep: true,
immediate: true
}
},
mounted() {
@@ -377,6 +440,9 @@ export default {
if (type && type === 'cddev') {
this.handleAction(this.actionList[0])
}
// 初始化新手引导开关状态
const hideStep = localStorage.getItem('hideStep')
this.hideStepEnabled = !hideStep || hideStep == 0
},
methods: {
// 校准设备
@@ -526,7 +592,7 @@ export default {
case 5:
// 校准墨盒上的打印头
that
.$confirm('请在进盘仓放入一张空白的可打印光盘,再点击确定', '温馨提示', {
.$confirm('请点击确定对齐打印机墨盒', '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
@@ -689,7 +755,7 @@ export default {
case 5:
// 进入循环模式
that
.$confirm('请确保已经取掉左右光盘桶托架,点击确定后设备将进入循环模式', '温馨提示', {
.$confirm('点击确定后设备将进入循环模式', '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
@@ -732,6 +798,32 @@ export default {
})
})
break
case 7:
// 设置光盘桶(手动选择左右光盘桶类型)
// 正在任务中禁止操作(与 dashboard handleRun 中的限制保持一致)
const cdList = that.$store.state.chat.cd_list || []
if ((cdList[0] && cdList[0].copy_scheduler) || (cdList[1] && cdList[1].copy_scheduler)) {
that.$message({
message: '正在任务中,无法进行操作',
type: 'warning'
})
return
}
if (!that.printer_info || !that.printer_info.strong_list || !that.printer_info.strong_list.length) {
that.$message({
message: '当前设备暂无光盘桶信息,无法设置',
type: 'error'
})
return
}
// 初始化默认光盘桶类型
that.changeCD.req_info.strong_list = that.printer_info.strong_list.map((item, index) => ({
strong_pos: index + 1,
strong_type: item.cd_type
}))
that.default_cd = JSON.parse(JSON.stringify(that.changeCD.req_info.strong_list))
that.setCDShow = true
break
}
},
cdChange(index, e) {
@@ -787,7 +879,7 @@ export default {
device_pos: 2
}
]
if (this.printer_info.printer_name !== 'SE3') {
if (this.printer_info.printer_name.indexOf('SE3') === -1) {
let top = this.cdList.filter((item) => item.cdType === 2)
let bottom = this.cdList.filter((item) => item.cdType === 3)
if (!top || top.length === 0) {
@@ -863,6 +955,147 @@ export default {
message: '设置成功',
type: 'success'
})
},
// 新手引导开关变化
handleGuideChange(value) {
if (value) {
// 开启新手引导:设置 hideStep 为 0
localStorage.setItem('hideStep', 0)
// 初始化 guideStep(如果不存在)
const guideStepStr = localStorage.getItem('guideStep')
if (!guideStepStr) {
this.initGuideStep()
}
this.$message({
message: '新手引导已开启',
type: 'success'
})
} else {
// 关闭新手引导:设置 hideStep 为 1
localStorage.setItem('hideStep', 1)
this.$message({
message: '新手引导已关闭',
type: 'success'
})
}
},
// 初始化新手引导步骤
initGuideStep() {
const guideStep = [
{
show: false,
placement: 'right',
step: '系统状态面板,显示系统名称,耗材信息等,通过下拉菜单可以执行设置光盘桶等常见操作。',
},
{
show: false,
placement: 'left',
step: '光驱状态面板,显示当前光驱工作状态,刻录进度和速度等相关信息。',
},
{
show: false,
placement: 'left',
step: '日志面板,显示当前日志动态。',
},
{
show: false,
placement: 'top',
step: '作业列表面板,显示当前作业信息。',
},
{
show: false,
placement: 'bottom',
step: '服务启动和停止切换按钮。',
},
{
show: false,
placement: 'bottom',
step: '任务统计面板。',
},
{
show: false,
placement: 'bottom',
step: '系统设置按钮,详情请见软件使用手册。',
},
{
show: false,
placement: 'bottom',
step: '新建作业和打开作业按钮。',
},
{
show: false,
placement: 'bottom',
step: '选择作业的光盘类型和任务类型。',
},
{
show: false,
placement: 'right',
step: '作业内容面板,选择文件夹和文件作为光盘刻录内容,修改光盘卷标名称。',
},
{
show: false,
placement: 'left',
step: '作业标签面板,选择或创建一个光盘标签,也可以通过下拉菜单选择默认标签,默认标签路径为软件目录下User Templates文件夹中。',
},
{
show: false,
placement: 'bottom',
step: '高级作业设置按钮,配置刻录速度,校验,跨盘,虚拟刻录等详细的作业设置。',
},
{
show: false,
placement: 'top',
step: '点击保存作业或提交按钮,还可以点击链接查看更多软件使用手册。',
},
]
localStorage.setItem('guideStep', JSON.stringify(guideStep))
localStorage.setItem('currentStep', 0)
},
// 设置光盘桶 - 选择类型
handleSetCDChange(index, val) {
if (!this.changeCD.req_info.strong_list[index]) return
this.changeCD.req_info.strong_list[index].strong_type = val
this.$set(this.changeCD.req_info.strong_list, index, {
...this.changeCD.req_info.strong_list[index]
})
},
// 设置光盘桶 - 取消
handleSetCDCancel() {
this.setCDShow = false
this.changeCD.req_info.strong_list = JSON.parse(JSON.stringify(this.default_cd || []))
},
// 设置光盘桶 - 确认
handleSetCDSubmit() {
let that = this
if (!that.changeCD.req_info.strong_list || !that.changeCD.req_info.strong_list.length) {
that.$message({
message: '请先设置光盘桶类型',
type: 'warning'
})
return
}
that.$store
.dispatch('chat/websocketsend', that.changeCD)
.then(() => {
// 同 dashboard 的行为:根据是否包含 BD 类型更新可选光盘类型
try {
const { getCurrentOSDiscTypes } = require('../../utils/fileSize')
let typeLists = getCurrentOSDiscTypes()
let isBD = that.changeCD.req_info.strong_list.findIndex(item => parseInt(item.strong_type) > 3)
if (isBD === -1) {
typeLists = typeLists.slice(0, 3)
}
that.$store.dispatch('chat/setDatas', { name: 'cd_types', data: typeLists })
} catch (err) {
// 安全兜底:不影响主流程
console.error(err)
}
that.$message({
message: '已发送指令',
type: 'success'
})
that.setCDShow = false
})
}
}
}
@@ -872,16 +1105,20 @@ export default {
.container_box {
.form_box {
padding: 0 32px;
.form_item {
padding: 10px 0;
.form_title {
border-bottom: 2px solid #e1f5e5;
.form_title_text {
font-weight: 500;
font-size: 25px;
color: #000000;
line-height: 37px;
}
.form_title_line {
width: 100%;
height: 6px;
@@ -889,19 +1126,24 @@ export default {
border-radius: 4px;
}
}
.form_content {
padding: 10px 0;
.form_option {
padding: 16px 0;
}
.form_label {
width: 140px;
font-weight: 500;
font-size: 19px;
color: #000000;
}
.form_val {
max-width: 1100px;
.form_tabs {
.form_tab {
width: 250px;
@@ -915,13 +1157,15 @@ export default {
font-weight: 500;
font-size: 19px;
margin: 6px;
&:hover {
background: rgba(0, 195, 37, 0.12);
border: 1px solid rgba(0, 195, 37, 0.32);
color: #00c325;
color: #009688;
}
}
}
.form_input {
width: 500px;
height: 42px;
@@ -929,6 +1173,7 @@ export default {
border-radius: 4px;
border: 1px solid #999;
overflow: hidden;
::v-deep {
.el-input-group__append {
font-size: 16px;
@@ -936,6 +1181,7 @@ export default {
font-weight: 500;
border-radius: 0;
}
.el-input__inner {
width: 360px;
height: 40px;
@@ -945,6 +1191,7 @@ export default {
font-size: 19px;
color: #000000;
}
.el-input__icon {
font-size: 19px;
height: 40px;
@@ -952,6 +1199,7 @@ export default {
font-weight: bold;
color: #000;
}
.el-button {
width: 140px;
height: 40px;
@@ -962,8 +1210,9 @@ export default {
color: #333;
font-weight: bold;
font-size: 16px;
&:hover {
background: #00c325;
background: #009688;
color: #fff;
border: none;
}
@@ -976,36 +1225,44 @@ export default {
display: flex;
align-items: center;
}
.el-checkbox__inner {
width: 24px;
height: 24px;
}
.el-checkbox__input.is-checked .el-checkbox__inner {
background-color: #00c325;
border-color: #00c325;
background-color: #009688;
border-color: #009688;
}
.el-checkbox__inner::after {
width: 6px;
height: 14px;
left: 8px;
}
.el-checkbox__label {
font-size: 19px;
line-height: 24px;
}
.el-checkbox__input.is-checked + .el-checkbox__label {
.el-checkbox__input.is-checked+.el-checkbox__label {
color: #333;
}
}
.form_select {
width: 500px;
height: 42px;
background: #ffffff;
border-radius: 4px;
border: 1px solid #98e8a6;
.el-select {
width: 100%;
padding-left: 10px;
::v-deep {
.el-input__inner {
border: none;
@@ -1016,6 +1273,7 @@ export default {
line-height: 42px;
color: #000000;
}
.el-input__icon {
font-size: 20px;
height: 42px;
@@ -1031,11 +1289,13 @@ export default {
}
}
}
.cd_box {
.el-input {
height: 40px;
line-height: 40px;
}
::v-deep {
.el-input__inner {
width: 240px;
@@ -1045,6 +1305,7 @@ export default {
color: #000;
font-weight: 500;
}
.el-input__icon {
font-size: 16px;
height: 40px;
@@ -1053,23 +1314,28 @@ export default {
color: #000;
}
}
.cd_title {
font-size: 16px;
font-weight: 500;
color: #000;
line-height: 28px;
}
.cd_desc {
font-size: 13px;
font-weight: 500;
color: #666;
line-height: 24px;
}
.cd_list {
padding: 32px 0;
.cd_item {
padding: 12px 0;
}
.el-button {
margin: 0;
padding: 0;
@@ -1077,17 +1343,21 @@ export default {
height: 40px;
font-size: 15px;
}
.cd_name {
font-size: 14px;
color: #000;
line-height: 24px;
}
.cd_r {
gap: 24px;
}
}
.cd_footer {
gap: 24px;
.cd_footer_btn1 {
width: 120px;
height: 40px;
@@ -1100,63 +1370,11 @@ export default {
padding: 0;
margin: 0 12px;
}
.cd_footer_btn2 {
width: 120px;
height: 40px;
background: #00c325;
border-radius: 4px;
font-weight: 500;
font-size: 16px;
padding: 0;
margin: 0 12px;
}
}
}
::v-deep .el-dialog__title {
font-size: 24px;
}
</style>
t: 24px;
}
.cd_list {
padding: 32px 0;
.cd_item {
padding: 12px 0;
}
.el-button {
margin: 0;
padding: 0;
width: 80px;
height: 40px;
font-size: 15px;
}
.cd_name {
font-size: 14px;
color: #000;
line-height: 24px;
}
.cd_r {
gap: 24px;
}
}
.cd_footer {
gap: 24px;
.cd_footer_btn1 {
width: 120px;
height: 40px;
background: #ffffff;
border-radius: 4px;
border: 1px solid #000000;
font-weight: 500;
font-size: 16px;
color: #000000;
padding: 0;
margin: 0 12px;
}
.cd_footer_btn2 {
width: 120px;
height: 40px;
background: #00c325;
background: #009688;
border-radius: 4px;
font-weight: 500;
font-size: 16px;
@@ -1165,6 +1383,7 @@ t: 24px;
}
}
}
::v-deep .el-dialog__title {
font-size: 24px;
}
+35 -32
View File
@@ -17,11 +17,7 @@
<div class="flex_box form_val">
<div class="form_select">
<el-select v-model="form.log_level" placeholder="请选择">
<el-option
v-for="item in levelList"
:key="item.value"
:label="item.label"
:value="item.value">
<el-option v-for="item in levelList" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
@@ -33,11 +29,7 @@
<div class="flex_box form_val">
<div class="form_select">
<el-select v-model="form.retry_freq" placeholder="请选择">
<el-option
v-for="item in timesList"
:key="item.value"
:label="item.label"
:value="item.value">
<el-option v-for="item in timesList" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
@@ -49,11 +41,7 @@
<div class="flex_box form_val">
<div class="form_select">
<el-select v-model="form.print_quality" placeholder="请选择">
<el-option
v-for="item in qualityList"
:key="item.value"
:label="item.label"
:value="item.value">
<el-option v-for="item in qualityList" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
@@ -83,11 +71,7 @@
<div class="flex_box form_val">
<div class="form_select">
<el-select v-model="form.has_sr0" placeholder="请选择">
<el-option
v-for="item in sr0CheckList"
:key="item.value"
:label="item.label"
:value="item.value">
<el-option v-for="item in sr0CheckList" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
@@ -98,7 +82,7 @@
<div class="form_label">RejectConfig</div>
<div class="flex_box form_val">
<div class="form_switch">
<el-switch active-color="#07C160" inactive-color="#aaa" v-model="form.RejectConfig"></el-switch>
<el-switch active-color="#009688" inactive-color="#aaa" v-model="form.RejectConfig"></el-switch>
</div>
<div class="form_text">出卡盒配置默认勾选为正常出卡取消勾选则从Reject出卡盒出卡</div>
</div>
@@ -107,7 +91,7 @@
<div class="form_label">StopOnFailure</div>
<div class="flex_box form_val">
<div class="form_switch">
<el-switch active-color="#07C160" inactive-color="#aaa" v-model="form.StopOnFailure"></el-switch>
<el-switch active-color="#009688" inactive-color="#aaa" v-model="form.StopOnFailure"></el-switch>
</div>
<div class="form_text">作业失败后停止接受任务默认不勾选</div>
</div>
@@ -116,7 +100,7 @@
<div class="form_label">KeepCombinedImage</div>
<div class="flex_box form_val">
<div class="form_switch">
<el-switch active-color="#07C160" inactive-color="#aaa" v-model="form.KeepCombinedImage"></el-switch>
<el-switch active-color="#009688" inactive-color="#aaa" v-model="form.KeepCombinedImage"></el-switch>
</div>
<div class="form_text">是否保留作业临时图片和字段数据</div>
</div>
@@ -125,7 +109,7 @@
<div class="form_label">CleanTaskFile</div>
<div class="flex_box form_val">
<div class="form_switch">
<el-switch active-color="#07C160" inactive-color="#aaa" v-model="form.CleanTaskFile"></el-switch>
<el-switch active-color="#009688" inactive-color="#aaa" v-model="form.CleanTaskFile"></el-switch>
</div>
<div class="form_text">是否清除作业临时文件</div>
</div>
@@ -134,7 +118,7 @@
<div class="form_label">UploadSharedDir</div>
<div class="flex_box form_val">
<div class="form_switch">
<el-switch active-color="#07C160" inactive-color="#aaa" v-model="form.UploadSharedDir"></el-switch>
<el-switch active-color="#009688" inactive-color="#aaa" v-model="form.UploadSharedDir"></el-switch>
</div>
<div class="form_text">启用作业缓存路径勾选后作业内容会先刻录到缓存路径后再刻录到存储卡</div>
</div>
@@ -241,7 +225,7 @@ export default {
watch: {
'$store.state.chat.settings': {
handler(val) {
this.form = {...val}
this.form = { ...val }
},
immediate: true,
deep: true
@@ -255,9 +239,9 @@ export default {
setSubmit() {
if (this.form.cache_path === '') {
this.$message({
message: '请输入作业缓存路径',
type: 'warning'
})
message: '请输入作业缓存路径',
type: 'warning'
})
return
}
this.$store
@@ -281,16 +265,20 @@ export default {
.container_box {
.form_box {
padding: 0 32px;
.form_item {
padding: 10px 0;
.form_title {
border-bottom: 2px solid #E1F5E5;
.form_title_text {
font-weight: 500;
font-size: 25px;
color: #000000;
line-height: 37px;
}
.form_title_line {
width: 100%;
height: 6px;
@@ -298,17 +286,21 @@ export default {
border-radius: 4px;
}
}
.form_content {
padding: 10px 0;
.form_option {
padding: 16px 0;
}
.form_label {
width: 300px;
font-weight: 500;
font-size: 19px;
color: #000000;
}
.form_val {
.form_input {
width: 300px;
@@ -317,6 +309,7 @@ export default {
border-radius: 4px;
border: 1px solid #98E8A6;
overflow: hidden;
::v-deep {
.el-input__inner {
width: 360px;
@@ -327,6 +320,7 @@ export default {
font-size: 19px;
color: #000000;
}
.el-input__icon {
font-size: 20px;
height: 40px;
@@ -334,19 +328,21 @@ export default {
font-weight: bold;
color: #000;
}
.el-button {
width: 140px;
height: 40px;
border: none;
padding: 0;
background: #00C325;
background: #009688;
border-radius: 0px 4px 4px 0px;
}
}
}
.form_switch {
width: 300px;
.el-switch {
zoom: 1.4;
}
@@ -358,9 +354,11 @@ export default {
background: #FFFFFF;
border-radius: 4px;
border: 1px solid #98E8A6;
.el-select {
width: 100%;
padding-left: 10px;
::v-deep {
.el-input__inner {
border: none;
@@ -371,6 +369,7 @@ export default {
line-height: 42px;
color: #000000;
}
.el-input__icon {
font-size: 20px;
height: 42px;
@@ -381,6 +380,7 @@ export default {
}
}
}
.form_text {
font-weight: 500;
font-size: 19px;
@@ -390,8 +390,10 @@ export default {
}
}
}
.footer_btns {
height: 80px;
.footer_submit {
width: 166px;
height: 45px;
@@ -402,8 +404,9 @@ export default {
font-size: 19px;
color: #333;
padding: 0;
&:hover {
background: #00C325;
background: #009688;
color: #FFFFFF;
}
}
+10 -3
View File
@@ -10,7 +10,8 @@
<el-table-column prop="logintime" label="最后登录时间"></el-table-column>
<el-table-column prop="status" label="状态">
<template slot-scope="scope">
<div class="table_status" :class="{'table_status1': scope.row.status === 1}">{{ scope.row.status === 1 ? '在线' : '离线' }}</div>
<div class="table_status" :class="{ 'table_status1': scope.row.status === 1 }">{{ scope.row.status === 1 ?
'在线' : '离线' }}</div>
</template>
</el-table-column>
</el-table>
@@ -49,7 +50,7 @@ export default {
...mapGetters(['name', 'roles'])
},
methods: {
}
}
</script>
@@ -57,16 +58,20 @@ export default {
<style rel="stylesheet/scss" lang="scss" scoped>
.container_box {
height: 100vh;
.table_box {
padding: 32px;
.el-table {
::v-deep {
th.el-table__cell {
background-color: #F2F9F4;
}
.el-table__cell {
padding: 6px 0;
}
.cell {
line-height: 28px;
font-size: 12px;
@@ -74,12 +79,14 @@ export default {
}
}
}
.table_status {
font-weight: 500;
color: #000000;
}
.table_status1 {
color: #00C325;
color: #009688;
}
}
}