first commit

This commit is contained in:
expressgy 2024-12-09 23:06:33 +08:00
commit 76a29d2cf1
86 changed files with 13872 additions and 0 deletions

8
.env Normal file
View File

@ -0,0 +1,8 @@
VITE_HOME_REDIRECT = '/initialization' # 默认路由
VITE_TITLE = 'JS-PAD' # 项目名称
VITE_SYSTEM_NAME = '延安嘉盛生产管理系统'
VITE_BASE_URL = '/api' # 请求默认前缀
VITE_HTTP_TIMEOUT = 30000
VITE_HTTP_PROXY = 'http://10.10.10.200:8080'
VITE_ELECTRON_IPC_MESSAGE_NAME = 'Electron.IpcRenderer.Message'
VITE_ELECTRON_MAIN_MESSAGE_NAME = 'Electron.Main.Message'

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-electron
out
out-render
release
dist-ssr
*.local
*.sqlite-shm
*.sqlite-wal
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

10
.npmrc Normal file
View File

@ -0,0 +1,10 @@
registry=https://registry.npmmirror.com/
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/
ELECTRON_CUSTOM_DIR="{{ version }}"
msvs_version=2022
#offline=true
prefer-offline=true
no-proxy=true
#https-proxy="http://127.0.0.1:7890"
#proxy="http://127.0.0.1:7890"

19
app.js Normal file
View File

@ -0,0 +1,19 @@
import { app, BrowserWindow } from 'electron';
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})

BIN
database.sqlite Normal file

Binary file not shown.

48
forge.config.js Normal file
View File

@ -0,0 +1,48 @@
import { FusesPlugin } from '@electron-forge/plugin-fuses';
import { FuseV1Options, FuseVersion} from '@electron/fuses';
export default {
packagerConfig: {
asar: {
smartUnpack: false,
unpackDir: 'out-render',
ignore: ['database.sqlite']
},
},
rebuildConfig: {},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {},
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin'],
},
{
name: '@electron-forge/maker-deb',
config: {},
},
{
name: '@electron-forge/maker-rpm',
config: {},
},
],
plugins: [
{
name: '@electron-forge/plugin-auto-unpack-natives',
config: {},
},
// Fuses are used to enable/disable various Electron functionality
// at package time, before code signing the application
new FusesPlugin({
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false,
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
],
};

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8"/>
<link rel="icon" type="image/svg+xml" href="/vite.svg"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>JS-II-PAD-Linux</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/render/main.js"></script>
</body>
</html>

9226
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

45
package.json Normal file
View File

@ -0,0 +1,45 @@
{
"name": "js-pad",
"version": "1.0.0",
"main": "src/electron/main.js",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"start": "cross-env NODE_ENV=development electron-forge start",
"package": "cross-env NODE_ENV=production electron-forge package",
"make": "electron-forge make"
},
"keywords": [],
"author": "nier",
"license": "ISC",
"description": "js pad",
"devDependencies": {
"@electron-forge/cli": "^7.6.0",
"@electron-forge/maker-deb": "^7.6.0",
"@electron-forge/maker-rpm": "^7.6.0",
"@electron-forge/maker-squirrel": "^7.6.0",
"@electron-forge/maker-zip": "^7.6.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.6.0",
"@electron-forge/plugin-fuses": "^7.6.0",
"@electron/fuses": "^1.8.0",
"@vitejs/plugin-vue": "^5.2.1",
"cross-env": "^7.0.3",
"electron": "^33.2.1",
"vite": "^6.0.3"
},
"dependencies": {
"ant-design-vue": "^4.2.6",
"axios": "^1.7.7",
"better-sqlite3": "^11.7.0",
"dayjs": "^1.11.13",
"electron-squirrel-startup": "^1.0.1",
"naive-ui": "^2.40.3",
"node-machine-id": "^1.1.12",
"pinia": "^2.3.0",
"sass": "^1.80.6",
"uuid": "^11.0.3",
"vfonts": "^0.0.3",
"vue-router": "^4.4.5"
}
}

87
src/electron/main.js Normal file
View File

@ -0,0 +1,87 @@
/**
*
* #适应性配置
*
*/
// * 允许使用require
const require = createRequire(import.meta.url)
// * 替换__dirname
const __dirname = path.dirname(fileURLToPath(import.meta.url))
// * 禁止页面控制台显示安全警告
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
/**
*
* # 全局配置
*
*/
// * 主进程发送给渲染进程消息的通道
global.MainChanleName = 'Electron.Main.Message'
// * 主进程接收渲染进程消息的通道
global.RenderChanleName = 'Electron.IpcRenderer.Message'
// * 主进程初始化状态
global.windowLoaded = false;
// * 程序启动时间
global.startTime = new Date().getTime();
// * 根目录
global.ROOT_DIR = path.join(__dirname, '../..')
console.log('root dir:', ROOT_DIR)
// * 主进程目录
global.ELECTRON_DIR = path.join(__dirname)
console.log('electron dir:', ELECTRON_DIR)
// * 渲染进程目录
global.RENDER_DIR = path.join(__dirname, '../../out-render')
console.log('render dir:', RENDER_DIR)
// * resource目录
global.RESOURCE_DIR = path.join(__dirname, '../../../app.asar.unpacked')
console.log('resource dir')
// * 是否是开发环境
global.isDev = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== undefined;
console.log('node env:', process.env.NODE_ENV)
import { app, BrowserWindow } from 'electron'
import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
import { runTask } from "./task/task.js";
function createWindow() {
const win = new BrowserWindow({
width: 1280,
height: 800,
show: false,
icon: path.join(RENDER_DIR, 'public/electron-vite.svg'),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
// 设置CSP
contentSecurityPolicy: "script-src 'self'; object-src 'self'",
devTools: true,
preload: path.join(__dirname, 'preload.mjs'),
},
frame: false // 设置无边框
})
runTask(win)
// 还原窗口
win.restore();
if (isDev) {
console.log('开发模式')
win.loadURL('http://localhost:17129')
} else {
// win.loadFile('dist/index.html')
win.loadFile(path.join(RESOURCE_DIR,'out-render', 'index.html'))
}
}
app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})

24
src/electron/preload.js Normal file
View File

@ -0,0 +1,24 @@
import { ipcRenderer, contextBridge } from 'electron'
// --------- Expose some API to the Renderer process ---------
contextBridge.exposeInMainWorld('ipcRenderer', {
on(...args) {
const [channel, listener] = args
return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args))
},
off(...args) {
const [channel, ...omit] = args
return ipcRenderer.off(channel, ...omit)
},
send(...args) {
const [channel, ...omit] = args
return ipcRenderer.send(channel, ...omit)
},
invoke(...args) {
const [channel, ...omit] = args
return ipcRenderer.invoke(channel, ...omit)
},
// You can expose other APTs you need here.
// ...
})

1
src/electron/preload.mjs Normal file
View File

@ -0,0 +1 @@
"use strict";const o=require("electron");o.contextBridge.exposeInMainWorld("ipcRenderer",{on(...e){const[n,r]=e;return o.ipcRenderer.on(n,(t,...c)=>r(t,...c))},off(...e){const[n,...r]=e;return o.ipcRenderer.off(n,...r)},send(...e){const[n,...r]=e;return o.ipcRenderer.send(n,...r)},invoke(...e){const[n,...r]=e;return o.ipcRenderer.invoke(n,...r)}});

View File

@ -0,0 +1,25 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: systemConfig.js -
// | @创建时间: 2024-11-11 17:14
// | @更新时间: 2024-11-11 17:14
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
// import {Sequelize} from "sequelize";
// export const systemConfig = {
// key: {
// type: Sequelize.STRING,
// unique: true,
// allowNull: false
// },
// value: {
// type: Sequelize.STRING,
// allowNull: false
// }
// }

View File

@ -0,0 +1,40 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: systemConfig.js -
// | @创建时间: 2024-11-11 17:29
// | @更新时间: 2024-11-11 17:29
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
export async function setServerHost(msg) {
// 查找是否存在ServerHost
const SQL = `SELECT * FROM SystemConfigs WHERE key = ?`
const data = await global.DB.prepare(SQL).get('ServerHost')
if (data === undefined) {
const createSQL = `INSERT INTO SystemConfigs (key, value) VALUES (?,?)`
await global.DB.prepare(createSQL).run('ServerHost', msg.data.host)
} else {
const updateSQL = `UPDATE SystemConfigs SET value = ? WHERE key = ?`
await global.DB.prepare(updateSQL).run('ServerHost', msg.data.host)
}
}
export async function setDeviceBinding(data) {
// 查找是否存在ServerHost
console.log('DD', data)
const SQL = `SELECT * FROM SystemConfigs WHERE key = ?;`
const result = await global.DB.prepare(SQL).get('DeviceBinding')
console.log(result)
if (result === undefined) {
const createSQL = `INSERT INTO SystemConfigs (key, value) VALUES (?,?)`
await global.DB.prepare(createSQL).run('DeviceBinding', JSON.stringify(data))
} else {
const updateSQL = `UPDATE SystemConfigs SET value = ? WHERE key = ?`
await global.DB.prepare(updateSQL).run('DeviceBinding', JSON.stringify(data))
}
}

View File

@ -0,0 +1,44 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: handleIpcChannel.js -
// | @创建时间: 2024-11-05 13:02
// | @更新时间: 2024-11-05 13:02
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import {toGetServerHost} from "./toRenderMessage.js";
import {connectServer} from "../searchServer/connectServer.js";
// 渲染器渲染成功
export function handleIpcChannel_Started(msg){
global.windowLoaded = true;
return;
}
// 重新扫描指定服务器地址
export async function handleIpcChannel_init_newServerHost(msg, win) {
try{
const serverHost = await connectServer(msg.data.host)
return toGetServerHost(win, {
status: 0,
serverHost
});
}catch (error) {
return toGetServerHost(win, {
status: -1,
msg: '链接服务器失败!'
});
}
}
// 存储服务器地址到本地配置数据库
export async function handleIpcChannel_init_saveServerHost(msg){
}

View File

@ -0,0 +1,37 @@
''// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: toRenderMessage.js -
// | @创建时间: 2024-11-04 15:08
// | @更新时间: 2024-11-04 15:08
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
function sendMsg(win, msg){
console.log('<==', msg)
win.webContents.send(MainChanleName, msg)
}
// 拿到服务器地址后的响应消息
export function toGetServerHost(win, option = {}) {
sendMsg(win, {
type: '/init/serverHost',
data: {
...option
}
})
}
// 拿到设备绑定信息后返回给页面
export function toGetDeviceBindingInfo(win, option = {}) {
sendMsg(win, {
type: '/init/deviceBindingInfo',
data: {
...option
}
})
}

View File

@ -0,0 +1,29 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: device.js -
// | @创建时间: 2024-11-26 10:53
// | @更新时间: 2024-11-26 10:53
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import DeviceService from "./service/device.service.js";
export default async function (ipcInvock){
switch (ipcInvock.path) {
// 将设备绑定信息写入本地
case '/device/setDeviceBinding':{
return DeviceService.setDeviceBinding(ipcInvock)
}
default: {
return {
code: 404,
message: `ipc invoke not found path: ${ipcInvoke.path}`,
data: {}
}
}
}
}

View File

@ -0,0 +1,34 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: index.js -
// | @创建时间: 2024-11-26 10:54
// | @更新时间: 2024-11-26 10:54
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import deviceRoute from "./device.route.js";
import initRoute from "./init.route.js";
export default function (ipcInvoke) {
console.log(`渲染器发送的请求: ${ipcInvoke.path}`, ipcInvoke.data);
switch (ipcInvoke.path.split('/')?.[1]) {
case 'init': {
return initRoute(ipcInvoke);
}
case 'device': {
return deviceRoute(ipcInvoke);
}
default: {
return {
code: 404,
message: `ipc invoke not found path: ${ipcInvoke.path}`,
data: {}
}
}
}
}

View File

@ -0,0 +1,33 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: init.route.js -
// | @创建时间: 2024-11-26 14:25
// | @更新时间: 2024-11-26 14:25
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import InitService from './service/init.service.js'
export default async function (ipcInvock) {
switch (ipcInvock.path) {
// 将服务器地址信息写入本地
case '/init/setServerHost': {
return InitService.setServerHost(ipcInvock)
}
// 探测手动输入的服务器地址
case '/init/sendNewServerHost':{
return InitService.checkServerHost(ipcInvock)
}
default: {
return {
code: 404,
message: `ipc invoke not found path: ${ipcInvoke.path}`,
data: {}
}
}
}
}

View File

@ -0,0 +1,23 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: device.service.js -
// | @创建时间: 2024-11-26 11:07
// | @更新时间: 2024-11-26 11:07
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import {setDeviceBinding} from "../../db/sql/systemConfig.js";
import {getSystemConfig} from "../../task_02_connectDB.js";
// DeviceBindingService
export default {
setDeviceBinding: async (ipcInvock) => {
await setDeviceBinding(ipcInvock.data)
await getSystemConfig()
}
}

View File

@ -0,0 +1,28 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: init.service.js -
// | @创建时间: 2024-11-26 14:26
// | @更新时间: 2024-11-26 14:26
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
// InitService
import {setServerHost} from "../../db/sql/systemConfig.js";
import {getSystemConfig} from "../../task_02_connectDB.js";
import {connectServer} from "../../searchServer/connectServer.js";
export default {
setServerHost: async (ipcInvock) => {
await setServerHost(ipcInvock)
await getSystemConfig()
},
checkServerHost: async (ipcInvock) => {
const serverHost = await connectServer(ipcInvock.data.host)
return serverHost
}
}

View File

@ -0,0 +1,63 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: connectServer.js -
// | @创建时间: 2024-11-12 09:36
// | @更新时间: 2024-11-12 09:36
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import * as dgram from "node:dgram";
import {sleep} from "../../tools/sleep.js";
export function connectServer(host) {
return new Promise(async (resolve, reject) => {
let status = false;
const udpClient = dgram.createSocket('udp4');
udpClient.on('error', (err) => {
const errMsg = `在尝试连接服务器的UDP客户端出现故障 ${err}`
reject(errMsg);
udpClient?.close();
});
udpClient.on('message', (msg, rinfo) => {
if(msg.indexOf('serverAddress') === 0){
status = true;
resolve(rinfo);
udpClient?.close();
}
});
udpClient.on('close', () => {
console.log(`尝试连接服务器的UDP客户端已关闭`)
})
console.log(`尝试连接服务器的UDP客户端已建立`);
const testCount = 8
for (let i = 0; i < testCount; i++) {
if(status){
break;
}
udpClient.send('checkAddress', 38600, host, (err) => {
if(err){
reject(`UDP消息发送失败${err}`)
udpClient?.close();
}else{
}
const address = udpClient.address();
});
if(i + 1 < testCount) {
await sleep(1000);
}else{
await sleep(1000);
reject('尝试连接服务器超时!')
udpClient?.close();
}
}
udpClient.on('listening', async () => {
});
})
}

View File

@ -0,0 +1,42 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-II-pad
// | @文件描述: testServer.js -
// | @创建时间: 2024-10-30 10:15
// | @更新时间: 2024-10-30 10:15
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import * as dgram from "node:dgram";
const udpServer = dgram.createSocket('udp4');
udpServer.on('error', (err) => {
console.log(`服务器出错: ${err}`);
udpServer.close();
});
udpServer.on('message', (msg, rinfo) => {
console.log(`服务器收到消息: ${msg} 来自 ${rinfo.address}:${rinfo.port}`);
if('checkAddress' == msg){
let i = 0;
const sendAddressInterval = setInterval(() => {
i++;
udpServer.send('serverAddress', rinfo.port, rinfo.address
)
if(i == 4){
clearInterval(sendAddressInterval);
}
}, 500)
}
});
udpServer.on('listening', () => {
const address = udpServer.address();
console.log(`服务器正在监听 ${address.address}:${address.port}`);
});
udpServer.bind(38600); // 绑定端口9999

35
src/electron/task/task.js Normal file
View File

@ -0,0 +1,35 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: task.js -
// | @创建时间: 2024-11-12 10:00
// | @更新时间: 2024-11-12 10:00
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import {initializationRender} from "./task_00_initializationRender.js";
import {createIpcChannel} from "./task_01_createIpcChannel.js";
import {connectDB} from "./task_02_connectDB.js";
import {checkServerHost} from "./task_03_checkServerHost.js";
import {getDeviceBinding} from "./task_04_getDeviceBinding.js";
export async function runTask(win){
// task0 设置渲染线程
initializationRender(win)
// task1 创建通信通道
createIpcChannel(win)
// 连接数据库,初始化配置信息
await connectDB()
// 检测服务器地址,++++++++同时等待渲染器渲染成功,所有消息在本任务之后发送
await checkServerHost(win).then(res => {})
// 获取谁被绑定信息
getDeviceBinding(win).then(res => {})
win.webContents.setZoomLevel(0)
console.log(win.webContents.getZoomLevel())
}

View File

@ -0,0 +1,48 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: task_00_initializationRender.js - 必要的渲染器设置
// | @创建时间: 2024-11-12 10:02
// | @更新时间: 2024-11-12 10:02
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import * as machineId from 'node-machine-id';
export function initializationRender(win){
if(global.isDev){
/**
* 开发模式
* */
// 打开开发者工具
win.webContents.openDevTools();
}else{
/**
* 生产模式
* */
// 全屏
win.setFullScreen(true);
}
// 测试向渲染器进程发送的活动推送消息。
win.webContents.on('did-finish-load', () => {
})
win.on('ready-to-show', () => {
win.show()
global.windowLoaded = true;
global.windowShowTime = new Date().getTime();
console.log('启动耗时: ', windowShowTime - startTime, 'ms');
})
// 获取设备ID
// console.log(machineId.default)
machineId.default.machineId().then(
res => {
console.log('设备ID', res)
}
)
}

View File

@ -0,0 +1,57 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: task_01_createIpcChannel.js -
// | @创建时间: 2024-11-12 10:08
// | @更新时间: 2024-11-12 10:08
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import {ipcMain} from "electron";
import {handleIpcChannel_Started} from "./methods/handleIpcChannel.js";
import router from "./router/index.js";
export function createIpcChannel(win) {
ipcMain.on(RenderChanleName, async (event, msg) => {
console.log('渲染器消息', msg)
switch (msg.type) {
case 'Started':
return handleIpcChannel_Started(msg)
break;
default:
console.log('IPC UNKNOW', msg)
}
return;
})
ipcMain.handle(RenderChanleName, async (event, msg) => {
if (msg.path) {
try {
// 正常逻辑
const data = await router(msg)
return {
code: 200,
message: 'success',
data
}
} catch (e) {
// 后台内部错误
console.log(e)
return {
code: 500,
message: `ipc invoke bad request: ${msg.path}`,
data: e
}
}
} else {
// 路径错误
return {
code: 404,
message: `ipc invoke not found path: ${msg.path}`,
data: {}
}
}
})
}

