init: CATL电池线PAD Web首次入库

This commit is contained in:
XingCheng3
2026-06-08 17:11:45 +08:00
commit 414f4aadab
43 changed files with 9669 additions and 0 deletions

2
.env.development Normal file
View File

@@ -0,0 +1,2 @@
ENV = 'development'
VITE_BASE_URL = 'http://localhost:10081'

2
.env.production Normal file
View File

@@ -0,0 +1,2 @@
ENV = 'production'
VITE_BASE_URL = 'http://127.0.0.1:10081'

70
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,70 @@
const { defineConfig } = require('eslint-define-config')
module.exports = defineConfig({
root: true,
env: {
browser: true,
node: true,
es6: true
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true
}
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended'
],
rules: {
'vue/no-setup-props-destructure': 'off',
'vue/script-setup-uses-vars': 'error',
'vue/no-reserved-component-names': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'vue/custom-event-name-casing': 'off',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'no-unused-vars': 'off',
'space-before-function-paren': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always'
},
svg: 'always',
math: 'always'
}
],
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'off',
'vue/require-toggle-inside-transition': 'off'
}
})

27
.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
*.zip
*.rar
*.7z
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

16
index.html Normal file
View File

@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link href="/vite.svg" rel="icon" type="image/svg+xml"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>AMES工位PAD</title>
<script type="text/javascript">
document.write("<script src='./public/config.js?v=" + new Date().getTime() + "'><\/script>");
</script>
</head>
<body>
<div id="app"></div>
<script src="/src/main.js" type="module"></script>
</body>
</html>

5802
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

36
package.json Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "zzbz-pad",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.7.7",
"bcryptjs": "^3.0.2",
"crypto-js": "^4.2.0",
"element-plus": "^2.8.6",
"js-cookie": "^3.0.5",
"mqtt": "^5.10.4",
"pinia": "^2.2.5",
"rollup-plugin-copy": "^3.5.0",
"tdesign-vue-next": "^1.10.3",
"vant": "^3.6.16",
"vue": "^3.5.12",
"vue-cookies": "^1.8.4",
"vue-router": "^4.4.5"
},
"devDependencies": {
"@vant/auto-import-resolver": "^1.3.0",
"@vitejs/plugin-vue": "^5.1.4",
"sass": "^1.80.5",
"unplugin-auto-import": "^19.1.1",
"unplugin-vue-components": "^28.4.1",
"vite": "^5.4.10",
"vite-plugin-style-import": "^1.4.1"
}
}

19
prettier.config.js Normal file
View File

@@ -0,0 +1,19 @@
export default {
printWidth: 100, // 一行最多多少字符
tabWidth: 2, // 缩进的宽度2 个空格
useTabs: false, // 是否使用制表符,不使用制表符,使用空格
semi: false, // 语句结尾是否使用分号
bracketSpacing: true, // 大括号 {} 中开始和结束是否要空格true — { foo: 1 }false — {foo: 1}
trailingComma: 'none', // 数组或对象或参数的最后一项是否尾随逗号none — 没有尾随逗号all — 尽可能使用尾随逗号es5 — 在 ES5 中有效的尾随逗号对象、数组等TypeScript 和 Flow 类型参数中的尾随逗号。
arrowParens: 'always', // 只有一个参数的箭头函数是否带括号always — 始终带括号avoid — 不带括号
proseWrap: 'always', // 什么对代码进行折行always — 如果超过 printWidth 指定的一行最多字符宽度则进行折行never — 将每块代码块展开成一行preserve — 什么都不做,保持原样。
htmlWhitespaceSensitivity: 'strict', // 根据显示样式决定 html 要不要折行
endOfLine: 'auto', // 每行的结束符(回车符、换行符),取值请参考 https://www.prettier.cn/docs/options.html#end-of-line
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的@prettier
requirePragma: false,
// 不需要自动在文件开头插入@prettier
insertPragma: false,
}

BIN
public/TopLogo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/TopLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

5
public/config.js Normal file
View File

@@ -0,0 +1,5 @@
window.g = {
// API_MES_URL: 'http://127.0.0.1:9981',
API_WEB_URL: 'http://192.168.10.3:10000',
Mqtt_Ip: "ws://127.0.0.1:8083/mqtt",
};

BIN
public/login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

1
public/vite.svg Normal file
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

70
src/App.vue Normal file
View File

@@ -0,0 +1,70 @@
<template>
<t-loading :loading="isLoading" size="large" text="加载中(大)...">
<router-view />
</t-loading>
</template>
<script setup>
import { useAppStore } from "@/store/app.js";
import { storeToRefs } from "pinia";
const store = useAppStore();
// storeToRefs 响应式解构赋值 pinia
const { isLoading } = storeToRefs(store);
</script>
<style lang="scss">
html {
font-size: clamp(14px, 4vw, 18px);
/* 根据视口动态调整 */
font-size: 1rem;
}
// 定义的全局变量
body {
margin: 0px;
}
/*修改提示框样式 更醒目*/
.el-message-box {
// 标题
.el-message-box__title {
font-size: 35px;
font-weight: 700;
color: rgb(255, 143, 0);
}
// 提示文本
.el-message-box__message p {
font-size: 24px;
line-height: 40px;
}
// icon图标
.el-message-box__status {
font-size: 40px;
}
.el-message-box__btns {
justify-content: space-evenly;
.el-button {
width: 48%;
height: 6vh;
font-size: 1.6rem;
}
}
}
// 页面主题卡片
.card {
padding: .5rem;
background-color: #fff;
border-radius: 6px;
box-shadow: 0 8px 12px #ebedf0;
margin-top: .5rem;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1,2 @@
/* src/assets/styles/element-variables.scss */
@forward 'element-plus/theme-chalk/src/mixins/config.scss' with ($--font-size-base: 1rem);

View File

@@ -0,0 +1,2 @@
/* src/assets/styles/element-variables.scss */
@forward 'element-plus/theme-chalk/src/mixins/config.scss' with ($--font-size-base: 1rem);

View File

@@ -0,0 +1,50 @@
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
<el-button type="primary">21321312</el-button>
<t-button disabled loading>确定</t-button>
<t-button variant="dashed" ghost>幽灵按钮</t-button>
<icon name="cps-icon-home-sheep" url="https://tdesign.gtimg.com/icon/default-demo/index.js" size="2em" />
123
</template>
<script setup>
import { ref } from 'vue'
import { Icon } from 'tdesign-icons-vue-next';
defineProps({
msg: String,
})
const count = ref(0)
</script>
<style lang="scss" scoped>
.read-the-docs {
color: #888;
}
</style>

54
src/main.js Normal file
View File

@@ -0,0 +1,54 @@
import {createApp} from 'vue'
import App from './App.vue'
//ElementPlus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// TDesign
import TDesign from 'tdesign-vue-next'
// vant
import Vant from 'vant';
// 引入组件库的少量全局样式变量
import 'tdesign-vue-next/es/style/index.css'
// pinia
import {createPinia} from 'pinia'
//router
import router from './router/index.js'
//crud
import curdVue3 from './utils/curdVue3'
// el-icon
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
// 自定义全局样式
import './assets/styles/global.css' // 引入全局样式文件
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 提供全局方法
app.use(ElementPlus, {
locale: zhCn,
})
app.use(TDesign)
const pinia = createPinia() // 创建 Pinia 实例
app.use(pinia)
app.use(curdVue3)
app.use(router)
app.use(Vant);
app.provide('CreateData', app.config.globalProperties.CreateData)
app.provide('ExecDatabase', app.config.globalProperties.ExecDatabase)
app.provide('ExecDatabaseByParam', app.config.globalProperties.ExecDatabaseByParam)
app.provide('ExecWritePLC', app.config.globalProperties.ExecWritePLC)
app.provide('ExecReadPLC', app.config.globalProperties.ExecReadPLC)
app.provide('ExecIMESAPI', app.config.globalProperties.ExecIMESAPI)
app.mount('#app')
// useRouteGuard(); // 使用路由守卫

155
src/router/index.js Normal file
View File

@@ -0,0 +1,155 @@
import { createRouter, createWebHashHistory } from "vue-router";
import Cookies from 'js-cookie'
// cookies
// 创建routes
const routes = [
{
// 重定向
path: "/home",
redirect: "/",
},
{
//前台首页
path: "/",
name: "root",
component: () => import("@/views/Layout/index.vue"),
children: [
{
path: '/',
name: 'OpStatus',
component: () => import("@/views/OpStatus/index.vue")
}
]
},
{
//前台首页
path: "/login",
component: () => import("@/views/Login/index.vue"),
},
// { // 404有毛病 目前 初始化动态路由有问题 404暂改为重定向到首页
// // 404
// path: "/:pathMatch(.*)*",
// component: () => import("@/views/404.vue"),
// },
{
// 404
path: "/:pathMatch(.*)*",
redirect: "/",
},
];
//History路由模型
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes,
});
//这里设置路由拦截
router.beforeEach(async (to, from, next) => {
// 设置游览器标题
if (to.meta.title) {
window.document.title = to.meta.title;
}
// 放行
// next();
if (to.path === '/login' || to.path === '/') {
next();
return;
} else {
const AID = Cookies.get('AID');
if (!AID) {
next('/');
return;
} else {
next();
return;
}
}
});
// 递归替换引入component
function dynamicRouter(routerList) {
const list = [];
routerList.forEach((route) => {
const routeItem = {
path: '/' + route.path,
name: route.path,
meta: {
title: route.权限模块,
},
component: async () => {
try {
const component = await import(`@/views/${route.path}/index.vue`);
console.log(component)
return component.default || component;
} catch (error) {
console.error('引入路由组件失败,请确认地址是否配置正确:', error);
return null;
}
},
};
list.push(routeItem);
});
return list;
}
// 动态添加路由的函数
export async function addDynamicRoutes(routerList) {
try {
// routerList.forEach(route => {
// console.log(123, route)
// // {权限模块:车体到位,path:CarBodyPlace}
// router.addRoute('root', {
// path: '/' + route.path,
// name:route.path,
// meta: {
// title: route.权限模块,
// },
// component: () => import(`@/views/${route.path}/index.vue`),
// });
// });
// router.addRoute('root',{
// children: dynamicRouter(routerList)
// })
// let userId = Cookies.get('userId')
// if(userId !== ''){
// // 获取权限列表
// var param = []
// param[0] = ['userId', userId]
// var Data = CreateData('11', '基础表_权限模块_查询', param)
// ExecDatabase(Data).then(response => {
// if(response.data.length > 0){
// routerData.value.userRouterList = response.data
// // 创建动态路由列表
// const dynamicRoutes = dynamicRouter(response.data);
//
// dynamicRoutes.forEach((route) => {
// // 检查是否已存在该路由,避免重复添加
// if (!router.hasRoute(route.name)) {
// router.addRoute('root', route);
// }
// });
// }else{
// ElMessage.warning('该角色未分配角色模块,请先进行分配!')
// }
// })
// 创建动态路由列表
const dynamicRoutes = dynamicRouter(routerList);
dynamicRoutes.forEach((route) => {
// 检查是否已存在该路由,避免重复添加
if (!router.hasRoute(route.name)) {
router.addRoute('root', route);
}
});
} catch (error) {
console.error('动态添加路由失败:', error);
}
}
export default router;

View File

