chore: 初始化平芝126KV总装线 PDA
This commit is contained in:
2
.env.development
Normal file
2
.env.development
Normal file
@@ -0,0 +1,2 @@
|
||||
ENV = 'development'
|
||||
VITE_BASE_URL = 'http://localhost:10081'
|
||||
2
.env.production
Normal file
2
.env.production
Normal file
@@ -0,0 +1,2 @@
|
||||
ENV = 'production'
|
||||
VITE_BASE_URL = 'http://127.0.0.1:10081'
|
||||
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?
|
||||
53
docs/库位信息改变清单.md
Normal file
53
docs/库位信息改变清单.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# 库位信息接口变更清单
|
||||
目前 AGV的库位信息发生了改变 我需要同步进行调整
|
||||
先根据我的需要改动内容列一个改动需求清单 然后一项项给我列出来让我审阅
|
||||
检查工作安排
|
||||
1.检查 程序页面中需要改动的地方 使用到这些接口数据的地方
|
||||
2.检查 数据库 相关联的存储过程 (如 记录送料记录 库位记录)的存储过程在改动中是否需要调整 哪些存储过程需要调整
|
||||
3.检查 存储过程 相关联的数据表 是否有一一对应的字段 也需要同步调整 仍然也是需要调整
|
||||
4.最终做整体审阅 把整个流程都审查一遍 看是否还存在遗漏 检查完后给我一份报告 注意 没我允许之前不允许擅自修改代码
|
||||
|
||||
** 接口名称 获取库位列表
|
||||
旧返回结果列表
|
||||
[{
|
||||
"StockCode": "1", // 库位编号
|
||||
"StockName": "隔离开关操作试验区", // 库位名称
|
||||
"StockStatus": false, // 库位满料状态 true 为满料 false 为空料(空料不代表没托盘)
|
||||
"PalletCode": null, // 托盘码,作为是否有托盘依据
|
||||
"EngineNo": null, // 产品编号
|
||||
"SortNo": null, // 产品型号
|
||||
"TaskId": null // 任务编号 若存在任务编号 则代表转运任务中
|
||||
}]
|
||||
新返回结果列表
|
||||
[{
|
||||
"StockCode": "1", // 库位编号
|
||||
"StockName": "B-DS 下线位 1-1", // 库位名称
|
||||
"StockStatus": false, // 库位满料状态 true 为满料 false 为空料(空料不代表没托盘)
|
||||
"ProjectNo": null, // 项目名称 (不是编号)
|
||||
"IntervalNo": null, // 间隔号 (间隔号 + 部件位置 才是唯一的)
|
||||
"ComponentLocation": null, // 部件位置 (间隔号 + 部件位置 才是唯一的)
|
||||
"TrayNo": null, // 托盘号
|
||||
"TaskId": null // 任务编号,若存在任务编号则代表转运任务中 不可再创建新任务
|
||||
}]
|
||||
|
||||
** 接口名称 更新库位信息
|
||||
旧请求参数列表
|
||||
{
|
||||
"StockCode": "42", // 库位编号
|
||||
"StockStatus": false, // 库位状态(true为满料,false为空料)
|
||||
"PalletCode": null, // 托盘码(作为是否有托盘的依据)
|
||||
"EngineNo": null, // 产品编号
|
||||
"SortNo": null, // 产品型号
|
||||
"TaskId": null // 任务编号
|
||||
}
|
||||
新请求参数列表
|
||||
{
|
||||
"StockCode": "42", // 库位编号
|
||||
"StockStatus": false, // 库位状态(true为满料,false为空料)
|
||||
"ProjectNo": null, // 项目编号
|
||||
"IntervalNo": null, // 间隔编号
|
||||
"ComponentLocation": null, // 部件位置
|
||||
"TrayNo": null, // 托盘编号
|
||||
"TaskId": null // 任务编号
|
||||
}
|
||||
返回参数无变化
|
||||
16
index.html
Normal file
16
index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link href="/vite.svg" rel="icon" type="image/svg+xml"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>平芝PDA</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>
|
||||
5820
package-lock.json
generated
Normal file
5820
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
package.json
Normal file
37
package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "zzbz-pad",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"axios": "^1.7.7",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"element-plus": "^2.8.6",
|
||||
"js-cookie": "^3.0.5",
|
||||
"mqtt": "^5.10.4",
|
||||
"pinia": "^2.2.5",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"tdesign-vue-next": "^1.10.3",
|
||||
"uuid": "^11.1.0",
|
||||
"vant": "^3.6.16",
|
||||
"vue": "^3.5.12",
|
||||
"vue-cookies": "^1.8.4",
|
||||
"vue-router": "^4.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vant/auto-import-resolver": "^1.3.0",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"sass": "^1.80.5",
|
||||
"unplugin-auto-import": "^19.1.1",
|
||||
"unplugin-vue-components": "^28.4.1",
|
||||
"vite": "^5.4.10",
|
||||
"vite-plugin-style-import": "^1.4.1"
|
||||
}
|
||||
}
|
||||
19
prettier.config.js
Normal file
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: 12 KiB |
BIN
public/TopLogo.png
Normal file
BIN
public/TopLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
8
public/config.js
Normal file
8
public/config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
window.g = {
|
||||
baseIP: '192.168.31.1'
|
||||
};
|
||||
|
||||
window.g.API_MES_URL = 'http://' + window.g.baseIP + ':9981';
|
||||
window.g.API_WEB_URL = 'http://' + window.g.baseIP + ':10000';
|
||||
window.g.Mqtt_Ip = 'ws://' + window.g.baseIP + ':8083/mqtt';
|
||||
window.g.agvApi = 'http://' + window.g.baseIP + ':9951/api/imes/MES_To_AGV_From_WEB';
|
||||
BIN
public/login.png
Normal file
BIN
public/login.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 MiB |
1
public/vite.svg
Normal file
1
public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
70
src/App.vue
Normal file
70
src/App.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<t-loading :loading="isLoading" size="large" text="加载中(大)...">
|
||||
<router-view />
|
||||
</t-loading>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { useAppStore } from "@/store/app.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
const store = useAppStore();
|
||||
// storeToRefs 响应式解构赋值 pinia
|
||||
const { isLoading } = storeToRefs(store);
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
html {
|
||||
font-size: clamp(14px, 4vw, 18px);
|
||||
/* 根据视口动态调整 */
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
// 定义的全局变量
|
||||
body {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
/*修改提示框样式 更醒目*/
|
||||
.el-message-box {
|
||||
|
||||
// 标题
|
||||
.el-message-box__title {
|
||||
font-size: 35px;
|
||||
font-weight: 700;
|
||||
color: rgb(255, 143, 0);
|
||||
}
|
||||
|
||||
// 提示文本
|
||||
.el-message-box__message p {
|
||||
font-size: 24px;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
// icon图标
|
||||
.el-message-box__status {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.el-message-box__btns {
|
||||
justify-content: space-evenly;
|
||||
|
||||
.el-button {
|
||||
width: 48%;
|
||||
height: 6vh;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 页面主题卡片
|
||||
.card {
|
||||
padding: .5rem;
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 8px 12px #ebedf0;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
</style>
|
||||
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 |
2
src/assets/404_images/style/global.css
Normal file
2
src/assets/404_images/style/global.css
Normal file
@@ -0,0 +1,2 @@
|
||||
/* src/assets/styles/element-variables.scss */
|
||||
@forward 'element-plus/theme-chalk/src/mixins/config.scss' with ($--font-size-base: 1rem);
|
||||
2
src/assets/styles/global.css
Normal file
2
src/assets/styles/global.css
Normal file
@@ -0,0 +1,2 @@
|
||||
/* src/assets/styles/element-variables.scss */
|
||||
@forward 'element-plus/theme-chalk/src/mixins/config.scss' with ($--font-size-base: 1rem);
|
||||
50
src/components/HelloWorld.vue
Normal file
50
src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
<el-button type="primary">21321312</el-button>
|
||||
<t-button disabled loading>确定</t-button>
|
||||
<t-button variant="dashed" ghost>幽灵按钮</t-button>
|
||||
<icon name="cps-icon-home-sheep" url="https://tdesign.gtimg.com/icon/default-demo/index.js" size="2em" />
|
||||
|
||||
123
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { Icon } from 'tdesign-icons-vue-next';
|
||||
|
||||
defineProps({
|
||||
msg: String,
|
||||
})
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
56
src/main.js
Normal file
56
src/main.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import {createApp} from 'vue'
|
||||
import App from './App.vue'
|
||||
//ElementPlus
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
// TDesign
|
||||
import TDesign from 'tdesign-vue-next'
|
||||
|
||||
// vant
|
||||
import Vant from 'vant';
|
||||
|
||||
// 引入组件库的少量全局样式变量
|
||||
import 'tdesign-vue-next/es/style/index.css'
|
||||
// pinia
|
||||
import {createPinia} from 'pinia'
|
||||
//router
|
||||
import router from './router/index.js'
|
||||
//crud
|
||||
import curdVue3 from './utils/curdVue3'
|
||||
// el-icon
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
// 自定义全局样式
|
||||
import './assets/styles/global.css' // 引入全局样式文件
|
||||
import 'vant/lib/index.css'
|
||||
|
||||
const app = createApp(App)
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
// 提供全局方法
|
||||
|
||||
|
||||
app.use(ElementPlus, {
|
||||
locale: zhCn,
|
||||
})
|
||||
|
||||
app.use(TDesign)
|
||||
const pinia = createPinia() // 创建 Pinia 实例
|
||||
app.use(pinia)
|
||||
app.use(curdVue3)
|
||||
app.use(router)
|
||||
app.use(Vant);
|
||||
|
||||
app.provide('CreateData', app.config.globalProperties.CreateData)
|
||||
app.provide('ExecDatabase', app.config.globalProperties.ExecDatabase)
|
||||
app.provide('ExecDatabaseByParam', app.config.globalProperties.ExecDatabaseByParam)
|
||||
app.provide('ExecWritePLC', app.config.globalProperties.ExecWritePLC)
|
||||
app.provide('ExecReadPLC', app.config.globalProperties.ExecReadPLC)
|
||||
app.provide('ExecIMESAPI', app.config.globalProperties.ExecIMESAPI)
|
||||
app.provide('ExecAgvApi', app.config.globalProperties.ExecAgvApi)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
// useRouteGuard(); // 使用路由守卫
|
||||
|
||||
149
src/router/index.js
Normal file
149
src/router/index.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import { createRouter, createWebHashHistory } from "vue-router";
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
// cookies
|
||||
|
||||
// 创建routes
|
||||
const routes = [
|
||||
{
|
||||
// 重定向
|
||||
path: "/home",
|
||||
redirect: "/",
|
||||
},
|
||||
{
|
||||
//前台首页
|
||||
path: "/",
|
||||
name: "root",
|
||||
component: () => import("@/views/Layout/index.vue"),
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
//前台首页
|
||||
path: "/login",
|
||||
component: () => import("@/views/Login/index.vue"),
|
||||
},
|
||||
// { // 404有毛病 目前 初始化动态路由有问题 404暂改为重定向到首页
|
||||
// // 404
|
||||
// path: "/:pathMatch(.*)*",
|
||||
// component: () => import("@/views/404.vue"),
|
||||
// },
|
||||
{
|
||||
// 404
|
||||
path: "/:pathMatch(.*)*",
|
||||
redirect: "/",
|
||||
},
|
||||
];
|
||||
|
||||
//History路由模型
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
});
|
||||
|
||||
//这里设置路由拦截
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
// 设置游览器标题
|
||||
if (to.meta.title) {
|
||||
window.document.title = to.meta.title;
|
||||
}
|
||||
// 放行
|
||||
// next();
|
||||
if (to.path === '/login' || to.path === '/') {
|
||||
next();
|
||||
return;
|
||||
} else {
|
||||
const AID = Cookies.get('AID');
|
||||
if (!AID) {
|
||||
next('/');
|
||||
return;
|
||||
} else {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 递归替换引入component
|
||||
function dynamicRouter(routerList) {
|
||||
const list = [];
|
||||
routerList.forEach((route) => {
|
||||
const routeItem = {
|
||||
path: '/' + route.path,
|
||||
name: route.path,
|
||||
meta: {
|
||||
title: route.权限模块,
|
||||
},
|
||||
component: async () => {
|
||||
try {
|
||||
const component = await import(`@/views/${route.path}/index.vue`);
|
||||
console.log(component)
|
||||
return component.default || component;
|
||||
} catch (error) {
|
||||
console.error('引入路由组件失败,请确认地址是否配置正确:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
list.push(routeItem);
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
// 动态添加路由的函数
|
||||
export async function addDynamicRoutes(routerList) {
|
||||
try {
|
||||
// routerList.forEach(route => {
|
||||
// console.log(123, route)
|
||||
// // {权限模块:车体到位,path:CarBodyPlace}
|
||||
// router.addRoute('root', {
|
||||
// path: '/' + route.path,
|
||||
// name:route.path,
|
||||
// meta: {
|
||||
// title: route.权限模块,
|
||||
// },
|
||||
// component: () => import(`@/views/${route.path}/index.vue`),
|
||||
// });
|
||||
// });
|
||||
// router.addRoute('root',{
|
||||
// children: dynamicRouter(routerList)
|
||||
// })
|
||||
|
||||
|
||||
// let userId = Cookies.get('userId')
|
||||
// if(userId !== ''){
|
||||
// // 获取权限列表
|
||||
// var param = []
|
||||
// param[0] = ['userId', userId]
|
||||
// var Data = CreateData('11', '基础表_权限模块_查询', param)
|
||||
// ExecDatabase(Data).then(response => {
|
||||
// if(response.data.length > 0){
|
||||
// routerData.value.userRouterList = response.data
|
||||
// // 创建动态路由列表
|
||||
// const dynamicRoutes = dynamicRouter(response.data);
|
||||
//
|
||||
// dynamicRoutes.forEach((route) => {
|
||||
// // 检查是否已存在该路由,避免重复添加
|
||||
// if (!router.hasRoute(route.name)) {
|
||||
// router.addRoute('root', route);
|
||||
// }
|
||||
// });
|
||||
// }else{
|
||||
// ElMessage.warning('该角色未分配角色模块,请先进行分配!')
|
||||
// }
|
||||
// })
|
||||
|
||||
// 创建动态路由列表
|
||||
const dynamicRoutes = dynamicRouter(routerList);
|
||||
|
||||
dynamicRoutes.forEach((route) => {
|
||||
// 检查是否已存在该路由,避免重复添加
|
||||
if (!router.hasRoute(route.name)) {
|
||||
router.addRoute('root', route);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('动态添加路由失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
export default router;
|
||||
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;
|
||||
}
|
||||
}
|
||||
30
src/store/app.js
Normal file
30
src/store/app.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import {defineStore} from 'pinia'
|
||||
|
||||
|
||||
// 第一个参数是应用程序中 store 的唯一 id
|
||||
export const useAppStore = defineStore('AppData', {
|
||||
// 其它配置项
|
||||
state: () => {
|
||||
return {
|
||||
user: {
|
||||
token: '',
|
||||
AID: '',
|
||||
name: '',
|
||||
classes: '',
|
||||
teamGroup: '',
|
||||
role: '',
|
||||
userName: '',
|
||||
password: '',
|
||||
opName: '',
|
||||
opNameTxt: '',
|
||||
},
|
||||
isLoading: false,
|
||||
routerData: {
|
||||
userRouterList: [],
|
||||
nowRouterPath: '',
|
||||
nowRouterTitle: '',
|
||||
selected: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
})
|
||||
32
src/utils/AES-Pkcs7.js
Normal file
32
src/utils/AES-Pkcs7.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import CryptoJS from 'crypto-js'
|
||||
import bcrypt from 'bcryptjs'
|
||||
export const encrypt = function(word, keyStr) {
|
||||
keyStr = keyStr || '1994+=--jkl;P,fj'
|
||||
const key = CryptoJS.enc.Utf8.parse(keyStr)
|
||||
const srcs = CryptoJS.enc.Utf8.parse(word)
|
||||
const encrypted = CryptoJS.AES.encrypt(srcs, key, {
|
||||
iv: key,
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
})
|
||||
return encrypted.toString()
|
||||
}
|
||||
export const md5 = function(word) {
|
||||
return CryptoJS.MD5(word).toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
* @param {any} word 解密数据
|
||||
*/
|
||||
export const cryptoDecrypt = (word, keyStr) => {
|
||||
const secretKey = CryptoJS.enc.Utf8.parse(keyStr)
|
||||
const ivParsed = CryptoJS.enc.Utf8.parse(keyStr)
|
||||
const decrypt = CryptoJS.AES.decrypt(word, secretKey, { iv: ivParsed, mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 })
|
||||
const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8)
|
||||
return decryptedStr
|
||||
}
|
||||
export const generateSalt = () => {
|
||||
return bcrypt.genSaltSync(10)
|
||||
}
|
||||
|
||||
15
src/utils/auth.js
Normal file
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)
|
||||
}
|
||||
}
|
||||
|
||||
153
src/utils/curdVue3.js
Normal file
153
src/utils/curdVue3.js
Normal file
@@ -0,0 +1,153 @@
|
||||
import request from '@/utils/request'
|
||||
import Cookie from 'js-cookie'
|
||||
import {ElMessage} from 'element-plus'
|
||||
|
||||
export default {
|
||||
install(app) {
|
||||
app.config.globalProperties.CreateData = function (type, name, data, pageSize, pageList) {
|
||||
var param_Str = ''
|
||||
var param_array = []
|
||||
if (type === '7') {
|
||||
data.map(v => {
|
||||
pageSize.map(h => {
|
||||
const val = h.toString()
|
||||
if (!v[val]) {
|
||||
this.$set(v, val, '')
|
||||
}
|
||||
})
|
||||
})
|
||||
param_Str = data
|
||||
} else {
|
||||
if (data !== undefined) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
param_array.push({
|
||||
name: data[i][0],
|
||||
value: data[i][1],
|
||||
type: data[i][2],
|
||||
output: data[i][3]
|
||||
})
|
||||
}
|
||||
param_Str = JSON.stringify(param_array)
|
||||
}
|
||||
}
|
||||
var obj = []
|
||||
obj[0] = {}
|
||||
obj[0].type = type
|
||||
obj[0].name = name
|
||||
obj[0].param = param_Str
|
||||
obj[0].pageSize = pageSize
|
||||
obj[0].pageList = pageList
|
||||
obj[0].UserID = Cookie.get('username')
|
||||
obj[0].ModularID = ''//this.$router.currentRoute.path
|
||||
var numn = JSON.stringify(obj[0])
|
||||
return numn
|
||||
}
|
||||
app.config.globalProperties.ExecDatabase = function (data) {
|
||||
return request({
|
||||
url: '',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
app.config.globalProperties.ExecDatabaseByParam = function (type, name, data, pageSize, pageList) {
|
||||
var param_Str = ''
|
||||
var param_array = []
|
||||
if (type === '7') {
|
||||
data.map(v => {
|
||||
pageSize.map(h => {
|
||||
const val = h.toString()
|
||||
if (!v[val]) {
|
||||
this.$set(v, val, '')
|
||||
}
|
||||
})
|
||||
})
|
||||
param_Str = data
|
||||
} else {
|
||||
if (data !== undefined) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
param_array.push({
|
||||
name: data[i][0],
|
||||
value: data[i][1],
|
||||
type: data[i][2],
|
||||
output: data[i][3]
|
||||
})
|
||||
}
|
||||
param_Str = JSON.stringify(param_array)
|
||||
}
|
||||
}
|
||||
var obj = []
|
||||
obj[0] = {}
|
||||
obj[0].type = type
|
||||
obj[0].name = name
|
||||
obj[0].param = param_Str
|
||||
obj[0].pageSize = pageSize
|
||||
obj[0].pageList = pageList
|
||||
obj[0].UserID = Cookie.get('AID')
|
||||
obj[0].ModularID = ''//this.$router.currentRoute.path
|
||||
var numn = JSON.stringify(obj[0])
|
||||
return request({
|
||||
url: '',
|
||||
method: 'post',
|
||||
data: numn
|
||||
})
|
||||
}
|
||||
|
||||
app.config.globalProperties.ExecWritePLC = function (opName, tagTypeCodeID, value) {
|
||||
return request({
|
||||
url: window.g.API_MES_URL + '/api/imes/Web_WritePLC',
|
||||
method: 'post',
|
||||
data: {
|
||||
tagTypeCodeID: tagTypeCodeID,
|
||||
OPName: opName,
|
||||
tagValue: value,
|
||||
}
|
||||
}).catch(function (error) {
|
||||
// 如果接口调用失败,弹出错误弹窗
|
||||
ElMessage.error('写入PLC数据接口失败: ' + error.message)
|
||||
});
|
||||
}
|
||||
app.config.globalProperties.ExecReadPLC = function (opName, tagTypeCodeID) {
|
||||
return request({
|
||||
url: window.g.API_MES_URL + '/api/imes/Web_ReadPLC',
|
||||
method: 'post',
|
||||
data: {
|
||||
tagTypeCodeID: tagTypeCodeID,
|
||||
OPName: opName,
|
||||
}
|
||||
}).catch(function (error) {
|
||||
// 如果接口调用失败,弹出错误弹窗
|
||||
ElMessage.error('读取PLC数据接口失败: ' + error.message)
|
||||
});
|
||||
}
|
||||
app.config.globalProperties.ExecIMESAPI = function (apiName, param) {
|
||||
return request({
|
||||
url: window.g.API_MES_URL + '/api/imes/' + apiName,
|
||||
method: 'post',
|
||||
data: param
|
||||
}).catch(function (error) {
|
||||
// 如果接口调用失败,弹出错误弹窗
|
||||
ElMessage.error('调用MES' + apiName + '接口失败: ' + error.message)
|
||||
});
|
||||
}
|
||||
app.config.globalProperties.ExecAgvApi = function (url, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request({
|
||||
url: window.g.agvApi,
|
||||
method: 'post',
|
||||
data: { url: url, data: data }
|
||||
}).then(response => {
|
||||
if (response.data.Result) {
|
||||
resolve(JSON.parse(response.data.Data))
|
||||
} else {
|
||||
ElMessage.error('调用AGV接口失败: ' + response.data.ErrMsg)
|
||||
resolve({ Result: false, ErrMsg: response.data.ErrMsg })
|
||||
}
|
||||
}).catch(error => {
|
||||
ElMessage.error('AGV接口调用失败:' + error.message)
|
||||
resolve({ Result: false, ErrMsg: error.message })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
34
src/utils/request.js
Normal file
34
src/utils/request.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import axios from 'axios'
|
||||
// import store from '@/store'
|
||||
import {ElMessage} from 'element-plus' // 引入Element Plus的消息提示组件
|
||||
const name_config = `OP8888`
|
||||
// import.meta.env.VITE_BASE_URL
|
||||
const request_config = window.g.API_WEB_URL + `/submit/MESCommonBase.ashx`
|
||||
export const name = name_config
|
||||
const service = axios.create({
|
||||
baseURL: request_config,
|
||||
timeout: 1000 * 5 // 请求超时时间
|
||||
})
|
||||
|
||||
// && config.data.name !== '菜单系统模块_通知信息_查询数据' && config.data.name !== '菜单系统模块_首页信息_查询数据1' && config.data.name !== '菜单系统模块_首页信息_查询数据2'
|
||||
service.interceptors.request.use(config => {
|
||||
|
||||
return config
|
||||
}, error => {
|
||||
if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
|
||||
ElMessage({
|
||||
message: '请求超时,请稍后再试。',
|
||||
type: 'error',
|
||||
duration: 5000
|
||||
})
|
||||
} else {
|
||||
ElMessage({
|
||||
message: '请求失败,请稍后再试。',
|
||||
type: 'error',
|
||||
duration: 5000
|
||||
})
|
||||
}
|
||||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
export default service
|
||||
90
src/utils/setMethods.js
Normal file
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>
|
||||
127
src/views/AGVTaskSearch/index.vue
Normal file
127
src/views/AGVTaskSearch/index.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="dataList">
|
||||
<div class="item" v-for="item in recordList" :key="item.TaskNo" @click="onItemClick(item)">
|
||||
<div class="tItem">
|
||||
<div class="name">任务状态:</div>
|
||||
<van-tag class="value" :type="getStatusTag(item.Status)">{{ getStatusText(item.Status) }}</van-tag>
|
||||
</div>
|
||||
<div class="tItem">
|
||||
<div class="name">起点库位:</div>
|
||||
<div class="value">{{ item.PickStockName || '-' }}</div>
|
||||
</div>
|
||||
<div class="tItem">
|
||||
<div class="name">终点库位:</div>
|
||||
<div class="value">{{ item.DropStockName || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="detailDialogShow" title="任务详情" width="90vw" :close-on-click-modal="true" class="mobile-dialog">
|
||||
<el-descriptions :column="1" border class="mobile-desc" :label-style="{width: '100px'}">
|
||||
<el-descriptions-item label="任务名称">{{ detailData.TaskName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务状态">{{ getStatusText(detailData.Status) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目名称">{{ detailData.ProjectNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="间隔编号">{{ detailData.IntervalNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="部件位置">{{ detailData.ComponentLocation }}</el-descriptions-item>
|
||||
<el-descriptions-item label="托盘编号">{{ detailData.TrayNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="起点库位">{{ detailData.PickStockName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="终点库位">{{ detailData.DropStockName }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="onCancelTask(detailData)">取消任务</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, onMounted, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const ExecAgvApi = inject('ExecAgvApi')
|
||||
const recordList = ref([])
|
||||
const detailDialogShow = ref(false)
|
||||
const detailData = ref({})
|
||||
|
||||
// 刷新任务列表
|
||||
const fetchTaskList = async () => {
|
||||
const res = await ExecAgvApi('getTaskList', { PageIndex: 1, PageSize: 20 })
|
||||
if (res.Result) {
|
||||
recordList.value = res.Data
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '查询失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 取消任务
|
||||
const onCancelTask = (item) => {
|
||||
ElMessageBox.confirm('是否确认取消该任务?', '取消任务', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const res = await ExecAgvApi('cancelTask', { taskId: item.TaskNo })
|
||||
if (res.Result) {
|
||||
ElMessage.success('任务已取消')
|
||||
detailDialogShow.value = false
|
||||
await fetchTaskList()
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '取消任务失败')
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchTaskList()
|
||||
})
|
||||
|
||||
const getStatusText = (status) => {
|
||||
switch (status) {
|
||||
case 'running': return '进行中'
|
||||
case 'aborted': return '已取消'
|
||||
case 'finish': return '已完成'
|
||||
case 'Dispatch': return '已调度'
|
||||
default: return status || '-'
|
||||
}
|
||||
}
|
||||
const getStatusTag = (status) => {
|
||||
switch (status) {
|
||||
case 'running': return 'primary'
|
||||
case 'aborted': return 'danger'
|
||||
case 'finish': return 'success'
|
||||
case 'Dispatch': return 'warning'
|
||||
default: return 'default'
|
||||
}
|
||||
}
|
||||
const onItemClick = (item) => {
|
||||
detailData.value = item
|
||||
detailDialogShow.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dataList {
|
||||
.item {
|
||||
border: 2px solid #ccc;
|
||||
padding: 10px 10px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
margin: 10px 0;
|
||||
.tItem {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-dialog {
|
||||
max-width: 98vw;
|
||||
}
|
||||
|
||||
.mobile-desc {
|
||||
word-break: break-all;
|
||||
font-size: 15px;
|
||||
.el-descriptions__cell {
|
||||
padding: 6px 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
797
src/views/CallMaterials/index.vue
Normal file
797
src/views/CallMaterials/index.vue
Normal file
@@ -0,0 +1,797 @@
|
||||
<template>
|
||||
<div class="card modern-card">
|
||||
<div style="display: flex; justify-content: flex-end; margin-bottom: 12px;position: relative;">
|
||||
<div v-if="currentPartitionName"
|
||||
style="position: absolute; left: 10px; top: 10px; font-size: .8rem; color: #409EFF; font-weight: bold; letter-spacing: 1px;">
|
||||
{{ currentPartitionName }}
|
||||
</div>
|
||||
<el-button type="primary" @click="onReturnPalletClick">退空托盘</el-button>
|
||||
</div>
|
||||
<div class="storList">
|
||||
<div class="storList-item modern-item" v-for="item in opStoreData" :key="item.StockCode"
|
||||
@click="handleClick(item)">
|
||||
<div class="storList-header">
|
||||
<span class="storList-title">{{ item.StockName }}</span>
|
||||
<div class="storList-tags">
|
||||
<van-tag type="danger" v-if="item.TaskId != null">任务中</van-tag>
|
||||
<van-tag type="primary" v-if="item.StockStatus == false && !item.TrayNo">空库位</van-tag>
|
||||
<van-tag type="warning" v-if="item.StockStatus == false && item.TrayNo">空托盘</van-tag>
|
||||
<van-tag type="success" v-if="item.StockStatus == true">满料</van-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="storList-info">
|
||||
<span><i class="iconfont icon-project"></i> 项目名称:<b>{{ item.ProjectNo }}</b></span>
|
||||
<span><i class="iconfont icon-product"></i> 间隔编号:<b>{{ item.IntervalNo }}</b></span>
|
||||
<span><i class="iconfont icon-model"></i> 部件位置:<b>{{ item.ComponentLocation }}</b></span>
|
||||
<span><i class="iconfont icon-tuopan"></i> 托盘编号:<b>{{ item.TrayNo }}</b></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="showStockDialog" :title="stockDialogTitle" width="400px" :close-on-click-modal="false"
|
||||
@close="onStockDialogClose">
|
||||
<div v-if="stockList.length > 0">
|
||||
<div v-for="item in stockList" :key="item.code" @click="onStockSelect(item)"
|
||||
style="cursor:pointer; border-bottom:1px solid #eee; padding:12px 0;">
|
||||
<div style="display:flex;flex-direction:column;">
|
||||
<span><b>库位名称:</b>{{ item.name }}</span>
|
||||
<span><b>项目名称:</b>{{ item.ProjectNo }}</span>
|
||||
<span><b>间隔编号:</b>{{ item.IntervalNo }}</span>
|
||||
<span><b>部件位置:</b>{{ item.ComponentLocation }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="text-align:center;color:#888;">暂无可用库位</div>
|
||||
</el-dialog>
|
||||
<van-action-sheet v-model:show="showActionSheet" :actions="actionList" @select="onActionSelect" title="请选择操作" />
|
||||
<van-action-sheet v-model:show="showCallMaterialSheet" :actions="callMaterialActions" @select="onCallMaterialSelect"
|
||||
title="请选择操作" />
|
||||
<van-action-sheet v-model:show="showReturnMaterialSheet" :actions="returnMaterialActions"
|
||||
@select="onReturnMaterialSelect" title="请选择操作" />
|
||||
<el-dialog v-model="showInputDialog" title="物料验证" width="320px" :close-on-click-modal="false"
|
||||
@close="onInputCancel">
|
||||
<el-form @submit.prevent>
|
||||
<el-form-item label="产品图号">
|
||||
<el-input v-model="MaterialData.SortNo" disabled clearable autofocus @keyup.enter="onInputConfirm" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品编号">
|
||||
<el-input v-model="inputValue" placeholder="请扫描物料编号" clearable autofocus @input="onInputChange"
|
||||
@keyup.enter="onInputConfirm" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onInputCancel">取消</el-button>
|
||||
<el-button type="primary" @click="onInputConfirm">校验</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="showReturnPalletDialog" title="退空托盘" width="320px" :close-on-click-modal="false"
|
||||
@close="onReturnPalletCancel">
|
||||
<el-form @submit.prevent>
|
||||
<el-form-item label="托盘码">
|
||||
<el-input v-model="returnPalletCode" placeholder="请扫描托盘码" clearable autofocus
|
||||
@keyup.enter="onReturnPalletConfirm" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onReturnPalletCancel">取消</el-button>
|
||||
<el-button type="primary" @click="onReturnPalletConfirm">校验</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useAppStore } from '@/store/app.js'
|
||||
import { Dialog, ActionSheet, Toast } from 'vant'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
const store = useAppStore()
|
||||
const { user } = storeToRefs(store)
|
||||
|
||||
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
|
||||
const ExecAgvApi = inject('ExecAgvApi')
|
||||
|
||||
const opStoreCode = ref(-1)
|
||||
|
||||
// 补充定义 stockList
|
||||
const stockList = ref([])
|
||||
|
||||
// 添加 selectedStock 定义
|
||||
const selectedStock = ref(null)
|
||||
|
||||
// StockCode 库位编号
|
||||
// StockName 库位名称
|
||||
// StockStatus 满料状态 bool
|
||||
// TrayNo 托盘号
|
||||
// IntervalNo 间隔号
|
||||
// ComponentLocation 组件位置
|
||||
// TaskId 任务号
|
||||
const opStoreData = reactive([])
|
||||
// 库区列表
|
||||
const partitionList = reactive([])
|
||||
|
||||
// 物料验证相关数据
|
||||
const MaterialData = reactive({})
|
||||
|
||||
// 目标库区数据
|
||||
const targetPartition = reactive({
|
||||
callMaterial: null, // 叫料库区
|
||||
returnMaterial: null, // 退料库区
|
||||
returnPallet: null // 退空托库区
|
||||
})
|
||||
|
||||
// 库位选择数据
|
||||
const showStockDialog = ref(false)
|
||||
const stockDialogTitle = ref('')
|
||||
|
||||
const showActionSheet = ref(false)
|
||||
const actionList = [
|
||||
{ name: '退料' }
|
||||
]
|
||||
const currentItem = ref(null)
|
||||
|
||||
const showInputDialog = ref(false)
|
||||
const inputValue = ref('')
|
||||
|
||||
const showReturnPalletDialog = ref(false)
|
||||
const returnPalletCode = ref('')
|
||||
const returnPalletStock = ref(null)
|
||||
|
||||
// 叫料相关数据
|
||||
const showCallMaterialSheet = ref(false)
|
||||
const callMaterialActions = [
|
||||
{ name: '叫料' }
|
||||
]
|
||||
|
||||
// 叫料选择处理
|
||||
const onCallMaterialSelect = async (action) => {
|
||||
showCallMaterialSheet.value = false
|
||||
if (action.name === '叫料') {
|
||||
// 获取叫料目标库区
|
||||
const routes = await getRoutePartitions(currentItem.value.StockCode)
|
||||
if (!routes || !routes.callMaterial) {
|
||||
ElMessage.warning('未配置叫料路线,请联系管理员')
|
||||
return
|
||||
}
|
||||
|
||||
// 记录目标库区
|
||||
targetPartition.callMaterial = routes.callMaterial
|
||||
|
||||
// 根据目标库区获取库位列表
|
||||
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: targetPartition.callMaterial })
|
||||
if (!stockRes.Result) {
|
||||
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
|
||||
return
|
||||
}
|
||||
|
||||
// 叫料流程:只显示满料库位,排除"无库位"
|
||||
stockList.value = (stockRes.Data || [])
|
||||
.filter(i => i.StockStatus === true && i.StockName !== '无库位')
|
||||
.map(i => ({ name: i.StockName, code: i.StockCode, ...i }))
|
||||
|
||||
if (stockList.value.length === 0) {
|
||||
ElMessage.warning('目标库区没有满料库位')
|
||||
return
|
||||
}
|
||||
|
||||
// 设置叫料流程标识
|
||||
isCallMaterialFlow.value = true
|
||||
stockDialogTitle.value = await getPartitionName(targetPartition.callMaterial)
|
||||
showStockDialog.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const isCallMaterialFlow = ref(false)
|
||||
const currentPartitionName = ref('')
|
||||
|
||||
// 退料相关数据
|
||||
const showReturnMaterialSheet = ref(false)
|
||||
const returnMaterialActions = [
|
||||
{ name: '物料验证' },
|
||||
{ name: '退料' }
|
||||
]
|
||||
|
||||
// 退料选择处理
|
||||
const onReturnMaterialSelect = async (action) => {
|
||||
showReturnMaterialSheet.value = false
|
||||
if (action.name === '物料验证') {
|
||||
inputValue.value = ''
|
||||
showInputDialog.value = true
|
||||
// 将当前物料信息赋值到验证数据
|
||||
Object.assign(MaterialData, currentItem.value)
|
||||
return
|
||||
}
|
||||
|
||||
if (action.name === '退料') {
|
||||
// 获取退料目标库区
|
||||
const routes = await getRoutePartitions(currentItem.value.StockCode)
|
||||
if (!routes || !routes.returnMaterial) {
|
||||
ElMessage.warning('未配置退料路线,请联系管理员')
|
||||
return
|
||||
}
|
||||
|
||||
// 记录目标库区
|
||||
targetPartition.returnMaterial = routes.returnMaterial
|
||||
|
||||
// 根据目标库区获取库位列表
|
||||
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: targetPartition.returnMaterial })
|
||||
if (!stockRes.Result) {
|
||||
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
|
||||
return
|
||||
}
|
||||
|
||||
// 退料流程:只显示空库位(无托盘且状态为空)
|
||||
stockList.value = stockRes.Data
|
||||
.filter(i => !i.PalletCode && i.StockStatus === false)
|
||||
.map(i => ({ name: i.StockName, code: i.StockCode, ...i }))
|
||||
|
||||
if (stockList.value.length === 0) {
|
||||
ElMessage.warning('目标库区没有可用空库位')
|
||||
return
|
||||
}
|
||||
|
||||
// 设置退料流程标识
|
||||
isReturnMaterialFlow.value = true
|
||||
stockDialogTitle.value = await getPartitionName(targetPartition.returnMaterial)
|
||||
showStockDialog.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const isReturnMaterialFlow = ref(false)
|
||||
|
||||
// 获取预设路线库区信息
|
||||
const getRoutePartitions = async (stockCode) => {
|
||||
try {
|
||||
const res = await ExecDatabaseByParam('11', 'PTCHV_AGV_送料路线_PDA查询_初始化', [['库位号', stockCode]])
|
||||
if (res.data && res.data.length > 0) {
|
||||
return {
|
||||
callMaterial: res.data[0].叫料库区,
|
||||
returnMaterial: res.data[0].退料库区,
|
||||
returnPallet: res.data[0].退空托库区
|
||||
}
|
||||
} else {
|
||||
ElMessage.warning('未找到预设的路线信息')
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取路线信息失败:', error)
|
||||
ElMessage.error('获取路线信息失败')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getPartitionList()
|
||||
await getOpStoreCode()
|
||||
})
|
||||
const getPartitionList = async () => {
|
||||
let res = await ExecDatabaseByParam('11', 'MES_计划BOM_工位与名称_库位编号_查询', [['工位号', user.value.opName]])
|
||||
if (res.data.length > 0) {
|
||||
// 处理库区编号,可能会返回多个库区,如 "7,14"
|
||||
const partitionCodes = res.data[0].库位编号.toString().split(',').map(code => code.trim())
|
||||
opStoreCode.value = partitionCodes[0] // 保存第一个库区编号作为主库区
|
||||
|
||||
// 查找库区名称
|
||||
partitionList.length = 0
|
||||
const partitionRes = await ExecAgvApi('getPartitionList', {})
|
||||
if (partitionRes.Result) {
|
||||
partitionList.push(...partitionRes.Data)
|
||||
currentPartitionName.value = await getPartitionName(opStoreCode.value)
|
||||
}
|
||||
} else {
|
||||
ElMessage.warning('此工位无对应库位!')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取对应库位编号名称
|
||||
const getPartitionName = async (partitionCode) => {
|
||||
const found = partitionList.find(i => i.PartitionCode == partitionCode)
|
||||
return found ? found.PartitionName : ''
|
||||
}
|
||||
|
||||
const getOpStoreCode = async () => {
|
||||
// 获取MES_计划BOM_工位与名称_库位编号_查询返回的所有库区编号
|
||||
const res = await ExecDatabaseByParam('11', 'MES_计划BOM_工位与名称_库位编号_查询', [['工位号', user.value.opName]])
|
||||
if (res.data.length === 0) {
|
||||
ElMessage.warning('此工位无对应库位!')
|
||||
return
|
||||
}
|
||||
|
||||
// 清空现有数据
|
||||
opStoreData.length = 0
|
||||
|
||||
// 处理可能有多个库区编号的情况(如 "7,14")
|
||||
const partitionCodes = res.data[0].库位编号.toString().split(',').map(code => code.trim())
|
||||
|
||||
// 依次查询每个库区的库位信息并合并
|
||||
for (const partitionCode of partitionCodes) {
|
||||
const agvPar = {
|
||||
"PartitionCode": partitionCode
|
||||
}
|
||||
const agvRes = await ExecAgvApi('getStockList', agvPar)
|
||||
|
||||
if (agvRes.Result) {
|
||||
// 将查询到的库位信息添加到列表中,过滤"无库位"
|
||||
const filteredData = (agvRes.Data || []).filter(item => item.StockName !== '无库位')
|
||||
opStoreData.push(...filteredData)
|
||||
} else {
|
||||
ElMessage.error(`获取库区${partitionCode}信息失败: ${agvRes.ErrMsg}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有任何库位信息
|
||||
if (opStoreData.length === 0) {
|
||||
ElMessage.error('未获取到任何库位信息')
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = async (item) => {
|
||||
if (item.TaskId != null) {
|
||||
Dialog.alert({
|
||||
title: '提示',
|
||||
message: '该库位存在任务,不可操作',
|
||||
})
|
||||
return
|
||||
}
|
||||
// 满料库位:退料流程
|
||||
if (item.StockStatus === true) {
|
||||
currentItem.value = item
|
||||
showReturnMaterialSheet.value = true
|
||||
return
|
||||
}
|
||||
// 空托盘:退空托盘流程
|
||||
if (item.StockStatus === false && item.TrayNo) {
|
||||
currentItem.value = item
|
||||
showActionSheet.value = true
|
||||
return
|
||||
}
|
||||
// 空库位:叫料流程
|
||||
if (item.StockStatus === false && !item.TrayNo) {
|
||||
currentItem.value = item
|
||||
showCallMaterialSheet.value = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 选择库位后的处理
|
||||
const onStockItemClick = (item) => {
|
||||
// 二次确认
|
||||
|
||||
}
|
||||
|
||||
const onStockDialogClose = () => {
|
||||
showStockDialog.value = false
|
||||
}
|
||||
|
||||
const onStockSelect = async (stock) => {
|
||||
selectedStock.value = stock
|
||||
showStockDialog.value = false
|
||||
|
||||
if (isCallMaterialFlow.value) {
|
||||
// 获取目标库区名称
|
||||
const partitionName = await getPartitionName(targetPartition.callMaterial)
|
||||
|
||||
// 叫料流程确认
|
||||
ElMessageBox.confirm(`是否将物料 从库位【${stock.name}】送至库位【${currentItem.value.StockName}】?`, '确认叫料', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const taskId = uuidv4()
|
||||
const res = await ExecAgvApi('createTask', {
|
||||
taskId,
|
||||
pickPosition: stock.code,
|
||||
dropPosition: currentItem.value.StockCode,
|
||||
})
|
||||
if (res.Result) {
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_送料记录_增加', [
|
||||
['任务号', taskId],
|
||||
['取料库位', stock.code],
|
||||
['取料库位名称', stock.name],
|
||||
['取料项目名称', stock.ProjectNo || ''],
|
||||
['取料间隔编号', stock.IntervalNo || ''],
|
||||
['取料部件位置', stock.ComponentLocation || ''],
|
||||
['取料托盘编号', stock.TrayNo || ''],
|
||||
['送料库位', currentItem.value.StockCode],
|
||||
['送料库位名称', currentItem.value.StockName],
|
||||
['操作人', user.value.name],
|
||||
['type', 1],
|
||||
])
|
||||
ElMessage.success('叫料任务创建成功')
|
||||
getOpStoreCode()
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '叫料任务创建失败')
|
||||
}
|
||||
isCallMaterialFlow.value = false
|
||||
})
|
||||
} else if (isReturnMaterialFlow.value) {
|
||||
// 获取目标库区名称
|
||||
const partitionName = await getPartitionName(targetPartition.returnMaterial)
|
||||
|
||||
// 退料流程确认
|
||||
Dialog.confirm({
|
||||
title: '确认退料',
|
||||
message: `是否将物料退至库位【${stock.name}】?`,
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
}).then(async () => {
|
||||
const taskId = uuidv4()
|
||||
const res = await ExecAgvApi('createTask', {
|
||||
taskId,
|
||||
pickPosition: currentItem.value.StockCode,
|
||||
dropPosition: stock.code,
|
||||
})
|
||||
if (res.Result) {
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_送料记录_增加', [
|
||||
['任务号', taskId],
|
||||
['取料库位', currentItem.value.StockCode],
|
||||
['取料库位名称', currentItem.value.StockName],
|
||||
['取料项目名称', currentItem.value.ProjectNo || ''],
|
||||
['取料间隔编号', currentItem.value.IntervalNo || ''],
|
||||
['取料部件位置', currentItem.value.ComponentLocation || ''],
|
||||
['取料托盘编号', currentItem.value.TrayNo || ''],
|
||||
['送料库位', stock.code],
|
||||
['送料库位名称', stock.name],
|
||||
['操作人', user.value.name],
|
||||
['type', 2],
|
||||
])
|
||||
ElMessage.success('退料任务创建成功')
|
||||
getOpStoreCode()
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '退料任务创建失败')
|
||||
}
|
||||
isReturnMaterialFlow.value = false
|
||||
}).catch(() => {
|
||||
isReturnMaterialFlow.value = false
|
||||
})
|
||||
} else if (isReturnPalletFlow.value) {
|
||||
// 获取目标库区名称
|
||||
const partitionName = await getPartitionName(targetPartition.returnPallet)
|
||||
|
||||
// 退空托盘流程确认
|
||||
Dialog.confirm({
|
||||
title: '确认退空托盘',
|
||||
message: `是否将空托盘退至 库位【${stock.name}】?`,
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
}).then(async () => {
|
||||
// 先更新库位信息
|
||||
const oldTrayNo = returnPalletStock.value.TrayNo || ''
|
||||
const oldIntervalNo = returnPalletStock.value.IntervalNo || ''
|
||||
const oldComponentLocation = returnPalletStock.value.ComponentLocation || ''
|
||||
const stockName = returnPalletStock.value.StockName || ''
|
||||
|
||||
const updateRes = await ExecAgvApi('updateStockInfo', {
|
||||
StockCode: returnPalletStock.value.StockCode,
|
||||
StockStatus: false,
|
||||
TrayNo: oldTrayNo,
|
||||
ProjectNo: null,
|
||||
IntervalNo: null,
|
||||
ComponentLocation: null,
|
||||
TaskId: returnPalletStock.value.TaskId
|
||||
})
|
||||
|
||||
if (!updateRes.Result) {
|
||||
ElMessage.error(updateRes.ErrMsg || '库位信息更新失败')
|
||||
return
|
||||
}
|
||||
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_库位更新记录_增加', [
|
||||
['库位号', returnPalletStock.value.StockCode],
|
||||
['库位名称', stockName],
|
||||
['项目', ''],
|
||||
['间隔号', ''],
|
||||
['组件位置', ''],
|
||||
['托盘号', oldTrayNo],
|
||||
['项目_old', ''],
|
||||
['间隔号_old', oldIntervalNo],
|
||||
['组件位置_old', oldComponentLocation],
|
||||
['托盘号_old', oldTrayNo],
|
||||
['操作人', user.value.name],
|
||||
['type', 2],
|
||||
])
|
||||
|
||||
// 创建退空托盘任务
|
||||
const taskId = uuidv4()
|
||||
const res = await ExecAgvApi('createTask', {
|
||||
taskId,
|
||||
pickPosition: returnPalletStock.value.StockCode,
|
||||
dropPosition: stock.code,
|
||||
})
|
||||
|
||||
if (res.Result) {
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_送料记录_增加', [
|
||||
['任务号', taskId],
|
||||
['取料库位', returnPalletStock.value.StockCode],
|
||||
['取料库位名称', stockName],
|
||||
['项目', ''],
|
||||
['间隔号', ''],
|
||||
['组件位置', ''],
|
||||
['托盘号', oldTrayNo],
|
||||
['送料库位', stock.code],
|
||||
['送料库位名称', stock.name],
|
||||
['操作人', user.value.name],
|
||||
['type', 3],
|
||||
])
|
||||
ElMessage.success('退空托盘任务创建成功')
|
||||
getOpStoreCode()
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '退空托盘任务创建失败')
|
||||
}
|
||||
isReturnPalletFlow.value = false
|
||||
}).catch(() => {
|
||||
isReturnPalletFlow.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onInputChange = (val) => {
|
||||
if (val.length > 10) {
|
||||
// 如果 出现 &符号说明是扫描了二维码 解析出来是 <物料编号&物料图号> 只把物料编号赋值给inputValue
|
||||
if (val.split('&').length === 2) {
|
||||
const [EngineNo, SortNo] = val.split('&')
|
||||
inputValue.value = EngineNo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const onActionSelect = async (action) => {
|
||||
showActionSheet.value = false
|
||||
if (action.name === '退料') {
|
||||
// 获取退空托盘目标库区
|
||||
const routes = await getRoutePartitions(currentItem.value.StockCode)
|
||||
if (!routes || !routes.returnPallet) {
|
||||
ElMessage.warning('未配置退空托盘路线,请联系管理员')
|
||||
return
|
||||
}
|
||||
|
||||
// 记录目标库区
|
||||
targetPartition.returnPallet = routes.returnPallet
|
||||
|
||||
// 根据目标库区获取库位列表
|
||||
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: targetPartition.returnPallet })
|
||||
if (!stockRes.Result) {
|
||||
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
|
||||
return
|
||||
}
|
||||
|
||||
// 退空托盘流程:只显示无托盘库位
|
||||
stockList.value = stockRes.Data
|
||||
.filter(i => !i.PalletCode || i.PalletCode === '')
|
||||
.map(i => ({ name: i.StockName, code: i.StockCode, ...i }))
|
||||
|
||||
if (stockList.value.length === 0) {
|
||||
ElMessage.warning('目标库区没有可用库位')
|
||||
return
|
||||
}
|
||||
|
||||
// 设置退空托盘流程标识和数据
|
||||
isReturnPalletFlow.value = true
|
||||
returnPalletStock.value = currentItem.value
|
||||
stockDialogTitle.value = await getPartitionName(targetPartition.returnPallet)
|
||||
showStockDialog.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const onInputConfirm = async () => {
|
||||
if (!inputValue.value) {
|
||||
ElMessage.warning('请输入物料码')
|
||||
return
|
||||
}
|
||||
|
||||
// 如果 出现 &符号说明是扫描了二维码 解析出来是 <物料编号&物料图号> 只把物料编号赋值给inputValue
|
||||
if (inputValue.value.split('&').length === 2) {
|
||||
const [EngineNo, SortNo] = inputValue.value.split('&')
|
||||
inputValue.value = EngineNo
|
||||
}
|
||||
|
||||
if (MaterialData.EngineNo != inputValue.value) {
|
||||
ElMessage.warning(`库位物料编号【${MaterialData.EngineNo}】与扫描物料编号【${inputValue.value}】不匹配`)
|
||||
return
|
||||
}
|
||||
const res = await ExecDatabaseByParam('11', 'PTCHV_物料验证记录_增加', [["工位号", user.value.opName], ["物料编号", inputValue.value], ["物料图号", MaterialData.SortNo]])
|
||||
if (res.data[0].result === 1) {
|
||||
ElMessage.success('物料校验成功')
|
||||
showInputDialog.value = false
|
||||
|
||||
if (user.value.opName == 'OP010') {
|
||||
// 1. 调用 PTCHV_AGV_上线绑定 存储过程
|
||||
const resBind = await ExecDatabaseByParam('11', 'PTCHV_AGV_上线绑定', [['工位号', user.value.opName]])
|
||||
if (resBind.data && resBind.data[0].result === 1) {
|
||||
try {
|
||||
const item = resBind.data[0]
|
||||
const agvReq = {
|
||||
taskId: uuidv4(),
|
||||
EngineNo: item.工件编号,
|
||||
SortNo: item.产品型号,
|
||||
RouteCode: item.工艺路线编号
|
||||
}
|
||||
// 2. 调用 AGV API 生产上线绑定
|
||||
const agvRes = await ExecAgvApi('productionLaunchBind', agvReq)
|
||||
if (agvRes.Result) {
|
||||
// 3. 调用 PTCHV_AGV_工位放行次数_记录 存储过程
|
||||
const resPass = await ExecDatabaseByParam('12', 'PTCHV_AGV_工位放行次数_记录', [
|
||||
['工件编号', item.工件编号],
|
||||
['OP10', agvRes.Data.OP010PassCount],
|
||||
['OP20', agvRes.Data.OP020PassCount],
|
||||
['OP30', agvRes.Data.OP030PassCount]
|
||||
])
|
||||
if (resPass.data && resPass.data[0].result === '1') {
|
||||
// 4. 调用 PTCHV_AGV_进离站 存储过程
|
||||
const param = [
|
||||
['工位号', user.value.opName],
|
||||
['工件编号', item.工件编号],
|
||||
['type', 1],
|
||||
['任务编号', agvReq.taskId]
|
||||
]
|
||||
const resInOut = await ExecDatabaseByParam('12', 'PTCHV_AGV_进离站', param)
|
||||
if (resInOut.data && resInOut.data[0].result === '1') {
|
||||
ElMessage.success('产品上线绑定AGV成功!')
|
||||
} else {
|
||||
ElMessage.error('产品上线绑定AGV失败')
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('放行次数记录失败')
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(agvRes.ErrMsg)
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('调度任务出错' + e.message)
|
||||
console.log(e)
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('调度任务出错' + (resBind.data && resBind.data[0] && resBind.data[0].msg ? resBind.data[0].msg : ''))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.data[0].msg)
|
||||
}
|
||||
}
|
||||
|
||||
const onInputCancel = () => {
|
||||
showInputDialog.value = false
|
||||
}
|
||||
|
||||
const onReturnPalletClick = () => {
|
||||
returnPalletCode.value = ''
|
||||
returnPalletStock.value = null
|
||||
showReturnPalletDialog.value = true
|
||||
}
|
||||
|
||||
const onReturnPalletCancel = () => {
|
||||
showReturnPalletDialog.value = false
|
||||
}
|
||||
|
||||
const onReturnPalletConfirm = async () => {
|
||||
if (!returnPalletCode.value) {
|
||||
ElMessage.warning('请输入托盘码')
|
||||
return
|
||||
}
|
||||
|
||||
// 校验托盘码是否属于本库区
|
||||
const stock = opStoreData.find(i => i.TrayNo === returnPalletCode.value)
|
||||
if (!stock) {
|
||||
ElMessage.error('该托盘码不属于本库区')
|
||||
return
|
||||
}
|
||||
|
||||
returnPalletStock.value = stock
|
||||
showReturnPalletDialog.value = false
|
||||
|
||||
// 获取退空托盘目标库区
|
||||
const routes = await getRoutePartitions(stock.StockCode)
|
||||
if (!routes || !routes.returnPallet) {
|
||||
ElMessage.warning('未配置退空托盘路线,请联系管理员')
|
||||
return
|
||||
}
|
||||
|
||||
// 记录目标库区
|
||||
targetPartition.returnPallet = routes.returnPallet
|
||||
|
||||
// 根据目标库区获取库位列表
|
||||
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: targetPartition.returnPallet })
|
||||
if (!stockRes.Result) {
|
||||
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
|
||||
return
|
||||
}
|
||||
|
||||
// 退空托盘流程:只显示无托盘库位,排除"无库位"
|
||||
stockList.value = (stockRes.Data || [])
|
||||
.filter(i => (!i.TrayNo || i.TrayNo === '') && i.StockName !== '无库位')
|
||||
.map(i => ({ name: i.StockName, code: i.StockCode, ...i }))
|
||||
|
||||
if (stockList.value.length === 0) {
|
||||
ElMessage.warning('目标库区没有可用库位')
|
||||
return
|
||||
}
|
||||
|
||||
// 设置退空托盘流程标识并显示库位选择
|
||||
isReturnPalletFlow.value = true
|
||||
showStockDialog.value = true
|
||||
}
|
||||
|
||||
// 标记当前流程类型
|
||||
const isReturnPalletFlow = ref(false)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card.modern-card {
|
||||
background: #fff;
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.08);
|
||||
padding: 10px 14px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.storList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.storList-item.modern-item {
|
||||
background: linear-gradient(135deg, #f8fafc 60%, #e0e7ef 100%);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
|
||||
padding: 20px 18px 16px 18px;
|
||||
min-width: 260px;
|
||||
max-width: 320px;
|
||||
flex: 1 1 260px;
|
||||
transition: box-shadow 0.2s, transform 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.12);
|
||||
transform: translateY(-2px) scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
.storList-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.storList-title {
|
||||
font-size: 1.18rem;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.storList-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.storList-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
font-size: 0.98rem;
|
||||
color: #555;
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
i {
|
||||
font-size: 1.1em;
|
||||
color: #8bb4f8;
|
||||
}
|
||||
|
||||
b {
|
||||
color: #222;
|
||||
font-weight: 500;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
112
src/views/CallMaterialsSearch/index.vue
Normal file
112
src/views/CallMaterialsSearch/index.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="dataList">
|
||||
<div class="item" v-for="item in recordList" :key="item.ID" @click="onItemClick(item)">
|
||||
<div class="tItem">
|
||||
<div class="name">取料库位:</div>
|
||||
<div class="value">{{ item.取料库位名称 }}</div>
|
||||
</div>
|
||||
<div class="tItem">
|
||||
<div class="name">送料库位:</div>
|
||||
<div class="value">{{ item.送料库位名称 }}</div>
|
||||
</div>
|
||||
<div class="tItem">
|
||||
<div class="name">任务类型:</div>
|
||||
<van-tag :type="getTypeTag(item.任务类型)" size="small">{{ getTypeText(item.任务类型) }}</van-tag>
|
||||
</div>
|
||||
<div class="tItem">
|
||||
<div class="name">操作时间:</div>
|
||||
<div class="value">{{ item.操作时间 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="detailDialogShow" title="详细信息" width="90vw" :close-on-click-modal="true" class="mobile-dialog">
|
||||
<el-descriptions :column="1" border class="mobile-desc">
|
||||
<!-- <el-descriptions-item label="取料库位">{{ detailData.取料库位 }}</el-descriptions-item> -->
|
||||
<el-descriptions-item label="取料库位名称">{{ detailData.取料库位名称 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="取料项目名称">{{ detailData.取料项目名称 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="取料间隔编号">{{ detailData.取料间隔编号 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="取料部件位置">{{ detailData.取料部件位置 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="取料托盘编号">{{ detailData.取料托盘编号 }}</el-descriptions-item>
|
||||
<!-- <el-descriptions-item label="送料库位">{{ detailData.送料库位 }}</el-descriptions-item> -->
|
||||
<el-descriptions-item label="送料库位名称">{{ detailData.送料库位名称 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务类型">{{ getTypeText(detailData.任务类型) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人">{{ detailData.操作人 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作时间">{{ detailData.操作时间 }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
|
||||
const recordList = ref([])
|
||||
const detailDialogShow = ref(false)
|
||||
const detailData = ref({})
|
||||
|
||||
onMounted(async () => {
|
||||
// 查询送料记录
|
||||
const res = await ExecDatabaseByParam('11', 'PTCHV_AGV_送料记录_PDA_查询', [])
|
||||
if (res.data) {
|
||||
recordList.value = res.data
|
||||
} else {
|
||||
ElMessage.error('查询失败')
|
||||
}
|
||||
})
|
||||
|
||||
// 离开网页 删除定时器
|
||||
onUnmounted(() => {
|
||||
})
|
||||
|
||||
const getTypeText = (type) => {
|
||||
if (type === 1 || type === '1') return '叫料'
|
||||
if (type === 2 || type === '2') return '退料'
|
||||
if (type === 3 || type === '3') return '退空托'
|
||||
if (type === 4 || type === '4') return '一键移库'
|
||||
return '未知'
|
||||
}
|
||||
|
||||
const getTypeTag = (type) => {
|
||||
if (type === 1 || type === '1') return 'success'
|
||||
if (type === 2 || type === '2') return 'warning'
|
||||
if (type === 3 || type === '3') return 'primary'
|
||||
if (type === 4 || type === '4') return 'primary'
|
||||
return 'default'
|
||||
}
|
||||
|
||||
const onItemClick = (item) => {
|
||||
detailData.value = item
|
||||
detailDialogShow.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dataList {
|
||||
.item {
|
||||
border: 2px solid #ccc;
|
||||
padding: 10px 10px;
|
||||
// 圆角 阴影
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
margin: 10px 0;
|
||||
.tItem {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-dialog {
|
||||
max-width: 98vw;
|
||||
}
|
||||
|
||||
.mobile-desc {
|
||||
word-break: break-all;
|
||||
font-size: 15px;
|
||||
.el-descriptions__cell {
|
||||
padding: 6px 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
438
src/views/CarMaterialsBind/index.vue
Normal file
438
src/views/CarMaterialsBind/index.vue
Normal file
@@ -0,0 +1,438 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<div class="card">
|
||||
<el-form-item label="选择库区">
|
||||
<el-select v-model="selectedPartition" placeholder="请选择库区" @change="onPartitionChange" style="width: 180px">
|
||||
<el-option v-for="item in partitionList" :key="item.PartitionCode" :label="item.PartitionName"
|
||||
:value="item.PartitionCode" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div class="storList">
|
||||
<div class="storList-item modern-item" v-for="item in stockList" :key="item.StockCode"
|
||||
@click="onStockCardClick(item)">
|
||||
<div class="storList-header">
|
||||
<span class="storList-title">{{ item.StockName }}</span>
|
||||
<div class="storList-tags">
|
||||
<van-tag type="danger" v-if="item.TaskId != null">任务中</van-tag>
|
||||
<van-tag type="primary" v-if="item.StockStatus == false && !item.TrayNo">空库位</van-tag>
|
||||
<van-tag type="warning" v-if="item.StockStatus == false && item.TrayNo">空托盘</van-tag>
|
||||
<van-tag type="success" v-if="item.StockStatus == true">满料</van-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="storList-info">
|
||||
<span>项目名称:<b>{{ item.ProjectNo || '-' }}</b></span>
|
||||
<span>间隔编号:<b>{{ item.IntervalNo || '-' }}</b></span>
|
||||
<span>部件位置:<b>{{ item.ComponentLocation || '-' }}</b></span>
|
||||
<span>托盘编号:<b>{{ item.TrayNo || '-' }}</b></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="showEditStockDialog" title="修改库位信息" width="340px" :close-on-click-modal="false"
|
||||
@close="onEditStockCancel">
|
||||
<el-form @submit.prevent>
|
||||
<el-form-item label="项目名称">
|
||||
<el-input v-model="editStockForm.ProjectName" placeholder="请扫描或输入 项目名称 或 三段码" clearable @input="onProjectNameInput" />
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔编号">
|
||||
<el-input v-model="editStockForm.IntervalNo" placeholder="请输入 间隔编号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="部件位置">
|
||||
<el-input v-model="editStockForm.ComponentLocation" placeholder="请输入 部件位置" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="托盘编号">
|
||||
<el-input v-model="editStockForm.TrayNo" placeholder="请输入 托盘编号(仅托盘码时只填此项)" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onEditStockCancel">取消</el-button>
|
||||
<el-button type="primary" @click="onEditStockConfirm">确认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<van-action-sheet v-model:show="showEditActionSheet" :actions="editActionList" @select="onEditActionSelect"
|
||||
title="请选择操作" />
|
||||
<van-action-sheet v-model:show="showTaskActionSheet" :actions="taskActionList" @select="onTaskActionSelect" title="任务操作" />
|
||||
|
||||
<!-- 增加目标库区选择弹窗 -->
|
||||
<el-dialog v-model="showTargetPartitionDialog" title="选择目标库区" width="320px" :close-on-click-modal="false">
|
||||
<el-select v-model="selectedTargetPartition" placeholder="请选择目标库区" style="width: 100%">
|
||||
<el-option v-for="item in partitionList" :key="item.PartitionCode" :label="item.PartitionName"
|
||||
:value="item.PartitionCode" />
|
||||
</el-select>
|
||||
<template #footer>
|
||||
<el-button @click="showTargetPartitionDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="onTargetPartitionConfirm">确认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 增加目标库位选择弹窗 -->
|
||||
<el-dialog v-model="showTargetStockDialog" :title="targetPartitionName" width="400px" :close-on-click-modal="false">
|
||||
<div v-if="targetStockList.length > 0">
|
||||
<div v-for="item in targetStockList" :key="item.StockCode" @click="onTargetStockClick(item)"
|
||||
style="cursor:pointer; border-bottom:1px solid #eee; padding:12px 0;">
|
||||
<div style="display:flex;flex-direction:column;">
|
||||
<span><b>库位名称:</b>{{ item.StockName }}</span>
|
||||
<span><b>项目名称:</b>{{ item.ProjectNo || '-' }}</span>
|
||||
<span><b>间隔编号:</b>{{ item.IntervalNo || '-' }}</span>
|
||||
<span><b>部件位置:</b>{{ item.ComponentLocation || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="text-align:center;color:#888;">暂无可用库位</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||
import { ElForm, ElFormItem, ElSelect, ElOption } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Dialog } from 'vant'
|
||||
import { useAppStore } from '@/store/app.js'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
const store = useAppStore()
|
||||
const { user } = storeToRefs(store)
|
||||
|
||||
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
|
||||
const ExecAgvApi = inject('ExecAgvApi')
|
||||
|
||||
const partitionList = ref([])
|
||||
const selectedPartition = ref()
|
||||
const stockList = ref([])
|
||||
const showEditStockDialog = ref(false)
|
||||
const showEditActionSheet = ref(false)
|
||||
const showTargetPartitionDialog = ref(false)
|
||||
const showTargetStockDialog = ref(false)
|
||||
const selectedTargetPartition = ref('')
|
||||
const targetPartitionName = ref('')
|
||||
const targetStockList = ref([])
|
||||
const currentSourceStock = ref(null)
|
||||
const showTaskActionSheet = ref(false)
|
||||
|
||||
const editActionList = [
|
||||
{ name: '修改库位信息' },
|
||||
{ name: '送料' }
|
||||
]
|
||||
const taskActionList = [
|
||||
{ name: '取消任务' }
|
||||
]
|
||||
const editStockForm = reactive({
|
||||
ProjectName: '',
|
||||
IntervalNo: '',
|
||||
ComponentLocation: '',
|
||||
TrayNo: '',
|
||||
StockCode: '',
|
||||
TaskId: ''
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
// 1) 获取 AGV 全量库区列表(名称映射用)
|
||||
const partitionRes = await ExecAgvApi('getPartitionList', {})
|
||||
if (!partitionRes.Result) {
|
||||
ElMessage.error(partitionRes.ErrMsg || '获取库区失败')
|
||||
return
|
||||
}
|
||||
const allPartitions = partitionRes.Data || []
|
||||
|
||||
// 2) 根据工位查询可用库区:-1 表示可选全部;否则为逗号分隔的库区号
|
||||
const mesRes = await ExecDatabaseByParam('11', 'MES_计划BOM_工位与名称_库位编号_查询', [['工位号', user.value.opName]])
|
||||
if (mesRes.data && mesRes.data.length > 0) {
|
||||
const codeStr = mesRes.data[0].库位编号
|
||||
const isAll = codeStr != null && codeStr.toString().trim() === '-1'
|
||||
if (isAll) {
|
||||
partitionList.value = allPartitions
|
||||
} else {
|
||||
const codes = codeStr.toString().split(',').map(s => s.trim()).filter(Boolean)
|
||||
partitionList.value = allPartitions.filter(p => codes.includes(p.PartitionCode?.toString()))
|
||||
}
|
||||
} else {
|
||||
ElMessage.warning('此工位无对应库区权限配置')
|
||||
partitionList.value = []
|
||||
}
|
||||
|
||||
// 3) 默认选中第一个可用库区并加载库位
|
||||
if (partitionList.value.length > 0) {
|
||||
selectedPartition.value = partitionList.value[0].PartitionCode
|
||||
await onPartitionChange(selectedPartition.value)
|
||||
}
|
||||
})
|
||||
|
||||
const onProjectNameInput = () => {
|
||||
const raw = editStockForm.ProjectName || ''
|
||||
const parts = raw.split('&')
|
||||
if (parts.length === 3) {
|
||||
editStockForm.ProjectName = parts[0].trim()
|
||||
editStockForm.IntervalNo = parts[1].trim()
|
||||
editStockForm.ComponentLocation = parts[2].trim()
|
||||
}
|
||||
}
|
||||
|
||||
const onPartitionChange = async (val) => {
|
||||
// 拉该库区下的库位列表
|
||||
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: val })
|
||||
if (stockRes.Result) {
|
||||
stockList.value = stockRes.Data
|
||||
} else {
|
||||
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
|
||||
}
|
||||
}
|
||||
|
||||
const onStockCardClick = (item) => {
|
||||
// 任务中的库位不可操作
|
||||
if (item.TaskId != null) {
|
||||
// 弹出操作菜单,只有取消任务选项
|
||||
currentSourceStock.value = item
|
||||
showEditActionSheet.value = false
|
||||
showTaskActionSheet.value = true
|
||||
return
|
||||
}
|
||||
|
||||
// 保存被点击的库位信息
|
||||
editStockForm.ProjectName = item.ProjectNo || ''
|
||||
editStockForm.IntervalNo = item.IntervalNo || ''
|
||||
editStockForm.ComponentLocation = item.ComponentLocation || ''
|
||||
editStockForm.TrayNo = item.TrayNo || ''
|
||||
editStockForm.StockCode = item.StockCode
|
||||
editStockForm.TaskId = item.TaskId
|
||||
currentSourceStock.value = item
|
||||
|
||||
// 显示操作菜单
|
||||
showEditActionSheet.value = true
|
||||
}
|
||||
|
||||
const onEditActionSelect = (action) => {
|
||||
showEditActionSheet.value = false
|
||||
if (action.name === '修改库位信息') {
|
||||
showEditStockDialog.value = true
|
||||
} else if (action.name === '送料') {
|
||||
// 空库位无料无托盘无法送料
|
||||
if (!currentSourceStock.value.StockStatus && !currentSourceStock.value.TrayNo) {
|
||||
Dialog.alert({
|
||||
title: '提示',
|
||||
message: '空库位无托盘无法送料',
|
||||
})
|
||||
return
|
||||
}
|
||||
// 打开目标库区选择弹窗
|
||||
showTargetPartitionDialog.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const onEditStockConfirm = async () => {
|
||||
// 处理参数
|
||||
const TrayNo = editStockForm.TrayNo ? editStockForm.TrayNo : null
|
||||
const ProjectNo = editStockForm.ProjectName ? editStockForm.ProjectName : null
|
||||
const IntervalNo = editStockForm.IntervalNo ? editStockForm.IntervalNo : null
|
||||
const ComponentLocation = editStockForm.ComponentLocation ? editStockForm.ComponentLocation : null
|
||||
// 间隔号与部件位置都存在则判定满料
|
||||
const StockStatus = (IntervalNo && ComponentLocation) ? true : false
|
||||
const params = {
|
||||
StockCode: editStockForm.StockCode,
|
||||
TrayNo,
|
||||
ProjectNo,
|
||||
IntervalNo,
|
||||
ComponentLocation,
|
||||
StockStatus,
|
||||
TaskId: editStockForm.TaskId
|
||||
}
|
||||
// 记录旧值
|
||||
const oldTrayNo = stockList.value.find(i => i.StockCode === editStockForm.StockCode)?.TrayNo || null
|
||||
const oldProjectNo = stockList.value.find(i => i.StockCode === editStockForm.StockCode)?.ProjectNo || null
|
||||
const oldIntervalNo = stockList.value.find(i => i.StockCode === editStockForm.StockCode)?.IntervalNo || null
|
||||
const oldComponentLocation = stockList.value.find(i => i.StockCode === editStockForm.StockCode)?.ComponentLocation || null
|
||||
const stockName = stockList.value.find(i => i.StockCode === editStockForm.StockCode)?.StockName || ''
|
||||
const res = await ExecAgvApi('updateStockInfo', params)
|
||||
if (res.Result) {
|
||||
// 调用存储过程记录日志
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_库位更新记录_增加', [
|
||||
['库位号', editStockForm.StockCode],
|
||||
['库位名称', stockName],
|
||||
['项目名称', ProjectNo || ''],
|
||||
['间隔编号', IntervalNo || ''],
|
||||
['部件位置', ComponentLocation || ''],
|
||||
['托盘编号', TrayNo || ''],
|
||||
['项目名称_old', oldProjectNo || ''],
|
||||
['间隔编号_old', oldIntervalNo || ''],
|
||||
['部件位置_old', oldComponentLocation || ''],
|
||||
['托盘编号_old', oldTrayNo || ''],
|
||||
['操作人', user.value.name],
|
||||
['type', 1],
|
||||
])
|
||||
ElMessage.success('库位信息修改成功')
|
||||
showEditStockDialog.value = false
|
||||
await onPartitionChange(selectedPartition.value)
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '库位信息修改失败')
|
||||
}
|
||||
}
|
||||
|
||||
const onEditStockCancel = () => {
|
||||
showEditStockDialog.value = false
|
||||
}
|
||||
|
||||
// 确认目标库区选择
|
||||
const onTargetPartitionConfirm = async () => {
|
||||
if (!selectedTargetPartition.value) {
|
||||
ElMessage.warning('请选择目标库区')
|
||||
return
|
||||
}
|
||||
|
||||
// 根据目标库区获取库位列表
|
||||
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: selectedTargetPartition.value })
|
||||
if (!stockRes.Result) {
|
||||
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
|
||||
return
|
||||
}
|
||||
|
||||
// 只显示空库位(无托盘且无物料)
|
||||
targetStockList.value = stockRes.Data
|
||||
.filter(i => i.StockStatus === false && !i.TrayNo)
|
||||
.map(i => ({ ...i }))
|
||||
|
||||
if (targetStockList.value.length === 0) {
|
||||
ElMessage.warning('目标库区没有可用空库位')
|
||||
return
|
||||
}
|
||||
|
||||
// 获取目标库区名称
|
||||
const targetPartition = partitionList.value.find(p => p.PartitionCode == selectedTargetPartition.value)
|
||||
targetPartitionName.value = targetPartition ? targetPartition.PartitionName : '未知库区'
|
||||
|
||||
// 关闭库区选择,打开库位选择
|
||||
showTargetPartitionDialog.value = false
|
||||
showTargetStockDialog.value = true
|
||||
}
|
||||
|
||||
// 点击目标库位,创建送料任务
|
||||
const onTargetStockClick = (targetStock) => {
|
||||
// 二次确认
|
||||
ElMessageBox.confirm(`是否将物料从库位【${currentSourceStock.value.StockName}】送至库位【${targetStock.StockName}】?`, '确认送料', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
// 创建AGV任务
|
||||
const taskId = uuidv4()
|
||||
const res = await ExecAgvApi('createTask', {
|
||||
taskId,
|
||||
pickPosition: currentSourceStock.value.StockCode,
|
||||
dropPosition: targetStock.StockCode,
|
||||
})
|
||||
|
||||
if (res.Result) {
|
||||
// 调用存储过程记录送料日志(新字段)
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_送料记录_增加', [
|
||||
['任务号', taskId],
|
||||
['取料库位', currentSourceStock.value.StockCode],
|
||||
['取料库位名称', currentSourceStock.value.StockName],
|
||||
['取料项目名称', currentSourceStock.value.ProjectNo || currentSourceStock.value.ProjectName || ''],
|
||||
['取料间隔编号', currentSourceStock.value.IntervalNo || ''],
|
||||
['取料部件位置', currentSourceStock.value.ComponentLocation || ''],
|
||||
['取料托盘编号', currentSourceStock.value.TrayNo || ''],
|
||||
['送料库位', targetStock.StockCode],
|
||||
['送料库位名称', targetStock.StockName],
|
||||
['操作人', user.value.name],
|
||||
['type', 1],
|
||||
])
|
||||
|
||||
ElMessage.success('送料任务创建成功')
|
||||
showTargetStockDialog.value = false
|
||||
// 刷新库位列表
|
||||
await onPartitionChange(selectedPartition.value)
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '送料任务创建失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消操作
|
||||
})
|
||||
}
|
||||
|
||||
// 任务操作菜单弹窗
|
||||
const onTaskActionSelect = (action) => {
|
||||
showTaskActionSheet.value = false
|
||||
if (action.name === '取消任务') {
|
||||
ElMessageBox.confirm('是否确认取消该任务?', '取消任务', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const res = await ExecAgvApi('cancelTask', { taskId: currentSourceStock.value.TaskId })
|
||||
if (res.Result) {
|
||||
ElMessage.success('任务已取消')
|
||||
await onPartitionChange(selectedPartition.value)
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '取消任务失败')
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 可复用呼叫物料模块的样式
|
||||
.storList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.storList-item.modern-item {
|
||||
background: linear-gradient(135deg, #f8fafc 60%, #e0e7ef 100%);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
|
||||
padding: 20px 18px 16px 18px;
|
||||
min-width: 260px;
|
||||
max-width: 320px;
|
||||
flex: 1 1 260px;
|
||||
transition: box-shadow 0.2s, transform 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.12);
|
||||
transform: translateY(-2px) scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
.storList-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.storList-title {
|
||||
font-size: 1.18rem;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.storList-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.storList-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
font-size: 0.98rem;
|
||||
color: #555;
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
b {
|
||||
color: #222;
|
||||
font-weight: 500;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
103
src/views/CarMaterialsBindSearch/index.vue
Normal file
103
src/views/CarMaterialsBindSearch/index.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="dataList">
|
||||
<div class="item" v-for="item in recordList" :key="item.ID" @click="onItemClick(item)">
|
||||
<div class="tItem">
|
||||
<div class="name">库位名称:</div>
|
||||
<div class="value">{{ item.库位名称 }}</div>
|
||||
</div>
|
||||
<div class="tItem">
|
||||
<div class="name">操作时间:</div>
|
||||
<div class="value">{{ item.操作时间 }}</div>
|
||||
</div>
|
||||
<div class="tItem">
|
||||
<div class="name">操作类型:</div>
|
||||
<van-tag class="value" :type="getTypeTag(item.type || item.操作类型)">{{ getTypeText(item.type || item.操作类型)
|
||||
}}</van-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="detailDialogShow" title="详细信息" width="90vw" :close-on-click-modal="true" class="mobile-dialog">
|
||||
<el-descriptions :column="1" border class="mobile-desc">
|
||||
<el-descriptions-item label="库位名称">{{ detailData.库位名称 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目名称(新)">{{ detailData.项目名称 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="间隔编号(新)">{{ detailData.间隔编号 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="部件位置(新)">{{ detailData.部件位置 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="托盘编号(新)">{{ detailData.托盘编号 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目名称(旧)">{{ detailData.项目名称_old }}</el-descriptions-item>
|
||||
<el-descriptions-item label="间隔编号(旧)">{{ detailData.间隔编号_old }}</el-descriptions-item>
|
||||
<el-descriptions-item label="部件位置(旧)">{{ detailData.部件位置_old }}</el-descriptions-item>
|
||||
<el-descriptions-item label="托盘编号(旧)">{{ detailData.托盘编号_old }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作人">{{ detailData.操作人 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作类型">{{ getTypeText(detailData.type || detailData.操作类型) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作时间">{{ detailData.操作时间 }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
|
||||
const recordList = ref([])
|
||||
const detailDialogShow = ref(false)
|
||||
const detailData = ref({})
|
||||
|
||||
onMounted(async () => {
|
||||
// 查询库位更新记录
|
||||
const res = await ExecDatabaseByParam('11', 'PTCHV_AGV_库位更新记录_PDA_查询', [])
|
||||
if (res.data) {
|
||||
recordList.value = res.data
|
||||
} else {
|
||||
ElMessage.error('查询失败')
|
||||
}
|
||||
})
|
||||
|
||||
// 离开网页 删除定时器
|
||||
onUnmounted(() => {
|
||||
})
|
||||
|
||||
const getTypeText = (type) => {
|
||||
if (type === 2 || type === '2') return '退空托盘'
|
||||
return '修改信息'
|
||||
}
|
||||
const getTypeTag = (type) => {
|
||||
if (type === 2 || type === '2') return 'warning'
|
||||
return 'success'
|
||||
}
|
||||
const onItemClick = (item) => {
|
||||
detailData.value = item
|
||||
detailDialogShow.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dataList {
|
||||
.item {
|
||||
border: 2px solid #ccc;
|
||||
padding: 10px 10px;
|
||||
// 圆角 阴影
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
margin: 10px 0;
|
||||
|
||||
.tItem {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-dialog {
|
||||
max-width: 98vw;
|
||||
}
|
||||
|
||||
.mobile-desc {
|
||||
word-break: break-all;
|
||||
font-size: 15px;
|
||||
.el-descriptions__cell {
|
||||
padding: 6px 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
246
src/views/Layout/index.vue
Normal file
246
src/views/Layout/index.vue
Normal file
@@ -0,0 +1,246 @@
|
||||
<template>
|
||||
<div class="topBar">
|
||||
<t-head-menu v-model="menuValue" style="height: 8vh;" theme="dark">
|
||||
<template #logo>
|
||||
<div class="logo" />
|
||||
</template>
|
||||
<div style="line-height: 8vh;width: 70vw;overflow: auto;display: flex">
|
||||
<t-menu-item v-for="router in routerData.userRouterList" :selected="router.selected" :value="router.path"
|
||||
:key="router.path" style="height: 8vh" @click="routerTo(router)">
|
||||
{{ router.权限模块 }}
|
||||
</t-menu-item>
|
||||
</div>
|
||||
<template #operations>
|
||||
<div class="t-demo-menu--dark">
|
||||
<el-button class="logout-btn" type="text" @click="logout">
|
||||
<el-icon style="font-size: 24px;">
|
||||
<SwitchButton />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</t-head-menu>
|
||||
</div>
|
||||
<div class="main">
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, onMounted, ref } from 'vue'
|
||||
import { useAppStore } from '@/store/app.js'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { addDynamicRoutes } from '@/router/index.js'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Cookies from 'js-cookie'
|
||||
import { SwitchButton } from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useAppStore()
|
||||
const CreateData = inject('CreateData')
|
||||
const ExecDatabase = inject('ExecDatabase')
|
||||
// storeToRefs 响应式解构赋值 pinia
|
||||
const { user, isLoading, routerData } = storeToRefs(store)
|
||||
|
||||
const menuValue = ref('')
|
||||
const date = ref('')
|
||||
const time = ref('')
|
||||
const nowWeek = ref('')
|
||||
const week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
|
||||
const now = new Date()
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
if (!store.user.AID) {
|
||||
if (Cookies.get('AID')) {
|
||||
user.value.token = Cookies.get('token')
|
||||
user.value.AID = Cookies.get('AID')
|
||||
user.value.name = Cookies.get('name')
|
||||
user.value.role = Cookies.get('role')
|
||||
user.value.userName = Cookies.get('userName')
|
||||
user.value.opName = Cookies.get('opName')
|
||||
user.value.opNameTxt = Cookies.get('opNameTxt')
|
||||
|
||||
var param = []
|
||||
param[0] = ['工位号', store.user.opName]
|
||||
var Data = CreateData('11', 'PDA_基础表_权限模块_查询', param)
|
||||
ExecDatabase(Data).then(response => {
|
||||
if (response.data.length > 0) {
|
||||
for (let item of response.data) {
|
||||
item.selected = false
|
||||
}
|
||||
response.data[0].selected = true
|
||||
routerData.value.userRouterList = response.data
|
||||
initRouter()
|
||||
} else {
|
||||
ElMessage.warning('该角色未分配角色模块,请先进行分配!')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('store.user.AID为空,返回主页!')
|
||||
ElMessage.warning('用户信息过期,请重新登陆!')
|
||||
router.push({ path: '/login' })
|
||||
}
|
||||
} else {
|
||||
getRouter()
|
||||
// 获取当前实时日期时间 与星期几
|
||||
// setInterval(() => {
|
||||
// date.value = now.toLocaleDateString()
|
||||
// time.value = now.toLocaleTimeString()
|
||||
// nowWeek.value = week[now.getDay()]
|
||||
// }, 1000)
|
||||
}
|
||||
}, 200)
|
||||
})
|
||||
|
||||
// 获取权限列表
|
||||
const getRouter = async () => {
|
||||
// 获取权限列表
|
||||
var param = []
|
||||
param[0] = ['工位号', store.user.opName]
|
||||
var Data = CreateData('11', 'PDA_基础表_权限模块_查询', param)
|
||||
ExecDatabase(Data).then(response => {
|
||||
if (response.data.length > 0) {
|
||||
for (let item of response.data) {
|
||||
item.selected = false
|
||||
}
|
||||
response.data[0].selected = true
|
||||
routerData.value.userRouterList = response.data
|
||||
initRouter()
|
||||
} else {
|
||||
ElMessage.warning('该角色未分配角色模块,请先进行分配!')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const initRouter = async () => {
|
||||
// 动态添加路由
|
||||
await addDynamicRoutes(routerData.value.userRouterList).then(() => {
|
||||
menuValue.value = routerData.value.userRouterList[0].path
|
||||
routerData.nowRouterPath = routerData.value.userRouterList[0].path
|
||||
routerData.nowRouterTitle = routerData.value.userRouterList[0].权限模块
|
||||
router.push({ path: '/' + routerData.value.userRouterList[0].path })
|
||||
store.isLoading = false
|
||||
})
|
||||
}
|
||||
|
||||
const routerTo = (item) => {
|
||||
const targetRoute = routerData.value.userRouterList.find(r => r.path === item.path)
|
||||
if (targetRoute) {
|
||||
routerData.nowRouterPath = targetRoute.path
|
||||
routerData.nowRouterTitle = targetRoute.权限模块
|
||||
routerData.selected = true
|
||||
router.push({ path: '/' + targetRoute.path })
|
||||
}
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
ElMessage.warning('退出登录成功!')
|
||||
store.$reset() // 重置pinia数据
|
||||
router.push('/login')
|
||||
// var param = []
|
||||
// param[0] = ['userId', user.AID]
|
||||
// param[1] = ['opName', user.opName]
|
||||
// var Data = CreateData('11', '基础表_用户角色_退出登录', param)
|
||||
// ExecDatabase(Data).then(response => {
|
||||
// if (response.data.length > 0) {
|
||||
// ElMessage.warning('退出登录成功!')
|
||||
// store.$reset() // 重置pinia数据
|
||||
// router.push('/login')
|
||||
// } else {
|
||||
// ElMessage.warning('退出登录失败,数据未清空!')
|
||||
// }
|
||||
// })
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.topBar {
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
|
||||
.logo {
|
||||
width: 20vw;
|
||||
height: 10vh;
|
||||
background-image: url("/public/TopLogo.png");
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.dateTime {
|
||||
font-size: .8vw;
|
||||
float: right;
|
||||
width: 10vw;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
|
||||
div {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 主内容窗体大小
|
||||
.main {
|
||||
height: 92vh;
|
||||
width: 100vw;
|
||||
overflow: auto;
|
||||
background-color: #f0f3f6;
|
||||
}
|
||||
|
||||
// .t-menu__operations {
|
||||
// .t-button {
|
||||
// margin-left: 8px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.t-demo-menu--dark {
|
||||
.t-button {
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: #4b4b4b;
|
||||
border-color: transparent;
|
||||
--ripple-color: #383838;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// logo 两旁留空
|
||||
::v-deep() .t-head-menu .t-menu__logo:not(:empty) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
::v-deep() .t-menu__logo>* {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
// 顶部栏高度
|
||||
::v-deep() .t-head-menu__inner {
|
||||
height: 8vh;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
/* 设置纵轴(y轴)轴滚动条 */
|
||||
height: 4px;
|
||||
/* 设置横轴(x轴)轴滚动条 */
|
||||
}
|
||||
|
||||
/* 滚动条滑块(里面小方块) */
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
|
||||
background: #7ba2cc;
|
||||
}
|
||||
|
||||
/* 滚动条轨道 */
|
||||
::-webkit-scrollbar-track {
|
||||
border-radius: 0;
|
||||
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
218
src/views/Login/index.vue
Normal file
218
src/views/Login/index.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="main">
|
||||
<div class="login">
|
||||
<div class="title">Login</div>
|
||||
<div class="logeType">
|
||||
<div class="item">用户名登录</div>
|
||||
</div>
|
||||
<div class="logFrom">
|
||||
<div class="item">
|
||||
<el-input v-model="userform.userName" :prefix-icon="User" clearable placeholder="请输入用户名" style="height: 40px"
|
||||
type="text" @input="userform.password = ''" />
|
||||
</div>
|
||||
<div class="item">
|
||||
<el-input v-model="userform.password" :prefix-icon="Lock" clearable placeholder="请输入密码" show-password
|
||||
style="height: 40px" type="password" @keyup.enter.native="login" />
|
||||
</div>
|
||||
<div class="item" style="margin-top: 30px">
|
||||
<el-button :prefix-icon="Promotion" style="width: 100%" type="danger" @click="login">
|
||||
登录
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, onMounted, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAppStore } from "@/store/app.js";
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { encrypt, cryptoDecrypt, generateSalt } from "../../utils/AES-Pkcs7";
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
const router = useRouter()
|
||||
const CreateData = inject('CreateData')
|
||||
const ExecDatabase = inject('ExecDatabase')
|
||||
const store = useAppStore();
|
||||
// storeToRefs 响应式解构赋值 pinia
|
||||
const { user, isLoading } = storeToRefs(store);
|
||||
|
||||
const opNameList = reactive([])
|
||||
const userform = reactive({
|
||||
userName: '',
|
||||
password: '',
|
||||
opName: ''
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getOpName()
|
||||
})
|
||||
|
||||
|
||||
// const login = () => {
|
||||
// console.log(user)
|
||||
// let cost = generateSalt().slice(-16)
|
||||
// var param = []
|
||||
// param[0] = ['userName', userform.userName]
|
||||
// param[1] = ['password', encrypt(userform.password, cost)]
|
||||
// param[2] = ['opName', userform.opName]
|
||||
// var Data = CreateData('11', 'PDA_用户角色_用户登录', param)
|
||||
// ExecDatabase(Data).then(response => {
|
||||
// if (response.data.length > 0) {
|
||||
// user.value.AID = response.data[0].AID
|
||||
// user.value.name = response.data[0].姓名
|
||||
// // user.value.classes = response.data[0].班次
|
||||
// // user.value.teamGroup = response.data[0].班组
|
||||
// user.value.role = response.data[0].用户角色
|
||||
// user.value.userName = response.data[0].登录密码
|
||||
// user.value.password = response.data[0].登录账号
|
||||
// user.value.opName = userform.opName
|
||||
// ElMessage.success('登录成功,跳转主页')
|
||||
// store.isLoading = true
|
||||
// router.push('/')
|
||||
// } else {
|
||||
// ElMessage.warning('用户名或密码错误,请重试!')
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
const login = () => {
|
||||
console.log(user)
|
||||
let cost = generateSalt().slice(-16)
|
||||
var param = []
|
||||
param.push(['用户名', userform.userName])
|
||||
// param.push(['Password', encrypt(userform.password, cost)])
|
||||
param.push(['密码', userform.password])
|
||||
// param.push(['cost', cost])
|
||||
var Data = CreateData('11', '菜单模块系统_用户名密码_查询数据_改造新增', param)
|
||||
ExecDatabase(Data).then(response => {
|
||||
const data = response.data
|
||||
if (data.length === 0 || data.result === '0' || data[0].result === '0') {
|
||||
ElMessage.warning('账号或密码错误,请重试!')
|
||||
} else {
|
||||
// 检查用户是否有绑定PDA权限
|
||||
if (!data[0].PDA权限) {
|
||||
ElMessage.warning('请先维护此用户PDA角色后再操作')
|
||||
return
|
||||
}
|
||||
|
||||
if (!opNameList.some(item => item.工位号 === data[0].PDA权限)) {
|
||||
ElMessage.warning('您绑定的PDA权限不在可用工位列表中,请联系管理员维护')
|
||||
return
|
||||
}
|
||||
|
||||
// 设置工位号并提示
|
||||
userform.opName = data[0].PDA权限
|
||||
ElMessage.success(`检测到您已绑定PDA权限:${opNameList.find(item => item.工位号 === data[0].PDA权限)?.工位名称 || data[0].PDA权限}`)
|
||||
|
||||
// 获取工位对应的工位名称 一起存入
|
||||
let opName = opNameList.find(item => item.工位号 === userform.opName)
|
||||
|
||||
if (opName.线体类型 != 0) {
|
||||
const param2 = []
|
||||
param2[0] = ['工位号', userform.opName]
|
||||
param2[1] = ['操作者工号', data[0].姓名]
|
||||
param2[2] = ['班次名称', '1']
|
||||
const Data = CreateData('12', '测量数据工位_监控系统_操作者工号_增加', param2)
|
||||
ExecDatabase(Data).then(() => {
|
||||
})
|
||||
}
|
||||
|
||||
Cookies.set('token', data[0].token, { expires: 1 })
|
||||
Cookies.set('AID', data[0].人员编号, { expires: 1 })
|
||||
Cookies.set('name', data[0].姓名, { expires: 1 })
|
||||
Cookies.set('role', data[0].角色编号, { expires: 1 })
|
||||
Cookies.set('userName', data[0].考勤编号, { expires: 1 })
|
||||
Cookies.set('opName', userform.opName, { expires: 1 })
|
||||
Cookies.set('opNameTxt', opName.工位名称, { expires: 1 })
|
||||
|
||||
user.value.token = response.data[0].token
|
||||
user.value.AID = response.data[0].人员编号
|
||||
user.value.name = response.data[0].姓名
|
||||
user.value.role = response.data[0].角色编号
|
||||
user.value.userName = response.data[0].考勤编号
|
||||
user.value.opName = userform.opName
|
||||
user.value.opNameTxt = opName.工位名称
|
||||
|
||||
ElMessage.success('登录成功')
|
||||
store.isLoading = true
|
||||
router.push('/')
|
||||
}
|
||||
if (response.data.length > 0) {
|
||||
} else {
|
||||
ElMessage.warning('用户名或密码错误,请重试!')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getOpName = () => {
|
||||
opNameList.length = 0
|
||||
// 获取工位数据(用于验证用户绑定的PDA权限是否存在)
|
||||
var param = []
|
||||
var Data = CreateData('11', 'MES_计划BOM_工位与名称_查询', param)
|
||||
ExecDatabase(Data).then(response => {
|
||||
if (response.data.length > 0) {
|
||||
opNameList.push(...response.data)
|
||||
}
|
||||
console.log(response)
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-image: url("/public/login.png");
|
||||
background-size: 300% 150%;
|
||||
|
||||
.login {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 80vw;
|
||||
//height: 40vh;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border: 2px solid #730215;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.8);
|
||||
|
||||
.title {
|
||||
color: #f50107;
|
||||
font-size: 40px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
margin-top: 3vh;
|
||||
}
|
||||
|
||||
.logeType {
|
||||
width: 80%;
|
||||
display: flex;
|
||||
border-bottom: 3px solid #bcbcbc;
|
||||
margin: 30px auto;
|
||||
height: 30px;
|
||||
|
||||
.item {
|
||||
color: #e43852;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
border-bottom: 2px solid #ff123a;
|
||||
}
|
||||
}
|
||||
|
||||
.logFrom {
|
||||
width: 80%;
|
||||
margin: 0px auto;
|
||||
|
||||
.item {
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
859
src/views/MaterialsOutLine/index.vue
Normal file
859
src/views/MaterialsOutLine/index.vue
Normal file
@@ -0,0 +1,859 @@
|
||||
<template>
|
||||
<div class="card modern-card">
|
||||
<div style="display: flex; justify-content: flex-end; margin-bottom: 12px;position: relative;">
|
||||
<div v-if="currentPartitionName"
|
||||
style="position: absolute; left: 10px; top: 10px; font-size: .8rem; color: #409EFF; font-weight: bold; letter-spacing: 1px;">
|
||||
{{ currentPartitionName }}
|
||||
</div>
|
||||
<!-- <el-button type="primary" @click="onReturnPalletClick">退空托盘</el-button> -->
|
||||
</div>
|
||||
<div class="storList">
|
||||
<div class="storList-item modern-item" v-for="item in opStoreData" :key="item.StockCode"
|
||||
@click="handleClick(item)">
|
||||
<div class="storList-header">
|
||||
<span class="storList-title">{{ item.StockName }}</span>
|
||||
<div class="storList-tags">
|
||||
<van-tag type="danger" v-if="item.TaskId != null">任务中</van-tag>
|
||||
<van-tag type="primary" v-if="item.StockStatus == false && !item.TrayNo">空库位</van-tag>
|
||||
<van-tag type="warning" v-if="item.StockStatus == false && item.TrayNo">空托盘</van-tag>
|
||||
<van-tag type="success" v-if="item.StockStatus == true">满料</van-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="storList-info">
|
||||
<span><i class="iconfont icon-project"></i> 项目名称:<b>{{ item.ProjectNo }}</b></span>
|
||||
<span><i class="iconfont icon-product"></i> 间隔编号:<b>{{ item.IntervalNo }}</b></span>
|
||||
<span><i class="iconfont icon-model"></i> 部件位置:<b>{{ item.ComponentLocation }}</b></span>
|
||||
<span><i class="iconfont icon-tuopan"></i> 托盘编号:<b>{{ item.TrayNo }}</b></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="showStockDialog" :title="stockDialogTitle" width="400px" :close-on-click-modal="false"
|
||||
@close="onStockDialogClose">
|
||||
<div v-if="stockList.length > 0">
|
||||
<div v-for="item in stockList" :key="item.code" @click="onStockSelect(item)"
|
||||
style="cursor:pointer; border-bottom:1px solid #eee; padding:12px 0;">
|
||||
<div style="display:flex;flex-direction:column;">
|
||||
<span><b>库位名称:</b>{{ item.name }}</span>
|
||||
<span><b>组件位置:</b>{{ item.ComponentLocation }}</span>
|
||||
<span><b>间隔号:</b>{{ item.IntervalNo }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="text-align:center;color:#888;">暂无可用库位</div>
|
||||
</el-dialog>
|
||||
<van-action-sheet v-model:show="showActionSheet" :actions="actionList" @select="onActionSelect" title="请选择操作" />
|
||||
<van-action-sheet v-model:show="showCallMaterialSheet" :actions="callMaterialActions" @select="onCallMaterialSelect"
|
||||
title="请选择操作" />
|
||||
<van-action-sheet v-model:show="showReturnMaterialSheet" :actions="returnMaterialActions"
|
||||
@select="onReturnMaterialSelect" title="请选择操作" />
|
||||
<el-dialog v-model="showReturnPalletDialog" title="退空托盘" width="320px" :close-on-click-modal="false"
|
||||
@close="onReturnPalletCancel">
|
||||
<el-form @submit.prevent>
|
||||
<el-form-item label="托盘码">
|
||||
<el-input v-model="returnPalletCode" placeholder="请扫描托盘码" clearable autofocus
|
||||
@keyup.enter="onReturnPalletConfirm" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onReturnPalletCancel">取消</el-button>
|
||||
<el-button type="primary" @click="onReturnPalletConfirm">校验</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="showEditStockDialog" title="修改库位信息" width="340px" :close-on-click-modal="false"
|
||||
@close="onEditStockCancel">
|
||||
<el-form @submit.prevent>
|
||||
<el-form-item label="项目名称">
|
||||
<el-input v-model="editStockForm.ProjectName" placeholder="请扫描或输入 项目名称 或 三段码" clearable @input="onEngineNoInput" />
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔编号">
|
||||
<el-input v-model="editStockForm.IntervalNo" placeholder="请输入 间隔编号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="部件位置">
|
||||
<el-input v-model="editStockForm.ComponentLocation" placeholder="请输入 部件位置" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="托盘编号">
|
||||
<el-input v-model="editStockForm.TrayNo" placeholder="请输入 托盘编号(仅托盘码时只填此项)" clearable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onEditStockCancel">取消</el-button>
|
||||
<el-button type="primary" @click="onEditStockConfirm">确认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useAppStore } from '@/store/app.js'
|
||||
import { Dialog, ActionSheet, Toast } from 'vant'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
const store = useAppStore()
|
||||
const { user } = storeToRefs(store)
|
||||
|
||||
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
|
||||
const ExecAgvApi = inject('ExecAgvApi')
|
||||
|
||||
const opStoreCode = ref(-1)
|
||||
|
||||
// 补充定义 stockList
|
||||
const stockList = ref([])
|
||||
|
||||
// 添加 selectedStock 定义
|
||||
const selectedStock = ref(null)
|
||||
|
||||
// StockCode 库位编号
|
||||
// StockName 库位名称
|
||||
// StockStatus 满料状态 bool
|
||||
// TrayNo 托盘号
|
||||
// IntervalNo 间隔号
|
||||
// ComponentLocation 组件位置
|
||||
// TaskId 任务号
|
||||
const opStoreData = reactive([])
|
||||
// 库区列表
|
||||
const partitionList = reactive([])
|
||||
|
||||
// 物料验证相关数据
|
||||
const MaterialData = reactive({})
|
||||
|
||||
// 目标库区数据
|
||||
const targetPartition = reactive({
|
||||
callMaterial: null, // 叫料库区
|
||||
returnMaterial: null, // 退料库区
|
||||
returnPallet: null // 退空托库区
|
||||
})
|
||||
|
||||
// 库位选择数据
|
||||
const showStockDialog = ref(false)
|
||||
const stockDialogTitle = ref('')
|
||||
|
||||
const showActionSheet = ref(false)
|
||||
const actionList = [
|
||||
{ name: '库位绑定' }
|
||||
]
|
||||
const currentItem = ref(null)
|
||||
|
||||
const showInputDialog = ref(false)
|
||||
const inputValue = ref('')
|
||||
|
||||
const showReturnPalletDialog = ref(false)
|
||||
const returnPalletCode = ref('')
|
||||
const returnPalletStock = ref(null)
|
||||
|
||||
// 叫托盘相关数据
|
||||
const showCallMaterialSheet = ref(false)
|
||||
const callMaterialActions = [
|
||||
{ name: '叫托盘' }
|
||||
]
|
||||
|
||||
// 空库位 -> 叫托盘
|
||||
const onCallMaterialSelect = async (action) => {
|
||||
showCallMaterialSheet.value = false
|
||||
if (action.name === '叫托盘') {
|
||||
// 获取空托盘来源库区(退空托库区)
|
||||
const routes = await getRoutePartitions(currentItem.value.StockCode)
|
||||
if (!routes || !routes.returnPallet) {
|
||||
ElMessage.warning('未配置退空托库区,请联系管理员')
|
||||
return
|
||||
}
|
||||
|
||||
// 记录目标库区
|
||||
targetPartition.returnPallet = routes.returnPallet
|
||||
|
||||
// 根据目标库区获取库位列表
|
||||
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: targetPartition.returnPallet })
|
||||
if (!stockRes.Result) {
|
||||
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
|
||||
return
|
||||
}
|
||||
|
||||
// 只显示空托盘库位(有托盘、无产品、状态为空),排除"无库位"
|
||||
stockList.value = (stockRes.Data || [])
|
||||
.filter(i => i.TrayNo && (!i.ProjectNo || i.ProjectNo === '') && (!i.IntervalNo || i.IntervalNo === '') && (!i.ComponentLocation || i.ComponentLocation === '') && i.StockStatus === false && i.StockName !== '无库位')
|
||||
.map(i => ({ name: i.StockName, code: i.StockCode, ...i }))
|
||||
|
||||
if (stockList.value.length === 0) {
|
||||
ElMessage.warning('目标库区没有可用空托盘库位')
|
||||
return
|
||||
}
|
||||
|
||||
// 设置叫托盘流程标识
|
||||
isCallMaterialFlow.value = true
|
||||
stockDialogTitle.value = await getPartitionName(targetPartition.returnPallet)
|
||||
showStockDialog.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const isCallMaterialFlow = ref(false)
|
||||
const currentPartitionName = ref('')
|
||||
|
||||
// 有产品库位 -> 送料/修改
|
||||
const showReturnMaterialSheet = ref(false)
|
||||
const returnMaterialActions = [
|
||||
{ name: '送料' },
|
||||
{ name: '修改库位信息' }
|
||||
]
|
||||
|
||||
// 有产品库位:选择送料或修改库位信息
|
||||
const onReturnMaterialSelect = async (action) => {
|
||||
showReturnMaterialSheet.value = false
|
||||
if (action.name === '修改库位信息') {
|
||||
// 预填当前库位信息
|
||||
editStockForm.ProjectName = currentItem.value.ProjectNo || ''
|
||||
editStockForm.IntervalNo = currentItem.value.IntervalNo || ''
|
||||
editStockForm.ComponentLocation = currentItem.value.ComponentLocation || ''
|
||||
editStockForm.TrayNo = currentItem.value.TrayNo || ''
|
||||
editStockForm.StockCode = currentItem.value.StockCode
|
||||
editStockForm.TaskId = currentItem.value.TaskId
|
||||
showEditStockDialog.value = true
|
||||
return
|
||||
}
|
||||
|
||||
if (action.name === '送料') {
|
||||
// 获取送料目标库区(叫料库区)
|
||||
const routes = await getRoutePartitions(currentItem.value.StockCode)
|
||||
if (!routes || !routes.callMaterial) {
|
||||
ElMessage.warning('未配置叫料路线,请联系管理员')
|
||||
return
|
||||
}
|
||||
|
||||
// 记录目标库区
|
||||
targetPartition.callMaterial = routes.callMaterial
|
||||
|
||||
// 根据目标库区获取库位列表
|
||||
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: targetPartition.callMaterial })
|
||||
if (!stockRes.Result) {
|
||||
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
|
||||
return
|
||||
}
|
||||
|
||||
// 只显示空库位(无托盘且状态为空),排除"无库位"
|
||||
stockList.value = (stockRes.Data || [])
|
||||
.filter(i => !i.TrayNo && i.StockStatus === false && i.StockName !== '无库位')
|
||||
.map(i => ({ name: i.StockName, code: i.StockCode, ...i }))
|
||||
|
||||
if (stockList.value.length === 0) {
|
||||
ElMessage.warning('目标库区没有可用空库位')
|
||||
return
|
||||
}
|
||||
|
||||
// 设置送料流程标识
|
||||
isReturnMaterialFlow.value = true
|
||||
stockDialogTitle.value = await getPartitionName(targetPartition.callMaterial)
|
||||
showStockDialog.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const isReturnMaterialFlow = ref(false)
|
||||
|
||||
// 获取预设路线库区信息
|
||||
const getRoutePartitions = async (stockCode) => {
|
||||
try {
|
||||
const res = await ExecDatabaseByParam('11', 'PTCHV_AGV_送料路线_PDA查询_初始化', [['库位号', stockCode]])
|
||||
if (res.data && res.data.length > 0) {
|
||||
return {
|
||||
callMaterial: res.data[0].叫料库区,
|
||||
returnMaterial: res.data[0].退料库区,
|
||||
returnPallet: res.data[0].退空托库区
|
||||
}
|
||||
} else {
|
||||
ElMessage.warning('未找到预设的路线信息')
|
||||
return null
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取路线信息失败:', error)
|
||||
ElMessage.error('获取路线信息失败')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getPartitionList()
|
||||
await getOpStoreCode()
|
||||
})
|
||||
const getPartitionList = async () => {
|
||||
let res = await ExecDatabaseByParam('11', 'MES_计划BOM_工位与名称_库位编号_查询', [['工位号', user.value.opName]])
|
||||
if (res.data.length > 0) {
|
||||
// 处理库区编号,可能会返回多个库区,如 "7,14"
|
||||
const partitionCodes = res.data[0].库位编号.toString().split(',').map(code => code.trim())
|
||||
opStoreCode.value = partitionCodes[0] // 保存第一个库区编号作为主库区
|
||||
|
||||
// 查找库区名称
|
||||
partitionList.length = 0
|
||||
const partitionRes = await ExecAgvApi('getPartitionList', {})
|
||||
if (partitionRes.Result) {
|
||||
partitionList.push(...partitionRes.Data)
|
||||
currentPartitionName.value = await getPartitionName(opStoreCode.value)
|
||||
}
|
||||
} else {
|
||||
ElMessage.warning('此工位无对应库位!')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取对应库位编号名称
|
||||
const getPartitionName = async (partitionCode) => {
|
||||
const found = partitionList.find(i => i.PartitionCode == partitionCode)
|
||||
return found ? found.PartitionName : ''
|
||||
}
|
||||
|
||||
const getOpStoreCode = async () => {
|
||||
// 获取MES_计划BOM_工位与名称_库位编号_查询返回的所有库区编号
|
||||
const res = await ExecDatabaseByParam('11', 'MES_计划BOM_工位与名称_库位编号_查询', [['工位号', user.value.opName]])
|
||||
if (res.data.length === 0) {
|
||||
ElMessage.warning('此工位无对应库位!')
|
||||
return
|
||||
}
|
||||
|
||||
// 清空现有数据
|
||||
opStoreData.length = 0
|
||||
|
||||
// 处理可能有多个库区编号的情况(如 "7,14")
|
||||
const partitionCodes = res.data[0].库位编号.toString().split(',').map(code => code.trim())
|
||||
|
||||
// 依次查询每个库区的库位信息并合并
|
||||
for (const partitionCode of partitionCodes) {
|
||||
const agvPar = {
|
||||
"PartitionCode": partitionCode
|
||||
}
|
||||
const agvRes = await ExecAgvApi('getStockList', agvPar)
|
||||
|
||||
if (agvRes.Result) {
|
||||
// 将查询到的库位信息添加到列表中,过滤"无库位"
|
||||
const filteredData = (agvRes.Data || []).filter(item => item.StockName !== '无库位')
|
||||
opStoreData.push(...filteredData)
|
||||
} else {
|
||||
ElMessage.error(`获取库区${partitionCode}信息失败: ${agvRes.ErrMsg}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有任何库位信息
|
||||
if (opStoreData.length === 0) {
|
||||
ElMessage.error('未获取到任何库位信息')
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = async (item) => {
|
||||
if (item.TaskId != null) {
|
||||
Dialog.alert({
|
||||
title: '提示',
|
||||
message: '该库位存在任务,不可操作',
|
||||
})
|
||||
return
|
||||
}
|
||||
// 满料库位:退料流程
|
||||
if (item.StockStatus === true) {
|
||||
currentItem.value = item
|
||||
showReturnMaterialSheet.value = true
|
||||
return
|
||||
}
|
||||
// 空托盘:退空托盘流程
|
||||
if (item.StockStatus === false && item.TrayNo) {
|
||||
currentItem.value = item
|
||||
showActionSheet.value = true
|
||||
return
|
||||
}
|
||||
// 空库位:叫料流程
|
||||
if (item.StockStatus === false && !item.TrayNo) {
|
||||
currentItem.value = item
|
||||
showCallMaterialSheet.value = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 选择库位后的处理
|
||||
const onStockItemClick = (item) => {
|
||||
// 二次确认
|
||||
|
||||
}
|
||||
|
||||
const onStockDialogClose = () => {
|
||||
showStockDialog.value = false
|
||||
}
|
||||
|
||||
const onStockSelect = async (stock) => {
|
||||
selectedStock.value = stock
|
||||
showStockDialog.value = false
|
||||
|
||||
if (isCallMaterialFlow.value) {
|
||||
// 校验所选是否空托盘库位
|
||||
const isPureEmptyPallet = stock.TrayNo && (!stock.IntervalNo || stock.IntervalNo === '') && (!stock.ComponentLocation || stock.ComponentLocation === '') && stock.StockStatus === false
|
||||
if (!isPureEmptyPallet) {
|
||||
ElMessage.warning('库位不符合空托盘库位')
|
||||
isCallMaterialFlow.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 叫托盘流程确认
|
||||
ElMessageBox.confirm(`是否将空托盘 从库位【${stock.name}】送至库位【${currentItem.value.StockName}】?`, '确认叫托盘', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const taskId = uuidv4()
|
||||
const res = await ExecAgvApi('createTask', {
|
||||
taskId,
|
||||
pickPosition: stock.code,
|
||||
dropPosition: currentItem.value.StockCode,
|
||||
})
|
||||
if (res.Result) {
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_送料记录_增加', [
|
||||
['任务号', taskId],
|
||||
['取料库位', stock.code],
|
||||
['取料库位名称', stock.name],
|
||||
['取料项目名称', ''],
|
||||
['取料间隔编号', ''],
|
||||
['取料部件位置', ''],
|
||||
['取料托盘编号', stock.TrayNo || ''],
|
||||
['送料库位', currentItem.value.StockCode],
|
||||
['送料库位名称', currentItem.value.StockName],
|
||||
['操作人', user.value.name],
|
||||
['type', 3],
|
||||
])
|
||||
ElMessage.success('叫托盘任务创建成功')
|
||||
getOpStoreCode()
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '叫托盘任务创建失败')
|
||||
}
|
||||
isCallMaterialFlow.value = false
|
||||
})
|
||||
} else if (isReturnMaterialFlow.value) {
|
||||
// 送料流程确认
|
||||
Dialog.confirm({
|
||||
title: '确认送料',
|
||||
message: `是否将物料送至库位【${stock.name}】?`,
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
}).then(async () => {
|
||||
const taskId = uuidv4()
|
||||
const res = await ExecAgvApi('createTask', {
|
||||
taskId,
|
||||
pickPosition: currentItem.value.StockCode,
|
||||
dropPosition: stock.code,
|
||||
})
|
||||
if (res.Result) {
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_送料记录_增加', [
|
||||
['任务号', taskId],
|
||||
['取料库位', currentItem.value.StockCode],
|
||||
['取料库位名称', currentItem.value.StockName],
|
||||
['取料项目名称', currentItem.value.ProjectNo || ''],
|
||||
['取料间隔编号', currentItem.value.IntervalNo || ''],
|
||||
['取料部件位置', currentItem.value.ComponentLocation || ''],
|
||||
['取料托盘编号', currentItem.value.TrayNo || ''],
|
||||
['送料库位', stock.code],
|
||||
['送料库位名称', stock.name],
|
||||
['操作人', user.value.name],
|
||||
['type', 1],
|
||||
])
|
||||
ElMessage.success('送料任务创建成功')
|
||||
getOpStoreCode()
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '送料任务创建失败')
|
||||
}
|
||||
isReturnMaterialFlow.value = false
|
||||
}).catch(() => {
|
||||
isReturnMaterialFlow.value = false
|
||||
})
|
||||
} else if (isReturnPalletFlow.value) {
|
||||
// 获取目标库区名称
|
||||
const partitionName = await getPartitionName(targetPartition.returnPallet)
|
||||
|
||||
// 退空托盘流程确认
|
||||
Dialog.confirm({
|
||||
title: '确认退空托盘',
|
||||
message: `是否将空托盘退至 库位【${stock.name}】?`,
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
}).then(async () => {
|
||||
// 先更新库位信息
|
||||
const oldTrayNo = returnPalletStock.value.TrayNo || ''
|
||||
const oldIntervalNo = returnPalletStock.value.IntervalNo || ''
|
||||
const oldComponentLocation = returnPalletStock.value.ComponentLocation || ''
|
||||
const stockName = returnPalletStock.value.StockName || ''
|
||||
|
||||
const updateRes = await ExecAgvApi('updateStockInfo', {
|
||||
StockCode: returnPalletStock.value.StockCode,
|
||||
StockStatus: false,
|
||||
TrayNo: oldTrayNo,
|
||||
ProjectNo: null,
|
||||
IntervalNo: null,
|
||||
ComponentLocation: null,
|
||||
TaskId: returnPalletStock.value.TaskId
|
||||
})
|
||||
|
||||
if (!updateRes.Result) {
|
||||
ElMessage.error(updateRes.ErrMsg || '库位信息更新失败')
|
||||
return
|
||||
}
|
||||
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_库位更新记录_增加', [
|
||||
['库位号', returnPalletStock.value.StockCode],
|
||||
['库位名称', stockName],
|
||||
['项目名称', ''],
|
||||
['间隔编号', ''],
|
||||
['部件位置', ''],
|
||||
['托盘编号', oldTrayNo],
|
||||
['项目名称_old', ''],
|
||||
['间隔编号_old', oldIntervalNo],
|
||||
['部件位置_old', oldComponentLocation],
|
||||
['托盘编号_old', oldTrayNo],
|
||||
['操作人', user.value.name],
|
||||
['type', 2],
|
||||
])
|
||||
|
||||
// 创建退空托盘任务
|
||||
const taskId = uuidv4()
|
||||
const res = await ExecAgvApi('createTask', {
|
||||
taskId,
|
||||
pickPosition: returnPalletStock.value.StockCode,
|
||||
dropPosition: stock.code,
|
||||
})
|
||||
|
||||
if (res.Result) {
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_送料记录_增加', [
|
||||
['任务号', taskId],
|
||||
['取料库位', returnPalletStock.value.StockCode],
|
||||
['取料库位名称', stockName],
|
||||
['取料项目名称', ''],
|
||||
['取料间隔编号', ''],
|
||||
['取料部件位置', ''],
|
||||
['取料托盘编号', oldTrayNo],
|
||||
['送料库位', stock.code],
|
||||
['送料库位名称', stock.name],
|
||||
['操作人', user.value.name],
|
||||
['type', 3],
|
||||
])
|
||||
ElMessage.success('退空托盘任务创建成功')
|
||||
getOpStoreCode()
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '退空托盘任务创建失败')
|
||||
}
|
||||
isReturnPalletFlow.value = false
|
||||
}).catch(() => {
|
||||
isReturnPalletFlow.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onInputChange = (val) => {
|
||||
if (val.length > 10) {
|
||||
// 如果 出现 &符号说明是扫描了二维码 解析出来是 <物料编号&物料图号> 只把物料编号赋值给inputValue
|
||||
if (val.split('&').length === 2) {
|
||||
const [EngineNo, SortNo] = val.split('&')
|
||||
inputValue.value = EngineNo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const onActionSelect = async (action) => {
|
||||
showActionSheet.value = false
|
||||
if (action.name === '库位绑定') {
|
||||
// 仅允许空托盘无产品信息库位执行绑定
|
||||
if (!(currentItem.value.StockStatus === false && currentItem.value.TrayNo && (!currentItem.value.ProjectNo && !currentItem.value.IntervalNo && !currentItem.value.ComponentLocation))) {
|
||||
ElMessage.warning('当前库位不符合空托盘库位条件')
|
||||
return
|
||||
}
|
||||
editStockForm.TrayNo = currentItem.value.TrayNo || ''
|
||||
editStockForm.ProjectName = ''
|
||||
editStockForm.IntervalNo = ''
|
||||
editStockForm.ComponentLocation = ''
|
||||
editStockForm.StockCode = currentItem.value.StockCode
|
||||
editStockForm.TaskId = currentItem.value.TaskId
|
||||
showEditStockDialog.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const onInputConfirm = async () => {
|
||||
if (!inputValue.value) {
|
||||
ElMessage.warning('请输入物料码')
|
||||
return
|
||||
}
|
||||
|
||||
// 如果 出现 &符号说明是扫描了二维码 解析出来是 <物料编号&物料图号> 只把物料编号赋值给inputValue
|
||||
if (inputValue.value.split('&').length === 2) {
|
||||
const [EngineNo, SortNo] = inputValue.value.split('&')
|
||||
inputValue.value = EngineNo
|
||||
}
|
||||
|
||||
if (MaterialData.EngineNo != inputValue.value) {
|
||||
ElMessage.warning(`库位物料编号【${MaterialData.EngineNo}】与扫描物料编号【${inputValue.value}】不匹配`)
|
||||
return
|
||||
}
|
||||
const res = await ExecDatabaseByParam('11', 'PTCHV_物料验证记录_增加', [["工位号", user.value.opName], ["物料编号", inputValue.value], ["物料图号", MaterialData.SortNo]])
|
||||
if (res.data[0].result === 1) {
|
||||
ElMessage.success('物料校验成功')
|
||||
showInputDialog.value = false
|
||||
|
||||
if (user.value.opName == 'OP010') {
|
||||
// 1. 调用 PTCHV_AGV_上线绑定 存储过程
|
||||
const resBind = await ExecDatabaseByParam('11', 'PTCHV_AGV_上线绑定', [['工位号', user.value.opName]])
|
||||
if (resBind.data && resBind.data[0].result === 1) {
|
||||
try {
|
||||
const item = resBind.data[0]
|
||||
const agvReq = {
|
||||
taskId: uuidv4(),
|
||||
EngineNo: item.工件编号,
|
||||
SortNo: item.产品型号,
|
||||
RouteCode: item.工艺路线编号
|
||||
}
|
||||
// 2. 调用 AGV API 生产上线绑定
|
||||
const agvRes = await ExecAgvApi('productionLaunchBind', agvReq)
|
||||
if (agvRes.Result) {
|
||||
// 3. 调用 PTCHV_AGV_工位放行次数_记录 存储过程
|
||||
const resPass = await ExecDatabaseByParam('12', 'PTCHV_AGV_工位放行次数_记录', [
|
||||
['工件编号', item.工件编号],
|
||||
['OP10', agvRes.Data.OP010PassCount],
|
||||
['OP20', agvRes.Data.OP020PassCount],
|
||||
['OP30', agvRes.Data.OP030PassCount]
|
||||
])
|
||||
if (resPass.data && resPass.data[0].result === '1') {
|
||||
// 4. 调用 PTCHV_AGV_进离站 存储过程
|
||||
const param = [
|
||||
['工位号', user.value.opName],
|
||||
['工件编号', item.工件编号],
|
||||
['type', 1],
|
||||
['任务编号', agvReq.taskId]
|
||||
]
|
||||
const resInOut = await ExecDatabaseByParam('12', 'PTCHV_AGV_进离站', param)
|
||||
if (resInOut.data && resInOut.data[0].result === '1') {
|
||||
ElMessage.success('产品上线绑定AGV成功!')
|
||||
} else {
|
||||
ElMessage.error('产品上线绑定AGV失败')
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('放行次数记录失败')
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(agvRes.ErrMsg)
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('调度任务出错' + e.message)
|
||||
console.log(e)
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('调度任务出错' + (resBind.data && resBind.data[0] && resBind.data[0].msg ? resBind.data[0].msg : ''))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.data[0].msg)
|
||||
}
|
||||
}
|
||||
|
||||
const onInputCancel = () => {
|
||||
showInputDialog.value = false
|
||||
}
|
||||
|
||||
const onReturnPalletClick = () => {
|
||||
returnPalletCode.value = ''
|
||||
returnPalletStock.value = null
|
||||
showReturnPalletDialog.value = true
|
||||
}
|
||||
|
||||
const onReturnPalletCancel = () => {
|
||||
showReturnPalletDialog.value = false
|
||||
}
|
||||
|
||||
const onReturnPalletConfirm = async () => {
|
||||
if (!returnPalletCode.value) {
|
||||
ElMessage.warning('请输入托盘码')
|
||||
return
|
||||
}
|
||||
|
||||
// 校验托盘码是否属于本库区
|
||||
const stock = opStoreData.find(i => i.TrayNo === returnPalletCode.value)
|
||||
if (!stock) {
|
||||
ElMessage.error('该托盘码不属于本库区')
|
||||
return
|
||||
}
|
||||
|
||||
returnPalletStock.value = stock
|
||||
showReturnPalletDialog.value = false
|
||||
|
||||
// 获取退空托盘目标库区
|
||||
const routes = await getRoutePartitions(stock.StockCode)
|
||||
if (!routes || !routes.returnPallet) {
|
||||
ElMessage.warning('未配置退空托盘路线,请联系管理员')
|
||||
return
|
||||
}
|
||||
|
||||
// 记录目标库区
|
||||
targetPartition.returnPallet = routes.returnPallet
|
||||
|
||||
// 根据目标库区获取库位列表
|
||||
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: targetPartition.returnPallet })
|
||||
if (!stockRes.Result) {
|
||||
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
|
||||
return
|
||||
}
|
||||
|
||||
// 退空托盘流程:只显示无托盘库位
|
||||
stockList.value = stockRes.Data
|
||||
.filter(i => !i.TrayNo || i.TrayNo === '')
|
||||
.map(i => ({ name: i.StockName, code: i.StockCode, ...i }))
|
||||
|
||||
if (stockList.value.length === 0) {
|
||||
ElMessage.warning('目标库区没有可用库位')
|
||||
return
|
||||
}
|
||||
|
||||
// 设置退空托盘流程标识并显示库位选择
|
||||
isReturnPalletFlow.value = true
|
||||
showStockDialog.value = true
|
||||
}
|
||||
|
||||
// 标记当前流程类型
|
||||
const isReturnPalletFlow = ref(false)
|
||||
|
||||
// 库位绑定对话框
|
||||
const showEditStockDialog = ref(false)
|
||||
const editStockForm = reactive({
|
||||
ProjectName: '',
|
||||
IntervalNo: '',
|
||||
ComponentLocation: '',
|
||||
TrayNo: '',
|
||||
StockCode: '',
|
||||
TaskId: ''
|
||||
})
|
||||
|
||||
const onEngineNoInput = () => {
|
||||
const raw = editStockForm.ProjectName || ''
|
||||
const parts = raw.split('&')
|
||||
if (parts.length === 3) {
|
||||
editStockForm.ProjectName = parts[0].trim()
|
||||
editStockForm.IntervalNo = parts[1].trim()
|
||||
editStockForm.ComponentLocation = parts[2].trim()
|
||||
}
|
||||
}
|
||||
|
||||
const onEditStockConfirm = async () => {
|
||||
const TrayNo = editStockForm.TrayNo ? editStockForm.TrayNo : null
|
||||
const ProjectNo = editStockForm.ProjectName ? editStockForm.ProjectName : null
|
||||
const IntervalNo = editStockForm.IntervalNo ? editStockForm.IntervalNo : null
|
||||
const ComponentLocation = editStockForm.ComponentLocation ? editStockForm.ComponentLocation : null
|
||||
const StockStatus = (IntervalNo && ComponentLocation) ? true : false
|
||||
const params = {
|
||||
StockCode: editStockForm.StockCode,
|
||||
TrayNo,
|
||||
ProjectNo,
|
||||
IntervalNo,
|
||||
ComponentLocation,
|
||||
StockStatus,
|
||||
TaskId: editStockForm.TaskId
|
||||
}
|
||||
// 记录旧值
|
||||
const current = opStoreData.find(i => i.StockCode === editStockForm.StockCode) || {}
|
||||
const oldTrayNo = current.TrayNo || null
|
||||
const oldProjectNo = current.ProjectNo || null
|
||||
const oldIntervalNo = current.IntervalNo || null
|
||||
const oldComponentLocation = current.ComponentLocation || null
|
||||
const stockName = current.StockName || ''
|
||||
|
||||
const res = await ExecAgvApi('updateStockInfo', params)
|
||||
if (res.Result) {
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_库位更新记录_增加', [
|
||||
['库位号', editStockForm.StockCode],
|
||||
['库位名称', stockName],
|
||||
['项目名称', ProjectNo || ''],
|
||||
['间隔编号', IntervalNo || ''],
|
||||
['部件位置', ComponentLocation || ''],
|
||||
['托盘编号', TrayNo || ''],
|
||||
['项目名称_old', oldProjectNo || ''],
|
||||
['间隔编号_old', oldIntervalNo || ''],
|
||||
['部件位置_old', oldComponentLocation || ''],
|
||||
['托盘编号_old', oldTrayNo || ''],
|
||||
['操作人', user.value.name],
|
||||
['type', 1],
|
||||
])
|
||||
ElMessage.success('库位信息修改成功')
|
||||
showEditStockDialog.value = false
|
||||
await getOpStoreCode()
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '库位信息修改失败')
|
||||
}
|
||||
}
|
||||
|
||||
const onEditStockCancel = () => {
|
||||
showEditStockDialog.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card.modern-card {
|
||||
background: #fff;
|
||||
border-radius: 18px;
|
||||
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.08);
|
||||
padding: 10px 14px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.storList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.storList-item.modern-item {
|
||||
background: linear-gradient(135deg, #f8fafc 60%, #e0e7ef 100%);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
|
||||
padding: 20px 18px 16px 18px;
|
||||
min-width: 260px;
|
||||
max-width: 320px;
|
||||
flex: 1 1 260px;
|
||||
transition: box-shadow 0.2s, transform 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.12);
|
||||
transform: translateY(-2px) scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
.storList-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.storList-title {
|
||||
font-size: 1.18rem;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.storList-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.storList-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
font-size: 0.98rem;
|
||||
color: #555;
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
i {
|
||||
font-size: 1.1em;
|
||||
color: #8bb4f8;
|
||||
}
|
||||
|
||||
b {
|
||||
color: #222;
|
||||
font-weight: 500;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
911
src/views/OnekeyMove/index.vue
Normal file
911
src/views/OnekeyMove/index.vue
Normal file
@@ -0,0 +1,911 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="header-container">
|
||||
<el-select v-model="currentPartitionCode" placeholder="请选择移动库区" @change="onPartitionChange">
|
||||
<el-option v-for="item in partitionList" :key="item.PartitionCode" :label="item.PartitionName" :value="item.PartitionCode" />
|
||||
</el-select>
|
||||
<div class="info-badge" v-if="targetPartitionName">
|
||||
目标库区:{{ targetPartitionName }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stock-list">
|
||||
<div class="stock-item" v-for="item in stockList" :key="item.StockCode" @click="onStockCardClick(item)">
|
||||
<div class="stock-header">
|
||||
<span class="stock-title">{{ item.StockName }}</span>
|
||||
<div class="stock-tags">
|
||||
<van-tag type="danger" v-if="item.TaskId != null">任务中</van-tag>
|
||||
<van-tag type="primary" v-if="item.StockStatus == false && !item.TrayNo">空库位</van-tag>
|
||||
<van-tag type="warning" v-if="item.StockStatus == false && item.TrayNo">空托盘</van-tag>
|
||||
<van-tag type="success" v-if="item.StockStatus == true">满料</van-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stock-info">
|
||||
<span><i class="iconfont icon-project"></i> 项目名称:<b>{{ item.ProjectNo || '无' }}</b></span>
|
||||
<span><i class="iconfont icon-product"></i> 间隔编号:<b>{{ item.IntervalNo || '无' }}</b></span>
|
||||
<span><i class="iconfont icon-model"></i> 部件位置:<b>{{ item.ComponentLocation || '无' }}</b></span>
|
||||
<span><i class="iconfont icon-tuopan"></i> 托盘编号:<b>{{ item.TrayNo || '无' }}</b></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-button style="position: fixed; bottom: 20px; right: 150px; z-index: 9999999;" type="warning" size="large"
|
||||
v-if="!moveDialogShow" @click="onUnbindClick">
|
||||
解绑产品
|
||||
</el-button>
|
||||
|
||||
<el-button style="position: fixed; bottom: 20px; right: 20px; z-index: 9999999;" type="primary" size="large"
|
||||
v-if="!moveDialogShow" @click="onMoveBtnClick">
|
||||
一键移库
|
||||
</el-button>
|
||||
|
||||
<el-dialog v-model="moveDialogShow" title="一键移库" width="90vw" :close-on-click-modal="false" class="move-dialog">
|
||||
<div class="target-info">
|
||||
<div>
|
||||
<span class="label">起始库区:</span>
|
||||
<span class="value">{{ currentPartitionName }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">目标库区:</span>
|
||||
<span class="value">{{ targetPartitionName }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">目标空库位数量:</span>
|
||||
<span class="value" :class="{ 'warning': targetEmptyStockCount < getFilledInputsCount() }">
|
||||
{{ targetEmptyStockCount }}个
|
||||
<span v-if="targetEmptyStockCount < getFilledInputsCount()" class="warning-text">
|
||||
(已填写{{ getFilledInputsCount() }}个{{ scanInputLabel }},超出可用库位数量)
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scan-inputs">
|
||||
<div class="scan-item" v-for="(code, index) in materialCodes" :key="index">
|
||||
<el-input v-model="materialCodes[index]" :placeholder="`请扫描第${index + 1}个${scanInputLabel}`" clearable
|
||||
@input="onMaterialInput(index)" @keyup.enter="onInputEnter(index)">
|
||||
<template #prefix>
|
||||
<i class="el-icon-barcode"></i>{{ index + 1 }}:
|
||||
</template>
|
||||
<template #suffix>
|
||||
<el-button v-if="materialCodes[index]" icon="el-icon-close" circle plain size="small"
|
||||
@click="materialCodes[index] = ''"></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="moveDialogShow = false">取消</el-button>
|
||||
<el-button type="primary" @click="onMoveConfirm" :disabled="!canSubmit()">确认移库</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="unbindDialogShow" title="解绑产品" width="320px" :close-on-click-modal="false">
|
||||
<el-form @submit.prevent>
|
||||
<el-form-item label="托盘码">
|
||||
<el-input v-model="unbindCode" placeholder="请扫描托盘码" clearable autofocus @keyup.enter="onUnbindConfirm" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="onUnbindCancel">取消</el-button>
|
||||
<el-button type="primary" @click="onUnbindConfirm">确认解绑</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 选择一键移库的目标库区(来自关联表可能为多个) -->
|
||||
<el-dialog v-model="showMoveTargetPartitionDialog" title="选择目标库区" width="320px" :close-on-click-modal="false">
|
||||
<el-select v-model="selectedMoveTargetPartition" placeholder="请选择目标库区" style="width: 100%">
|
||||
<el-option v-for="item in moveTargetPartitionCandidates" :key="item.PartitionCode" :label="item.PartitionName" :value="item.PartitionCode" />
|
||||
</el-select>
|
||||
<template #footer>
|
||||
<el-button @click="showMoveTargetPartitionDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="onMoveTargetPartitionConfirm">确认</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 空库位 -> 叫料:选择来源库位(来自当前库区的叫料库区) -->
|
||||
<el-dialog v-model="showCallMaterialDialog" :title="callDialogTitle" width="90vw" :close-on-click-modal="false" class="call-dialog">
|
||||
<div v-if="callMaterialStockList.length > 0">
|
||||
<div v-for="item in callMaterialStockList" :key="item.StockCode" @click="onCallMaterialStockClick(item)"
|
||||
style="cursor:pointer; border-bottom:1px solid #eee; padding:12px 0;">
|
||||
<div style="display:flex;flex-direction:column;">
|
||||
<span><b>库位名称:</b>{{ item.StockName }}</span>
|
||||
<span><b>项目名称:</b>{{ item.ProjectNo || '-' }}</span>
|
||||
<span><b>间隔编号:</b>{{ item.IntervalNo || '-' }}</span>
|
||||
<span><b>部件位置:</b>{{ item.ComponentLocation || '-' }}</span>
|
||||
<span><b>托盘编号:</b>{{ item.TrayNo || '-' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="text-align:center;color:#888;">暂无可用库位</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { inject, onMounted, ref, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useAppStore } from '@/store/app.js'
|
||||
import { storeToRefs } from 'pinia'
|
||||
const store = useAppStore()
|
||||
const { user } = storeToRefs(store)
|
||||
|
||||
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
|
||||
const ExecAgvApi = inject('ExecAgvApi')
|
||||
|
||||
// 库区数据
|
||||
const partitionList = ref([]) // 当前工位可选库区(由 MES_计划BOM_工位与名称_库位编号_查询 得到)
|
||||
const currentPartitionCode = ref('')
|
||||
const currentPartitionName = ref('')
|
||||
const targetPartitionCode = ref('')
|
||||
const targetPartitionName = ref('')
|
||||
const allPartitions = ref([]) // AGV 返回的所有库区,用于名称映射
|
||||
|
||||
// 库区内库位数据
|
||||
const stockList = ref([])
|
||||
// 目标库区空库位数据
|
||||
const targetEmptyStocks = ref([])
|
||||
const targetEmptyStockCount = ref(0)
|
||||
|
||||
// 移库对话框相关
|
||||
const moveDialogShow = ref(false)
|
||||
const materialCodes = ref(['', '', '', '', '', '']) // 六个输入框对应的物料码
|
||||
|
||||
// 空库位 -> 叫料相关
|
||||
const showCallMaterialDialog = ref(false)
|
||||
const callPartitionCode = ref('')
|
||||
const callPartitionName = ref('')
|
||||
const callMaterialStockList = ref([])
|
||||
const currentEmptyStock = ref(null)
|
||||
const callDialogTitle = ref('')
|
||||
|
||||
// 解绑产品
|
||||
const unbindDialogShow = ref(false)
|
||||
const unbindCode = ref('')
|
||||
|
||||
// 空支架区判定与扫描提示:切换整体移库逻辑到“空托盘模式”
|
||||
const isEmptyFrameTarget = computed(() => targetPartitionName.value.includes('空支架区'))
|
||||
// 扫描输入框的占位文本与提示
|
||||
const scanInputLabel = computed(() => (isEmptyFrameTarget.value ? '托盘码' : '组合产品码'))
|
||||
// 顶部提示、弹窗文案中用于描述当前操作主体
|
||||
const headerActionLabel = computed(() => (isEmptyFrameTarget.value ? '托盘' : '物料'))
|
||||
|
||||
// 统一处理扫码字符串:去除首尾空格
|
||||
const normalizeScanCode = (code) => (typeof code === 'string' ? code.trim() : '')
|
||||
// 解析扫码结果:期望格式 “组合产品码”;若是托盘码则仅一段
|
||||
const parseIntervalComponentFromScan = (code) => {
|
||||
const trimmed = normalizeScanCode(code)
|
||||
if (!trimmed) return { projectName: '', intervalNo: '', componentLocation: '', trayOnly: '' }
|
||||
const parts = trimmed.split('&')
|
||||
if (parts.length === 1) {
|
||||
return { projectName: '', intervalNo: '', componentLocation: '', trayOnly: trimmed }
|
||||
}
|
||||
if (parts.length !== 3) return { projectName: '', intervalNo: '', componentLocation: '', trayOnly: '' }
|
||||
const projectName = normalizeScanCode(parts[0])
|
||||
const intervalNo = normalizeScanCode(parts[1])
|
||||
const componentLocation = normalizeScanCode(parts[2])
|
||||
return { projectName, intervalNo, componentLocation, trayOnly: '' }
|
||||
}
|
||||
|
||||
// 空托盘判断:用于空支架区移库与解绑校验
|
||||
const isPureEmptyPallet = (stock) => {
|
||||
if (!stock) return false
|
||||
const hasTray = !!stock.TrayNo
|
||||
const noInterval = !stock.IntervalNo || stock.IntervalNo === ''
|
||||
const noComponent = !stock.ComponentLocation || stock.ComponentLocation === ''
|
||||
return hasTray && noInterval && noComponent && stock.StockStatus === false
|
||||
}
|
||||
|
||||
// 库位升序排序(送料):目标库位按 StockCode 从小到大(1 -> 8)
|
||||
const compareStockAsc = (a, b) => {
|
||||
const codeA = parseInt(a.StockCode)
|
||||
const codeB = parseInt(b.StockCode)
|
||||
if (!isNaN(codeA) && !isNaN(codeB)) {
|
||||
return codeA - codeB
|
||||
}
|
||||
return a.StockCode.localeCompare(b.StockCode)
|
||||
}
|
||||
|
||||
// 库位降序排序(取料):源库位按 StockCode 从大到小(8 -> 1)
|
||||
const compareStockDesc = (a, b) => {
|
||||
const codeA = parseInt(a.StockCode)
|
||||
const codeB = parseInt(b.StockCode)
|
||||
if (!isNaN(codeA) && !isNaN(codeB)) {
|
||||
return codeB - codeA
|
||||
}
|
||||
return b.StockCode.localeCompare(a.StockCode)
|
||||
}
|
||||
|
||||
// 获取 AGV 全量库区列表
|
||||
// 拉取全部库区:用于名称映射与目标库区显示
|
||||
const fetchAllPartitions = async () => {
|
||||
const partitionRes = await ExecAgvApi('getPartitionList', {})
|
||||
if (partitionRes.Result) {
|
||||
allPartitions.value = partitionRes.Data || []
|
||||
} else {
|
||||
allPartitions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 依据工位,获取可选库区列表(逗号分隔的库区号)
|
||||
// MES 工位配置 -> 获取可操作的库区列表
|
||||
// 特殊:当返回 -1 时,说明是管理员账号,可操作所有库区
|
||||
const isAdminUser = ref(false) // 管理员标识
|
||||
const fetchWorkstationPartitions = async () => {
|
||||
const res = await ExecDatabaseByParam('11', 'MES_计划BOM_工位与名称_库位编号_查询', [['工位号', user.value.opName]])
|
||||
if (res.data && res.data.length > 0) {
|
||||
const codeStr = (res.data[0].库位编号 || '').toString().trim()
|
||||
|
||||
// 判断是否为管理员(返回 -1)
|
||||
if (codeStr === '-1') {
|
||||
isAdminUser.value = true
|
||||
// 管理员:显示所有库区
|
||||
partitionList.value = allPartitions.value.map(p => ({
|
||||
PartitionCode: p.PartitionCode,
|
||||
PartitionName: p.PartitionName
|
||||
}))
|
||||
} else {
|
||||
isAdminUser.value = false
|
||||
// 普通用户:按配置的库区号过滤
|
||||
const codes = codeStr.split(',').map(s => s.trim()).filter(Boolean)
|
||||
partitionList.value = codes.map(code => {
|
||||
const p = allPartitions.value.find(x => x.PartitionCode == code)
|
||||
return { PartitionCode: code, PartitionName: p ? p.PartitionName : `库区${code}` }
|
||||
})
|
||||
}
|
||||
|
||||
if (partitionList.value.length > 0) {
|
||||
currentPartitionCode.value = partitionList.value[0].PartitionCode
|
||||
currentPartitionName.value = partitionList.value[0].PartitionName
|
||||
await onPartitionChange(currentPartitionCode.value)
|
||||
} else {
|
||||
ElMessage.warning('此工位无对应库区!')
|
||||
}
|
||||
} else {
|
||||
ElMessage.warning('此工位无对应库区!')
|
||||
}
|
||||
}
|
||||
|
||||
// 库区选择变更处理
|
||||
// 切换源库区时重置界面状态并刷新库位信息
|
||||
const onPartitionChange = async () => {
|
||||
if (!currentPartitionCode.value) return
|
||||
// 更新当前库区名称
|
||||
const currentPartition = partitionList.value.find(p => p.PartitionCode == currentPartitionCode.value)
|
||||
currentPartitionName.value = currentPartition ? currentPartition.PartitionName : ''
|
||||
// 清空先前选择的目标库区
|
||||
targetPartitionCode.value = ''
|
||||
targetPartitionName.value = ''
|
||||
// 获取源库区库位信息
|
||||
await fetchStockList()
|
||||
}
|
||||
|
||||
// 获取源库区库位信息
|
||||
// 拉取当前库区的全部库位数据
|
||||
const fetchStockList = async () => {
|
||||
try {
|
||||
const res = await ExecAgvApi('getStockList', { PartitionCode: currentPartitionCode.value })
|
||||
if (res.Result) {
|
||||
// 过滤掉"无库位"的库位
|
||||
stockList.value = (res.Data || []).filter(item => item.StockName !== '无库位')
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '获取库位信息失败')
|
||||
stockList.value = []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取库位信息失败:', error)
|
||||
ElMessage.error('获取库位信息失败')
|
||||
stockList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 查询目标库区空库位并按序号排序(用于先进先出)
|
||||
const fetchTargetEmptyStocks = async () => {
|
||||
try {
|
||||
const res = await ExecAgvApi('getStockList', { PartitionCode: targetPartitionCode.value })
|
||||
if (res.Result) {
|
||||
// 筛选空库位(无托盘且状态为空)并按StockCode升序排列(送料从小到大)
|
||||
targetEmptyStocks.value = (res.Data || [])
|
||||
.filter(i => !i.TrayNo && i.StockStatus === false && i.TaskId == null && i.StockName !== '无库位')
|
||||
.sort(compareStockAsc)
|
||||
targetEmptyStockCount.value = targetEmptyStocks.value.length
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '获取目标库区信息失败')
|
||||
targetEmptyStocks.value = []
|
||||
targetEmptyStockCount.value = 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取目标库区信息失败:', error)
|
||||
ElMessage.error('获取目标库区信息失败')
|
||||
targetEmptyStocks.value = []
|
||||
targetEmptyStockCount.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
// 点击一键移库按钮
|
||||
// 一键移库:先选择目标库区(当前库区可能对应多个目标库区)
|
||||
const moveTargetPartitionCandidates = ref([])
|
||||
const showMoveTargetPartitionDialog = ref(false)
|
||||
const selectedMoveTargetPartition = ref('')
|
||||
|
||||
// 获取当前库区的一键移库目标库区候选列表
|
||||
const fetchMoveTargetPartitionsForCurrent = async () => {
|
||||
moveTargetPartitionCandidates.value = []
|
||||
selectedMoveTargetPartition.value = ''
|
||||
|
||||
// 管理员:可以移库到任意库区(排除当前库区)
|
||||
if (isAdminUser.value) {
|
||||
moveTargetPartitionCandidates.value = allPartitions.value
|
||||
.filter(p => p.PartitionCode != currentPartitionCode.value)
|
||||
.map(p => ({
|
||||
PartitionCode: p.PartitionCode,
|
||||
PartitionName: p.PartitionName
|
||||
}))
|
||||
if (moveTargetPartitionCandidates.value.length === 0) {
|
||||
ElMessage.warning('无可用目标库区')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 普通用户:按配置的关联表查询
|
||||
const res = await ExecDatabaseByParam('11', 'PTCHV_AGV_一键移库关联_查询', [])
|
||||
if (!res.data || res.data.length === 0) {
|
||||
ElMessage.warning('未获取到移库关联数据')
|
||||
return
|
||||
}
|
||||
const row = res.data.find(r => (r.移动库区号 || '').toString() == currentPartitionCode.value)
|
||||
if (!row) {
|
||||
ElMessage.warning('当前库区未配置一键移库关联')
|
||||
return
|
||||
}
|
||||
let targets = (row.目标库区号 || '').toString()
|
||||
targets = targets.replace(/[()]/g, '')
|
||||
const codes = targets.split(',').map(s => s.trim()).filter(Boolean)
|
||||
moveTargetPartitionCandidates.value = codes.map(code => {
|
||||
const p = allPartitions.value.find(x => x.PartitionCode == code)
|
||||
return { PartitionCode: code, PartitionName: p ? p.PartitionName : `库区${code}` }
|
||||
})
|
||||
}
|
||||
|
||||
const onMoveBtnClick = async () => {
|
||||
await fetchMoveTargetPartitionsForCurrent()
|
||||
if (moveTargetPartitionCandidates.value.length === 0) return
|
||||
showMoveTargetPartitionDialog.value = true
|
||||
}
|
||||
|
||||
const onMoveTargetPartitionConfirm = async () => {
|
||||
if (!selectedMoveTargetPartition.value) {
|
||||
ElMessage.warning('请选择目标库区')
|
||||
return
|
||||
}
|
||||
targetPartitionCode.value = selectedMoveTargetPartition.value
|
||||
const p = moveTargetPartitionCandidates.value.find(i => i.PartitionCode == targetPartitionCode.value)
|
||||
targetPartitionName.value = p ? p.PartitionName : ''
|
||||
showMoveTargetPartitionDialog.value = false
|
||||
|
||||
// 清空扫描输入
|
||||
materialCodes.value = ['', '', '', '', '', '']
|
||||
// 刷新目标库区空库位数据
|
||||
await fetchTargetEmptyStocks()
|
||||
// 打开移库对话框
|
||||
moveDialogShow.value = true
|
||||
}
|
||||
|
||||
// 统计有效扫码数量(根据模式自动判断空字符串)
|
||||
const getFilledInputsCount = () => {
|
||||
if (isEmptyFrameTarget.value) {
|
||||
return materialCodes.value.filter(code => normalizeScanCode(code) !== '').length
|
||||
}
|
||||
return materialCodes.value.filter(code => {
|
||||
const { projectName, intervalNo, componentLocation } = parseIntervalComponentFromScan(code)
|
||||
return !!projectName && !!intervalNo && !!componentLocation
|
||||
}).length
|
||||
}
|
||||
|
||||
// 判断是否可以提交:至少一个有效扫码且不超可用空位
|
||||
const canSubmit = () => {
|
||||
const filledCount = getFilledInputsCount()
|
||||
return filledCount > 0 && filledCount <= targetEmptyStockCount.value
|
||||
}
|
||||
|
||||
// 输入框回车处理:快速定位下一个输入框
|
||||
const onInputEnter = (index) => {
|
||||
// 如果当前输入框有值且不是最后一个,自动聚焦下一个输入框
|
||||
if (materialCodes.value[index] && index < materialCodes.value.length - 1) {
|
||||
const nextInput = document.querySelector(`.scan-item:nth-child(${index + 2}) input`)
|
||||
if (nextInput) {
|
||||
nextInput.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确认移库:构建任务、排序并创建调度
|
||||
const onMoveConfirm = async () => {
|
||||
const isTargetEmptyFrameArea = isEmptyFrameTarget.value
|
||||
|
||||
const validEntries = materialCodes.value
|
||||
.map(code => normalizeScanCode(code))
|
||||
.filter(code => code !== '')
|
||||
|
||||
if (validEntries.length === 0) {
|
||||
ElMessage.warning(`请至少扫描一个${scanInputLabel.value}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (validEntries.length > targetEmptyStockCount.value) {
|
||||
ElMessage.warning(`目标库区只有${targetEmptyStockCount.value}个空库位,当前扫描了${validEntries.length}个${scanInputLabel.value}`)
|
||||
return
|
||||
}
|
||||
|
||||
const materialsToMove = []
|
||||
const missingCodes = []
|
||||
|
||||
for (const code of validEntries) {
|
||||
let stock
|
||||
if (isTargetEmptyFrameArea) {
|
||||
stock = stockList.value.find(s => s.TrayNo === code)
|
||||
} else {
|
||||
const { projectName, intervalNo, componentLocation } = parseIntervalComponentFromScan(code)
|
||||
if (!projectName || !intervalNo || !componentLocation) {
|
||||
missingCodes.push(code)
|
||||
continue
|
||||
}
|
||||
stock = stockList.value.find(s => s.ProjectNo === projectName && s.IntervalNo === intervalNo && s.ComponentLocation === componentLocation)
|
||||
}
|
||||
|
||||
if (!stock) {
|
||||
missingCodes.push(code)
|
||||
continue
|
||||
}
|
||||
|
||||
if (stock.TaskId) {
|
||||
ElMessage.warning(`库位 ${stock.StockName} 存在未完成的任务,无法移库`)
|
||||
return
|
||||
}
|
||||
|
||||
if (isTargetEmptyFrameArea && !isPureEmptyPallet(stock)) {
|
||||
ElMessage.warning(`托盘 ${code} 非空托盘,请先解绑后再移库`)
|
||||
return
|
||||
}
|
||||
|
||||
materialsToMove.push(stock)
|
||||
}
|
||||
|
||||
if (missingCodes.length > 0) {
|
||||
const label = isTargetEmptyFrameArea ? '托盘码' : '组合产品码'
|
||||
ElMessage.warning(`以下${label}在当前库区未找到:${missingCodes.join(', ')}`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认将 ${materialsToMove.length} 个${scanInputLabel.value}从 ${currentPartitionName.value} 移库到 ${targetPartitionName.value}?`,
|
||||
'确认移库',
|
||||
{
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
|
||||
const taskList = []
|
||||
const taskRecords = []
|
||||
|
||||
// 取料从大到小(StockCode降序),送料从小到大(StockCode升序)
|
||||
const sortedSources = [...materialsToMove].sort(compareStockDesc)
|
||||
const sortedTargets = [...targetEmptyStocks.value].slice(0, sortedSources.length).sort(compareStockAsc)
|
||||
console.log('sortedSources', sortedSources)
|
||||
console.log('sortedTargets', sortedTargets)
|
||||
for (let i = 0; i < sortedSources.length; i++) {
|
||||
const source = sortedSources[i]
|
||||
const target = sortedTargets[i]
|
||||
|
||||
if (!target) {
|
||||
ElMessage.warning('目标库位数量不足,请刷新后重试')
|
||||
return
|
||||
}
|
||||
|
||||
const taskId = uuidv4()
|
||||
|
||||
taskList.push({
|
||||
taskId,
|
||||
pickPosition: source.StockCode,
|
||||
dropPosition: target.StockCode
|
||||
})
|
||||
|
||||
taskRecords.push([
|
||||
['任务号', taskId],
|
||||
['取料库位', source.StockCode],
|
||||
['取料库位名称', source.StockName],
|
||||
['取料项目名称', source.ProjectNo || ''],
|
||||
['取料间隔编号', source.IntervalNo || ''],
|
||||
['取料部件位置', source.ComponentLocation || ''],
|
||||
['取料托盘编号', source.TrayNo || ''],
|
||||
['送料库位', target.StockCode],
|
||||
['送料库位名称', target.StockName],
|
||||
['操作人', user.value.name],
|
||||
['type', 4],
|
||||
])
|
||||
}
|
||||
console.log(taskList)
|
||||
console.log(taskRecords)
|
||||
|
||||
const res = await ExecAgvApi('oneclickinstock', { list: taskList })
|
||||
if (res.Result) {
|
||||
for (const record of taskRecords) {
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_送料记录_增加', record)
|
||||
}
|
||||
|
||||
ElMessage.success(`成功创建 ${taskList.length} 个移库任务`)
|
||||
moveDialogShow.value = false
|
||||
|
||||
fetchStockList()
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '创建移库任务失败')
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('用户取消移库操作')
|
||||
}
|
||||
}
|
||||
|
||||
const onMaterialInput = (index) => {
|
||||
const raw = materialCodes.value[index]
|
||||
materialCodes.value[index] = normalizeScanCode(raw)
|
||||
}
|
||||
|
||||
// 解绑产品:扫描托盘码后清空当前库区对应库位的物料信息(保留托盘号)
|
||||
const onUnbindClick = () => {
|
||||
if (!currentPartitionCode.value) {
|
||||
ElMessage.warning('请先选择移动库区')
|
||||
return
|
||||
}
|
||||
unbindCode.value = ''
|
||||
unbindDialogShow.value = true
|
||||
}
|
||||
|
||||
// 关闭解绑弹窗
|
||||
const onUnbindCancel = () => {
|
||||
unbindDialogShow.value = false
|
||||
}
|
||||
|
||||
// 执行解绑:根据托盘码找到库位,清空物料信息(项目名称、间隔编号、部件位置),保留托盘号
|
||||
const onUnbindConfirm = async () => {
|
||||
const trayCode = normalizeScanCode(unbindCode.value)
|
||||
if (!trayCode) {
|
||||
ElMessage.warning('请输入托盘码')
|
||||
return
|
||||
}
|
||||
|
||||
// 根据托盘码查找库位
|
||||
const stock = stockList.value.find(s => s.TrayNo === trayCode)
|
||||
if (!stock) {
|
||||
ElMessage.warning(`托盘码 ${trayCode} 在当前库区未找到`)
|
||||
return
|
||||
}
|
||||
|
||||
if (stock.TaskId) {
|
||||
ElMessage.warning(`库位 ${stock.StockName} 存在未完成的任务,无法解绑`)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有产品信息可以解绑
|
||||
if (!stock.ProjectNo && !stock.IntervalNo && !stock.ComponentLocation) {
|
||||
ElMessage.warning(`托盘 ${trayCode} 当前没有绑定产品信息,无需解绑`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认将托盘 ${trayCode} 从库位 ${stock.StockName} 解绑产品信息?\n项目名称:${stock.ProjectNo || '-'}\n间隔编号:${stock.IntervalNo || '-'}\n部件位置:${stock.ComponentLocation || '-'}`,
|
||||
'确认解绑',
|
||||
{
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
|
||||
const res = await ExecAgvApi('updateStockInfo', {
|
||||
StockCode: stock.StockCode,
|
||||
TrayNo: stock.TrayNo, // 保留托盘号
|
||||
ProjectNo: null, // 清空项目名称
|
||||
IntervalNo: null, // 清空间隔编号
|
||||
ComponentLocation: null, // 清空部件位置
|
||||
StockStatus: false, // 状态改为空
|
||||
TaskId: stock.TaskId,
|
||||
})
|
||||
|
||||
if (res.Result) {
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_库位更新记录_增加', [
|
||||
['库位号', stock.StockCode],
|
||||
['库位名称', stock.StockName || ''],
|
||||
['项目名称', ''],
|
||||
['间隔编号', ''],
|
||||
['部件位置', ''],
|
||||
['托盘编号', stock.TrayNo || ''],
|
||||
['项目名称_old', stock.ProjectNo || ''],
|
||||
['间隔编号_old', stock.IntervalNo || ''],
|
||||
['部件位置_old', stock.ComponentLocation || ''],
|
||||
['托盘编号_old', stock.TrayNo || ''],
|
||||
['操作人', user.value.name],
|
||||
['type', 2],
|
||||
])
|
||||
ElMessage.success('解绑成功')
|
||||
await fetchStockList()
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '解绑失败')
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('用户取消解绑操作')
|
||||
}
|
||||
|
||||
unbindDialogShow.value = false
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchAllPartitions()
|
||||
await fetchWorkstationPartitions()
|
||||
})
|
||||
|
||||
// 点击库位卡片:空库位 -> 叫料
|
||||
const onStockCardClick = async (item) => {
|
||||
// 仅空库位(无托盘、无物料)支持叫料
|
||||
const isEmptySlot = (!item.TrayNo) && (item.StockStatus === false)
|
||||
if (!isEmptySlot) return
|
||||
|
||||
currentEmptyStock.value = item
|
||||
|
||||
// 查询预设路线,拿到叫料库区(此模块按库区号传入“库位号”参数)
|
||||
try {
|
||||
const routeRes = await ExecDatabaseByParam('11', 'PTCHV_AGV_送料路线_PDA查询_初始化', [['库位号', currentPartitionCode.value]])
|
||||
if (!routeRes.data || routeRes.data.length === 0 || !routeRes.data[0].叫料库区) {
|
||||
ElMessage.warning('未配置该库区叫料路线')
|
||||
return
|
||||
}
|
||||
callPartitionCode.value = routeRes.data[0].叫料库区
|
||||
const part = allPartitions.value.find(p => p.PartitionCode == callPartitionCode.value)
|
||||
callPartitionName.value = part ? part.PartitionName : `库区${callPartitionCode.value}`
|
||||
callDialogTitle.value = `${callPartitionName.value} 库区叫料`
|
||||
|
||||
// 拉取叫料库区的库位列表
|
||||
const stockRes = await ExecAgvApi('getStockList', { PartitionCode: callPartitionCode.value })
|
||||
if (!stockRes.Result) {
|
||||
ElMessage.error(stockRes.ErrMsg || '获取库位失败')
|
||||
return
|
||||
}
|
||||
|
||||
// 过滤:空库位不可叫;允许 空托盘 或 满料(且无任务更稳妥);排除"无库位"
|
||||
const isEmptyPallet = (s) => s.TrayNo && (!s.IntervalNo || s.IntervalNo === '') && (!s.ComponentLocation || s.ComponentLocation === '') && s.StockStatus === false
|
||||
const isFull = (s) => s.StockStatus === true
|
||||
|
||||
// 判断来料库区是否为空支架区
|
||||
const isFromEmptyFrameArea = callPartitionName.value.includes('空支架区')
|
||||
|
||||
let filteredList = (stockRes.Data || [])
|
||||
.filter(s => !s.TaskId && s.StockName !== '无库位' && (isEmptyPallet(s) || isFull(s)))
|
||||
|
||||
// 如果来料库区是空支架区,只显示空托盘(但显示所有空托盘,点击时再校验)
|
||||
if (isFromEmptyFrameArea) {
|
||||
// 只保留空托盘,排序后显示所有
|
||||
const emptyPallets = filteredList.filter(s => isEmptyPallet(s))
|
||||
if (emptyPallets.length === 0) {
|
||||
ElMessage.warning('空支架区没有可用的空托盘')
|
||||
return
|
||||
}
|
||||
// 按 StockCode 降序排序,方便用户识别哪个是最外侧的
|
||||
emptyPallets.sort(compareStockDesc)
|
||||
callMaterialStockList.value = emptyPallets // 显示所有空托盘
|
||||
} else {
|
||||
callMaterialStockList.value = filteredList
|
||||
}
|
||||
|
||||
if (callMaterialStockList.value.length === 0) {
|
||||
ElMessage.warning('目标叫料库区没有可调用的库位')
|
||||
return
|
||||
}
|
||||
|
||||
showCallMaterialDialog.value = true
|
||||
} catch (e) {
|
||||
console.error('获取路线信息失败:', e)
|
||||
ElMessage.error('获取路线信息失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 选择来源库位后,创建叫料任务
|
||||
const onCallMaterialStockClick = (source) => {
|
||||
// 如果来料库区是空支架区,校验是否选择了序号最大的空托盘(最外侧)
|
||||
const isFromEmptyFrameArea = callPartitionName.value.includes('空支架区')
|
||||
if (isFromEmptyFrameArea) {
|
||||
// 从当前显示列表中找出所有空托盘
|
||||
const isEmptyPallet = (s) => s.TrayNo && (!s.IntervalNo || s.IntervalNo === '') && (!s.ComponentLocation || s.ComponentLocation === '') && s.StockStatus === false
|
||||
const allEmptyPallets = callMaterialStockList.value.filter(s => isEmptyPallet(s))
|
||||
if (allEmptyPallets.length > 0) {
|
||||
// 按 StockCode 降序排序,找出序号最大的(最外侧的)
|
||||
allEmptyPallets.sort(compareStockDesc)
|
||||
const maxStock = allEmptyPallets[0]
|
||||
if (source.StockCode !== maxStock.StockCode) {
|
||||
ElMessage.warning(`空支架区只能选择最外侧的空托盘:${maxStock.StockName}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(`是否将物料从库位【${source.StockName}】送至库位【${currentEmptyStock.value.StockName}】?`, '确认叫料', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const taskId = uuidv4()
|
||||
const res = await ExecAgvApi('createTask', {
|
||||
taskId,
|
||||
pickPosition: source.StockCode,
|
||||
dropPosition: currentEmptyStock.value.StockCode,
|
||||
})
|
||||
if (res.Result) {
|
||||
await ExecDatabaseByParam('12', 'PTCHV_AGV_送料记录_增加', [
|
||||
['任务号', taskId],
|
||||
['取料库位', source.StockCode],
|
||||
['取料库位名称', source.StockName],
|
||||
['取料项目名称', source.ProjectNo || ''],
|
||||
['取料间隔编号', source.IntervalNo || ''],
|
||||
['取料部件位置', source.ComponentLocation || ''],
|
||||
['取料托盘编号', source.TrayNo || ''],
|
||||
['送料库位', currentEmptyStock.value.StockCode],
|
||||
['送料库位名称', currentEmptyStock.value.StockName],
|
||||
['操作人', user.value.name],
|
||||
['type', 1],
|
||||
])
|
||||
ElMessage.success('叫料任务创建成功')
|
||||
showCallMaterialDialog.value = false
|
||||
await fetchStockList()
|
||||
} else {
|
||||
ElMessage.error(res.ErrMsg || '叫料任务创建失败')
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.call-dialog {
|
||||
:deep(.el-dialog__body) {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
padding-top: 8px;
|
||||
}
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
padding: 16px;
|
||||
// height: calc(100vh - 40px);
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.info-badge {
|
||||
padding: 6px 12px;
|
||||
background-color: #ecf5ff;
|
||||
color: #409eff;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.stock-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.stock-item {
|
||||
background: #f8f8f8;
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
flex: 1 1 280px;
|
||||
min-width: 280px;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.stock-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.stock-title {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.stock-tags {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.stock-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
b {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.move-dialog {
|
||||
.target-info {
|
||||
background: #f8f8f8;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #333;
|
||||
width: 50%;
|
||||
|
||||
&.warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
color: #f56c6c;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scan-inputs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.scan-item {
|
||||
.el-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
193
src/views/OpOnOutWork/index.vue
Normal file
193
src/views/OpOnOutWork/index.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<el-button style="position: fixed;bottom: 10px;right: 10px;z-index: 9999999;" type="primary" size="large" @click="onScanClick">扫描产品码</el-button>
|
||||
<el-form inline style="margin-bottom: -20px;">
|
||||
<el-form-item label="产品编号">
|
||||
<el-input v-model="searchCode" placeholder="请输入产品编号" clearable style="width: 100%" @input="fetchList" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="dataList fixed-list">
|
||||
<div class="item" v-for="item in recordList" :key="item.ID" @click="onItemClick(item)" style="position: relative;">
|
||||
<van-tag
|
||||
:type="item.离开时间 ? 'success' : 'warning'"
|
||||
size="small"
|
||||
style="position: absolute; right: 10px; top: 10px; z-index: 2;"
|
||||
>
|
||||
{{ item.到达时间 && item.离开时间 ? '已完成' : '未离站' }}
|
||||
</van-tag>
|
||||
<div class="tItem">
|
||||
<div class="name">产品编号:</div>
|
||||
<div class="value">{{ item.工件编号 }}</div>
|
||||
</div>
|
||||
<div class="tItem">
|
||||
<div class="name">到达时间:</div>
|
||||
<div class="value">{{ item.到达时间 }}</div>
|
||||
</div>
|
||||
<div class="tItem">
|
||||
<div class="name">离开时间:</div>
|
||||
<div class="value">{{ item.离开时间 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="detailDialogShow" title="详细信息" width="90vw" :close-on-click-modal="true" class="mobile-dialog">
|
||||
<el-descriptions :column="1" border class="mobile-desc">
|
||||
<el-descriptions-item label="工位号">{{ detailData.工位号 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="制造编号">{{ detailData.制造编号 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="项目名称">{{ detailData.项目名称 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="工件编号">{{ detailData.工件编号 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="工件图号">{{ detailData.工件图号 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="产品名称">{{ detailData.产品名称 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作者1">{{ detailData.操作者1 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作者2">{{ detailData.操作者2 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="到达时间">{{ detailData.到达时间 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="离开时间">{{ detailData.离开时间 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="在线时间">{{ detailData.在线时间 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否合格">{{ detailData.是否合格 == 1 ? '是' : '否' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="scanDialogShow" title="扫描产品码" width="320px" :close-on-click-modal="false">
|
||||
<el-input v-model="scanCode" placeholder="请输入产品码" clearable autofocus @keyup.enter.native="onScanConfirm" @input="onSearchCodeInput" />
|
||||
<template #footer>
|
||||
<el-button @click="scanDialogShow = false">取消</el-button>
|
||||
<el-button type="primary" @click="onScanConfirm">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useAppStore } from '@/store/app.js'
|
||||
import { storeToRefs } from 'pinia'
|
||||
const store = useAppStore()
|
||||
const { user } = storeToRefs(store)
|
||||
|
||||
|
||||
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
|
||||
const recordList = ref([])
|
||||
const detailDialogShow = ref(false)
|
||||
const detailData = ref({})
|
||||
const searchCode = ref('')
|
||||
const scanDialogShow = ref(false)
|
||||
const scanCode = ref('')
|
||||
|
||||
const fetchList = async () => {
|
||||
searchCode.value = onMaterialInput(searchCode.value)
|
||||
|
||||
const res = await ExecDatabaseByParam('11', '测量数据发动机在线状态_PDA_查询', [["工位号", user.value.opName], ["产品编号", searchCode.value]])
|
||||
if (res.data) {
|
||||
recordList.value = res.data
|
||||
} else {
|
||||
ElMessage.error('查询失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
fetchList()
|
||||
})
|
||||
|
||||
const onItemClick = (item) => {
|
||||
detailData.value = item
|
||||
detailDialogShow.value = true
|
||||
}
|
||||
|
||||
const onScanClick = () => {
|
||||
scanCode.value = ''
|
||||
scanDialogShow.value = true
|
||||
}
|
||||
|
||||
const onScanConfirm = async () => {
|
||||
if (!scanCode.value) {
|
||||
ElMessage.warning('请输入产品码')
|
||||
return
|
||||
}
|
||||
// 进离站前检查
|
||||
const checkRes = await ExecDatabaseByParam('11', 'PDA_产品进离站_前检查', [["工位号", user.value.opName], ["工件编号", scanCode.value]])
|
||||
if (checkRes.data && checkRes.data.length > 0) {
|
||||
const check = checkRes.data[0]
|
||||
if (check.result == 1) {
|
||||
// 校验通过,二次确认
|
||||
ElMessageBox.confirm(check.msg, '确认操作', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
|
||||
.then(async () => {
|
||||
// 调用进离站
|
||||
const res = await ExecDatabaseByParam('12', 'PDA_产品进离站', [["工位号", user.value.opName], ["工件编号", scanCode.value], ["type", check.type]])
|
||||
if (res.data && res.data.length > 0 && res.data[0].result == 1) {
|
||||
ElMessage.success(res.data[0].msg || '操作成功')
|
||||
scanDialogShow.value = false
|
||||
fetchList()
|
||||
} else {
|
||||
ElMessage.error(res.data && res.data[0] && res.data[0].msg ? res.data[0].msg : '操作失败')
|
||||
}
|
||||
})
|
||||
.catch(() => { })
|
||||
} else if (check.result == 2) {
|
||||
ElMessage.error(check.msg)
|
||||
} else {
|
||||
ElMessage.error(check.msg || '校验失败')
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('校验失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 离开网页 删除定时器
|
||||
onUnmounted(() => {
|
||||
})
|
||||
|
||||
const getTypeText = (type) => {
|
||||
if (type === 2 || type === '2') return '退空托盘'
|
||||
return '修改信息'
|
||||
}
|
||||
const getTypeTag = (type) => {
|
||||
if (type === 2 || type === '2') return 'warning'
|
||||
return 'success'
|
||||
}
|
||||
|
||||
// 处理扫码输入,自动提取&前的物料编号
|
||||
const onMaterialInput = (barCode) => {
|
||||
if (typeof barCode === 'string' && barCode.includes('&')) {
|
||||
const [EngineNo] = barCode.split('&')
|
||||
return EngineNo
|
||||
}
|
||||
return barCode
|
||||
}
|
||||
|
||||
const onSearchCodeInput = () => {
|
||||
// scanCode.value = onMaterialInput(scanCode.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dataList {
|
||||
.item {
|
||||
border: 2px solid #ccc;
|
||||
padding: 10px 10px;
|
||||
// 圆角 阴影
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
margin: 10px 0;
|
||||
|
||||
.tItem {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-dialog {
|
||||
max-width: 98vw;
|
||||
}
|
||||
|
||||
.mobile-desc {
|
||||
word-break: break-all;
|
||||
font-size: 15px;
|
||||
|
||||
.el-descriptions__cell {
|
||||
padding: 6px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-list {
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
230
src/views/OpStatus/index.vue
Normal file
230
src/views/OpStatus/index.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<van-cell-group>
|
||||
<van-cell title="工位号" :label="user.opName" :value="user.opNameTxt" center />
|
||||
<van-cell>
|
||||
<template #title>
|
||||
<span class="custom-title">装配状态</span>
|
||||
</template>
|
||||
<template #value>
|
||||
<!-- 如果有进站时间 那就是进行中 反之则是待进站 -->
|
||||
<van-tag v-if="engineValue.startPlanTime" type="primary">进行中</van-tag>
|
||||
<van-tag v-else type="warning">待进站</van-tag>
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell title="制造编号" :value="engineValue.workCode" is-link center />
|
||||
<van-cell title="项目名称" :value="engineValue.projectName" center />
|
||||
<van-cell title="工件编号" :value="engineValue.EngineID" />
|
||||
<van-cell title="产品型号" :value="engineValue.EngineType" center />
|
||||
<van-cell title="进站时间" :value="engineValue.startPlanTime" />
|
||||
</van-cell-group>
|
||||
|
||||
<!-- 物料验证列表 -->
|
||||
<div v-if="materialList.length > 0" class="material-list">
|
||||
<van-cell-group title="物料验证列表">
|
||||
<van-cell v-for="item in materialList" :key="item.ID" :title="item.物料名称"
|
||||
:label="`物料图号:${item.物料图号}\n物料编号:${item.物料编号}`" @click="showDetail(item)">
|
||||
<template #value>
|
||||
<span v-if="item.校验时间" style="color: #67C23A;">已校验</span>
|
||||
<span v-else style="color: #F56C6C;">未校验</span>
|
||||
</template>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="detailDialogShow" title="详细信息" width="90vw" :close-on-click-modal="true" class="mobile-dialog">
|
||||
<el-descriptions :column="1" border class="mobile-desc">
|
||||
<el-descriptions-item label="ID">{{ detailData.ID }}</el-descriptions-item>
|
||||
<el-descriptions-item label="工位号">{{ detailData.工位号 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="间隔图号">{{ detailData.间隔图号 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="物料名称">{{ detailData.物料名称 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="物料图号">{{ detailData.物料图号 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="匹配规则前">{{ detailData.匹配规则前 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="匹配规则后">{{ detailData.匹配规则后 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注1">{{ detailData.备注1 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注2">{{ detailData.备注2 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注3">{{ detailData.备注3 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注4">{{ detailData.备注4 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否验证">
|
||||
<van-tag v-if="detailData.是否验证 === 1" type="success">需验证</van-tag>
|
||||
<van-tag v-else type="warning">不验证</van-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="物料编号">{{ detailData.物料编号 }}</el-descriptions-item>
|
||||
<el-descriptions-item label="校验时间">{{ detailData.校验时间 }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
|
||||
import { useAppStore } from '@/store/app.js'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const store = useAppStore()
|
||||
const { user, routerData } = storeToRefs(store)
|
||||
const ExecDatabaseByParam = inject('ExecDatabaseByParam')
|
||||
|
||||
// 物料验证列表
|
||||
const materialList = ref([])
|
||||
|
||||
// 物料明细弹窗相关变量
|
||||
const detailDialogShow = ref(false)
|
||||
const detailData = reactive({})
|
||||
|
||||
const carSectionList = reactive([])
|
||||
const engineValue = reactive({
|
||||
EngineID: '', // 产品编号
|
||||
EngineType: '', // 产品型号
|
||||
workCode: '', // 制造编号
|
||||
projectName: '', // 项目名称
|
||||
startPlanTime: '', // 开始时间
|
||||
})
|
||||
|
||||
|
||||
var timer1 = null;
|
||||
|
||||
onMounted(() => {
|
||||
getData()
|
||||
// 每十秒更新一次
|
||||
timer1 = setInterval(() => {
|
||||
getData()
|
||||
}, 1000 * 10)
|
||||
})
|
||||
|
||||
// 离开网页 删除定时器
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer1)
|
||||
})
|
||||
|
||||
const getData = async () => {
|
||||
let response = await ExecDatabaseByParam('11', 'PTCHV_工位Moby_工位查询', [['工位号', store.user.opName]])
|
||||
if (response.data.length > 0) {
|
||||
const obj = response.data[0]
|
||||
engineValue.workCode = obj.制造编号
|
||||
engineValue.projectName = obj.项目名称
|
||||
engineValue.EngineID = obj.工件编号
|
||||
engineValue.EngineType = obj.工件图号
|
||||
engineValue.startPlanTime = obj.到达时间
|
||||
// 查物料列表
|
||||
await getMaterialList(store.user.opName, obj.工件编号)
|
||||
} else {
|
||||
engineValue.workCode = ''
|
||||
engineValue.projectName = ''
|
||||
engineValue.EngineID = ''
|
||||
engineValue.EngineType = ''
|
||||
engineValue.startPlanTime = ''
|
||||
materialList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 查询物料列表
|
||||
const getMaterialList = async (opName, engineId) => {
|
||||
if (!engineId) {
|
||||
materialList.value = []
|
||||
return
|
||||
}
|
||||
const res = await ExecDatabaseByParam('11', '电子看板_装配零件_工位查询', [
|
||||
['工位号', opName],
|
||||
['工件编号', engineId]
|
||||
])
|
||||
if (res.data && res.data.length > 0) {
|
||||
materialList.value = res.data
|
||||
} else {
|
||||
materialList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 物料明细弹窗
|
||||
const showDetail = async (item) => {
|
||||
Object.assign(detailData, item)
|
||||
detailDialogShow.value = true
|
||||
}
|
||||
|
||||
// 对应el-descriptions中使用的方法
|
||||
const getTypeText = (type) => {
|
||||
// 这里可以根据type值转换为对应的文本
|
||||
return type || ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.carMainData {
|
||||
width: 80vw;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
border: 1px solid #a8a8a8;
|
||||
margin: 10vh auto 30px auto;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 10px;
|
||||
|
||||
|
||||
padding: 30px 0;
|
||||
|
||||
.item {
|
||||
width: 50%;
|
||||
margin: 5px 0;
|
||||
display: flex;
|
||||
line-height: 32px;
|
||||
|
||||
.title {
|
||||
width: 30%;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.twoLayerMaterial {
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 26px;
|
||||
line-height: 80px;
|
||||
margin: 10vh auto 30px auto;
|
||||
|
||||
|
||||
.posList {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 10px;
|
||||
border: 1px solid #a8a8a8;
|
||||
padding: 0 30px;
|
||||
background-color: #f6eadb;
|
||||
|
||||
:deep(.el-checkbox.el-checkbox--large) {
|
||||
zoom: 200%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-top: 10vh;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
|
||||
.el-button {
|
||||
width: 20vw;
|
||||
height: 8vh;
|
||||
font-size: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.topOp {
|
||||
position: absolute;
|
||||
top: 7vh;
|
||||
left: 5vw;
|
||||
font-size: 2vw;
|
||||
|
||||
.text {
|
||||
background-color: #008000;
|
||||
margin-left: 20px;
|
||||
padding: 0 1vw;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.material-list {
|
||||
margin-top: 18px;
|
||||
}
|
||||
</style>
|
||||
58
vite.config.js
Normal file
58
vite.config.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
import copy from 'rollup-plugin-copy'
|
||||
import { resolve } from 'path'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
import { VantResolver } from '@vant/auto-import-resolver';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver(),VantResolver()],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [ElementPlusResolver(),VantResolver()],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
pluginOptions: {
|
||||
'style-resources-loader': {
|
||||
preProcessor: 'scss',
|
||||
patterns: []
|
||||
}
|
||||
},
|
||||
define: {
|
||||
'process.env': {}
|
||||
},
|
||||
// 完全禁用默认的 public 目录处理
|
||||
publicDir: false,
|
||||
build: {
|
||||
// 清空输出目录时保留自定义拷贝的文件
|
||||
emptyOutDir: true,
|
||||
|
||||
rollupOptions: {
|
||||
plugins: [
|
||||
copy({
|
||||
targets: [
|
||||
{
|
||||
src: 'public/**/*', // 复制整个 public 目录
|
||||
dest: 'dist/public' // 输出到 dist/public
|
||||
}
|
||||
],
|
||||
hook: 'writeBundle', // 在构建完成后执行
|
||||
copyOnce: true // 仅拷贝一次
|
||||
})
|
||||
]
|
||||
}
|
||||
},
|
||||
})
|
||||
1
vue.config.js
Normal file
1
vue.config.js
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user