View File

@ -0,0 +1,77 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: task_02_connectDB.js -
// | @创建时间: 2024-11-12 10:25
// | @更新时间: 2024-11-12 10:25
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import sqlite3 from 'better-sqlite3';
import path from 'node:path';
// import {systemConfig} from "./db/modules/systemConfig.js";
// 连接数据库
export async function connectDB() {
console.log('DB_FILE', path.join(global.RESOURCE_DIR, 'database.sqlite'))
const db = new sqlite3(path.join(global.RESOURCE_DIR, 'database.sqlite'));
db.pragma('journal_mode = WAL');
global.DB = db;
await initTable()
await getSystemConfig()
return;
}
async function initTable() {
const SQL = `CREATE TABLE IF NOT EXISTS "SystemConfigs" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"key" VARCHAR(255) NOT NULL,
"value" VARCHAR(255) NOT NULL,
UNIQUE ("key" ASC)
);`
await global.DB.prepare(SQL).run()
}
// 加载模型
function mountedModules(sequelize) {
return new Promise((resolve, reject) => {
// 逐个加载模型
const SystemConfig = sequelize.define(
'SystemConfig',
systemConfig
)
sequelize.sync().then(() => {
resolve({
modules: {
SystemConfig
},
sequelize
})
});
})
}
// 获取系统参数
export async function getSystemConfig() {
const selectStmt = global.DB.prepare("SELECT * FROM SystemConfigs;");
const data = await selectStmt.all();
console.log('TEST', data)
const systemConfigObject = {}
data.forEach(item => {
let content = item.value
if (['DeviceBinding'].includes(item.key)) {
try {
content = JSON.parse(content)
} catch (err) {
content = undefined
}
}
systemConfigObject[item.key] = content
})
console.log('系统配置信息', systemConfigObject)
global.SystemConfig = systemConfigObject
return
}

View File

@ -0,0 +1,39 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: task_02_checkServerHost.js -
// | @创建时间: 2024-11-12 10:24
// | @更新时间: 2024-11-12 10:24
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import {connectServer} from "./searchServer/connectServer.js";
import {toGetServerHost} from "./methods/toRenderMessage.js";
import {sleep} from "../tools/sleep.js";
export async function checkServerHost(win) {
let serverHost = global.SystemConfig.ServerHost || "10.10.10.10"
let message;
try{
message = {
status: 0,
serverHost: await connectServer(serverHost),
}
}catch (e) {
message = {
status: -1,
serverHost: e
}
}
for(let i = 0; i< 20; i++){
if(global.windowLoaded){
toGetServerHost(win, message);
break
}
await sleep(1000);
}
}

View File

@ -0,0 +1,29 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: task_04_getDeviceBinding.js -
// | @创建时间: 2024-11-18 10:52
// | @更新时间: 2024-11-18 10:52
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import {toGetDeviceBindingInfo} from "./methods/toRenderMessage.js";
export async function getDeviceBinding(win) {
// 读取设备绑定信息
if(global.SystemConfig.DeviceBinding === undefined){
toGetDeviceBindingInfo(win, {
status: -1,
deviceBinding: undefined
})
}else{
toGetDeviceBindingInfo(win, {
status: 0,
deviceBinding: global.SystemConfig.DeviceBinding
})
}
}

View File

@ -0,0 +1,20 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-II-pad
// | @文件描述: sleep.js -
// | @创建时间: 2024-10-31 11:23
// | @更新时间: 2024-10-31 11:23
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
export function sleep(time = 300) {
return new Promise((res) => {
setTimeout(() => {
res()
}, time)
})
}

8
src/render/.env Normal file
View File

@ -0,0 +1,8 @@
VITE_HOME_REDIRECT = '/initialization' # 默认路由
VITE_TITLE = 'JS-PAD' # 项目名称
VITE_SYSTEM_NAME = '延安嘉盛生产管理系统'
VITE_BASE_URL = '/api' # 请求默认前缀
VITE_HTTP_TIMEOUT = 30000
VITE_HTTP_PROXY = 'http://10.10.10.200:8080'
VITE_ELECTRON_IPC_MESSAGE_NAME = 'Electron.IpcRenderer.Message'
VITE_ELECTRON_MAIN_MESSAGE_NAME = 'Electron.Main.Message'

62
src/render/App.vue Normal file
View File

@ -0,0 +1,62 @@
<script setup>
import {useIpcStore} from "@/stores/ipc.js";
import {useSystemStore} from "./stores/system.js";
import {onBeforeMount, onMounted, onBeforeUnmount, reactive, ref} from "vue";
import { ConfigProvider } from 'ant-design-vue';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
import { useRouter } from 'vue-router';
const IpcStore = useIpcStore();
const systemStore = useSystemStore();
onBeforeMount(() => {
IpcStore.initRender();
})
const router = useRouter();
window.goBack = () => {
router.back();
};
const locale = ref(zhCN);
const antdTheme = reactive({
token: {
borderRadius: 4,
// fontSize: '3rem'
},
});
const handleKeyDown = (event) => {
//
if (event.ctrlKey && event.key === 'q') {
router.back();
event.preventDefault();
console.log('返回!');
// API
}
if (event.ctrlKey && event.key === 'a') {
router.forward();
event.preventDefault();
console.log('前进!');
// API
}
};
onMounted(() => {
//
window.addEventListener('keydown', handleKeyDown);
});
onBeforeUnmount(() => {
//
window.removeEventListener('keydown', handleKeyDown);
});
</script>
<template>
<ConfigProvider :locale="locale" :theme="antdTheme" :component-size="systemStore.state.componentSize">
<RouterView />
</ConfigProvider>
</template>
<style>
</style>

71
src/render/api/device.js Normal file
View File

@ -0,0 +1,71 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: device.js -
// | @创建时间: 2024-11-13 17:01
// | @更新时间: 2024-11-13 17:01
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import request from "@/api/request.js";
export default {
// 获取公司名称集合
// GET http://10.10.10.200:8080/jiaSheng/factory/getCompanyNameSet
// 接口ID225762766
// 接口地址https://app.apifox.com/link/project/2515915/apis/api-225762766
// * 获取公司名称集合
getCompanyNameSet: params => request({
url: `/jiaSheng/factory/getCompanyNameSet`,
method: 'get',
params
}),
// 分页-工厂
// POST http://10.10.10.200:8080/jiaSheng/factory/pageFactory
// 接口ID225847024
// 接口地址https://app.apifox.com/link/project/2515915/apis/api-225847024
// * 获取工厂信息
getFactory: data => request({
url: `/jiaSheng/factory/pageFactory`,
method: 'post',
data
}),
// 分页-车间
// POST http://10.10.10.200:8080/jiaSheng/workshop/pageWorkshop
// 接口ID226776125
// 接口地址https://app.apifox.com/link/project/2515915/apis/api-226776125
// * 获取车间信息
getWorkshop: data => request({
url: `/jiaSheng/workshop/pageWorkshop`,
method: 'post',
data
}),
// 分页-生产线
// POST http://10.10.10.200:8080/jiaSheng/workline/pageWorkline
// 接口ID227277470
// 接口地址https://app.apifox.com/link/project/2515915/apis/api-227277470
// * 获取生产线信息
getWorkline: data => request({
url: `/jiaSheng/workline/pageWorkline`,
method: 'post',
data
}),
// 分页-工控机
// POST http://10.10.10.200:8080/jiaSheng/ipc/pageIpc
// 接口ID227408517
// 接口地址https://app.apifox.com/link/project/2515915/apis/api-227408517
// * 获取工控机信息
getIpc: data => request({
url: `/jiaSheng/ipc/pageIpc`,
method: 'post',
data
}),
}

View File

@ -0,0 +1,21 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: device_ipc.js -
// | @创建时间: 2024-11-26 10:49
// | @更新时间: 2024-11-26 10:49
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import ipcInvoke from "@/api/ipcRequest.js";
export default {
// 设置设备绑定信息
setDeviceBinding: data =>
ipcInvoke('/device/setDeviceBinding', data),
}

24
src/render/api/index.js Normal file
View File

@ -0,0 +1,24 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: index.js -
// | @创建时间: 2024-11-13 17:01
// | @更新时间: 2024-11-13 17:01
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import device from '@/api/device.js'
import device_ipc from "@/api/device_ipc.js";
import init_ipc from "@/api/init_ipc.js";
import process from "@/api/process.js";
export default {
device,
deviceIpc: device_ipc,
initIpc: init_ipc,
process
}

View File

@ -0,0 +1,23 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: init_ipc.js -
// | @创建时间: 2024-11-26 14:37
// | @更新时间: 2024-11-26 14:37
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import ipcInvoke from "@/api/ipcRequest.js";
export default {
// 设置服务器地址
setServerHost: data =>
ipcInvoke('/init/setServerHost', data),
// 发送新的服务器地址测试服务器状态
sendNewServerHost: data =>
ipcInvoke('/init/sendNewServerHost', data),
}

View File

@ -0,0 +1,32 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: ipcRequest.js -
// | @创建时间: 2024-11-26 10:46
// | @更新时间: 2024-11-26 10:46
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
const IPC_NAME = import.meta.env.VITE_ELECTRON_IPC_MESSAGE_NAME
export default async function ipcInvoke(path, data){
console.log(`%c<<=== IPC_INVOKE ${path}`, consoleColor.ipcInvoke.IN, data || '')
const response = await window?.ipcRenderer?.invoke(IPC_NAME, {
path,
data
})
console.log(`%c===>> IPC_INVOKE ${path}`, consoleColor.ipcInvoke.TO, data)
if(response.code !== 200){
const e = new Error()
e.message = response.message
e.code = response.code
e.errorData = response.data
throw e
}else{
return response
}
}

24
src/render/api/process.js Normal file
View File

@ -0,0 +1,24 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: process.js -
// | @创建时间: 2024-12-09 15:32
// | @更新时间: 2024-12-09 15:32
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import request from "@/api/request.js";
export default {
// 获取可用炉批号
// GET http://10.10.10.200:8080/jiaSheng/furnaceLotNumber/getList4Pad/{youtubeCode}
// 接口ID237583910
// 接口地址https://app.apifox.com/link/project/2515915/apis/api-237583910
getFurnaceLotNumber: youtubeCode => request({
url: `/jiaSheng/furnaceLotNumber/getList4Pad/${youtubeCode}`,
method: 'get',
}),
};

29
src/render/api/request.js Normal file
View File

@ -0,0 +1,29 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: request.js -
// | @创建时间: 2024-11-13 17:00
// | @更新时间: 2024-11-13 17:00
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import axios from "axios";
/* axios默认配置*/
const instance = axios.create({
timeout: 10000,
baseURL: "/api", // 服务器请求地址
});
instance.interceptors.response.use(async response => {
return response.data;
}, err => {
return Promise.reject(err.response);
})
export default instance;

View File

@ -0,0 +1,51 @@
html, body, #app{
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
user-select: none;
}
#app{
overflow: auto;
}
:root{
--framework-left-width: 100px;
--framework-header-height: 100px;
--framework-padding: 0px;
--framework-background: #2684fe;
--framework-border-radius: 15px;
}
/*滚动条+++++++++++++++++++++++++++++++++++++*/
/* 全局滚动条样式 */
/* 为所有滚动条设置样式 */
::-webkit-scrollbar {
position: absolute;
border-radius: 10px;
width: 4px; /* 设置滚动条的宽度 */
height: 4px; /* 设置滚动条的高度 */
}
/* 滚动条轨道 */
::-webkit-scrollbar-track {
position: absolute;
background: #33333311; /* 设置滚动条轨道的颜色 */
border-radius: 10px;
}
/* 滚动条的滑块 */
::-webkit-scrollbar-thumb {
position: absolute;
background: #888; /* 设置滚动条滑块的颜色 */
border-radius: 10px;
}
/* 当鼠标悬停在滑块上 */
::-webkit-scrollbar-thumb:hover {
position: absolute;
background: #555; /* 设置滚动条滑块在悬停状态下的颜色 */
}
/*滚动条---------------------------------------*/

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731394614164" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5376" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 64c-192 0-339.2 153.6-345.6 345.6 0 140.8 192 403.2 288 524.8 25.6 32 76.8 32 102.4 6.4l6.4-6.4c96-128 288-384 288-524.8C851.2 217.6 704 64 512 64z m0 569.6c-121.6 0-224-102.4-224-224s102.4-224 224-224 224 102.4 224 224-102.4 224-224 224zM544 288h-64v192h192v-64H544V288z" fill="#ffffff" p-id="5377" data-spm-anchor-id="a313x.search_index.0.i3.24bb3a81lAvIyh" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 728 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731395421526" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11301" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512.2 876.8c-27.5 0-301.1-247.9-301.1-428.8s119.7-300.4 301.1-300.4 301 119.4 301 300.3-273.7 428.9-301 428.9z m0-647.3c-120.9 0-218.9 97.8-218.9 218.4 0 120.6 98 218.5 218.9 218.5s219-97.8 219-218.5c-0.1-120.6-98.1-218.4-219-218.4z m109.4 273.1H484.8c-15.1 0-27.4-12.2-27.4-27.3V338.7c0-15.1 12.2-27.3 27.4-27.3 15.1 0 27.4 12.2 27.4 27.3V448h109.5c15.1 0 27.4 12.2 27.4 27.3s-12.4 27.3-27.5 27.3z m0 0" p-id="11302" data-spm-anchor-id="a313x.search_index.0.i10.24bb3a81lAvIyh" class="selected" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 859 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731395525141" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11555" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M511.795 60.374c-211.41 0-382.81 169.967-382.81 379.536 0 107.24 44.922 204.042 116.961 273.114l220.109 212.843c27.833 40.01 70.81 50.039 95.063 7.265l240.88-245.792c18.113-20.978 34.076-43.797 47.174-68.458 29.164-54.336 45.23-114.812 45.23-178.972 0.101-209.569-171.196-379.536-382.607-379.536z m0.921 651.013c-152.776 0-276.696-122.999-276.696-274.752s123.92-274.854 276.696-274.854S789.515 284.78 789.515 436.635s-123.92 274.752-276.799 274.752zM633.464 417.91h-82.17c-10.028 0-16.782-6.755-16.782-16.885V264.11c0-20.261-16.782-37.146-38.578-33.769-16.781 3.377-28.447 18.624-28.447 35.508v185.93c0 18.625 15.042 33.77 33.564 35.509H635.1c20.159 0 36.838-18.624 33.564-40.522-3.377-16.987-18.42-28.857-35.201-28.857z" fill="#ffffff" p-id="11556"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731394614164" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5376" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 64c-192 0-339.2 153.6-345.6 345.6 0 140.8 192 403.2 288 524.8 25.6 32 76.8 32 102.4 6.4l6.4-6.4c96-128 288-384 288-524.8C851.2 217.6 704 64 512 64z m0 569.6c-121.6 0-224-102.4-224-224s102.4-224 224-224 224 102.4 224 224-102.4 224-224 224zM544 288h-64v192h192v-64H544V288z" fill="#2684fe" p-id="5377" data-spm-anchor-id="a313x.search_index.0.i3.24bb3a81lAvIyh" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 728 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731395421526" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11301" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512.2 876.8c-27.5 0-301.1-247.9-301.1-428.8s119.7-300.4 301.1-300.4 301 119.4 301 300.3-273.7 428.9-301 428.9z m0-647.3c-120.9 0-218.9 97.8-218.9 218.4 0 120.6 98 218.5 218.9 218.5s219-97.8 219-218.5c-0.1-120.6-98.1-218.4-219-218.4z m109.4 273.1H484.8c-15.1 0-27.4-12.2-27.4-27.3V338.7c0-15.1 12.2-27.3 27.4-27.3 15.1 0 27.4 12.2 27.4 27.3V448h109.5c15.1 0 27.4 12.2 27.4 27.3s-12.4 27.3-27.5 27.3z m0 0" p-id="11302" data-spm-anchor-id="a313x.search_index.0.i10.24bb3a81lAvIyh" class="selected" fill="#2684fe"></path></svg>

After

Width:  |  Height:  |  Size: 859 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731395525141" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11555" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M511.795 60.374c-211.41 0-382.81 169.967-382.81 379.536 0 107.24 44.922 204.042 116.961 273.114l220.109 212.843c27.833 40.01 70.81 50.039 95.063 7.265l240.88-245.792c18.113-20.978 34.076-43.797 47.174-68.458 29.164-54.336 45.23-114.812 45.23-178.972 0.101-209.569-171.196-379.536-382.607-379.536z m0.921 651.013c-152.776 0-276.696-122.999-276.696-274.752s123.92-274.854 276.696-274.854S789.515 284.78 789.515 436.635s-123.92 274.752-276.799 274.752zM633.464 417.91h-82.17c-10.028 0-16.782-6.755-16.782-16.885V264.11c0-20.261-16.782-37.146-38.578-33.769-16.781 3.377-28.447 18.624-28.447 35.508v185.93c0 18.625 15.042 33.77 33.564 35.509H635.1c20.159 0 36.838-18.624 33.564-40.522-3.377-16.987-18.42-28.857-35.201-28.857z" fill="#2684fe" p-id="11556"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1732871886442" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5793" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 85.333333C277.333333 85.333333 85.333333 277.333333 85.333333 512s192 426.666667 426.666667 426.666667 426.666667-192 426.666667-426.666667S746.666667 85.333333 512 85.333333z m204.8 586.666667c12.8 10.666667 10.666667 32 0 44.8s-32 12.8-44.8 0L512 556.8l-160 160c-10.666667 12.8-32 12.8-44.8 0-12.8-10.666667-12.8-32 0-44.8l160-160-160-160c-12.8-10.666667-12.8-32 0-44.8 10.666667-12.8 32-12.8 44.8 0l160 160 160-162.133333c12.8-10.666667 34.133333-10.666667 44.8 2.133333 12.8 10.666667 12.8 32 0 44.8L556.8 512l160 160z" fill="#F25858" p-id="5794"></path></svg>

After

Width:  |  Height:  |  Size: 902 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733205540918" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12628" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 0a512 512 0 1 0 0 1024A512 512 0 0 0 512 0z m201.362286 548.571429H398.921143l71.972571 71.972571a36.571429 36.571429 0 0 1-51.712 51.712L284.818286 537.819429a36.571429 36.571429 0 0 1 0-51.712l134.436571-134.436572a36.571429 36.571429 0 0 1 51.712 51.712l-72.045714 72.045714h314.441143a36.571429 36.571429 0 0 1 0 73.142858z" fill="#2684fe" p-id="12629"></path></svg>

After