@@ -0,0 +1,76 @@
import { inject } from 'vue';
import { useRouter } from 'vue-router';
import {ElMessage} from "element-plus";
import Cookies from "vue-cookies";
export function useRouteGuard() {
const router = useRouter();
const CreateData = inject('CreateData');
const ExecDatabase = inject('ExecDatabase');
let userId = Cookies.get('userId')
// 防止首次或者刷新界面路由失效
let registerRouteFresh = true
router.beforeEach(async (to, from, next) => {
if (registerRouteFresh) {
if(userId !== ''){
// 获取权限列表
var param = []
param[0] = ['userId', userId]
var Data = CreateData('11', '基础表_权限模块_查询', param)
ExecDatabase(Data).then(response => {
if(response.data.length > 0){
routerData.value.userRouterList = response.data
// 创建动态路由列表
const dynamicRoutes = dynamicRouter(response.data);
dynamicRoutes.forEach((route) => {
// 检查是否已存在该路由,避免重复添加
if (!router.hasRoute(route.name)) {
router.addRoute('root', route);
}
});
}else{
ElMessage.warning('该角色未分配角色模块,请先进行分配!')
}
next({ ...to, replace: true })
registerRouteFresh = false
})
}else{
next({ ...to, replace: true })
registerRouteFresh = false
}
} else {
if (to.meta.title) {
window.document.title = to.meta.title;
}
next()
}
})
// 递归替换引入component
function dynamicRouter(routerList) {
const list = [];
routerList.forEach((route) => {
const routeItem = {
path: '/' + route.path,
name:route.path,
meta: {
title: route.权限模块,
},
component: async () => {
try {
const component = await import(`@/views/${route.path}/index.vue`);
console.log(component)
return component.default || component;
} catch (error) {
console.error('引入路由组件失败,请确认地址是否配置正确:', error);
return null;
}
},
};
list.push(routeItem);
});
return list;
}
}

29
src/store/app.js Normal file
View File

@@ -0,0 +1,29 @@
import {defineStore} from 'pinia'
// 第一个参数是应用程序中 store 的唯一 id
export const useAppStore = defineStore('AppData', {
// 其它配置项
state: () => {
return {
user: {
token: '',
AID: '',
name: '',
classes: '',
teamGroup: '',
role: '',
userName: '',
password: '',
opName: '',
},
isLoading: false,
routerData: {
userRouterList: [],
nowRouterPath: '',
nowRouterTitle: '',
selected: false,
},
};
},
})

32
src/utils/AES-Pkcs7.js Normal file
View File

@@ -0,0 +1,32 @@
import CryptoJS from 'crypto-js'
import bcrypt from 'bcryptjs'
export const encrypt = function(word, keyStr) {
keyStr = keyStr || '1994+=--jkl;P,fj'
const key = CryptoJS.enc.Utf8.parse(keyStr)
const srcs = CryptoJS.enc.Utf8.parse(word)
const encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: key,
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
})
return encrypted.toString()
}
export const md5 = function(word) {
return CryptoJS.MD5(word).toString()
}
/**
* AES解密
* @param {any} word 解密数据
*/
export const cryptoDecrypt = (word, keyStr) => {
const secretKey = CryptoJS.enc.Utf8.parse(keyStr)
const ivParsed = CryptoJS.enc.Utf8.parse(keyStr)
const decrypt = CryptoJS.AES.decrypt(word, secretKey, { iv: ivParsed, mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 })
const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8)
return decryptedStr
}
export const generateSalt = () => {
return bcrypt.genSaltSync(10)
}

15
src/utils/auth.js Normal file
View File

@@ -0,0 +1,15 @@
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}

435
src/utils/curd.js Normal file
View File

