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,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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

Binary file not shown.

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 'datav-vue3'
// 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,686 @@
<template>
<div
class="production-dashboard"
:style="{ width: `${style.width}px`, height: `${style.height}px`, transform: style.transform }"
>
<div class="dashboard-shell">
<header class="dashboard-header">
<div class="brand">
<img class="brand-logo" :src="clientLogoUrl" alt="RENLY" />
</div>
<h1>生产任务综合看板</h1>
<div class="current-time">{{ currentTime }}</div>
</header>
<main class="dashboard-content">
<section class="summary-grid first-row">
<article class="panel kpi-card rate-card">
<div class="ring-wrap">
<div class="progress-ring">{{complete.completeset}}%</div>
</div>
<div class="rate-info">
<div class="card-title">按期齐套率</div>
<div class="mini-row"><span>未齐套数</span><b>{{ complete.untasknum }}</b></div>
<div class="mini-row"><span>应齐套数</span><b>{{ complete.tasknum }}</b></div>
</div>
</article>
<article v-for="item in topStats" :key="item.title" class="panel kpi-card top-stat-card">
<div class="icon-slot">
<img :src="item.icon" :alt="item.title" />
</div>
<div class="stat-info">
<div class="card-title">{{ item.title }}</div>
<div class="stat-number">{{ item.value }}</div>
</div>
</article>
</section>
<section class="summary-grid second-row">
<article class="panel status-card split-number-card">
<div class="icon-slot wide">
<img :src="svgIcons.purchaseDelay" alt="采购未及时到货" />
</div>
<div class="status-info">
<div class="card-title">采购未及时到货</div>
<div class="split-number"><em>8</em><span>/46</span></div>
</div>
</article>
<article class="panel status-card">
<div class="icon-slot gear">
<img :src="svgIcons.machineDelay" alt="机加工未及时开工" />
</div>
<div class="status-info">
<div class="card-title">机加未及时开工</div>
<div class="orange-number">{{ notintimenum }}</div>
</div>
</article>
<article class="panel status-card rate-small-card">
<div class="small-ring">87.5%</div>
<div class="status-info">
<div class="card-title">质检及时率</div>
<div class="mini-row"><span>已检数</span><b>84</b></div>
<div class="mini-row"><span>未检数</span><b>12</b></div>
<div class="mini-row"><span>全部待检数</span><b>96</b></div>
</div>
</article>
<article class="panel status-card">
<div class="icon-slot house">
<img :src="svgIcons.stockDelay" alt="未及时入库" />
</div>
<div class="status-info">
<div class="card-title">未及时入库</div>
<div class="orange-number">11</div>
</div>
</article>
<article class="panel status-card urgent-status-card">
<div class="icon-slot alarm">
<img :src="svgIcons.urgent" alt="加急件" />
</div>
<div class="status-info">
<div class="card-title">加急件</div>
<div class="red-number">{{ Urgentnum }}</div>
</div>
</article>
</section>
<section class="table-area">
<article class="panel table-panel">
<div class="panel-caption">未按期齐套任务</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th v-for="column in leftColumns" :key="column.key">{{ column.label }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in kitRows" :key="`${row.salesOrder}-${index}`">
<td v-for="column in leftColumns" :key="column.key">{{ row[column.key] }}</td>
</tr>
</tbody>
</table>
</div>
</article>
<article class="panel table-panel">
<div class="panel-caption">加急件</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th v-for="column in urgentColumns" :key="column.key">{{ column.label }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in urgentRows" :key="`${row.salesOrder}-${index}`">
<td v-for="column in urgentColumns" :key="column.key">{{ row[column.key] }}</td>
</tr>
</tbody>
</table>
</div>
</article>
</section>
</main>
</div>
</div>
</template>
<script setup>
import { computed, inject, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
const CreateData = inject('CreateData')
const ExecDatabase = inject('ExecDatabase')
const tableData = ref([])
const now = ref(new Date('2026-05-06 10:10:10'))
let timer = null
let resizeHandler = null
let Urgentnum = null
let notintimenum = null
let complete = reactive({
completeset:100,
tasknum:0,
untasknum:0
})
const style = reactive({
width: 1920,
height: 1080,
transform: 'scaleY(1) scaleX(1) translate(-50%, -50%)'
})
const setScale = () => {
const scale = getScale()
style.transform = 'scaleY(' + scale.y + ') scaleX(' + scale.x + ') translate(-50%, -50%)'
}
const getScale = () => {
const w = window.innerWidth / style.width
const h = window.innerHeight / style.height
return { x: w, y: h }
}
const pad = (value) => String(value).padStart(2, '0')
const currentTime = computed(() => {
const date = now.value
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
})
const clientLogoUrl = './public/ClientLogo.png'
const svgIcons = {
pendingSchedule: './public/svg/待排产任务.svg',
salesOrder: './public/svg/待处理销售订单.svg',
bom: './public/svg/工艺BOM待完成.svg',
purchaseDelay: './public/svg/采购未及时到货.svg',
machineDelay: './public/svg/机加未及时开工.svg',
stockDelay: './public/svg/未及时入库.svg',
urgent: './public/svg/加急件.svg',
}
onMounted(() => {
startClock()
setScale()
resizeHandler = () => {
setScale()
}
window.addEventListener('resize', resizeHandler)
searchDashboardData()
})
onUnmounted(() => {
destroyDashboard()
})
const searchDashboardData = () => {
searchCompleteSetData()
searchNoProcessData()
searchUrgentData()
searchUnCompleteData()
searchNotInTimeData()
}
const searchCompleteSetData = () => {
queryDashboardData('生产任务综合看板_按期齐套率', applyCompleteSetData)
}
const searchNoProcessData = () => {
queryDashboardData('生产任务综合看板_未排产无工艺查询', applyNoProcessData)
}
const searchUrgentData = () => {
queryDashboardData('生产任务综合看板_加急任务查询', applyUrgentData)
}
const searchNotInTimeData = () => {
queryDashboardData('生产任务综合看板_未及时开工查询', applyNotInTimeData)
}
const searchUnCompleteData = () => {
queryDashboardData('生产任务综合看板_未按期齐套任务', applyUnCompleteData)
}
const applyCompleteSetData = rows => {
complete.completeset = rows[0].齐套率
complete.untasknum = rows[0].未齐套任务数
complete.tasknum = rows[0].总任务数
}
const applyNoProcessData = rows => {
topStats[0].value = rows[0].未排产任务数
topStats[2].value = rows[0].无生产工艺计划数
}
const applyNotInTimeData = rows => {
notintimenum = rows[0].未及时开工总数
}
const applyUrgentData = rows => {
urgentRows = rows.map( item => ({
salesOrder: item.合同号,
productionOrder: item.订单编号,
productName: item.物料描述,
productCode: item.物料编号,
property: item.自制件属性,
planQty: item.计划数量,
releaseDate: item.下达日期,
startDate: item.计划开始,
finishDate: item.计划完成,
}))
Urgentnum = rows[0].加急订单总数
}
const applyUnCompleteData = rows => {
kitRows = rows.map( item => ({
productionOrder: item.订单编号,
productName: item.物料描述,
productCode: item.物料编号,
property: item.自制件属性,
planQty: item.计划数量,
releaseDate: item.下达日期,
startDate: item.计划开始,
finishDate: item.计划完成,
planNo: item.合同号,
stockQty: '0',
kitDate: item.计划开始,
pickStatus: item.拣配状态 == 1 ?'已拣配':'未拣配',
issueStatus: item.发料状态 == 2 ?'发料缺件':'未发料',
}))
}
const queryDashboardData = (procedureName, applyData) => {
let data
data = CreateData('11', procedureName, [])
ExecDatabase(data).then(response => {
const rows = getResponseRows(response)
tableData.value = rows
applyData(rows)
})
}
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 startClock = () => {
timer = window.setInterval(() => {
now.value = new Date(now.value.getTime() + 1000)
}, 1000)
}
const destroyDashboard = () => {
if (timer) {
window.clearInterval(timer)
timer = null
}
if (resizeHandler) {
window.removeEventListener('resize', resizeHandler)
resizeHandler = null
}
}
const topStats = [
{ title: '待排产任务', value: 18, icon: svgIcons.pendingSchedule },
{ title: '待处理销售订单', value: 12, icon: svgIcons.salesOrder },
{ title: '工艺BOM待完成', value: 9, icon: svgIcons.bom },
]
const leftColumns = [
{ label: '销售订单', key: 'salesOrder' },
{ label: '计划号', key: 'planNo' },
{ label: '生产订单', key: 'productionOrder' },
{ label: '产品名称', key: 'productName' },
{ label: '产品编码', key: 'productCode' },
{ label: '计划数', key: 'planQty' },
{ label: '入库数', key: 'stockQty' },
{ label: '预计齐套', key: 'kitDate' },
{ label: '拣配状态', key: 'pickStatus' },
{ label: '发料状态', key: 'issueStatus' },
]
const urgentColumns = [
{ label: '销售订单', key: 'salesOrder' },
{ label: '生产订单', key: 'productionOrder' },
{ label: '产品名称', key: 'productName' },
{ label: '产品编码', key: 'productCode' },
{ label: '属性', key: 'property' },
{ label: '计划数', key: 'planQty' },
{ label: '下达日期', key: 'releaseDate' },
{ label: '计划开始', key: 'startDate' },
{ label: '计划完成', key: 'finishDate' },
]
let kitRows = Array.from({ length: 9 }, () => ({
salesOrder: 'SH260014',
planNo: '54023',
productionOrder: '1075',
productName: '垫铁+行程开关部装',
productCode: '213-YGLQ-DF150V1.2.18',
planQty: '1',
stockQty: '0',
kitDate: '2026-01-21',
pickStatus: '已拣配',
issueStatus: '未发料',
}))
let urgentRows = Array.from({ length: 10 }, () => ({
salesOrder: 'SH260014',
productionOrder: '1075',
productName: '垫铁+行程开关部装',
productCode: '213-YGLQ-DF150V1.2.18',
property: '机加',
planQty: '1',
releaseDate: '2026-01-21',
startDate: '2026-01-21',
finishDate: '2026-01-21',
}))
</script>
<style scoped lang="scss">
* {
box-sizing: border-box;
}
.production-dashboard {
position: fixed;
left: 50%;
top: 50%;
width: 1920px;
height: 1080px;
transform-origin: 0 0;
transition: 0.3s;
overflow: hidden;
color: #f3f9ff;
font-family: 'Microsoft YaHei', Arial, sans-serif;
background-color: #07001f;
}
.dashboard-shell {
width: 100%;
height: 100%;
padding: 0px 15px 15px 15px;
}
.dashboard-header {
position: relative;
display: grid;
grid-template-columns: 336px 1fr 442px;
align-items: center;
height: 84px;
border-bottom: 1px solid rgba(21, 162, 240, 0.62);
}
.logo-slot {
width: 230px;
height: 45px;
margin-left: 8px;
border: 1px solid rgba(100, 196, 255, 0.48);
background: rgba(255, 255, 255, 0.05);
box-shadow: inset 0 0 14px rgba(83, 181, 255, 0.25);
}
.dashboard-header h1 {
margin: 0;
text-align: center;
font-size: 55px;
font-weight: 900;
letter-spacing: 0.08em;
}
.current-time {
padding-right: 38px;
text-align: right;
font-size: 22px;
color: #eef7ff;
}
.dashboard-content {
display: grid;
grid-template-rows: 145px 145px 1fr;
gap: 17px;
height: calc(100% - 84px);
padding-top: 12px;
}
.summary-grid {
display: grid;
gap: 17px;
}
.first-row {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
.second-row {
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
}
.panel {
position: relative;
border: 1px solid rgba(29, 153, 229, 0.78);
box-shadow:
inset 0 0 18px rgba(27, 130, 214, 0.24),
0 0 10px rgba(1, 132, 220, 0.18);
}
.panel::before {
position: absolute;
inset: -1px;
content: '';
pointer-events: none;
background:
linear-gradient(90deg, #2bd8ff 0 14px, transparent 14px) left top / 14px 2px no-repeat,
linear-gradient(180deg, #2bd8ff 0 14px, transparent 14px) left top / 2px 14px no-repeat,
linear-gradient(270deg, #2bd8ff 0 14px, transparent 14px) right top / 14px 2px no-repeat,
linear-gradient(180deg, #2bd8ff 0 14px, transparent 14px) right top / 2px 14px no-repeat,
linear-gradient(90deg, #2bd8ff 0 14px, transparent 14px) left bottom / 14px 2px no-repeat,
linear-gradient(0deg, #2bd8ff 0 14px, transparent 14px) left bottom / 2px 14px no-repeat,
linear-gradient(270deg, #2bd8ff 0 14px, transparent 14px) right bottom / 14px 2px no-repeat,
linear-gradient(0deg, #2bd8ff 0 14px, transparent 14px) right bottom / 2px 14px no-repeat;
}
.panel::after {
position: absolute;
inset: 0;
content: '';
pointer-events: none;
box-shadow: inset 0 0 12px rgba(43, 216, 255, 0.12);
}
.kpi-card,
.status-card {
display: flex;
align-items: center;
justify-content: center;
gap: 70px;
min-width: 0;
}
.card-title,
.panel-caption {
color: #8cd3ff;
font-size: 20px;
font-weight: 600;
text-align: center;
letter-spacing: 0.03em;
}
.rate-card {
justify-content: flex-start;
padding-left: 42px;
box-sizing: border-box;
}
.ring-wrap,
.progress-ring,
.small-ring {
display: flex;
align-items: center;
justify-content: center;
}
.progress-ring {
width: 110px;
height: 110px;
min-width: 58px;
min-height: 58px;
border-radius: 50%;
font-size: 22px;
font-weight: 900;
background:
radial-gradient(circle, #08234f 53%, transparent 55%),
conic-gradient(#1ce6ff 0deg 333deg, rgba(47, 84, 131, 0.8) 333deg 360deg);
box-shadow: 0 0 15px rgba(0, 208, 255, 0.65);
}
.rate-info {
width: 154px;
}
.mini-row {
display: flex;
justify-content: space-between;
margin-top: 8px;
color: #e7f6ff;
font-size: 15px;
line-height: 1;
}
.mini-row b {
color: #ffffff;
}
.icon-slot {
display: flex;
align-items: center;
justify-content: center;
width: 110px;
height: 110px;
min-width: 60px;
min-height: 60px;
}
.icon-slot img {
width: 76%;
height: 76%;
object-fit: contain;
display: block;
}
.icon-slot.wide {
width: 108px;
}
.icon-slot.gear,
.icon-slot.house,
.icon-slot.alarm {
border-radius: 50%;
}
.stat-info,
.status-info {
min-width: 0;
text-align: center;
}
.stat-number {
margin-top: 5px;
font-size: 58px;
line-height: 1;
font-weight: 900;
color: #ffffff;
text-shadow: 0 0 8px rgba(255, 255, 255, 0.36);
}
.split-number,
.orange-number,
.red-number {
margin-top: 2px;
font-size: 52px;
line-height: 1;
font-weight: 900;
}
.split-number em {
color: #ff2026;
font-style: normal;
}
.split-number span {
color: #ffffff;
}
.orange-number {
color: #ff9e14;
}
.red-number {
color: #ff242a;
}
.small-ring {
width: 110px;
height: 110px;
min-width: 56px;
min-height: 56px;
border-radius: 50%;
font-size: 17px;
font-weight: 900;
background:
radial-gradient(circle, #08234f 52%, transparent 54%),
conic-gradient(#19d4ff 0deg 315deg, rgba(47, 84, 131, 0.85) 315deg 360deg);
box-shadow: 0 0 13px rgba(0, 208, 255, 0.54);
}
.table-area {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 17px;
min-height: 0;
}
.table-panel {
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
}
.panel-caption {
height: 38px;
line-height: 38px;
}
.table-wrap {
flex: 1;
min-height: 0;
overflow: hidden;
padding: 0 9px 10px;
}
table {
width: 100%;
height: 100%;
table-layout: fixed;
border-collapse: collapse;
}
th,
td {
border: 1px solid rgba(53, 149, 217, 0.48);
text-align: center;
vertical-align: middle;
color: #f1f8ff;
font-size: 12px;
line-height: 1.18;
word-break: break-all;
}
th {
height: 38px;
font-size: 14px;
font-weight: 200;
background: rgba(12, 68, 119, 0.28);
}
td {
height: 50px;
}
.brand {
width: 300px;
height: 72px;
display: flex;
align-items: center;
overflow: visible;
position: relative;
z-index: 2;
}
.brand-logo {
width: 224px;
height: 58px;
padding: 5px 10px;
background: #ffffff;
border-radius: 6px;
object-fit: contain;
object-position: left center;
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.28));
}
</style>

View File

@@ -0,0 +1,748 @@
<template>
<div class="assembly-dashboard"
:style="{ width: `${style.width}px`, height: `${style.height}px`, transform: style.transform }">
<div class="dashboard-shell">
<header class="dashboard-header">
<div class="brand"><img class="brand-logo" :src="clientLogoUrl" alt="RENLY" /></div>
<h1>装配中心任务看板</h1>
<div class="current-time">{{ currentTime }}</div>
</header>
<main class="dashboard-content">
<section class="summary-row">
<article v-for="item in summaryCards" :key="item.title" class="panel summary-card" :class="item.className">
<div>
<div class="summary-title">{{ item.title }}</div>
<div class="summary-body">
<img class="summary-icon" :src="item.icon" :alt="item.title" />
<div class="summary-value" :class="item.valueClass"><span>{{ item.value }}</span><small
v-if="item.unit">{{ item.unit }}</small></div>
</div>
</div>
<div v-if="item.people" class="people-list">
<div v-for="person in item.people" :key="person.name"><span>{{ person.name }}</span><b>{{ person.group
}}</b></div>
</div>
</article>
</section>
<section class="main-grid">
<div class="left-column">
<article class="panel table-panel">
<div class="panel-caption">发货状态列表</div>
<div class="scroll-wrap">
<table>
<thead>
<tr>
<th class="idx-th">序号</th>
<th v-for="c in taskColumns2" :key="c.key">{{ c.label }}</th>
</tr>
</thead>
<tbody :style="deliveryScrollStyle">
<tr v-for="(r, i) in deliveryDisplayRows" :key="i">
<td class="idx-td">{{ r._idx }}</td>
<td v-for="c in taskColumns2" :key="c.key">{{ r[c.key] }}</td>
</tr>
</tbody>
</table>
</div>
</article>
<article class="panel table-panel task-panel">
<div class="panel-caption">装配任务列表</div>
<div class="scroll-wrap">
<table>
<thead>
<tr>
<th class="idx-th">序号</th>
<th v-for="c in taskColumns" :key="c.key">{{ c.label }}</th>
</tr>
</thead>
<tbody :style="taskScrollStyle">
<tr v-for="(r, i) in taskDisplayRows" :key="i" :class="{ warning: r.urgentQty > 0 }">
<td class="idx-td">{{ r._idx }}</td>
<td v-for="c in taskColumns" :key="c.key">{{ r[c.key] }}</td>
</tr>
</tbody>
</table>
</div>
</article>
</div>
<aside class="right-column">
<article class="panel table-panel">
<div class="panel-caption">在制任务缺件明细</div>
<div class="scroll-wrap">
<table>
<thead>
<tr>
<th class="idx-th">序号</th>
<th v-for="c in materialColumns" :key="c.key">{{ c.label }}</th>
</tr>
</thead>
<tbody :style="materialScrollStyle">
<tr v-for="(r, i) in materialDisplayRows" :key="i">
<td class="idx-td">{{ r._idx }}</td>
<td v-for="c in materialColumns" :key="c.key">{{ r[c.key] }}</td>
</tr>
</tbody>
</table>
</div>
</article>
<article class="panel table-panel">
<div class="panel-caption">已完工作务</div>
<div class="scroll-wrap">
<table>
<thead>
<tr>
<th class="idx-th">序号</th>
<th v-for="c in doneColumns" :key="c.key">{{ c.label }}</th>
</tr>
</thead>
<tbody :style="doneScrollStyle">
<tr v-for="(r, i) in doneDisplayRows" :key="i">
<td class="idx-td">{{ r._idx }}</td>
<td v-for="c in doneColumns" :key="c.key" :class="c.key === 'passRate' ? r.rateClass : ''">{{ r[c.key] }}</td>
</tr>
</tbody>
</table>
</div>
</article>
</aside>
</section>
</main>
</div>
</div>
</template>
<script setup>
import { computed, inject, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
const CreateData = inject('CreateData')
const ExecDatabase = inject('ExecDatabase')
defineOptions({ name: 'AssemblyCenterTaskDashboard' })
const now = ref(new Date('2026-05-06 10:10:10'))
let timer = null
let resizeHandler = null
let scrollTimers = []
const style = reactive({ width: 1920, height: 1080, transform: 'scaleY(1) scaleX(1) translate(-50%, -50%)' })
const setScale = () => { const s = getScale(); style.transform = `scaleY(${s.y}) scaleX(${s.x}) translate(-50%, -50%)` }
const getScale = () => ({ x: window.innerWidth / style.width, y: window.innerHeight / style.height })
const pad = v => String(v).padStart(2, '0')
const currentTime = computed(() => `${now.value.getFullYear()}-${pad(now.value.getMonth() + 1)}-${pad(now.value.getDate())} ${pad(now.value.getHours())}:${pad(now.value.getMinutes())}:${pad(now.value.getSeconds())}`)
const clientLogoUrl = './public/ClientLogo.png'
const icon = name => `./public/svg/${name}.svg`
const tableData = ref([])
const getFirstRow = rows => {
if (Array.isArray(rows) && rows.length > 0) {
return rows[0]
}
return {}
}
onMounted(() => {
startClock();
setScale();
resizeHandler = setScale;
window.addEventListener('resize', resizeHandler)
searchDashboardData()
startAllScroll()
})
onUnmounted(() => {
if (timer) window.clearInterval(timer);
if (resizeHandler) window.removeEventListener('resize', resizeHandler)
stopAllScroll()
})
// ============ 滚动数据 ============
const deliveryRows = ref([])
const taskRows = ref([])
const materialRows = ref([])
const doneRows = ref([])
// 每个列表的滚动偏移
const deliveryOffset = ref(0)
const taskOffset = ref(0)
const materialOffset = ref(0)
const doneOffset = ref(0)
// 可见行数
const VISIBLE_ROWS = {
delivery: 8,
task: 7,
material: 8,
done: 7
}
// 生成带序号和循环滚动的显示行
const makeDisplayRows = (rows, offset, visibleCount) => {
if (!rows || rows.length === 0) return []
const len = rows.length
const result = []
for (let i = 0; i < visibleCount; i++) {
const idx = (offset + i) % len
result.push({ ...rows[idx], _idx: idx + 1 })
}
return result
}
const deliveryDisplayRows = computed(() => makeDisplayRows(deliveryRows.value, deliveryOffset.value, VISIBLE_ROWS.delivery))
const taskDisplayRows = computed(() => makeDisplayRows(taskRows.value, taskOffset.value, VISIBLE_ROWS.task))
const materialDisplayRows = computed(() => makeDisplayRows(materialRows.value, materialOffset.value, VISIBLE_ROWS.material))
const doneDisplayRows = computed(() => makeDisplayRows(doneRows.value, doneOffset.value, VISIBLE_ROWS.done))
// 滚动样式(平滑过渡)
const deliveryScrollStyle = computed(() => ({ transition: 'none' }))
const taskScrollStyle = computed(() => ({ transition: 'none' }))
const materialScrollStyle = computed(() => ({ transition: 'none' }))
const doneScrollStyle = computed(() => ({ transition: 'none' }))
// 启动单个列表滚动
const startScroll = (rowsRef, offsetRef) => {
const id = window.setInterval(() => {
if (rowsRef.value.length > 0) {
offsetRef.value = (offsetRef.value + 1) % rowsRef.value.length
}
}, 2000)
scrollTimers.push(id)
return id
}
const startAllScroll = () => {
startScroll(deliveryRows, deliveryOffset)
startScroll(taskRows, taskOffset)
startScroll(materialRows, materialOffset)
startScroll(doneRows, doneOffset)
}
const stopAllScroll = () => {
scrollTimers.forEach(id => window.clearInterval(id))
scrollTimers = []
}
// ============ 数据查询 ============
const searchDashboardData = () => {
searchUnclosedData()
searchMaterialshortageData()
searchUrgentData()
searchDeliveryData()
searchTaskData()
searchPassRateData()
searchDoneTaskData()
}
const searchUnclosedData = () => {
queryDashboardData('装配中心任务看板_异常未关闭总数', applyUnclosedData)
}
const searchDeliveryData = () => {
queryDashboardData('装配中心任务看板_发货状态查询', applyDeliveryData)
}
const searchMaterialshortageData = () => {
queryDashboardData('装配中心任务看板_在制品缺件查询', applyMaterialshortageData)
}
const searchUrgentData = () => {
queryDashboardData('装配中心任务看板_加急任务总数查询', applyUrgentData)
}
const searchTaskData = () => {
queryDashboardData('装配中心任务看板_装配任务查询', applyTaskData)
}
const searchPassRateData = () => {
queryDashboardData('装配中心任务看板_装配合格率', applyPassRateData)
}
const searchDoneTaskData = () => {
queryDashboardData('装配中心任务看板_已完成任务', applyDoneTaskData)
}
const applyUnclosedData = rows => {
summaryCards[3].value = rows[0].装配异常未关闭总数
}
const applyDeliveryData = rows => {
deliveryRows.value = rows.map(item => {
let requiredDate = ''
if (item.要求发货日期) {
// 如果是字符串
if (typeof item.要求发货日期 === 'string') {
requiredDate = item.要求发货日期.split(' ')[0]
}
// 如果是 Date 对象
else if (item.要求发货日期 instanceof Date) {
requiredDate = item.要求发货日期.toISOString().split('T')[0]
}
// 如果是其他格式
else {
const dateStr = String(item.要求发货日期)
requiredDate = dateStr.split(' ')[0]
}
}
return {
productionOrder: item.订单编号,
salesOrder: item.合同号 || '',
materialName: item.物料描述 || '',
materialCode: item.物料编号 || '',
completeSet: item.齐套 === 1 ? '是' : '否',
issueStatus: item.发料状态 === '1' ? '发料齐套' : (item.发料状态 === '2' ? '发料缺件' : ''),
urgentQty: item.操作人,
assignQty: item.交货数量,
taskdate: requiredDate,
operator: item.任务状态 === 0 ? '待执行' : (item.发料状态 === 1 ? '可执行' : '已开工'),
}
})
}
const applyUrgentData = rows => {
summaryCards[1].value = rows[0].加急总数
}
const applyPassRateData = rows => {
summaryCards[2].value = rows[0].总合格率+'%'
}
const applyDoneTaskData = rows => {
doneRows.value = rows.map(item => ({
productionOrder:item.计划号?.toString() || '',
materialName: item.零件名称 || '',
materialCode: item.零件编码 || '',
passRate: item.单项合格率+'%',
rateClass:item.单项合格率 <= 70 ? 'orange-cell' : 'green-cell'
}))
}
const applyTaskData = rows => {
taskRows.value = rows.map(item => ({
productionOrder: item.订单号?.toString() || '',
salesOrder: item.合同号 || '',
materialName: item.零件名称 || '',
materialCode: item.零件编码 || '',
processName: item.工序名称 || '',
completeSet: item.齐套 === '是' ? '是' : '否',
issueStatus: item.发料状态 === '发料齐套' ? '发料齐套' : (item.发料状态 === '发料缺件' ? '发料缺件' : ''),
urgentQty: (item.加急总数 && item.加急总数 > 0) ? item.加急总数 : (item.加急 === '加急' ? (item.分配数 || 0) : 0),
assignQty: item.分配数 || 0,
inspectQty: item.完成数量,
assignTarget: item.指派对象 || '',
workHours: '',
operator: item.指派对象 || ''
}))
}
const applyMaterialshortageData = rows => {
console.log(rows)
materialRows.value = rows.map(item => ({
wipOrder: item.DocEntry,
shortageStatus: item.U_Name,
finishTime: item.ItemCode,
owner: item.加急数量
}))
console.log(materialRows.value)
}
const queryDashboardData = (procedureName, applyData) => {
let data
data = CreateData('11', procedureName, [])
ExecDatabase(data).then(response => {
const rows = getResponseRows(response)
tableData.value = rows
applyData(rows)
})
}
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 startClock = () => { timer = window.setInterval(() => { now.value = new Date(now.value.getTime() + 1000) }, 1000) }
const svgData = svg => 'data:image/svg+xml;utf8,' + encodeURIComponent(svg)
const svgIcon = (from, to, body) => svgData(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72" fill="none">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="${from}"/>
<stop offset="1" stop-color="${to}"/>
</linearGradient>
<linearGradient id="shine" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fff" stop-opacity=".35"/>
<stop offset="1" stop-color="#fff" stop-opacity="0"/>
</linearGradient>
<filter id="glow" x="-55%" y="-55%" width="210%" height="210%"><feGaussianBlur stdDeviation="4" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
</defs>
<rect x="7" y="7" width="58" height="58" rx="11" fill="url(#bg)" filter="url(#glow)"/>
<path d="M14 14h44v20c-10-4-29-4-44 1z" fill="url(#shine)"/>
<g transform="translate(36 36) scale(1.12) translate(-36 -36)" stroke="#fff" stroke-width="4.1" stroke-linecap="round" stroke-linejoin="round" opacity=".96">${body}</g>
</svg>`
)
const icons = {
pending: svgIcon('#37a9ff', '#0862f3', '<rect x="23" y="20" width="26" height="36" rx="4"/><path d="M29 20h14l-3-6h-8l-3 6zM29 34h14M29 43h14"/>'),
urgent: svgIcon('#ff7469', '#dc2524', '<path d="M21 55h30M25 50V34a11 11 0 0 1 22 0v16M36 13v8M18 21l6 6M54 21l-6 6M15 38h8M49 38h8"/>'),
cart: svgIcon('#28a8ff', '#0a58ff', '<path d="M16 22h7l5 25h27l5-17H28"/><circle cx="32" cy="56" r="2.8"/><circle cx="52" cy="56" r="2.8"/>'),
checklist: svgIcon('#49e89e', '#08a86e', '<rect x="21" y="17" width="32" height="42" rx="5"/><path d="M28 35l5 5 12-13M28 49h19"/>'),
returnDown: svgIcon('#ffb23f', '#fb7418', '<rect x="21" y="23" width="31" height="32" rx="4"/><path d="M36 14v24M27 31l9 9 9-9"/>'),
returnSales: svgIcon('#9867ff', '#6232e6', '<path d="M51 22H28a10 10 0 0 0 0 20h26M28 14l-11 9 11 9"/><rect x="35" y="37" width="18" height="18" rx="2"/>'),
finalCheck: svgIcon('#3a95ff', '#0a61e8', '<path d="M36 14l16 7v13c0 11-7 18-16 24-9-6-16-13-16-24V21z"/><path d="M27 37l6 6 13-15"/>'),
other: svgIcon('#4bd8ff', '#0a88cc', '<path d="M19 26l17-10 17 10-17 10zM19 26v19l17 10 17-10V26M36 36v19"/>'),
shield: svgIcon('#3a9aff', '#0861e8', '<path d="M36 14l16 7v13c0 11-7 18-16 24-9-6-16-13-16-24V21z"/><path d="M28 37l6 6 12-14"/>'),
done: svgIcon('#47eaa0', '#0da76b', '<rect x="19" y="19" width="34" height="34" rx="8"/><path d="M27 37l7 7 15-18"/>')
}
const summaryCards = [
{ title: '及时送检数', value: 18, unit: '条', icon: icons.pending },
{ title: '加急任务数', value: 125, unit: '条', icon: icons.urgent, valueClass: 'red-text' },
{ title: '装配合格率', value: '100%', icon: icons.finalCheck, valueClass: 'green-text full-value' },
{ title: '异常未关闭消息数', value: 5, unit: '条', icon: icons.urgent, valueClass: 'red-text' },
{ title: '缺勤人员', value: 5, unit: '人', icon:icons.returnDown, valueClass: 'orange-text', className: 'absence-card', people: [{ name: '张三', group: '装配1班' }, { name: '李四', group: '装配2班' }, { name: '王五', group: '装配3班' }] },
]
const taskColumns = [
{ label: '生产订单', key: 'productionOrder', width: '72px' },
{ label: '销售订单', key: 'salesOrder', width: '92px' },
{ label: '物料名称', key: 'materialName', width: '190px' },
{ label: '物料编码', key: 'materialCode', width: '176px' },
{ label: '齐套', key: 'completeSet', width: '52px' },
{ label: '发料状态', key: 'issueStatus', width: '88px' },
{ label: '加急数', key: 'urgentQty', width: '62px' },
{ label: '分配数', key: 'assignQty', width: '70px' },
{ label: '送检数', key: 'inspectQty', width: '70px' },
{ label: '指派对象', key: 'assignTarget', width: '88px' },
]
const taskColumns2 = [
{ label: '生产订单', key: 'productionOrder', width: '72px' },
{ label: '销售订单', key: 'salesOrder', width: '92px' },
{ label: '物料名称', key: 'materialName', width: '190px' },
{ label: '物料编码', key: 'materialCode', width: '176px' },
{ label: '数量', key: 'assignQty', width: '176px' },
{ label: '要求发货日期', key: 'taskdate', width: '176px' },
{ label: '齐套', key: 'completeSet', width: '52px' },
{ label: '发料状态', key: 'issueStatus', width: '88px' },
{ label: '操作人', key: 'urgentQty', width: '62px' },
{ label: '装配开工', key: 'operator', width: '82px' },
]
const materialColumns = [
{ label: '生产订单', key: 'wipOrder', width: '76px' },
{ label: '物料名称', key: 'shortageStatus', width: '105px' },
{ label: '物料编码', key: 'finishTime', width: '160px' },
{ label: '加急数量', key: 'owner', width: '90px' },
]
const doneColumns = [
{ label: '生产订单', key: 'productionOrder', width: '88px' },
{ label: '物料名称', key: 'materialName', width: '150px' },
{ label: '物料编码', key: 'materialCode', width: '165px' },
{ label: '合格率', key: 'passRate', width: '86px' },
]
</script>
<style scoped lang="scss">
* {
box-sizing: border-box;
}
.assembly-dashboard {
position: fixed;
left: 50%;
top: 50%;
width: 1920px;
height: 1080px;
transform-origin: 0 0;
transition: .3s;
overflow: hidden;
color: #f3f9ff;
font-family: 'Microsoft YaHei', Arial, sans-serif;
background: #07001f;
}
.dashboard-shell {
width: 100%;
height: 100%;
padding: 0 15px 15px;
}
.dashboard-header {
display: grid;
grid-template-columns: 336px 1fr 442px;
align-items: center;
height: 95px;
border-bottom: 1px solid rgba(21, 162, 240, .7);
}
.brand {
width: 300px;
height: 72px;
display: flex;
align-items: center;
overflow: visible;
position: relative;
z-index: 2;
}
.brand-logo {
width: 224px;
height: 58px;
padding: 5px 10px;
background: #ffffff;
border-radius: 6px;
object-fit: contain;
object-position: left center;
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.28));
}
.dashboard-header h1 {
margin: 0;
color: #fff;
font-size: 55px;
font-weight: 900;
letter-spacing: .08em;
text-align: center;
}
.current-time {
padding-right: 40px;
color: #eef7ff;
font-size: 27px;
text-align: right;
}
.dashboard-content {
display: grid;
grid-template-rows: 126px 1fr;
gap: 17px;
height: calc(100% - 95px);
padding-top: 10px;
}
.summary-row {
display: grid;
grid-template-columns: 1.05fr 1.05fr 1fr 1.2fr 1.55fr;
gap: 17px;
}
.main-grid {
display: grid;
grid-template-columns: 2.05fr 1fr;
gap: 17px;
min-height: 0;
}
.left-column {
display: grid;
grid-template-rows: 1.05fr .9fr;
gap: 17px;
min-height: 0;
}
.right-column {
display: grid;
grid-template-rows: 1.05fr .9fr;
gap: 17px;
min-height: 0;
}
.panel {
position: relative;
border: 1px solid rgba(29, 153, 229, .78);
background: rgba(3, 16, 48, .38);
box-shadow: inset 0 0 18px rgba(27, 130, 214, .24), 0 0 10px rgba(1, 132, 220, .18);
}
.panel::before {
position: absolute;
inset: -1px;
content: '';
pointer-events: none;
background: linear-gradient(90deg, #2bd8ff 0 14px, transparent 14px) left top/14px 2px no-repeat, linear-gradient(180deg, #2bd8ff 0 14px, transparent 14px) left top/2px 14px no-repeat, linear-gradient(270deg, #2bd8ff 0 14px, transparent 14px) right top/14px 2px no-repeat, linear-gradient(180deg, #2bd8ff 0 14px, transparent 14px) right top/2px 14px no-repeat, linear-gradient(90deg, #2bd8ff 0 14px, transparent 14px) left bottom/14px 2px no-repeat, linear-gradient(0deg, #2bd8ff 0 14px, transparent 14px) left bottom/2px 14px no-repeat, linear-gradient(270deg, #2bd8ff 0 14px, transparent 14px) right bottom/14px 2px no-repeat, linear-gradient(0deg, #2bd8ff 0 14px, transparent 14px) right bottom/2px 14px no-repeat;
}
.panel::after {
position: absolute;
inset: 0;
content: '';
pointer-events: none;
box-shadow: inset 0 0 12px rgba(43, 216, 255, .12);
}
.summary-card {
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
padding: 8px 18px;
}
.summary-card > div:first-child {
width: 100%;
min-width: 0;
}
.absence-card {
justify-content: space-between;
}
.absence-card > div:first-child {
width: auto;
padding-right: 22px;
border-right: 3px solid rgba(238, 247, 255, 0.92);
}
.absence-card .summary-title {
text-align: left;
}
.absence-card .summary-body {
display: flex;
gap: 26px;
width: auto;
}
.absence-card .summary-value {
display: flex;
gap: 8px;
}
.absence-card .summary-value span {
text-align: left;
}
.summary-title {
margin-bottom: 5px;
color: #89d4ff;
font-size: 20px;
font-weight: 700;
text-align: left;
}
.summary-body {
display: grid;
grid-template-columns: 66px 214px;
align-items: center;
justify-content: center;
column-gap: 26px;
width: 100%;
}
.summary-icon {
width: 66px;
height: 66px;
object-fit: contain;
}
.summary-value {
display: grid;
grid-template-columns: 150px 32px;
align-items: baseline;
justify-content: center;
color: #fff;
font-weight: 900;
line-height: 1;
}
.summary-value span {
font-size: 57px;
text-align: center;
}
.summary-value small {
color: #fff;
font-size: 22px;
font-weight: 700;
text-align: left;
}
.full-value span {
grid-column: 1 / -1;
}
.red-text span {
color: #ff1e2e;
}
.green-text span {
color: #18c65b;
}
.orange-text span {
color: #ff9c12;
}
.absence-card .summary-value::before {
content: '总数:';
color: #fff;
font-size: 20px;
font-weight: 700;
}
.people-list {
min-width: 130px;
color: #fff;
font-size: 13px;
line-height: 1.75;
}
.people-list div {
display: flex;
gap: 18px;
justify-content: space-between;
}
.panel-caption {
height: 38px;
color: #8cd3ff;
font-size: 22px;
font-weight: 600;
line-height: 38px;
text-align: center;
}
.table-panel {
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
}
.scroll-wrap {
flex: 1;
min-height: 0;
overflow: hidden;
padding: 0 9px 10px;
}
table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
th, td {
height: 48px;
border: 1px solid rgba(53, 149, 217, .54);
color: #f1f8ff;
font-size: 14px;
line-height: 1.18;
text-align: center;
vertical-align: middle;
white-space: pre-line;
word-break: break-all;
background: rgba(4, 24, 57, .44);
}
th {
height: 44px;
color: #bde9ff;
font-weight: 500;
background: rgba(12, 68, 119, .28);
}
.idx-th {
width: 44px;
}
.idx-td {
width: 44px;
color: #89d4ff;
font-size: 13px;
}
.task-panel td {
height: 47px;
}
.warning td {
color: #ff4949;
}
.green-cell {
color: #32ff32;
}
.orange-cell {
color: #ff7a1a;
}
</style>