Width:  |  Height:  |  Size: 709 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733205491907" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8132" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M755.492145 487.531651a268.165921 268.165921 0 1 1-268.165921 268.165922 268.165921 268.165921 0 0 1 268.165921-268.165922zM487.326224 0.000683a267.961162 267.961162 0 0 1 238.339373 391.29428 365.494657 365.494657 0 0 0-263.046949 582.880368c-39.51847 0-84.565431 0.819036-130.704438 0.819036h-25.321853c-151.794607-0.955542-306.455838-10.715716-306.455837-65.795863 0-146.880393 147.699428-271.305558 350.001233-310.824028a46.275514 46.275514 0 0 0 26.004382-45.115214 81.152782 81.152782 0 0 0-36.242328-61.427674 267.824657 267.824657 0 0 1 147.699428-491.421388z m267.756403 625.87974l-5.05072 3.412648-4.572949 4.095178a48.732621 48.732621 0 0 0-7.576079 59.311832l3.412648 5.05072 35.218533 45.456478H628.200355a48.732621 48.732621 0 0 0 0 96.850965H881.487129a48.732621 48.732621 0 0 0 36.378833-73.644955l-3.20789-4.641202L819.103914 637.619934l-4.026926-4.572949a48.732621 48.732621 0 0 0-59.584843-7.166562z" fill="#2684fe" p-id="8133"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733208642087" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10155" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M957.06 511.84c0 246.74-200.15 446.76-447 446.76S63 758.58 63 511.84 263.13 65.09 510 65.09s447.06 200.02 447.06 446.75z m-81.28 0c0-201.56-164.07-365.52-365.76-365.52s-365.75 164-365.75 365.52S308.33 877.37 510 877.37s365.78-163.96 365.78-365.53z" fill="#2684fe" p-id="10156" data-spm-anchor-id="a313x.search_index.0.i0.1f3d3a811GuZun" class="selected"></path><path d="M713.22 511.84c0 246.74-91 446.76-203.2 446.76s-203.19-200-203.19-446.76S397.8 65.09 510 65.09s203.22 200.02 203.22 446.75z m-81.28 0c0-237.41-86.52-365.52-121.92-365.52S388.1 274.43 388.1 511.84 474.62 877.37 510 877.37s121.94-128.11 121.94-365.53z" fill="#2684fe" p-id="10157" data-spm-anchor-id="a313x.search_index.0.i1.1f3d3a811GuZun" class="selected"></path><path d="M144.27 308.78h731.51v81.23H144.27zM144.27 593.07h731.51v81.23H144.27z" fill="#2684fe" p-id="10158" data-spm-anchor-id="a313x.search_index.0.i2.1f3d3a811GuZun" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733208667170" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11476" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M470.820571 36.571429a453.485714 453.485714 0 0 1 448.658286 394.971428l0.658286 5.668572a328.704 328.704 0 0 0-73.142857-43.373715 389.266286 389.266286 0 0 0-101.193143-175.762285 454.217143 454.217143 0 0 1-75.337143 48.128 642.889143 642.889143 0 0 1 27.830857 101.705142 324.681143 324.681143 0 0 0-60.379428 9.216 588.8 588.8 0 0 0-21.942858-87.405714 445.659429 445.659429 0 0 1-89.234285 20.150857l-18.285715 1.792v130.962286a331.922286 331.922286 0 0 0-64.585142 71.606857v-2.852571H296.557714a619.629714 619.629714 0 0 0 30.976 182.235428 446.610286 446.610286 0 0 1 61.622857-16.018285 343.332571 343.332571 0 0 0 2.304 64.731428 367.762286 367.762286 0 0 0-38.290285 10.057143 173.750857 173.750857 0 0 0 60.416 72.374857 331.995429 331.995429 0 0 0 85.430857 120.429714c-9.325714 0.548571-18.724571 0.841143-28.196572 0.841143a454.765714 454.765714 0 0 1 0-909.531428zM296.996571 775.936a387.218286 387.218286 0 0 0-39.826285 23.076571l-12.617143 8.740572h-1.536l-0.768-0.402286a385.060571 385.060571 0 0 0 125.988571 61.001143 311.332571 311.332571 0 0 1-71.277714-92.452571z m-63.744-264.923429H82.358857a390.070857 390.070857 0 0 0 113.371429 254.354286 457.435429 457.435429 0 0 1 75.410285-48.164571 654.226286 654.226286 0 0 1-37.705142-206.116572z m93.44-221.293714a608.146286 608.146286 0 0 0-28.306285 137.545143l-1.426286 20.114286h146.285714v-134.948572a450.633143 450.633143 0 0 1-116.553143-22.710857z m-133.12-72.374857a389.814857 389.814857 0 0 0-110.153142 212.589714l-2.230858 15.140572h150.857143a643.072 643.072 0 0 1 37.705143-181.138286 459.995429 459.995429 0 0 1-76.178286-46.628571z m250.441143-75.410286a172.763429 172.763429 0 0 0-90.88 89.088 385.389714 385.389714 0 0 0 72.411429 15.689143l18.505143 1.718857z m64.512 3.035429v102.4a387.364571 387.364571 0 0 0 82.176-16.676572 183.369143 183.369143 0 0 0-82.139428-85.76z m68.608-29.952a311.332571 311.332571 0 0 1 71.387429 92.416 386.998857 386.998857 0 0 0 39.826286-23.04l12.617142-8.740572-1.536-0.402285a385.097143 385.097143 0 0 0-122.185142-60.269715z m-210.066285-0.365714a385.024 385.024 0 0 0-122.587429 60.598857 390.656 390.656 0 0 0 51.2 31.853714 310.162286 310.162286 0 0 1 71.314286-92.452571z" fill="#8D8D8D" p-id="11477"></path><path d="M716.8 408.649143a289.426286 289.426286 0 1 1-285.257143 289.389714 287.305143 287.305143 0 0 1 285.257143-289.389714z m0 413.257143a41.325714 41.325714 0 1 0 40.777143 41.325714 38.729143 38.729143 0 0 0-40.777143-41.179429z m0-330.715429a42.203429 42.203429 0 0 0-40.813714 45.604572l16.274285 223.085714a24.795429 24.795429 0 0 0 48.896 0l16.310858-227.401143a41.142857 41.142857 0 0 0-40.667429-41.216z" fill="#FFA200" p-id="11478"></path></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731394468311" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3211" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M697.6 499.2c134.4 0 236.8 108.8 236.8 236.8s-108.8 236.8-236.8 236.8-236.8-108.8-236.8-236.8 108.8-236.8 236.8-236.8z m64-422.4c32 0 57.6 25.6 57.6 57.6v268.8c0 19.2-12.8 32-32 32s-32-12.8-32-32V140.8H160v716.8h211.2c19.2 0 32 12.8 32 32s-12.8 32-32 32H160c-32 0-57.6-25.6-57.6-57.6V140.8c0-32 25.6-57.6 57.6-57.6h601.6z m-64 480c-96 0-179.2 83.2-179.2 179.2s83.2 179.2 179.2 179.2 179.2-83.2 179.2-179.2-76.8-179.2-179.2-179.2z m0 64c19.2 0 32 12.8 32 32v76.8l57.6 57.6c12.8 12.8 12.8 32 0 44.8-12.8 12.8-32 12.8-44.8 0l-57.6-64c-12.8-12.8-19.2-25.6-19.2-44.8v-76.8c6.4-12.8 19.2-25.6 32-25.6zM371.2 499.2c19.2 0 32 12.8 32 32s-12.8 32-32 32H249.6c-19.2 0-32-12.8-32-32s12.8-32 32-32h121.6z m121.6-121.6c19.2 0 32 12.8 32 32s-19.2 25.6-32 25.6H249.6c-19.2 0-32-12.8-32-32s12.8-32 32-32h243.2zM608 256c19.2 0 32 12.8 32 32s-12.8 32-32 32H249.6c-12.8 0-25.6-12.8-25.6-32s12.8-32 25.6-32h358.4z" fill="#ffffff" p-id="3212" data-spm-anchor-id="a313x.search_index.0.i0.24bb3a81lAvIyh" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731394468311" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3211" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M697.6 499.2c134.4 0 236.8 108.8 236.8 236.8s-108.8 236.8-236.8 236.8-236.8-108.8-236.8-236.8 108.8-236.8 236.8-236.8z m64-422.4c32 0 57.6 25.6 57.6 57.6v268.8c0 19.2-12.8 32-32 32s-32-12.8-32-32V140.8H160v716.8h211.2c19.2 0 32 12.8 32 32s-12.8 32-32 32H160c-32 0-57.6-25.6-57.6-57.6V140.8c0-32 25.6-57.6 57.6-57.6h601.6z m-64 480c-96 0-179.2 83.2-179.2 179.2s83.2 179.2 179.2 179.2 179.2-83.2 179.2-179.2-76.8-179.2-179.2-179.2z m0 64c19.2 0 32 12.8 32 32v76.8l57.6 57.6c12.8 12.8 12.8 32 0 44.8-12.8 12.8-32 12.8-44.8 0l-57.6-64c-12.8-12.8-19.2-25.6-19.2-44.8v-76.8c6.4-12.8 19.2-25.6 32-25.6zM371.2 499.2c19.2 0 32 12.8 32 32s-12.8 32-32 32H249.6c-19.2 0-32-12.8-32-32s12.8-32 32-32h121.6z m121.6-121.6c19.2 0 32 12.8 32 32s-19.2 25.6-32 25.6H249.6c-19.2 0-32-12.8-32-32s12.8-32 32-32h243.2zM608 256c19.2 0 32 12.8 32 32s-12.8 32-32 32H249.6c-12.8 0-25.6-12.8-25.6-32s12.8-32 25.6-32h358.4z" fill="#2684fe" p-id="3212" data-spm-anchor-id="a313x.search_index.0.i0.24bb3a81lAvIyh" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1732874297557" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8631" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M0 0h1024v1024H0V0z" fill="#202425" opacity=".01" p-id="8632"></path><path d="M989.866667 512c0 263.918933-213.947733 477.866667-477.866667 477.866667S34.133333 775.918933 34.133333 512 248.081067 34.133333 512 34.133333s477.866667 213.947733 477.866667 477.866667z" fill="#f84031" p-id="8633" data-spm-anchor-id="a313x.search_index.0.i8.68633a81nGYnmH" class="selected"></path><path d="M221.866667 512A51.2 51.2 0 0 1 273.066667 460.8h477.866666a51.2 51.2 0 0 1 0 102.4H273.066667A51.2 51.2 0 0 1 221.866667 512z" fill="#FFFFFF" p-id="8634"></path></svg>

After

Width:  |  Height:  |  Size: 888 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731394747841" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6520" data-spm-anchor-id="a313x.search_index.0.i6.24bb3a81lAvIyh" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M449.194667 82.346667a128 128 0 0 1 125.610666 0l284.16 160a128 128 0 0 1 65.194667 111.530666v316.245334a128 128 0 0 1-65.194667 111.530666l-284.16 160a128 128 0 0 1-125.610666 0l-284.16-160a128 128 0 0 1-65.194667-111.530666V353.877333A128 128 0 0 1 165.034667 242.346667z m83.754666 74.410666a42.666667 42.666667 0 0 0-41.898666 0L206.933333 316.714667a42.666667 42.666667 0 0 0-21.76 37.162666v316.245334a42.666667 42.666667 0 0 0 21.76 37.162666l284.16 160a42.666667 42.666667 0 0 0 41.898667 0l284.16-160a42.666667 42.666667 0 0 0 21.76-37.162666V353.877333a42.666667 42.666667 0 0 0-21.76-37.162666zM512 341.333333a170.666667 170.666667 0 1 1 0 341.333334 170.666667 170.666667 0 0 1 0-341.333334z m0 85.333334a85.333333 85.333333 0 1 0 0 170.666666 85.333333 85.333333 0 0 0 0-170.666666z" fill="#ffffff" p-id="6521" data-spm-anchor-id="a313x.search_index.0.i7.24bb3a81lAvIyh" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731394747841" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6520" data-spm-anchor-id="a313x.search_index.0.i6.24bb3a81lAvIyh" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M449.194667 82.346667a128 128 0 0 1 125.610666 0l284.16 160a128 128 0 0 1 65.194667 111.530666v316.245334a128 128 0 0 1-65.194667 111.530666l-284.16 160a128 128 0 0 1-125.610666 0l-284.16-160a128 128 0 0 1-65.194667-111.530666V353.877333A128 128 0 0 1 165.034667 242.346667z m83.754666 74.410666a42.666667 42.666667 0 0 0-41.898666 0L206.933333 316.714667a42.666667 42.666667 0 0 0-21.76 37.162666v316.245334a42.666667 42.666667 0 0 0 21.76 37.162666l284.16 160a42.666667 42.666667 0 0 0 41.898667 0l284.16-160a42.666667 42.666667 0 0 0 21.76-37.162666V353.877333a42.666667 42.666667 0 0 0-21.76-37.162666zM512 341.333333a170.666667 170.666667 0 1 1 0 341.333334 170.666667 170.666667 0 0 1 0-341.333334z m0 85.333334a85.333333 85.333333 0 1 0 0 170.666666 85.333333 85.333333 0 0 0 0-170.666666z" fill="#2684fe" p-id="6521" data-spm-anchor-id="a313x.search_index.0.i7.24bb3a81lAvIyh" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1732871835667" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13231" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 1024c-281.6 0-512-230.4-512-512s230.4-512 512-512 512 230.4 512 512-230.4 512-512 512z m-64-364.836571l275.163429-243.2a30.939429 30.939429 0 0 0 0-44.763429 30.939429 30.939429 0 0 0-44.763429 0L435.2 588.8 345.6 492.763429a30.939429 30.939429 0 0 0-44.763429 0 30.939429 30.939429 0 0 0 0 44.836571L409.6 659.163429c6.436571 6.436571 12.8 12.8 25.6 12.8 0-6.363429 6.363429-12.8 12.8-12.8z" fill="#12B56A" p-id="13232"></path></svg>

After

Width:  |  Height:  |  Size: 773 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1732863636910" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12182" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M147.29104 871.313145A511.974204 511.974204 0 1 1 511.974204 1023.999606a510.280751 510.280751 0 0 1-364.683164-152.686461zM177.22184 803.456872a384.807689 384.807689 0 0 1 232.948263-181.829608 204.789682 204.789682 0 1 1 203.214376 0 384.965219 384.965219 0 0 1 233.105794 181.829608 443.684722 443.684722 0 1 0-669.268433 0z" fill="#2684fe" p-id="12183" data-spm-anchor-id="a313x.search_index.0.i6.216a3a81MhI4rv" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1732863636910" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12182" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M147.29104 871.313145A511.974204 511.974204 0 1 1 511.974204 1023.999606a510.280751 510.280751 0 0 1-364.683164-152.686461zM177.22184 803.456872a384.807689 384.807689 0 0 1 232.948263-181.829608 204.789682 204.789682 0 1 1 203.214376 0 384.965219 384.965219 0 0 1 233.105794 181.829608 443.684722 443.684722 0 1 0-669.268433 0z" fill="#888888" p-id="12183" data-spm-anchor-id="a313x.search_index.0.i6.216a3a81MhI4rv" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 782 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731394055477" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1494" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M380.705 571.991c44.475 0 80.53 36.032 80.53 80.48v200.05c0 44.447-36.055 80.479-80.53 80.479H180.53c-44.475 0-80.53-36.032-80.53-80.48V652.47c0-44.447 36.055-80.479 80.53-80.479h200.175z m441.765 0c44.475 0 80.53 36.032 80.53 80.48v200.05C903 896.967 866.945 933 822.47 933H622.295c-44.475 0-80.53-36.032-80.53-80.48V652.47c0-44.447 36.055-80.479 80.53-80.479H822.47z m-441.765 59.785H180.53c-11.232 0-20.376 8.938-20.699 20.085l-0.009 0.61v200.05c0 11.225 8.943 20.363 20.098 20.685l0.61 0.01h200.175c11.232 0 20.376-8.938 20.699-20.085l0.009-0.61V652.47c0-11.225-8.943-20.363-20.098-20.686l-0.61-0.009z m441.765 0H622.295c-11.232 0-20.376 8.938-20.699 20.085l-0.009 0.61v200.05c0 11.225 8.943 20.363 20.098 20.685l0.61 0.01H822.47c11.232 0 20.376-8.938 20.699-20.085l0.009-0.61V652.47c0-11.225-8.943-20.363-20.098-20.686l-0.61-0.009zM722.383 142C822.135 142 903 222.815 903 322.504c0 99.69-80.865 180.505-180.617 180.505-99.753 0-180.618-80.815-180.618-180.505S622.63 142 722.383 142z m-341.678 0c44.475 0 80.53 36.032 80.53 80.48v200.05c0 44.447-36.055 80.479-80.53 80.479H180.53c-44.475 0-80.53-36.032-80.53-80.48V222.48c0-44.447 36.055-80.48 80.53-80.48h200.175z m341.678 59.785c-66.714 0-120.796 54.048-120.796 120.72 0 66.67 54.082 120.719 120.796 120.719 66.713 0 120.795-54.048 120.795-120.72 0-66.671-54.082-120.72-120.795-120.72z m-341.678 0H180.53c-11.232 0-20.376 8.937-20.699 20.084l-0.009 0.61v200.05c0 11.225 8.943 20.363 20.098 20.686l0.61 0.009h200.175c11.232 0 20.376-8.938 20.699-20.085l0.009-0.61V222.48c0-11.225-8.943-20.363-20.098-20.685l-0.61-0.01z" fill="#ffffff" p-id="1495" data-spm-anchor-id="a313x.search_index.0.i7.68e03a81hoUzJ0" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731394055477" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1494" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M380.705 571.991c44.475 0 80.53 36.032 80.53 80.48v200.05c0 44.447-36.055 80.479-80.53 80.479H180.53c-44.475 0-80.53-36.032-80.53-80.48V652.47c0-44.447 36.055-80.479 80.53-80.479h200.175z m441.765 0c44.475 0 80.53 36.032 80.53 80.48v200.05C903 896.967 866.945 933 822.47 933H622.295c-44.475 0-80.53-36.032-80.53-80.48V652.47c0-44.447 36.055-80.479 80.53-80.479H822.47z m-441.765 59.785H180.53c-11.232 0-20.376 8.938-20.699 20.085l-0.009 0.61v200.05c0 11.225 8.943 20.363 20.098 20.685l0.61 0.01h200.175c11.232 0 20.376-8.938 20.699-20.085l0.009-0.61V652.47c0-11.225-8.943-20.363-20.098-20.686l-0.61-0.009z m441.765 0H622.295c-11.232 0-20.376 8.938-20.699 20.085l-0.009 0.61v200.05c0 11.225 8.943 20.363 20.098 20.685l0.61 0.01H822.47c11.232 0 20.376-8.938 20.699-20.085l0.009-0.61V652.47c0-11.225-8.943-20.363-20.098-20.686l-0.61-0.009zM722.383 142C822.135 142 903 222.815 903 322.504c0 99.69-80.865 180.505-180.617 180.505-99.753 0-180.618-80.815-180.618-180.505S622.63 142 722.383 142z m-341.678 0c44.475 0 80.53 36.032 80.53 80.48v200.05c0 44.447-36.055 80.479-80.53 80.479H180.53c-44.475 0-80.53-36.032-80.53-80.48V222.48c0-44.447 36.055-80.48 80.53-80.48h200.175z m341.678 59.785c-66.714 0-120.796 54.048-120.796 120.72 0 66.67 54.082 120.719 120.796 120.719 66.713 0 120.795-54.048 120.795-120.72 0-66.671-54.082-120.72-120.795-120.72z m-341.678 0H180.53c-11.232 0-20.376 8.937-20.699 20.084l-0.009 0.61v200.05c0 11.225 8.943 20.363 20.098 20.686l0.61 0.009h200.175c11.232 0 20.376-8.938 20.699-20.085l0.009-0.61V222.48c0-11.225-8.943-20.363-20.098-20.685l-0.61-0.01z" fill="#2684fe" p-id="1495" data-spm-anchor-id="a313x.search_index.0.i7.68e03a81hoUzJ0" class="selected"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@ -0,0 +1,2 @@
@import './base.css';