@@ -0,0 +1,435 @@
import { ExcelDownLoad } from '@/utils/request'
import request from '@/utils/request'
import Cookie from 'js-cookie'
import { type } from './tool' // type判断传入值的类型比typeof更详细
export default {
exportExcel_NPOI(param) {
console.log(param, 1)
download(`${ExcelDownLoad}?param=${param}`)
},
// 用于生成传通讯服务器参数。
// 传入参数
// 1type11查询12增删改必须
// 2name存储过程名必须
// 3data存储过程参数名和对应值例如
// param[0] = ['工艺计划流水号', this.工艺计划流水号] 输入参数
// param[1] = ['PageCurrent', '1111', 'int', '1'] output参数
// 数组里四个分别对应存储过程参数名必须存储过程参数值必须参数类型若是output必须参数是否为output0否1是
// 4pageSizepageList分页使用可选
CreateData(type, name, data, pageSize, pageList) {
var param_Str = ''
var param_array = []
if (type === '7') {
data.map(v => {
pageSize.map(h => {
const val = h.toString()
if (!v[val]) {
this.$set(v, val, '')
}
})
})
param_Str = data
} else {
if (data !== undefined) {
for (let i = 0; i < data.length; i++) {
param_array.push({
name: data[i][0],
value: data[i][1],
type: data[i][2],
output: data[i][3]
})
}
param_Str = JSON.stringify(param_array)
}
}
var obj = []
obj[0] = {}
obj[0].type = type
obj[0].name = name
obj[0].param = param_Str
obj[0].pageSize = pageSize
obj[0].pageList = pageList
obj[0].UserID = Cookie.get('username')
obj[0].ModularID = ''//this.$router.currentRoute.path
console.log(obj[0], 'obj[0]')
var numn = JSON.stringify(obj[0])
return numn
},
// 新通讯11/12---<
ExecDatabase(data) {
return request({
url: '',
method: 'post',
data: data
})
},
// 高精原CreateData
// Vue.prototype.CreateData = function(type, name, data, pageSize, pageList) {
// var paramStr = ''
// var paramarray = []
// if (data !== undefined) {
// for (let i = 0; i < data.length; i++) {
// paramarray.push({
// name: data[i][0],
// value: data[i][1],
// type: data[i][2],
// output: data[i][3]
// })
// }
// paramStr = JSON.stringify(paramarray)
// }
// var obj = []
// obj[0] = {}
// obj[0].type = type
// obj[0].name = name
// obj[0].param = paramStr
// obj[0].pageSize = pageSize
// obj[0].pageList = pageList
// var numn = JSON.stringify(obj[0])
// return numn
// }
// 用于打开增加框,方法能重置表单,并使表单内容回到初始化状态。
// 传入参数
// 1表单名称字符串形式如'form',必须
// 2) 打开表单后需要做什么,可以传数组[this.dialogvisiale = false fetchData],也可以传回调函数function() { this.dialogvisiale = false } 可选
addForm(form, callback) {
if (type(form) === 'string') {
if (this.$refs[form]) { // 判断是否需要重置表单,如果需要重置表单
this.$refs[form].resetFields()
}
if (type(callback) === 'array') { // 判断传入是否为数组,如果是遍历数组,遇到函数将函数体变成函数执行,其余正常执行
if (callback && callback.length !== 0) {
for (let i = 0, len = callback.length; i < len; i++) {
typeof callback[i] === 'function' ? callback[i]() : callback[i]
}
}
} else if (type(callback) === 'function') { // 判断传入是否为回调函数
callback.call(this) // 回调函数执行需将this改为该组件
}
this[form] = Object.assign(this.$data[form], this.$options.data()[form]) // 将表单中的数据变为初始状态。其中,this.$options.data是所有初始化的数据this.$data是目前的数据
} else {
console.error('传入参数错误')
}
},
// 用于打开编辑框,方法能自动传入行的数据到对应的表单中,并将表单中所有纯数字转成int。前提是行的名称需跟表单名称一样。
// 传入参数
// 1) 编辑这一行的row
// 2表单名称字符串形式如'form'
// 3) 打开编辑需要做什么,同样是[] 和 function
editForm(row, form, callback) {
if (type(row) === 'object' && type(form) === 'string') {
if (this.$refs[form]) { // 判断是否需要重置表单
this.$refs[form].resetFields()
}
const isNum = /^[0-9]+$/ // 正则表达式,是否全都为纯数字
for (const prop in this[form]) { // 遍历表单当表单和row中属性名一样时表单row中的属性赋值给form
isNum.lastIndex = 0 // 正则每一次匹配后的索引归0否则循环会造成问题
isNum.exec(row[prop]) ? this[form][prop] = parseInt(row[prop]) : this[form][prop] = row[prop] // 通过正则匹配数字当匹配成功后将字符串转为int。当然你也不用担心空转成NaN,因为''匹配通不过!
}
if (type(callback) === 'array') { // 这部分处理与打开表单相同
if (callback && callback.length !== 0) {
for (let i = 0, len = callback.length; i < len; i++) {
typeof callback[i] === 'function' ? callback[i]() : callback[i]
}
}
} else if (type(callback) === 'function') {
callback.call(this)
}
} else {
console.error('传入参数错误')
}
},
// 用于获取下拉框中的数据
// 传入参数
// 1请求数据函数引用如getXXX必须
// 2数组的实行名称['XXXX名称', 'XXXX代码'],必须
// 3需要用哪个数组接收这个结果字符串形式。如在组件也需要用this.select,传入值就为'select',必须
// 4) 请求函数传入的参数,可选
getSelect(requestData, select, carrier, param) {
if (type(requestData) === 'function' && type(select) === 'array' && type(carrier) === 'string') {
if (type(param) === 'array') {
requestData(...param).then(response => { // 这个方法没什么难度,不注释了
this[carrier] = []
for (let i = 0, len = response.data.length; i < len; i++) {
if (select.length === 2) {
this[carrier].push({
label: response.data[i][select[0]],
value: response.data[i][select[1]]
})
} else {
this[carrier].push({
value: response.data[i][select[0]]
})
}
}
})
} else {
requestData(param).then(response => { // 这个方法没什么难度,不注释了
this[carrier] = []
for (let i = 0, len = response.data.length; i < len; i++) {
if (select.length === 2) {
this[carrier].push({
label: response.data[i][select[0]],
value: response.data[i][select[1]]
})
} else {
this[carrier].push({
value: response.data[i][select[0]]
})
}
}
})
}
} else {
console.error('参数传递错误')
}
},
// 普通获取数据
// 传入参数
// 1) 请求数据函数引用如getXXX必须
// 2) 需要用哪个数组接收这个结果字符串形式。如在组件也需要用this.select,传入值就为'select',必须
// 3) 请求函数传入的参数,可选
getData(requestData, carrier, param) {
if (type(requestData) === 'function' && type(carrier) === 'string') {
if (type(param) === 'undefined') {
requestData(param).then(response => {
const data = response.data
this[carrier] = data
})
} else {
let Param = []
if (type(param) !== 'array') {
Param.push(param)
} else {
Param = param
}
requestData(...Param).then(response => {
const data = response.data
this[carrier] = data
})
}
} else {
console.error('传入参数错误')
}
},
// 获取表格数据,将表格中的年月日时分秒,转换成年月日
// 传入参数
// 1请求数据函数引用如getXXX必须
// 2请求的参数可选
// 3接受值数组一般是['table','total','loading']顺序不能变
getTable(requestData, param, e) {
requestData(...param).then(response => {
const data = response.data.rows
this[e[0]] = []
this[e[1]] = 0
if (data && data.length > 0) {
const isTime = /^\d{4}([-\/.])\d{1,2}\1\d{1,2}/ // 判断是否是时间的正则表达式
for (let i = 0, len = data.length; i < len; i++) { // 循环得到每一条数据
for (const prop in data[i]) { // 循环每一条的每一项
isTime.lastIndex = 0 // 正则每一次指针复位
if (isTime.test(data[i][prop])) { // 判断是否是时间,如果是切割
data[i][prop] = data[i][prop].split(' ')[0]
}
}
}
this[e[0]] = data
this[e[1]] = parseInt(response.data.total)
} else {
this[e[0]] = []
// console.log('暂无数据')
}
}).then(() => {
this[e[2]] = false
})
},
// 添加表格数据
// 传入参数
// 1) 请求数据函数引用如getXXX必须
// 2) 请求的参数,可选
// 3) 验证的表单名称,必须
// 4) 回调函数,必须
addTable(requestData, param, form, callback) {
if (type(callback) === 'function' && type(requestData) === 'function' && type(form) === 'string') {
console.log(this.$refs[form])
this.$refs[form].validate((valid) => {
console.log(valid)
if (valid) {
requestData(...param).then(response => {
if (response.data[0].result === '1') {
this.$message.success('增加成功')
callback.call(this)
} else { this.$message.error('增加失败') }
})
} else {
console.error('error submit!!')
return false
}
})
} else {
console.error('参数传递错误')
}
},
// 编辑表格数据
// 传入参数
// 1) 请求数据函数引用如getXXX必须
// 2) 请求的参数,可选
// 3) 验证的表单名称,必须
// 4) 回调函数,必须
editTable(requestData, param, form, callback) {
if (type(callback) === 'function' && type(requestData) === 'function' && type(form) === 'string') {
this.$refs[form].validate((valid) => {
if (valid) {
requestData(...param).then(response => {
if (response.data[0].result === '1') {
this.$message.success('修改成功')
callback.call(this) // 回调函数修正this指向组件本身
} else { this.$message.error('修改失败') }
})
} else {
console.error('error submit!!')
return false
}
})
} else {
console.error('参数传递错误')
}
},
// 删除表格数据
// 传入参数
// 1) 请求数据函数引用如getXXX必须
// 2) id传入删除条件必须
// 3) 回调函数,必须
deleteRow(requestData, id, callback) {
if (type(callback) === 'function' && type(requestData) === 'function') {
if (this.tableData && this.tableData.length === 1 && this.pageCurrent > 1) { // 判断删除的是否是最后一页最后一行,如果是且不是第一页,页数减一
this.pageCurrent--
}
this.$confirm('此操作将永久删除该行, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
requestData(id).then(response => {
if (response.data[0].result === '1') {
this.$message.success('删除成功')
callback.call(this) // 回调函数修正this指向组件本身
} else { this.$message.error('数据占用') }
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
} else {
console.error('参数传递错误')
}
},
// 设置字段宽度
setColumnWidth(str) {
let columnWidth = 0
if (str === '日期') {
columnWidth = 90
} else if (str === '订单编号' || str === '领料单号' || str === '调拨单号' || str === '订单号' || str === '机床名称') {
columnWidth = 140
} else if (str === '合同编号' || str === '补货单号') {
columnWidth = 140
} else if (str === '产品名称' || str === '部件名称') {
columnWidth = 110
} else if (str === '序号') {
columnWidth = 60
} else if (str === '单位') {
columnWidth = 60
} else if (str === '数量' || str === '库存') {
columnWidth = 80
} else if (str === '单价') {
columnWidth = 90
} else if (str === '金额') {
columnWidth = 120
} else if (str === '状态' || str === '类型' || str === '选入' || str === '选出') {
columnWidth = 80
} else if (str === '付款方式') {
columnWidth = 100
} else if (str === '买方' || str === '买方名称') {
columnWidth = 150
} else if (str === '外协厂家' || str === '调入仓库' || str === '调出仓库') {
columnWidth = 150
} else if (str === '姓名') {
columnWidth = 80
} else if (str === '日期时间') {
columnWidth = 140
} else if (str === '工序') {
columnWidth = 80
} else if (str === '物料名称' || str === '工件名称' || str === '零件名称') {
columnWidth = 140
} else if (str === '图号或型号' || str === '规格' || str === '零件图号' || str === '图号' || str === '图号/型号') {
columnWidth = 120
} else if (str === '物料编码' || str === '物料编号') {
columnWidth = 120
} else if (str === '材料') {
columnWidth = 80
} else if (str === '仓位' || str === '库位') {
columnWidth = 90
} else if (str === '品名/规格/型号') {
columnWidth = 140
} else if (str === '备注') {
columnWidth = 120
} else if (str === '工序名称') {
columnWidth = 150
} else if (str === '工序说明') {
columnWidth = 300
} else if (str === '加工工时M' || str === '加工时长H' || str === '加工时长M') {
columnWidth = 90
} else if (str === '加工设备') {
columnWidth = 130
} else if (str === '产品详情' || str === '详情') {
columnWidth = 340
} else if (str === '最低库存') {
columnWidth = 110
} else if (str === '本次到货数' || str === '本次入库' || str === '补货数') {
columnWidth = 100
} else {
columnWidth = 110
}
return columnWidth
}
}
function download(url) {
getBlob(url, function(blob, filename) {
console.log(blob, filename, 2)
saveAs(blob, filename)
})
}
function getBlob(url, cb) {
var xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.responseType = 'blob'
xhr.onload = function() {
if (xhr.status === 200) {
const name = xhr.getResponseHeader('content-disposition').split('=')[1]
var filename = decodeURIComponent(name)
cb(xhr.response, filename)
}
}
xhr.send()
}
function saveAs(blob, filename) {
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, filename)
} else {
var link = document.createElement('a')
var body = document.querySelector('body')
link.href = window.URL.createObjectURL(blob)
link.download = filename
// fix Firefox
link.style.display = 'none'
body.appendChild(link)
link.click()
body.removeChild(link)
window.URL.revokeObjectURL(link.href)
}
}

134
src/utils/curdVue3.js Normal file
View File

@@ -0,0 +1,134 @@
import request from '@/utils/request'
import Cookie from 'js-cookie'
import {ElMessage} from 'element-plus'
export default {
install(app) {
app.config.globalProperties.CreateData = function (type, name, data, pageSize, pageList) {
var param_Str = ''
var param_array = []
if (type === '7') {
data.map(v => {
pageSize.map(h => {
const val = h.toString()
if (!v[val]) {
this.$set(v, val, '')
}
})
})
param_Str = data
} else {
if (data !== undefined) {
for (let i = 0; i < data.length; i++) {
param_array.push({
name: data[i][0],
value: data[i][1],
type: data[i][2],
output: data[i][3]
})
}
param_Str = JSON.stringify(param_array)
}
}
var obj = []
obj[0] = {}
obj[0].type = type
obj[0].name = name
obj[0].param = param_Str
obj[0].pageSize = pageSize
obj[0].pageList = pageList
obj[0].UserID = Cookie.get('username')
obj[0].ModularID = ''//this.$router.currentRoute.path
var numn = JSON.stringify(obj[0])
return numn
}
app.config.globalProperties.ExecDatabase = function (data) {
return request({
url: '',
method: 'post',
data: data
})
}
app.config.globalProperties.ExecDatabaseByParam = function (type, name, data, pageSize, pageList) {
var param_Str = ''
var param_array = []
if (type === '7') {
data.map(v => {
pageSize.map(h => {
const val = h.toString()
if (!v[val]) {
this.$set(v, val, '')
}
})
})
param_Str = data
} else {
if (data !== undefined) {
for (let i = 0; i < data.length; i++) {
param_array.push({
name: data[i][0],
value: data[i][1],
type: data[i][2],
output: data[i][3]
})
}
param_Str = JSON.stringify(param_array)
}
}
var obj = []
obj[0] = {}
obj[0].type = type
obj[0].name = name
obj[0].param = param_Str
obj[0].pageSize = pageSize
obj[0].pageList = pageList
obj[0].UserID = Cookie.get('AID')
obj[0].ModularID = ''//this.$router.currentRoute.path
var numn = JSON.stringify(obj[0])
return request({
url: '',
method: 'post',
data: numn
})
}
app.config.globalProperties.ExecWritePLC = function (opName, tagTypeCodeID, value) {
return request({
url: window.g.API_MES_URL + '/api/imes/Web_WritePLC',
method: 'post',
data: {
tagTypeCodeID: tagTypeCodeID,
OPName: opName,
tagValue: value,
}
}).catch(function (error) {
// 如果接口调用失败,弹出错误弹窗
ElMessage.error('写入PLC数据接口失败: ' + error.message)
});
}
app.config.globalProperties.ExecReadPLC = function (opName, tagTypeCodeID) {
return request({
url: window.g.API_MES_URL + '/api/imes/Web_ReadPLC',
method: 'post',
data: {
tagTypeCodeID: tagTypeCodeID,
OPName: opName,
}
}).catch(function (error) {
// 如果接口调用失败,弹出错误弹窗
ElMessage.error('读取PLC数据接口失败: ' + error.message)
});
}
app.config.globalProperties.ExecIMESAPI = function (apiName, param) {
return request({
url: window.g.API_MES_URL + '/api/imes/' + apiName,
method: 'post',
data: param
}).catch(function (error) {
// 如果接口调用失败,弹出错误弹窗
ElMessage.error('调用MES' + apiName + '接口失败: ' + error.message)
});
}
}
}

96
src/utils/index.js Normal file
View File

@@ -0,0 +1,96 @@
/**
* Created by jiachenpan on 16/11/18.
*/
export function parseTime(time, cFormat) {
if (arguments.length === 0) {
return null
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if (('' + time).length === 10) time = parseInt(time) * 1000
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
export function formatTime(time, option) {
time = +time * 1000
const d = new Date(time)
const now = Date.now()
const diff = (now - d) / 1000
if (diff < 30) {
return '刚刚'
} else if (diff < 3600) {
// less 1 hour
return Math.ceil(diff / 60) + '分钟前'
} else if (diff < 3600 * 24) {
return Math.ceil(diff / 3600) + '小时前'
} else if (diff < 3600 * 24 * 2) {
return '1天前'
}
if (option) {
return parseTime(time, option)
} else {
return (
d.getMonth() +
1 +
'月' +
d.getDate() +
'日' +
d.getHours() +
'时' +
d.getMinutes() +
'分'
)
}
}
function zeroFill(i) {
if (i >= 0 && i <= 9) {
return '0' + i
} else {
return i
}
}
export function getNowDAY() { // 默认显示今天
// 获取当前时间
const date = new Date()
const month = zeroFill(date.getMonth() + 1)
const day = zeroFill(date.getDate())
const time = date.getFullYear() + '-' + month + '-' + day
return time
}
export function get90DAY() { // 3个月显示今天
var Data = new Date()
Data.setTime(Data.getTime() - 3600 * 1000 * 24 * 90)
const month = zeroFill(Data.getMonth() + 1)
const day = zeroFill(Data.getDate())
const time = Data.getFullYear() + '-' + month + '-' + day
return time
}

104
src/utils/mqtt.js Normal file
View File

@@ -0,0 +1,104 @@
import { v4 as uuid } from 'uuid'
import mqtt from 'mqtt'
export { Mqtt }
class Mqtt {
constructor(config) {
this.connection = {
host: config.host,
port: config.port,
endpoint: config.endpoint || '/mqtt',
clean: config.clean || true,
connectTimeout: config.connectTimeout || 4000,
reconnectPeriod: config.reconnectPeriod || 4000,
clientId: config.clientId || uuid(),
username: config.username || '',
password: config.password || ''
}
this.client = {
connected: false
}
}
// 创建连接
createConnection() {
// 连接字符串, 通过协议指定使用的连接方式
// ws 未加密 WebSocket 连接
// wss 加密 WebSocket 连接
// mqtt 未加密 TCP 连接
// mqtts 加密 TCP 连接
// wxs 微信小程序连接
// alis 支付宝小程序连接
const { host, port, endpoint, ...options } = this.connection
const connectUrl = `ws://${host}:${port}${endpoint}`
try {
this.client = mqtt.connect(connectUrl, options)
} catch (error) {
// 连接错位、
this.connectError(error)
}
}
connectError(error) {
console.log('mqtt.connect error', error)
}
// 订阅主题
doSubscribe(subscription) {
const { topic, qos } = subscription
let subscribeSuccess = false
this.client.subscribe(topic, { qos }, (error, res) => {
if (error) {
subscribeSuccess = false
console.log('Subscribe to topics error', error)
} else {
subscribeSuccess = true
console.log('Subscribe to topics res', res)
}
})
return subscribeSuccess
}
// 取消订阅
doUnSubscribe(subscription) {
const { topic } = subscription
let unSubscribeSuccess = false
this.client.unsubscribe(topic, error => {
if (error) {
unSubscribeSuccess = false
console.log('Unsubscribe error', error)
} else {
unSubscribeSuccess = true
}
})
return unSubscribeSuccess
}
// 发送消息
doPublish(publish) {
const { topic, qos, payload } = publish
let doPublishSuccess = false
this.client.publish(topic, payload, qos, error => {
if (error) {
doPublishSuccess = false
console.log('Publish error', error)
} else {
doPublishSuccess = true
}
})
return doPublishSuccess
}
// 断开连接
destroyConnection() {
let destroyConnectionSuccess = false
if (this.client.connected) {
try {
this.client.end()
this.client = {
connected: false
}
destroyConnectionSuccess = true
console.log('Successfully disconnected!')
} catch (error) {
destroyConnectionSuccess = false
console.log('Disconnect failed', error.toString())
}
}
return destroyConnectionSuccess
}
}

34
src/utils/request.js Normal file
View File

@@ -0,0 +1,34 @@
import axios from 'axios'
// import store from '@/store'
import {ElMessage} from 'element-plus' // 引入Element Plus的消息提示组件
const name_config = `OP8888`
// import.meta.env.VITE_BASE_URL
const request_config = window.g.API_WEB_URL + `/submit/MESCommonBase.ashx`
export const name = name_config
const service = axios.create({
baseURL: request_config,
timeout: 1000 * 5 // 请求超时时间
})
// && config.data.name !== '菜单系统模块_通知信息_查询数据' && config.data.name !== '菜单系统模块_首页信息_查询数据1' && config.data.name !== '菜单系统模块_首页信息_查询数据2'
service.interceptors.request.use(config => {
return config
}, error => {
if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
ElMessage({
message: '请求超时,请稍后再试。',
type: 'error',
duration: 5000
})
} else {
ElMessage({
message: '请求失败,请稍后再试。',
type: 'error',
duration: 5000
})
}
return Promise.reject(error)
})
export default service

90
src/utils/setMethods.js Normal file
View File

@@ -0,0 +1,90 @@
/**
* Parse the time to string
* @param {(Object|string|number)} time
* @param {string} cFormat
* @returns {string}
*/
export function parseTime(time, cFormat) {
if (arguments.length === 0) {
return null
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
time = parseInt(time)
}
if (typeof time === 'number' && time.toString().length === 10) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
// eslint-disable-next-line
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value]
}
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
// eslint-disable-next-line
return time_str
}
/**
* Parse the json to excel
* tableJson 导出数据 ; filenames导出表的名字; autowidth表格宽度自动 true or false; bookTypes xlsx & csv & txt
* @param {(Object)} tableJson
* @param {string} filenames
* @param {boolean} autowidth
* @param {string} bookTypes
*/
export function json2excel(tableJson, filenames, autowidth, bookTypes) {
import('@/vendor/Export2Excel').then(excel => {
var tHeader = []
var dataArr = []
var sheetnames = []
for (var i in tableJson) {
tHeader.push(tableJson[i].tHeader)
dataArr.push(formatJson(tableJson[i].filterVal, tableJson[i].tableDatas))
sheetnames.push(tableJson[i].sheetName)
}
console.log(dataArr, 9191)
excel.export_json_to_excel2({
header: tHeader,
data: dataArr,
sheetname: sheetnames,
filename: filenames,
autoWidth: autowidth,
bookType: bookTypes
})
})
}
// 数据过滤,时间过滤
function formatJson(filterVal, jsonData) {
return jsonData.map(v =>
filterVal.map(j => {
if (j === 'timestamp') {
return parseTime(v[j])
} else {
return v[j]
}
})
)
}

