MVP
This commit is contained in:
3
.env.development
Normal file
3
.env.development
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ENV = 'development'
|
||||||
|
VITE_BASE_URL = 'http://localhost:10081'
|
||||||
|
VITE_CONFIG_URL = './public/config.js'
|
||||||
3
.env.production
Normal file
3
.env.production
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ENV = 'production'
|
||||||
|
VITE_BASE_URL = 'http://127.0.0.1:10081'
|
||||||
|
VITE_CONFIG_URL = './config.js'
|
||||||
70
.eslintrc.cjs
Normal file
70
.eslintrc.cjs
Normal 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'
|
||||||
|
}
|
||||||
|
})
|
||||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar"]
|
||||||
|
}
|
||||||
20
index.html
Normal file
20
index.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link href="/favicon.ico" rel="icon" type="image/x-icon" />
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||||
|
<title>PLCS</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>
|
||||||
4555
package-lock.json
generated
Normal file
4555
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
package.json
Normal file
31
package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "vue3-view",
|
||||||
|
"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",
|
||||||
|
"element-plus": "^2.8.6",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
|
"mqtt": "^5.14.0",
|
||||||
|
"path": "^0.12.7",
|
||||||
|
"pinia": "^2.2.5",
|
||||||
|
"rollup-plugin-copy": "^3.5.0",
|
||||||
|
"tdesign-vue-next": "^1.10.3",
|
||||||
|
"uuid": "^11.1.0",
|
||||||
|
"vue": "^3.5.12",
|
||||||
|
"vue-cookies": "^1.8.4",
|
||||||
|
"vue-router": "^4.4.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
|
"sass": "^1.80.5",
|
||||||
|
"vite": "^5.4.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
prettier.config.js
Normal file
19
prettier.config.js
Normal 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
BIN
public/TopLogo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
11
public/config.js
Normal file
11
public/config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
window.g = {
|
||||||
|
// API_WEB_URL: 'http://127.0.0.1:10260/submit/MESCommonBase.ashx',
|
||||||
|
API_WEB_URL: 'http://172.16.1.5:10000/submit/MESCommonBase.ashx',
|
||||||
|
API_MES_Controller_URL: 'http://172.16.1.5:9983',
|
||||||
|
mqttIP: '172.16.1.5',
|
||||||
|
mqttPortNumber: '8083',
|
||||||
|
mqttSubscriptionTopic: 'MES/InstantMessaging/CATL_PDC',
|
||||||
|
mqttPublishTopic: 'WEB/InstantMessaging/CATL_PDC/MisServer',
|
||||||
|
imageShareRoot: '\\\\cait-mesfile01\\pto\\PL001N\\首件照片',
|
||||||
|
};
|
||||||
|
// http://localhost:5173/#/?stationNo=xxx&workpieceNo=xxx
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/login.png
Normal file
BIN
public/login.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 MiB |
56
src/App.vue
Normal file
56
src/App.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<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">
|
||||||
|
// 定义的全局变量
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
BIN
src/assets/404_images/404.png
Normal file
BIN
src/assets/404_images/404.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
BIN
src/assets/404_images/404_cloud.png
Normal file
BIN
src/assets/404_images/404_cloud.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
301
src/components/PermissionVerify.vue
Normal file
301
src/components/PermissionVerify.vue
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="dialogTitle"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:show-close="false"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
center
|
||||||
|
:width="dialogWidth"
|
||||||
|
>
|
||||||
|
<div style="text-align: center; padding: 20px;">
|
||||||
|
<!-- 提示文本 -->
|
||||||
|
<div style="font-size: 1.2rem; margin-bottom: 10px; color: #606266;">
|
||||||
|
{{ promptText }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 权限级别显示 -->
|
||||||
|
<div v-if="permissionLevel" style="font-size: 1rem; margin-bottom: 20px; color: #E6A23C; font-weight: 600;">
|
||||||
|
权限级别:{{ permissionLevel }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 密码输入框 -->
|
||||||
|
<el-input
|
||||||
|
ref="passwordInput"
|
||||||
|
v-model="password"
|
||||||
|
type="password"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
show-password
|
||||||
|
clearable
|
||||||
|
style="width: 100%; max-width: 360px; font-size: 1.1rem;"
|
||||||
|
@keyup.enter="handleSubmit"
|
||||||
|
@focus="handleInputFocus"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon><Lock /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<!-- 错误提示 -->
|
||||||
|
<div v-if="errorMessage" style="color: #F56C6C; margin-top: 10px; font-size: 0.9rem;">
|
||||||
|
{{ errorMessage }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部按钮 -->
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer" style="text-align: center;">
|
||||||
|
<el-button @click="handleCancel" style="width: 200px;">取消权限确认</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, nextTick, watch, defineExpose, computed } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { Lock } from '@element-plus/icons-vue'
|
||||||
|
import Cookie from 'js-cookie'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('权限确认')
|
||||||
|
const promptText = ref('请输入密码进行权限验证')
|
||||||
|
const permissionLevel = ref('INFO')
|
||||||
|
const password = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
|
const errorMessage = ref('')
|
||||||
|
const passwordInput = ref(null)
|
||||||
|
|
||||||
|
// 自适应竖屏PAD的对话框宽度
|
||||||
|
const dialogWidth = computed(() => (window.innerWidth < 800 ? '92vw' : '520px'))
|
||||||
|
|
||||||
|
let resolve = null
|
||||||
|
let reject = null
|
||||||
|
let CreateData = null
|
||||||
|
let ExecDatabase = null
|
||||||
|
let $store = null
|
||||||
|
|
||||||
|
// 显示权限确认弹窗
|
||||||
|
const show = (options = {}) => {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
dialogTitle.value = options.title || '权限确认'
|
||||||
|
promptText.value = options.promptText || '请刷卡进行权限验证'
|
||||||
|
permissionLevel.value = options.permissionLevel || ''
|
||||||
|
password.value = ''
|
||||||
|
errorMessage.value = ''
|
||||||
|
loading.value = false
|
||||||
|
resolve = res
|
||||||
|
reject = rej
|
||||||
|
dialogVisible.value = true
|
||||||
|
// 等待DOM更新后聚焦输入框
|
||||||
|
nextTick(() => {
|
||||||
|
// 多次尝试聚焦,确保对话框完全渲染
|
||||||
|
setTimeout(() => {
|
||||||
|
if (passwordInput.value && passwordInput.value.$el) {
|
||||||
|
// Element Plus 的 Input 组件需要访问 $el
|
||||||
|
const inputElement = passwordInput.value.$el.querySelector('input')
|
||||||
|
if (inputElement) {
|
||||||
|
inputElement.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理输入框聚焦事件
|
||||||
|
const handleInputFocus = () => {
|
||||||
|
errorMessage.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限验证方法
|
||||||
|
const verifyPermission = async (pwd) => {
|
||||||
|
try {
|
||||||
|
const param = []
|
||||||
|
param[0] = ['工位号', $store?.state?.station?.stationNumber || '']
|
||||||
|
param[1] = ['卡号', pwd]
|
||||||
|
param[2] = ['操作人', $store?.state?.user?.name || '']
|
||||||
|
param[3] = ['权限级别', permissionLevel.value || '']
|
||||||
|
|
||||||
|
const data = CreateData('11', '人员刷卡权限校验', param)
|
||||||
|
const response = await ExecDatabase(data)
|
||||||
|
|
||||||
|
if (response.data.length > 0) {
|
||||||
|
const result = response.data[0]
|
||||||
|
|
||||||
|
// 根据存储过程返回结果判断
|
||||||
|
if (result.result === 1) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: result,
|
||||||
|
message: result.msg
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: result.msg || '权限验证失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: '权限验证失败,验证执行失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('权限验证接口调用失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理提交
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!password.value.trim()) {
|
||||||
|
errorMessage.value = '请输入密码'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
errorMessage.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用人员刷卡权限校验存储过程
|
||||||
|
const result = await verifyPermission(password.value)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
ElMessage.success('权限验证成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 写入 Cookie(有效期1天)
|
||||||
|
try {
|
||||||
|
if (result.data && result.data.userId) {
|
||||||
|
Cookie.set('userId', String(result.data.userId), { expires: 1 })
|
||||||
|
}
|
||||||
|
if (result.data && result.data.userName) {
|
||||||
|
Cookie.set('userName', result.data.userName, { expires: 1 })
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('写入Cookie失败:', e)
|
||||||
|
}
|
||||||
|
if (resolve) {
|
||||||
|
resolve({
|
||||||
|
success: true,
|
||||||
|
data: result.data,
|
||||||
|
message: result.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorMessage.value = result.message
|
||||||
|
password.value = ''
|
||||||
|
// 重新聚焦输入框
|
||||||
|
nextTick(() => {
|
||||||
|
if (passwordInput.value) {
|
||||||
|
passwordInput.value.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('权限验证异常:', error)
|
||||||
|
errorMessage.value = '权限验证异常,请重试'
|
||||||
|
password.value = ''
|
||||||
|
nextTick(() => {
|
||||||
|
if (passwordInput.value) {
|
||||||
|
passwordInput.value.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理取消
|
||||||
|
const handleCancel = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
router.push('/')
|
||||||
|
if (reject) {
|
||||||
|
reject({
|
||||||
|
success: false,
|
||||||
|
message: '用户取消操作',
|
||||||
|
cancelled: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听对话框显示状态,自动聚焦密码输入框
|
||||||
|
watch(dialogVisible, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (passwordInput.value && passwordInput.value.$el) {
|
||||||
|
const inputElement = passwordInput.value.$el.querySelector('input')
|
||||||
|
if (inputElement) {
|
||||||
|
inputElement.focus()
|
||||||
|
inputElement.select() // 选中输入框内容
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 200) // 给对话框足够的渲染时间
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 暴露给父组件的方法和属性
|
||||||
|
defineExpose({
|
||||||
|
show,
|
||||||
|
handleInputFocus,
|
||||||
|
handleSubmit,
|
||||||
|
handleCancel,
|
||||||
|
setCreateData(fn) {
|
||||||
|
CreateData = fn
|
||||||
|
},
|
||||||
|
setExecDatabase(fn) {
|
||||||
|
ExecDatabase = fn
|
||||||
|
},
|
||||||
|
setStore(store) {
|
||||||
|
$store = store
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-dialog) {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__header) {
|
||||||
|
padding: 20px 20px 10px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__title) {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__body) {
|
||||||
|
padding: 30px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__prefix) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
padding: 10px 20px 20px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
48
src/main.js
Normal file
48
src/main.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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'
|
||||||
|
// 引入组件库的少量全局样式变量
|
||||||
|
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'
|
||||||
|
|
||||||
|
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.provide('CreateData', app.config.globalProperties.CreateData)
|
||||||
|
app.provide('ExecDatabase', app.config.globalProperties.ExecDatabase)
|
||||||
|
app.provide('ExecDatabaseByParam', app.config.globalProperties.ExecDatabaseByParam)
|
||||||
|
app.provide('ExecHttpRequest', app.config.globalProperties.ExecHttpRequest)
|
||||||
|
// 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(); // 使用路由守卫
|
||||||
|
|
||||||
84
src/router/index.js
Normal file
84
src/router/index.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { createRouter, createWebHashHistory } from "vue-router";
|
||||||
|
|
||||||
|
// cookies
|
||||||
|
|
||||||
|
// 创建routes
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
// 重定向
|
||||||
|
path: "/home",
|
||||||
|
redirect: "/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
//首页 各页面导航页
|
||||||
|
path: "/",
|
||||||
|
component: () => import("@/views/index.vue"),
|
||||||
|
meta: { title: "系统导航页" },
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
//PLCS接口调用记录
|
||||||
|
path: "/InterfaceUseLog",
|
||||||
|
component: () => import("@/views/InterfaceUseLog.vue"),
|
||||||
|
meta: { title: "PLCS接口调用记录" },
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
//返修上线工位
|
||||||
|
path: "/repairOnline",
|
||||||
|
component: () => import("@/views/repairOnline.vue"),
|
||||||
|
meta: { title: "PLCS返修上线" },
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 电检工艺路线返修上线
|
||||||
|
path: "/electricRepairOnline",
|
||||||
|
component: () => import("@/views/electricRepairOnline.vue"),
|
||||||
|
meta: { title: "电检工艺路线返修上线" },
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 返修记录查询
|
||||||
|
path: "/repairOnlineSearch",
|
||||||
|
component: () => import("@/views/repairOnlineSearch.vue"),
|
||||||
|
meta: { title: "返修记录查询" },
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 首件拍照上传
|
||||||
|
path: "/firstPartImageUpload",
|
||||||
|
component: () => import("@/views/FirstPartImageUpload.vue"),
|
||||||
|
meta: { title: "PLCS首件拍照上传" },
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 工位异常处理
|
||||||
|
path: "/op",
|
||||||
|
component: () => import("@/views/OpStatus.vue"),
|
||||||
|
meta: { title: "PLCS工位异常处理" },
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// PACK下线三码校验
|
||||||
|
path: "/threeCodeVerify",
|
||||||
|
component: () => import("@/views/ThreeCodeVerify.vue"),
|
||||||
|
meta: { title: "PACK下线三码校验" },
|
||||||
|
children: [],
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
//History路由模型
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHashHistory('./'),
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 路由守卫:自动更新浏览器标题
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
if (to.meta && to.meta.title) {
|
||||||
|
document.title = to.meta.title;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
76
src/router/useRouteGuard.js
Normal file
76
src/router/useRouteGuard.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/store/app.js
Normal file
28
src/store/app.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import {defineStore} from 'pinia'
|
||||||
|
|
||||||
|
|
||||||
|
// 第一个参数是应用程序中 store 的唯一 id
|
||||||
|
export const useAppStore = defineStore('AppData', {
|
||||||
|
// 其它配置项
|
||||||
|
state: () => {
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
AID: '',
|
||||||
|
name: '',
|
||||||
|
classes: '',
|
||||||
|
teamGroup: '',
|
||||||
|
role: '',
|
||||||
|
userName: '',
|
||||||
|
password: '',
|
||||||
|
opName: '',
|
||||||
|
},
|
||||||
|
isLoading: false,
|
||||||
|
routerData: {
|
||||||
|
userRouterList: [],
|
||||||
|
nowRouterPath: '',
|
||||||
|
nowRouterTitle: '',
|
||||||
|
selected: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
15
src/utils/auth.js
Normal file
15
src/utils/auth.js
Normal 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
435
src/utils/curd.js
Normal 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}`)
|
||||||
|
},
|
||||||
|
// 用于生成传通讯服务器参数。
|
||||||
|
// 传入参数
|
||||||
|
// 1)type:11查询;12增删改,必须
|
||||||
|
// 2)name:存储过程名,必须
|
||||||
|
// 3)data:存储过程参数名和对应值,例如:
|
||||||
|
// param[0] = ['工艺计划流水号', this.工艺计划流水号] 输入参数
|
||||||
|
// param[1] = ['PageCurrent', '1111', 'int', '1'] output参数
|
||||||
|
// 数组里四个分别对应:存储过程参数名(必须);存储过程参数值(必须);参数类型(若是output必须);参数是否为output(0否1是)
|
||||||
|
// 4)pageSize,pageList:分页使用,可选
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
147
src/utils/curdVue3.js
Normal file
147
src/utils/curdVue3.js
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
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
|
||||||
|
console.log(obj[0], 'obj[0]')
|
||||||
|
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('username')
|
||||||
|
obj[0].ModularID = ''//this.$router.currentRoute.path
|
||||||
|
console.log(obj[0], 'obj[0]')
|
||||||
|
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)
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
app.config.globalProperties.ExecHttpRequest = function (url, method, data) {
|
||||||
|
return request({
|
||||||
|
url: url,
|
||||||
|
method: method || 'post',
|
||||||
|
data: data
|
||||||
|
}).catch(function (error) {
|
||||||
|
// 如果接口调用失败,弹出错误弹窗
|
||||||
|
ElMessage.error('接口调用失败: ' + error.message)
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/utils/index.js
Normal file
96
src/utils/index.js
Normal 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
|
||||||
|
}
|
||||||
27
src/utils/log.js
Normal file
27
src/utils/log.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* 工位操作日志工具 - 适用于 MES_ApiFailRetry(Vue 3 + inject 模式)
|
||||||
|
* msgType: 'INFO' | 'ERROR' | 'WARNING'
|
||||||
|
* operationType: 'StationOperate' | 'LOGOUT' 等
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录工位操作日志
|
||||||
|
* @param {{ ExecDatabaseByParam: Function }} apis - include ExecDatabaseByParam injectable
|
||||||
|
* @param {Object} msgInfo - 日志内容对象
|
||||||
|
* @param {string} msgInfo.stationNumber - 工位号
|
||||||
|
* @param {string} msgInfo.msgType - 消息类型 INFO / ERROR / WARNING
|
||||||
|
* @param {string} msgInfo.operationType - 操作类型
|
||||||
|
* @param {string} msgInfo.Content - 日志内容
|
||||||
|
*/
|
||||||
|
export function logAdd(apis, msgInfo) {
|
||||||
|
const { ExecDatabaseByParam } = apis
|
||||||
|
const param = [
|
||||||
|
['工位号', msgInfo.stationNumber || ''],
|
||||||
|
['消息类型', msgInfo.msgType || 'INFO'],
|
||||||
|
['操作类型', msgInfo.operationType || 'StationOperate'],
|
||||||
|
['内容', msgInfo.Content || '']
|
||||||
|
]
|
||||||
|
ExecDatabaseByParam('12', '日志_工位操作_增加', param).catch(e => {
|
||||||
|
console.warn('日志记录失败:', e)
|
||||||
|
})
|
||||||
|
}
|
||||||
104
src/utils/mqtt.js
Normal file
104
src/utils/mqtt.js
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
35
src/utils/request.js
Normal file
35
src/utils/request.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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
|
||||||
|
console.log(window.g)
|
||||||
|
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
90
src/utils/setMethods.js
Normal 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
361
src/utils/tool.js
Normal 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
32
src/utils/validate.js
Normal 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
248
src/views/404.vue
Normal 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>
|
||||||
561
src/views/FirstPartImageUpload.vue
Normal file
561
src/views/FirstPartImageUpload.vue
Normal file
@@ -0,0 +1,561 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 权限验证组件 -->
|
||||||
|
<PermissionVerify ref="permissionVerify" />
|
||||||
|
|
||||||
|
<template v-if="isAuthenticated">
|
||||||
|
<!-- 顶部标题与用户信息 -->
|
||||||
|
<div class="header-section">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="title-area" @click="navigateHome">
|
||||||
|
<h1 class="page-title">首件拍照上传</h1>
|
||||||
|
<p class="page-subtitle">First Part Photo Upload</p>
|
||||||
|
</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<div style="display:flex;align-items:center;color:#fff;opacity:.95;">
|
||||||
|
登录人:{{ loginUserName }}
|
||||||
|
</div>
|
||||||
|
<el-button type="danger" icon="SwitchButton" @click="handleLogout" size="small">退出登录</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查询筛选区域 -->
|
||||||
|
<div class="search-section">
|
||||||
|
<div class="search-card">
|
||||||
|
<div class="search-form">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">工位号:</label>
|
||||||
|
<el-select v-model="stationNumberValue" placeholder="请选择工位号" filterable clearable
|
||||||
|
class="form-input" @change="loadPhotoItems">
|
||||||
|
<el-option v-for="item in stationNumber" :key="item.value" :label="item.label"
|
||||||
|
:value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="form-item form-actions">
|
||||||
|
<el-button type="primary" icon="Search" @click="loadPhotoItems"
|
||||||
|
:disabled="!stationNumberValue">查询</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 数据展示区域:仅显示拍照上传项(项目类型=3) -->
|
||||||
|
<div class="data-section">
|
||||||
|
<div class="data-card">
|
||||||
|
<el-table v-loading="loading" :data="photoItems" :height="tableHeight" border stripe
|
||||||
|
style="width:100%"
|
||||||
|
:header-cell-style="{ background: '#f5f7fa', color: '#303133', fontWeight: 'bold', textAlign: 'center' }"
|
||||||
|
:cell-style="{ textAlign: 'center' }">
|
||||||
|
<el-table-column type="index" label="序号" width="60" fixed="left" />
|
||||||
|
<el-table-column prop="检测项目" label="检测项目" min-width="180" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="工位号" label="工位号" width="110" />
|
||||||
|
<el-table-column prop="是否合格" label="是否合格" width="90">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag
|
||||||
|
:type="parseInt(scope.row.是否合格) === 1 ? 'success' : (parseInt(scope.row.是否合格) === 2 ? 'warning' : 'info')"
|
||||||
|
size="small">
|
||||||
|
{{ scope.row.是否合格 === 1 || scope.row.是否合格 === '1' ? 'OK' : (scope.row.是否合格 === 2 ||
|
||||||
|
scope.row.是否合格 === '2' ? 'NG' : '未填写') }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="结果确认" label="结果确认" width="90">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="scope.row.结果确认 == 1 ? 'success' : 'warning'" size="small">
|
||||||
|
{{ scope.row.结果确认 == 1 ? '已确认' : '未确认' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="测量值" label="图片路径" min-width="260" show-overflow-tooltip />
|
||||||
|
<el-table-column label="操作" width="140" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="primary" size="small" :disabled="scope.row.结果确认 == 1"
|
||||||
|
@click="openUploadDialog(scope.row)">拍照上传</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div class="pagination-wrapper">
|
||||||
|
<el-pagination v-model:current-page="pagination.currentPage"
|
||||||
|
v-model:page-size="pagination.pageSize" :page-sizes="[20, 50, 100, 200]"
|
||||||
|
:total="pagination.total" layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="handleSizeChange" @current-change="handleCurrentChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 上传对话框 -->
|
||||||
|
<el-dialog v-model="uploadDialog.visible" title="拍照上传" width="640px" :close-on-click-modal="false">
|
||||||
|
<div class="upload-form">
|
||||||
|
<el-form label-width="120px">
|
||||||
|
<el-form-item label="检测项目">
|
||||||
|
<el-input v-model="uploadDialog.row.检测项目" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="相对路径">
|
||||||
|
<el-input v-model.trim="uploadDialog.relativePath"
|
||||||
|
placeholder="例如 OP120(固定架装配)\\2025-08-30\\首件项目-20250830-094503.png" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="选择图片">
|
||||||
|
<input type="file" accept="image/*" capture="environment" @change="onFileChange" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="uploadDialog.preview" label="预览">
|
||||||
|
<img :src="uploadDialog.preview" alt="预览"
|
||||||
|
style="max-width:100%;max-height:260px;border:1px solid #e4e7ed;border-radius:4px;" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div v-if="uploadDialog.error" style="color:#f56c6c;">{{ uploadDialog.error }}</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div style="display:flex;justify-content:flex-end;gap:8px;">
|
||||||
|
<el-button @click="uploadDialog.visible = false" :disabled="uploadDialog.loading">取 消</el-button>
|
||||||
|
<el-button type="primary" :loading="uploadDialog.loading" @click="submitUpload">上 传</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-else class="auth-loading">
|
||||||
|
<i class="el-icon-loading"></i>
|
||||||
|
<p>正在进行权限验证...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted, inject, computed } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import PermissionVerify from '@/components/PermissionVerify.vue'
|
||||||
|
import Cookie from 'js-cookie'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
// 注入后端执行方法
|
||||||
|
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
|
||||||
|
const CreateData = inject('CreateData')
|
||||||
|
const ExecDatabase = inject('ExecDatabase')
|
||||||
|
const ExecHttpRequest = inject('ExecHttpRequest')
|
||||||
|
|
||||||
|
// 路由
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 返回导航首页
|
||||||
|
const navigateHome = () => {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限
|
||||||
|
const isAuthenticated = ref(false)
|
||||||
|
const permissionVerify = ref(null)
|
||||||
|
const userInfo = ref({})
|
||||||
|
const loginUserName = computed(() => Cookie.get('userName') || userInfo.value.姓名 || userInfo.value.userName || '-')
|
||||||
|
|
||||||
|
// 工位与数据
|
||||||
|
const stationNumber = ref([])
|
||||||
|
const stationNumberValue = ref('')
|
||||||
|
const loading = ref(false)
|
||||||
|
const photoItems = ref([])
|
||||||
|
const tableHeight = ref(500)
|
||||||
|
|
||||||
|
// 分页(如数据量不大,也可不分页;这里做简单分页)
|
||||||
|
const pagination = reactive({ currentPage: 1, pageSize: 50, total: 0 })
|
||||||
|
|
||||||
|
// 上传对话框
|
||||||
|
const uploadDialog = reactive({
|
||||||
|
visible: false,
|
||||||
|
loading: false,
|
||||||
|
row: {},
|
||||||
|
relativePath: '',
|
||||||
|
file: null,
|
||||||
|
preview: '',
|
||||||
|
error: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const calculateTableHeight = () => {
|
||||||
|
tableHeight.value = window.innerHeight - 280
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录与初始化
|
||||||
|
const checkPermission = async () => {
|
||||||
|
try {
|
||||||
|
const cookieUserId = Cookie.get('userId')
|
||||||
|
const cookieUserName = Cookie.get('userName')
|
||||||
|
if (cookieUserId && cookieUserName) {
|
||||||
|
isAuthenticated.value = true
|
||||||
|
userInfo.value = { UserID: cookieUserId, 姓名: cookieUserName }
|
||||||
|
ElMessage.success(`欢迎 ${cookieUserName} 登录系统`)
|
||||||
|
await initializePage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await permissionVerify.value.show({
|
||||||
|
title: '首件拍照上传登录',
|
||||||
|
promptText: '请刷卡进行身份验证',
|
||||||
|
permissionLevel: '操作工'
|
||||||
|
})
|
||||||
|
if (result.success) {
|
||||||
|
isAuthenticated.value = true
|
||||||
|
userInfo.value = result.data
|
||||||
|
ElMessage.success(`欢迎 ${result.data.姓名 || result.data.userName || '用户'} 登录系统`)
|
||||||
|
await initializePage()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e.cancelled) {
|
||||||
|
ElMessage.warning('已取消登录')
|
||||||
|
} else {
|
||||||
|
ElMessage.error('权限验证失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializePage = async () => {
|
||||||
|
calculateTableHeight()
|
||||||
|
window.addEventListener('resize', calculateTableHeight)
|
||||||
|
await loadStationNumber()
|
||||||
|
if (stationNumber.value.length > 0) {
|
||||||
|
stationNumberValue.value = stationNumber.value[0].value
|
||||||
|
await loadPhotoItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取工位列表(用于筛选)
|
||||||
|
const loadStationNumber = async () => {
|
||||||
|
try {
|
||||||
|
stationNumber.value = []
|
||||||
|
const param = []
|
||||||
|
const data = CreateData('11', '电子看板_质量数据_首件数据_工位表_查询', param)
|
||||||
|
const res = await ExecDatabase(data)
|
||||||
|
if (Array.isArray(res.data)) {
|
||||||
|
for (let i = 0; i < res.data.length; i++) {
|
||||||
|
stationNumber.value.push({
|
||||||
|
label: `【${res.data[i].工位号}】${res.data[i].工位名称 || ''}`,
|
||||||
|
value: res.data[i].工位号
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('加载工位列表失败:', e)
|
||||||
|
ElMessage.error('加载工位列表失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取拍照上传项:项目类型=3
|
||||||
|
const loadPhotoItems = async () => {
|
||||||
|
if (!stationNumberValue.value) {
|
||||||
|
photoItems.value = []
|
||||||
|
pagination.total = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const param = []
|
||||||
|
param.push(['工位号', stationNumberValue.value])
|
||||||
|
const data = CreateData('11', '电子看板_质量数据_首件数据_MOBY_查询', param)
|
||||||
|
const res = await ExecDatabase(data)
|
||||||
|
const all = Array.isArray(res.data) ? res.data : []
|
||||||
|
const onlyPhoto = all.filter(x => Number(x.项目类型) === 3)
|
||||||
|
pagination.total = onlyPhoto.length
|
||||||
|
// 简单分页
|
||||||
|
const start = (pagination.currentPage - 1) * pagination.pageSize
|
||||||
|
photoItems.value = onlyPhoto.slice(start, start + pagination.pageSize)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('查询失败:', e)
|
||||||
|
ElMessage.error('查询失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSizeChange = (val) => {
|
||||||
|
pagination.pageSize = val
|
||||||
|
pagination.currentPage = 1
|
||||||
|
loadPhotoItems()
|
||||||
|
}
|
||||||
|
const handleCurrentChange = (val) => {
|
||||||
|
pagination.currentPage = val
|
||||||
|
loadPhotoItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开上传弹窗
|
||||||
|
const openUploadDialog = (row) => {
|
||||||
|
try {
|
||||||
|
uploadDialog.visible = true
|
||||||
|
uploadDialog.loading = false
|
||||||
|
uploadDialog.row = row
|
||||||
|
uploadDialog.file = null
|
||||||
|
uploadDialog.preview = ''
|
||||||
|
uploadDialog.error = ''
|
||||||
|
// 默认路径与文件名
|
||||||
|
const today = new Date()
|
||||||
|
const yyyy = today.getFullYear()
|
||||||
|
const mm = String(today.getMonth() + 1).padStart(2, '0')
|
||||||
|
const dd = String(today.getDate()).padStart(2, '0')
|
||||||
|
const hh = String(today.getHours()).padStart(2, '0')
|
||||||
|
const mi = String(today.getMinutes()).padStart(2, '0')
|
||||||
|
const ss = String(today.getSeconds()).padStart(2, '0')
|
||||||
|
const ymd = `${yyyy}-${mm}-${dd}`
|
||||||
|
const ymdhms = `${yyyy}${mm}${dd}-${hh}${mi}${ss}`
|
||||||
|
// 默认相对路径:{工位号}({工位名称})\\{日期}\\{首件项目名称}-{时间}.png
|
||||||
|
const stationLabel = (stationNumber.value.find(x => x.value === stationNumberValue.value)?.label || '')
|
||||||
|
.replace(/^【[^】]*】/, '') // 去掉【工位号】前缀
|
||||||
|
.trim()
|
||||||
|
const stationName = stationLabel || ''
|
||||||
|
const projectName = (row.检测项目 || '').toString().replace(/[\\/:*?"<>|]/g, '').replace('照片路径','')
|
||||||
|
uploadDialog.relativePath = `${stationNumberValue.value}${stationName ? '(' + stationName + ')' : ''}\\${ymd}\\${projectName ? projectName + '-' : ''}${ymdhms}.png`
|
||||||
|
} catch (e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件选择
|
||||||
|
const onFileChange = (e) => {
|
||||||
|
uploadDialog.error = ''
|
||||||
|
const files = e && e.target ? e.target.files : []
|
||||||
|
if (!files || !files[0]) {
|
||||||
|
uploadDialog.file = null
|
||||||
|
uploadDialog.preview = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const file = files[0]
|
||||||
|
if (!/^image\//.test(file.type)) {
|
||||||
|
uploadDialog.error = '请选择图片文件'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uploadDialog.file = file
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = (ev) => {
|
||||||
|
uploadDialog.preview = ev.target && ev.target.result ? ev.target.result.toString() : ''
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交上传
|
||||||
|
const submitUpload = async () => {
|
||||||
|
try {
|
||||||
|
if (!uploadDialog.relativePath) { uploadDialog.error = '请填写相对路径'; return }
|
||||||
|
if (!uploadDialog.file || !uploadDialog.preview) { uploadDialog.error = '请选择图片'; return }
|
||||||
|
uploadDialog.loading = true
|
||||||
|
|
||||||
|
// 调用本地上传接口(127.0.0.1:9982/UploadImageToShare)
|
||||||
|
const payload = {
|
||||||
|
MsgId: Math.random().toString(36).substring(2),
|
||||||
|
RelativePath: uploadDialog.relativePath,
|
||||||
|
ImageBase64: uploadDialog.preview // 可包含 data:image/png;base64, 前缀
|
||||||
|
}
|
||||||
|
const res = await ExecHttpRequest(window.g.API_MES_Controller_URL + '/UploadImageToShare', 'post', payload)
|
||||||
|
const code = res && res.data ? res.data.Code : null
|
||||||
|
const message = res && res.data ? (res.data.Message || '') : ''
|
||||||
|
if (code !== '200') {
|
||||||
|
ElMessage.error(message || '上传失败')
|
||||||
|
uploadDialog.loading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传成功 -> 调用结果确认存储过程:电子看板_质量数据_首件数据_MOBY_结果确认
|
||||||
|
// 需要保存完整UNC前缀:\\Cait-officefile\\mes\\首件照片 + RelativePath
|
||||||
|
const root = (window.g && window.g.imageShareRoot) ? window.g.imageShareRoot : ''
|
||||||
|
const rel = uploadDialog.relativePath.replace(/\//g, '\\')
|
||||||
|
const fullPath = root ? `${root}\\${rel}` : rel
|
||||||
|
const param = []
|
||||||
|
param.push(['ID', uploadDialog.row.ID])
|
||||||
|
param.push(['测量值', fullPath])
|
||||||
|
param.push(['是否合格', 1])
|
||||||
|
param.push(['检测人', Cookie.get('userName') || loginUserName.value || ''])
|
||||||
|
const data = CreateData('12', '电子看板_质量数据_首件数据_MOBY_结果确认', param)
|
||||||
|
const confirmRes = await ExecDatabase(data)
|
||||||
|
if (Array.isArray(confirmRes.data) && confirmRes.data[0] && confirmRes.data[0].result === '1') {
|
||||||
|
ElMessage.success('上传成功并已结果确认')
|
||||||
|
uploadDialog.visible = false
|
||||||
|
await loadPhotoItems()
|
||||||
|
} else {
|
||||||
|
ElMessage.warning('上传成功,但结果确认失败,请稍后重试')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('提交上传失败:', e)
|
||||||
|
ElMessage.error('提交上传失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
uploadDialog.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定要退出登录吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
|
||||||
|
isAuthenticated.value = false
|
||||||
|
userInfo.value = {}
|
||||||
|
photoItems.value = []
|
||||||
|
pagination.total = 0
|
||||||
|
Cookie.remove('userId')
|
||||||
|
Cookie.remove('userName')
|
||||||
|
router.push('/')
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (permissionVerify.value) {
|
||||||
|
permissionVerify.value.setCreateData(CreateData)
|
||||||
|
permissionVerify.value.setExecDatabase(ExecDatabase)
|
||||||
|
permissionVerify.value.setStore({
|
||||||
|
state: {
|
||||||
|
station: { stationNumber: '' },
|
||||||
|
user: { name: Cookie.get('userName') || '' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
checkPermission()
|
||||||
|
} else {
|
||||||
|
ElMessage.error('权限验证组件初始化失败')
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: #f0f2f5;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-section {
|
||||||
|
background: linear-gradient(135deg, #1d976c 0%, #93f9b9 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 12px 16px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-section {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form .form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form .form-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form .form-item.form-actions {
|
||||||
|
flex: none;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form .form-label {
|
||||||
|
width: 80px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form .form-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-section {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 12px 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-card :deep(.el-table) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-card :deep(.el-table .el-table__cell) {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-loading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 竖屏PAD优化 */
|
||||||
|
@media (orientation: portrait) {
|
||||||
|
.app-container {
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form .form-label {
|
||||||
|
width: 100px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
min-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__icon) {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-select .el-input__wrapper) {
|
||||||
|
min-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-button) {
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
813
src/views/InterfaceUseLog.vue
Normal file
813
src/views/InterfaceUseLog.vue
Normal file
@@ -0,0 +1,813 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 权限验证组件 -->
|
||||||
|
<PermissionVerify ref="permissionVerify" />
|
||||||
|
|
||||||
|
<!-- 主内容区域 - 权限验证通过后才显示 -->
|
||||||
|
<template v-if="isAuthenticated">
|
||||||
|
<div class="header-section">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="title-area" @click="navigateHome">
|
||||||
|
<h1 class="page-title">接口调用记录查询</h1>
|
||||||
|
<p class="page-subtitle">MOM接口数据查询系统</p>
|
||||||
|
</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<div style="display: flex; align-items: center; color: #fff; opacity: 0.95;">
|
||||||
|
登录人:{{ loginUserName }}
|
||||||
|
</div>
|
||||||
|
<el-button type="danger" icon="SwitchButton" @click="handleLogout" size="small">
|
||||||
|
退出登录
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查询条件区域 -->
|
||||||
|
<div class="search-section">
|
||||||
|
<div class="search-card">
|
||||||
|
<div class="search-form">
|
||||||
|
<!-- 第一行 -->
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">工位号:</label>
|
||||||
|
<el-select @change="handleSearch();" v-model="searchForm.stationNumber" placeholder="请选择工位号" filterable
|
||||||
|
clearable class="form-input">
|
||||||
|
<el-option v-for="item in stationList" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">工件编号:</label>
|
||||||
|
<el-input @change="handleSearch();" v-model="searchForm.workpieceNumber" placeholder="请输入工件编号" clearable
|
||||||
|
class="form-input" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第二行 -->
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">时间范围:</label>
|
||||||
|
<el-date-picker @change="handleSearch();" v-model="searchForm.dateRange" type="datetimerange"
|
||||||
|
range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss" :default-time="[
|
||||||
|
new Date(2000, 1, 1, 0, 0, 0),
|
||||||
|
new Date(2000, 1, 1, 23, 59, 59)
|
||||||
|
]" class="form-date-picker" />
|
||||||
|
</div>
|
||||||
|
<div class="form-item form-actions">
|
||||||
|
<el-button type="primary" icon="Search" @click="handleSearch" :loading="loading">
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<!-- <el-button icon="Refresh" @click="handleReset">
|
||||||
|
重置时间
|
||||||
|
</el-button> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 数据展示区域 -->
|
||||||
|
<div class="data-section">
|
||||||
|
<div class="data-card">
|
||||||
|
<el-table v-loading="loading" :data="tableData" :height="tableHeight" border stripe style="width: 100%"
|
||||||
|
:header-cell-style="{
|
||||||
|
background: '#f5f7fa',
|
||||||
|
color: '#303133',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'center'
|
||||||
|
}" :cell-style="{ textAlign: 'center' }">
|
||||||
|
<el-table-column type="index" label="序号" width="60" fixed="left" />
|
||||||
|
<!-- <el-table-column prop="ID" label="ID" width="80" /> -->
|
||||||
|
<el-table-column prop="发动机号" label="工件编号" width="260" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="工位号" label="工位号" width="100" />
|
||||||
|
<el-table-column prop="工位名称" label="工位名称" width="140" show-overflow-tooltip />
|
||||||
|
<!-- <el-table-column prop="接口名称" label="接口名称" width="220" show-overflow-tooltip /> -->
|
||||||
|
<el-table-column prop="接口说明" label="接口说明" show-overflow-tooltip min-width="140" />
|
||||||
|
<el-table-column prop="是否启用" label="是否启用" width="90">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="scope.row.是否启用 == '1' ? 'success' : 'danger'" size="small">
|
||||||
|
{{ scope.row.是否启用 }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="请求时间" label="请求时间" width="190" />
|
||||||
|
<!-- <el-table-column prop="返回时间" label="返回时间" width="190" /> -->
|
||||||
|
<el-table-column prop="返回代码" label="返回代码" width="100" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="scope.row.返回代码 === '0' ? 'success' : 'warning'" size="small">
|
||||||
|
{{ scope.row.返回代码 }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<!-- <el-table-column prop="累积用时" label="累积用时(ms)" width="110" /> -->
|
||||||
|
<!-- <el-table-column prop="操作时间" label="操作时间" width="160" /> -->
|
||||||
|
<el-table-column label="操作" width="200" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="warning" size="small" @click="handleUpdateReturnCode(scope.row)">
|
||||||
|
修改返回代码
|
||||||
|
</el-button>
|
||||||
|
<el-button link type="primary" size="small" @click="handleViewDetail(scope.row)">
|
||||||
|
查看详情
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="pagination-wrapper">
|
||||||
|
<el-pagination v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize"
|
||||||
|
:page-sizes="[20, 50, 100, 200]" :total="pagination.total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详情对话框 -->
|
||||||
|
<el-dialog v-model="detailDialog.visible" title="接口调用详情" width="800px" :close-on-click-modal="false">
|
||||||
|
<div class="detail-content">
|
||||||
|
<el-descriptions :column="2" border>
|
||||||
|
<el-descriptions-item label="ID">{{ detailDialog.data.ID }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="发动机号">{{ detailDialog.data.发动机号 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="工位号">{{ detailDialog.data.工位号 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="工位名称">{{ detailDialog.data.工位名称 }}</el-descriptions-item>
|
||||||
|
<!-- <el-descriptions-item label="接口名称" :span="2">{{ detailDialog.data.接口名称 }}</el-descriptions-item> -->
|
||||||
|
<el-descriptions-item label="接口说明" :span="2">{{ detailDialog.data.接口说明 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="请求时间">{{ detailDialog.data.请求时间 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="返回时间">{{ detailDialog.data.返回时间 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="返回代码">{{ detailDialog.data.返回代码 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="累积用时">{{ detailDialog.data.累积用时 }}ms</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<div style="margin: 5px 0; display: flex;">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="detailDialog.data.返回代码 === '0'"
|
||||||
|
@click="handleApiRetry(detailDialog.data)"
|
||||||
|
>
|
||||||
|
接口重试
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
:disabled="detailDialog.data.返回代码 === '0'"
|
||||||
|
@click="handleFlowRetry(detailDialog.data)"
|
||||||
|
>
|
||||||
|
流程重试
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-tabs v-model="detailDialog.activeTab" class="detail-tabs">
|
||||||
|
<el-tab-pane label="请求内容" name="request">
|
||||||
|
<div class="code-block">
|
||||||
|
<pre>{{ detailDialog.data.请求内容 || '无' }}</pre>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="返回内容" name="response">
|
||||||
|
<div class="code-block">
|
||||||
|
<pre>{{ detailDialog.data.返回内容 || '无' }}</pre>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 未登录时显示提示 -->
|
||||||
|
<div v-else class="auth-loading">
|
||||||
|
<i class="el-icon-loading"></i>
|
||||||
|
<p>正在进行权限验证...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted, inject, computed } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import PermissionVerify from '@/components/PermissionVerify.vue'
|
||||||
|
import Cookie from 'js-cookie'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
// 注入数据库执行方法
|
||||||
|
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
|
||||||
|
const CreateData = inject('CreateData')
|
||||||
|
const ExecDatabase = inject('ExecDatabase')
|
||||||
|
const ExecHttpRequest = inject('ExecHttpRequest')
|
||||||
|
|
||||||
|
// 路由
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 返回导航首页
|
||||||
|
const navigateHome = () => {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限验证相关
|
||||||
|
const isAuthenticated = ref(false)
|
||||||
|
const permissionVerify = ref(null)
|
||||||
|
const userInfo = ref({})
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const loading = ref(false)
|
||||||
|
const stationList = ref([])
|
||||||
|
const tableData = ref([])
|
||||||
|
const tableHeight = ref(500)
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({
|
||||||
|
stationNumber: '',
|
||||||
|
workpieceNumber: '',
|
||||||
|
dateRange: []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分页信息
|
||||||
|
const pagination = reactive({
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 50,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 详情对话框
|
||||||
|
const detailDialog = reactive({
|
||||||
|
visible: false,
|
||||||
|
data: {},
|
||||||
|
activeTab: 'request'
|
||||||
|
})
|
||||||
|
|
||||||
|
const loginUserName = computed(() => Cookie.get('userName') || userInfo.value.姓名 || userInfo.value.userName || '-')
|
||||||
|
|
||||||
|
// 计算表格高度
|
||||||
|
const calculateTableHeight = () => {
|
||||||
|
// 窗口高度 - 头部 - 搜索区域 - 分页 - 边距
|
||||||
|
tableHeight.value = window.innerHeight - 280
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限验证
|
||||||
|
const checkPermission = async () => {
|
||||||
|
console.log('checkPermission called')
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 优先检查Cookie是否已登录
|
||||||
|
const cookieUserId = Cookie.get('userId')
|
||||||
|
const cookieUserName = Cookie.get('userName')
|
||||||
|
if (cookieUserId && cookieUserName) {
|
||||||
|
isAuthenticated.value = true
|
||||||
|
userInfo.value = { UserID: cookieUserId, 姓名: cookieUserName }
|
||||||
|
ElMessage.success(`欢迎 ${cookieUserName} 登录系统`)
|
||||||
|
await initializeData()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Calling permissionVerify.show...')
|
||||||
|
const result = await permissionVerify.value.show({
|
||||||
|
title: '接口查询系统登录',
|
||||||
|
promptText: '请刷卡进行身份验证',
|
||||||
|
permissionLevel: '高级刷卡权限'
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Permission verify result:', result)
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
isAuthenticated.value = true
|
||||||
|
userInfo.value = result.data
|
||||||
|
ElMessage.success(`欢迎 ${result.data.姓名 || result.data.userName || '用户'} 登录系统`)
|
||||||
|
|
||||||
|
// 权限验证成功后初始化页面数据
|
||||||
|
await initializeData()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Permission verify error:', error)
|
||||||
|
|
||||||
|
if (error.cancelled) {
|
||||||
|
ElMessage.warning('已取消登录')
|
||||||
|
// 可以选择跳转到其他页面或关闭窗口
|
||||||
|
} else {
|
||||||
|
ElMessage.error('权限验证失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(async () => {
|
||||||
|
console.log('Index page mounted')
|
||||||
|
|
||||||
|
// 使用 setTimeout 确保组件完全加载
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Checking permissionVerify ref:', permissionVerify.value)
|
||||||
|
|
||||||
|
if (permissionVerify.value) {
|
||||||
|
console.log('PermissionVerify component found, injecting methods...')
|
||||||
|
|
||||||
|
// 为权限验证组件注入必要的方法
|
||||||
|
permissionVerify.value.setCreateData(CreateData)
|
||||||
|
permissionVerify.value.setExecDatabase(ExecDatabase)
|
||||||
|
permissionVerify.value.setStore({
|
||||||
|
state: {
|
||||||
|
station: { stationNumber: '' },
|
||||||
|
user: { name: Cookie.get('userName') || '' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Methods injected, calling checkPermission...')
|
||||||
|
// 进行权限验证(含Cookie免登录)
|
||||||
|
checkPermission()
|
||||||
|
} else {
|
||||||
|
console.error('PermissionVerify component not found!')
|
||||||
|
ElMessage.error('权限验证组件初始化失败')
|
||||||
|
}
|
||||||
|
}, 500) // 增加延迟时间确保组件加载
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化数据(权限验证通过后调用)
|
||||||
|
const initializeData = async () => {
|
||||||
|
calculateTableHeight()
|
||||||
|
window.addEventListener('resize', calculateTableHeight)
|
||||||
|
|
||||||
|
// 获取URL参数 - 处理hash路由的情况
|
||||||
|
let urlParams;
|
||||||
|
let stationNo = '';
|
||||||
|
let workpieceNo = '';
|
||||||
|
|
||||||
|
// 检查是否是hash路由
|
||||||
|
if (window.location.hash && window.location.hash.includes('?')) {
|
||||||
|
// 处理 http://localhost:5173/#/?stationNo=xxx&workpieceNo=xxx 格式
|
||||||
|
const hashParts = window.location.hash.split('?');
|
||||||
|
if (hashParts.length > 1) {
|
||||||
|
urlParams = new URLSearchParams(hashParts[1]);
|
||||||
|
stationNo = urlParams.get('stationNo');
|
||||||
|
workpieceNo = urlParams.get('workpieceNo');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 处理 http://localhost:5173/?stationNo=xxx&workpieceNo=xxx#/ 格式
|
||||||
|
urlParams = new URLSearchParams(window.location.search);
|
||||||
|
stationNo = urlParams.get('stationNo');
|
||||||
|
workpieceNo = urlParams.get('workpieceNo');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 始终设置默认时间范围为今天
|
||||||
|
const today = new Date()
|
||||||
|
const startTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0)
|
||||||
|
const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59)
|
||||||
|
searchForm.dateRange = [
|
||||||
|
formatDate(startTime, 'yyyy-MM-dd HH:mm:ss'),
|
||||||
|
formatDate(endTime, 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
]
|
||||||
|
|
||||||
|
// 加载工位列表
|
||||||
|
await loadStationList()
|
||||||
|
|
||||||
|
// 如果URL有参数,设置默认值
|
||||||
|
if (stationNo) searchForm.stationNumber = stationNo
|
||||||
|
if (workpieceNo) searchForm.workpieceNumber = workpieceNo
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
await handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
const formatDate = (date, format) => {
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0')
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||||
|
|
||||||
|
return format
|
||||||
|
.replace('yyyy', year)
|
||||||
|
.replace('MM', month)
|
||||||
|
.replace('dd', day)
|
||||||
|
.replace('HH', hours)
|
||||||
|
.replace('mm', minutes)
|
||||||
|
.replace('ss', seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载工位列表
|
||||||
|
const loadStationList = async () => {
|
||||||
|
try {
|
||||||
|
const param = []
|
||||||
|
const response = await ExecDatabaseByParam('11', 'MES_计划BOM_工位与名称_查询', param)
|
||||||
|
if (response.data && response.data.length > 0) {
|
||||||
|
stationList.value = response.data.map(item => ({
|
||||||
|
label: `${item.工位号} - ${item.工位名称 || ''}`,
|
||||||
|
value: item.工位号
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载工位列表失败:', error)
|
||||||
|
ElMessage.error('加载工位列表失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
|
const handleSearch = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// 构建查询参数
|
||||||
|
const param = []
|
||||||
|
param[0] = ['工位号', searchForm.stationNumber || '']
|
||||||
|
param[1] = ['工件编号', searchForm.workpieceNumber || '']
|
||||||
|
param[2] = ['开始时间', searchForm.dateRange[0] || '']
|
||||||
|
param[3] = ['结束时间', searchForm.dateRange[1] || '']
|
||||||
|
param[4] = ['PageCurrent', pagination.currentPage]
|
||||||
|
param[5] = ['PageSize', pagination.pageSize]
|
||||||
|
param[6] = ['PageCount', '0', 'int', '1']
|
||||||
|
param[7] = ['ItemCount', '0', 'int', '1']
|
||||||
|
|
||||||
|
const response = await ExecDatabaseByParam('11', 'MOM_接口_Moby_视图_分页_查询', param)
|
||||||
|
|
||||||
|
if (response.data && response.data.result) {
|
||||||
|
tableData.value = response.data.result
|
||||||
|
// 获取总数
|
||||||
|
if (response.data.output && response.data.output.length > 0) {
|
||||||
|
pagination.total = parseInt(response.data.output[0].ItemCount) || 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tableData.value = []
|
||||||
|
pagination.total = 0
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('查询失败:', error)
|
||||||
|
ElMessage.error('查询失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置查询条件
|
||||||
|
const handleReset = () => {
|
||||||
|
searchForm.stationNumber = ''
|
||||||
|
searchForm.workpieceNumber = ''
|
||||||
|
// 重置为今天
|
||||||
|
const today = new Date()
|
||||||
|
const startTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0)
|
||||||
|
const endTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59)
|
||||||
|
searchForm.dateRange = [
|
||||||
|
formatDate(startTime, 'yyyy-MM-dd HH:mm:ss'),
|
||||||
|
formatDate(endTime, 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
]
|
||||||
|
pagination.currentPage = 1
|
||||||
|
tableData.value = []
|
||||||
|
pagination.total = 0
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页大小改变
|
||||||
|
const handleSizeChange = (val) => {
|
||||||
|
pagination.pageSize = val
|
||||||
|
pagination.currentPage = 1
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前页改变
|
||||||
|
const handleCurrentChange = (val) => {
|
||||||
|
pagination.currentPage = val
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleViewDetail = (row) => {
|
||||||
|
detailDialog.data = row
|
||||||
|
detailDialog.visible = true
|
||||||
|
detailDialog.activeTab = 'request'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接口重试
|
||||||
|
const handleApiRetry = async (row) => {
|
||||||
|
try {
|
||||||
|
const retryData = {
|
||||||
|
MsgId: Math.random().toString(36).substring(2), // 随机生成UUID
|
||||||
|
UserId: Number(Cookie.get('userId') || userInfo.value.UserID || 0),
|
||||||
|
ID: row.ID,
|
||||||
|
OpName: row.工位号 || '',
|
||||||
|
EngineID: row.发动机号 || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await ExecHttpRequest(
|
||||||
|
window.g.API_MES_Controller_URL + '/ApiFailRetry',
|
||||||
|
'post',
|
||||||
|
retryData
|
||||||
|
)
|
||||||
|
|
||||||
|
// 检查返回结果
|
||||||
|
if (response.data && response.data.Code === '200') {
|
||||||
|
ElMessage.success('接口重试请求已发送')
|
||||||
|
// 关闭详情对话框
|
||||||
|
detailDialog.visible = false
|
||||||
|
// 刷新数据
|
||||||
|
await handleSearch()
|
||||||
|
} else {
|
||||||
|
// 处理失败情况
|
||||||
|
const errorMessage = response.data?.Message || '接口重试失败'
|
||||||
|
ElMessage.error(errorMessage)
|
||||||
|
console.error('接口重试失败:', response.data)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('接口重试失败:', error)
|
||||||
|
ElMessage.error('接口重试失败,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 流程重试
|
||||||
|
const handleFlowRetry = async (row) => {
|
||||||
|
try {
|
||||||
|
const retryData = {
|
||||||
|
MsgId: Math.random().toString(36).substring(2), // 随机生成UUID
|
||||||
|
UserId: Number(Cookie.get('userId') || userInfo.value.UserID || 0),
|
||||||
|
OpName: row.工位号 || '',
|
||||||
|
EngineID: row.发动机号 || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await ExecHttpRequest(
|
||||||
|
window.g.API_MES_Controller_URL + '/ApiFailRetry',
|
||||||
|
'post',
|
||||||
|
retryData
|
||||||
|
)
|
||||||
|
|
||||||
|
// 检查返回结果
|
||||||
|
if (response.data && response.data.Code === '200') {
|
||||||
|
ElMessage.success('流程重试请求已发送')
|
||||||
|
// 关闭详情对话框
|
||||||
|
detailDialog.visible = false
|
||||||
|
// 刷新数据
|
||||||
|
await handleSearch()
|
||||||
|
} else {
|
||||||
|
// 处理失败情况
|
||||||
|
const errorMessage = response.data?.Message || '流程重试失败'
|
||||||
|
ElMessage.error(errorMessage)
|
||||||
|
console.error('流程重试失败:', response.data)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('流程重试失败:', error)
|
||||||
|
ElMessage.error('流程重试失败,请稍后重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改返回代码
|
||||||
|
const handleUpdateReturnCode = async (row) => {
|
||||||
|
try {
|
||||||
|
const { value } = await ElMessageBox.prompt(
|
||||||
|
`当前返回代码:${row.返回代码 ?? '无'}\n请输入新的返回代码:`,
|
||||||
|
'修改返回代码',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确认修改',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
inputValue: row.返回代码 || '',
|
||||||
|
inputPlaceholder: '请输入返回代码,如0',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const newCode = (value ?? '').trim()
|
||||||
|
if (newCode === '') {
|
||||||
|
ElMessage.warning('返回代码不能为空')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const param = [
|
||||||
|
['ID', row.ID],
|
||||||
|
['返回代码', newCode]
|
||||||
|
]
|
||||||
|
const data = CreateData('12', 'MOM_接口_Moby_修改返回代码', param)
|
||||||
|
const res = await ExecDatabase(data)
|
||||||
|
if (res.data && res.data.length > 0 && res.data[0].result === '1') {
|
||||||
|
ElMessage.success('返回代码修改成功')
|
||||||
|
await handleSearch()
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.data?.[0]?.msg || '修改失败')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e !== 'cancel') {
|
||||||
|
console.error('修改返回代码失败:', e)
|
||||||
|
ElMessage.error('修改返回代码失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
'确定要退出登录吗?',
|
||||||
|
'提示',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
isAuthenticated.value = false
|
||||||
|
userInfo.value = {}
|
||||||
|
tableData.value = []
|
||||||
|
pagination.total = 0
|
||||||
|
|
||||||
|
// 清理登录Cookie
|
||||||
|
Cookie.remove('userId')
|
||||||
|
Cookie.remove('userName')
|
||||||
|
|
||||||
|
ElMessage.success('已退出登录')
|
||||||
|
|
||||||
|
// 重新返回首页
|
||||||
|
router.push('/')
|
||||||
|
} catch {
|
||||||
|
// 用户取消
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: #f0f2f5;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 头部样式
|
||||||
|
.header-section {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 20px 30px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-area {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限验证加载样式
|
||||||
|
.auth-loading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
color: #909399;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 40px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询区域样式
|
||||||
|
.search-section {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.search-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&.form-actions {
|
||||||
|
flex: none;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
width: 80px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-date-picker {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 360px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据区域样式
|
||||||
|
.data-section {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 20px 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.data-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.el-table__cell {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页样式
|
||||||
|
.pagination-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 详情对话框样式
|
||||||
|
.detail-content {
|
||||||
|
.detail-tabs {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
background: #f5f7fa;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式适配
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.search-form {
|
||||||
|
.form-row {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
min-width: 300px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1138
src/views/OpStatus.vue
Normal file
1138
src/views/OpStatus.vue
Normal file
File diff suppressed because it is too large
Load Diff
540
src/views/ThreeCodeVerify.vue
Normal file
540
src/views/ThreeCodeVerify.vue
Normal file
@@ -0,0 +1,540 @@
|
|||||||
|
<template>
|
||||||
|
<div class="three-code-page" :class="pageStatusClass">
|
||||||
|
<PermissionVerify ref="permissionVerifyRef" />
|
||||||
|
<template v-if="isAuthenticated">
|
||||||
|
<!-- 头部 -->
|
||||||
|
<div class="header-bar">
|
||||||
|
<div class="header-left" @click="navigateHome">
|
||||||
|
<h1>PACK下线三码校验</h1>
|
||||||
|
<p>Three-Code Verification</p>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<span class="user-info">操作人:{{ loginUserName }}</span>
|
||||||
|
<el-button type="danger" icon="SwitchButton" size="small" @click="handleLogout">退出登录</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主体 -->
|
||||||
|
<div class="main-body">
|
||||||
|
<!-- 扫码区 -->
|
||||||
|
<div class="scan-section">
|
||||||
|
<div class="scan-box">
|
||||||
|
<el-icon :size="26"><Aim /></el-icon>
|
||||||
|
<input ref="scanInputRef" v-model.trim="scanInput" class="scan-input"
|
||||||
|
placeholder="请使用扫码枪扫描任意码值(PACK码 / 箱体码 / 客户标签码)..."
|
||||||
|
@keyup.enter="handleScan" />
|
||||||
|
</div>
|
||||||
|
<div class="last-scan" v-if="lastScanCode">
|
||||||
|
<span class="ls-label">最近扫码:</span>
|
||||||
|
<span class="ls-value">{{ lastScanCode }}</span>
|
||||||
|
<span class="ls-time">{{ lastScanTime }}</span>
|
||||||
|
<el-tag :type="lastScanTagType" size="small">{{ lastScanResultText }}</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 整体状态横幅 -->
|
||||||
|
<div class="overall-banner" :class="overallBannerClass">
|
||||||
|
<el-icon :size="32"><component :is="overallIcon" /></el-icon>
|
||||||
|
<span class="overall-text">{{ overallStatusText }}</span>
|
||||||
|
<span class="overall-progress" v-if="overallProgress">({{ overallProgress }})</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 三码卡片 -->
|
||||||
|
<div class="code-cards">
|
||||||
|
<div class="code-card" :class="packCardClass">
|
||||||
|
<el-icon :size="24" class="card-icon"><component :is="packIcon" /></el-icon>
|
||||||
|
<span class="card-label">PACK码</span>
|
||||||
|
<span class="card-value">{{ productInfo.packCode || '—' }}</span>
|
||||||
|
<span class="card-status">{{ packStatusText }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="code-card" :class="boxCardClass">
|
||||||
|
<el-icon :size="24" class="card-icon"><component :is="boxIcon" /></el-icon>
|
||||||
|
<span class="card-label">箱体码</span>
|
||||||
|
<span class="card-value">{{ expectedBoxCode || '—' }}</span>
|
||||||
|
<span class="card-scan" v-if="scannedBoxCode && boxStatus !== 'idle'">扫:{{ scannedBoxCode }}</span>
|
||||||
|
<span class="card-status">{{ boxStatusText }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="code-card" :class="tagCardClass">
|
||||||
|
<el-icon :size="24" class="card-icon"><component :is="tagIcon" /></el-icon>
|
||||||
|
<span class="card-label">客户标签码</span>
|
||||||
|
<span class="card-value">{{ expectedClientTag || '—' }}</span>
|
||||||
|
<span class="card-scan" v-if="scannedClientTag && tagStatus !== 'idle'">扫:{{ scannedClientTag }}</span>
|
||||||
|
<span class="card-status">{{ tagStatusText }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 产品信息 -->
|
||||||
|
<div class="product-info" v-if="productInfo.productNo">
|
||||||
|
<div class="info-title">产品信息</div>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-item"><span class="il">产品型号</span><span class="iv">{{ productInfo.productType || '-' }}</span></div>
|
||||||
|
<div class="info-item"><span class="il">机型代码</span><span class="iv">{{ productInfo.engineTypeID || '-' }}</span></div>
|
||||||
|
<div class="info-item"><span class="il">模组码</span><span class="iv">{{ productInfo.moduleCode || '-' }}</span></div>
|
||||||
|
<div class="info-item"><span class="il">上线时间</span><span class="iv">{{ productInfo.onlineTime || '-' }}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 扫码历史 -->
|
||||||
|
<div class="scan-history" v-if="scanHistory.length">
|
||||||
|
<div class="history-title">扫码记录</div>
|
||||||
|
<div class="history-list">
|
||||||
|
<div v-for="(item, idx) in scanHistory" :key="idx" class="history-item" :class="'hi-' + item.result">
|
||||||
|
<span class="hi-time">{{ item.time }}</span>
|
||||||
|
<span class="hi-code">{{ item.code }}</span>
|
||||||
|
<el-tag :type="item.result === 'success' ? 'success' : item.result === 'error' ? 'warning' : 'info'" size="small">
|
||||||
|
{{ item.label }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else class="auth-loading"><p>正在进行权限验证...</p></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, onMounted, nextTick, inject } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import PermissionVerify from '@/components/PermissionVerify.vue'
|
||||||
|
import Cookie from 'js-cookie'
|
||||||
|
import { Aim, CircleCheck, Clock, Warning, CircleClose, Select, Loading, Remove } from '@element-plus/icons-vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const CreateData = inject('CreateData')
|
||||||
|
const ExecDatabase = inject('ExecDatabase')
|
||||||
|
const router = useRouter()
|
||||||
|
const navigateHome = () => router.push('/')
|
||||||
|
|
||||||
|
// === 权限 ===
|
||||||
|
const isAuthenticated = ref(false)
|
||||||
|
const permissionVerifyRef = ref(null)
|
||||||
|
const userInfo = ref({})
|
||||||
|
const loginUserName = computed(() => Cookie.get('userName') || userInfo.value.姓名 || '-')
|
||||||
|
|
||||||
|
const checkPermission = async () => {
|
||||||
|
try {
|
||||||
|
const ud = Cookie.get('userId'), un = Cookie.get('userName')
|
||||||
|
if (ud && un) { isAuthenticated.value = true; userInfo.value = { UserID: ud, 姓名: un }; focusScan(); return }
|
||||||
|
const result = await permissionVerifyRef.value.show({ title: '三码校验登录', promptText: '请刷卡进行身份验证', permissionLevel: '操作工' })
|
||||||
|
if (result.success) { isAuthenticated.value = true; userInfo.value = result.data; focusScan() }
|
||||||
|
} catch (e) { if (e?.cancelled) ElMessage.warning('已取消登录'); else ElMessage.error('权限验证失败') }
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定要退出登录吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
|
||||||
|
isAuthenticated.value = false; Cookie.remove('userId'); Cookie.remove('userName'); router.push('/')
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 扫码 ===
|
||||||
|
const scanInputRef = ref(null)
|
||||||
|
const scanInput = ref('')
|
||||||
|
const lastScanCode = ref('')
|
||||||
|
const lastScanTime = ref('')
|
||||||
|
const lastScanResult = ref('') // success | error | info
|
||||||
|
const lastScanResultText = ref('')
|
||||||
|
const lastScanTagType = computed(() => lastScanResult.value === 'success' ? 'success' : lastScanResult.value === 'error' ? 'warning' : 'info')
|
||||||
|
const scanHistory = ref([])
|
||||||
|
|
||||||
|
const focusScan = () => { nextTick(() => { scanInputRef.value?.focus() }) }
|
||||||
|
|
||||||
|
const addHistory = (code, result, label) => {
|
||||||
|
const now = new Date()
|
||||||
|
const t = `${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}:${String(now.getSeconds()).padStart(2,'0')}`
|
||||||
|
scanHistory.value.unshift({ code, result, label, time: t })
|
||||||
|
if (scanHistory.value.length > 20) scanHistory.value.pop()
|
||||||
|
lastScanCode.value = code; lastScanTime.value = t; lastScanResult.value = result; lastScanResultText.value = label
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 产品与码值 ===
|
||||||
|
const productInfo = reactive({ productNo: '', productType: '', engineTypeID: 0, packCode: '', moduleCode: '', boxCode: '', onlineTime: '' })
|
||||||
|
const expectedBoxCode = ref('')
|
||||||
|
const expectedClientTag = ref('')
|
||||||
|
const clientTagLoading = ref(false)
|
||||||
|
|
||||||
|
// 三码状态: idle | success | error
|
||||||
|
const packStatus = ref('idle')
|
||||||
|
const boxStatus = ref('idle')
|
||||||
|
const tagStatus = ref('idle')
|
||||||
|
const scannedBoxCode = ref('')
|
||||||
|
const scannedClientTag = ref('')
|
||||||
|
|
||||||
|
const resetAll = () => {
|
||||||
|
Object.assign(productInfo, { productNo: '', productType: '', engineTypeID: 0, packCode: '', moduleCode: '', boxCode: '', onlineTime: '' })
|
||||||
|
expectedBoxCode.value = ''; expectedClientTag.value = ''; clientTagLoading.value = false
|
||||||
|
packStatus.value = 'idle'; boxStatus.value = 'idle'; tagStatus.value = 'idle'
|
||||||
|
scannedBoxCode.value = ''; scannedClientTag.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 扫码主逻辑 ===
|
||||||
|
const handleScan = async () => {
|
||||||
|
const code = scanInput.value.trim()
|
||||||
|
scanInput.value = ''
|
||||||
|
if (!code) return
|
||||||
|
focusScan()
|
||||||
|
|
||||||
|
if (code.startsWith('001PBP000')) {
|
||||||
|
// PACK码 → 重置并查询
|
||||||
|
resetAll()
|
||||||
|
packStatus.value = 'success'
|
||||||
|
productInfo.packCode = code
|
||||||
|
productInfo.productNo = code
|
||||||
|
addHistory(code, 'success', 'PACK码 ✓ 已锁定')
|
||||||
|
await fetchProductInfo(code)
|
||||||
|
} else {
|
||||||
|
// 非PACK码 → 校验
|
||||||
|
if (!productInfo.productNo) {
|
||||||
|
addHistory(code, 'error', '请先扫描PACK码')
|
||||||
|
ElMessage.warning('请先扫描PACK码(001PBP000开头)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 三码已全部通过,忽略非PACK码输入
|
||||||
|
if (allSuccess.value) {
|
||||||
|
addHistory(code, 'info', '已完成,请扫下一个PACK码')
|
||||||
|
ElMessage.info('当前产品三码已校验完成,请扫描下一个PACK码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let matched = false
|
||||||
|
// 匹配箱体码
|
||||||
|
if (expectedBoxCode.value && code === expectedBoxCode.value) {
|
||||||
|
boxStatus.value = 'success'; scannedBoxCode.value = code; matched = true
|
||||||
|
addHistory(code, 'success', '箱体码 ✓')
|
||||||
|
}
|
||||||
|
// 匹配客户标签码
|
||||||
|
if (expectedClientTag.value && code === expectedClientTag.value) {
|
||||||
|
tagStatus.value = 'success'; scannedClientTag.value = code; matched = true
|
||||||
|
addHistory(code, 'success', '客户标签码 ✓')
|
||||||
|
}
|
||||||
|
if (!matched) {
|
||||||
|
// 判断更可能是哪个码
|
||||||
|
if (boxStatus.value !== 'success' && expectedBoxCode.value) {
|
||||||
|
boxStatus.value = 'error'; scannedBoxCode.value = code
|
||||||
|
} else if (tagStatus.value !== 'success' && expectedClientTag.value) {
|
||||||
|
tagStatus.value = 'error'; scannedClientTag.value = code
|
||||||
|
}
|
||||||
|
addHistory(code, 'error', '不匹配 ✗')
|
||||||
|
ElMessage.error('扫描的码值与期望不匹配,请核对!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否全部通过(含skip场景)
|
||||||
|
if (allSuccess.value) {
|
||||||
|
await saveVerifyRecord()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
focusScan()
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 查询产品信息 ===
|
||||||
|
const fetchProductInfo = async (packCode) => {
|
||||||
|
try {
|
||||||
|
const param = [['产品编号', packCode]]
|
||||||
|
const data = CreateData('11', 'PACK下线三码校验_查询', param)
|
||||||
|
const res = await ExecDatabase(data)
|
||||||
|
const rows = Array.isArray(res.data) ? res.data : []
|
||||||
|
if (!rows.length) { ElMessage.warning('未查询到该PACK码对应的产品信息'); return }
|
||||||
|
const row = rows[0]
|
||||||
|
productInfo.productType = row.产品型号 || ''
|
||||||
|
productInfo.engineTypeID = row.机型代码 || 0
|
||||||
|
productInfo.moduleCode = row.模组码 || ''
|
||||||
|
productInfo.boxCode = row.箱体码 || ''
|
||||||
|
productInfo.onlineTime = row.上线时间 || ''
|
||||||
|
// 机型代码2/3不扫描箱体码
|
||||||
|
if (productInfo.engineTypeID === 2 || productInfo.engineTypeID === 3) {
|
||||||
|
expectedBoxCode.value = ''
|
||||||
|
boxStatus.value = 'skip'
|
||||||
|
} else {
|
||||||
|
expectedBoxCode.value = row.箱体码 || ''
|
||||||
|
}
|
||||||
|
// 获取客户标签码
|
||||||
|
await fetchClientTag(packCode, productInfo.engineTypeID)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('查询产品信息失败:', e)
|
||||||
|
ElMessage.error('查询产品信息失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 获取客户标签码 ===
|
||||||
|
const fetchClientTag = async (engineID, engineTypeID) => {
|
||||||
|
if (!engineID || !engineTypeID) { expectedClientTag.value = ''; return }
|
||||||
|
clientTagLoading.value = true
|
||||||
|
try {
|
||||||
|
const resp = await axios.post(
|
||||||
|
`${window.g.API_MES_Controller_URL}/GetClientTagValue`,
|
||||||
|
{ MsgId: Date.now().toString(), EngineID: engineID, EngineTypeID: engineTypeID },
|
||||||
|
{ timeout: 15000 }
|
||||||
|
)
|
||||||
|
const d = resp.data
|
||||||
|
if ((d.Code === '200' || d.Code === 200) && d.TagValue) {
|
||||||
|
expectedClientTag.value = d.TagValue
|
||||||
|
} else {
|
||||||
|
expectedClientTag.value = ''
|
||||||
|
ElMessage.warning('获取客户标签码失败:' + (d.Message || '无可用标签码'))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取客户标签码失败:', e)
|
||||||
|
expectedClientTag.value = ''
|
||||||
|
ElMessage.error('获取客户标签码接口调用失败')
|
||||||
|
} finally {
|
||||||
|
clientTagLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 校验成功写库 ===
|
||||||
|
const saveVerifyRecord = async () => {
|
||||||
|
try {
|
||||||
|
const param = [
|
||||||
|
['产品编号', productInfo.productNo],
|
||||||
|
['产品型号', productInfo.productType],
|
||||||
|
['机型代码', productInfo.engineTypeID],
|
||||||
|
['期望箱体码', expectedBoxCode.value],
|
||||||
|
['期望客户标签码', expectedClientTag.value],
|
||||||
|
['扫描箱体码', scannedBoxCode.value],
|
||||||
|
['扫描客户标签码', scannedClientTag.value],
|
||||||
|
['箱体码校验结果', 1],
|
||||||
|
['客户标签码校验结果', 1],
|
||||||
|
['校验状态', 1],
|
||||||
|
['操作人', loginUserName.value]
|
||||||
|
]
|
||||||
|
const data = CreateData('11', 'PACK下线三码校验_记录_增加', param)
|
||||||
|
await ExecDatabase(data)
|
||||||
|
ElMessage.success('三码校验通过,已记录!')
|
||||||
|
addHistory('--- 三码校验完成 ---', 'success', '全部通过 ✓✓✓')
|
||||||
|
} catch (e) {
|
||||||
|
console.error('保存校验记录失败:', e)
|
||||||
|
ElMessage.error('校验记录保存失败,请联系管理员')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 计算属性:卡片样式 ===
|
||||||
|
const cardClass = (status) => status === 'success' ? 'card-success' : status === 'error' ? 'card-error' : 'card-idle'
|
||||||
|
const cardIcon = (status) => status === 'success' ? 'CircleCheck' : status === 'error' ? 'Warning' : status === 'skip' ? 'Remove' : 'Clock'
|
||||||
|
const packCardClass = computed(() => cardClass(packStatus.value))
|
||||||
|
const boxCardClass = computed(() => cardClass(boxStatus.value))
|
||||||
|
const tagCardClass = computed(() => cardClass(tagStatus.value))
|
||||||
|
const packIcon = computed(() => cardIcon(packStatus.value))
|
||||||
|
const boxIcon = computed(() => cardIcon(boxStatus.value))
|
||||||
|
const tagIcon = computed(() => cardIcon(tagStatus.value))
|
||||||
|
|
||||||
|
const packStatusText = computed(() => packStatus.value === 'success' ? '已锁定' : '待扫描')
|
||||||
|
const boxStatusText = computed(() => {
|
||||||
|
if (boxStatus.value === 'skip') return '无需扫描'
|
||||||
|
if (clientTagLoading.value && boxStatus.value === 'idle') return '待扫描'
|
||||||
|
return boxStatus.value === 'success' ? '校验通过' : boxStatus.value === 'error' ? '不匹配!请重扫' : expectedBoxCode.value ? '待扫描' : '等待PACK码'
|
||||||
|
})
|
||||||
|
const tagStatusText = computed(() => {
|
||||||
|
if (clientTagLoading.value) return '标签码获取中...'
|
||||||
|
return tagStatus.value === 'success' ? '校验通过' : tagStatus.value === 'error' ? '不匹配!请重扫' : expectedClientTag.value ? '待扫描' : '等待PACK码'
|
||||||
|
})
|
||||||
|
|
||||||
|
// === 整体状态 ===
|
||||||
|
const hasError = computed(() => boxStatus.value === 'error' || tagStatus.value === 'error')
|
||||||
|
const boxOk = computed(() => boxStatus.value === 'success' || boxStatus.value === 'skip')
|
||||||
|
const allSuccess = computed(() => packStatus.value === 'success' && boxOk.value && tagStatus.value === 'success')
|
||||||
|
const totalCodes = computed(() => boxStatus.value === 'skip' ? 2 : 3)
|
||||||
|
const successCount = computed(() => {
|
||||||
|
let c = 0
|
||||||
|
if (packStatus.value === 'success') c++
|
||||||
|
if (boxStatus.value === 'success') c++
|
||||||
|
if (tagStatus.value === 'success') c++
|
||||||
|
return c
|
||||||
|
})
|
||||||
|
|
||||||
|
const overallBannerClass = computed(() => allSuccess.value ? 'banner-success' : hasError.value ? 'banner-error' : 'banner-idle')
|
||||||
|
const overallStatusText = computed(() => {
|
||||||
|
if (allSuccess.value) return boxStatus.value === 'skip' ? '两码校验全部通过!' : '三码校验全部通过!'
|
||||||
|
if (hasError.value) return '校验不匹配,请核对后重新扫描'
|
||||||
|
if (!productInfo.productNo) return '请扫描PACK码开始校验'
|
||||||
|
return '校验进行中...'
|
||||||
|
})
|
||||||
|
const overallProgress = computed(() => {
|
||||||
|
if (!productInfo.productNo) return ''
|
||||||
|
if (allSuccess.value) return `${totalCodes.value}/${totalCodes.value}`
|
||||||
|
return `${successCount.value}/${totalCodes.value}`
|
||||||
|
})
|
||||||
|
const overallIcon = computed(() => allSuccess.value ? 'CircleCheck' : hasError.value ? 'Warning' : 'Clock')
|
||||||
|
const pageStatusClass = computed(() => allSuccess.value ? 'page-success' : hasError.value ? 'page-error' : '')
|
||||||
|
|
||||||
|
// === 生命周期 ===
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (permissionVerifyRef.value) {
|
||||||
|
permissionVerifyRef.value.setCreateData(CreateData)
|
||||||
|
permissionVerifyRef.value.setExecDatabase(ExecDatabase)
|
||||||
|
permissionVerifyRef.value.setStore({ state: { station: { stationNumber: '' }, user: { name: Cookie.get('userName') || '' } } })
|
||||||
|
checkPermission()
|
||||||
|
} else { ElMessage.error('权限验证组件初始化失败') }
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.three-code-page {
|
||||||
|
width: 100%; height: 100vh; display: flex; flex-direction: column;
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
overflow: hidden; transition: background 0.5s ease;
|
||||||
|
&.page-success { background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); }
|
||||||
|
&.page-error { background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 头部 - 紧凑
|
||||||
|
.header-bar {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: #fff; padding: 10px 20px; display: flex; justify-content: space-between; align-items: center;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15); flex-shrink: 0;
|
||||||
|
.header-left { cursor: pointer;
|
||||||
|
h1 { margin: 0; font-size: 20px; font-weight: 700; }
|
||||||
|
p { margin: 1px 0 0; font-size: 12px; opacity: 0.85; }
|
||||||
|
}
|
||||||
|
.header-right { display: flex; align-items: center; gap: 12px;
|
||||||
|
.user-info { font-size: 14px; opacity: 0.95; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主体 - 紧凑间距,禁止滚动
|
||||||
|
.main-body {
|
||||||
|
flex: 1; padding: 10px 16px; overflow: hidden;
|
||||||
|
display: flex; flex-direction: column; gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫码区
|
||||||
|
.scan-section {
|
||||||
|
flex-shrink: 0;
|
||||||
|
.scan-box {
|
||||||
|
display: flex; align-items: center; gap: 10px; background: #fff; border-radius: 10px;
|
||||||
|
padding: 8px 14px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); border: 2px solid #667eea;
|
||||||
|
.scan-input {
|
||||||
|
flex: 1; border: none; outline: none; font-size: 18px; font-weight: 500;
|
||||||
|
color: #303133; background: transparent; height: 34px;
|
||||||
|
&::placeholder { color: #c0c4cc; font-weight: 400; font-size: 15px; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.last-scan {
|
||||||
|
margin-top: 4px; padding: 2px 14px; display: flex; align-items: center; gap: 8px;
|
||||||
|
font-size: 14px; color: #606266;
|
||||||
|
.ls-label { color: #909399; }
|
||||||
|
.ls-value { font-family: 'Consolas', monospace; font-weight: 600; color: #303133;
|
||||||
|
max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.ls-time { color: #909399; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 整体状态横幅 - 醒目
|
||||||
|
.overall-banner {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex; align-items: center; gap: 12px; padding: 14px 20px; border-radius: 10px;
|
||||||
|
font-size: 22px; font-weight: 700; transition: all 0.4s ease;
|
||||||
|
&.banner-idle { background: #f4f4f5; color: #909399; border: 2px solid #dcdfe6; }
|
||||||
|
&.banner-success {
|
||||||
|
background: linear-gradient(135deg, #43a047 0%, #66bb6a 100%);
|
||||||
|
color: #fff; border: 2px solid #43a047;
|
||||||
|
box-shadow: 0 4px 20px rgba(67, 160, 71, 0.4);
|
||||||
|
animation: pulse-green 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
&.banner-error {
|
||||||
|
background: linear-gradient(135deg, #ef6c00 0%, #fb8c00 100%);
|
||||||
|
color: #fff; border: 2px solid #ef6c00;
|
||||||
|
box-shadow: 0 4px 20px rgba(239, 108, 0, 0.4);
|
||||||
|
animation: pulse-orange 1s ease-in-out 3;
|
||||||
|
}
|
||||||
|
.overall-progress { font-size: 18px; opacity: 0.9; font-weight: 600; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-green {
|
||||||
|
0%, 100% { box-shadow: 0 4px 20px rgba(67, 160, 71, 0.4); }
|
||||||
|
50% { box-shadow: 0 4px 40px rgba(67, 160, 71, 0.7); }
|
||||||
|
}
|
||||||
|
@keyframes pulse-orange {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.01); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 三码卡片 - 每行一个,横向排列内容
|
||||||
|
.code-cards {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex; flex-direction: column; gap: 6px;
|
||||||
|
}
|
||||||
|
.code-card {
|
||||||
|
border-radius: 10px; padding: 14px 20px; transition: all 0.4s ease;
|
||||||
|
border: 3px solid transparent;
|
||||||
|
display: flex; align-items: center; gap: 14px;
|
||||||
|
.card-icon { flex-shrink: 0; }
|
||||||
|
.card-label { font-size: 18px; font-weight: 700; min-width: 100px; flex-shrink: 0; }
|
||||||
|
.card-value {
|
||||||
|
flex: 1; font-size: 18px; font-family: 'Consolas', monospace; font-weight: 600;
|
||||||
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||||
|
}
|
||||||
|
.card-scan {
|
||||||
|
font-size: 14px; opacity: 0.8; max-width: 220px;
|
||||||
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||||
|
}
|
||||||
|
.card-status { font-size: 17px; font-weight: 700; flex-shrink: 0; min-width: 110px; text-align: right; }
|
||||||
|
|
||||||
|
&.card-idle {
|
||||||
|
background: #fff; border-color: #dcdfe6; color: #909399;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||||
|
.card-value { color: #606266; }
|
||||||
|
}
|
||||||
|
&.card-success {
|
||||||
|
background: linear-gradient(145deg, #43a047, #66bb6a); border-color: #388e3c; color: #fff;
|
||||||
|
box-shadow: 0 4px 20px rgba(67, 160, 71, 0.35);
|
||||||
|
.card-value { color: #fff; }
|
||||||
|
.card-status { color: rgba(255,255,255,0.95); }
|
||||||
|
}
|
||||||
|
&.card-error {
|
||||||
|
background: linear-gradient(145deg, #ef6c00, #fb8c00); border-color: #e65100; color: #fff;
|
||||||
|
box-shadow: 0 4px 20px rgba(239, 108, 0, 0.35);
|
||||||
|
animation: shake 0.5s ease-in-out;
|
||||||
|
.card-value { color: #fff; }
|
||||||
|
.card-status { color: rgba(255,255,255,0.95); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0%, 100% { transform: translateX(0); }
|
||||||
|
20% { transform: translateX(-6px); }
|
||||||
|
40% { transform: translateX(6px); }
|
||||||
|
60% { transform: translateX(-4px); }
|
||||||
|
80% { transform: translateX(4px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 产品信息 - 两列显示
|
||||||
|
.product-info {
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #fff; border-radius: 10px; padding: 12px 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||||
|
.info-title { font-size: 15px; font-weight: 600; color: #909399; margin-bottom: 8px; }
|
||||||
|
.info-grid {
|
||||||
|
display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px 20px;
|
||||||
|
.info-item {
|
||||||
|
.il { display: block; font-size: 14px; color: #909399; margin-bottom: 1px; }
|
||||||
|
.iv { display: block; font-size: 16px; color: #303133; font-weight: 600; word-break: break-all; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫码历史 - 占剩余空间但限制高度,内部滚动
|
||||||
|
.scan-history {
|
||||||
|
flex: 1; min-height: 0; max-height: 140px;
|
||||||
|
background: #fff; border-radius: 10px; padding: 6px 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||||
|
display: flex; flex-direction: column; overflow: hidden;
|
||||||
|
.history-title { font-size: 13px; font-weight: 600; color: #909399; margin-bottom: 2px; flex-shrink: 0; }
|
||||||
|
.history-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; }
|
||||||
|
.history-item {
|
||||||
|
display: flex; align-items: center; gap: 8px; padding: 2px 0; font-size: 13px;
|
||||||
|
border-bottom: 1px solid #f8f8f9;
|
||||||
|
.hi-time { color: #909399; min-width: 55px; font-size: 12px; }
|
||||||
|
.hi-code { flex: 1; font-family: 'Consolas', monospace; color: #303133; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限等待
|
||||||
|
.auth-loading { display: flex; justify-content: center; align-items: center; height: 100vh; color: #909399; font-size: 16px; }
|
||||||
|
|
||||||
|
// PAD响应式 - 小于480px才竖排
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.code-cards { grid-template-columns: 1fr; }
|
||||||
|
.product-info .info-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
843
src/views/electricRepairOnline.vue
Normal file
843
src/views/electricRepairOnline.vue
Normal file
@@ -0,0 +1,843 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<PermissionVerify ref="permissionVerify" />
|
||||||
|
|
||||||
|
<template v-if="isAuthenticated">
|
||||||
|
<div class="header-section">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="title-area" @click="navigateHome">
|
||||||
|
<h1 class="page-title">电检工艺返修上线</h1>
|
||||||
|
<p class="page-subtitle">Electrical Repair Upline</p>
|
||||||
|
</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<!-- 电检开关控件 -->
|
||||||
|
<div class="switch-control">
|
||||||
|
<span class="switch-label">电检工艺路线:</span>
|
||||||
|
<el-switch v-model="electricStatus" active-value="1" inactive-value="0" active-text="开启"
|
||||||
|
inactive-text="关闭" inline-prompt :loading="electricSwitchLoading"
|
||||||
|
@change="handleElectricStatusChange" />
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; align-items: center; color: #fff; opacity: 0.95; margin-left:16px;">
|
||||||
|
登录人:{{ loginUserName }}
|
||||||
|
</div>
|
||||||
|
<el-button type="danger" icon="SwitchButton" @click="handleLogout" size="small"
|
||||||
|
style="height: 30px; margin-left: 10px;">
|
||||||
|
退出登录
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scroll-container">
|
||||||
|
<div class="mqtt-status-bar" v-if="mqttStatus.show" :class="mqttStatus.type">
|
||||||
|
<div class="mqtt-status-content">
|
||||||
|
<el-icon class="status-icon">
|
||||||
|
<component :is="mqttStatus.icon" />
|
||||||
|
</el-icon>
|
||||||
|
<span>{{ mqttStatus.message }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 上半部分:扫码与产品信息 -->
|
||||||
|
<div class="top-section">
|
||||||
|
<div class="card">
|
||||||
|
<div class="form">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">产品扫码:</label>
|
||||||
|
<el-input v-model.trim="scanCode" placeholder="支持 Model/PACK/箱体 码"
|
||||||
|
@keyup.enter="handleScan" clearable prefix-icon="Scanner" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">pack码:</label>
|
||||||
|
<el-input v-model="formData.packNo" disabled prefix-icon="Tickets" />
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">发动机号:</label>
|
||||||
|
<el-input v-model="formData.engineNo" disabled prefix-icon="Memo" />
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">产品型号:</label>
|
||||||
|
<el-input v-model="formData.productType" disabled prefix-icon="Document" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<!-- <div class="form-item">
|
||||||
|
<label class="form-label">机型代码:</label>
|
||||||
|
<el-input v-model="formData.machineCode" disabled prefix-icon="Tools" />
|
||||||
|
</div> -->
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">上线时间:</label>
|
||||||
|
<el-input v-model="formData.uplineTime" disabled prefix-icon="Clock" />
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">下线时间:</label>
|
||||||
|
<el-input v-model="formData.downlineTime" disabled prefix-icon="Clock" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 下半部分:返修上线口选择 -->
|
||||||
|
<div class="bottom-section">
|
||||||
|
<div class="card">
|
||||||
|
<div class="form">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">返修上线口:</label>
|
||||||
|
<el-input v-model.trim="repairUplineInput" placeholder="扫描返修上线口二维码"
|
||||||
|
@keyup.enter="handleQueryRepairUpline" @input="handleRepairUplineInput"
|
||||||
|
@blur="handleRepairUplineBlur" clearable @clear="handleClearRepairUpline"
|
||||||
|
class="w-100">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Rank />
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="form-item">
|
||||||
|
<label class="form-label">再起始工位:</label>
|
||||||
|
<el-input model-value="999 - 默认工位" disabled prefix-icon="Location" class="w-100" />
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<!-- <div class="form-item">
|
||||||
|
<label class="form-label">返修描述:</label>
|
||||||
|
<el-input v-model.trim="formData.repairDesc" placeholder="请输入返修描述" clearable
|
||||||
|
prefix-icon="EditPen" />
|
||||||
|
</div> -->
|
||||||
|
<div class="form-item form-actions">
|
||||||
|
<el-button type="primary" :disabled="!canUpline" @click="handleUplineClick"
|
||||||
|
icon="Upload">电检返修上线</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 二次确认弹窗 -->
|
||||||
|
<el-dialog v-model="confirmDialog.visible" title="返修上线确认" width="560px" :close-on-click-modal="false">
|
||||||
|
<el-descriptions :column="1" border>
|
||||||
|
<el-descriptions-item label="pack码">{{ formData.packNo || '-' }}</el-descriptions-item>
|
||||||
|
<!-- <el-descriptions-item label="发动机号">{{ formData.engineNo || '-' }}</el-descriptions-item> -->
|
||||||
|
<el-descriptions-item label="产品型号">{{ formData.productType || '-' }}</el-descriptions-item>
|
||||||
|
<!-- <el-descriptions-item label="机型代码">{{ formData.machineCode || '-' }}</el-descriptions-item> -->
|
||||||
|
<!-- <el-descriptions-item label="返修描述">{{ formData.repairDesc || '-' }}</el-descriptions-item> -->
|
||||||
|
<!-- <el-descriptions-item label="再起始工位">999 (默认)</el-descriptions-item> -->
|
||||||
|
<el-descriptions-item label="返修上线口">{{ selectedRepairUplineStationLabel }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<template #footer>
|
||||||
|
<div style="display:flex; justify-content:flex-end; gap:8px;">
|
||||||
|
<el-button @click="confirmDialog.visible = false" icon="Close">取 消</el-button>
|
||||||
|
<el-button type="primary" :loading="confirmDialog.loading" @click="confirmPublish"
|
||||||
|
icon="Upload">确 认 上 线</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-else class="auth-loading">
|
||||||
|
<i class="el-icon-loading"></i>
|
||||||
|
<p>正在进行权限验证...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted, inject, computed, onBeforeUnmount, watch } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import PermissionVerify from '@/components/PermissionVerify.vue'
|
||||||
|
import Cookie from 'js-cookie'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { Location, Rank, Tickets, Memo, Document, Clock, EditPen, Upload, Close, Warning, CircleCheck, CircleClose } from '@element-plus/icons-vue'
|
||||||
|
import { Mqtt } from '@/utils/mqtt.js'
|
||||||
|
|
||||||
|
// 注入后端执行方法
|
||||||
|
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
|
||||||
|
const CreateData = inject('CreateData')
|
||||||
|
const ExecDatabase = inject('ExecDatabase')
|
||||||
|
|
||||||
|
// 路由
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 返回导航首页
|
||||||
|
const navigateHome = () => {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限
|
||||||
|
const isAuthenticated = ref(false)
|
||||||
|
const permissionVerify = ref(null)
|
||||||
|
const userInfo = ref({})
|
||||||
|
const loginUserName = computed(() => Cookie.get('userName') || userInfo.value.姓名 || userInfo.value.userName || '-')
|
||||||
|
|
||||||
|
// 电检开关
|
||||||
|
const electricStatus = ref('0')
|
||||||
|
const electricSwitchLoading = ref(false)
|
||||||
|
const E_STATUS_NAME = '电检工艺路线开启状态'
|
||||||
|
|
||||||
|
// 表单与数据
|
||||||
|
const scanCode = ref('')
|
||||||
|
const loadingScan = ref(false)
|
||||||
|
const formData = reactive({
|
||||||
|
id: null,
|
||||||
|
packNo: '',
|
||||||
|
engineNo: '',
|
||||||
|
machineCode: '',
|
||||||
|
productType: '',
|
||||||
|
uplineTime: '',
|
||||||
|
downlineTime: '',
|
||||||
|
repairDesc: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 下拉与选择
|
||||||
|
const uplineStationList = ref([]) // 返修上线口下拉
|
||||||
|
const selectedRepairUplineStation = ref('')
|
||||||
|
const repairUplineInput = ref('')
|
||||||
|
|
||||||
|
// 计算显示文本
|
||||||
|
const getLabelByValue = (listRef, value) => {
|
||||||
|
if (!value) return ''
|
||||||
|
const item = listRef.value.find(x => String(x.value) === String(value))
|
||||||
|
return item ? item.label : value
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedRepairUplineStationLabel = computed(() => getLabelByValue(uplineStationList, selectedRepairUplineStation.value))
|
||||||
|
|
||||||
|
// 提交按钮是否可用
|
||||||
|
const canUpline = computed(() => {
|
||||||
|
return !!(formData.packNo && selectedRepairUplineStation.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 持久化变量读取
|
||||||
|
const readPersistentValue = (name) => {
|
||||||
|
const param = []
|
||||||
|
param[0] = ['工位号', 'PLCS']
|
||||||
|
param[1] = ['名称', name]
|
||||||
|
const Data = CreateData('11', 'MES_持久化变量_读取', param)
|
||||||
|
return ExecDatabase(Data).then(response => {
|
||||||
|
const row = response.data && response.data.length ? response.data[0] : null
|
||||||
|
return row && row.值 ? String(row.值).trim() : ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 持久化变量写入
|
||||||
|
const writePersistentValue = (name, value) => {
|
||||||
|
const param = []
|
||||||
|
param[0] = ['工位号', 'PLCS']
|
||||||
|
param[1] = ['名称', name]
|
||||||
|
param[2] = ['值', value || '']
|
||||||
|
const Data = CreateData('12', 'MES_持久化变量_写入', param)
|
||||||
|
return ExecDatabase(Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化读取电检状态
|
||||||
|
const fetchElectricStatus = async () => {
|
||||||
|
try {
|
||||||
|
const val = await readPersistentValue(E_STATUS_NAME)
|
||||||
|
electricStatus.value = val === '1' ? '1' : '0'
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('读取电检工艺状态失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改电检状态
|
||||||
|
const handleElectricStatusChange = async (val) => {
|
||||||
|
electricSwitchLoading.value = true
|
||||||
|
try {
|
||||||
|
await writePersistentValue(E_STATUS_NAME, val)
|
||||||
|
ElMessage.success(`电检工艺路线已${val === '1' ? '开启' : '关闭'}`)
|
||||||
|
const newVal = await readPersistentValue(E_STATUS_NAME)
|
||||||
|
electricStatus.value = newVal === '1' ? '1' : '0'
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('设置电检工艺状态失败')
|
||||||
|
// 恢复旧值
|
||||||
|
const oldVal = await readPersistentValue(E_STATUS_NAME)
|
||||||
|
electricStatus.value = oldVal === '1' ? '1' : '0'
|
||||||
|
} finally {
|
||||||
|
electricSwitchLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MQTT连接状态
|
||||||
|
const mqttStatus = reactive({
|
||||||
|
show: false,
|
||||||
|
type: 'success', // success, error, warning
|
||||||
|
message: '',
|
||||||
|
icon: 'CircleCheck'
|
||||||
|
})
|
||||||
|
|
||||||
|
// MQTT
|
||||||
|
let mqttTimeout = null
|
||||||
|
let mqttClient = null
|
||||||
|
const mqttConnection = reactive({
|
||||||
|
host: window.g.mqttIP,
|
||||||
|
port: window.g.mqttPortNumber,
|
||||||
|
endpoint: '/mqtt',
|
||||||
|
clean: true,
|
||||||
|
connectTimeout: 4000,
|
||||||
|
reconnectPeriod: 4000,
|
||||||
|
clientId: '',
|
||||||
|
username: '',
|
||||||
|
password: ''
|
||||||
|
})
|
||||||
|
const publication = reactive({ topic: window.g.mqttPublishTopic, qos: 0, payload: '' })
|
||||||
|
const subscription = reactive({ topic: '', qos: 0 })
|
||||||
|
|
||||||
|
const createMqttConnection = () => {
|
||||||
|
try {
|
||||||
|
if (mqttClient) mqttClient.destroyConnection()
|
||||||
|
|
||||||
|
mqttStatus.show = true
|
||||||
|
mqttStatus.type = 'warning'
|
||||||
|
mqttStatus.icon = 'Warning'
|
||||||
|
mqttStatus.message = 'MQTT连接中...'
|
||||||
|
|
||||||
|
mqttClient = new Mqtt(mqttConnection)
|
||||||
|
mqttClient.createConnection()
|
||||||
|
|
||||||
|
mqttClient.client.on('connect', () => {
|
||||||
|
console.log('MQTT connected in electricRepairOnline')
|
||||||
|
mqttStatus.show = true
|
||||||
|
mqttStatus.type = 'success'
|
||||||
|
mqttStatus.icon = 'CircleCheck'
|
||||||
|
mqttStatus.message = 'MQTT已连接'
|
||||||
|
setTimeout(() => {
|
||||||
|
if (mqttStatus.type === 'success') {
|
||||||
|
mqttStatus.show = false
|
||||||
|
}
|
||||||
|
}, 3000)
|
||||||
|
})
|
||||||
|
|
||||||
|
mqttClient.client.on('reconnect', () => {
|
||||||
|
mqttStatus.show = true
|
||||||
|
mqttStatus.type = 'warning'
|
||||||
|
mqttStatus.icon = 'Warning'
|
||||||
|
mqttStatus.message = 'MQTT重新连接中...'
|
||||||
|
})
|
||||||
|
|
||||||
|
mqttClient.client.on('error', err => {
|
||||||
|
mqttStatus.show = true
|
||||||
|
mqttStatus.type = 'error'
|
||||||
|
mqttStatus.icon = 'CircleClose'
|
||||||
|
mqttStatus.message = `MQTT错误: ${err.message || '未知错误'}`
|
||||||
|
})
|
||||||
|
|
||||||
|
subscription.topic = window.g.mqttSubscriptionTopic
|
||||||
|
mqttClient.doSubscribe(subscription)
|
||||||
|
|
||||||
|
mqttClient.client.on('message', (topic, message) => {
|
||||||
|
const parts = message.toString().split('|')
|
||||||
|
const source = parts[0]
|
||||||
|
const command = parts[1]
|
||||||
|
if (source === 'MES' && command === 'RepairUplineMES') {
|
||||||
|
const returnedUplineStation = (parts[2] || '').trim()
|
||||||
|
const returnedProductNo = (parts[3] || '').trim()
|
||||||
|
// 发的是 packNo 或 barcode,以触发扫码的为准
|
||||||
|
if (
|
||||||
|
returnedUplineStation === String(selectedRepairUplineStation.value || '').trim() &&
|
||||||
|
returnedProductNo === String(scanCode.value || '').trim()
|
||||||
|
) {
|
||||||
|
onMqttRepairUplineSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error('MQTT setup failed', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMqttRepairUplineSuccess = async () => {
|
||||||
|
try {
|
||||||
|
clearTimeout(mqttTimeout)
|
||||||
|
confirmDialog.loading = false
|
||||||
|
confirmDialog.visible = false
|
||||||
|
|
||||||
|
// MES_工位返修记录_上线口增加 @ID, @返修上线口, @再起始工位, @返修描述
|
||||||
|
const param = []
|
||||||
|
param[0] = ['ID', formData.id || 0]
|
||||||
|
param[1] = ['返修上线口', selectedRepairUplineStation.value || '']
|
||||||
|
param[2] = ['再起始工位', '999']
|
||||||
|
param[3] = ['返修描述', formData.repairDesc || '']
|
||||||
|
|
||||||
|
const data = CreateData('12', 'MES_工位返修记录_上线口增加', param)
|
||||||
|
const res = await ExecDatabase(data)
|
||||||
|
if (res.data && res.data.length >= 0) {
|
||||||
|
if (res.data[0].result === "1" || res.data[0].result === 1) {
|
||||||
|
ElMessage.success('返修上线成功,已记录数据库')
|
||||||
|
resetForm(false)
|
||||||
|
} else {
|
||||||
|
ElMessage.error('数据库记录失败 请重试')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.warning('数据返回异常,请重发')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('数据库交互失败,请联系管理员')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫码查询产品信息
|
||||||
|
const handleScan = async () => {
|
||||||
|
if (!scanCode.value) {
|
||||||
|
ElMessage.warning('请先扫码或输入条码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loadingScan.value = true
|
||||||
|
try {
|
||||||
|
const param = []
|
||||||
|
param[0] = ['barCode', scanCode.value]
|
||||||
|
const data = CreateData('11', 'MES_电检艺路线返修上线_产品查询', param)
|
||||||
|
const res = await ExecDatabase(data)
|
||||||
|
|
||||||
|
const rows = Array.isArray(res.data) ? res.data : []
|
||||||
|
if (!rows.length) {
|
||||||
|
ElMessage.warning('未查询到该产品信息')
|
||||||
|
clearProduct()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = rows[0]
|
||||||
|
formData.id = row.ID || null
|
||||||
|
formData.packNo = row.pack码 || ''
|
||||||
|
formData.engineNo = row.发动机号 || ''
|
||||||
|
formData.machineCode = row.机型代码 || ''
|
||||||
|
formData.productType = row.产品型号 || ''
|
||||||
|
formData.uplineTime = row.上线时间 || ''
|
||||||
|
formData.downlineTime = row.下线时间 || ''
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('扫码查询异常:', e)
|
||||||
|
ElMessage.error('查询异常')
|
||||||
|
} finally {
|
||||||
|
loadingScan.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearProduct = () => {
|
||||||
|
formData.packNo = ''
|
||||||
|
formData.engineNo = ''
|
||||||
|
formData.machineCode = ''
|
||||||
|
formData.productType = ''
|
||||||
|
formData.uplineTime = ''
|
||||||
|
formData.downlineTime = ''
|
||||||
|
formData.repairDesc = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = (clearScan = true) => {
|
||||||
|
if (clearScan) scanCode.value = ''
|
||||||
|
clearProduct()
|
||||||
|
selectedRepairUplineStation.value = ''
|
||||||
|
repairUplineInput.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询返修上线口
|
||||||
|
const handleQueryRepairUpline = async () => {
|
||||||
|
if (!repairUplineInput.value) return
|
||||||
|
try {
|
||||||
|
const param = []
|
||||||
|
param[0] = ['OpCode', repairUplineInput.value]
|
||||||
|
const res = await ExecDatabaseByParam('11', 'MES_工位返修记录_编号查询上线口', param)
|
||||||
|
|
||||||
|
if (res.data && res.data.length > 0) {
|
||||||
|
const row = res.data[0]
|
||||||
|
selectedRepairUplineStation.value = row.工位号
|
||||||
|
repairUplineInput.value = `${row.工位号} - ${row.工位名称 || ''}`
|
||||||
|
} else {
|
||||||
|
ElMessage.warning('工位码不正确')
|
||||||
|
repairUplineInput.value = ''
|
||||||
|
selectedRepairUplineStation.value = ''
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('查询上线口失败')
|
||||||
|
repairUplineInput.value = ''
|
||||||
|
selectedRepairUplineStation.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClearRepairUpline = () => {
|
||||||
|
repairUplineInput.value = ''
|
||||||
|
selectedRepairUplineStation.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRepairUplineInput = () => {
|
||||||
|
selectedRepairUplineStation.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRepairUplineBlur = () => {
|
||||||
|
if (!selectedRepairUplineStation.value) {
|
||||||
|
repairUplineInput.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载返修上线口下拉 (沿用原接口)
|
||||||
|
const loadUplineStationList = async () => {
|
||||||
|
try {
|
||||||
|
const resUpline = await ExecDatabaseByParam('11', 'MES_计划BOM_工位与名称_查询_返修上线口', [])
|
||||||
|
if (resUpline.data && resUpline.data.length > 0) {
|
||||||
|
uplineStationList.value = resUpline.data.map(x => ({
|
||||||
|
label: `${x.工位号} - ${x.工位名称 || ''}`,
|
||||||
|
value: x.工位号
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('加载返修上线口列表失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 二次确认弹窗
|
||||||
|
const confirmDialog = reactive({ visible: false, loading: false })
|
||||||
|
const handleUplineClick = async () => {
|
||||||
|
if (!canUpline.value) {
|
||||||
|
ElMessage.warning('产品和返修上线口不能为空')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
confirmDialog.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmPublish = async () => {
|
||||||
|
try {
|
||||||
|
confirmDialog.loading = true
|
||||||
|
// 格式:WEB|RepairUplineMES|返修上线口工位|再起始工位号^产品编号(使用扫描入的code)^订单号^产品型号^产品型号代码
|
||||||
|
// 此模块固定999,很多字段为空也可以用下划线或者空代替,保持协议层一致
|
||||||
|
const pubCode = scanCode.value || ''
|
||||||
|
const pubType = formData.productType || ''
|
||||||
|
const pubTypeCode = formData.machineCode || '0'
|
||||||
|
const payload = `WEB|RepairUplineMES|${selectedRepairUplineStation.value}|999^${pubCode}^^${pubType}^${pubTypeCode}`
|
||||||
|
|
||||||
|
publication.topic = window.g.mqttPublishTopic
|
||||||
|
publication.payload = payload
|
||||||
|
console.log(publication)
|
||||||
|
const ok = mqttClient?.doPublish(publication)
|
||||||
|
if (ok) {
|
||||||
|
ElMessage.success('上线请求已发送,等待回执...')
|
||||||
|
mqttTimeout = setTimeout(() => {
|
||||||
|
confirmDialog.loading = false
|
||||||
|
ElMessage.error('超时,未收到后端确认消息,请重试')
|
||||||
|
}, 10000)
|
||||||
|
} else {
|
||||||
|
ElMessage.error('消息发送失败')
|
||||||
|
confirmDialog.loading = false
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error('发送异常')
|
||||||
|
confirmDialog.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录初始化相关
|
||||||
|
const checkPermission = async () => {
|
||||||
|
try {
|
||||||
|
const cookieUserId = Cookie.get('userId')
|
||||||
|
const cookieUserName = Cookie.get('userName')
|
||||||
|
if (cookieUserId && cookieUserName) {
|
||||||
|
isAuthenticated.value = true
|
||||||
|
userInfo.value = { UserID: cookieUserId, 姓名: cookieUserName }
|
||||||
|
ElMessage.success(`欢迎 ${cookieUserName} 登录`)
|
||||||
|
await initPage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 需要管理员权限
|
||||||
|
const result = await permissionVerify.value.show({
|
||||||
|
title: '电检返修上线登录',
|
||||||
|
promptText: '请刷卡验证 (管理员权限)',
|
||||||
|
permissionLevel: '管理员'
|
||||||
|
})
|
||||||
|
if (result.success) {
|
||||||
|
isAuthenticated.value = true
|
||||||
|
userInfo.value = result.data
|
||||||
|
ElMessage.success(`欢迎管理员 ${result.data.姓名 || result.data.userName} 登录`)
|
||||||
|
await initPage()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e.cancelled) {
|
||||||
|
ElMessage.warning('已取消登录')
|
||||||
|
} else {
|
||||||
|
ElMessage.error('权限验证失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initPage = async () => {
|
||||||
|
await loadUplineStationList()
|
||||||
|
await fetchElectricStatus()
|
||||||
|
createMqttConnection()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定退出吗?', '提示', { type: 'warning' })
|
||||||
|
isAuthenticated.value = false
|
||||||
|
resetForm()
|
||||||
|
Cookie.remove('userId')
|
||||||
|
Cookie.remove('userName')
|
||||||
|
router.push('/')
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastSubscribed = ref('')
|
||||||
|
const handleReSubscribe = () => {
|
||||||
|
if (!mqttClient || !mqttClient.client) return
|
||||||
|
const topic = window.g.mqttSubscriptionTopic + "/RepairUplinePAD"
|
||||||
|
if (lastSubscribed.value && lastSubscribed.value !== topic) {
|
||||||
|
mqttClient.doUnSubscribe({ topic: lastSubscribed.value })
|
||||||
|
}
|
||||||
|
subscription.topic = topic
|
||||||
|
mqttClient.doSubscribe(subscription)
|
||||||
|
lastSubscribed.value = topic
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => selectedRepairUplineStation.value,
|
||||||
|
async (newVal) => {
|
||||||
|
if (isAuthenticated.value) handleReSubscribe()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(() => confirmDialog.visible, (newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
clearTimeout(mqttTimeout)
|
||||||
|
confirmDialog.loading = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (permissionVerify.value) {
|
||||||
|
permissionVerify.value.setCreateData(CreateData)
|
||||||
|
permissionVerify.value.setExecDatabase(ExecDatabase)
|
||||||
|
permissionVerify.value.setStore({
|
||||||
|
state: {
|
||||||
|
station: { stationNumber: '' },
|
||||||
|
user: { name: Cookie.get('userName') || '' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
checkPermission()
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
try {
|
||||||
|
if (mqttClient) mqttClient.destroyConnection()
|
||||||
|
} catch { }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: #f0f2f5;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-section {
|
||||||
|
background: linear-gradient(135deg, #1f4037 0%, #99f2c8 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 12px 16px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-area {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 4px 0;
|
||||||
|
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-control {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-label {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-section,
|
||||||
|
.bottom-section {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item.form-actions {
|
||||||
|
flex: none;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
width: 135px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-right: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-100 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mqtt-status-bar {
|
||||||
|
padding: 8px 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mqtt-status-bar.success {
|
||||||
|
background-color: #f0f9ff;
|
||||||
|
color: #67c23a;
|
||||||
|
border-bottom: 1px solid #b3e19d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mqtt-status-bar.error {
|
||||||
|
background-color: #fef0f0;
|
||||||
|
color: #f56c6c;
|
||||||
|
border-bottom: 1px solid #fab6b6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mqtt-status-bar.warning {
|
||||||
|
background-color: #fdf6ec;
|
||||||
|
color: #e6a23c;
|
||||||
|
border-bottom: 1px solid #f5dab1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mqtt-status-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-loading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.form-row {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
min-width: 100%;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
width: 140px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: portrait) {
|
||||||
|
.app-container {
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
width: 140px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
min-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__icon) {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-button) {
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
388
src/views/index.vue
Normal file
388
src/views/index.vue
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 头部区域 -->
|
||||||
|
<div class="header-section">
|
||||||
|
<div class="header-content">
|
||||||
|
<div>
|
||||||
|
<h1 class="page-title">PDC线PAD模块路由导航</h1>
|
||||||
|
<p class="page-subtitle">PLCS系统集成导航页</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 欢迎区域 -->
|
||||||
|
<div class="welcome-section">
|
||||||
|
<div class="welcome-card">
|
||||||
|
<div class="welcome-content">
|
||||||
|
<div class="welcome-icon">
|
||||||
|
<el-icon size="60" color="#667eea">
|
||||||
|
<Grid />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="welcome-text">
|
||||||
|
<h2>PDC线PAD模块路由导航</h2>
|
||||||
|
<p>请选择以下功能模块进入相应系统</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 导航功能区 -->
|
||||||
|
<div class="navigation-section">
|
||||||
|
<div class="nav-grid">
|
||||||
|
<!-- PLCS接口调用记录 -->
|
||||||
|
<div class="nav-card" @click="navigateTo('/InterfaceUseLog')">
|
||||||
|
<div class="nav-icon">
|
||||||
|
<el-icon size="48" color="#667eea">
|
||||||
|
<DataBoard />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="nav-content">
|
||||||
|
<h3>PLCS接口调用记录</h3>
|
||||||
|
<p>查询MOM接口数据调用记录,支持接口重试和流程重试</p>
|
||||||
|
</div>
|
||||||
|
<div class="nav-arrow">
|
||||||
|
<el-icon>
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PLCS返修上线 -->
|
||||||
|
<div class="nav-card" @click="navigateTo('/repairOnline')">
|
||||||
|
<div class="nav-icon">
|
||||||
|
<el-icon size="48" color="#67c23a">
|
||||||
|
<RefreshRight />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="nav-content">
|
||||||
|
<h3>PLCS返修上线</h3>
|
||||||
|
<p>处理产品返修上线业务,选择再起始工位和返修上线口</p>
|
||||||
|
</div>
|
||||||
|
<div class="nav-arrow">
|
||||||
|
<el-icon>
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 电检工艺路线返修上线 -->
|
||||||
|
<div class="nav-card" @click="navigateTo('/electricRepairOnline')">
|
||||||
|
<div class="nav-icon">
|
||||||
|
<el-icon size="48" color="#1f4037">
|
||||||
|
<Connection />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="nav-content">
|
||||||
|
<h3>电检路线返修上线</h3>
|
||||||
|
<p>专门处理电检路线产品返修,附带工艺开关</p>
|
||||||
|
</div>
|
||||||
|
<div class="nav-arrow">
|
||||||
|
<el-icon>
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 返修记录查询 -->
|
||||||
|
<div class="nav-card" @click="navigateTo('/repairOnlineSearch')">
|
||||||
|
<div class="nav-icon">
|
||||||
|
<el-icon size="48" color="#f093fb">
|
||||||
|
<Search />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="nav-content">
|
||||||
|
<h3>返修记录查询</h3>
|
||||||
|
<p>查询产品返修记录,支持按工位、产品编号、状态筛选</p>
|
||||||
|
</div>
|
||||||
|
<div class="nav-arrow">
|
||||||
|
<el-icon>
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PLCS首件拍照上传 -->
|
||||||
|
<div class="nav-card" @click="navigateTo('/firstPartImageUpload')">
|
||||||
|
<div class="nav-icon">
|
||||||
|
<el-icon size="48" color="#e6a23c">
|
||||||
|
<Camera />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="nav-content">
|
||||||
|
<h3>PLCS首件拍照上传</h3>
|
||||||
|
<p>首件产品拍照上传系统</p>
|
||||||
|
</div>
|
||||||
|
<div class="nav-arrow">
|
||||||
|
<el-icon>
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PLCS工位异常处理 -->
|
||||||
|
<div class="nav-card" @click="navigateTo('/op')">
|
||||||
|
<div class="nav-icon">
|
||||||
|
<el-icon size="48" color="#f56c6c">
|
||||||
|
<Warning />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="nav-content">
|
||||||
|
<h3>PLCS工位异常处理</h3>
|
||||||
|
<p>处理工位进站、出站异常,申请条码重试等操作</p>
|
||||||
|
</div>
|
||||||
|
<div class="nav-arrow">
|
||||||
|
<el-icon>
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PACK下线三码校验 -->
|
||||||
|
<div class="nav-card" @click="navigateTo('/threeCodeVerify')">
|
||||||
|
<div class="nav-icon">
|
||||||
|
<el-icon size="48" color="#409eff">
|
||||||
|
<Aim />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="nav-content">
|
||||||
|
<h3>PACK下线三码校验</h3>
|
||||||
|
<p>扫描PACK码、箱体码、客户标签码进行三码一致性校验</p>
|
||||||
|
</div>
|
||||||
|
<div class="nav-arrow">
|
||||||
|
<el-icon>
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { Grid, DataBoard, RefreshRight, Connection, Camera, Warning, ArrowRight, Search, Aim } from '@element-plus/icons-vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
// 获取路由实例
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 页面跳转
|
||||||
|
const navigateTo = (path) => {
|
||||||
|
router.push(path)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 头部样式
|
||||||
|
.header-section {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 20px 30px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 欢迎区域
|
||||||
|
.welcome-section {
|
||||||
|
padding: 30px;
|
||||||
|
|
||||||
|
.welcome-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 30px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.welcome-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.welcome-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text {
|
||||||
|
h2 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
color: #303133;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导航区域
|
||||||
|
.navigation-section {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 30px 30px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.nav-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: linear-gradient(135deg, #f0f2f5 0%, #e8ecf2 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-content {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
color: #303133;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-arrow {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #c0c4cc;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 响应式适配
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.navigation-section {
|
||||||
|
.nav-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header-section {
|
||||||
|
padding: 16px 20px;
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-section {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.welcome-card {
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
.welcome-content {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.welcome-text {
|
||||||
|
h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation-section {
|
||||||
|
padding: 0 20px 20px;
|
||||||
|
|
||||||
|
.nav-card {
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.nav-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-content {
|
||||||
|
h3 {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1319
src/views/repairOnline.vue
Normal file
1319
src/views/repairOnline.vue
Normal file
File diff suppressed because it is too large
Load Diff
792
src/views/repairOnlineSearch.vue
Normal file
792
src/views/repairOnlineSearch.vue
Normal file
@@ -0,0 +1,792 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 权限验证组件 -->
|
||||||
|
<PermissionVerify ref="permissionVerify" />
|
||||||
|
|
||||||
|
<template v-if="isAuthenticated">
|
||||||
|
<!-- 顶部标题与用户信息 -->
|
||||||
|
<div class="header-section">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="title-area" @click="navigateHome">
|
||||||
|
<h1 class="page-title">返修记录查询</h1>
|
||||||
|
<p class="page-subtitle">Repair Record Search</p>
|
||||||
|
</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<div style="display:flex;align-items:center;color:#fff;opacity:.95;">
|
||||||
|
登录人:{{ loginUserName }}
|
||||||
|
</div>
|
||||||
|
<el-button type="danger" icon="SwitchButton" @click="handleLogout" size="small">
|
||||||
|
退出登录
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查询条件区域 -->
|
||||||
|
<div class="search-section">
|
||||||
|
<div class="search-card">
|
||||||
|
<div class="search-form">
|
||||||
|
<!-- 第一行 -->
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">工位号:</label>
|
||||||
|
<el-select v-model="searchForm.stationNumber" placeholder="请选择工位号" filterable clearable
|
||||||
|
class="form-input" @change="handleSearch">
|
||||||
|
<el-option v-for="item in stationList" :key="item.value" :label="item.label"
|
||||||
|
:value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">产品编号:</label>
|
||||||
|
<el-input v-model="searchForm.productNo" placeholder="请输入产品编号" clearable
|
||||||
|
class="form-input" @keyup.enter="handleSearch" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第二行 -->
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">返修状态:</label>
|
||||||
|
<el-select v-model="searchForm.repairStatus" placeholder="全部状态" clearable
|
||||||
|
class="form-input" @change="handleSearch">
|
||||||
|
<el-option label="全部状态" value="" />
|
||||||
|
<el-option label="未下线" value="0" />
|
||||||
|
<el-option label="已下线" value="1" />
|
||||||
|
<el-option label="回流中" value="2" />
|
||||||
|
<el-option label="已完成" value="3" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label">时间范围:</label>
|
||||||
|
<el-date-picker v-model="searchForm.dateRange" type="daterange" range-separator="至"
|
||||||
|
start-placeholder="开始日期" end-placeholder="结束日期" format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD" class="form-date-picker" @change="handleSearch" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第三行:操作按钮 -->
|
||||||
|
<div class="form-row form-row-actions">
|
||||||
|
<el-button type="primary" icon="Search" @click="handleSearch" :loading="loading"
|
||||||
|
size="large">
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<!-- <el-button icon="Refresh" @click="handleReset" size="large">
|
||||||
|
重置
|
||||||
|
</el-button> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 数据展示区域 -->
|
||||||
|
<div class="data-section">
|
||||||
|
<div class="data-card">
|
||||||
|
<el-table v-loading="loading" :data="tableData" :height="tableHeight" border stripe
|
||||||
|
style="width: 100%" :header-cell-style="{
|
||||||
|
background: '#f5f7fa',
|
||||||
|
color: '#303133',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'center'
|
||||||
|
}" :cell-style="{ textAlign: 'center' }">
|
||||||
|
<el-table-column type="index" label="序号" width="60" fixed="left" />
|
||||||
|
<el-table-column prop="订单号" label="订单号" width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="产品编号" label="产品编号" width="240" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="产品型号" label="产品型号" width="100" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="不合格工位" label="不合格工位" width="100" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="不合格时间" label="不合格时间" width="170" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="返修上线口" label="返修上线口" width="100" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="返修上线时间" label="返修上线时间" width="170" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="再起始工位" label="再起始工位" width="100" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="再起始工位到达时间" label="再起始工位到达时间" width="170" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="返修描述" label="返修描述" min-width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column label="返修状态" width="100" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="getStatusType(scope.row.返修状态)" size="small"
|
||||||
|
@dblclick="handleStatusDblClick(scope.row)">
|
||||||
|
{{ getStatusText(scope.row.返修状态) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="pagination-wrapper">
|
||||||
|
<el-pagination v-model:current-page="pagination.currentPage"
|
||||||
|
v-model:page-size="pagination.pageSize" :page-sizes="[20, 50, 100, 200]"
|
||||||
|
:total="pagination.total" layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="handleSizeChange" @current-change="handleCurrentChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详情对话框 -->
|
||||||
|
<el-dialog v-model="detailDialog.visible" title="返修记录详情" width="90%" :close-on-click-modal="false">
|
||||||
|
<div class="detail-content">
|
||||||
|
<el-descriptions :column="1" border size="large">
|
||||||
|
<el-descriptions-item label="订单号">{{ detailDialog.data.订单号 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="产品编号">{{ detailDialog.data.产品编号 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="产品型号">{{ detailDialog.data.产品型号 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="不合格工位">{{ detailDialog.data.不合格工位 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="不合格时间">{{ detailDialog.data.不合格时间 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="返修上线口">{{ detailDialog.data.返修上线口 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="返修上线时间">{{ detailDialog.data.返修上线时间 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="再起始工位">{{ detailDialog.data.再起始工位 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="再起始工位到达时间">{{ detailDialog.data.再起始工位到达时间 }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="返修描述">{{ detailDialog.data.返修描述 || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="返修状态">
|
||||||
|
<el-tag :type="getStatusType(detailDialog.data.返修状态)">
|
||||||
|
{{ getStatusText(detailDialog.data.返修状态) }}
|
||||||
|
</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="detailDialog.visible = false" size="large">关 闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 未登录时显示提示 -->
|
||||||
|
<div v-else class="auth-loading">
|
||||||
|
<i class="el-icon-loading"></i>
|
||||||
|
<p>正在进行权限验证...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted, inject, computed } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import PermissionVerify from '@/components/PermissionVerify.vue'
|
||||||
|
import Cookie from 'js-cookie'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
// 注入数据库执行方法
|
||||||
|
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
|
||||||
|
const CreateData = inject('CreateData')
|
||||||
|
const ExecDatabase = inject('ExecDatabase')
|
||||||
|
|
||||||
|
// 路由
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 返回导航首页
|
||||||
|
const navigateHome = () => {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限验证相关
|
||||||
|
const isAuthenticated = ref(false)
|
||||||
|
const permissionVerify = ref(null)
|
||||||
|
const userInfo = ref({})
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const loading = ref(false)
|
||||||
|
const stationList = ref([])
|
||||||
|
const tableData = ref([])
|
||||||
|
const tableHeight = ref(500)
|
||||||
|
|
||||||
|
// 搜索表单
|
||||||
|
const searchForm = reactive({
|
||||||
|
stationNumber: '',
|
||||||
|
productNo: '',
|
||||||
|
repairStatus: '',
|
||||||
|
dateRange: []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分页信息
|
||||||
|
const pagination = reactive({
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 50,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 详情对话框
|
||||||
|
const detailDialog = reactive({
|
||||||
|
visible: false,
|
||||||
|
data: {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const loginUserName = computed(() => Cookie.get('userName') || userInfo.value.姓名 || userInfo.value.userName || '-')
|
||||||
|
|
||||||
|
// 计算表格高度
|
||||||
|
const calculateTableHeight = () => {
|
||||||
|
tableHeight.value = window.innerHeight - 320
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取状态类型
|
||||||
|
const getStatusType = (status) => {
|
||||||
|
const statusMap = {
|
||||||
|
'0': 'danger', // 未下线
|
||||||
|
'1': 'warning', // 已下线
|
||||||
|
'2': 'info', // 回流中
|
||||||
|
'3': 'success' // 已完成
|
||||||
|
}
|
||||||
|
return statusMap[String(status)] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取状态文本
|
||||||
|
const getStatusText = (status) => {
|
||||||
|
const statusMap = {
|
||||||
|
'0': '未下线',
|
||||||
|
'1': '已下线',
|
||||||
|
'2': '回流中',
|
||||||
|
'3': '已完成'
|
||||||
|
}
|
||||||
|
return statusMap[String(status)] || '未知状态'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限验证
|
||||||
|
const checkPermission = async () => {
|
||||||
|
try {
|
||||||
|
// 优先检查Cookie是否已登录
|
||||||
|
const cookieUserId = Cookie.get('userId')
|
||||||
|
const cookieUserName = Cookie.get('userName')
|
||||||
|
if (cookieUserId && cookieUserName) {
|
||||||
|
isAuthenticated.value = true
|
||||||
|
userInfo.value = { UserID: cookieUserId, 姓名: cookieUserName }
|
||||||
|
ElMessage.success(`欢迎 ${cookieUserName} 登录系统`)
|
||||||
|
await initializeData()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await permissionVerify.value.show({
|
||||||
|
title: '返修记录查询登录',
|
||||||
|
promptText: '请刷卡进行身份验证',
|
||||||
|
permissionLevel: '高级刷卡权限'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
isAuthenticated.value = true
|
||||||
|
userInfo.value = result.data
|
||||||
|
ElMessage.success(`欢迎 ${result.data.姓名 || result.data.userName || '用户'} 登录系统`)
|
||||||
|
await initializeData()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.cancelled) {
|
||||||
|
ElMessage.warning('已取消登录')
|
||||||
|
} else {
|
||||||
|
ElMessage.error('权限验证失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(async () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (permissionVerify.value) {
|
||||||
|
permissionVerify.value.setCreateData(CreateData)
|
||||||
|
permissionVerify.value.setExecDatabase(ExecDatabase)
|
||||||
|
permissionVerify.value.setStore({
|
||||||
|
state: {
|
||||||
|
station: { stationNumber: '' },
|
||||||
|
user: { name: Cookie.get('userName') || '' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
checkPermission()
|
||||||
|
} else {
|
||||||
|
ElMessage.error('权限验证组件初始化失败')
|
||||||
|
}
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化数据(权限验证通过后调用)
|
||||||
|
const initializeData = async () => {
|
||||||
|
calculateTableHeight()
|
||||||
|
window.addEventListener('resize', calculateTableHeight)
|
||||||
|
|
||||||
|
// 初始化时间范围为最近30天
|
||||||
|
const today = new Date()
|
||||||
|
const thirtyDaysAgo = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000)
|
||||||
|
searchForm.dateRange = [formatDate(thirtyDaysAgo), formatDate(today)]
|
||||||
|
|
||||||
|
// 加载工位列表
|
||||||
|
await loadStationList()
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
await handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
const formatDate = (date) => {
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载工位列表
|
||||||
|
const loadStationList = async () => {
|
||||||
|
try {
|
||||||
|
const param = []
|
||||||
|
const response = await ExecDatabaseByParam('11', 'MES_计划BOM_工位与名称_查询', param)
|
||||||
|
if (response.data && response.data.length > 0) {
|
||||||
|
stationList.value = response.data.map(item => ({
|
||||||
|
label: `${item.工位号} - ${item.工位名称 || ''}`,
|
||||||
|
value: item.工位号
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载工位列表失败:', error)
|
||||||
|
ElMessage.error('加载工位列表失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询数据
|
||||||
|
const handleSearch = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
// 构建查询参数 - 复用存储过程 MES_工位返修记录_分页_查询
|
||||||
|
const param = []
|
||||||
|
param[0] = ['工位号', searchForm.stationNumber || '']
|
||||||
|
param[1] = ['产品编号', searchForm.productNo || '']
|
||||||
|
param[2] = ['返修状态', searchForm.repairStatus || '']
|
||||||
|
param[3] = ['开始时间', searchForm.dateRange && searchForm.dateRange[0] ? searchForm.dateRange[0] + ' 00:00:00' : '']
|
||||||
|
param[4] = ['结束时间', searchForm.dateRange && searchForm.dateRange[1] ? searchForm.dateRange[1] + ' 23:59:59' : '']
|
||||||
|
param[5] = ['PageCurrent', pagination.currentPage]
|
||||||
|
param[6] = ['PageSize', pagination.pageSize]
|
||||||
|
param[7] = ['PageCount', '1111', 'int', '1']
|
||||||
|
param[8] = ['ItemCount', '1111', 'int', '1']
|
||||||
|
|
||||||
|
const response = await ExecDatabaseByParam('11', 'MES_工位返修记录_分页_查询', param)
|
||||||
|
|
||||||
|
if (response.data && response.data.result) {
|
||||||
|
tableData.value = response.data.result
|
||||||
|
// 获取总数
|
||||||
|
if (response.data.output && response.data.output.length > 0) {
|
||||||
|
pagination.total = parseInt(response.data.output[0].ItemCount) || 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tableData.value = []
|
||||||
|
pagination.total = 0
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('查询失败:', error)
|
||||||
|
ElMessage.error('查询失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置查询条件
|
||||||
|
const handleReset = () => {
|
||||||
|
searchForm.stationNumber = ''
|
||||||
|
searchForm.productNo = ''
|
||||||
|
searchForm.repairStatus = ''
|
||||||
|
// 重置为最近30天
|
||||||
|
const today = new Date()
|
||||||
|
const thirtyDaysAgo = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000)
|
||||||
|
searchForm.dateRange = [formatDate(thirtyDaysAgo), formatDate(today)]
|
||||||
|
pagination.currentPage = 1
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 双击返修状态列 - 隐蔽的删除功能
|
||||||
|
const handleStatusDblClick = async (row) => {
|
||||||
|
// 只有非完成状态才允许删除(状态 !== 3)
|
||||||
|
if (String(row.返修状态) === '3') {
|
||||||
|
return // 已完成状态不允许删除,静默忽略
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`确定要删除该返修记录吗?\n产品编号:${row.产品编号 || '-'}\n当前状态:${getStatusText(row.返修状态)}`,
|
||||||
|
'删除返修记录',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定删除',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
confirmButtonClass: 'el-button--danger'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 调用删除存储过程
|
||||||
|
const param = []
|
||||||
|
param[0] = ['ID', row.ID || '']
|
||||||
|
const data = CreateData('12', 'MES_工位返修记录_删除记录', param)
|
||||||
|
const res = await ExecDatabase(data)
|
||||||
|
|
||||||
|
if (res.data && res.data.length >= 0) {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
// 刷新列表
|
||||||
|
await handleSearch()
|
||||||
|
} else {
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e !== 'cancel') {
|
||||||
|
console.error('删除失败:', e)
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页大小改变
|
||||||
|
const handleSizeChange = (val) => {
|
||||||
|
pagination.pageSize = val
|
||||||
|
pagination.currentPage = 1
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前页改变
|
||||||
|
const handleCurrentChange = (val) => {
|
||||||
|
pagination.currentPage = val
|
||||||
|
handleSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleViewDetail = (row) => {
|
||||||
|
detailDialog.data = row
|
||||||
|
detailDialog.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
'确定要退出登录吗?',
|
||||||
|
'提示',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
isAuthenticated.value = false
|
||||||
|
userInfo.value = {}
|
||||||
|
tableData.value = []
|
||||||
|
pagination.total = 0
|
||||||
|
|
||||||
|
// 清理登录Cookie
|
||||||
|
Cookie.remove('userId')
|
||||||
|
Cookie.remove('userName')
|
||||||
|
|
||||||
|
ElMessage.success('已退出登录')
|
||||||
|
|
||||||
|
// 重新返回首页
|
||||||
|
router.push('/')
|
||||||
|
} catch {
|
||||||
|
// 用户取消
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.app-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
background: #f0f2f5;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 头部样式
|
||||||
|
.header-section {
|
||||||
|
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 16px 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-area {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限验证加载样式
|
||||||
|
.auth-loading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
color: #909399;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 40px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询区域样式
|
||||||
|
.search-section {
|
||||||
|
padding: 12px 16px;
|
||||||
|
|
||||||
|
.search-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.form-row-actions {
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 280px;
|
||||||
|
|
||||||
|
&.form-actions {
|
||||||
|
flex: none;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
width: 80px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-date-picker {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 260px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据区域样式
|
||||||
|
.data-section {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 16px 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.data-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.el-table__cell {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页样式
|
||||||
|
.pagination-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 详情对话框样式
|
||||||
|
.detail-content {
|
||||||
|
:deep(.el-descriptions) {
|
||||||
|
.el-descriptions__label {
|
||||||
|
font-weight: 600;
|
||||||
|
width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-descriptions__content {
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触屏优化 - PAD竖屏适配
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.header-section {
|
||||||
|
padding: 12px 16px;
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-section {
|
||||||
|
padding: 10px 12px;
|
||||||
|
|
||||||
|
.search-card {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
.form-row {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
width: 90px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-section {
|
||||||
|
padding: 0 12px 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PAD触屏优化 - 增大点击区域
|
||||||
|
@media (orientation: portrait) {
|
||||||
|
.app-container {
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form .form-label {
|
||||||
|
width: 90px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
min-height: 44px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-select .el-input__wrapper) {
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-button) {
|
||||||
|
height: 34px;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-button--large) {
|
||||||
|
height: 34px;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 14px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-date-editor) {
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tag) {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-pagination) {
|
||||||
|
|
||||||
|
.el-pagination__total,
|
||||||
|
.el-pagination__sizes,
|
||||||
|
.el-pagination__jump {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-pager li {
|
||||||
|
min-width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
line-height: 36px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-prev,
|
||||||
|
.btn-next {
|
||||||
|
min-width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 横屏优化
|
||||||
|
@media (orientation: landscape) and (max-height: 600px) {
|
||||||
|
.header-section {
|
||||||
|
padding: 8px 16px;
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-section {
|
||||||
|
padding: 8px 12px;
|
||||||
|
|
||||||
|
.search-card {
|
||||||
|
padding: 10px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
48
vite.config.js
Normal file
48
vite.config.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
base: './',
|
||||||
|
plugins: [
|
||||||
|
vue()
|
||||||
|
],
|
||||||
|
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 // 仅拷贝一次
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
54
vite.config.js.timestamp-1776568467146-217c9cf11583d.mjs
Normal file
54
vite.config.js.timestamp-1776568467146-217c9cf11583d.mjs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// vite.config.js
|
||||||
|
import { defineConfig } from "file:///D:/%E9%A1%B9%E7%9B%AE%E6%96%87%E4%BB%B6/52.%E6%99%BA%E6%9F%94PDC%E7%BA%BF/%E5%89%8D%E7%AB%AF/MES_ApiFailRetry/node_modules/vite/dist/node/index.js";
|
||||||
|
import vue from "file:///D:/%E9%A1%B9%E7%9B%AE%E6%96%87%E4%BB%B6/52.%E6%99%BA%E6%9F%94PDC%E7%BA%BF/%E5%89%8D%E7%AB%AF/MES_ApiFailRetry/node_modules/@vitejs/plugin-vue/dist/index.mjs";
|
||||||
|
import { fileURLToPath, URL } from "node:url";
|
||||||
|
import copy from "file:///D:/%E9%A1%B9%E7%9B%AE%E6%96%87%E4%BB%B6/52.%E6%99%BA%E6%9F%94PDC%E7%BA%BF/%E5%89%8D%E7%AB%AF/MES_ApiFailRetry/node_modules/rollup-plugin-copy/dist/index.commonjs.js";
|
||||||
|
var __vite_injected_original_import_meta_url = "file:///D:/%E9%A1%B9%E7%9B%AE%E6%96%87%E4%BB%B6/52.%E6%99%BA%E6%9F%94PDC%E7%BA%BF/%E5%89%8D%E7%AB%AF/MES_ApiFailRetry/vite.config.js";
|
||||||
|
var vite_config_default = defineConfig({
|
||||||
|
base: "./",
|
||||||
|
plugins: [
|
||||||
|
vue()
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": fileURLToPath(new URL("./src", __vite_injected_original_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
|
||||||
|
// 仅拷贝一次
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
export {
|
||||||
|
vite_config_default as default
|
||||||
|
};
|
||||||
|
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcuanMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFxcdTk4NzlcdTc2RUVcdTY1ODdcdTRFRjZcXFxcNTIuXHU2NjdBXHU2N0Q0UERDXHU3RUJGXFxcXFx1NTI0RFx1N0FFRlxcXFxNRVNfQXBpRmFpbFJldHJ5XCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCJEOlxcXFxcdTk4NzlcdTc2RUVcdTY1ODdcdTRFRjZcXFxcNTIuXHU2NjdBXHU2N0Q0UERDXHU3RUJGXFxcXFx1NTI0RFx1N0FFRlxcXFxNRVNfQXBpRmFpbFJldHJ5XFxcXHZpdGUuY29uZmlnLmpzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9EOi8lRTklQTElQjklRTclOUIlQUUlRTYlOTYlODclRTQlQkIlQjYvNTIuJUU2JTk5JUJBJUU2JTlGJTk0UERDJUU3JUJBJUJGLyVFNSU4OSU4RCVFNyVBQiVBRi9NRVNfQXBpRmFpbFJldHJ5L3ZpdGUuY29uZmlnLmpzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSAndml0ZSdcbmltcG9ydCB2dWUgZnJvbSAnQHZpdGVqcy9wbHVnaW4tdnVlJ1xuaW1wb3J0IHsgZmlsZVVSTFRvUGF0aCwgVVJMIH0gZnJvbSBcIm5vZGU6dXJsXCI7XG5pbXBvcnQgY29weSBmcm9tICdyb2xsdXAtcGx1Z2luLWNvcHknXG5pbXBvcnQgeyByZXNvbHZlIH0gZnJvbSAncGF0aCdcblxuLy8gaHR0cHM6Ly92aXRlLmRldi9jb25maWcvXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xuICAgIGJhc2U6ICcuLycsXG4gICAgcGx1Z2luczogW1xuICAgICAgICB2dWUoKVxuICAgIF0sXG4gICAgcmVzb2x2ZToge1xuICAgICAgICBhbGlhczoge1xuICAgICAgICAgICAgXCJAXCI6IGZpbGVVUkxUb1BhdGgobmV3IFVSTChcIi4vc3JjXCIsIGltcG9ydC5tZXRhLnVybCkpLFxuICAgICAgICB9LFxuICAgIH0sXG4gICAgcGx1Z2luT3B0aW9uczoge1xuICAgICAgICAnc3R5bGUtcmVzb3VyY2VzLWxvYWRlcic6IHtcbiAgICAgICAgICAgIHByZVByb2Nlc3NvcjogJ3Njc3MnLFxuICAgICAgICAgICAgcGF0dGVybnM6IFtdXG4gICAgICAgIH1cbiAgICB9LFxuICAgIGRlZmluZToge1xuICAgICAgICAncHJvY2Vzcy5lbnYnOiB7fVxuICAgIH0sXG4gICAgLy8gXHU1QjhDXHU1MTY4XHU3OTgxXHU3NTI4XHU5RUQ4XHU4QkE0XHU3Njg0IHB1YmxpYyBcdTc2RUVcdTVGNTVcdTU5MDRcdTc0MDZcbiAgICBwdWJsaWNEaXI6IGZhbHNlLFxuICAgIGJ1aWxkOiB7XG4gICAgICAgIC8vIFx1NkUwNVx1N0E3QVx1OEY5M1x1NTFGQVx1NzZFRVx1NUY1NVx1NjVGNlx1NEZERFx1NzU1OVx1ODFFQVx1NUI5QVx1NEU0OVx1NjJGN1x1OEQxRFx1NzY4NFx1NjU4N1x1NEVGNlxuICAgICAgICBlbXB0eU91dERpcjogdHJ1ZSxcblxuICAgICAgICByb2xsdXBPcHRpb25zOiB7XG4gICAgICAgICAgICBwbHVnaW5zOiBbXG4gICAgICAgICAgICAgICAgY29weSh7XG4gICAgICAgICAgICAgICAgICAgIHRhcmdldHM6IFtcbiAgICAgICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcmM6ICdwdWJsaWMvKiovKicsICAvLyBcdTU5MERcdTUyMzZcdTY1NzRcdTRFMkEgcHVibGljIFx1NzZFRVx1NUY1NVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlc3Q6ICdkaXN0L3B1YmxpYycgIC8vIFx1OEY5M1x1NTFGQVx1NTIzMCBkaXN0L3B1YmxpY1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBdLFxuICAgICAgICAgICAgICAgICAgICBob29rOiAnd3JpdGVCdW5kbGUnLCAgICAgLy8gXHU1NzI4XHU2Nzg0XHU1RUZBXHU1QjhDXHU2MjEwXHU1NDBFXHU2MjY3XHU4ODRDXG4gICAgICAgICAgICAgICAgICAgIGNvcHlPbmNlOiB0cnVlICAgICAgICAgIC8vIFx1NEVDNVx1NjJGN1x1OEQxRFx1NEUwMFx1NkIyMVxuICAgICAgICAgICAgICAgIH0pXG4gICAgICAgICAgICBdXG4gICAgICAgIH1cbiAgICB9LFxufSlcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFBbVgsU0FBUyxvQkFBb0I7QUFDaFosT0FBTyxTQUFTO0FBQ2hCLFNBQVMsZUFBZSxXQUFXO0FBQ25DLE9BQU8sVUFBVTtBQUgwSyxJQUFNLDJDQUEyQztBQU81TyxJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUN4QixNQUFNO0FBQUEsRUFDTixTQUFTO0FBQUEsSUFDTCxJQUFJO0FBQUEsRUFDUjtBQUFBLEVBQ0EsU0FBUztBQUFBLElBQ0wsT0FBTztBQUFBLE1BQ0gsS0FBSyxjQUFjLElBQUksSUFBSSxTQUFTLHdDQUFlLENBQUM7QUFBQSxJQUN4RDtBQUFBLEVBQ0o7QUFBQSxFQUNBLGVBQWU7QUFBQSxJQUNYLDBCQUEwQjtBQUFBLE1BQ3RCLGNBQWM7QUFBQSxNQUNkLFVBQVUsQ0FBQztBQUFBLElBQ2Y7QUFBQSxFQUNKO0FBQUEsRUFDQSxRQUFRO0FBQUEsSUFDSixlQUFlLENBQUM7QUFBQSxFQUNwQjtBQUFBO0FBQUEsRUFFQSxXQUFXO0FBQUEsRUFDWCxPQUFPO0FBQUE7QUFBQSxJQUVILGFBQWE7QUFBQSxJQUViLGVBQWU7QUFBQSxNQUNYLFNBQVM7QUFBQSxRQUNMLEtBQUs7QUFBQSxVQUNELFNBQVM7QUFBQSxZQUNMO0FBQUEsY0FDSSxLQUFLO0FBQUE7QUFBQSxjQUNMLE1BQU07QUFBQTtBQUFBLFlBQ1Y7QUFBQSxVQUNKO0FBQUEsVUFDQSxNQUFNO0FBQUE7QUFBQSxVQUNOLFVBQVU7QUFBQTtBQUFBLFFBQ2QsQ0FBQztBQUFBLE1BQ0w7QUFBQSxJQUNKO0FBQUEsRUFDSjtBQUNKLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==
|
||||||
Reference in New Issue
Block a user