View File

@ -0,0 +1,423 @@
<script setup>
defineOptions({
name: 'FrameWork',
});
import {ref, onMounted, onUnmounted} from "vue";
import {useRouter, useRoute} from "vue-router";
import workbench from '@/assets/icon/workbench.svg'
import workbenchActivate from '@/assets/icon/workbench_activate.svg'
import newspaperWorker from '@/assets/icon/newspaperWorker.svg'
import newspaperWorkerActivate from '@/assets/icon/newspaperWorker_activate.svg'
import clockin from '@/assets/icon/clockin_3.svg'
import clockinActivate from '@/assets/icon/clockin_activate_3.svg'
import setting from '@/assets/icon/setting.svg'
import settingActivate from '@/assets/icon/setting_activate.svg'
import userSvg from '@/assets/icon/user.svg'
import userUnchooseSvg from '@/assets/icon/user_unchoose.svg'
import networkSvg from '@/assets/icon/network.svg'
import networkErrorSvg from '@/assets/icon/network_error.svg'
import logoutSvg from '@/assets/icon/logoutuser.svg'
import {useSystemStore} from "@/stores/system.js";
import 'dayjs/locale/zh-cn'
import dayjs from "dayjs";
const router = useRouter()
const route = useRoute()
const systemStore = useSystemStore()
//
const moduleList = ref([
{
name: '工作台',
tag:'workbench',
default: workbench,
activate: workbenchActivate
},
{
name: '报工',
tag:'newspaperWorker',
default: newspaperWorker,
activate: newspaperWorkerActivate
},
{
name: '打卡',
tag:'clockin',
default: clockin,
activate: clockinActivate
},
{
name: '',
tag:'占位',
default: '',
activate: ''
},
{
name: '设置',
tag:'setting',
default: setting,
activate: settingActivate
},
])
//
const activateModule = ref('');
//
const date = ref('')
let timeInterval = null;
//
const handleChangeModule = (item) => {
if(item.tag == '占位'){
return
}
activateModule.value = item.tag;
router.push('/home/'+item.tag);
}
// 退
const logoutUser = () => {
router.push('/login');
}
//
const handleChooseUser = (user) => {
systemStore.setFocusUser(user)
}
//
const handleChooseProcessList = () => {
router.push('/productionBatch');
}
onMounted(() => {
timeInterval = setInterval(() => {
date.value = dayjs().locale('zh-cn').format('YYYY-MM-DD HH:mm:ss dddd')
}, 500)
// 访
const path = route.path; //
const params = route.params; //
const query = route.query; //
const name = route.name; //
const fullPath = route.fullPath; //
activateModule.value = path.split('/')[2];
// console.log(path.split('/')[1]);
// console.log(path)
// console.log(params)
// console.log(query)
// console.log(name)
// console.log(fullPath)
// console.log(systemStore.state)
})
onUnmounted(() => {
if (timeInterval) {
clearInterval(timeInterval)
}
})
</script>
<template>
<div class="framework">
<div class="left">
<header>
<div @click="handleChooseProcessList">
<div><img :src="userSvg" alt=""></div>
<div>{{systemStore.state.nowFocusUser?.nickName}}</div>
</div>
</header>
<main>
<div v-for="(item, index) in moduleList" class="module-container" @click="handleChangeModule(item)">
<div :class="item.tag == activateModule ? 'module-box module-box-activate' : 'module-box'">
<div class="module-icon">
<div>
<img :src="item.tag == activateModule ? item.activate : item.default" alt="">
</div>
</div>
<div class="module-title">{{item.name}}</div>
</div>
</div>
</main>
</div>
<div class="right">
<header>
<div class="onlineUserContainer">
<template v-for="user in systemStore.state.onlineUserList">
<div class="onlineUserBox" @click="handleChooseUser(user)" v-if="systemStore.state.nowFocusUser?.username != user?.username">
<div>
<div><img :src="userUnchooseSvg" alt=""></div>
<div>{{user?.nickName}}</div>
</div>
</div>
</template>
</div>
<div class="systemStatusConatiner">
<div>
<div class="time_networkBox">
<div class="timeBox">
<div>{{date.split(' ')[0]}}</div>
<div>{{date.split(' ')[1]}}</div>
<div>{{date.split(' ')[2]}}</div>
</div>
<div class="network">
<div v-if="systemStore.state.networkStatus" class="networkOnline"></div>
<div v-else class="networkOutline"></div>
</div>
</div>
<div class="logoutBox">
<div @click="logoutUser"><img :src="logoutSvg" alt=""></div>
</div>
</div>
</div>
</header>
<main><slot /></main>
</div>
</div>
</template>
<style scoped lang="scss">
.framework{
position: relative;
width: 100%;
height: 100%;
background: #fefefe;
display: flex;
overflow: hidden;
border-radius: 10px;
padding: var(--framework-padding);
& > div{
position: relative;
display: block;
}
& > div.left{
flex-shrink: 0;
width: var(--framework-left-width);
height: 100%;
display: flex;
flex-direction: column;
& > header{
position: relative;
flex-shrink: 0;
height: var(--framework-header-height);
width: 100%;
display: flex;
align-items: center;
justify-content: center;
& > div{
position: relative;
width: 80%;
text-align: center;
cursor: pointer;
& > div:first-child{
position: relative;
& > img{
width: 60%;
}
}
& > div:last-child{
line-height: 2em;
font-weight: bold;
color: #444;
}
}
&:after{
content: '';
position: absolute;
width: 5px;
height: 50px;
background: #cdcdcd;
border-radius: 10px;
right: -3px;
}
}
& > main{
position: relative;
flex: 1;
width: 100%;
overflow: auto;
border-radius: 0 var(--framework-border-radius) 0 0;
background: var(--framework-background);
display: flex;
flex-direction: column;
& > div.module-container{
position: relative;
width: 100%;
height: var(--framework-header-height);
display: flex;
align-items: center;
justify-content: center;
&:nth-last-child(2){
flex: 1;
}
& > div.module-box-activate{
& > div.module-icon > div{
background: #fefefe;
}
& > div.module-title{
//color: #000 !important;
}
}
& > div.module-box{
position: relative;
width: 80%;
height: 80%;
display: flex;
flex-direction: column;
cursor: pointer;
& > div.module-icon{
position: relative;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
& > div{
position: relative;
width: 45px;
height: 45px;
margin: 0 auto;
border-radius: var(--framework-border-radius);
display: flex;
align-items: center;
justify-content: center;
& > img{
width: 38px;
}
}
}
& > div.module-title{
position: relative;
flex-shrink: 0;
line-height: 2em;
text-align: center;
color: #fefefe;
}
}
}
}
}
& > div.right{
position: relative;
flex: 1;
height: 100%;
overflow: auto;
display: flex;
flex-direction: column;
& > header{
position: relative;
height: var(--framework-header-height);
overflow: hidden;
border-bottom: 2px solid #f0f0f0;
display: flex;
padding: 20px;
flex-shrink: 0;
& > div.onlineUserContainer{
position: relative;
height: 100%;
flex: 1;
//max-width: 600px;
display: flex;
overflow: auto;
& > div.onlineUserBox{
position: relative;
height: 100%;
aspect-ratio: 1/1;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
& > div{
position: relative;
text-align: center;
& > div:first-child{
position: relative;
width: 70%;
display: flex;
margin: 0 auto;
align-items: center;
justify-content: center;
text-align: center;
& > img{
width: 80%;
}
}
& > div:last-child{
line-height: 1.2em;
}
}
}
}
& > div.systemStatusConatiner{
position: relative;
flex-shrink: 0;
width: 160px;
& > div{
position: relative;
height: 100%;
display: flex;
flex-direction: row-reverse;
& > div.time_networkBox{
position: relative;
flex-shrink: 0;
height: 100%;
display: flex;
flex-direction: column;
margin-left: 20px;
&:after{
content: '';
position: absolute;
width: 5px;
height: 30px;
background: #cdcdcd;
border-radius: 10px;
left: -15px;
top: 15px;
}
& > div.timeBox{
position: relative;
display: inline-block;
text-align: right;
font-size: 0.8em;
flex-shrink: 0;
& > div{
display: block;
}
}
& > div.network{
position: relative;
display: flex;
align-items: center;
justify-content: center;
flex: 1;
& > div.networkOnline{
background: #1fd624;
width: 10px;
height: 10px;
border-radius: 100%;
}
& > div.networkOutline{
background: #777;
width: 10px;
height: 10px;
border-radius: 100%;
}
}
}
& > div.logoutBox{
position: relative;
flex: 1;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
img{
width: 40px;
}
}
}
}
}
& > main{
position: relative;
flex: 1;
width: 100%;
padding: 0 20px 20px 20px;
overflow: hidden;
}
}
}
</style>

View File