361
src/utils/tool.js Normal file
View File

@@ -0,0 +1,361 @@
// 数组去重
export function arrayUnique(arr) {
arr.filter((element, index, arr) => {
return arr.indexOf(element) === index
})
}
// 数组去重,数组中值是对象
export function arrayUnique2(arr, name) {
const hash = {}
return arr.reduce(function (item, next) {
hash[next[name]] ? '' : hash[next[name]] = true && item.push(next)
return item
}, [])
}
// 时间转时间戳
export function timeToStamp(time) {
const date = new Date(time)
return date.getTime()
}
// 数组的深度拷贝
export function arrDeepCopy(arr) {
const newArr = []
for (const prop in arr) newArr[prop] = typeof arr[prop] === 'object' ? arrDeepCopy(arr[prop]) : arr[prop]
return newArr
}
// 对象的深度拷贝
export function objDeepCopy(obj) {
const newObj = {}
for (const prop in obj) newObj[prop] = typeof obj[prop] === 'object' ? objDeepCopy(obj[prop]) : obj[prop]
return newObj
}
function zeroFill(i) {
if (i >= 0 && i <= 9) {
return '0' + i
} else {
return i
}
}
// 获取当前时间(yyyy-mm-dd hh:mm:ss)
export function getNowTime() {
const date = new Date()
const month = zeroFill(date.getMonth() + 1)
const day = zeroFill(date.getDate())
const hour = zeroFill(date.getHours())
const minute = zeroFill(date.getMinutes())
const second = zeroFill(date.getSeconds())
const time = date.getFullYear() + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second
return time
}
export function getNowTime2() {
const date66 = new Date()
const month66 = zeroFill(date66.getMonth() + 1)
const day66 = zeroFill(date66.getDate())
const date = date66.getFullYear() + '-' + month66 + '-' + day66
// var date = '2019-3-31'
var arr = date.split('-')
var year = arr[0] // 获取当前日期的年份
var month = arr[1] // 获取当前日期的月份
var day = arr[2] // 获取当前日期的日
// var days = new Date(year, month, 0)
// days = days.getDate() // 获取当前日期中月的天数
var year2 = year
var month2 = parseInt(month) - 1
if (month2 === 0) {
year2 = parseInt(year2) - 1
month2 = 12
}
var day2 = day
var days2 = new Date(year2, month2, 0)
days2 = days2.getDate()
if (day2 > days2) {
day2 = days2
}
if (month2 < 10) {
month2 = '0' + month2
}
var t2 = year2 + '-' + month2 + '-' + day2
return t2
}
// 获取当前时间(yyyy-mm-dd)
export function getNowShotTime() {
const date = new Date()
const month = zeroFill(date.getMonth() + 1)
const day = zeroFill(date.getDate())
const time = date.getFullYear() + '-' + month + '-' + day
return time
}
// 获取当前时间(yyyy/mm/dd)
export function getNowShotTime1() {
const date = new Date()
const month = zeroFill(date.getMonth() + 1)
const day = zeroFill(date.getDate())
const time = date.getFullYear() + '/' + month + '/' + day
return time
}
// 获取当前0晨
export function getNowTime1() {
const date = new Date()
const month = zeroFill(date.getMonth() + 1)
const year = zeroFill(date.getFullYear())
const day = zeroFill(date.getDate())
let hour = zeroFill(date.getHours())
hour = hour < 10 ? ('0' + hour) : hour
let minute = zeroFill(date.getMinutes())
minute = minute < 10 ? ('0' + minute) : minute
let second = zeroFill(date.getSeconds())
second = second < 10 ? ('0' + second) : second
// const hour = '00'
// const minute = '00'
// const second = '00'
const time = year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second
return time
}
export function getNowTime3(date) {
const month = zeroFill2(date.getMonth() + 1)
const day = zeroFill(date.getDate())
return date.getFullYear() + '-' + month + '-' + day
}
function zeroFill2(i) {
if (i >= 0 && i <= 9) {
if (i === 0) {
return '01'
} else {
return '0' + i
}
} else {
return i
}
}
// 工时统计上个月26-本月25
export function getTime26() {
const date = new Date()
const day = date.getDate()
if (day >= 25) {
var month = zeroFill(date.getMonth() + 1)
if (month === 0) {
month = 12
const day = 26
const time = date.getFullYear() - 1 + '-' + month + '-' + day
return time
} else {
const day = 26
const time = date.getFullYear() + '-' + month + '-' + day
return time
}
} else {
var month2 = zeroFill(date.getMonth() + 1) - 1
if (month2 === 0) {
month2 = 12
const day = 26
const time = date.getFullYear() - 1 + '-' + month2 + '-' + day
return time
} else {
const day = 26
const time = date.getFullYear() + '-' + month2 + '-' + day
return time
}
}
}
export function getTime25() {
const date = new Date()
const day = date.getDate()
if (day >= 25) {
const month = zeroFill(date.getMonth() + 2)
const day2 = 25
const time = date.getFullYear() + '-' + month + '-' + day2
return time
} else {
const month = zeroFill(date.getMonth() + 1)
const day2 = 25
const time = date.getFullYear() + '-' + month + '-' + day2
return time
}
}
// 判断类型
export function type(target) {
const ret = typeof (target)
const template = {
'[object Array]': 'array',
'[object Object]': 'object',
'[object String]': 'string - object',
'[object Number]': 'Number - object',
'[object Boolean]': 'Boolean - object'
}
if (target === null) {
return 'null'
}
if (ret === 'object') {
const str = Object.prototype.toString.call(target)
return template[str]
} else {
return ret
}
}
export function SectionToChinese(section) {
const chnNumChar = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
const chnUnitChar = ['', '十', '百', '千']
let strIns = ''
let chnStr = ''
let unitPos = 0
let zero = true
while (section > 0) {
const v = section % 10
if (v === 0) {
if (!zero) {
zero = true
chnStr = chnNumChar[v] + chnStr
}
} else {
zero = false
strIns = chnNumChar[v]
strIns += chnUnitChar[unitPos]
chnStr = strIns + chnStr
}
unitPos++
section = Math.floor(section / 10)
}
return chnStr
}
export function sleep(time) {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
export function groupBy(datas, keys) {
const list = datas || []
const groups = []
const result = []
list.forEach(v => {
const key = {}
keys.forEach(k => {
key[k] = v[k]
})
let group = groups.find(v => {
return v._key === JSON.stringify(key)
})
if (!group) {
group = {
_key: JSON.stringify(key),
key: key
}
groups.push(group)
}
group.data1 = group.data1 || 0
group.key.数量 = group.data1 += v.数量
group.data2 = group.data2 || ''
group.key.机床图号_组号 = group.data2 += (v.机床图号 || '') + '-' + (v.组号 ? v.组号.split('组')[0] : '') + '<br/>'
group.data3 = group.data3 || 0
group.key.合计 = group.data3 += v.合计
})
groups.map(v => {
result.push(v.key)
})
return result
}
// 去除js浮点加法bug
export function numAdd(arg1, arg2) {
var r1, r2, m
try {
r1 = arg1.toString().split('.')[1].length
} catch (e) {
r1 = 0
}
try {
r2 = arg2.toString().split('.')[1].length
} catch (e) {
r2 = 0
}
m = Math.pow(10, Math.max(r1, r2))
return (arg1 * m + arg2 * m) / m
}
// 去除js浮点减法bug
export function numSubtract(arg1, arg2) {
var r1, r2, m, n
try {
r1 = arg1.toString().split('.')[1].length
} catch (e) {
r1 = 0
}
try {
r2 = arg2.toString().split('.')[1].length
} catch (e) {
r2 = 0
}
m = Math.pow(10, Math.max(r1, r2))
n = (r1 >= r2) ? r1 : r2
return ((arg1 * m - arg2 * m) / m).toFixed(n)
}
// 去除js浮点乘法bug
export function numMultiply(arg1, arg2) {
var m = 0
var s1 = arg1.toString()
var s2 = arg2.toString()
try {
m += s1.split('.')[1].length
} catch (e) {
console.log(e)
}
try {
m += s2.split('.')[1].length
} catch (e) {
console.log(e)
}
return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m)
}
// 去除js浮点除法bug
export function numDivide(arg1, arg2) {
var t1 = 0
var t2 = 0
var r1, r2
try {
t1 = arg1.toString().split('.')[1].length
} catch (e) {
console.log(e)
}
try {
t2 = arg2.toString().split('.')[1].length
} catch (e) {
console.log(e)
}
r1 = Math.Number(arg1.toString().replace('.', ''))
r2 = Math.Number(arg2.toString().replace('.', ''))
return (r1 / r2) * Math.pow(10, t2 - t1)
}
export function base64ImgtoFile(dataurl, filename = 'file') {
const arr = dataurl.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const suffix = mime.split('/')[1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new File([u8arr], `${filename}.${suffix}`, {
type: mime
})
}

32
src/utils/validate.js Normal file
View File

@@ -0,0 +1,32 @@
/**
* Created by jiachenpan on 16/11/18.
*/
export function isvalidUsername(str) {
const user = /^[0-9]{5,7}$/
return true
}
/* 合法uri*/
export function validateURL(textval) {
const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return urlregex.test(textval)
}
/* 小写字母*/
export function validateLowerCase(str) {
const reg = /^[a-z]+$/
return reg.test(str)
}
/* 大写字母*/
export function validateUpperCase(str) {
const reg = /^[A-Z]+$/
return reg.test(str)
}
/* 大小写字母*/
export function validatAlphabets(str) {
const reg = /^[A-Za-z]+$/
return reg.test(str)
}

248
src/views/404.vue Normal file
View File

@@ -0,0 +1,248 @@
<template>
<div class="wscn-http404-container">
<div class="wscn-http404">
<div class="pic-404">
<img
class="pic-404__parent"
src="@/assets/404_images/404.png"
alt="404"
/>
<img
class="pic-404__child left"
src="@/assets/404_images/404_cloud.png"
alt="404"
/>
<img
class="pic-404__child mid"
src="@/assets/404_images/404_cloud.png"
alt="404"
/>
<img
class="pic-404__child right"
src="@/assets/404_images/404_cloud.png"
alt="404"
/>
</div>
<div class="bullshit">
<div class="bullshit__oops">OOPS!</div>
<div class="bullshit__info">
版权所有
<a class="link-type" href="https://wallstreetcn.com" target="_blank"
>华尔街见闻</a
>
</div>
<div class="bullshit__headline">{{ message }}</div>
<div class="bullshit__info">
请检查您输入的网址是否正确请点击以下按钮返回主页或者发送错误报告
</div>
<a href="/" class="bullshit__return-home">返回首页</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Page404",
computed: {
message() {
return "网管说这个页面你不能进......";
},
},
};
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.wscn-http404-container {
transform: translate(-50%, -50%);
position: absolute;
top: 40%;
left: 50%;
}
.wscn-http404 {
position: relative;
width: 1200px;
padding: 0 50px;
overflow: hidden;
.pic-404 {
position: relative;
float: left;
width: 600px;
overflow: hidden;
&__parent {
width: 100%;
}
&__child {
position: absolute;
&.left {
width: 80px;
top: 17px;
left: 220px;
opacity: 0;
animation-name: cloudLeft;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
&.mid {
width: 46px;
top: 10px;
left: 420px;
opacity: 0;
animation-name: cloudMid;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1.2s;
}
&.right {
width: 62px;
top: 100px;
left: 500px;
opacity: 0;
animation-name: cloudRight;
animation-duration: 2s;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-delay: 1s;
}
@keyframes cloudLeft {
0% {
top: 17px;
left: 220px;
opacity: 0;
}
20% {
top: 33px;
left: 188px;
opacity: 1;
}
80% {
top: 81px;
left: 92px;
opacity: 1;
}
100% {
top: 97px;
left: 60px;
opacity: 0;
}
}
@keyframes cloudMid {
0% {
top: 10px;
left: 420px;
opacity: 0;
}
20% {
top: 40px;
left: 360px;
opacity: 1;
}
70% {
top: 130px;
left: 180px;
opacity: 1;
}
100% {
top: 160px;
left: 120px;
opacity: 0;
}
}
@keyframes cloudRight {
0% {
top: 100px;
left: 500px;
opacity: 0;
}
20% {
top: 120px;
left: 460px;
opacity: 1;
}
80% {
top: 180px;
left: 340px;
opacity: 1;
}
100% {
top: 200px;
left: 300px;
opacity: 0;
}
}
}
}
.bullshit {
position: relative;
float: left;
width: 300px;
padding: 30px 0;
overflow: hidden;
&__oops {
font-size: 32px;
font-weight: bold;
line-height: 40px;
color: #1482f0;
opacity: 0;
margin-bottom: 20px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
&__headline {
font-size: 20px;
line-height: 24px;
color: #222;
font-weight: bold;
opacity: 0;
margin-bottom: 10px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.1s;
animation-fill-mode: forwards;
}
&__info {
font-size: 13px;
line-height: 21px;
color: grey;
opacity: 0;
margin-bottom: 30px;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.2s;
animation-fill-mode: forwards;
}
&__return-home {
display: block;
float: left;
width: 110px;
height: 36px;
background: #1482f0;
border-radius: 100px;
text-align: center;
color: #ffffff;
opacity: 0;
font-size: 14px;
line-height: 36px;
cursor: pointer;
animation-name: slideUp;
animation-duration: 0.5s;
animation-delay: 0.3s;
animation-fill-mode: forwards;
}
@keyframes slideUp {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
}
}
</style>

View File

@@ -0,0 +1,124 @@
<template>
<div class="card">
<van-steps :active="callActive" active-color="#23d96e">
<van-step>选择物料</van-step>
<van-step>AGV送料中</van-step>
<van-step>物料验证</van-step>
</van-steps>
<el-tabs type="border-card" v-model="callActive" style="margin-top: 5vh;">
<el-tab-pane :disabled="callActive != 0" :name="0" label="选择物料">
<van-dropdown-menu>
<van-field v-model="materials" is-link readonly name="picker" label="叫料图号" placeholder="选择物料图号"
@click="isShowMaterialsPicker = true" />
<van-popup :show="isShowMaterialsPicker" destroy-on-close position="bottom">
<van-picker :columns="materialsList" :model-value="carValue" @confirm="materialsOnConfirm"
@cancel="isShowMaterialsPicker = false" />
</van-popup>
<van-field v-model="carPos" is-link readonly name="picker" label="叫料库位" placeholder="选择库位"
@click="isShowCarPosPicker = true" />
<van-popup :show="isShowCarPosPicker" destroy-on-close position="bottom">
<van-picker :columns="carPosList" :model-value="materialsValue" @confirm="carPosOnConfirm"
@cancel="isShowCarPosPicker = false" />
</van-popup>
</van-dropdown-menu>
<el-button icon="ShoppingCartFull" type="primary" @click="CallMaterials" style="margin-top: 5vh;">请求叫料</el-button>
</el-tab-pane>
<el-tab-pane :disabled="callActive != 1" :name="1" label="AGV送料中">
<van-cell-group>
<van-cell title="叫料时间" value="2025-03-25 10:00:00" />
<van-cell title="叫料图号" :value="materials" />
<van-cell title="叫料库位" :value="carPos" />
</van-cell-group>
<el-button icon="FolderChecked" type="primary" @click="handHandle" style="margin-top: 2vh;">已到达手动确认</el-button>
</el-tab-pane>
<el-tab-pane :disabled="callActive != 2" :name="2" label="物料验证">
<van-cell-group>
<van-cell title="计划订单" value="TestOrder1" />
<van-cell title="工件编号" value="DES1235123" />
<van-cell title="物料型号" value="DES" />
<van-cell title="叫料时间" value="2025-03-25 10:00:00" />
<van-cell title="叫料图号" :value="materials" />
<van-cell title="叫料库位" :value="carPos" />
<van-field value="物料码值" label="码值" right-icon="scan" :right-icon-size="19" />
</van-cell-group>
<el-button icon="FullScreen" type="primary" @click="verifyCode" style="margin-top: 5vh;">校验物料</el-button>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup>
import { ElMessageBox } from 'element-plus'
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
// import { showSuccessToast, showFailToast } from 'vant';
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
const callActive = ref(0)
// 缓存库下拉
const carPos = ref()
const carValue = ref()
const isShowCarPosPicker = ref(false)
const carPosList = reactive([
{ text: '缓存库位1', value: '缓存库位1' },
{ text: '缓存库位2', value: '缓存库位2' },
{ text: '缓存库位3', value: '缓存库位3' },
])
// 选择物料下拉
const materials = ref()
const materialsValue = ref()
const isShowMaterialsPicker = ref(false)
const materialsList = reactive([
{ text: 'MaterialsType1', value: 'MaterialsType1' },
{ text: 'MaterialsType2', value: 'MaterialsType2' },
{ text: 'MaterialsType3', value: 'MaterialsType3' },
])
onMounted(() => {
})
const nextTo = () => {
if (callActive.value == 2) {
callActive.value = 0
} else {
callActive.value++
}
}
const carPosOnConfirm = (selectedValues, eslectedOptions) => {
carPos.value = selectedValues?.text;
isShowCarPosPicker.value = false;
};
const materialsOnConfirm = (selectedValues, eslectedOptions) => {
materials.value = selectedValues?.text;
isShowMaterialsPicker.value = false;
setTimeout(() => {
if (carPos.value == undefined) {
carPos.value = carPosList[2].text
}
}, 1000);
};
const CallMaterials = () => {
ElMessage.success('物料呼叫成功!等待送达')
nextTo()
}
const handHandle = () => {
ElMessage.success('手动确认到达,请扫码校验物料')
nextTo()
}
const verifyCode = () => {
ElMessage.success('物料验证成功!送料结束')
carPos.value = ''
materials.value = ''
nextTo()
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,167 @@
<template>
<div class="card">
<div class="dataList">
<div class="item">
<div class="tItem">
<div class="name">叫料时间:</div>
<div class="value">2025-03-22 13:04:21</div>
</div>
<div class="tItem">
<div class="name">到料时间:</div>
<div class="value">2025-03-22 13:05:12</div>
</div>
<div class="tItem">
<div class="name">绑定工件:</div>
<div class="value">DES1235123</div>
</div>
<div class="tItem">
<div class="name">物料名称:</div>
<div class="value">断路器总成</div>
</div>
<div class="tItem">
<div class="name">物料编号:</div>
<div class="value">MaterialsNo1</div>
</div>
</div>
<div class="item">
<div class="tItem">
<div class="name">叫料时间:</div>
<div class="value">2025-03-22 13:18:21</div>
</div>
<div class="tItem">
<div class="name">到料时间:</div>
<div class="value">2025-03-22 13:22:12</div>
</div>
<div class="tItem">
<div class="name">绑定工件:</div>
<div class="value">DES1235124</div>
</div>
<div class="tItem">
<div class="name">物料名称:</div>
<div class="value">断路器总成</div>
</div>
<div class="tItem">
<div class="name">物料编号:</div>
<div class="value">MaterialsNo2</div>
</div>
</div>
<div class="item">
<div class="tItem">
<div class="name">叫料时间:</div>
<div class="value">2025-03-22 13:18:21</div>
</div>
<div class="tItem">
<div class="name">到料时间:</div>
<div class="value">2025-03-22 13:22:12</div>
</div>
<div class="tItem">
<div class="name">绑定工件:</div>
<div class="value">DES1235124</div>
</div>
<div class="tItem">
<div class="name">物料名称:</div>
<div class="value">断路器总成</div>
</div>
<div class="tItem">
<div class="name">物料编号:</div>
<div class="value">MaterialsNo2</div>
</div>
</div>
<div class="item">
<div class="tItem">
<div class="name">叫料时间:</div>
<div class="value">2025-03-22 13:18:21</div>
</div>
<div class="tItem">
<div class="name">到料时间:</div>
<div class="value">2025-03-22 13:22:12</div>
</div>
<div class="tItem">
<div class="name">绑定工件:</div>
<div class="value">DES1235124</div>
</div>
<div class="tItem">
<div class="name">物料名称:</div>
<div class="value">断路器总成</div>
</div>
<div class="tItem">
<div class="name">物料编号:</div>
<div class="value">MaterialsNo2</div>
</div>
</div>
<div class="item">
<div class="tItem">
<div class="name">叫料时间:</div>
<div class="value">2025-03-22 13:18:21</div>
</div>
<div class="tItem">
<div class="name">到料时间:</div>
<div class="value">2025-03-22 13:22:12</div>
</div>
<div class="tItem">
<div class="name">绑定工件:</div>
<div class="value">DES1235124</div>
</div>
<div class="tItem">
<div class="name">物料名称:</div>
<div class="value">断路器总成</div>
</div>
<div class="tItem">
<div class="name">物料编号:</div>
<div class="value">MaterialsNo2</div>
</div>
</div>
<div class="item">
<div class="tItem">
<div class="name">叫料时间:</div>
<div class="value">2025-03-22 13:18:21</div>
</div>
<div class="tItem">
<div class="name">到料时间:</div>
<div class="value">2025-03-22 13:22:12</div>
</div>
<div class="tItem">
<div class="name">绑定工件:</div>
<div class="value">DES1235124</div>
</div>
<div class="tItem">
<div class="name">物料名称:</div>
<div class="value">断路器总成</div>
</div>
<div class="tItem">
<div class="name">物料编号:</div>
<div class="value">MaterialsNo2</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
onMounted(() => {
})
// 离开网页 删除定时器
onUnmounted(() => {
})
</script>
<style lang="scss" scoped>
.dataList {
.item {
border: 2px solid #ccc;
padding: 10px 10px;
// 圆角 阴影
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
margin: 10px 0;
.tItem {
display: flex;
}
}
}
</style>

View File

@@ -0,0 +1,109 @@
<template>
<div class="main">
<div class="card">
<van-cell-group>
<van-field v-model="carBigPos" is-link readonly label="选择库位" placeholder="请选库位号"
@click="isShowCarBigPosPicker = true" />
<van-popup :show="isShowCarBigPosPicker" round position="bottom">
<van-cascader v-model="carBigPosValue" title="请选库位号" :options="carBigPosList"
@close="isShowCarBigPosPicker = false" @finish="carBigPosFinish" />
</van-popup>
</van-cell-group>
</div>
<div class="card" v-if="carStatus == 1">
<van-cell-group inset>
库位绑定信息
<van-cell title="物料名称" value="断路器总成" />
<van-cell title="物料图号" value="DESA" />
<van-cell title="物料编号" value="DESA1235123" />
<van-cell title="订单号" value="Order001" />
<van-cell title="入库时间" value="2025-03-22" />
</van-cell-group>
<van-button icon="replay" type="primary" size="large">库位解绑</van-button>
</div>
<div class="card" v-if="carStatus == 2">
<van-cell-group inset>
空库位绑定
<!-- <van-field label="物料名称" v-model="bindMaterialsData.materialName" right-icon="browsing-history-o"
:right-icon-size="19" /> -->
<van-field v-model="bindMaterialsData.materialName" is-link readonly name="picker" label="物料名称" placeholder="选择物料"
@click="isShowMaterialsPicker = true" />
<van-popup :show="isShowMaterialsPicker" destroy-on-close position="bottom">
<van-picker :columns="materialsList" :model-value="bindMaterialsData.materialCode" @confirm="bindMaterialsOnConfirm"
@cancel="isShowMaterialsPicker = false" />
</van-popup>
<van-cell title="物料图号" :value="bindMaterialsData.materialCode" right-icon="more-o" :right-icon-size="19" />
<!-- <van-field label="物料号" v-model="bindMaterialsData.materialCode" right-icon="more-o" :right-icon-size="19" /> -->
<van-field label="物料编号" v-model="bindMaterialsData.materialNo" right-icon="scan" :right-icon-size="19" />
<van-field label="订单号" v-model="bindMaterialsData.orderNo" right-icon="newspaper-o" :right-icon-size="19" />
<van-button icon="back-top" type="primary" size="large">绑定库位信息</van-button>
</van-cell-group>
</div>
</div>
</template>
<script setup>
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
const carStatus = ref(0)
// 选择库位 下拉
const carBigPos = ref()
const carBigPosValue = ref()
const isShowCarBigPosPicker = ref(false)
const carBigPosList = reactive([
{
text: '缓存库1',
value: '缓存库1',
children: [{ text: '1-1', value: '1-1' }, { text: '1-2', value: '1-2' }, { text: '1-3', value: '1-3' }],
},
{
text: '缓存库2',
value: '缓存库2',
children: [{ text: '2-1', value: '2-1' }, { text: '2-2', value: '2-2' }, { text: '3-3', value: '3-3' }],
},
])
// 选择物料种类 下拉
const isShowMaterialsPicker = ref(false)
const materialsList = reactive([
{ text: '测试物料1', value: 'MaterialsType1' },
{ text: '测试物料2', value: 'MaterialsType2' },
{ text: '测试物料3', value: 'MaterialsType3' },
])
const bindMaterialsData = reactive({
materialName: '',
materialNo: '',
materialCode: '',
orderNo: '',
inTime: '',
})
onMounted(() => {
})
// 全部选项选择完毕后,会触发 finish 事件
const carBigPosFinish = (selectedOptions) => {
console.log(selectedOptions, selectedOptions.selectedOptions);
if(selectedOptions.selectedOptions[0].text == '缓存库1') {
carStatus.value = 1
} else {
carStatus.value = 2
}
carBigPos.value = selectedOptions.selectedOptions.map((option) => option.text).join(' / ');
isShowCarBigPosPicker.value = false;
console.log(carStatus)
};
const bindMaterialsOnConfirm = (selectedValues, eslectedOptions) => {
bindMaterialsData.materialName = selectedValues?.text;
bindMaterialsData.materialCode = selectedValues?.value;
isShowMaterialsPicker.value = false;
};
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,123 @@
<template>
<div class="card">
<div class="dataList">
<div class="item">
<div class="tItem">
<div class="name">操作时间:</div>
<div class="value">2025-03-22 13:04:21</div>
</div>
<div class="tItem">
<div class="name">操作类型:</div>
<van-tag class="value" type="success">库位绑定</van-tag>
</div>
<div class="tItem">
<div class="name">操作库位:</div>
<div class="value">缓存库2 / 1-1</div>
</div>
<div class="tItem">
<div class="name">物料名称:</div>
<div class="value">断路器壳体</div>
</div>
<div class="tItem">
<div class="name">物料编号:</div>
<div class="value">MaterialsNo1</div>
</div>
</div>
<div class="item">
<div class="tItem">
<div class="name">操作时间:</div>
<div class="value">2025-03-22 13:04:21</div>
</div>
<div class="tItem">
<div class="name">操作类型:</div>
<van-tag class="value" type="warning">库位解绑</van-tag>
</div>
<div class="tItem">
<div class="name">操作库位:</div>
<div class="value">缓存库2 / 1-1</div>
</div>
<div class="tItem">
<div class="name">物料名称:</div>
<div class="value">断路器壳体</div>
</div>
<div class="tItem">
<div class="name">物料编号:</div>
<div class="value">MaterialsNo1</div>
</div>
</div>
<div class="item">
<div class="tItem">
<div class="name">操作时间:</div>
<div class="value">2025-03-22 13:04:21</div>
</div>
<div class="tItem">
<div class="name">操作类型:</div>
<van-tag class="value" type="warning">库位解绑</van-tag>
</div>
<div class="tItem">
<div class="name">操作库位:</div>
<div class="value">缓存库2 / 1-1</div>
</div>
<div class="tItem">
<div class="name">物料名称:</div>
<div class="value">断路器壳体</div>
</div>
<div class="tItem">
<div class="name">物料编号:</div>
<div class="value">MaterialsNo1</div>
</div>
</div>
<div class="item">
<div class="tItem">
<div class="name">操作时间:</div>
<div class="value">2025-03-22 13:04:21</div>
</div>
<div class="tItem">
<div class="name">操作类型:</div>
<van-tag class="value" type="success">库位绑定</van-tag>
</div>
<div class="tItem">
<div class="name">操作库位:</div>
<div class="value">缓存库2 / 1-1</div>
</div>
<div class="tItem">
<div class="name">物料名称:</div>
<div class="value">断路器壳体</div>
</div>
<div class="tItem">
<div class="name">物料编号:</div>
<div class="value">MaterialsNo1</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
onMounted(() => {
})
// 离开网页 删除定时器
onUnmounted(() => {
})
</script>
<style lang="scss" scoped>
.dataList {
.item {
border: 2px solid #ccc;
padding: 10px 10px;
// 圆角 阴影
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
margin: 10px 0;
.tItem {
display: flex;
}
}
}
</style>

206
src/views/Layout/index.vue Normal file
View File

@@ -0,0 +1,206 @@
<template>
<div class="main">
<router-view />
</div>
</template>
<script setup>
import { inject, onMounted, ref } from 'vue'
import { useAppStore } from '@/store/app.js'
import { storeToRefs } from 'pinia'
import { addDynamicRoutes } from '@/router/index.js'
import { ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
import Cookies from 'js-cookie'
const router = useRouter()
const store = useAppStore()
const CreateData = inject('CreateData')
const ExecDatabase = inject('ExecDatabase')
// storeToRefs 响应式解构赋值 pinia
const { user, isLoading, routerData } = storeToRefs(store)
const menuValue = ref('')
const date = ref('')
const time = ref('')
const nowWeek = ref('')
const week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
const now = new Date()
onMounted(() => {
setTimeout(() => {
if (!store.user.AID) {
if (Cookies.get('AID')) {
user.value.token = Cookies.get('token')
user.value.AID = Cookies.get('AID')
user.value.name = Cookies.get('name')
user.value.role = Cookies.get('role')
user.value.userName = Cookies.get('userName')
user.value.opName = Cookies.get('opName')
user.value.opNameText = Cookies.get('opNameText')
} else {
console.log('store.user.AID为空返回主页')
ElMessage.warning('用户信息过期,请重新登陆!')
router.push({ path: '/login' })
}
} else {
// 获取当前实时日期时间 与星期几
// setInterval(() => {
// date.value = now.toLocaleDateString()
// time.value = now.toLocaleTimeString()
// nowWeek.value = week[now.getDay()]
// }, 1000)
}
}, 200)
})
// 获取权限列表
const getRouter = async () => {
// 获取权限列表
var param = []
param[0] = ['userId', store.user.AID]
var Data = CreateData('11', 'PDA_基础表_权限模块_查询', param)
ExecDatabase(Data).then(response => {
if (response.data.length > 0) {
for (let item of response.data) {
item.selected = false
}
response.data[0].selected = true
routerData.value.userRouterList = response.data
initRouter()
} else {
ElMessage.warning('该角色未分配角色模块,请先进行分配!')
}
})
}
const initRouter = async () => {
// 动态添加路由
await addDynamicRoutes(routerData.value.userRouterList).then(() => {
menuValue.value = routerData.value.userRouterList[0].path
routerData.nowRouterPath = routerData.value.userRouterList[0].path
routerData.nowRouterTitle = routerData.value.userRouterList[0].权限模块
router.push({ path: '/' + routerData.value.userRouterList[0].path })
store.isLoading = false
})
}
const routerTo = (item) => {
const targetRoute = routerData.value.userRouterList.find(r => r.path === item.path)
if (targetRoute) {
routerData.nowRouterPath = targetRoute.path
routerData.nowRouterTitle = targetRoute.权限模块
routerData.selected = true
router.push({ path: '/' + targetRoute.path })
}
}
const logout = () => {
ElMessage.warning('退出登录成功!')
store.$reset() // 重置pinia数据
router.push('/login')
// var param = []
// param[0] = ['userId', user.AID]
// param[1] = ['opName', user.opName]
// var Data = CreateData('11', '基础表_用户角色_退出登录', param)
// ExecDatabase(Data).then(response => {
// if (response.data.length > 0) {
// ElMessage.warning('退出登录成功!')
// store.$reset() // 重置pinia数据
// router.push('/login')
// } else {
// ElMessage.warning('退出登录失败,数据未清空!')
// }
// })
}
</script>
<style lang="scss" scoped>
.topBar {
width: 100vw;
overflow: hidden;
.logo {
width: 20vw;
height: 10vh;
background-image: url("/public/TopLogo.png");
background-size: 100% 100%;
}
.dateTime {
font-size: .8vw;
float: right;
width: 10vw;
color: #fff;
text-align: center;
div {
height: 20px;
line-height: 20px;
}
}
}
// 主内容窗体大小
.main {
height: 92vh;
width: 100vw;
overflow: auto;
background-color: #f0f3f6;
}
// .t-menu__operations {
// .t-button {
// margin-left: 8px;
// }
// }
.t-demo-menu--dark {
.t-button {
color: #fff;
&:hover {
background-color: #4b4b4b;
border-color: transparent;
--ripple-color: #383838;
}
}
}
// logo 两旁留空
::v-deep() .t-head-menu .t-menu__logo:not(:empty) {
margin-right: 0;
}
::v-deep() .t-menu__logo>* {
margin-left: 0 !important;
}
// 顶部栏高度
::v-deep() .t-head-menu__inner{
height: 8vh;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 4px;
/* 设置纵轴y轴轴滚动条 */
height: 4px;
/* 设置横轴x轴轴滚动条 */
}
/* 滚动条滑块(里面小方块) */
::-webkit-scrollbar-thumb {
border-radius: 10px;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: #7ba2cc;
}
/* 滚动条轨道 */
::-webkit-scrollbar-track {
border-radius: 0;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: #fff;
}
</style>

238
src/views/Login/index.vue Normal file
View File

@@ -0,0 +1,238 @@
<template>
<div class="main">
<div class="login">
<div class="title">AMES 工位登录</div>
<div class="login-form">
<div class="form-item">
<el-input v-model="userform.userName" :prefix-icon="User" clearable placeholder="请输入用户名" size="large"
class="login-input" />
</div>
<div class="form-item">
<el-input v-model="userform.password" :prefix-icon="Lock" clearable placeholder="请输入密码" show-password
size="large" class="login-input" @keyup.enter="login" />
</div>
<div class="form-item">
<el-select v-model="userform.opName" placeholder="请选择台位号" size="large" class="login-select">
<el-option v-for="item in opNameList" :key="item.工位号" :label="item.工位号" :value="item.工位号" />
</el-select>
</div>
<div class="form-item">
<el-button type="danger" size="large" class="login-button" @click="login">
</el-button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { inject, onMounted, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from "@/store/app.js";
import { storeToRefs } from 'pinia';
import { encrypt, cryptoDecrypt, generateSalt } from "../../utils/AES-Pkcs7";
import Cookies from 'js-cookie'
const router = useRouter()
const CreateData = inject('CreateData')
const ExecDatabase = inject('ExecDatabase')
const store = useAppStore();
// storeToRefs 响应式解构赋值 pinia
const { user, isLoading } = storeToRefs(store);
const opNameList = reactive([])
const userform = reactive({
userName: '',
password: '',
opName: '',
opNameText: ''
})
onMounted(() => {
getOpName()
})
const getOpName = () => {
opNameList.length = 0
// 获取当前操作员名称
var param = []
var Data = CreateData('11', 'MES_计划BOM_工位与名称_PDA用_查询', param)
ExecDatabase(Data).then(response => {
if (response.data.length > 0) {
opNameList.push(...response.data)
// 获取上次登录的工位号
const lastOpName = Cookies.get('lastOpName')
if (lastOpName && opNameList.some(op => op.工位号 === lastOpName)) {
userform.opName = lastOpName
userform.opNameText = opNameList.find(op => op.工位号 === lastOpName).工位名称
} else {
userform.opName = opNameList[0].工位号
userform.opNameText = opNameList[0].工位名称
}
}
console.log(response)
})
}
const login = () => {
console.log(user)
var param = []
param.push(['用户名', userform.userName])
param.push(['密码', userform.password])
param.push(['功能类型', 0])
var Data = CreateData('11', '菜单模块系统_用户名密码_查询数据_改造新增', param)
ExecDatabase(Data).then(response => {
const data = response.data
if (data.length === 0 || data.result === '0' || data[0].result === '0') {
ElMessage.warning('账号或密码错误,请重试!')
} else {
const param2 = []
param2[0] = ['工位号', userform.opName]
param2[1] = ['操作者工号', userform.userName]
param2[2] = ['班次名称', 'A'] // this.loginForm.class]
const Data = CreateData('12', '测量数据工位_监控系统_操作者工号_增加', param2)
ExecDatabase(Data).then(() => { })
// 存储当前工位号到 cookie
Cookies.set('lastOpName', userform.opName, { expires: 365 }) // 保存一年
Cookies.set('AID', data[0].人员编号, { expires: 1 })
Cookies.set('name', data[0].姓名, { expires: 1 })
Cookies.set('role', data[0].角色编号, { expires: 1 })
Cookies.set('userName', data[0].考勤编号, { expires: 1 })
Cookies.set('opName', userform.opName, { expires: 1 })
Cookies.set('opNameText', userform.opNameText, { expires: 1 })
user.value.AID = response.data[0].人员编号
user.value.name = response.data[0].姓名
user.value.role = response.data[0].角色编号
user.value.userName = response.data[0].考勤编号
user.value.opName = userform.opName
user.value.opNameText = userform.opNameText
ElMessage.success('登录成功')
store.isLoading = true
router.push('/')
}
})
}
</script>
<style lang="scss" scoped>
.main {
position: relative;
height: 100vh;
width: 100vw;
background-image: url("/public/login.png");
background-size: cover;
background-position: center;
display: flex;
align-items: center;
justify-content: center;
.login {
width: 85vw;
padding: 40px 20px;
background-color: rgba(255, 255, 255, 0.8);
border: 2px solid #730215;
border-radius: 16px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.8);
.title {
color: #e43852;
font-size: 42px;
text-align: center;
font-weight: bold;
margin-bottom: 40px;
}
.login-form {
width: 90%;
margin: 0 auto;
.form-item {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
margin-top: 40px;
}
.login-input {
height: 50px;
font-size: 16px;
:deep(.el-input__wrapper) {
background-color: rgba(255, 255, 255, 0.9);
border-radius: 8px;
padding: 0 15px;
backdrop-filter: blur(5px);
}
:deep(.el-input__inner) {
height: 50px;
}
}
.login-select {
width: 100%;
height: 50px;
:deep(.el-input__wrapper) {
background-color: rgba(255, 255, 255, 0.9);
border-radius: 8px;
padding: 0 15px;
backdrop-filter: blur(5px);
}
:deep(.el-input__inner) {
height: 50px;
font-size: 16px;
}
}
.login-button {
width: 100%;
height: 50px;
font-size: 18px;
font-weight: bold;
border-radius: 8px;
background-color: #e43852;
border: none;
&:hover {
background-color: darken(#e43852, 10%);
}
&:active {
background-color: darken(#e43852, 20%);
}
}
}
}
}
}
// 适配不同屏幕尺寸
@media screen and (min-width: 768px) {
.main {
.login {
width: 70vw;
max-width: 600px;
}
}
}
@media screen and (max-width: 767px) {
.main {
.login {
width: 90vw;
.title {
font-size: 36px;
}
}
}
}
</style>

View File

@@ -0,0 +1,642 @@
<template>
<div class="container">
<!-- 数据栏 -->
<div class="data-section">
<div class="data-grid">
<div class="data-item">
<span class="label">工位名称</span>
<span class="value">{{ user.opNameText }}</span>
</div>
<div class="data-item">
<span class="label">工位编号</span>
<span class="value">{{ user.opName }}</span>
</div>
<div class="data-item">
<span class="label">操作者</span>
<span class="value">{{ user.name }}</span>
</div>
<div class="data-item">
<span class="label">产品编号</span>
<span class="value long-text">{{ opSatus.productNumber }}</span>
</div>
<div class="data-item">
<span class="label">产品型号</span>
<span class="value">{{ opSatus.productModel }}</span>
</div>
<div class="data-item">
<span class="label">订单号</span>
<span class="value">{{ opSatus.orderNumber }}</span>
</div>
</div>
<div class="data-item chassis-item">
<span class="label">底盘编号</span>
<span class="value">{{ opSatus.chassisNumber }}</span>
</div>
</div>
<!-- 功能按钮 -->
<div class="button-section">
<el-button type="primary" class="action-button" @click="showScanDialog">
产品进站
</el-button>
<el-button type="warning" class="action-button" @click="handleProblemReport">
检查项确认
</el-button>
<el-button type="success" class="action-button" @click="handleProcessComplete">
装配完成
</el-button>
<el-button type="danger" class="action-button" @click="handleLogout">
退出登录
</el-button>
</div>
<!-- 扫码弹窗 -->
<el-dialog v-model="scanDialogVisible" title="扫码" width="80%" :close-on-click-modal="false"
:close-on-press-escape="false" :show-close="false">
<div class="scan-dialog-content">
<el-input v-model="scanCode" placeholder="请扫描或输入随车码" size="large" @keyup.enter="submitScanCode" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="closeScanDialog">取消</el-button>
<el-button type="primary" @click="submitScanCode">
提交码值
</el-button>
</span>
</template>
</el-dialog>
<!-- 装配完成弹窗 -->
<el-dialog v-model="workFinishDialogVisible" title="装配完成" width="80%" :close-on-click-modal="false"
:close-on-press-escape="false" :show-close="false">
<div class="work-finish-dialog-content">
<el-form :model="workFinishForm" :rules="workFinishFormRules" ref="workFinishFormRef">
<el-form-item label="订单号:" prop="orderCode">
<el-input v-model="workFinishForm.orderCode" readonly />
</el-form-item>
<el-form-item label="产品编号:" prop="productNumber">
<el-input v-model="workFinishForm.productNumber" readonly />
</el-form-item>
<el-form-item label="产品型号:" prop="productModel">
<el-input v-model="workFinishForm.productModel" readonly />
</el-form-item>
<el-form-item label="底盘编号:" prop="chassisNumber">
<el-input v-model="workFinishForm.chassisNumber" readonly />
</el-form-item>
<el-form-item label="结果:" prop="isOk">
<el-select v-model="workFinishForm.isOk" placeholder="请选择结果">
<el-option label="通过" value="1" />
<el-option label="未通过" value="0" />
</el-select>
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="workFinishDialogVisible = false">取消</el-button>
<el-button type="primary" @click="finishWorkSubmit">
提交
</el-button>
</span>
</template>
</el-dialog>
<!-- 检查项弹窗 -->
<el-dialog v-model="checkItemsDialogVisible" title="检查项确认" :top="'10vh'" width="100%"
:close-on-click-modal="false" :close-on-press-escape="false" :show-close="false">
<div v-loading="checkItemsLoading" class="check-items-dialog-content">
<el-table :data="checkItemsList" border style="width: 100%" max-height="400px">
<el-table-column prop="排序" label="序号" width="60" align="center" />
<el-table-column prop="检查内容" label="检查内容" min-width="120">
<template #default="scope">
<div class="multiline-cell">{{ scope.row.检查内容 }}</div>
</template>
</el-table-column>
<el-table-column prop="检查要求" label="检查要求" min-width="180">
<template #default="scope">
<div class="multiline-cell">{{ scope.row.检查要求 }}</div>
</template>
</el-table-column>
<el-table-column label="状态" width="120" align="center">
<template #default="scope">
<el-tag v-if="scope.row.检查结果 === '0'" type="warning">未确认</el-tag>
<el-tag v-else-if="scope.row.检查结果 === '1'" type="success">OK</el-tag>
<el-tag v-else-if="scope.row.检查结果 === '2'" type="danger">NOK</el-tag>
<span v-else>{{ scope.row.检查结果 }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template #default="scope">
<div v-if="scope.row.检查结果 === '0'">
<el-button type="success" size="mini" @click="handleCheckResult(scope.row, '1')">OK</el-button>
<el-button type="danger" size="mini" @click="handleCheckResult(scope.row, '2')">NOK</el-button>
</div>
<div v-else>
<el-button type="info" size="mini" disabled>已确认</el-button>
</div>
</template>
</el-table-column>
<el-table-column prop="检查备注" label="备注" min-width="120">
<template #default="scope">
<span v-if="scope.row.检查结果 !== '0'">{{ scope.row.检查备注 || '-' }}</span>
</template>
</el-table-column>
</el-table>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="checkItemsDialogVisible = false">关闭</el-button>
</span>
</template>
</el-dialog>
<!-- NOK备注弹窗 -->
<el-dialog v-model="nokRemarkDialogVisible" title="NOK备注" width="500px" :append-to-body="true">
<el-form :model="nokRemarkForm" ref="nokRemarkFormRef" :rules="nokRemarkRules">
<el-form-item label="NOK原因" prop="remark" label-width="80px">
<el-input v-model="nokRemarkForm.remark" type="textarea" :rows="3" placeholder="请输入NOK原因">
</el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="nokRemarkDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmNokRemark">确认</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
import { useAppStore } from '@/store/app.js'
import { storeToRefs } from 'pinia'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
const router = useRouter()
const store = useAppStore()
const { user, isLoading, routerData } = storeToRefs(store)
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
const isEnableUI = ref(false)
const carSectionList = reactive([])
const opSatus = reactive({
workstationName: '',
workstationCode: '',
operator: '',
productNumber: '',
productModel: '',
orderNumber: '',
chassisNumber: '',
})
const nowStatus = reactive({
isPeople: false,
isCarArrive: false,
isRgvShipping: false,
})
var timer1 = null
// 扫码相关
const scanDialogVisible = ref(false)
const scanCode = ref('')
// 装配完成相关
const workFinishDialogVisible = ref(false)
const workFinishForm = reactive({
orderCode: '',
productNumber: '',
productModel: '',
chassisNumber: '',
isOk: '1'
})
const workFinishFormRef = ref(null)
const workFinishFormRules = {
orderCode: [
{ required: true, message: '请输入订单号', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在2到50个字符之间', trigger: 'blur' }
],
productNumber: [
{ required: true, message: '请输入产品编号', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在2到50个字符之间', trigger: 'blur' }
],
productModel: [
{ required: true, message: '请输入产品型号', trigger: 'blur' },
{ min: 0, max: 50, message: '长度在2到50个字符之间', trigger: 'blur' }
],
chassisNumber: [
{ required: true, message: '请输入底盘编号', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在2到50个字符之间', trigger: 'blur' }
],
isOk: [
{ required: true, message: '请选择结果', trigger: 'change' }
]
}
// 检查项相关
const checkItemsDialogVisible = ref(false)
const checkItemsLoading = ref(false)
const checkItemsList = ref([])
const nokRemarkDialogVisible = ref(false)
const nokRemarkForm = reactive({
id: null,
remark: '',
resultType: ''
})
const nokRemarkRules = {
remark: [
{ required: true, message: '请输入NOK原因', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在2到50个字符之间', trigger: 'blur' }
]
}
onMounted(() => {
setTimeout(() => {
getOpMoby()
}, 1000)
store.isLoading = false
})
// 离开网页 删除定时器
onUnmounted(() => {
clearInterval(timer1)
})
// 显示扫码弹窗
const showScanDialog = async () => {
await getOpMoby()
if (opSatus.productNumber != '') {
ElMessage.warning('当前工件未离站 不可重新进站')
return
}
scanCode.value = ''
scanDialogVisible.value = true
}
// 关闭扫码弹窗
const closeScanDialog = () => {
scanDialogVisible.value = false
scanCode.value = ''
}
// 提交扫码数据
const submitScanCode = async () => {
if (!scanCode.value) {
ElMessage.warning('请输入随车码')
return
}
try {
const param = []
param[0] = ['工位号', user.value.opName]
param[1] = ['产品编码', scanCode.value]
param[2] = ['操作者', user.value.name]
let response = await ExecDatabaseByParam('11', 'CATL_测量数据车体MOBY_车体进站', param)
if (response.data.length > 0) {
const isOk = response.data[0].是否合格
const msg = response.data[0].msg
if (isOk === 1) {
ElMessage.success(msg || '产品进站成功')
closeScanDialog()
getOpMoby()
} else {
ElMessage.error(msg || '产品进站失败')
}
} else {
ElMessage.error('产品进站失败!')
}
} catch (error) {
console.error('提交失败:', error)
ElMessage.error('系统错误,请重试')
}
}
// 获取工位数据
const getOpMoby = async () => {
let param = []
console.log(store.user.opName)
param[0] = ['工位号', store.user.opName]
let response = await ExecDatabaseByParam('11', 'CATL_测量数据工位MOBY_详细信息_查询', param)
if (response.data.length > 0) {
opSatus.workstationName = response.data[0].工位名称
opSatus.workstationCode = response.data[0].工位编号
opSatus.operator = response.data[0].操作者
opSatus.productNumber = response.data[0].Last_发动机号
opSatus.productModel = response.data[0].Last_机型号
opSatus.orderNumber = response.data[0].Last_工单号
opSatus.chassisNumber = response.data[0].车体二维码
} else {
opSatus.workstationName = ''
opSatus.workstationCode = ''
opSatus.productNumber = ''
opSatus.productModel = ''
opSatus.orderNumber = ''
opSatus.chassisNumber = ''
}
}
// 装配完成前检查
const handleProcessComplete = async () => {
if (!opSatus.productNumber) {
ElMessage.error('工位未在工作!')
return
}
try {
let param = []
param[0] = ['产品编码', opSatus.productNumber]
let response = await ExecDatabaseByParam('11', 'CATL_测量数据_出站前检查', param)
if (response.data.length > 0) {
const isOk = response.data[0].是否合格
const msg = response.data[0].msg
if (isOk === 1) {
showFinishDialog()
} else {
ElMessage.error(msg || '出站检查未通过')
}
} else {
ElMessage.error('产品出站失败!')
}
} catch (error) {
console.error('出站检查失败:', error)
ElMessage.error('系统错误,请重试')
}
}
// 显示装配完成弹窗
const showFinishDialog = () => {
workFinishForm.orderCode = opSatus.orderNumber
workFinishForm.productNumber = opSatus.productNumber
workFinishForm.productModel = opSatus.productModel
workFinishForm.chassisNumber = opSatus.chassisNumber
workFinishForm.isOk = '1'
workFinishDialogVisible.value = true
}
// 装配完成提交
const finishWorkSubmit = async () => {
try {
let param = []
param[0] = ['发动机流水号', workFinishForm.productNumber]
param[1] = ['工位编码', user.value.opName]
param[2] = ['托盘号', 1]
param[3] = ['过点属性', 1]
param[4] = ['采集时间', '']
param[5] = ['操作者', user.value.name]
param[6] = ['合格标志', workFinishForm.isOk]
param[7] = ['过站状态', 1]
param[8] = ['岔道上下件', 1]
param[9] = ['重量', '0']
param[10] = ['操作标记', 1]
param[11] = ['车体二维码', workFinishForm.chassisNumber]
let response = await ExecDatabaseByParam('12', 'Event_MOM_过点数据', param)
if (response.data.length > 0 && response.data[0].result == "1") {
ElMessage.success('装配完成')
workFinishDialogVisible.value = false
getOpMoby() // 刷新数据
} else {
ElMessage.error(response.data[0]?.msg || '装配完成失败')
}
} catch (error) {
console.error('装配完成失败:', error)
ElMessage.error('系统错误,请重试')
}
}
// 检查项相关
const handleProblemReport = async () => {
if (!opSatus.productNumber) {
ElMessage.error('工位未在工作!')
return
}
checkItemsDialogVisible.value = true
loadCheckItems()
}
// 加载检查项列表
const loadCheckItems = async () => {
checkItemsLoading.value = true
try {
let param = []
param[0] = ['工位号', user.value.opName]
param[1] = ['工件编号', opSatus.productNumber]
let response = await ExecDatabaseByParam('11', 'CATL_工位检查项_记录表_工位查询', param)
checkItemsList.value = response.data || []
} catch (error) {
console.error('加载检查项失败:', error)
ElMessage.error('加载检查项失败')
} finally {
checkItemsLoading.value = false
}
}
// 处理检查结果
const handleCheckResult = (row, resultType) => {
if (resultType === '2') {
// NOK需要填写备注
nokRemarkForm.id = row.ID
nokRemarkForm.resultType = resultType
nokRemarkForm.remark = ''
nokRemarkDialogVisible.value = true
} else {
// OK直接提交
submitCheckResult(row.ID, resultType, '')
}
}
// 确认NOK备注
const confirmNokRemark = () => {
if (!nokRemarkForm.remark) {
ElMessage.warning('请输入NOK原因')
return
}
submitCheckResult(nokRemarkForm.id, nokRemarkForm.resultType, nokRemarkForm.remark)
nokRemarkDialogVisible.value = false
}
// 提交检查结果
const submitCheckResult = async (id, resultType, remark) => {
try {
let param = []
param[0] = ['ID', id]
param[1] = ['操作者', user.value.name]
param[2] = ['检查结果', resultType]
param[3] = ['检查备注', remark]
let response = await ExecDatabaseByParam('12', 'CATL_工位检查项_记录表_工位检查确认', param)
if (response.data?.[0]?.result === '1') {
ElMessage.success(resultType === '1' ? '检查项确认OK' : '检查项确认NOK')
loadCheckItems() // 刷新列表
} else {
ElMessage.error('提交失败')
}
} catch (error) {
console.error('提交检查结果失败:', error)
ElMessage.error('提交失败')
}
}
// 退出登录
const handleLogout = () => {
ElMessageBox.confirm('确定要退出登录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
ElMessage.warning('退出登录成功!')
store.$reset() // 重置pinia数据
router.push('/login')
}).catch(() => { })
}
</script>
<style lang="scss" scoped>
.container {
height: 80vh;
display: flex;
flex-direction: column;
padding: 20px;
background-color: #f5f7fa;
}
.data-section {
flex: 1;
background-color: white;
border-radius: 12px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
}
.data-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-bottom: 15px;
}
.data-item {
display: flex;
align-items: center;
font-size: 1.2rem;
padding: 10px;
border-bottom: 1px solid #ebeef5;
.label {
width: 120px;
color: #606266;
font-weight: bold;
}
.value {
flex: 1;
color: #303133;
word-break: break-all;
}
.long-text {
font-size: 1rem;
}
}
.chassis-item {
grid-column: 1 / -1;
border-top: 2px solid #dcdfe6;
margin-top: 10px;
padding-top: 15px;
}
.button-section {
display: flex;
flex-direction: column;
gap: 15px;
padding: 20px;
background-color: white;
border-radius: 12px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.action-button {
height: 60px;
font-size: 1.2rem;
border-radius: 8px;
}
.el-button+.el-button {
margin-left: 0px;
}
// 响应式调整
@media screen and (max-width: 1024px) {
.data-item {
font-size: 1rem;
.label {
width: 100px;
}
}
.action-button {
height: 50px;
font-size: 1rem;
}
}
.scan-dialog-content {
padding: 20px;
.el-input {
width: 100%;
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
// 调整按钮样式
.button-section {
.el-button--danger {
background-color: #f56c6c;
border-color: #f56c6c;
&:hover {
background-color: #f78989;
border-color: #f78989;
}
}
}
.work-finish-dialog-content {
padding: 20px;
}
.check-items-dialog-content {
padding: 20px;
}
.nok-remark-dialog-content {
padding: 20px;
}
.multiline-cell {
white-space: pre-wrap;
word-break: break-all;
line-height: 1.5;
}
</style>

58
vite.config.js Normal file
View File

@@ -0,0 +1,58 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from "node:url";
import copy from 'rollup-plugin-copy'
import { resolve } from 'path'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { VantResolver } from '@vant/auto-import-resolver';
// https://vite.dev/config/
export default defineConfig({
base: './',
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver(),VantResolver()],
}),
Components({
resolvers: [ElementPlusResolver(),VantResolver()],
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
pluginOptions: {
'style-resources-loader': {
preProcessor: 'scss',
patterns: []
}
},
define: {
'process.env': {}
},
// 完全禁用默认的 public 目录处理
publicDir: false,
build: {
// 清空输出目录时保留自定义拷贝的文件
emptyOutDir: true,
rollupOptions: {
plugins: [
copy({
targets: [
{
src: 'public/**/*', // 复制整个 public 目录
dest: 'dist/public' // 输出到 dist/public
}
],
hook: 'writeBundle', // 在构建完成后执行
copyOnce: true // 仅拷贝一次
})
]
}
},
})