master
吴普建 11 months ago
parent 6550d40efd
commit 3bd002c96f

@ -1,130 +1,149 @@
<template>
<div>
<my-card title="搜索条件" search>
<n-form inline>
<n-form-item label="车间">
<n-select
v-model:value="searchForm.location"
placeholder="请选择车间"
:options="locationList"
class="w-160px"
></n-select>
</n-form-item>
<n-form-item>
<component :is="useSearchBtn(search, resetThen)"></component>
</n-form-item>
</n-form>
</my-card>
<n-card :bordered="false" class="h-full rounded-8px shadow-sm">
<n-grid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<n-grid-item v-for="item in cardData" :key="item.id">
<gradient-bg class="h-100px" :start-color="item.colors[0]" :end-color="item.colors[1]">
<h3 class="text-16px">{{ item.title }}</h3>
<div class="flex justify-between pt-12px">
<svg-icon :icon="item.icon" class="text-32px" />
<count-to
:prefix="item.unit"
:start-value="1"
:end-value="item.value"
class="text-30px text-white dark:text-dark"
/>
</div>
</gradient-bg>
</n-grid-item>
</n-grid>
<n-divider title-placement="center" style="font-size: 18px; margin: 30px 0">设备信息</n-divider>
<n-grid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<n-grid-item v-for="item in deviceDataList" :key="item.code">
<div class="device-card" :class="getStatus(item.status).className">
<div>设备编码 : {{ item.code }}</div>
<div>状态 : {{ getStatus(item.status).text }}</div>
<div>开始时间 : {{ item.startTime }}</div>
<div>加工数量 : {{ item.count }}</div>
<div>结束日期 : {{ item.endTime }}</div>
</div>
</n-grid-item>
</n-grid>
</n-card>
</div>
<div>
<my-card title="搜索条件" search>
<n-form inline>
<n-form-item label="车间">
<n-select
v-model:value="searchForm.location"
placeholder="请选择车间"
:options="locationList"
class="w-160px"
></n-select>
</n-form-item>
<n-form-item>
<component :is="useSearchBtn(search, resetThen)"></component>
</n-form-item>
</n-form>
</my-card>
<n-card :bordered="false" class="h-full rounded-8px shadow-sm">
<n-grid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<n-grid-item v-for="item in cardData" :key="item.id">
<gradient-bg class="h-100px" :start-color="item.colors[0]" :end-color="item.colors[1]">
<h3 class="text-16px">{{ item.title }}</h3>
<div class="flex justify-between pt-12px">
<svg-icon :icon="item.icon" class="text-32px" />
<count-to
:prefix="item.unit"
:start-value="1"
:end-value="item.value"
class="text-30px text-white dark:text-dark"
/>
</div>
</gradient-bg>
</n-grid-item>
</n-grid>
<n-divider title-placement="center" style="font-size: 18px; margin: 30px 0">设备信息</n-divider>
<n-grid cols="s:1 m:2 l:4" responsive="screen" :x-gap="16" :y-gap="16">
<n-grid-item v-for="item in deviceDataList" :key="item.code">
<div class="device-card" :class="getStatus(item.status).className">
<div>设备编码 : {{ item.code }}</div>
<div>设备类型 : 开料机</div>
<div>运行状态 : {{ getStatus(item.status).text }}</div>
<div>产品型号 : {{ item.productModel }}</div>
<div>生产产量 :
<count-to
:start-value="1"
:end-value="item.count"
:duration="800"
:autoplay="true"
:use-easing="true"
easing-function="cubicOut"
class="animated-number"
/></div>
<div>生产速率 : <span style="color:yellow">{{item.rate}}</span> /</div>
<div>结束日期 : {{ item.endTime }}</div>
</div>
</n-grid-item>
</n-grid>
</n-card>
</div>
</template>
<script setup lang="tsx">
import { ref, onMounted, onUnmounted, reactive } from 'vue';
import {ref, onMounted, onUnmounted, reactive} from 'vue';
import mqtt from 'mqtt';
import { getWorkbenchWiredrawingList } from '@/service/api/md/workbench/wiredrawing';
import { selectAllWorkbenchEnamellingList } from '@/service/api/md/workbench/enamelling';
import { useResetSearch } from '~/src/utils/common/searchReset';
import { useSearchBtn } from '~/src/hooks/common/useBtn';
import { formatDate } from '~/src/utils/form/rule';
import { GradientBg } from './components';
const { searchForm, reset } = useResetSearch({
location: '全部'
});
import {getWorkbenchWiredrawingList} from '@/service/api/md/workbench/wiredrawing';
import {selectAllWorkbenchEnamellingList} from '@/service/api/md/workbench/enamelling';
import {useResetSearch} from '~/src/utils/common/searchReset';
import {useSearchBtn} from '~/src/hooks/common/useBtn';
import {formatDate} from '~/src/utils/form/rule';
import {GradientBg} from './components';
const {searchForm, reset} = useResetSearch({
location: '全部'
});
const productModels = ['LX25-Y39-002金', 'LX30-Y40-005浅蓝', 'LX35-Z25-006蓝灰', 'LX40-B02-002黑', 'LX30-Y40-005蓝', 'LX35-Z5-004白', 'LX25-A08-007黑'
, 'LX-Y39-030黑', 'LX20-Y30-010灰', 'LX35-Z5-006蓝', 'LX40-B01-003黑', 'LX30-Y42-002蓝', 'LX35-Z15-004单蓝', 'LX25-A08-006灰']; //
interface CardData {
id: string;
title: string;
value: number;
unit: string;
colors: [string, string];
icon: string;
id: string;
title: string;
value: number;
unit: string;
colors: [string, string];
icon: string;
}
type device = {
code: string;
status: number;
startTime: string;
count: number;
endTime: string;
process: string;
code: string;
status: number;
startTime: string;
count: number;
endTime: string;
process: string;
productModel: string;
timerId?: number;
interval: number;
originalCode: string; //
currentModelIndex: number; //
rate?: String;
};
const deviceAllList = ref<Array<device>>([]);
const locationList = ref<Array<{ label: string; value: string }>>([
{
label: '全部',
value: '全部'
}
{
label: '全部',
value: '全部'
}
]);
const deviceDataList = ref<Array<device>>([]);
const cardData = reactive<CardData[]>([
{
id: 'visit',
title: '设备总数',
value: 72,
unit: '',
colors: ['#ec4786', '#b955a4'],
icon: 'ant-design:bar-chart-outlined'
},
{
id: 'amount',
title: '运行中',
value: 60,
unit: '$',
colors: ['#865ec0', '#5144b4'],
icon: 'ant-design:money-collect-outlined'
},
{
id: 'download',
title: '待机',
value: 10,
unit: '',
colors: ['#56cdf3', '#719de3'],
icon: 'carbon:document-download'
},
{
id: 'trade',
title: '故障',
value: 0,
unit: '',
colors: ['#fcbc25', '#f68057'],
icon: 'ant-design:trademark-circle-outlined'
}
{
id: 'visit',
title: '设备总数',
value: 0,
unit: '',
colors: ['#ec4786', '#b955a4'],
icon: 'ant-design:bar-chart-outlined'
},
{
id: 'amount',
title: '运行中',
value: 40,
unit: '$',
colors: ['#865ec0', '#5144b4'],
icon: 'ant-design:money-collect-outlined'
},
{
id: 'download',
title: '待机',
value: 9,
unit: '',
colors: ['#56cdf3', '#719de3'],
icon: 'carbon:document-download'
},
{
id: 'trade',
title: '故障',
value: 0,
unit: '',
colors: ['#fcbc25', '#f68057'],
icon: 'ant-design:trademark-circle-outlined'
}
]);
// MQTT
@ -138,171 +157,222 @@ const clientId = `mqtt_${Math.random().toString(16).slice(3)}`;
//
const options = {
clean: true, //
connectTimeout: 4000, //
reconnectPeriod: 1000, //
clientId
clean: true, //
connectTimeout: 4000, //
reconnectPeriod: 1000, //
clientId
};
const isConnect = false;
const connectToMQTT = () => {
// MQTT broker
if (!isConnect) return;
client = mqtt.connect(mqttBrokerUrl, options);
//
client.on('connect', () => {
client.subscribe(topic, err => {
console.log('topic ==>', topic);
if (err) {
console.error('Failed to subscribe:', err);
} else {
console.log(`Subscribed to topic: ${topic}`);
}
});
});
//
client.on('message', (topic1, message) => {
console.log(topic1);
// Uint8ArraytoString
const data = JSON.parse(message.toString());
console.log('data ==>', data);
});
//
client.on('error', error => {
console.error('MQTT connection error:', error);
});
// MQTT broker
if (!isConnect) return;
client = mqtt.connect(mqttBrokerUrl, options);
//
client.on('connect', () => {
client.subscribe(topic, err => {
console.log('topic ==>', topic);
if (err) {
console.error('Failed to subscribe:', err);
} else {
console.log(`Subscribed to topic: ${topic}`);
}
});
});
//
client.on('message', (topic1, message) => {
console.log(topic1);
// Uint8ArraytoString
const data = JSON.parse(message.toString());
console.log('data ==>', data);
});
//
client.on('error', error => {
console.error('MQTT connection error:', error);
});
};
function getStatus(status: number) {
switch (status) {
case 0:
return {
text: '运行中',
className: 'amount-color'
};
case 1:
return {
text: '待机',
className: 'download-color'
};
case 2:
return {
text: '故障',
className: 'trade-color'
};
default:
return {
text: '运行中',
className: 'amount-color'
};
}
switch (status) {
case 0:
return {
text: '运行中',
className: 'amount-color'
};
case 1:
return {
text: '待机',
className: 'download-color'
};
case 2:
return {
text: '故障',
className: 'trade-color'
};
default:
return {
text: '运行中',
className: 'amount-color'
};
}
}
async function getList() {
await getWorkbenchWiredrawingList({ pageSize: 999999 }).then(res => {
if (res.code === 200) {
res.rows.forEach(item => {
deviceEach(item);
});
}
});
await selectAllWorkbenchEnamellingList({}).then(res => {
if (res.code === 200) {
res.data.forEach(item => {
deviceEach(item);
});
}
});
// cardData[0].value = deviceAllList.value.length;
init();
await getWorkbenchWiredrawingList({pageSize: 999999}).then(res => {
if (res.code === 200) {
res.rows.forEach(item => {
deviceEach(item);
});
}
});
await selectAllWorkbenchEnamellingList({}).then(res => {
if (res.code === 200) {
res.data.forEach(item => {
deviceEach(item);
});
}
});
cardData[0].value = deviceAllList.value.length;
init();
}
function deviceEach(item: any) {
if (locationList.value.findIndex(ele => ele.value === item.process) === -1) {
locationList.value.push({
label: `${item.process}车间`,
value: item.process
});
}
deviceAllList.value.push({
code: item.equipmentCode,
status: 0,
startTime: formatDate(
new Date(getRandom(new Date().getTime() - 1000 * 60 * 60 * 48, new Date().getTime())),
'yyyy-MM-dd hh:mm:ss'
),
count: getRandom(1, 20),
endTime: formatDate(
new Date(getRandom(new Date().getTime(), new Date().getTime() + 1000 * 60 * 60 * 48)),
'yyyy-MM-dd'
),
process: item.process
});
if (locationList.value.findIndex(ele => ele.value === item.process) === -1) {
locationList.value.push({
label: `${item.process}车间`,
value: item.process
});
}
let interval = Math.floor((16 + Math.random() * 10)) * 1000;
const newDevice = reactive({
code: item.equipmentCode,
status: 0,
productModel: productModels[getRandom(1, 4)],
startTime: formatDate(
new Date(getRandom(new Date().getTime() - 1000 * 60 * 60 * 48, new Date().getTime())),
'yyyy-MM-dd hh:mm:ss'
),
count: getRandom(1, 20),
endTime: formatDate(
new Date(getRandom(new Date().getTime(), new Date().getTime() + 1000 * 60 * 60 * 48)),
'yyyy-MM-dd'
),
process: item.process,
originalCode: item.equipmentCode,
currentModelIndex: 0,
interval: interval, // 16-24
// count: 0,
timerId: undefined,
rate: (60 * 1000 / interval).toFixed(1)
});
startDeviceTimer(newDevice); //
deviceAllList.value.push(newDevice);
}
//
function startDeviceTimer(device: device) {
device.timerId = window.setInterval(() => {
device.count++;
if (device.count >= 200) {
resetDeviceProduction(device);
}
}, device.interval); // 20
}
//
function resetDeviceProduction(device: device) {
device.count = 0;
device.currentModelIndex = (device.currentModelIndex + 1) % productModels.length;
device.productModel = productModels[device.currentModelIndex];
clearInterval(device.timerId);
device.interval = Math.floor((15 + Math.random() * 10)) * 1000; //
device.rate = (60 * 1000 / device.interval).toFixed(1);
startDeviceTimer(device);
}
function getRandom(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
return Math.floor(Math.random() * (max - min + 1) + min);
}
function search() {
init();
init();
}
function resetThen() {
reset();
init();
reset();
init();
}
function init() {
if (!searchForm.value.location) {
deviceDataList.value = [];
return;
}
if (searchForm.value.location === '全部') {
deviceDataList.value = deviceAllList.value.map(item => item);
return;
}
deviceDataList.value = deviceAllList.value.filter(item => item.process === searchForm.value.location);
if (!searchForm.value.location) {
deviceDataList.value = [];
return;
}
if (searchForm.value.location === '全部') {
deviceDataList.value = deviceAllList.value.map(item => item);
return;
}
deviceDataList.value = deviceAllList.value.filter(item => item.process === searchForm.value.location);
}
//
let intervalId: number;
// MQTT
onMounted(() => {
connectToMQTT();
init();
getList();
connectToMQTT();
init();
getList();
});
// MQTT
onUnmounted(() => {
if (client) {
client.end();
}
deviceAllList.value.forEach(device => {
if (device.timerId) clearInterval(device.timerId);
});
if (client) {
client.end();
}
});
</script>
<style scoped>
/* 你的样式 */
.device-card {
color: #fff;
border-radius: 8px;
padding: 10px;
box-sizing: border-box;
font-size: 16px;
color: #fff;
border-radius: 8px;
padding: 10px;
box-sizing: border-box;
font-size: 16px;
}
.amount-color {
background: linear-gradient(45deg, #865ec0, #5144b4);
background: linear-gradient(45deg, #865ec0, #5144b4);
}
.download-color {
background: linear-gradient(45deg, #56cdf3, #719de3);
background: linear-gradient(45deg, #56cdf3, #719de3);
}
.trade-color {
background: linear-gradient(45deg, #fcbc25, #f68057);
background: linear-gradient(45deg, #fcbc25, #f68057);
}
/* 添加数字变化动画 */
.flip-animation {
transition: transform 0.5s;
}
.flip-animation-up {
transform: translateY(-20px);
opacity: 0;
}
.flip-animation-down {
transform: translateY(20px);
opacity: 0;
}
</style>

Loading…
Cancel
Save