@ -0,0 +1,20 @@
<script setup>
</script>
<template>
<div class="hello">
Hello World!
</div>
</template>
<style scoped lang="scss">
.hello{
position: relative;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -0,0 +1,110 @@
<script setup>
defineOptions({
name: 'SimpleFrameWork',
});
import {useSystemStore} from "@/stores/system.js";
import {onMounted, onUnmounted, ref} from "vue";
import 'dayjs/locale/zh-cn'
import dayjs from "dayjs";
import {useRouter} from "vue-router";
import gobackSvg from '@/assets/icon/goback.svg'
const router = useRouter()
const systemStore = useSystemStore();
let timeInterval = null;
const date = ref('')
const goback = () => {
router.back()
}
onMounted(() => {
timeInterval = setInterval(() => {
date.value = dayjs().locale('zh-cn').format('YYYY-MM-DD HH:mm:ss dddd')
}, 500)
})
onUnmounted(() => {
if (timeInterval) {
clearInterval(timeInterval)
}
})
</script>
<template>
<div class="simpleFrameWork">
<header>
<div>{{ systemStore.state.systemName }}</div>
<div class="time">{{ date }}</div>
<div class="goback" @click="goback"><img :src="gobackSvg" alt=""></div>
</header>
<main>
<slot/>
</main>
</div>
</template>
<style scoped lang="scss">
.simpleFrameWork{
position: relative;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
& > header {
position: relative;
flex-shrink: 0;
background: #2684fe;
height: 10vh;
width: 100%;
display: flex;
color: #fefefe;
padding-right: 10px;
& > div:first-child {
position: relative;
flex: 1;
height: 100%;
display: flex;
align-items: center;
padding-left: 20px;
font-size: 1.2em;
font-weight: bold;
}
& > div.goback{
position: relative;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
& > img{
background: #fefefe;
padding: 1px;
border-radius: 100%;
width: 50px;
}
}
& > div.time {
position: relative;
flex-shrink: 0;
width: 300px;
display: flex;
align-items: center;
justify-content: right;
padding-right: 20px;
}
}
& > main {
position: relative;
flex: 1;
width: 100%;
display: flex;
box-sizing: border-box;
padding: 20px;
background: #f9f9f9;
overflow: hidden;
}
}
</style>

60
src/render/main.js Normal file
View File

@ -0,0 +1,60 @@
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import Antd, {message} from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
import naive from 'naive-ui'
// 通用字体
import 'vfonts/Lato.css'
// 等宽字体
import 'vfonts/FiraCode.css'
const baseColor = 'border-radius: 3px; padding: 2px 3px; font-family: consolas'
window.consoleColor = {
ipcMessage: {
IN: 'color: #ffffff; background: #ff7f50;' + baseColor,
TO: 'color: #000000; background: #ff7f50;' + baseColor,
},
ipcInvoke: {
IN: 'color: #ffffff; background: #ffa502;' + baseColor,
TO: 'color: #000000; background: #ffa502;' + baseColor,
},
router: {
IN: 'color: #ffffff; background: #2273b2;' + baseColor,
TO: "color: #eb3650; background: #2273b2;" + baseColor
},
wsRequest: {
IN: "color: #fefefe; background: #2ed573;" + baseColor,
TO: "color: #000000; background: #2ed573;" + baseColor
},
wsServer: {
IN: 'color: #fefefe; background: #7bed9f;' + baseColor,
TO: 'color: #000000; background: #7bed9f;' + baseColor,
},
}
import '@/assets/main.css'
import App from '@/App.vue'
import createRoutering from '@/router/index.js';
import authRouter from '@/router/authorization.js';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
const router = createRoutering();
app.use(router);
app.use(authRouter, {
router,
});
app.use(Antd);
app.use(naive);
message.config({
top: `200px`,
duration: 2,
maxCount: 3,
})
app.provide('message', message);
app.mount('#app');

View File

@ -0,0 +1,34 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M63.9202 127.84C99.2223 127.84 127.84 99.2223 127.84 63.9202C127.84 28.6181 99.2223 0 63.9202 0C28.6181 0 0 28.6181 0 63.9202C0 99.2223 28.6181 127.84 63.9202 127.84Z" fill="url(#paint0_linear_103_2)"/>
<g id="not-lightning" clip-path="url(#clip0_103_2)">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
from="0 64 64"
to="360 64 64"
dur="20s"
repeatCount="indefinite"/>
<path d="M51.3954 39.5028C52.3733 39.6812 53.3108 39.033 53.4892 38.055C53.6676 37.0771 53.0194 36.1396 52.0414 35.9612L51.3954 39.5028ZM28.9393 60.9358C29.4332 61.7985 30.5329 62.0976 31.3957 61.6037C32.2585 61.1098 32.5575 60.0101 32.0636 59.1473L28.9393 60.9358ZM37.6935 66.7457C37.025 66.01 35.8866 65.9554 35.1508 66.6239C34.415 67.2924 34.3605 68.4308 35.029 69.1666L37.6935 66.7457ZM96.9206 89.515C97.7416 88.9544 97.9526 87.8344 97.3919 87.0135C96.8313 86.1925 95.7113 85.9815 94.8904 86.5422L96.9206 89.515ZM52.0414 35.9612C46.4712 34.9451 41.2848 34.8966 36.9738 35.9376C32.6548 36.9806 29.0841 39.1576 27.0559 42.6762L30.1748 44.4741C31.5693 42.0549 34.1448 40.3243 37.8188 39.4371C41.5009 38.5479 46.1547 38.5468 51.3954 39.5028L52.0414 35.9612ZM27.0559 42.6762C24.043 47.9029 25.2781 54.5399 28.9393 60.9358L32.0636 59.1473C28.6579 53.1977 28.1088 48.0581 30.1748 44.4741L27.0559 42.6762ZM35.029 69.1666C39.6385 74.24 45.7158 79.1355 52.8478 83.2597L54.6499 80.1432C47.8081 76.1868 42.0298 71.5185 37.6935 66.7457L35.029 69.1666ZM52.8478 83.2597C61.344 88.1726 70.0465 91.2445 77.7351 92.3608C85.359 93.4677 92.2744 92.6881 96.9206 89.515L94.8904 86.5422C91.3255 88.9767 85.4902 89.849 78.2524 88.7982C71.0793 87.7567 62.809 84.8612 54.6499 80.1432L52.8478 83.2597ZM105.359 84.9077C105.359 81.4337 102.546 78.6127 99.071 78.6127V82.2127C100.553 82.2127 101.759 83.4166 101.759 84.9077H105.359ZM99.071 78.6127C95.5956 78.6127 92.7831 81.4337 92.7831 84.9077H96.3831C96.3831 83.4166 97.5892 82.2127 99.071 82.2127V78.6127ZM92.7831 84.9077C92.7831 88.3817 95.5956 91.2027 99.071 91.2027V87.6027C97.5892 87.6027 96.3831 86.3988 96.3831 84.9077H92.7831ZM99.071 91.2027C102.546 91.2027 105.359 88.3817 105.359 84.9077H101.759C101.759 86.3988 100.553 87.6027 99.071 87.6027V91.2027Z" fill="#A2ECFB"/>
<path d="M91.4873 65.382C90.8456 66.1412 90.9409 67.2769 91.7002 67.9186C92.4594 68.5603 93.5951 68.465 94.2368 67.7058L91.4873 65.382ZM84.507 35.2412C83.513 35.2282 82.6967 36.0236 82.6838 37.0176C82.6708 38.0116 83.4661 38.8279 84.4602 38.8409L84.507 35.2412ZM74.9407 39.8801C75.9127 39.6716 76.5315 38.7145 76.323 37.7425C76.1144 36.7706 75.1573 36.1517 74.1854 36.3603L74.9407 39.8801ZM25.5491 80.9047C25.6932 81.8883 26.6074 82.5688 27.5911 82.4247C28.5747 82.2806 29.2552 81.3664 29.1111 80.3828L25.5491 80.9047ZM94.2368 67.7058C97.8838 63.3907 100.505 58.927 101.752 54.678C103.001 50.4213 102.9 46.2472 100.876 42.7365L97.7574 44.5344C99.1494 46.9491 99.3603 50.0419 98.2974 53.6644C97.2323 57.2945 94.9184 61.3223 91.4873 65.382L94.2368 67.7058ZM100.876 42.7365C97.9119 37.5938 91.7082 35.335 84.507 35.2412L84.4602 38.8409C91.1328 38.9278 95.7262 41.0106 97.7574 44.5344L100.876 42.7365ZM74.1854 36.3603C67.4362 37.8086 60.0878 40.648 52.8826 44.8146L54.6847 47.931C61.5972 43.9338 68.5948 41.2419 74.9407 39.8801L74.1854 36.3603ZM52.8826 44.8146C44.1366 49.872 36.9669 56.0954 32.1491 62.3927C27.3774 68.63 24.7148 75.2115 25.5491 80.9047L29.1111 80.3828C28.4839 76.1026 30.4747 70.5062 35.0084 64.5802C39.496 58.7143 46.2839 52.7889 54.6847 47.931L52.8826 44.8146Z" fill="#A2ECFB"/>
<path d="M49.0825 87.2295C48.7478 86.2934 47.7176 85.8059 46.7816 86.1406C45.8455 86.4753 45.358 87.5055 45.6927 88.4416L49.0825 87.2295ZM78.5635 96.4256C79.075 95.5732 78.7988 94.4675 77.9464 93.9559C77.0941 93.4443 75.9884 93.7205 75.4768 94.5729L78.5635 96.4256ZM79.5703 85.1795C79.2738 86.1284 79.8027 87.1379 80.7516 87.4344C81.7004 87.7308 82.71 87.2019 83.0064 86.2531L79.5703 85.1795ZM69.156 22.5301C68.2477 22.1261 67.1838 22.535 66.7799 23.4433C66.3759 24.3517 66.7848 25.4155 67.6931 25.8194L69.156 22.5301ZM45.6927 88.4416C47.5994 93.7741 50.1496 98.2905 53.2032 101.505C56.2623 104.724 59.9279 106.731 63.9835 106.731V103.131C61.1984 103.131 58.4165 101.765 55.8131 99.0249C53.2042 96.279 50.8768 92.2477 49.0825 87.2295L45.6927 88.4416ZM63.9835 106.731C69.8694 106.731 74.8921 102.542 78.5635 96.4256L75.4768 94.5729C72.0781 100.235 68.0122 103.131 63.9835 103.131V106.731ZM83.0064 86.2531C85.0269 79.7864 86.1832 72.1831 86.1832 64.0673H82.5832C82.5832 71.8536 81.4723 79.0919 79.5703 85.1795L83.0064 86.2531ZM86.1832 64.0673C86.1832 54.1144 84.4439 44.922 81.4961 37.6502C78.5748 30.4436 74.3436 24.8371 69.156 22.5301L67.6931 25.8194C71.6364 27.5731 75.3846 32.1564 78.1598 39.0026C80.9086 45.7836 82.5832 54.507 82.5832 64.0673H86.1832Z" fill="#A2ECFB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M103.559 84.9077C103.559 82.4252 101.55 80.4127 99.071 80.4127C96.5924 80.4127 94.5831 82.4252 94.5831 84.9077C94.5831 87.3902 96.5924 89.4027 99.071 89.4027C101.55 89.4027 103.559 87.3902 103.559 84.9077Z" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.8143 89.4027C31.2929 89.4027 33.3023 87.3902 33.3023 84.9077C33.3023 82.4252 31.2929 80.4127 28.8143 80.4127C26.3357 80.4127 24.3264 82.4252 24.3264 84.9077C24.3264 87.3902 26.3357 89.4027 28.8143 89.4027Z" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
<path d="M63.9835 27.6986C66.4621 27.6986 68.4714 25.6861 68.4714 23.2036C68.4714 20.7211 66.4621 18.7086 63.9835 18.7086C61.5049 18.7086 59.4956 20.7211 59.4956 23.2036C59.4956 25.6861 61.5049 27.6986 63.9835 27.6986Z" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
</g>
<path d="M70.7175 48.0096L56.3133 50.676C56.0766 50.7199 55.9013 50.9094 55.887 51.1369L55.001 65.2742C54.9801 65.6072 55.3038 65.8656 55.6478 65.7907L59.6582 64.9163C60.0334 64.8346 60.3724 65.1468 60.2953 65.5033L59.1038 71.0151C59.0237 71.386 59.3923 71.7032 59.7758 71.5932L62.2528 70.8822C62.6368 70.7721 63.0057 71.0902 62.9245 71.4615L61.031 80.1193C60.9126 80.6608 61.6751 80.9561 61.9931 80.4918L62.2055 80.1817L73.9428 58.053C74.1393 57.6825 73.8004 57.26 73.3696 57.3385L69.2417 58.0912C68.8538 58.1618 68.5237 57.8206 68.6332 57.462L71.3274 48.6385C71.437 48.2794 71.1058 47.9378 70.7175 48.0096Z" fill="url(#paint1_linear_103_2)"/>
<defs>
<linearGradient id="paint0_linear_103_2" x1="1.43824" y1="7.91009" x2="56.3296" y2="82.4569" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear_103_2" x1="60.3173" y1="48.7336" x2="64.237" y2="77.1962" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
<clipPath id="clip0_103_2">
<rect width="128" height="128" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -0,0 +1,26 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_103_2)">
<path d="M63.9202 127.84C99.2223 127.84 127.84 99.2223 127.84 63.9202C127.84 28.6181 99.2223 0 63.9202 0C28.6181 0 0 28.6181 0 63.9202C0 99.2223 28.6181 127.84 63.9202 127.84Z" fill="url(#paint0_linear_103_2)"/>
<path d="M51.3954 39.5028C52.3733 39.6812 53.3108 39.033 53.4892 38.055C53.6676 37.0771 53.0194 36.1396 52.0414 35.9612L51.3954 39.5028ZM28.9393 60.9358C29.4332 61.7985 30.5329 62.0976 31.3957 61.6037C32.2585 61.1098 32.5575 60.0101 32.0636 59.1473L28.9393 60.9358ZM37.6935 66.7457C37.025 66.01 35.8866 65.9554 35.1508 66.6239C34.415 67.2924 34.3605 68.4308 35.029 69.1666L37.6935 66.7457ZM96.9206 89.515C97.7416 88.9544 97.9526 87.8344 97.3919 87.0135C96.8313 86.1925 95.7113 85.9815 94.8904 86.5422L96.9206 89.515ZM52.0414 35.9612C46.4712 34.9451 41.2848 34.8966 36.9738 35.9376C32.6548 36.9806 29.0841 39.1576 27.0559 42.6762L30.1748 44.4741C31.5693 42.0549 34.1448 40.3243 37.8188 39.4371C41.5009 38.5479 46.1547 38.5468 51.3954 39.5028L52.0414 35.9612ZM27.0559 42.6762C24.043 47.9029 25.2781 54.5399 28.9393 60.9358L32.0636 59.1473C28.6579 53.1977 28.1088 48.0581 30.1748 44.4741L27.0559 42.6762ZM35.029 69.1666C39.6385 74.24 45.7158 79.1355 52.8478 83.2597L54.6499 80.1432C47.8081 76.1868 42.0298 71.5185 37.6935 66.7457L35.029 69.1666ZM52.8478 83.2597C61.344 88.1726 70.0465 91.2445 77.7351 92.3608C85.359 93.4677 92.2744 92.6881 96.9206 89.515L94.8904 86.5422C91.3255 88.9767 85.4902 89.849 78.2524 88.7982C71.0793 87.7567 62.809 84.8612 54.6499 80.1432L52.8478 83.2597ZM105.359 84.9077C105.359 81.4337 102.546 78.6127 99.071 78.6127V82.2127C100.553 82.2127 101.759 83.4166 101.759 84.9077H105.359ZM99.071 78.6127C95.5956 78.6127 92.7831 81.4337 92.7831 84.9077H96.3831C96.3831 83.4166 97.5892 82.2127 99.071 82.2127V78.6127ZM92.7831 84.9077C92.7831 88.3817 95.5956 91.2027 99.071 91.2027V87.6027C97.5892 87.6027 96.3831 86.3988 96.3831 84.9077H92.7831ZM99.071 91.2027C102.546 91.2027 105.359 88.3817 105.359 84.9077H101.759C101.759 86.3988 100.553 87.6027 99.071 87.6027V91.2027Z" fill="#A2ECFB"/>
<path d="M91.4873 65.382C90.8456 66.1412 90.9409 67.2769 91.7002 67.9186C92.4594 68.5603 93.5951 68.465 94.2368 67.7058L91.4873 65.382ZM84.507 35.2412C83.513 35.2282 82.6967 36.0236 82.6838 37.0176C82.6708 38.0116 83.4661 38.8279 84.4602 38.8409L84.507 35.2412ZM74.9407 39.8801C75.9127 39.6716 76.5315 38.7145 76.323 37.7425C76.1144 36.7706 75.1573 36.1517 74.1854 36.3603L74.9407 39.8801ZM25.5491 80.9047C25.6932 81.8883 26.6074 82.5688 27.5911 82.4247C28.5747 82.2806 29.2552 81.3664 29.1111 80.3828L25.5491 80.9047ZM94.2368 67.7058C97.8838 63.3907 100.505 58.927 101.752 54.678C103.001 50.4213 102.9 46.2472 100.876 42.7365L97.7574 44.5344C99.1494 46.9491 99.3603 50.0419 98.2974 53.6644C97.2323 57.2945 94.9184 61.3223 91.4873 65.382L94.2368 67.7058ZM100.876 42.7365C97.9119 37.5938 91.7082 35.335 84.507 35.2412L84.4602 38.8409C91.1328 38.9278 95.7262 41.0106 97.7574 44.5344L100.876 42.7365ZM74.1854 36.3603C67.4362 37.8086 60.0878 40.648 52.8826 44.8146L54.6847 47.931C61.5972 43.9338 68.5948 41.2419 74.9407 39.8801L74.1854 36.3603ZM52.8826 44.8146C44.1366 49.872 36.9669 56.0954 32.1491 62.3927C27.3774 68.63 24.7148 75.2115 25.5491 80.9047L29.1111 80.3828C28.4839 76.1026 30.4747 70.5062 35.0084 64.5802C39.496 58.7143 46.2839 52.7889 54.6847 47.931L52.8826 44.8146Z" fill="#A2ECFB"/>
<path d="M49.0825 87.2295C48.7478 86.2934 47.7176 85.8059 46.7816 86.1406C45.8455 86.4753 45.358 87.5055 45.6927 88.4416L49.0825 87.2295ZM78.5635 96.4256C79.075 95.5732 78.7988 94.4675 77.9464 93.9559C77.0941 93.4443 75.9884 93.7205 75.4768 94.5729L78.5635 96.4256ZM79.5703 85.1795C79.2738 86.1284 79.8027 87.1379 80.7516 87.4344C81.7004 87.7308 82.71 87.2019 83.0064 86.2531L79.5703 85.1795ZM69.156 22.5301C68.2477 22.1261 67.1838 22.535 66.7799 23.4433C66.3759 24.3517 66.7848 25.4155 67.6931 25.8194L69.156 22.5301ZM45.6927 88.4416C47.5994 93.7741 50.1496 98.2905 53.2032 101.505C56.2623 104.724 59.9279 106.731 63.9835 106.731V103.131C61.1984 103.131 58.4165 101.765 55.8131 99.0249C53.2042 96.279 50.8768 92.2477 49.0825 87.2295L45.6927 88.4416ZM63.9835 106.731C69.8694 106.731 74.8921 102.542 78.5635 96.4256L75.4768 94.5729C72.0781 100.235 68.0122 103.131 63.9835 103.131V106.731ZM83.0064 86.2531C85.0269 79.7864 86.1832 72.1831 86.1832 64.0673H82.5832C82.5832 71.8536 81.4723 79.0919 79.5703 85.1795L83.0064 86.2531ZM86.1832 64.0673C86.1832 54.1144 84.4439 44.922 81.4961 37.6502C78.5748 30.4436 74.3436 24.8371 69.156 22.5301L67.6931 25.8194C71.6364 27.5731 75.3846 32.1564 78.1598 39.0026C80.9086 45.7836 82.5832 54.507 82.5832 64.0673H86.1832Z" fill="#A2ECFB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M103.559 84.9077C103.559 82.4252 101.55 80.4127 99.071 80.4127C96.5924 80.4127 94.5831 82.4252 94.5831 84.9077C94.5831 87.3902 96.5924 89.4027 99.071 89.4027C101.55 89.4027 103.559 87.3902 103.559 84.9077Z" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.8143 89.4027C31.2929 89.4027 33.3023 87.3902 33.3023 84.9077C33.3023 82.4252 31.2929 80.4127 28.8143 80.4127C26.3357 80.4127 24.3264 82.4252 24.3264 84.9077C24.3264 87.3902 26.3357 89.4027 28.8143 89.4027Z" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
<path d="M63.9835 27.6986C66.4621 27.6986 68.4714 25.6861 68.4714 23.2036C68.4714 20.7211 66.4621 18.7086 63.9835 18.7086C61.5049 18.7086 59.4956 20.7211 59.4956 23.2036C59.4956 25.6861 61.5049 27.6986 63.9835 27.6986Z" stroke="#A2ECFB" stroke-width="3.6" stroke-linecap="round"/>
<path d="M70.7175 48.0096L56.3133 50.676C56.0766 50.7199 55.9013 50.9094 55.887 51.1369L55.001 65.2742C54.9801 65.6072 55.3038 65.8656 55.6478 65.7907L59.6582 64.9163C60.0334 64.8346 60.3724 65.1468 60.2953 65.5033L59.1038 71.0151C59.0237 71.386 59.3923 71.7032 59.7758 71.5932L62.2528 70.8822C62.6368 70.7721 63.0057 71.0902 62.9245 71.4615L61.031 80.1193C60.9126 80.6608 61.6751 80.9561 61.9931 80.4918L62.2055 80.1817L73.9428 58.053C74.1393 57.6825 73.8004 57.26 73.3696 57.3385L69.2417 58.0912C68.8538 58.1618 68.5237 57.8206 68.6332 57.462L71.3274 48.6385C71.437 48.2794 71.1058 47.9378 70.7175 48.0096Z" fill="url(#paint1_linear_103_2)"/>
</g>
<defs>
<linearGradient id="paint0_linear_103_2" x1="1.43824" y1="7.91009" x2="56.3296" y2="82.4569" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear_103_2" x1="60.3173" y1="48.7336" x2="64.237" y2="77.1962" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
<clipPath id="clip0_103_2">
<rect width="128" height="128" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,60 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: authorization.js -
// | @创建时间: 2024-11-04 14:15
// | @更新时间: 2024-11-04 14:15
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { useSystemStore } from '@/stores/system.js';
import { useIpcStore } from "@/stores/ipc.js";
export default function authRouter(app, option) {
const { router } = option;
const systemStore = useSystemStore();
const ipcStore = useIpcStore();
router.beforeEach(async (to, from) => {
console.log('%c<<=== ROUTER', consoleColor.router.IN, from.path)
console.log('%c===>> ROUTER', consoleColor.router.TO, to.path)
// 核心条件1: 服务器地址明确
const condition1 = ipcStore.state.serverHostInit === 1;
// 核心条件2: 设备绑定过
const condition2 = systemStore.state.deviceBinding !== undefined;
// 核心条件3: 存在登录用户
const condition3 = systemStore.state.onlineUserList.length !== 0
if(condition1 && condition2 && condition3){
return true;
}else{
if(!condition1 && to.path !== '/initialization'){
// 服务器地址未初始化
return router.push('/initialization');
}
if(condition1 && !condition2 && to.path !== '/deviceBinding'){
// 设备信息未绑定
return router.push('/deviceBinding');
}
if(condition1 && condition2 && !condition3 && to.path !== '/login'){
// 不存在登录用户
return router.push('/login');
}
}
// ? 判断是否在白名单
if (systemStore.state.routeWhiteList.includes(to.path)) {
return true;
}
// ? 其他均在权限范围内
});
router.afterEach(async (to, from) => {
// console.log('AFETR AUTH ROUTER');
});
}

View File

@ -0,0 +1,74 @@
import {createRouter, createWebHashHistory, createWebHistory} from 'vue-router'
import {useSystemStore} from '@/stores/system.js';
export default function createRoutering() {
const systemStore = useSystemStore();
if(['', '/'].includes(systemStore.state.defaultRoute)) {
throw new Error('默认跳转路由不能为空!')
}
// console.log(systemStore.state.defaultRoute)
const router = createRouter({
// 打包后就不能使用WebHistory了
// 要使用Hash
// history: createWebHistory(import.meta.env.BASE_URL),
history:createWebHashHistory(),
routes: [
{
path: '',
redirect: systemStore.state.defaultRoute,
},
{
path: '/',
name: '/',
component: () => import('@/components/Hello/index.vue'),
},
{
path: '/initialization',
name: 'initialization',
component: () => import('@/views/Initialization/index.vue'),
},
{
path: '/login',
name: 'login',
component: () => import('@/views/Login/index.vue'),
},
{
path: '/deviceBinding',
name: 'deviceBinding',
component: () => import('@/views/DeviceBinding/index.vue'),
},
{
path: '/productionBatch',
name: 'productionBatch',
component: () => import('@/views/ProductionBatch/index.vue'),
},
{
path: '/home',
name: 'home',
component: () => import('@/views/Home/index.vue'),
children: [
{
path: 'workbench',
name: 'workbench',
component: () => import('@/components/Hello/index.vue'),
},
{
path: 'newspaperWorker',
name: 'newspaperWorker',
component: () => import('@/views/NewspaperWorker/index.vue'),
},
{
path: 'clockin',
name: 'clockin',
component: () => import('@/components/Hello/index.vue'),
},
{
path: 'setting',
name: 'setting',
component: () => import('@/components/Hello/index.vue'),
},
]
}]
})
return router
}

View File

@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

81
src/render/stores/ipc.js Normal file
View File

@ -0,0 +1,81 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: ipc.js -
// | @创建时间: 2024-11-04 14:31
// | @更新时间: 2024-11-04 14:31
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import {reactive} from "vue";
import {defineStore} from 'pinia';
import {useSystemStore} from "@/stores/system.js";
import api from "@/api/index.js";
const IPC_NAME = import.meta.env.VITE_ELECTRON_IPC_MESSAGE_NAME
const MAIN_NAME = import.meta.env.VITE_ELECTRON_MAIN_MESSAGE_NAME
export const useIpcStore = defineStore('ipc', () => {
const state = reactive({
// 服务器地址状态
serverHostInit: 0,
serverHost: null,
});
window?.ipcRenderer?.on(MAIN_NAME, (_event, message) => {
if (!message.type) {
return console.log('未知进程消息:', message)
}
console.log('%c===>> IPC_MESSAGE ', consoleColor.ipcMessage.TO,message)
switch (message.type) {
case '/init/serverHost':
// 有服务器地址传回
if (message.data.status === 0) {
state.serverHostInit = 1;
state.serverHost = message.data.serverHost;
} else if (message.data.status === -1) {
if (state.serverHostInit !== 1) {
state.serverHostInit = -1;
}
}
console.log('ABC')
break;
case '/init/deviceBindingInfo':
const systemStore = useSystemStore();
if(message.data.status === 0){
systemStore.setDeviceBinding(message.data.deviceBinding, true);
}
break;
default:
console.log('IPC UNKNOW', message)
break;
}
})
// 告诉主线程,初始化成功
const initRender = () => {
console.log('%c<<=== IPC_MESSAGE ', consoleColor.ipcMessage.IN, 'Started')
window?.ipcRenderer?.send(IPC_NAME, {
type: 'Started',
data: {
msg: 'Init ok!'
}
})
}
// 修改渲染器服务器地址状态
const setRenderServerHostState = (status = -1) => {
state.serverHostInit = status
}
// 设置有效的服务器地址
const setValidServerAddress = async (host) => {
await api.initIpc.setServerHost({host})
state.serverHostInit = 1;
state.serverHost = host;
}
return {state, initRender, setRenderServerHostState, setValidServerAddress}
})

131
src/render/stores/system.js Normal file
View File

@ -0,0 +1,131 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: system.js -
// | @创建时间: 2024-11-04 14:06
// | @更新时间: 2024-11-04 14:06
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import {reactive} from 'vue';
import {defineStore} from 'pinia';
import {useIpcStore} from "@/stores/ipc.js";
import api from "@/api/index.js";
export const useSystemStore = defineStore('system', () => {
const localStore = {
checkedUserList: localStorage.getItem('checkedUserList') === null ? [] : JSON.parse(localStorage.getItem('checkedUserList')),
}
const state = reactive({
name: 'systemStore',
// * 路由白名单
routeWhiteList: ['/', '/signin'],
// * 接口白名单
apiWhiteList: ['/authUser/sign/in'],
// * 默认跳转路由
defaultRoute: import.meta.env.VITE_HOME_REDIRECT,
// * 标题
title: import.meta.env.VITE_TITLE,
// * 组件默认大小
componentSize: 'large',
// * 主页地址
homePath: '/home',
// * 在线用户列表
onlineUserList: [],
// * 当前选中的用户
nowFocusUser: {},
// * 系统名称
systemName: import.meta.env.VITE_SYSTEM_NAME,
// * 设备绑定信息
deviceBinding: undefined,
// * 记住密码列表
checkedUserList: localStore.checkedUserList,
// * 网络状态
networkStatus: true,
// * 生产计划
productionBatch: null,
// * 公用报工参数
reportParam: {},
// * 工序队列
reportQueue: {},
// * 工序队列变化值
reportQueueChange: 0,
})
// * 设置当前选中用户
const setFocusUser = (data) => {
state.nowFocusUser = data
}
// * 新增在线用户
const setNewOnlineUser = (data) => {
state.onlineUserList.push(data)
}
// * 覆盖在线用户
const copyOnlineUser = (data) => {
state.onlineUserList = (data)
}
// * 退出登录指定用户
const logoutUser = (username) => {
let userCount;
state.onlineUserList.forEach((user, index) => {
if (user.username === username) {
userCount = index
}
})
state.onlineUserList.splice(userCount, 1)
}
// * 设置设备信息
const setDeviceBinding = async (deviceBinding) => {
await api.deviceIpc.setDeviceBinding(deviceBinding)
state.deviceBinding = deviceBinding
}
// * 新增用户信息库
const addUserInfo = (user) => {
const checkedUserList = state.checkedUserList
checkedUserList.push(user)
localStorage.setItem('checkedUserList', JSON.stringify(checkedUserList))
}
// * 设置用户的工序
const setUserProcessList = (processList) => {
state.nowFocusUser.defaultProcessList = processList
}
// * 设置生产计划
const setProductionBatch = (batch) => {
state.productionBatch = batch
}
// * 添加报工参数
const addReportParam = (key, value) => {
state.reportParam[key] = value
}
// * 改变工序队列
const handleChangeReportQueue = (key, value) => {
state.reportQueue[key] = value
state.reportQueueChange = new Date().getTime()
}
return {
state,
setFocusUser,
setNewOnlineUser,
logoutUser,
setDeviceBinding,
addUserInfo,
copyOnlineUser,
setUserProcessList,
setProductionBatch,
addReportParam,
handleChangeReportQueue
}
})

287
src/render/stores/ws.js Normal file
View File

@ -0,0 +1,287 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-nie】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-ii-pad-linux
// | @文件描述: ws.js -
// | @创建时间: 2024-11-26 17:05
// | @更新时间: 2024-11-26 17:05
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import {defineStore} from "pinia";
import {inject, reactive} from "vue";
import {v4 as getUUID} from 'uuid';
import {sleep} from "@/tools/sleep.js";
import {message} from "ant-design-vue";
import {useSystemStore} from "@/stores/system.js";
const Message = message
class MyWebSocket {
// 实时请求池
#requestPool = {};
// websocket客户端
#mwsc = null;
// 请求默认超时时间
#requestTimeout = 10000;
// ws默认地址
#host = '10.10.10.10';
// 外部的state状态值
#state = undefined;
// 相当于token的东西
#deviceInfo = undefined;
// 重复启动不会重复发心跳
#heartStatus = 0;
// 正在启动
#starting = false;
constructor(options = {host: '10.10.10.10', requestTimeout: 10000}, state) {
if (options.requestTimeout) {
this.#requestTimeout = options.requestTimeout;
}
if (options.host) {
this.#host = options.host;
}
this.#state = state;
const systemStore = useSystemStore();
this.#deviceInfo = systemStore.state.deviceBinding;
this.createServer()
}
createServer() {
if(this.#starting){
return ;
}
return new Promise((resolve, reject) => {
// console.log('正在启动WS')
this.#starting = true;
const wsc = new WebSocket(`ws://${this.#host}:38610`);
this.#heart()
this.#heartStatus = 1;
wsc.onopen = () => {
this.#starting = false;
// console.log("wsc open");
this.#state.wsState = 1;
resolve();
}
wsc.onclose = () => {
this.#starting = false;
console.log("wsc close");
this.#state.wsState = -1;
reject('wsc closed');
}
wsc.onerror = (e) => {
// console.log("wsc error:", e)
this.#state.wsState = -1;
reject("wsc error:", e);
}
wsc.onmessage = (msg) => {
let message = undefined;
// console.log("wsc message:", msg);
try {
message = JSON.parse(msg.data);
} catch (err) {
console.log('ws响应数据不合法无法转换为Object')
}
if (!message) {
return
}
if (message.type === 'realTimeRequest') {
if (Object.keys(this.#requestPool).includes(message.uuid)) {
clearTimeout(this.#requestPool[message.uuid].timeout);
this.#requestPool[message.uuid].end(message);
}
}else if(message.type === 'simpleRequest') {
console.log(`%c===>> WS-Sim : ${message.path}`,consoleColor.wsServer.IN, message.data || '');
const ctx = {
data: message,
reply: data => {
try{
console.log(`%c<<=== WS-Sim : ${message.path}`,consoleColor.wsServer.TO, data || '');
this.#mwsc.send(JSON.stringify({
...message,
data,
}));
}catch (e) {
console.log(`wsc server callback Error`, e)
}
}
}
ctx.reply(this.#simpleRequestServer(ctx));
}
}
this.#mwsc = wsc;
})
}
async #heart() {
const systemStore = useSystemStore();
if (this.#heartStatus !== 0) {
return;
}
// 心跳请求每5秒发送一次不间断
let heartNumber = 0;
let heartErrorNumber = 0;
await sleep(1000);
const sendHeart = () => {
if(heartErrorNumber >= 2){
systemStore.state.networkStatus = false;
}else{
systemStore.state.networkStatus = true;
}
this.request('/heart', {
heartNumber: heartNumber++, timestamp: new Date().getTime(),
}, 3000).then(res => {
heartErrorNumber = 0;
}, err => {
heartErrorNumber++;
console.log('wsc 心跳发送失败: ', heartErrorNumber);
})
}
sendHeart();
setInterval(() => {
sendHeart();
}, 5000)
}
// 等待存活
async #awaitSurvival(){
if (this.#mwsc.readyState !== 1) {
if (this.#mwsc.readyState === 2) {
for (let i = 0; i < 5; i++) {
await sleep(1000);
}
}
await sleep(1000);
if (this.#mwsc.readyState !== 1) {
this.#mwsc?.close();
console.log('ws已断开, 正在重启!!!');
await this.createServer()
}
} else if (this.#mwsc.readyState === 0) {
for (let i = 0; i < 5; i++) {
await sleep(1000);
if (this.#mwsc.readyState === 1) {
break;
}
}
}
}
request = (path, data, timeout = undefined) => {
return new Promise(async (resolve, reject) => {
if (!['/heart'].includes(path)) {
console.log(`%c<<=== WS-Req : ${path}`,consoleColor.wsRequest.IN, data || '');
}
const uuid = getUUID();
this.#requestPool[uuid] = {
uuid: uuid,
request: data,
timeout: setTimeout(() => {
delete this.#requestPool[uuid]
reject(`timeout : ${path}`)
}, timeout === undefined ? this.#requestTimeout : timeout),
end: (response) => {
if (!['/heart'].includes(path)) {
console.log(`%c===>> WS-Req : ${path}`, consoleColor.wsRequest.TO, response.data);
}
delete this.#requestPool[uuid];
if (response.code !== 200) {
console.log('实时消息错误', response.message);
Message.warn(response.message);
reject(response);
} else {
resolve(response);
}
}
}
await this.#awaitSurvival()
try {
const requestDataStringify = JSON.stringify({
type: "realTimeRequest", uuid: uuid, path: path, data: data, license: {
deviceInfo: this.#deviceInfo
}
})
this.#mwsc.send(requestDataStringify);
} catch (err) {
console.log('wsc 消息发送失败:', err);
}
})
}
// 作为服务端的响应
#simpleRequestServer(ctx){
const systemStore = useSystemStore();
switch(ctx.data.path) {
case '/report': {
console.log('???')
console.log(ctx.data)
systemStore.state.reportQueue[ctx.data.data.processId]
systemStore.state.reportQueue[ctx.data.data.processId]?.push(ctx.data.data)
systemStore.state.reportQueueChange = new Date().getTime();
return 'ok'
}
}
}
}
export const useWSStore = defineStore('ws', () => {
const state = reactive({
wsState: 0, // 0是默认状态未启动1是正常启动状态 -1是出错状态
wsc: undefined, // wsc客户端
})
// 连接服务器
const connectWebSocketServer = (host = '10.10.10.10') => {
if (state.wsState === 1) {
return;
}
const mwsc = new MyWebSocket({host: host}, state);
state.wsc = mwsc;
}
const wsMethod = {
// 登录
login: data => state.wsc.request('/login', data), // 退出登录
logout: data => state.wsc.request('/login/logout', data), // 获取当前设备上登陆的用户
getDeviceUserList: data => state.wsc.request('/login/getDeviceUserList', data), // 获取用户的登陆设备列表
getUserLoginDeviceList: data => state.wsc.request('/login/getUserLoginDeviceList', data),
// 获取当前产线生产批次
getProductionBatch: () => state.wsc.request('/production/getProductionBatch'),
// 获取当前计划的pad的工序
getProductionRouteByRouteIdAndIpcId: data => state.wsc.request('/production/getProductionRouteByRouteIdAndIpcId', data),
// 保存用户默认工序
setUserDefaultProcessList: data => state.wsc.request('/production/setUserDefaultProcessList', data),
// 获取报工参数
getReportParam: data => state.wsc.request('/production/getReportParam', data),
// 正常报工
addProductionReport: data => state.wsc.request('/production/addProductionReport', data),
// 获取当前工序的队列
getReportQueue: data => state.wsc.request('/production/getReportQueue', data),
// 湖区当前产线流水号
getWorklineSerial: data => state.wsc.request('/production/getWorklineSerial', data),
}
const onWsConnect = async (callback) => {
for (let i = 0; i < 5; i++) {
if(state.wsState === 1) {
callback()
break;
}else{
await sleep(200);
}
}
}
return {state, connectWebSocketServer, wsMethod, onWsConnect}
})

