Add all dashboard projects

This commit is contained in:
meswork764
2026-05-23 14:07:11 +08:00
commit ba82fbfc14
3550 changed files with 467904 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
ENV = 'development'
VITE_BASE_URL = 'http://localhost:10081'
VITE_CONFIG_URL = './public/config.js'

View File

@@ -0,0 +1,3 @@
ENV = 'production'
VITE_BASE_URL = 'http://127.0.0.1:10081'
VITE_CONFIG_URL = './config.js'

View File

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

View 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?

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

@@ -0,0 +1,20 @@
<!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>精力生产看板</title>
<script type="text/javascript">
document.write("<script src='/config.js?v=" + new Date().getTime() + "'><\/script>");
</script>
</head>
<body>
<div id="app"></div>
<script src="/src/main.js" type="module"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
{
"name": "vue3-view",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@kjgl77/datav-vue3": "^1.7.4",
"axios": "^1.7.7",
"echarts": "^6.0.0",
"element-plus": "^2.8.6",
"js-cookie": "^3.0.5",
"path": "^0.12.7",
"pinia": "^2.2.5",
"rollup-plugin-copy": "^3.5.0",
"tdesign-vue-next": "^1.10.3",
"vue": "^3.5.12",
"vue-cookies": "^1.8.4",
"vue-router": "^4.4.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"sass": "^1.80.5",
"vite": "^5.4.10"
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,3 @@
window.g = {
API_WEB_URL: 'https://192.168.2.92:10101',
};

View File

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

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,56 @@
<template>
<t-loading :loading="isLoading" size="large" text="加载中(大)...">
<router-view/>
</t-loading>
</template>
<script setup>
import {useAppStore} from "@/store/app.js";
import {storeToRefs} from "pinia";
const store = useAppStore();
// storeToRefs 响应式解构赋值 pinia
const {isLoading} = storeToRefs(store);
</script>
<style lang="scss">
// 定义的全局变量
body {
margin: 0px;
}
/*修改提示框样式 更醒目*/
.el-message-box {
// 标题
.el-message-box__title {
font-size: 35px;
font-weight: 700;
color: rgb(255, 143, 0);
}
// 提示文本
.el-message-box__message p {
font-size: 24px;
line-height: 40px;
}
// icon图标
.el-message-box__status {
font-size: 40px;
}
.el-message-box__btns {
justify-content: space-evenly;
.el-button {
width: 48%;
height: 6vh;
font-size: 1.6rem;
}
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,4 @@
@font-face{
font-family:electronicFont;
src:url(./DS-DIGIT.TTF)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
import {createRouter, createWebHashHistory} from "vue-router";
// cookies
// 创建routes
const routes = [
{
// 重定向
path: "/home",
redirect: "/",
},
{
//质检任务看板
path: "/",
name: "root",
component: () => import("@/views/index.vue"),
children: [],
}
];
//History路由模型
const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes,
});
export default router;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,828 @@
<template>
<div
class="machining-two-page"
:style="{ width: `${style.width}px`, height: `${style.height}px`, transform: style.transform }"
>
<header class="dashboard-header">
<div class="brand">
<img class="brand-logo" :src="clientLogoUrl" alt="RENLY" />
</div>
<h1>机加设备生产运营看板</h1>
<div class="clock">{{ currentTime }}</div>
</header>
<main class="dashboard-body">
<section v-for="card in summaryCards" :key="card.title" class="panel summary-card" :class="card.className">
<img class="summary-icon" :src="card.icon" alt="" />
<div class="summary-content">
<div class="summary-title">{{ card.title }}</div>
<div class="summary-label">总数</div>
<div class="summary-number">
<strong :class="card.valueClass">{{ card.value }}</strong>
<span>{{ card.unit }}</span>
</div>
</div>
<div v-if="card.people" class="absence-list">
<div v-for="person in card.people" :key="person.name">
<span>{{ person.name }}</span>
<span>{{ person.station }}</span>
</div>
</div>
</section>
<section class="panel work-detail-panel">
<div class="panel-title">在制任务明细</div>
<div class="table-box datav-table main-task-table">
<dv-scroll-board :config="inProcessBoardConfig" />
</div>
</section>
<section class="panel urgent-list-panel">
<div class="panel-title">加急任务列表</div>
<div class="table-box datav-table">
<dv-scroll-board :config="urgentBoardConfig" />
</div>
</section>
<section class="panel process-task-panel">
<div class="panel-title">关键工序任务明细</div>
<div class="table-box datav-table">
<dv-scroll-board :config="keyProcessBoardConfig" />
</div>
</section>
<section class="panel start-task-panel">
<div class="panel-title">正在开工任务</div>
<div class="table-box datav-table">
<dv-scroll-board :config="startedBoardConfig" />
</div>
</section>
</main>
</div>
</template>
<script setup>
// import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
import { computed, inject, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
const tableData = ref([])
const CreateData = inject('CreateData')
const ExecDatabase = inject('ExecDatabase')
defineOptions({
name: 'MachiningOperationTwoDashboard'
})
const clientLogoUrl = './public/ClientLogo.png'
const svgData = svg => 'data:image/svg+xml;utf8,' + encodeURIComponent(svg)
const lineIcon = (color, body, viewBox = '0 0 64 64') => {
const size = parseFloat(viewBox.split(' ')[2]) || 64
const sw = size <= 28 ? 1.4 : 2.0
return svgData(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="' + viewBox + '" fill="none">' +
'<g stroke="' + color + '" stroke-width="' + sw + '" stroke-linecap="round" stroke-linejoin="round">' +
body +
'</g></svg>'
)
}
const icons = {
clipboard: lineIcon('#35c36a', '<rect x="18" y="13" width="31" height="42" rx="3"/><path d="M27 13h13l-3-6h-8zM25 27h16M25 36h16M25 45h16"/>'),
rocket: lineIcon('#248cff', '<path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2Z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09Z"/><circle cx="15" cy="9" r="1.5"/>', '0 0 24 24'),
shield: lineIcon('#6954dd', '<path d="M32 8 52 17v15c0 12-8 20-20 25-12-5-20-13-20-25V17z"/><circle cx="32" cy="32" r="9"/><path d="M32 22v4M32 38v4M22 32h4M38 32h4M25 25l3 3M36 36l3 3M39 25l-3 3M28 36l-3 3"/>'),
user: lineIcon('#ff941e', '<circle cx="32" cy="19" r="10"/><path d="M14 55c2-12 9-19 18-19s16 7 18 19z"/>')
}
const style = reactive({
width: 1920,
height: 1080,
transform: 'scaleY(1) scaleX(1) translate(-50%, -50%)'
})
const currentTime = ref('')
let clockTimer = null
let resizeHandler = null
// 将 computed 改为 ref
const summaryCards = ref([
{
title: '在制任务汇总',
value: 84,
unit: '单',
icon: icons.clipboard,
className: 'wip-card',
valueClass: 'green-text'
},
{
title: '加急任务汇总',
value: 26,
unit: '单',
icon: icons.rocket,
className: 'urgent-card',
valueClass: 'blue-text'
},
{
title: '关键工序任务',
value: 14,
unit: '单',
icon: icons.shield,
className: 'key-card',
valueClass: 'purple-text'
},
{
title: '缺勤人员',
value: 4,
unit: '人',
icon: icons.user,
className: 'absence-card',
valueClass: 'orange-text',
people: [
{ name: '张三', station: '铣床1#' },
{ name: '王五', station: '车床2#' },
{ name: '孙八', station: '磨床1#' },
{ name: '郑十一', station: '检验台1#' }
]
}
])
const inProcessRows = ref([
])
const urgentRows = ref([
{ productionOrder: 'MO260428-001', salesOrder: 'SO260413-001', materialName: '主轴壳体', materialCode: 'MP-BOX-001', processName: '精铣', assignQty: 120, prevPassQty: 105, urgentQty: 15, assignee: '张伟' },
{ productionOrder: 'MO260428-002', salesOrder: 'SO260413-005', materialName: '法兰盘', materialCode: 'MP-FLANGE-002', processName: '镗孔', assignQty: 80, prevPassQty: 68, urgentQty: 12, assignee: '李强' },
{ productionOrder: 'MO260428-003', salesOrder: 'SO260414-002', materialName: '齿轮轴', materialCode: 'MP-GEAR-001', processName: '磨削', assignQty: 60, prevPassQty: 48, urgentQty: 12, assignee: '王磊' },
{ productionOrder: 'MO260428-004', salesOrder: 'SO260414-003', materialName: '端盖', materialCode: 'MP-COVER-003', processName: '钻孔', assignQty: 90, prevPassQty: 75, urgentQty: 15, assignee: '刘洋' },
{ productionOrder: 'MO260428-005', salesOrder: 'SO260414-007', materialName: '导轨座', materialCode: 'MP-RAIL-002', processName: '精镗', assignQty: 70, prevPassQty: 60, urgentQty: 10, assignee: '赵峰' },
{ productionOrder: 'MO260428-006', salesOrder: 'SO260415-001', materialName: '轴承座', materialCode: 'MP-BEAR-001', processName: '铣平面', assignQty: 50, prevPassQty: 42, urgentQty: 8, assignee: '陈晨' },
{ productionOrder: 'MO260428-007', salesOrder: 'SO260415-003', materialName: '联轴器', materialCode: 'MP-COUP-001', processName: '车削', assignQty: 40, prevPassQty: 32, urgentQty: 8, assignee: '周杰' },
{ productionOrder: 'MO260428-008', salesOrder: 'SO260415-005', materialName: '垫板', materialCode: 'MP-PLATE-001', processName: '钻孔', assignQty: 100, prevPassQty: 88, urgentQty: 12, assignee: '孙鹏' }
])
const keyProcessRows = ref([
{ productionOrder: 'MO260428-201', salesOrder: 'SO260413-001', materialName: '主轴壳体', materialCode: 'MP-BOX-001', processName: '精镗面', assignQty: 120, prevPassQty: 105, urgentQty: 15, assignee: '周检' },
{ productionOrder: 'MO260428-202', salesOrder: 'SO260413-002', materialName: '法兰盘', materialCode: 'MP-FLANGE-002', processName: '镗孔尺寸', assignQty: 80, prevPassQty: 68, urgentQty: 12, assignee: '吕检' },
{ productionOrder: 'MO260428-203', salesOrder: 'SO260413-003', materialName: '齿轮轴', materialCode: 'MP-GEAR-001', processName: '磨削圆度', assignQty: 60, prevPassQty: 48, urgentQty: 12, assignee: '何检' },
{ productionOrder: 'MO260428-204', salesOrder: 'SO260413-004', materialName: '端盖', materialCode: 'MP-COVER-003', processName: '平面度', assignQty: 90, prevPassQty: 75, urgentQty: 15, assignee: '郭检' },
{ productionOrder: 'MO260428-205', salesOrder: 'SO260414-001', materialName: '导轨座', materialCode: 'MP-RAIL-002', processName: '平行度', assignQty: 70, prevPassQty: 60, urgentQty: 10, assignee: '丁检' },
{ productionOrder: 'MO260428-206', salesOrder: 'SO260414-002', materialName: '轴承座', materialCode: 'MP-BEAR-001', processName: '孔距尺寸', assignQty: 50, prevPassQty: 42, urgentQty: 8, assignee: '黄检' },
{ productionOrder: 'MO260428-207', salesOrder: 'SO260414-003', materialName: '联轴器', materialCode: 'MP-COUP-001', processName: '端面跳动', assignQty: 40, prevPassQty: 32, urgentQty: 8, assignee: '蒋检' },
{ productionOrder: 'MO260428-208', salesOrder: 'SO260414-004', materialName: '垫板', materialCode: 'MP-PLATE-001', processName: '厚度', assignQty: 100, prevPassQty: 88, urgentQty: 12, assignee: '韩检' }
])
const startedRows = ref([
{ productionOrder: 'MO260429-301', salesOrder: 'SO260415-101', materialName: '箱体壳体', materialCode: 'MP-BOX-009', processName: '粗铣', assignQty: 30 },
{ productionOrder: 'MO260429-302', salesOrder: 'SO260415-102', materialName: '轴套', materialCode: 'MP-SHAFT-008', processName: '轴车', assignQty: 24 },
{ productionOrder: 'MO260429-303', salesOrder: 'SO260415-103', materialName: '端盖', materialCode: 'MP-CAP-006', processName: '钻孔', assignQty: 20 },
{ productionOrder: 'MO260429-304', salesOrder: 'SO260415-104', materialName: '支架', materialCode: 'MP-BRKT-009', processName: '平面铣', assignQty: 16 },
{ productionOrder: 'MO260429-305', salesOrder: 'SO260415-105', materialName: '导轨座', materialCode: 'MP-RAIL-010', processName: '铣槽', assignQty: 18 },
{ productionOrder: 'MO260429-306', salesOrder: 'SO260415-106', materialName: '盖板', materialCode: 'MP-COVER-011', processName: '攻丝', assignQty: 22 },
{ productionOrder: 'MO260429-307', salesOrder: 'SO260415-107', materialName: '连接板', materialCode: 'MP-PLATE-010', processName: '钻孔', assignQty: 28 },
{ productionOrder: 'MO260429-308', salesOrder: 'SO260415-108', materialName: '滑块', materialCode: 'MP-SLIDER-009', processName: '精磨', assignQty: 14 }
])
const extendRows = (rows, minRows, idKey = 'productionOrder') => {
const result = [...rows]
let round = 1
while (result.length < minRows) {
rows.forEach(row => {
if (result.length >= minRows) return
const suffix = String(round + 1).padStart(2, '0')
result.push({
...row,
[idKey]: row[idKey].replace(/(\d+)$/, value => String(Number(value) + round * 100).padStart(value.length, '0'))
|| `${row[idKey]}-${suffix}`
})
})
round += 1
}
return result
}
const boardBaseConfig = {
headerBGC: 'rgba(8, 65, 126, .94)',
oddRowBGC: 'rgba(5, 28, 65, .52)',
evenRowBGC: 'rgba(3, 20, 51, .42)',
headerHeight: 34,
waitTime: 1400,
carousel: 'single',
hoverPause: true,
align: 'center',
index: false
}
const progressText = value => (
'<div class="progress-cell">' +
'<span class="progress-track"><span class="progress-fill" style="width:' + value + '%"></span></span>' +
'<strong>' + Number(value).toFixed(1) + '%</strong>' +
'</div>'
)
const inProcessBoardConfig = computed(() => ({
...boardBaseConfig,
rowNum: 12,
columnWidth: [126, 130, 116, 136, 88, 80, 80, 158],
header: ['生产订单', '销售订单', '物料名称', '物料编码', '工序名称', '分配数量', '完成数量', '进度百分比%'],
data: extendRows(inProcessRows.value, 18).map(row => [
row.productionOrder,
row.salesOrder,
row.materialName,
row.materialCode,
row.processName,
row.assignQty,
row.finishQty,
progressText(row.progress)
])
}))
const urgentBoardConfig = computed(() => makeTaskBoard(urgentRows.value))
const keyProcessBoardConfig = computed(() => makeTaskBoard(keyProcessRows.value))
const startedBoardConfig = computed(() => ({
...boardBaseConfig,
rowNum: 8,
columnWidth: [160, 170, 140, 176, 140, 128],
header: ['生产订单', '销售订单', '物料名称', '物料编码', '工序名称', '分配数量'],
data: extendRows(startedRows.value, 12).map(row => [
row.productionOrder,
row.salesOrder,
row.materialName,
row.materialCode,
row.processName,
row.assignQty
])
}))
function makeTaskBoard(rows) {
return {
...boardBaseConfig,
rowNum: 8,
columnWidth: [122, 132, 132, 152, 92, 82, 92, 82, 94],
header: ['生产订单', '销售订单', '物料名称', '物料编码', '工序名称', '分配数量', '加急数量', '指派对象'],
data: extendRows(rows, 12).map(row => [
row.productionOrder,
row.salesOrder,
row.materialName,
row.materialCode,
row.processName,
row.assignQty,
row.urgentQty,
row.assignee
])
}
}
onMounted(() => {
refreshClock()
clockTimer = window.setInterval(refreshClock, 1000)
setScale()
resizeHandler = setScale
window.addEventListener('resize', resizeHandler)
searchDashboardData()
})
onUnmounted(() => {
if (clockTimer) {
window.clearInterval(clockTimer)
clockTimer = null
}
if (resizeHandler) {
window.removeEventListener('resize', resizeHandler)
resizeHandler = null
}
})
const queryDashboardData = (procedureName, applyData) => {
let data
if (typeof CreateData !== 'function' || typeof ExecDatabase !== 'function') {
return
}
data = CreateData('11', procedureName, [])
ExecDatabase(data).then(response => {
const rows = getResponseRows(response)
if (rows.length > 0) {
tableData.value = rows
applyData(rows)
nextTick(() => {
})
}
})
}
const getResponseRows = response => {
if (response && Array.isArray(response.data)) {
return response.data
}
if (response && response.data && Array.isArray(response.data.result)) {
return response.data.result
}
return []
}
const searchDashboardData = () => {
searchUrgentData()
searchInProcessData()
searchInProcessData2()
}
const searchUrgentData = () => {
queryDashboardData('机加设备生产运营看板_加急任务查询', applyUrgentData)
}
const searchInProcessData = () => {
queryDashboardData('机加设备生产运营看板_机加在制查询', applyInProcessData)
}
const searchInProcessData2 = () => {
queryDashboardData('机加设备生产运营看板_机加在制查询2', applyInProcessData2)
}
// 修改赋值逻辑
const applyUrgentData = rows => {
const first = getFirstRow(rows)
console.log(first)
// 加急任务汇总卡片是索引1
if (first.加急订单总数 !== undefined) {
summaryCards.value[1].value = first.加急订单总数
}
urgentRows .value = rows.map(row => ({
productionOrder: row.订单编号,
salesOrder:row.合同号,
materialName: row.物料描述,
materialCode: row.物料编号,
assignQty: row.计划数量,
urgentQty: row.加急数量,
prevPassQty:'',
processName:row.工序名称,
assignee:row.指派对象,
}))
}
const applyInProcessData = rows => {
const first = getFirstRow(rows)
startedRows .value = rows.map(row => ({
productionOrder: row.订单号,
salesOrder:row.合同号,
materialName: row.零件名称,
materialCode: row.零件编码,
assignQty: row.分配数,
processName:row.工序名称,
assignee:row.指派对象,
}))
}
const applyInProcessData2 = rows => {
const first = getFirstRow(rows)
summaryCards.value[0].value = first.总数
inProcessRows .value = rows.map(row => ({
productionOrder: row.订单号,
salesOrder:row.合同号,
materialName: row.零件名称,
materialCode: row.零件编码,
assignQty: row.分配数,
processName:row.工序名称,
assignee: row.指派对象,
finishQty: row.完成数量,
progress: ((row.完成数量 / row.分配数) * 100).toFixed(2)
}))
}
const getFirstRow = rows => {
if (Array.isArray(rows) && rows.length > 0) {
return rows[0]
}
return {}
}
const refreshClock = () => {
const now = new Date()
const pad = value => String(value).padStart(2, '0')
const weeks = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
currentTime.value = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())} ${weeks[now.getDay()]}`
}
const setScale = () => {
const scale = getScale()
style.transform = `scaleY(${scale.y}) scaleX(${scale.x}) translate(-50%, -50%)`
}
const getScale = () => ({
x: window.innerWidth / style.width,
y: window.innerHeight / style.height
})
</script>
<style scoped>
:global(html),
:global(body),
:global(#app) {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
background: #010716;
}
:global(.t-loading),
:global(.t-loading__parent) {
min-height: 100%;
background: #010716;
}
* {
box-sizing: border-box;
}
.machining-two-page {
position: fixed;
left: 50%;
top: 50%;
width: 1920px;
height: 1080px;
overflow: hidden;
color: #dce9ff;
font-family: "Microsoft YaHei", "PingFang SC", Arial, sans-serif;
transform-origin: 0 0;
transition: 0.3s;
background:
linear-gradient(90deg, rgba(18, 88, 176, 0.07) 1px, transparent 1px),
linear-gradient(180deg, rgba(18, 88, 176, 0.055) 1px, transparent 1px),
radial-gradient(ellipse at 50% -4%, rgba(19, 101, 209, 0.38), rgba(4, 27, 76, 0.16) 28%, transparent 58%),
linear-gradient(180deg, #010716 0%, #021330 48%, #010716 100%);
background-size: 66px 66px, 66px 66px, auto, auto;
}
.dashboard-header {
position: relative;
height: 76px;
display: flex;
align-items: center;
padding: 0 18px;
border-top: 3px solid rgba(82, 148, 224, .9);
background:
linear-gradient(112deg, rgba(8, 24, 70, .96) 0 16%, rgba(2, 8, 27, .98) 16% 30%, rgba(5, 23, 60, .78) 50%, rgba(2, 8, 27, .98) 70% 100%);
}
.dashboard-header::after {
content: "";
position: absolute;
left: 323px;
right: 323px;
bottom: 0;
height: 2px;
background: linear-gradient(90deg, transparent, #0c6dc4 18%, #4ee7ff 50%, #0c6dc4 82%, transparent);
box-shadow: 0 0 12px rgba(51, 210, 255, .78);
}
.brand {
width: 318px;
display: flex;
align-items: center;
position: relative;
z-index: 2;
}
.brand-logo {
width: 194px;
height: 47px;
padding: 2px 8px;
object-fit: contain;
object-position: left center;
background: #fff;
border-radius: 4px;
}
.dashboard-header h1 {
position: absolute;
left: 50%;
top: 20px;
margin: 0;
transform: translateX(-50%);
color: #fbfdff;
font-size: 34px;
font-weight: 900;
line-height: 1;
letter-spacing: 13px;
white-space: nowrap;
text-shadow: 0 0 10px rgba(255, 255, 255, .35);
}
.clock {
margin-left: auto;
min-width: 468px;
color: #dfe6f4;
font-size: 22px;
font-weight: 700;
letter-spacing: 2px;
text-align: right;
}
.dashboard-body {
position: relative;
height: calc(100% - 76px);
padding: 0;
}
.panel {
position: absolute;
overflow: hidden;
border: 1px solid rgba(0, 214, 255, .76);
border-radius: 5px;
background:
linear-gradient(90deg, rgba(6, 40, 94, .22), transparent 18%, transparent 82%, rgba(6, 40, 94, .22)),
radial-gradient(circle at 8% 0, rgba(24, 120, 230, .14), transparent 28%),
linear-gradient(180deg, rgba(4, 29, 73, .92), rgba(2, 12, 34, .98));
box-shadow:
inset 0 0 24px rgba(0, 123, 255, .22),
0 0 9px rgba(0, 210, 255, .42);
}
.panel::before {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
background:
linear-gradient(90deg, #21e1ff 0 18px, transparent 18px) left top / 76px 1px no-repeat,
linear-gradient(180deg, #21e1ff 0 18px, transparent 18px) left top / 1px 76px no-repeat,
linear-gradient(270deg, #21e1ff 0 18px, transparent 18px) right top / 76px 1px no-repeat,
linear-gradient(180deg, #21e1ff 0 18px, transparent 18px) right top / 1px 76px no-repeat,
linear-gradient(90deg, #21e1ff 0 18px, transparent 18px) left bottom / 76px 1px no-repeat,
linear-gradient(0deg, #21e1ff 0 18px, transparent 18px) left bottom / 1px 76px no-repeat,
linear-gradient(270deg, #21e1ff 0 18px, transparent 18px) right bottom / 76px 1px no-repeat,
linear-gradient(0deg, #21e1ff 0 18px, transparent 18px) right bottom / 1px 76px no-repeat;
filter: drop-shadow(0 0 6px rgba(0, 226, 255, .72));
}
.summary-card {
top: 10px;
height: 128px;
display: flex;
align-items: center;
padding: 0 38px;
}
.wip-card {
left: 14px;
width: 466px;
}
.urgent-card {
left: 496px;
width: 424px;
}
.key-card {
left: 934px;
width: 404px;
}
.absence-card {
left: 1352px;
width: 554px;
padding-right: 28px;
}
.summary-icon {
width: 79px;
height: 79px;
margin-right: 34px;
object-fit: contain;
}
.summary-content {
min-width: 0;
}
.summary-title {
margin-bottom: 8px;
color: #e8f1ff;
font-size: 20px;
font-weight: 900;
letter-spacing: 5px;
white-space: nowrap;
}
.summary-label {
margin-bottom: 2px;
color: #b9c8df;
font-size: 15px;
font-weight: 700;
letter-spacing: 2px;
}
.summary-number {
display: flex;
align-items: flex-end;
gap: 22px;
}
.summary-number strong {
font-size: 50px;
line-height: .9;
font-weight: 900;
letter-spacing: 6px;
text-shadow: 0 0 13px currentColor;
}
.summary-number span {
padding-bottom: 3px;
color: #d8e4f8;
font-size: 15px;
font-weight: 800;
}
.green-text {
color: #28d961;
}
.blue-text {
color: #09a8ff;
}
.purple-text {
color: #7459ef;
}
.orange-text {
color: #ff951c;
}
.absence-list {
flex: 1;
min-width: 0;
margin-left: 32px;
padding-left: 24px;
color: #f1f5ff;
font-size: 16px;
font-weight: 800;
line-height: 27px;
border-left: 1px solid rgba(47, 144, 225, .32);
}
.absence-list div {
display: grid;
grid-template-columns: 66px 1fr;
white-space: nowrap;
}
.panel-title {
position: relative;
z-index: 1;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
color: #f4f8ff;
font-size: 21px;
font-weight: 900;
letter-spacing: 8px;
white-space: nowrap;
text-shadow: 0 0 8px rgba(255, 255, 255, .42);
}
.panel-title::before,
.panel-title::after {
content: "";
width: 50px;
height: 1px;
margin: 0 12px;
background: linear-gradient(90deg, transparent, rgba(34, 166, 255, .5));
}
.panel-title::after {
transform: rotate(180deg);
}
.work-detail-panel {
left: 14px;
top: 149px;
width: 938px;
height: 818px;
}
.urgent-list-panel {
left: 968px;
top: 149px;
width: 938px;
height: 270px;
}
.process-task-panel {
left: 968px;
top: 431px;
width: 938px;
height: 270px;
}
.start-task-panel {
left: 968px;
top: 713px;
width: 938px;
height: 254px;
}
.table-box {
position: absolute;
left: 12px;
right: 12px;
top: 49px;
bottom: 14px;
overflow: hidden;
border: 1px solid rgba(0, 148, 231, .78);
scrollbar-width: none;
}
.table-box::-webkit-scrollbar {
display: none;
}
.datav-table :deep(.dv-scroll-board) {
width: 100%;
height: 100%;
color: #c2cbe0;
font-size: 13px;
font-weight: 400;
}
.datav-table :deep(.header) {
color: #d5e2f8;
font-size: 13px;
font-weight: 800;
background: linear-gradient(180deg, rgba(12, 73, 143, .96), rgba(5, 43, 97, .96)) !important;
}
.datav-table :deep(.header-item),
.datav-table :deep(.ceil) {
display: flex;
align-items: center;
justify-content: center;
padding: 0 2px;
border-right: 1px solid rgba(0, 164, 255, .55);
border-bottom: 1px solid rgba(0, 164, 255, .42);
white-space: nowrap;
overflow: hidden;
text-overflow: clip;
}
.datav-table :deep(.header-item:last-child),
.datav-table :deep(.ceil:last-child) {
border-right: 0;
}
.datav-table :deep(.rows) {
overflow: hidden;
}
.datav-table :deep(.row-item) {
color: #c2cbe0 !important;
font-weight: 400 !important;
}
.datav-table :deep(.row-item .ceil) {
color: #c2cbe0 !important;
font-weight: 400 !important;
text-align: center;
}
.datav-table :deep(.header .header-item) {
color: #d5e2f8 !important;
font-weight: 800 !important;
text-align: center;
}
.main-task-table :deep(.row-item) {
font-size: 13px;
}
.datav-table :deep(.progress-cell) {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
height: 100%;
}
.datav-table :deep(.progress-track) {
position: relative;
width: 78px;
height: 16px;
border: 1px solid rgba(73, 160, 232, .76);
border-radius: 2px;
overflow: hidden;
background: rgba(14, 41, 67, .92);
box-shadow: inset 0 0 9px rgba(16, 130, 255, .25);
}
.datav-table :deep(.progress-fill) {
position: absolute;
left: 0;
top: 0;
bottom: 0;
background: linear-gradient(90deg, #314dff 0%, #126dff 55%, #22e6ff 100%);
box-shadow: 0 0 10px rgba(23, 150, 255, .75);
}
.datav-table :deep(.progress-cell strong) {
min-width: 46px;
color: #62c6ec;
font-size: 12px;
font-weight: 500;
text-align: right;
}
</style>

View File

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

View File

@@ -0,0 +1,536 @@
# 机加设备生产运营看板(二)实施文档
---
## 一、业务背景与目标
### 1.1 业务描述
本看板用于**机械加工车间**的生产运营实时监控,面向车间管理人员和调度人员,提供机加设备相关的在制任务、加急任务、关键工序和人员出勤等核心运营数据的可视化展示。
**业务痛点:**
- 车间管理人员无法实时掌握在制任务的整体进度
- 加急订单的优先级无法直观呈现,导致交期延误风险
- 关键工序的瓶颈难以快速识别
- 缺勤人员信息不透明,影响排产调度
**看板目标:**
- 一屏展示机加车间当日生产运营全貌
- 实时刷新任务进度数据
- 大屏自适应展示,适配 1920×1080 至各种分辨率
### 1.2 看板布局
```
┌──────────────────────────────────────────────────────┐
│ LOGO 机加设备生产运营看板(二) 时钟 │ ← 页头 Header
├──────────┬──────────┬──────────┬──────────────────────┤
│ 在制任务 │ 加急任务 │ 关键工序 │ 缺勤人员 │ ← 汇总卡片行
│ 汇总 84单 │ 汇总 26单 │ 任务 14单 │ 4人姓名+工位) │
├──────────┴──────────┴──────────┴──────────────────────┤
│ │
│ 在制任务明细(进度百分比) │ ← 左侧大表
│ 生产订单│销售订单│物料名称│物料编码│工序│分配数│完成数│进度% │
│ │
├──────────────────────────┬───────────────────────────┤
│ 加急任务列表 │ 关键工序任务明细 │ ← 右上两表
│ 订单│合同│物料│编码│工序 │ 订单│合同│物料│编码│工序 │
│ 分配数│加急数│指派对象 │ 分配数│加急数│指派对象 │
├──────────────────────────┼───────────────────────────┤
│ 正在开工任务 │ 报工运行(新增) │ ← 右下两表
│ 订单│合同│物料│编码│工序 │ 设备│状态│运行时长│报工数 │
│ 分配数 │ 运行/待机/停机 仪表盘 │
└──────────────────────────┴───────────────────────────┘
```
---
## 二、技术方案
### 2.1 技术栈选型
| 层级 | 技术 | 版本 | 用途 |
|------|------|------|------|
| 前端框架 | Vue 3 (Composition API) | ^3.5.12 | 核心框架,`<script setup>` 语法 |
| 构建工具 | Vite | ^5.4.10 | 开发/构建 |
| 路由 | Vue Router | ^4.4.5 | Hash 模式路由 |
| 状态管理 | Pinia | ^2.2.5 | 全局状态(用户信息/加载态) |
| UI 组件库 | Element Plus + TDesign + DataV-Vue3 | - | 消息提示 + 加载组件 + 大屏滚动表 |
| 图表 | ECharts | ^6.0.0 | 备用图表能力 |
| HTTP 客户端 | Axios | ^1.7.7 | 后端接口调用 |
| Cookie | js-cookie | ^3.0.5 | 用户身份读取 |
| CSS 预处理 | SCSS/Sass | ^1.80.5 | 样式编写 |
### 2.2 整体架构
```
┌──────────────────────────────────────────────────────┐
│ 浏览器 (Browser) │
│ ┌────────────────────────────────────────────────┐ │
│ │ Vue 3 SPA (单页应用) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │ │
│ │ │ 路由层 │ │ 状态层 │ │ 视图层 │ │ │
│ │ │ Router │ │ Pinia │ │ index.vue │ │ │
│ │ │ / → │ │ AppData │ │ (大屏看板) │ │ │
│ │ │ index │ │ │ │ │ │ │
│ │ └──────────┘ └──────────┘ └──────┬───────┘ │ │
│ │ │ │ │
│ │ ┌──────────────────────────────────┐│ │ │
│ │ │ 工具层 (Plugins) ││ │ │
│ │ │ CreateData() → 构造请求参数 ││ │ │
│ │ │ ExecDatabase() → 执行数据库查询 ││ │ │
│ │ │ request.js → Axios 封装 ││ │ │
│ │ └──────────────────┬───────────────┘│ │ │
│ └─────────────────────┼─────────────────┘ │ │
└────────────────────────┼─────────────────────────────┘
│ HTTP POST
┌──────────────────────────────────────────────────────┐
│ 中间层 (MESCommonBase.ashx) │
│ 接收 JSON → 解析 type/name/param → 调用存储过程 │
│ 返回 JSON: { data: [...] } 或 { data: { result: [...] } } │
└────────────────────┬─────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ 数据库 (SQL Server) │
│ 存储过程: │
│ - 机加设备生产运营看板_加急任务查询 │
│ - 机加设备生产运营看板_机加在制查询 │
│ - 机加设备生产运营看板_机加在制查询2 │
│ - 机加设备生产运营看板_报工运行查询 (新增) │
│ 涉及表: 生产订单表 / 工序表 / 人员表 / 设备表 / 报工表... │
└──────────────────────────────────────────────────────┘
```
### 2.3 API 通信协议
请求格式type=11 表示调用存储过程):
```json
{
"type": "11",
"name": "存储过程名称",
"param": "[]",
"pageSize": null,
"pageList": null,
"UserID": "username_from_cookie",
"ModularID": ""
}
```
响应格式(两种可能结构):
```json
// 格式A
{ "data": [ { "字段1": "值1", "字段2": "值2" } ] }
// 格式B
{ "data": { "result": [ { "字段1": "值1", "字段2": "值2" } ] } }
```
---
## 三、技术路线(开发阶段)
| 阶段 | 内容 | 预计工时 | 产出 |
|------|------|----------|------|
| **阶段1基础设施** | 项目初始化、依赖安装、路由/状态管理配置、API 层搭建 | 0.5天 | 项目可启动 |
| **阶段2数据库层** | 确认表结构、编写3个核心存储过程(SP1-3)、报工运行SP4、接口联调 | 1天 | 4个SP可返回数据 |
| **阶段3页面开发** | 看板布局、汇总卡片、4个数据表格组件、报工运行面板开发 | 1.5天 | 页面渲染完成 |
| **阶段4数据对接** | 前后端联调、真实数据替换 Mock 数据、异常处理 | 1天 | 真实数据展示 |
| **阶段5样式与优化** | 样式布局调整、大屏缩放适配、定时刷新、性能优化 | 0.5天 | 多分辨率适配 |
| **阶段6测试与部署** | 功能测试、兼容性测试、生产打包部署 | 0.5天 | 上线运行 |
---
## 四、需要完成的具体模块
### 4.1 模块总览
```
机加设备生产运营看板(二)
├── M1: 页面框架模块
│ ├── 页头Logo + 标题 + 实时时钟)
│ ├── 自适应缩放引擎
│ └── 全局加载态
├── M2: 汇总卡片模块
│ ├── 在制任务汇总卡片
│ ├── 加急任务汇总卡片
│ ├── 关键工序任务汇总卡片
│ └── 缺勤人员卡片
├── M3: 在制任务明细模块(大表)
│ └── dv-scroll-board 滚动表格 + 进度条
├── M4: 加急任务列表模块
│ └── dv-scroll-board 滚动表格
├── M5: 关键工序任务明细模块
│ └── dv-scroll-board 滚动表格
├── M6: 正在开工任务模块
│ └── dv-scroll-board 滚动表格
├── M7: 数据服务模块
│ ├── API 请求封装
│ ├── 参数构造CreateData
│ ├── 响应解析getResponseRows
│ └── 数据映射applyUrgentData / applyInProcessData / applyInProcessData2
└── M8: 报工运行模块(新增)
├── 设备运行状态总览(运行/待机/停机数量统计)
├── 设备报工明细列表dv-scroll-board 或 ECharts 仪表盘)
└── 数据存储过程 SP4
```
### 4.2 各模块详细说明
#### M1: 页面框架模块
| 属性 | 说明 |
|------|------|
| 文件 | `src/views/index.vue` `<template>` 头部区域 |
| 功能 | 品牌 Logo 展示、看板标题、实时数字时钟(秒级刷新) |
| 关键逻辑 | `refreshClock()` 每秒更新 `currentTime` ref |
| 自适应 | `setScale()` 通过 CSS transform 等比缩放,基于 1920×1080 设计稿 |
#### M2: 汇总卡片模块
| 卡片 | 数据字段 | 数据来源 | 图标 | 颜色 |
|------|----------|----------|------|------|
| 在制任务汇总 | `summaryCards[0].value` | SP3 首行 `总数` | 剪贴板 | 绿色 #28d961 |
| 加急任务汇总 | `summaryCards[1].value` | SP1 首行 `加急订单总数` | 火箭 | 蓝色 #09a8ff |
| 关键工序任务 | `summaryCards[2].value` | 静态/待对接 | 盾牌 | 紫色 #7459ef |
| 缺勤人员 | `summaryCards[3].value` + `people[]` | 数据接口对接中 | 用户 | 橙色 #ff951c |
#### M3: 在制任务明细模块(核心大表)
| 属性 | 说明 |
|------|------|
| 展示形式 | DataV `dv-scroll-board` 滚动表格12行可见单页轮播 |
| 列定义 | 生产订单、销售订单、物料名称、物料编码、工序名称、分配数量、完成数量、进度百分比% |
| 列宽 | [126, 130, 116, 136, 88, 80, 80, 158] |
| 进度列 | 自定义 HTML 渲染:进度条 + 百分比数字 |
| 最小行数 | 18行不足自动扩展填充 |
| 数据源 | SP3 `机加设备生产运营看板_机加在制查询2` |
#### M4: 加急任务列表模块
| 属性 | 说明 |
|------|------|
| 展示形式 | DataV `dv-scroll-board`8行可见 |
| 列定义 | 生产订单、销售订单、物料名称、物料编码、工序名称、分配数量、加急数量、指派对象 |
| 列宽 | [122, 132, 132, 152, 92, 82, 92, 82, 94] |
| 最小行数 | 12行 |
| 数据源 | SP1 `机加设备生产运营看板_加急任务查询` |
#### M5: 关键工序任务明细模块
| 属性 | 说明 |
|------|------|
| 展示形式 | 同 M4共用 `makeTaskBoard` 函数) |
| 数据源 | **待对接**(当前为静态 Mock 数据) |
#### M6: 正在开工任务模块
| 属性 | 说明 |
|------|------|
| 展示形式 | DataV `dv-scroll-board`8行可见 |
| 列定义 | 生产订单、销售订单、物料名称、物料编码、工序名称、分配数量 |
| 列宽 | [160, 170, 140, 176, 140, 128] |
| 最小行数 | 12行 |
| 数据源 | SP2 `机加设备生产运营看板_机加在制查询` |
#### M7: 数据服务模块
| 组件 | 文件 | 说明 |
|------|------|------|
| CreateData() | `src/utils/curdVue3.js` | 构造 MES API 标准请求体 JSON |
| ExecDatabase() | `src/utils/curdVue3.js` | 发送 POST 请求URL 为空,使用 baseURL |
| request.js | `src/utils/request.js` | Axios 实例封装baseURL = `window.g.API_WEB_URL + '/submit/MESCommonBase.ashx'` |
| getResponseRows() | `src/views/index.vue` | 解析两种响应格式,提取数据行数组 |
| queryDashboardData() | `src/views/index.vue` | 统一查询入口:构造参数 → 请求 → 解析 → 应用数据 |
#### M8: 报工运行模块(新增)
| 属性 | 说明 |
|------|------|
| 展示形式 | ① 顶部状态统计行(运行中 / 待机 / 停机 数量)② ECharts 仪表盘 或 DataV 滚动表格展示设备明细 |
| 列定义(表格方案) | 设备编号、设备名称、运行状态(带颜色标识)、当日运行时长(h)、当日报工数量、最近报工时间 |
| 数据源 | SP4 `机加设备生产运营看板_报工运行查询` |
| 布局位置 | 右侧下方区域(可替换/合并原关键工序或缺勤人员区域) |
| 刷新 | 随页面加载调用,后续可扩展定时刷新 |
---
## 五、模块功能流程图
### 5.1 页面加载数据流程
```mermaid
flowchart TD
A["页面 onMounted"] --> B["refreshClock() 启动时钟"]
A --> C["setScale() 计算缩放比例"]
A --> D["searchDashboardData() 请求数据"]
D --> E["searchUrgentData()"]
D --> F["searchInProcessData()"]
D --> G["searchInProcessData2()"]
E --> E1["queryDashboardData('机加设备生产运营看板_加急任务查询', applyUrgentData)"]
F --> F1["queryDashboardData('机加设备生产运营看板_机加在制查询', applyInProcessData)"]
G --> G1["queryDashboardData('机加设备生产运营看板_机加在制查询2', applyInProcessData2)"]
E1 --> H["CreateData('11', procedureName, [])"]
H --> I["构造 JSON 请求体: type=11, name=SP名, param=[]"]
I --> J["ExecDatabase(JSON) → Axios POST"]
J --> K["MESCommonBase.ashx"]
K --> L["SQL Server 执行存储过程"]
L --> M["返回 JSON 响应"]
M --> N["getResponseRows(response)"]
N --> N1{响应结构判断}
N1 -->|"response.data 是数组"| N2["返回 response.data"]
N1 -->|"response.data.result 是数组"| N3["返回 response.data.result"]
N1 -->|"都不是"| N4["返回 []"]
N2 --> O["applyXxxData(rows) 数据映射"]
N3 --> O
O --> P["更新响应式数据,页面自动渲染"]
```
### 5.2 API 请求构造流程
```mermaid
flowchart LR
A["调用方传入 type=11, name, data=[]"] --> B["CreateData(type, name, data)"]
B --> C{type === '7' ?}
C -->|"是(批量保存)"| D["遍历 data 按 pageSize 格式化"]
C -->|"否(查询)"| E["遍历 data 构造参数数组"]
D --> F["param_Str = data(原样)"]
E --> G["param_Str = JSON.stringify(param_array)"]
F --> H["构造请求对象 obj"]
G --> H
H --> I["obj = { type, name, param, pageSize, pageList, UserID, ModularID }"]
I --> J["JSON.stringify(obj) 返回字符串"]
J --> K["ExecDatabase(jsonStr)"]
K --> L["axios.post(baseURL + '', jsonStr)"]
```
### 5.3 数据映射流程(以加急任务为例)
```mermaid
flowchart TD
A["SP返回原始行数据"] --> B["rows = [{ 订单编号, 合同号, 物料描述, 物料编号, 计划数量, 加急数量, 工序名称, 指派对象, 加急订单总数 }]"]
B --> C["applyUrgentData(rows)"]
C --> D["first = rows[0]"]
D --> E{"first.加急订单总数 !== undefined ?"}
E -->|是| F["summaryCards[1].value = first.加急订单总数"]
E -->|否| G["保持原值不变"]
F --> H["urgentRows.value = rows.map(row => 字段映射)"]
G --> H
H --> I["映射规则:
订单编号 → productionOrder
合同号 → salesOrder
物料描述 → materialName
物料编号 → materialCode
计划数量 → assignQty
加急数量 → urgentQty
工序名称 → processName
指派对象 → assignee
prevPassQty → ''(空)"]
I --> J["urgentBoardConfig(computed) 自动响应更新"]
J --> K["dv-scroll-board 重新渲染"]
```
### 5.4 自适应缩放流程
```mermaid
flowchart TD
A["页面加载 / 窗口 resize"] --> B["setScale()"]
B --> C["getScale()"]
C --> D["x = window.innerWidth / 1920"]
C --> E["y = window.innerHeight / 1080"]
D --> F["style.transform = scaleY(y) scaleX(x) translate(-50%, -50%)"]
E --> F
F --> G["页面等比缩放填满屏幕"]
```
---
## 六、涉及的数据库表与存储过程
### 6.1 存储过程清单
#### SP1: `机加设备生产运营看板_加急任务查询`
| 属性 | 说明 |
|------|------|
| 用途 | 查询当日加急/紧急生产任务 |
| 调用方式 | type=11, 无参数 |
| 返回字段 | `订单编号`, `合同号`, `物料描述`, `物料编号`, `计划数量`, `加急数量`, `工序名称`, `指派对象`, `加急订单总数` |
| 备注 | 首行 `加急订单总数` 用于更新汇总卡片值 |
#### SP2: `机加设备生产运营看板_机加在制查询`
| 属性 | 说明 |
|------|------|
| 用途 | 查询当前正在开工的生产任务 |
| 调用方式 | type=11, 无参数 |
| 返回字段 | `订单号`, `合同号`, `零件名称`, `零件编码`, `分配数`, `工序名称`, `指派对象` |
| 备注 | 用于"正在开工任务"表格 |
#### SP3: `机加设备生产运营看板_机加在制查询2`
| 属性 | 说明 |
|------|------|
| 用途 | 查询在制任务明细(含完成数量和进度) |
| 调用方式 | type=11, 无参数 |
| 返回字段 | `订单号`, `合同号`, `零件名称`, `零件编码`, `分配数`, `工序名称`, `指派对象`, `完成数量`, `总数` |
| 备注 | 首行 `总数` 更新在制汇总卡片;每行计算 `progress = (完成数量/分配数)*100` |
#### SP4: `机加设备生产运营看板_报工运行查询`(待开发)
| 属性 | 说明 |
|------|------|
| 用途 | 查询各设备当日运行状态和报工数据 |
| 调用方式 | type=11, 无参数 |
| 返回字段 | `设备编号`, `设备名称`, `运行状态`, `当日运行时长(h)`, `当日报工数量`, `最近报工时间` |
| 备注 | 用于新增"报工运行"面板 |
### 6.2 推断涉及的数据库表
基于存储过程返回字段和业务逻辑,推断涉及以下核心表:
| 表名(推测) | 字段 | 用途 |
|------|------|------|
| `ProductionOrder` (生产订单表) | 订单编号, 合同号, 计划数量, 加急标识 | 生产任务主表 |
| `Material` (物料表) | 物料编号, 物料描述/零件名称, 零件编码 | 物料/零件信息 |
| `ProcessRouting` (工序工艺表) | 工序名称, 工序类型, 是否关键工序 | 工序定义 |
| `WorkOrderOperation` (工单工序表) | 订单号, 工序名称, 分配数, 完成数量, 指派对象 | 工单执行明细 |
| `Personnel` (人员表) | 姓名, 工位, 出勤状态 | 人员信息 |
| `AttendanceRecord` (考勤记录表) | 人员ID, 日期, 出勤状态 | 考勤记录 |
| `Equipment` (设备表) | 设备编号, 设备名称, 设备类型, 所属工位 | 设备主数据(报工运行用) |
| `EquipmentRuntime` (设备运行记录表) | 设备ID, 运行状态(运行/待机/停机), 开始时间, 结束时间 | 设备运行日志(报工运行用) |
| `WorkReportRecord` (报工记录表) | 设备ID/工单ID, 报工数量, 报工时间, 操作员 | 报工数据(报工运行用) |
> **注意**: 以上表名为业务推断,需与数据库实际结构核对确认。
### 6.3 存储过程实现建议模板
```sql
-- 示例机加设备生产运营看板_加急任务查询
CREATE PROCEDURE [dbo].[_加急任务查询]
AS
BEGIN
SET NOCOUNT ON;
-- 加急订单总数
SELECT COUNT(DISTINCT PO.OrderID) AS
FROM ProductionOrder PO
WHERE PO.IsUrgent = 1
AND PO.OrderDate = CAST(GETDATE() AS DATE);
-- 加急任务明细
SELECT
PO.OrderNo AS ,
PO.ContractNo AS ,
M.MaterialDesc AS ,
M.MaterialCode AS ,
WO.PlannedQty AS ,
WO.UrgentQty AS ,
PR.ProcessName AS ,
WO.Assignee AS
FROM ProductionOrder PO
INNER JOIN Material M ON PO.MaterialID = M.MaterialID
INNER JOIN WorkOrderOperation WO ON PO.OrderID = WO.OrderID
INNER JOIN ProcessRouting PR ON WO.ProcessID = PR.ProcessID
WHERE PO.IsUrgent = 1
AND PO.OrderDate = CAST(GETDATE() AS DATE)
ORDER BY WO.UrgentQty DESC;
END
```
```sql
-- 示例机加设备生产运营看板_报工运行查询
CREATE PROCEDURE [dbo].[_报工运行查询]
AS
BEGIN
SET NOCOUNT ON;
SELECT
E.EquipCode AS ,
E.EquipName AS ,
ER.RunStatus AS ,
ISNULL(SUM(DATEDIFF(MINUTE, ER.StartTime, ISNULL(ER.EndTime, GETDATE()))) / 60.0, 0) AS ,
ISNULL(WRR.ReportQty, 0) AS ,
WRR.LastReportTime AS
FROM Equipment E
LEFT JOIN EquipmentRuntime ER
ON E.EquipID = ER.EquipID
AND ER.StartTime >= CAST(GETDATE() AS DATE)
LEFT JOIN (
SELECT EquipID, SUM(ReportQty) AS ReportQty, MAX(ReportTime) AS LastReportTime
FROM WorkReportRecord
WHERE ReportTime >= CAST(GETDATE() AS DATE)
GROUP BY EquipID
) WRR ON E.EquipID = WRR.EquipID
GROUP BY E.EquipCode, E.EquipName, ER.RunStatus, WRR.ReportQty, WRR.LastReportTime
ORDER BY E.EquipCode;
END
```
---
## 七、今日详细工作步骤
### 上午09:00 - 12:00
| 步骤 | 任务 | 详细操作 | 产出 | 预计用时 |
|------|------|----------|------|----------|
| **Step 1** | 环境确认 | ① `npm install` 安装依赖;② `npm run dev` 验证项目可启动;③ 确认 `/config.js` 正常加载;④ 浏览器控制台检查无报错 | 项目正常启动 | 30min |
| **Step 2** | 数据库调研 | ① 连接 SQL Server 数据库;② 确认上文推断的 6 张表实际表名和字段;③ 导出相关表结构文档 | 表结构文档 | 1h |
| **Step 3** | SP1 编写与测试 | ① 编写 `机加设备生产运营看板_加急任务查询` 存储过程;② 在 SSMS 中执行验证数据正确性;③ 调优查询性能(添加索引) | SP1 可用 | 1h |
| **Step 4** | SP2 编写与测试 | ① 编写 `机加设备生产运营看板_机加在制查询` 存储过程;② SSMS 执行验证 | SP2 可用 | 30min |
### 下午13:30 - 18:00
| 步骤 | 任务 | 详细操作 | 产出 | 预计用时 |
|------|------|----------|------|----------|
| **Step 5** | SP3 编写与测试 | ① 编写 `机加设备生产运营看板_机加在制查询2` 存储过程;② SSMS 执行验证 | SP3 可用 | 30min |
| **Step 6** | 前后端联调SP1-3 | ① 通过 MESCommonBase.ashx 实际调用 3 个存储过程;② 用浏览器 DevTools Network 面板检查请求/响应;③ 验证响应 JSON 结构;④ 如有结构不匹配,调整 `getResponseRows()` | 3个SP前后端调通 | 1h |
| **Step 7** | 真实数据验证 | ① 确认汇总卡片数字正确;② 确认表格数据显示正确;③ 确认进度百分比计算正确;④ 处理空数据/异常情况 | 页面展示真实数据 | 1h |
| **Step 8** | 样式布局修改 | ① 根据用户反馈调整看板整体布局(卡片尺寸/间距/位置);② 修改表格列宽适配真实数据长度;③ 调整字体大小、颜色、对比度提升可读性;④ 优化表格行高和滚动速率;⑤ 调整缺勤人员卡片内边距和字体样式;⑥ 页面背景/边框/阴影等视觉细节微调 | 样式布局验收通过 | 1.5h |
| **Step 9** | 报工运行功能开发 | ① 确认报工运行数据来源(设备运行状态表/报工记录表);② 编写报工运行存储过程,返回:设备编号、设备名称、运行状态(运行/待机/停机)、当日运行时长(h)、当日报工数量、最近报工时间;③ 新增报工运行面板组件(建议 ECharts 仪表盘 + 运行状态列表或 DataV 滚动表格);④ 调整页面布局容纳新面板(右侧下方区域);⑤ 前后端联调验证数据正确性 | 报工运行面板展示真实数据 | 1.5h |
| **Step 10** | 整体测试 | ① 全流程回归测试(含报工运行功能);② 异常场景测试(接口超时、空数据、网络断开);③ 多分辨率测试1920×1080 / 1366×768 / 2560×1440④ 样式布局走查确认 | 测试报告 | 30min |
---
## 八、当前状态与待办事项
### 已完成 ✅
- [x] 项目框架搭建Vue3 + Vite + Pinia + Router
- [x] 页面布局与样式1920×1080 大屏设计)
- [x] 4 个滚动表格组件集成DataV-Vue3
- [x] 自适应缩放引擎
- [x] 实时时钟
- [x] API 通信层封装CreateData / ExecDatabase / request.js
- [x] 数据映射逻辑applyUrgentData / applyInProcessData / applyInProcessData2
- [x] public/config.js 加载路径修复(本次修复)
### 进行中 🔄
- [ ] 数据库表结构确认
- [ ] SP1~SP3 存储过程编写
- [ ] 前后端联调
### 待开发 ⏳
- [ ] SP4 报工运行查询存储过程
- [ ] M8 报工运行面板组件开发
- [ ] 页面样式布局调整优化
- [ ] 定时自动刷新机制setInterval 30s
- [ ] 错误处理优化(接口失败重试、友好提示)
---
## 九、关键风险与注意事项
| 风险 | 影响 | 应对措施 |
|------|------|----------|
| 数据库实际表结构未知 | SP 无法编写 | Step 2 优先确认表结构 |
| MESCommonBase.ashx 响应格式不确定 | 数据解析失败 | `getResponseRows` 已兼容两种格式,需实测确认 |
| 内网 API 地址 `192.168.2.92:10101` 不可达 | 无法联调 | 确认网络连通性,或使用代理 |
| 存储过程性能差 | 大屏卡顿 | 添加索引,限制返回行数 |
| 生产数据敏感 | 数据安全 | 确认仅查询权限,不涉及写操作 |