20
src/render/tools/sleep.js Normal file
View File

@ -0,0 +1,20 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: js-II-pad
// | @文件描述: sleep.js -
// | @创建时间: 2024-10-31 11:23
// | @更新时间: 2024-10-31 11:23
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
export function sleep(time = 300) {
return new Promise((res) => {
setTimeout(() => {
res()
}, time)
})
}

View File

@ -0,0 +1,260 @@
<script setup>
import {onMounted, reactive, watch} from "vue";
defineOptions({
name: 'DeviceBinding',
});
import api from "@/api/index.js";
import {useSystemStore} from "@/stores/system.js";
import {useRouter} from "vue-router";
const systemStore = useSystemStore();
const router = useRouter()
const deviceOption = reactive({
//
companyOption: [],
companyValue: null,
companyName: null,
companyLoading: false,
companyDisabled: false,
//
factoryOption: [],
factoryValue: null,
factoryName: null,
factoryLoading: false,
factoryDisabled: false,
//
workshopOption: [],
workshopValue: null,
workshopName: null,
workshopLoading: false,
workshopDisabled: false,
// 线
worklineOption: [],
worklineValue: null,
workLineFlag:'',
worklineName: null,
worklineLoading: false,
worklineDisabled: false,
//
ipcOption: [],
ipcValue: null,
ipcName: null,
ipcLoading: false,
ipcDisabled: false,
// buttonDisable
buttonDisable: true,
})
//
watch(() => deviceOption.companyValue, (val, oldValue) => {
if(val == oldValue) {
return;
}
deviceOption.companyOption.forEach(item => {
if (item.id == val) {
deviceOption.factoryOption = item.children
deviceOption.companyName = item.label
deviceOption.factoryValue = null
deviceOption.workshopValue = null
deviceOption.worklineValue = null
deviceOption.ipcValue = null
deviceOption.workshopOption = []
deviceOption.worklineOption = []
}
})
})
//
watch(() => deviceOption.factoryValue, (val, oldValue) => {
if(val == oldValue) {
return;
}
deviceOption.factoryOption.forEach(item => {
if (item.id == val) {
deviceOption.workshopOption = item.children
deviceOption.factoryName = item.label
deviceOption.workshopValue = null
deviceOption.worklineValue = null
deviceOption.ipcValue = null
deviceOption.worklineOption = []
}
})
})
//
watch(() => deviceOption.workshopValue, (val, oldValue) => {
if(val == oldValue) {
return;
}
deviceOption.workshopOption.forEach(item => {
if (item.id == val) {
deviceOption.worklineOption = item.children
deviceOption.workshopName = item.label
deviceOption.worklineValue = null
deviceOption.ipcValue = null
}
})
})
// 线
watch(() => deviceOption.worklineValue, (val, oldValue) => {
if(val == oldValue) {
return;
}
deviceOption.worklineOption.forEach(item => {
if (item.id == val) {
deviceOption.worklineName = item.label
deviceOption.workLineFlag = item.workLineFlag
}
})
if (val !== null) {
api.device.getIpc({
currentPage:1,
ipcName: "",
pageSize: 100,
worklineId: val
}).then(res => {
deviceOption.ipcOption = res.rows
deviceOption.ipcValue = null
})
}
})
watch(() => deviceOption.ipcValue, (val, oldValue) => {
deviceOption.ipcOption.forEach(item => {
if (item.ipcId == val) {
deviceOption.ipcName = item.ipcName
}
})
if(val){
deviceOption.buttonDisable = false
}else {
deviceOption.buttonDisabled = true
}
})
const handleBindingDeviceClick = async () => {
await systemStore.setDeviceBinding({
companyValue: deviceOption.companyValue,
companyName: deviceOption.companyName,
factoryValue: deviceOption.factoryValue,
factoryName: deviceOption.factoryName,
workshopValue: deviceOption.workshopValue,
workshopName: deviceOption.workshopName,
worklineValue: deviceOption.worklineValue,
worklineName: deviceOption.worklineName,
workLineFlag: deviceOption.workLineFlag,
ipcValue: deviceOption.ipcValue,
ipcName: deviceOption.ipcName,
})
router.push('/home')
}
onMounted(() => {
api.device.getCompanyNameSet().then(res => {
deviceOption.companyOption = res.data;
deviceOption.companyValue = res.data[0]?.id;
})
})
</script>
<template>
<div class="deviceBinding">
<div class="deviceBinding-container">
<div>
<a-select
v-model:value="deviceOption.companyValue"
style="width: 300px"
:fieldNames="{label: 'label', value: 'id'}"
:loading="deviceOption.companyLoading"
:disabled="deviceOption.companyDisabled"
placeholder="请选择企业"
:options="deviceOption.companyOption">
<template v-slot:option="{value, label, children}">
<div>{{label}}
<a-badge
:count="children.length"
:number-style="{ backgroundColor: '#fff', color: '#999', boxShadow: '0 0 0 1px #d9d9d9 inset' }"
/>
</div>
</template>
</a-select>
</div>
<div>
<a-select
v-model:value="deviceOption.factoryValue"
style="width: 300px"
:fieldNames="{label: 'label', value: 'id'}"
:loading="deviceOption.factoryLoading"
:disabled="deviceOption.factoryDisabled"
placeholder="请选择工厂"
:options="deviceOption.factoryOption">
<template v-slot:option="{value, label, children}">
<div>{{label}}
<a-badge
:count="children.length"
:number-style="{ backgroundColor: '#fff', color: '#999', boxShadow: '0 0 0 1px #d9d9d9 inset' }"
/>
</div>
</template>
</a-select>
</div>
<div>
<a-select
v-model:value="deviceOption.workshopValue"
style="width: 300px"
:fieldNames="{label: 'label', value: 'id'}"
:loading="deviceOption.workshopLoading"
:disabled="deviceOption.workshopDisabled"
placeholder="请选择车间"
:options="deviceOption.workshopOption">
<template v-slot:option="{value, label, children}">
<div>{{label}}
<a-badge
:count="children.length"
:number-style="{ backgroundColor: '#fff', color: '#999', boxShadow: '0 0 0 1px #d9d9d9 inset' }"
/>
</div>
</template>
</a-select>
</div>
<div>
<a-select
v-model:value="deviceOption.worklineValue"
style="width: 300px"
:fieldNames="{label: 'label', value: 'id'}"
:loading="deviceOption.worklineLoading"
:disabled="deviceOption.worklineDisabled"
placeholder="请选择产线"
:options="deviceOption.worklineOption">
</a-select>
</div>
<div>
<a-select
v-model:value="deviceOption.ipcValue"
style="width: 300px"
:fieldNames="{label: 'ipcName', value: 'ipcId'}"
:loading="deviceOption.ipcLoading"
:disabled="deviceOption.ipcDisabled"
placeholder="请选择设备"
:options="deviceOption.ipcOption"/>
</div>
<div>
<a-button @click="handleBindingDeviceClick" type="primary" style="width: 300px" :disabled="deviceOption.buttonDisable">绑定工控机</a-button>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.deviceBinding {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
& > div.deviceBinding-container {
position: relative;
& > div{
margin: 10px 0;
}
}
}
</style>

View File

@ -0,0 +1,21 @@
<script setup>
import FrameWork from "@/components/FrameWork/index.vue";
import {useWSStore} from "@/stores/ws.js";
import {onMounted} from "vue";
const wsStore = useWSStore();
onMounted(() => {
wsStore.connectWebSocketServer()
})
</script>
<template>
<FrameWork>
<RouterView />
</FrameWork>
</template>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,55 @@
<script setup>
import api from "@/api/index.js";
defineOptions({
name: 'InputServerHost',
});
import {reactive, inject, onUnmounted } from "vue";
import {useIpcStore} from "@/stores/ipc.js";
const store = useIpcStore();
const message = inject('message');
//
const serverInfo = reactive({
host: '10.10.10.10',
searchStatus: false,
})
let sendNewServerHostTimeout = undefined;
//
async function handleSearchServerClick(){
const isIpv4 = isValidIPv4(serverInfo.host);
if (!isIpv4) {
return message.warn('输入的地址不符合IPv4规范')
}
serverInfo.searchStatus = true;
sendNewServerHostTimeout = setTimeout(() => {
serverInfo.searchStatus = false;
}, 10 * 1000)
const data = await api.initIpc.sendNewServerHost({host: serverInfo.host});
await store.setValidServerAddress(serverInfo.host);
}
//
onUnmounted(() => {
console.log('卸载')
sendNewServerHostTimeout && clearTimeout(sendNewServerHostTimeout)
})
// IPv4
function isValidIPv4(ip) {
const re = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
return re.test(ip);
}
</script>
<template>
<div class="inputServerHost">
<a-input-search
@search="handleSearchServerClick"
v-model:value="serverInfo.host"
placeholder="请输入服务器IP地址"
:loading="serverInfo.searchStatus"/>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,74 @@
<script setup>
defineOptions({
name: 'Initialization',
});
import InputServerHost from "./InputServerHost.vue";
import {useRouter} from "vue-router";
import {onMounted, watchEffect} from "vue";
import {useIpcStore} from "@/stores/ipc.js";
import {useSystemStore} from "@/stores/system.js";
const IpcStore = useIpcStore();
const systemStore = useSystemStore();
const router = useRouter()
let serverHostTimeout = undefined;
watchEffect(() => {
if(IpcStore.state.serverHostInit === 1){
setTimeout(()=>{
router.push('/home');
}, 1000)
}
if(IpcStore.state.serverHostInit !== 0){
clearTimeout(serverHostTimeout);
}
})
onMounted(() => {
serverHostTimeout = setTimeout(() => {
IpcStore.setRenderServerHostState();
}, 10 * 1000)
})
</script>
<template>
<div class="initialization">
<div class="helloContainer" v-if="IpcStore.state.serverHostInit === 0">
<div>正在获取产线服务器地址信息...</div>
<div>
<a-spin/>
</div>
</div>
<div class="helloContainer" v-else-if="IpcStore.state.serverHostInit === 1">
<div>已获取到产线服务器地址信息正在连接...</div>
<div>
<a-spin size="large"/>
</div>
</div>
<div class="helloContainer" v-else>
<div>获取产线服务器地址信息失败</div>
<InputServerHost/>
</div>
</div>
</template>
<style scoped lang="scss">
.initialization {
position: relative;
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
& > .helloContainer {
position: relative;
& > div {
height: 10rem;
display: flex;
align-items: center;
justify-content: center;
}
}
}
</style>

View File

@ -0,0 +1,432 @@
<script setup>
import reduceSvg from "@/assets/icon/reduce.svg";
defineOptions({
name: 'Login',
});
import {inject, onMounted,reactive, ref} from "vue";
import {useSystemStore} from "@/stores/system.js";
import {useWSStore} from "@/stores/ws.js";
import {useRouter} from "vue-router";
import SimpleFrameWork from '@/components/SImpleFrameWork/index.vue'
const systemStore = useSystemStore();
const wsStore = useWSStore();
const router = useRouter()
const message = inject('message');
const loginInfo = reactive({
username: '',
password: '',
checked: true,
})
//
const userDeviceStatus = ref(false)
//
const userDeviceList = ref([])
//
const handleLoginClick = async () => {
//
if (loginInfo.username.length === 0) {
return message.warn('账号不能为空!')
}
if (loginInfo.password.length === 0) {
return message.warn('密码不能为空!')
}
// 1.
for (let user of systemStore.state.onlineUserList) {
// console.log(user)
if (user.username === loginInfo.username) {
return message.warn('当前用户已经登陆!')
}
}
// 2.
const resd = await wsStore.wsMethod.login({
username: loginInfo.username,
password: loginInfo.password,
code: ''
})
message.success('登陆成功!')
systemStore.setNewOnlineUser(resd.data.userInfo)
systemStore.setFocusUser(resd.data.userInfo)
if (true && loginInfo.checked) {
//
let existUserInfo = false;
systemStore.state.checkedUserList.forEach((user) => {
if (user.username === loginInfo.username) {
existUserInfo = true;
user.username = loginInfo.username;
user.password = loginInfo.password;
user.value = loginInfo.username
}
})
if (!existUserInfo) {
systemStore.addUserInfo({
...loginInfo,
value: loginInfo.username
})
}
}
// 线
if (resd.data.onlineUserList?.length > 0) {
userDeviceList.value = resd.data.onlineUserList
userDeviceStatus.value = true;
} else {
router.push('/productionBatch'); //
}
}
// 退
const logoutOtherDevice = async (deviceId) => {
await wsStore.wsMethod.logout({
deviceId,
username: loginInfo.username,
});
const resd = await wsStore.wsMethod.getUserLoginDeviceList({
username: loginInfo.username,
})
userDeviceList.value = resd.data
}
// 退
const logoutOtherUser = async (deviceId, username) => {
await wsStore.wsMethod.logout({
deviceId,
username
});
getNowDeviceUser()
}
// 线退
const handleClearUserOk = () => {
userDeviceStatus.value = false;
router.push('/productionBatch'); //
}
//
const getNowDeviceUser = async () => {
const resd = await wsStore.wsMethod.getDeviceUserList()
// console.log(resd.data)
systemStore.copyOnlineUser(resd.data)
}
// 线
const chooseNowUser = (user) => {
systemStore.setFocusUser(user);
router.push('/productionBatch'); //
}
//
const filterOption = (input, option) => {
return option.value.toUpperCase().indexOf(input.toUpperCase()) >= 0;
};
//
const handleSelectUser = (val) => {
// console.log(val, systemStore.state.checkedUserList)
for (let user of systemStore.state.checkedUserList) {
if (user.username === val) {
loginInfo.password = user.password;
}
}
}
onMounted(() => {
wsStore.connectWebSocketServer()
wsStore.onWsConnect(() => {
getNowDeviceUser()
})
})
</script>
<template>
<SimpleFrameWork>
<main class="login">
<div class="login_container">
<header>登录</header>
<div>
<div class="loginBox">
<div class="login_content">
<div>
欢迎使用
</div>
<div>
<div>
<div class="title">账号</div>
<div>
<a-auto-complete
v-model:value="loginInfo.username"
:options="systemStore.state.checkedUserList"
style="width: 100%"
placeholder="请输入用户名"
allowClear
:filter-option="filterOption"
@select="handleSelectUser"
>
<template #option="{ value }">
{{ value }}
</template>
</a-auto-complete>
</div>
</div>
</div>
<div>
<div>
<div class="title">密码</div>
<div>
<a-input-password v-model:value="loginInfo.password" placeholder="请输入密码"/>
</div>
</div>
</div>
<div>
<div>
<div>
<a-checkbox v-model:checked="loginInfo.checked">记住我</a-checkbox>
</div>
<div>
<a-button @click="handleLoginClick" type="primary" style="width: 100%">登录
</a-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="loginList_container">
<header>已登陆</header>
<div>
<div class="loginList_container_box" v-for="item in systemStore.state.onlineUserList">
<header @click="chooseNowUser(item)">{{ item?.nickName }}</header>
<main @click="chooseNowUser(item)">
<div class="processList" v-for="i in item.defaultProcessList">
<div></div>
<div>{{i.processName}}</div>
</div>
</main>
<footer>
<a-popconfirm
title="确定要退出用户吗?"
ok-text="确定"
cancel-text="取消"
@confirm="logoutOtherUser(systemStore.state.deviceBinding.ipcValue,item.username)"
>
<a-button type="primary" danger @click="" style="width: 100%">退出登录</a-button>
</a-popconfirm>
</footer>
</div>
</div>
</div>
</main>
<footer>
<a-modal v-model:open="userDeviceStatus" width="800px" centered title="登录提示" @ok="handleClearUserOk">
<div class="userDeviceModal">
<header>检测到您的账号已在多个Pad上登录您是否要退出登录的账号</header>
<div class="userDeviceModal_container">
<div v-for="item in userDeviceList" class="userDeviceModal_item">
<div>{{ item.workshopName }} - {{ item.worklineName }} - {{ item.ipcName }}</div>
<div>
<a-button type="primary" danger @click="logoutOtherDevice(item.ipcValue)"
:disabled="item.ipcValue == systemStore.state.deviceBinding.ipcValue">退出
</a-button>
</div>
</div>
</div>
</div>
</a-modal>
</footer>
</SimpleFrameWork>
</template>
<style scoped lang="scss">
main.login {
position: relative;
flex: 1;
height: 100%;
width: 100%;
display: flex;
box-sizing: border-box;
overflow: hidden;
& > div {
position: relative;
display: flex;
flex-direction: column;
& > header {
position: relative;
height: 2em;
flex-shrink: 0;
font-weight: bold;
}
& > div {
flex: 1;
background: #fefefe;
border-radius: var(--framework-border-radius);
& > div.loginBox {
position: relative;
padding: 20px;
height: 100%;
overflow: hidden;
& > div.login_content {
position: relative;
height: 100%;
display: flex;
flex-direction: column;
& > div {
position: relative;
display: flex;
align-items: center;
}
& > div:first-child {
justify-content: center;
flex: 1;
}
& > div:last-child {
flex: 3;
& > div {
position: relative;
width: 100%;
& > div:first-child {
margin-bottom: 20px;
}
}
}
& > div:nth-child(2), & > div:nth-child(3) {
flex: 2;
& > div {
position: relative;
width: 100%;
& > div.title {
position: relative;
line-height: 3em;
}
}
}
}
}
}
}
& > div.login_container {
flex: 1;
padding-right: 20px;
}
& > div.loginList_container {
position: relative;
flex: 2;
overflow: auto;
& > div {
position: relative;
display: flex;
overflow: auto;
padding: 20px;
& > div.loginList_container_box{
position: relative;
display: flex;
flex-direction: column;
flex: 1;
padding: 20px;
margin-right: 20px;
border: 1px dashed #2993ff;
border-radius: 10px;
min-width: 260px;
max-width: 300px;
& > header {
position: relative;
font-size: 1.5em;
flex-shrink: 0;
margin-bottom: 20px;
}
& > main{
position: relative;
flex: 1;
overflow: auto;
& > div.processList{
position: relative;
display: flex;
border-radius: 10px;
background: #f4f9ff;
padding: 10px;
margin-bottom: 10px;
& > div:first-child{
position: relative;
flex-shrink: 0;
width: 40px;
&:after{
display: block;
content: '';
position: relative;
width: 1em;
height: 1em;
background: #2993ff;
border-radius: 1em;
}
}
& > div:nth-child(2){
position: relative;
flex: 1;
//white-space: nowrap; /* */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 超出部分显示省略号 */
white-space: normal; /* 允许文本换行 */
}
}
}
& > footer{
position: relative;
flex-shrink: 0;
text-align: center;
margin-top: 20px;
}
}
}
}
}
.userDeviceModal {
position: relative;
max-height: 70vh;
width: 100%;
overflow: hidden;
& > header {
font-size: 1.2em;
}
& > div.userDeviceModal_container {
position: relative;
max-height: 100%;
overflow: auto;
.userDeviceModal_item {
position: relative;
display: flex;
margin: 10px;
& > div:first-child {
position: relative;
flex: 1;
}
& > div:last-child {
position: relative;
flex-shrink: 0;
margin-left: 20px;
}
}
}
}
</style>

View File

@ -0,0 +1,504 @@
<script setup>
import dayjs from "dayjs";
defineOptions({
name: 'ProcessListForm',
})
import {computed, onMounted, ref, watch} from "vue";
import {useSystemStore} from "@/stores/system.js";
import {useWSStore} from "@/stores/ws.js";
import api from "@/api/index.js";
import {message} from "ant-design-vue";
const systemStore = useSystemStore();
const wsStore = useWSStore();
const props = defineProps({
processId: ''
})
//
const reportParam = ref({});
//
const reportParamData = ref([]);
//
const reportList = ref([]);
//
const serialNumber = ref(null);
// sort
const processSort = ref('');
//
const nowFocusProcessReport = ref(null);
//
const furnaceLotNumber = ref(null);
//
const getReportParam = async () => {
if(!Object.keys(systemStore.state.reportParam).includes(props.processId)){
const resd = await wsStore.wsMethod.getReportParam({
youtubeId: systemStore.state.productionBatch?.youtubeCodeId,
processId: props.processId
})
systemStore.addReportParam(props.processId, resd.data)
}
reportParam.value = systemStore.state.reportParam[props.processId]
const params = []
reportParam.value?.['报工参数TAB2'].forEach((value, index) => {
const data = {
parameterName: value.parameterName,
parameterType: value.parameterType,
parameterId: value.parameterId,
value: '',
maxLength: value.maxLength || 200,
maxLimit: value.maxLimit,
minLimit: value.minLimit,
}
if(value.parameterType == 2 && value.defaultNumber != null){
data.value = value.defaultNumber
}else if(value.parameterType == 1 && value.defaultText != null){
data.value = value.defaultText
}else if(value.parameterType == 3 || value.parameterType == 4){
try{
data.selectValue = JSON.parse(value.selectValue)
}catch (e) {
}
const selectData = data.selectValue.map(i => i.checked == 1 ? i.optionValue : '')
if(selectData){
data.value = selectData.filter(i => i != '')
}
}
params.push(data)
})
reportParamData.value = params;
}
//
const getReportList = async () => {
const wsd = await wsStore.wsMethod.getReportQueue({
processId: props.processId,
})
// ID
const wsdd = wsd.data.filter(item => item.youtubeId === systemStore.state.productionBatch?.youtubeCodeId)
reportList.value = wsdd
systemStore.handleChangeReportQueue(props.processId, wsdd)
}
// 线
const getWorklineSerial = async () => {
if(processSort.value !== 1){
return
}
const wsd = await wsStore.wsMethod.getWorklineSerial({worklineId: systemStore.state.deviceBinding.worklineValue});
const date = dayjs();
const year = date.year().toString().slice(-2)
const month = date.month()+1;
const workLineFlag = systemStore.state.deviceBinding.workLineFlag.toString().padStart(2, '0');
const number = wsd.data.serialNumber.toString().padStart(4, '0');
serialNumber.value = year + month + workLineFlag + number;
return wsd.data.serialNumber;
}
// *
const getFurnaceLotNumber = async () => {
const resd = await api.process.getFurnaceLotNumber(systemStore.state.productionBatch?.youtubeCode);
furnaceLotNumber.value = resd.data?.[0].furnaceLotNumber || null
}
//
const chooseReport = (reportProcessSort = null) => {
if(reportProcessSort == null){
nowFocusProcessReport.value = reportList.value.find(i => i.status === '0')
serialNumber.value = nowFocusProcessReport.value?.serialNumber || null
furnaceLotNumber.value = nowFocusProcessReport.value?.furnaceLotNumber || null
}else{
if(reportProcessSort.status === '0'){
nowFocusProcessReport.value = reportProcessSort
serialNumber.value = nowFocusProcessReport.value?.serialNumber || null
furnaceLotNumber.value = nowFocusProcessReport.value?.furnaceLotNumber || null
}
}
}
//
const addProcductionReport = async () => {
if(processSort.value === 1){
//
await getWorklineSerial();
// ID
await getFurnaceLotNumber();
}
if(!serialNumber.value || !furnaceLotNumber.value){
return message.warn('获取必要数据出现错误,无法报工!')
}
const otherUserList = []
systemStore.state.onlineUserList.map(i => {
if(i.username != systemStore.state.nowFocusUser.username){
i.defaultProcessList.find(j => {
if(j.processId == props.processId){
otherUserList.push({
userId: i.userDetails.user.userId,
nickName: i.nickName,
})
}
})
}
})
const data = {
processSort: processSort.value,
serialNumber: serialNumber.value,
processId: props.processId,
furnaceLotNumber: furnaceLotNumber.value,
processRouteId: systemStore.state.productionBatch.processRouteDetails.processRouteId,
worklineId: systemStore.state.deviceBinding.worklineValue,
youtubeId: systemStore.state.productionBatch?.youtubeCodeId,
youtubeCode: systemStore.state.productionBatch?.youtubeCode,
batchId: systemStore.state.productionBatch?.batchId,
reportShift: systemStore.state.productionBatch?.processingGroup,
//
isScrap: 1, // 1- 2-
//
singleOperationQualified: 1, // (1- 2-)
data : reportParamData.value.map(i => {
return {
parameterName: i.parameterName,
parameterType: i.parameterType,
parameterId: i.parameterId,
value: i.value
}
}),
reportTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
reportMainPersonId: systemStore.state.nowFocusUser.userDetails.user.userId,
reportMainPerson: systemStore.state.nowFocusUser.nickName,
reportSecPersonIds: otherUserList.map(i => i.userId),
reportSecPerson: otherUserList.map(i => i.nickName),
}
//
await wsStore.wsMethod.addProductionReport(data)
//
await getReportParam();
//
await getReportList();
//
chooseReport();
}
//
const getProcessSort = async () => {
systemStore.state.nowFocusUser?.defaultProcessList.find(i => {
if(i.processId === props.processId){
processSort.value = i.processSort
return true
}
})
}
//
watch(() => props.processId, async (newValue) => {
//
await getReportParam();
//
await getReportList();
//
getProcessSort();
//
chooseReport();
})
onMounted(async () => {
//
await getReportParam();
//
await getReportList();
//
getProcessSort()
//
chooseReport()
})
</script>
<template>
<div class="processListForm">
<main>
<header>
<div class="oneLine">
<div class="inputTitle">流水号</div>
<div class="inputText" v-if="processSort == 1">提交后生成</div>
<div class="inputText" v-else> {{ serialNumber }}</div>
</div>
<div class="oneLine">
<div class="inputTitle">炉批号</div>
<div class="inputText" v-if="processSort == 1">提交后生成</div>
<div class="inputText" v-else> {{ furnaceLotNumber }}</div>
</div>
</header>
<main>
<template v-for="(item, index) in reportParam?.['报工参数TAB2']">
<div class="reportParamContainer">
<div>{{item.parameterName}}</div>
<div>
<div v-if="item.parameterType == 1">
<a-input
v-model:value="reportParamData[index].value"
show-count :maxlength="reportParamData[index].maxLength"/></div>
<div v-if="item.parameterType == 2">
<a-input-number
style="width: 100%"
v-model:value="reportParamData[index].value"
:min="reportParamData[index].minLimit"
:max="reportParamData[index].maxLimit" /></div>
<div v-if="item.parameterType == 3">
<a-select
v-model:value="reportParamData[index].value"
style="width: 100%"
:field-names="{ label: 'optionValue', value: 'optionValue' }"
:options="reportParamData[index].selectValue"
/>
</div>
<div v-if="item.parameterType == 4">
<a-select
v-model:value="reportParamData[index].value"
mode="multiple"
style="width: 100%"
:field-names="{ label: 'optionValue', value: 'optionValue' }"
:options="reportParamData[index].selectValue"
/>
</div>
</div>
<div>{{item?.unit}}</div>
</div>
</template>
</main>
<footer>
<div>
<div class="left">
<div><n-button type="error" size="large">管材异常</n-button></div>
<div v-if="reportParam?.['是否允许报废'] == '是'"><n-button type="error" size="large">管材报废</n-button></div>
</div>
<div class="right">
<div><n-button
type="info"
size="large"
:disabled="!(processSort == 1 || (processSort != 1 && nowFocusProcessReport != null))"
@click="addProcductionReport">正常报工</n-button></div>
</div>
</div>
<div>
<template v-for="item in Object.keys(reportParam)">
<div v-if="typeof reportParam[item] == 'string'">{{item}} {{reportParam[item]}}</div>
</template>
</div>
</footer>
</main>
<div class="right">
<header>实时报工队列 {{reportList.length}}</header>
<main>
<div class="reportListItem" v-for="item in reportList" @click="chooseReport(item)">
<div :class="item.status == 10 ? 'past' : 'future' "></div>
<div :class="nowFocusProcessReport?.serialNumber == item.serialNumber ? 'reportListItemChoose reportListItemContainer' : 'reportListItemContainer'">
{{item.serialNumber}}
</div>
</div>
</main>
</div>
</div>
</template>
<style scoped lang="scss">
.processListForm{
position: relative;
height: 100%;
width: 100%;
display: flex;
overflow: hidden;
& > main{
position: relative;
flex: 1;
min-width: 400px;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
& > header{
position: relative;
flex-shrink: 0;
border-radius: 5px;
background: #ededed;
padding: 20px;
margin: 10px 0;
font-size: 24px;
color: #333;
}
& > main {
position: relative;
flex: 1;
overflow: auto;
& > div.reportParamContainer{
position: relative;
display: flex;
margin-bottom: 10px;
&:last-child{
margin-bottom: 0;
}
font-size: 18px;
& > div:first-child{
position: relative;
flex-shrink: 0;
width: 160px;
border-right: 1px solid #ededed;
display: flex;
align-items: center;
}
& > div:nth-child(2){
position: relative;
flex: 1;
margin: 0 20px;
}
& > div:last-child{
position: relative;
flex-shrink: 0;
width: 40px;
display: flex;
align-items: center;
}
}
}
& > footer{
position: relative;
& > div:first-child{
position: relative;
flex-shrink: 0;
display: flex;
& > div.left{
position: relative;
flex: 1;
display: flex;
& > div{
&:first-child{
margin: 0;
}
}
}
& > div.right{
position: relative;
flex: 1;
display: flex;
flex-direction: row-reverse;
& > div{
&:first-child{
margin: 0;
}
}
}
& > div{
& > div{
margin: 0 20px;
}
}
}
& > div:last-child{
position: relative;
display: flex;
margin-top: 10px;
& > div{
position: relative;
flex: 1;
color: #666;
}
}
}
div.oneLine{
position: relative;
display: flex;
margin-bottom: 20px;
&:last-child{
margin-bottom: 0;
}
}
div.inputTitle{
position: relative;
flex-shrink: 0;
}
div.inputText{
position: relative;
flex: 1;
}
}
& > div.right{
position: relative;
flex-shrink: 1;
width: 260px;
//height: 100%;
overflow: hidden;
border-left: 1px solid #cdcdcd;
padding-left: 20px;
margin-left: 20px;
margin-top: 10px;
display: flex;
flex-direction: column;
& > header{
position: relative;
flex-shrink: 0;
font-size: 1.1em;
font-weight: bold;
background: #ededed;
color: #333;
text-align: center;
line-height: 3em;
border-radius: 5px;
margin-bottom: 20px;
}
& > main{
position: relative;
flex: 1;
overflow: auto;
& > div.reportListItem{
position: relative;
display: flex;
margin: 10px 0;
cursor: pointer;
border-radius: 5px;
border: 1px solid #cdcdcd;
overflow: hidden;
&:first-child{
margin-top: 0;
}
&:last-child{
margin-bottom: 0;
}
& > div:first-child{
position: relative;
flex-shrink: 0;
width: 5px;
}
& > div.past{
background: #cdcdcd;
}
& > div.future{
background: #2684fe;
}
& > div.reportListItemContainer{
padding: 5px 0;
position: relative;
flex: 1;
font-weight: bold;
text-align: center;
white-space: nowrap; /* 保持文本在一行显示 */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 超出部分显示省略号 */
}
& > div.reportListItemChoose{
background: #2684fe;
font-weight: bold;
color: #fefefe;
}
}
}
}
}
</style>

View File

@ -0,0 +1,91 @@
<script setup>
import ProcessListForm from "@/views/NewspaperWorker/ProcessListForm.vue";
defineOptions({
name: 'NewspaperWorker',
})
import {nextTick, onMounted, ref, watch} from "vue";
import {useSystemStore} from "@/stores/system.js";
const systemStore = useSystemStore();
//
const activeProcessListId = ref('');
//
const ntab = ref(null);
//
const nowUserQueueCanSubmitNum = ref({});
//
const setUserQueue = () => {
const obj = {}
systemStore.state.nowFocusUser?.defaultProcessList.forEach(item => {
const dd = systemStore.state.reportQueue[item.processId]
.filter(i => i.youtubeId == systemStore.state.productionBatch.youtubeCodeId)
.filter(i => i.status == '0');
obj[item.processId] = dd.length
})
nowUserQueueCanSubmitNum.value = obj;
}
watch(() => systemStore.state.nowFocusUser, () => {
activeProcessListId.value = systemStore.state.nowFocusUser?.defaultProcessList[0].processId;
setUserQueue()
nextTick(() => ntab.value?.syncBarPosition())
})
watch(() => systemStore.state.reportQueueChange, () => {
setUserQueue()
})
onMounted(() => {
activeProcessListId.value = systemStore.state.nowFocusUser?.defaultProcessList[0].processId;
setUserQueue()
// console.log(systemStore.state.nowFocusUser);
// console.log(systemStore.state.onlineUserList)
})
</script>
<template>
<div class="newspaperWorker">
<n-tabs class="ntabs" type="card" size="large" ref="ntab" v-model:value="activeProcessListId" animated tab-style="padding-right: 30px">
<n-tab
v-for="processList in systemStore.state.nowFocusUser?.defaultProcessList"
:name="processList.processId"
:tab="processList.processName"
:key="processList.processId">
<template #default>
<n-badge
:value="nowUserQueueCanSubmitNum[processList.processId]"
:max="99"
type="error"
:offset="[15, 0]">
{{ processList.processName }}
</n-badge>
</template>
</n-tab>
</n-tabs>
<ProcessListForm v-if="activeProcessListId" class="processListForm" :processId="activeProcessListId"/>
</div>
</template>
<style scoped lang="scss">
.newspaperWorker{
position: relative;
height: 100%;
width: 100%;
overflow: hidden;
box-sizing: border-box;
display: flex;
flex-direction: column;
& > .ntabs{
position: relative;
flex-shrink: 0;
}
& > .processListForm{
position: relative;
flex: 1;
overflow: hidden;
}
}
</style>

View File

@ -0,0 +1,475 @@
<script setup>
import {onMounted, ref} from "vue";
import {useWSStore} from "@/stores/ws.js";
import {useSystemStore} from "@/stores/system.js";
import userSvg from '@/assets/icon/user.svg'
import trueSvg from '@/assets/icon/true.svg'
import falseSvg from '@/assets/icon/false.svg'
import reduceSvg from '@/assets/icon/reduce.svg'
import SimpleFrameWork from "@/components/SimpleFrameWork/index.vue";
import {message} from "ant-design-vue";
import {useRouter} from "vue-router";
const wsStore = useWSStore();
const systemStore = useSystemStore();
const router = useRouter();
defineOptions({
name: 'ProductionBatch',
});
//
const productionBatchInfo = ref({})
// PAD
const processList = ref([])
// /
const defaultProcessList = ref([])
//
const getProductionBatch = async () => {
const resd = await wsStore.wsMethod.getProductionBatch()
productionBatchInfo.value = {
...resd.data
}
systemStore.setProductionBatch(resd.data)
return resd.data
}
// pad
const getProductionRouteByRouteIdAndIpcId = async (processRouteId) => {
if(processRouteId === undefined) {
return
}
const resd = await wsStore.wsMethod.getProductionRouteByRouteIdAndIpcId({
processRouteId
})
processList.value = resd.data
// console.log(resd)
}
//
const handleUnchooseProcessList = data => {
const index = defaultProcessList.value.findIndex(i => i.processId == data.processId)
// console.log(index)
defaultProcessList.value.splice(index, 1)
}
//
const handleChooseProcessList = data => {
if(defaultProcessList.value.map(i => i.processId).includes(data.processId)){
//
handleUnchooseProcessList(data)
}else{
defaultProcessList.value.push(data)
}
}
//
const getReportList = async (processId) => {
const wsd = await wsStore.wsMethod.getReportQueue({
processId,
})
// ID
return wsd.data.filter(item => item.youtubeId === systemStore.state.productionBatch?.youtubeCodeId)
}
//
const handleConfirmProcessList = async () => {
if(defaultProcessList.value.length === 0){
return message.warn('请选择工序!')
}
systemStore.setUserProcessList(defaultProcessList.value)
await wsStore.wsMethod.setUserDefaultProcessList({
username: systemStore.state.nowFocusUser.username,
defaultProcessList: defaultProcessList.value
})
for (let i of defaultProcessList.value){
const wsd = await getReportList(i.processId)
systemStore.handleChangeReportQueue(i.processId, wsd)
}
router.push('/home/newspaperWorker')
}
onMounted(() => {
wsStore.onWsConnect(async () => {
//
const productionBatch = await getProductionBatch()
// pad
await getProductionRouteByRouteIdAndIpcId(productionBatch.processRouteDetails?.processRouteId)
//
// console.log(systemStore.state.nowFocusUser)
if(systemStore.state.nowFocusUser.defaultProcessList){
// console.log('AA')
systemStore.state.nowFocusUser.defaultProcessList.forEach(item => {
defaultProcessList.value.push(item)
})
}
})
// console.log(systemStore.state)
})
</script>
<template>
<SimpleFrameWork>
<div class="productionBatch">
<div class="left">
<header>
<div>
<img :src="userSvg" alt="">
</div>
<div>{{ systemStore?.state.nowFocusUser.nickName }}</div>
</header>
<main>
<div class="processList" v-for="item in defaultProcessList">
<div></div>
<div>{{item.processName}}</div>
<div @click="handleUnchooseProcessList(item)"><img :src="reduceSvg" alt=""></div>
</div>
</main>
<footer>
<a-button type="primary" @click="handleConfirmProcessList" style="width: 100%">确定</a-button>
</footer>
</div>
<main>
<header>
<div class="productionBatchInfo_title">
<div>{{productionBatchInfo?.batchName}}</div>
</div>
<div class="productionBatchInfo_container">
<div class="firstLine">
<div class="productionBatchInfoBox">
<div>生产批号</div>
<div>{{productionBatchInfo?.batchNumber}}</div>
</div>
<div class="productionBatchInfoBox">
<div>原料批号</div>
<div>{{productionBatchInfo?.rawMaterialsLotNumber}}</div>
<div>{{productionBatchInfo?.specification}}</div>
<div>{{productionBatchInfo?.steelGrade}}</div>
<div>{{productionBatchInfo?.materialsFactory}}</div>
</div>
</div>
<div class="secondLine">
<div class="productionBatchInfoBox">
<div>车间</div>
<div>{{systemStore.state.deviceBinding?.workshopName}}</div>
</div>
<div class="productionBatchInfoBox">
<div>产线</div>
<div>{{systemStore.state.deviceBinding?.worklineName}}</div>
</div>
<div class="productionBatchInfoBox">
<div>班次</div>
<div>{{productionBatchInfo?.batchName}}</div>
</div>
<div class="productionBatchInfoBox">
<div>班组负责人</div>
<div>{{productionBatchInfo?.batchName}}</div>
</div>
</div>
</div>
</header>
<main>
<div class="defaultProductionRoute">
<div class="title">默认工序</div>
<div class="processList">
<div class="processList_content" v-for="item in defaultProcessList" @click="handleUnchooseProcessList(item)">
<div class="processList_content_subscript">
<img :src="falseSvg" alt="">
</div>
<div>
<div>{{item.processName }}</div>
<div>{{ item.processNumber}}</div>
</div>
</div>
</div>
</div>
<div class="allProductionRoute">
<div class="title">全部工序</div>
<div class="processList">
<div class="processList_content" v-for="item in processList" @click="handleChooseProcessList(item)">
<div class="processList_content_subscript" v-if="defaultProcessList.map(i => i.processId).includes(item.processId)">
<img :src="trueSvg" alt="">
</div>
<div>
<div>{{item.processName }}</div>
<div>{{ item.processNumber}}</div>
</div>
</div>
</div>
</div>
</main>
</main>
</div>
</SimpleFrameWork>
</template>
<style scoped lang="scss">
.productionBatch{
position: relative;
display: flex;
height: 100%;
width: 100%;
overflow: hidden;
& > div.left{
position: relative;
flex-shrink: 0;
min-width: 300px;
display: flex;
background: #fefefe;
flex-direction: column;
margin-right: 20px;
padding: 10px 10px;
border-radius: 10px;
& > header {
position: relative;
flex-shrink: 0;
display: flex;
align-items: center;
border-bottom: 3px solid #bebebe;
padding: 10px 0;
& > div:first-child{
margin-right: 30px;
& > img{
width: 3em;
margin: 0.5em 0;
}
}
& > div:last-child{
font-size: 1.5em;
flex: 1;
font-weight: bold;
}
}
& > main{
position: relative;
flex: 1;
margin: 20px 0 20px 0;
margin: 10px 0;
padding: 0 10px;
overflow: auto;
& > div.processList{
position: relative;
display: flex;
border-radius: 10px;
background: #f4f9ff;
padding: 10px;
margin-bottom: 10px;
& > div:first-child{
position: relative;
flex-shrink: 0;
width: 40px;
&:after{
display: block;
content: '';
position: relative;
width: 1em;
height: 1em;
background: #2993ff;
border-radius: 1em;
}
}
& > div:nth-child(2){
position: relative;
flex: 1;
//white-space: nowrap; /* */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 超出部分显示省略号 */
white-space: normal; /* 允许文本换行 */
}
& > div:last-child{
position: relative;
flex-shrink: 0;
width: 40px;
cursor: pointer;
text-align: right;
& > img{
width: 1em;
}
}
}
}
& > footer{
position: relative;
flex-shrink: 0;
text-align: center;
border-top: 3px solid #bebebe;
padding: 10px 0;
}
}
& > main{
position: relative;
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
overflow: hidden;
color: #4f4f4f;
& > header{
position: relative;
flex-shrink: 0;
background: #eef3f9;
margin-bottom: 10px;
border-radius: 10px;
display: flex;
padding: 20px;
height: 100px;
& > div.productionBatchInfo_title{
position: relative;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
width: 150px;
font-weight: bold;
margin-right: 10px;
& > div{
position: relative;
height: 3em;
line-height: 1.5em;
//white-space: nowrap; /* */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 超出部分显示省略号 */
white-space: normal; /* 允许文本换行 */
}
}
& > div.productionBatchInfo_container{
position: relative;
flex: 1;
align-content: center;
& > div.firstLine{
position: relative;
display: flex;
margin-bottom: 10px;
& > div.productionBatchInfoBox{
position: relative;
display: flex;
margin-right: 10px;
& > div:first-child{
color: #808080;
flex-shrink: 0;
}
& > div:last-child{
flex: 1;
font-weight: bold;
white-space: nowrap; /* 保持文本在一行显示 */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 超出部分显示省略号 */
}
& > div{
margin-right: 10px;
}
}
}
& > div.secondLine{
position: relative;
display: flex;
& > div.productionBatchInfoBox{
position: relative;
display: flex;
flex: 1;
& > div:first-child{
color: #808080;
flex-shrink: 0;
}
& > div:last-child{
position: relative;
max-width: 150px;
flex: 1;
font-weight: bold;
white-space: nowrap; /* 保持文本在一行显示 */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 超出部分显示省略号 */
}
}
}
}
}
& > main{
position: relative;
padding: 20px;
flex: 1;
background: #eef3f9;
border-radius: 10px;
display: flex;
flex-direction: column;
color: #333333;
overflow: hidden;
& > div{
position: relative;
flex: 1 0 auto;
display: flex;
flex-direction: column;
width: 100%;
overflow: hidden;
& > div.title{
position: relative;
font-size: 1.2em;
font-weight: bold;
flex-shrink: 0;
}
& > div.processList{
position: relative;
display: flex;
flex:1;
align-items: center;
box-sizing: border-box;
padding: 20px 0;
overflow: auto;
width: 100%;
& > div.processList_content{
position: relative;
margin-right: 20px;
height: 70%;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
background: #d8e9f9;
aspect-ratio: 1/1;
cursor: pointer;
& > div.processList_content_subscript{
position: absolute;
right: -25px;
top: -25px;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
& > img{
width: 1.8em;
}
}
& > div{
position: relative;
text-align: center;
& > div:first-child{
font-size: 1.2em;
line-height: 1.5em;
}
& > div:nth-child(2){
color: #888;
}
& > div:nth-child(3){
color: #888;
}
}
}
}
}
& > div.defaultProductionRoute{
flex: 1;
}
& > div.allProductionRoute{
margin-top: 10px;
flex: 1;
div.processList_content{
background: #fefefe !important;
}
}
}
}
}
</style>

38
vite.config.js Normal file
View File

@ -0,0 +1,38 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
// import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig((command, mode) => {
const env = loadEnv(mode, process.cwd());
return {
build: {
// 设置输出目录
outDir: 'out-render',
// 如果你想要在outDir外层再嵌套一层子目录可以通过assetsDir来实现
assetsDir: 'assets',
},
plugins: [
vue(),
// vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src/render', import.meta.url))
},
},
server: {
host: "0.0.0.0",
port: 17129,
proxy: {
[env.VITE_BASE_URL]: {
target: env.VITE_HTTP_PROXY,
changeOrigin: true,
rewrite: (path) => path.replace(new RegExp(env.VITE_BASE_URL), ''),
}
}
}
}
})