完善类型配置,新增L1, L4数据展示界面

This commit is contained in:
tao
2025-09-26 10:44:15 +08:00
parent 614e5ad34e
commit db87adf26a
33 changed files with 1294 additions and 309 deletions

2
.env.production Normal file
View File

@@ -0,0 +1,2 @@
# 生产环境
VITE_APP_BASE_API = '/prod-api'

31
components.d.ts vendored
View File

@@ -11,72 +11,53 @@ declare module 'vue' {
AButton: typeof import('ant-design-vue/es')['Button'] AButton: typeof import('ant-design-vue/es')['Button']
ACol: typeof import('ant-design-vue/es')['Col'] ACol: typeof import('ant-design-vue/es')['Col']
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider'] AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
ActionButton: typeof import('./src/components/common/ActionButton/ActionButton.vue')['default']
ActionButtons: typeof import('./src/components/common/ActionButtons/index.vue')['default'] ActionButtons: typeof import('./src/components/common/ActionButtons/index.vue')['default']
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
AForm: typeof import('ant-design-vue/es')['Form'] AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem'] AFormItem: typeof import('ant-design-vue/es')['FormItem']
AInput: typeof import('ant-design-vue/es')['Input'] AInput: typeof import('ant-design-vue/es')['Input']
AInputGroup: typeof import('ant-design-vue/es')['InputGroup'] AInputGroup: typeof import('ant-design-vue/es')['InputGroup']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
AList: typeof import('ant-design-vue/es')['List']
AListItem: typeof import('ant-design-vue/es')['ListItem']
AModal: typeof import('ant-design-vue/es')['Modal'] AModal: typeof import('ant-design-vue/es')['Modal']
ARadio: typeof import('ant-design-vue/es')['Radio'] ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARow: typeof import('ant-design-vue/es')['Row'] ARow: typeof import('ant-design-vue/es')['Row']
ASelect: typeof import('ant-design-vue/es')['Select'] ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASpace: typeof import('ant-design-vue/es')['Space'] ASpace: typeof import('ant-design-vue/es')['Space']
ASwitch: typeof import('ant-design-vue/es')['Switch']
ATable: typeof import('ant-design-vue/es')['Table'] ATable: typeof import('ant-design-vue/es')['Table']
ATextarea: typeof import('ant-design-vue/es')['Textarea']
CameraModal: typeof import('./src/components/CameraModal/index.vue')['default']
CameraStatus: typeof import('./src/components/CameraStatus/index.vue')['default'] CameraStatus: typeof import('./src/components/CameraStatus/index.vue')['default']
ExecutionResult: typeof import('./src/components/common/ExecutionResult/index.vue')['default'] ExecutionResult: typeof import('./src/components/common/ExecutionResult/index.vue')['default']
ILucideActivity: typeof import('~icons/lucide/activity')['default'] ILucideActivity: typeof import('~icons/lucide/activity')['default']
ILucideCamera: typeof import('~icons/lucide/camera')['default'] ILucideCamera: typeof import('~icons/lucide/camera')['default']
ILucideClock: typeof import('~icons/lucide/clock')['default']
ILucideCpu: typeof import('~icons/lucide/cpu')['default'] ILucideCpu: typeof import('~icons/lucide/cpu')['default']
ILucideDatabase: typeof import('~icons/lucide/database')['default'] ILucideDatabase: typeof import('~icons/lucide/database')['default']
ILucideEdit: typeof import('~icons/lucide/edit')['default'] ILucideEdit: typeof import('~icons/lucide/edit')['default']
ILucideFileText: typeof import('~icons/lucide/file-text')['default']
ILucideInbox: typeof import('~icons/lucide/inbox')['default'] ILucideInbox: typeof import('~icons/lucide/inbox')['default']
ILucideLink: typeof import('~icons/lucide/link')['default']
ILucideLinkOff: typeof import('~icons/lucide/link-off')['default']
ILucideLinOff: typeof import('~icons/lucide/lin-off')['default']
ILucideLoader: typeof import('~icons/lucide/loader')['default'] ILucideLoader: typeof import('~icons/lucide/loader')['default']
ILucideLock: typeof import('~icons/lucide/lock')['default'] ILucideLock: typeof import('~icons/lucide/lock')['default']
ILucideLogIn: typeof import('~icons/lucide/log-in')['default']
ILucideLoOff: typeof import('~icons/lucide/lo-off')['default']
ILucideLucideDatabase: typeof import('~icons/lucide/lucide-database')['default']
ILucideMonitor: typeof import('~icons/lucide/monitor')['default'] ILucideMonitor: typeof import('~icons/lucide/monitor')['default']
ILucidePackage: typeof import('~icons/lucide/package')['default'] ILucidePackage: typeof import('~icons/lucide/package')['default']
ILucidePlug: typeof import('~icons/lucide/plug')['default']
ILucidePlugOff: typeof import('~icons/lucide/plug-off')['default']
ILucideRadio: typeof import('~icons/lucide/radio')['default'] ILucideRadio: typeof import('~icons/lucide/radio')['default']
ILucideRefreshCw: typeof import('~icons/lucide/refresh-cw')['default'] ILucideRefreshCw: typeof import('~icons/lucide/refresh-cw')['default']
ILucideSave: typeof import('~icons/lucide/save')['default'] ILucideSave: typeof import('~icons/lucide/save')['default']
ILucideSearch: typeof import('~icons/lucide/search')['default']
ILucideShieldCheck: typeof import('~icons/lucide/shield-check')['default'] ILucideShieldCheck: typeof import('~icons/lucide/shield-check')['default']
ILucideSquarePen: typeof import('~icons/lucide/square-pen')['default'] ILucideSquarePen: typeof import('~icons/lucide/square-pen')['default']
ILucideTrash2: typeof import('~icons/lucide/trash2')['default'] ILucideTrash2: typeof import('~icons/lucide/trash2')['default']
ILucideUnlink: typeof import('~icons/lucide/unlink')['default']
ILucideUnlinkOff: typeof import('~icons/lucide/unlink-off')['default']
ILucideUser: typeof import('~icons/lucide/user')['default'] ILucideUser: typeof import('~icons/lucide/user')['default']
ILucideWifi: typeof import('~icons/lucide/wifi')['default'] ILucideWifi: typeof import('~icons/lucide/wifi')['default']
ILucideX: typeof import('~icons/lucide/x')['default'] ILucideX: typeof import('~icons/lucide/x')['default']
ILucideZap: typeof import('~icons/lucide/zap')['default'] ILucideZap: typeof import('~icons/lucide/zap')['default']
MesModal: typeof import('./src/components/MesModal/index.vue')['default'] LmsStatus: typeof import('./src/components/LmsStatus/index.vue')['default']
MesStatus: typeof import('./src/components/MesStatus/index.vue')['default'] MesStatus: typeof import('./src/components/MesStatus/index.vue')['default']
NetworkModal: typeof import('./src/components/NetworkModal/index.vue')['default']
NetworkStatus: typeof import('./src/components/NetworkStatus/index.vue')['default'] NetworkStatus: typeof import('./src/components/NetworkStatus/index.vue')['default']
PlcModal: typeof import('./src/components/PlcModal/index.vue')['default']
PlcStatus: typeof import('./src/components/PlcStatus/index.vue')['default'] PlcStatus: typeof import('./src/components/PlcStatus/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
Settings: typeof import('./src/components/Settings/index.vue')['default']
SettingsModal: typeof import('./src/components/SettingsModal/index.vue')['default'] SettingsModal: typeof import('./src/components/SettingsModal/index.vue')['default']
SSELogs: typeof import('./src/components/SSELogs/index.vue')['default'] SSELogs: typeof import('./src/components/SSELogs/index.vue')['default']
SseModal: typeof import('./src/components/SseModal/index.vue')['default']
SseStatus: typeof import('./src/components/SseStatus/index.vue')['default'] SseStatus: typeof import('./src/components/SseStatus/index.vue')['default']
XxxModal: typeof import('./src/components/xxxModal/index.vue')['default']
} }
} }

1
global.d.ts vendored
View File

@@ -2,3 +2,4 @@
declare module '@/components/*'; declare module '@/components/*';
declare module '@/views/*'; declare module '@/views/*';
declare module '@/api/*'; declare module '@/api/*';
declare module '@/App.vue';

View File

@@ -1,11 +1,11 @@
{ {
"name": "rd_mes_front_hmi", "name": "rd_mes_front_hmi",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --open", "dev": "vite --open",
"build": "vue-tsc -b && vite build", "build": "vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {

View File

@@ -6,4 +6,5 @@
<script setup> <script setup>
import zhCN from 'ant-design-vue/es/locale/zh_CN'; import zhCN from 'ant-design-vue/es/locale/zh_CN';
import 'dayjs/locale/zh-cn';
</script> </script>

7
src/api/common/model.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
export interface ApiResponse<T = any> {
code?: number;
msg?: string;
data?: T;
rows?: T[];
total?: number;
}

20
src/api/data/index.ts Normal file
View File

@@ -0,0 +1,20 @@
import request from '../request';
import type { QueryParams } from './model';
// 获取 L1 数据
export function getL1Data(params: QueryParams) {
return request({
url: '/jinghua/l1Data/list',
method: 'get',
params
})
}
// 获取 L4 数据
export function getL4Data(params: QueryParams) {
return request({
url: '/jinghua/l4Data/list',
method: 'get',
params
})
}

9
src/api/data/model.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
export interface QueryParams {
pageNum?: number;
pageSize?: number;
orderByColumn?: string;
isAsc?: boolean;
qrCode?: string;
createTimeBegin?: string;
createTimeEnd?: string;
}

View File

@@ -4,7 +4,7 @@ import type { LmsWorkMode } from './model';
// 获取 LMS 工作模式 // 获取 LMS 工作模式
export const fetchLmsWorkMode = () => { export const fetchLmsWorkMode = () => {
return request({ return request({
url: '/jinghua/mes/work-mode', url: '/jinghua/work-mode',
method: 'get', method: 'get',
}) })
} }
@@ -12,7 +12,7 @@ export const fetchLmsWorkMode = () => {
// 更新 LMS 工作模式 // 更新 LMS 工作模式
export const updateLmsWorkMode = (workMode: LmsWorkMode) => { export const updateLmsWorkMode = (workMode: LmsWorkMode) => {
return request({ return request({
url: `/jinghua/mes/work-mode/${workMode}`, url: `/jinghua/work-mode/${workMode}`,
method: 'put', method: 'put',
}) })
} }

View File

@@ -1,7 +1,9 @@
import axios from 'axios'; import axios from 'axios';
import { Modal, notification } from 'ant-design-vue';
import router from '@/router'; import router from '@/router';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
import { Modal, notification } from 'ant-design-vue';
import type { AxiosRequestConfig } from "axios";
import type { ApiResponse } from "@/api/common/model";
const errCodeMap: { [key: string]: string } = { const errCodeMap: { [key: string]: string } = {
'403': '当前操作没有权限', '403': '当前操作没有权限',
@@ -12,7 +14,7 @@ const errCodeMap: { [key: string]: string } = {
// 创建axios实例 // 创建axios实例
const service = axios.create({ const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API as string, baseURL: import.meta.env.VITE_APP_BASE_API as string,
timeout: 10000 timeout: 10000,
}); });
// 请求拦截器 // 请求拦截器
@@ -36,12 +38,13 @@ service.interceptors.request.use(
service.interceptors.response.use( service.interceptors.response.use(
response => { response => {
console.log(response) console.log(response)
// 未设置状态码则默认成功状态
const code = response.data.code || 200; const code = response.data.code || 200;
const data = response.data;
if (code === 200) { switch (code) {
return response.data ?? response; case 200:
} else if (code === 401) { return data ?? response;
case 401:
Modal.error({ Modal.error({
title: '系统提示', title: '系统提示',
content: '登录状态已过期,请重新登录', content: '登录状态已过期,请重新登录',
@@ -49,24 +52,32 @@ service.interceptors.response.use(
router.push('/login') router.push('/login')
} }
}) })
} else { return Promise.reject(data);
const codeStr = String(code); default:
const errMsg = errCodeMap[codeStr] || response.data.msg || errCodeMap['default']; const errMsg = errCodeMap[code] || data.msg || errCodeMap['default'];
notification.error({ notification.error({
message: '请求错误', message: '请求错误',
description: errMsg, description: errMsg,
}); });
return Promise.reject(response.data); return Promise.reject(data);
} }
}, },
error => { error => {
error.message = error.code === "ECONNABORTED" ? '请求超时,请稍后重试' : error.message;
// 网络/服务器错误统一处理 // 网络/服务器错误统一处理
notification.error({ notification.error({
message: '网络错误', message: "网络错误",
description: error.message || '请求失败', description: error.message,
}); });
return Promise.reject(error); return Promise.reject(error);
} }
); );
export default service; // 类型检查
function request<T = any>(config: AxiosRequestConfig): Promise<ApiResponse<T>> {
return service(config) as unknown as Promise<ApiResponse<T>>;
}
export default request;
// export default service;

View File

@@ -1,153 +1,9 @@
@use './variables' as *; @use './variables' as *;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container { // 解决外部图标无法居中问题
width: 100%; .ant-btn:has(svg) {
height: 100%; display: inline-flex;
background-color: #000;
color: #fff;
display: flex;
flex-direction: column;
}
.info-row {
height: 5vh;
min-height: 5vh;
&.title {
height: 6vh;
min-height: 6vh;
}
.title {
display: flex;
justify-content: center;
align-items: center; align-items: center;
font-size: $title-font-size;
}
}
.logList-row {
flex: 1 1 auto; // 修改
min-height: 0; // 新增
.logList-col {
height: 100%;
padding: 0;
}
.logList {
overflow-y: auto;
}
}
.ant-col {
border-right: 2px solid #fff;
border-bottom: 2px solid #fff;
font-size: $content-font-size;
&.option {
display: flex;
justify-content: center; justify-content: center;
align-items: center; gap: 4px;
padding: 5px 3px 3px;
}
&.subtitle,
&.text,
&.label {
text-align: center;
padding: 0 1rem;
line-height: 5vh;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.logList-col {
padding: 2px 2px 0 2px;
}
}
.ant-btn,
.ant-btn.ant-btn-sm {
width: 100%;
height: 100%;
border-radius: 0;
background-color: #d6d6d6;
color: #000;
font-size: $content-font-size;
overflow: hidden;
&:hover {
border: 1px solid #000;
color: #fff;
background-color: #000;
font-size: 1vw;
}
&:active {
transform: scale(.9);
transition: 100ms;
background-color: #252525;
}
}
// 隐藏空列表
:deep(.ant-list-empty-text) {
display: none;
}
// 列表
.ant-list {
height: 100%;
width: 100%;
padding-right: 0 !important;
.ant-list-item {
padding: 4px 12px;
color: #fff;
font-size: $log-font-size;
}
}
// 下拉框
.ant-select {
width: 100%;
height: 100%;
display: block;
margin-top: 1px;
:deep(.ant-select-selector) {
height: 100%;
border: none;
border-radius: 0;
background-color: unset;
}
:deep(.ant-select-selection-search-input) {
height: 100% !important;
}
:deep(.ant-select-selection-item) {
display: flex;
justify-content: center;
align-items: center;
font-size: $content-font-size;
color: #adff2f;
}
:deep(.ant-select-selection-placeholder) {
display: flex;
justify-content: center;
align-items: center;
font-size: $content-font-size;
color: #888;
}
:deep(.ant-select-suffix svg) {
width: 1.5em;
height: 1.5em;
color: #fff;
}
} }

View File

@@ -1,2 +1,3 @@
@forward './base'; @forward './base';
@forward './variables'; @forward './variables';
@forward './ant-design';

View File

@@ -41,7 +41,7 @@ defineExpose({
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/assets/styles/_variables.scss'; @use "@/assets/styles/variables" as *;
.status-item { .status-item {
display: flex; display: flex;

View File

@@ -0,0 +1,161 @@
<template>
<!-- Status Item -->
<div class="status-item status-button" @click="show">
<i-lucide-zap class="status-icon" />
<span class="status-label">LMS 状态:</span>
<span class="status-value" :class="onlineStatusClass">{{ lmsStatus }}</span>
</div>
<a-modal v-model:open="dialogVisible" :title="modalTitle" :footer="null">
<a-row :gutter="[0, 12]">
<a-col :span="8" class="modal-label">LMS 状态</a-col>
<a-col :span="16" class="modal-value">
<a-switch v-model:checked="isOnline" checked-children="在线" un-checked-children="离线" @change="handleToggleOnline" />
</a-col>
</a-row>
<a-row :gutter="[0, 12]">
<template v-for="(item, index) in modalItems" :key="index">
<a-col :span="8" class="modal-label">{{ item.label }}</a-col>
<a-col :span="16" class="modal-value">{{ item.value }}</a-col>
</template>
</a-row>
<div class="actions">
<a-button size="large" @click="redirectTo('L1')">L1 数据</a-button>
<a-button size="large" @click="redirectTo('L4')">L4 数据</a-button>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router';
import { useDialog } from '@/utils/useDialog';
import { fetchLmsWorkMode, updateLmsWorkMode } from '@/api/lms';
import { message } from 'ant-design-vue';
// useDialog管理弹窗状态
const { visible: dialogVisible, show, hide } = useDialog();
const isOnline = ref(false);
const lmsStatus = computed(() => isOnline.value ? '在线' : '离线');
const onlineStatusClass = computed(() => {
return isOnline.value ? 'success' : 'error';
});
// 当请求发送成功后才切换状态
async function handleToggleOnline(checked: boolean | string | number) {
await updateLmsWorkMode(checked ? 1 : 0).then(() => {
isOnline.value = Boolean(checked);
message.success('切换成功');
}).catch((error) => {
message.error('切换失败');
});
}
const modalTitle = ref('LMS 状态');
const modalItems = ref<{ label: string; value: string }[]>([]);
// 初始化 LMS 状态
async function initLmsStatus() {
try {
const { msg } = await fetchLmsWorkMode();
isOnline.value = msg === "1";
} catch (error) {
console.error('获取 LMS 工作模式失败:', error);
}
}
const router = useRouter();
function redirectTo(type: string) {
router.push(`/${type}-data`);
}
// 初始化 LMS 状态
onMounted(initLmsStatus);
// 暴露方法
defineExpose({
show,
hide
});
</script>
<style scoped lang="scss">
@use "@/assets/styles/variables" as *;
.status-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 6px;
transition: all 0.2s ease;
&.status-button {
cursor: pointer;
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
}
.status-icon {
width: 16px;
height: 16px;
color: #8395B6;
&.success {
color: $success-color;
}
&.error {
color: $error-color;
}
&.warning {
color: $warning-color;
}
}
.status-label {
font-size: 13px;
color: #8395B6;
white-space: nowrap;
}
.status-value {
font-size: 13px;
font-weight: 500;
&.success {
color: $success-color;
}
&.error {
color: $error-color;
}
&.warning {
color: $warning-color;
}
}
}
.modal-label {
font-size: $text-size;
font-weight: bold;
}
.actions {
margin: 20px 0 10px;
display: flex;
gap: 10px;
justify-content: space-between;
.ant-btn {
flex: 1;
}
}
</style>

View File

@@ -42,7 +42,7 @@ defineExpose({
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/assets/styles/_variables.scss'; @use "@/assets/styles/variables" as *;
.status-item { .status-item {
display: flex; display: flex;

View File

@@ -54,7 +54,7 @@ defineExpose({
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/assets/styles/_variables.scss'; @use "@/assets/styles/variables" as *;
.status-item { .status-item {
display: flex; display: flex;

View File

@@ -42,7 +42,7 @@ defineExpose({
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/assets/styles/_variables.scss'; @use "@/assets/styles/variables" as *;
.status-item { .status-item {
display: flex; display: flex;

View File

@@ -45,7 +45,7 @@ defineExpose({
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/assets/styles/_variables.scss'; @use "@/assets/styles/variables" as *;
.modal-label { .modal-label {
font-size: $text-size; font-size: $text-size;

View File

@@ -27,10 +27,6 @@
<i-lucide-trash-2 /> <i-lucide-trash-2 />
清空日志 清空日志
</a-button> </a-button>
<a-button @click="test" class="clear-btn">
<i-lucide-trash-2 />
测试
</a-button>
</div> </div>
<div class="sse-logs" ref="logContainer"> <div class="sse-logs" ref="logContainer">
@@ -81,16 +77,14 @@ let cooldownTimer: number | null = null;
// 内部日志管理 // 内部日志管理
const logContainer = ref(); const logContainer = ref();
function test() {
addSseLog('测试日志');
}
// 使用SSE工具函数 // 使用SSE工具函数
const { const {
clientId, clientId,
serverUrl, serverUrl,
isConnected, isConnected,
isConnecting, isConnecting,
sseStatusText, sseStatus,
sseStatusClass, sseStatusClass,
logs, logs,
connect, connect,
@@ -127,12 +121,11 @@ const {
} }
}); });
const sseStatus = ref(sseStatusText.value);
const modalTitle = ref('SSE 连接状态'); const modalTitle = ref('SSE 连接状态');
const modalItems = ref([ const modalItems = ref([
{ label: '当前状态', value: sseStatus.value }, { label: '当前状态', value: sseStatus },
{ label: '客户端ID', value: clientId || 'hmi-main-client' }, { label: '客户端ID', value: clientId || 'hmi-main-client' },
{ label: '服务器地址', value: serverUrl.value } { label: '服务器地址', value: serverUrl }
]); ]);
// 启动连接冷却时间 // 启动连接冷却时间
@@ -175,6 +168,7 @@ const handleClearSseLogs = () => {
// 监听日志变化,自动滚动 // 监听日志变化,自动滚动
watch(() => logs.value.length, () => { watch(() => logs.value.length, () => {
if (logContainer.value) {
const { scrollTop = 0, scrollHeight = 0, clientHeight = 0 } = logContainer.value; const { scrollTop = 0, scrollHeight = 0, clientHeight = 0 } = logContainer.value;
const isUserAtBottom = scrollTop + clientHeight >= scrollHeight - 10; const isUserAtBottom = scrollTop + clientHeight >= scrollHeight - 10;
nextTick(() => { nextTick(() => {
@@ -182,6 +176,7 @@ watch(() => logs.value.length, () => {
logContainer.value.scrollTop = logContainer.value.scrollHeight; logContainer.value.scrollTop = logContainer.value.scrollHeight;
} }
}); });
}
}); });
// 暴露方法和数据 // 暴露方法和数据
@@ -207,7 +202,7 @@ onBeforeUnmount(() => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/assets/styles/_variables.scss'; @use "@/assets/styles/variables" as *;
.status-item { .status-item {
display: flex; display: flex;

View File

@@ -1,37 +0,0 @@
{
"id": "主键",
"processInfoId": "加工信息ID",
"pltNo": "PLT No",
"processF1": "加工F1",
"processF2": "加工F2",
"goodProductF1": "良品F1",
"goodProductF2": "良品F2",
"electricalResult": "电气检测结果",
"engraveResult": "印字检测结果",
"qrCode": "二维码",
"qrCodeLevel": "二维码等级",
"pressure15Riveting": "压力1_5#_铆接",
"height15Riveting": "高度1_5#_铆接",
"pressure25Magnet1": "压力2_5#_磁石1",
"height25Magnet1": "高度2_5#_磁石1",
"pressure36Magnet2": "压力3_6#_磁石2",
"height36Magnet2": "高度3_6#_磁石2",
"torque47AxisInsert": "扭矩4_7#_轴旋入",
"height47AxisInsert": "高度4_7#_轴旋入",
"pressure58LowerCase": "压力5_8#_下壳装入",
"height58LowerCase": "高度5_8#_下壳装入",
"pressure69UpperCase": "压力6_9#_上壳装入",
"height69UpperCase": "高度6_9#_上壳装入",
"height79HeightCheck": "高度7_9#_高度检测",
"pressure79Laser": "压力7_9#_激光",
"height89Laser": "高度8_9#_激光",
"value19DcrUpper": "数値1_9#_DCR(上)",
"value29DcrLower": "数値2_9#_DCR(下)",
"value39LcrUpperLs": "数値3_9#_LCR(上)LS",
"value49LcrLowerQ": "数値4_9#_LCR(下)Q",
"value59LcrLowerLs": "数値5_9#_LCR(下)LS",
"value69LcrLowerQ": "数値6_9#_LCR(下)Q",
"value79IrR": "数値7_9#_IR R",
"value89IrI": "数値8_9#_IR I",
"createTime": "创建时间"
}

View File

@@ -1,5 +1,4 @@
import { createApp } from "vue"; import { createApp } from "vue";
// import Antd from "ant-design-vue";
import App from "./App.vue"; import App from "./App.vue";
// Pinia 状态管理 // Pinia 状态管理
@@ -11,7 +10,7 @@ import router from "./router";
// 样式文件 // 样式文件
import "ant-design-vue/dist/reset.css"; import "ant-design-vue/dist/reset.css";
import "./assets/styles/index.scss"; import "@/assets/styles/index.scss";
const app = createApp(App); const app = createApp(App);
app.use(pinia).use(router).mount("#app"); app.use(pinia).use(router).mount("#app");

View File

@@ -15,6 +15,16 @@ const routes = [
path: '/package-station', path: '/package-station',
name: 'PackageStation', name: 'PackageStation',
component: () => import('@/views/package-station/index.vue') component: () => import('@/views/package-station/index.vue')
},
{
path: '/L1-data',
name: 'L1Data',
component: () => import('@/views/L1-data-list/index.vue')
},
{
path: '/L4-data',
name: 'L4Data',
component: () => import('@/views/L4-data-list/index.vue')
} }
]; ];

5
src/shim-vue.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}

View File

@@ -3,7 +3,8 @@ import { EventSourcePolyfill } from 'event-source-polyfill';
import { generateUUID } from './uuidUtils'; import { generateUUID } from './uuidUtils';
import { getToken } from '@/utils/auth'; import { getToken } from '@/utils/auth';
const defaultServerUrl = 'http://192.168.1.38:18081/sse'; // const defaultServerUrl = 'http://192.168.1.38:18081/sse';
const defaultServerUrl = 'http://127.0.0.1:18081/sse';
interface EventData { interface EventData {
timestamp: string; timestamp: string;
@@ -66,7 +67,7 @@ export function useSSE(options: SSEOptions) {
let connectionTimeout: number | null = null; let connectionTimeout: number | null = null;
// 计算属性 // 计算属性
const sseStatusText = computed(() => { const sseStatus = computed(() => {
if (isConnecting.value) return '连接中'; if (isConnecting.value) return '连接中';
if (isConnected.value) return '已连接'; if (isConnected.value) return '已连接';
return '未连接'; return '未连接';
@@ -234,7 +235,7 @@ export function useSSE(options: SSEOptions) {
isConnected, isConnected,
clientId, clientId,
serverUrl, serverUrl,
sseStatusText, sseStatus,
sseStatusClass, sseStatusClass,
connect, connect,
disconnect, disconnect,

View File

@@ -0,0 +1,48 @@
import type { TableColumnType, TableColumnsType } from "ant-design-vue";
const INDEX_COLUMN: TableColumnType = {
key: "index",
title: "序号",
width: 60,
fixed: true,
align: "center",
} as const;
// 定义操作列配置
const ACTION_COLUMN: TableColumnType = {
key: "action",
title: "操作",
width: 120,
ellipsis: true,
fixed: "right",
align: "center",
} as const;
// 使用 Record 类型确保键值对的安全性
export const fields: Record<string, string> = {
productStatus: "产品状态",
goodFlag: "良品标记",
processMemory: "加工记忆",
functionSw: "功能SW",
axisNumber: "轴号",
plugTerminalPressure: "插端子压力值",
plugTerminalHeight: "插端子高度值",
resistance: "电阻值",
inductorLs: "电感LS值",
inductorQ: "电感Q值",
pressureResistanceR: "耐压R值",
pressureResistanceI: "耐压I值",
visualResult: "视觉结果",
createTime: "创建时间",
} as const;
// 导出完整的列配置
export const columns: TableColumnsType = [
INDEX_COLUMN,
...Object.entries(fields).map(([key, title]) => ({
key,
title,
ellipsis: true,
})),
ACTION_COLUMN,
];

View File

@@ -0,0 +1,347 @@
<template>
<div class="l1-data-container">
<div class="page-header">
<h1 class="page-title">L1数据列表</h1>
</div>
<!-- 筛选区域 -->
<div class="filter-section">
<a-form layout="inline" :model="formData">
<a-form-item label="日期范围">
<a-range-picker v-model:value="formData.dateRange" :placeholder="['开始日期', '结束日期']"
format="YYYY-MM-DD hh:mm:ss" class="date-range-picker" show-time />
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleSearch" :loading="loading">
<template #icon>
<i-lucide-search />
</template>
搜索
</a-button>
<a-button @click="handleReset">
<template #icon>
<i-lucide-refresh-cw />
</template>
重置
</a-button>
</a-space>
</a-form-item>
</a-form>
</div>
<!-- 数据表格 -->
<div class="table-section">
<a-table :columns="columns" :data-source="tableData" :loading="loading" :pagination="pagination"
@change="handleTableChange" row-key="id" size="middle" :scroll="{ x: 1200 }">
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-else-if="column.key === 'createTime'">
<span>{{ formatDateTime(record.createTime) }}</span>
</template>
<!-- <template v-else-if="column.key === 'productStatus'">
<a-tag :color="record.productStatus === 1 ? 'success' : 'default'">
{{ record.productStatus === 1 ? '有' : '无' }}
</a-tag>
</template>
<template v-else-if="column.key === 'goodFlag'">
<a-tag :color="record.goodFlag === 1 ? 'success' : 'error'">
{{ record.goodFlag === 1 ? '良品' : '不良品' }}
</a-tag>
</template>
<template v-else-if="column.key === 'processMemory'">
<a-tag :color="record.processMemory === 1 ? 'success' : 'default'">
{{ record.processMemory === 1 ? '已加工' : '未开工' }}
</a-tag>
</template>
<template v-else-if="column.key === 'functionSw'">
<a-tag :color="record.functionSw === 1 ? 'success' : 'default'">
{{ record.functionSw === 1 ? '有效' : '无效' }}
</a-tag>
</template>
<template v-else-if="column.key === 'visualResult'">
<a-tag :color="record.visualResult === 1 ? 'success' : 'error'">
{{ record.visualResult === 1 ? 'OK' : 'NG' }}
</a-tag>
</template> -->
<template v-else-if="column.key === 'action'">
<a-button type="link" size="small" @click="handleView(record as L1Data)">
查看详情
</a-button>
</template>
<template v-else>
{{ record[column.key as string] }}
</template>
</template>
</a-table>
</div>
<!-- 详情弹窗 -->
<a-modal v-model:open="detailVisible" title="详情信息" :footer="null" width="800px">
<div v-if="selectedRecord" class="detail-content">
<a-descriptions :column="2" bordered>
<a-descriptions-item
v-for="(value, key) in selectedRecord"
:key="key"
:label="columns.find((col) => col.key === key)?.title || key"
>
<template v-if="key === 'createTime'">
{{ formatDateTime(value) }}
</template>
<!-- <template v-else-if="key === 'productStatus'">
<a-tag :color="selectedRecord.productStatus === 1 ? 'success' : 'default'">
{{ selectedRecord.productStatus === 1 ? '有' : '无' }}
</a-tag>
</template>
<template v-else-if="key === 'goodFlag'">
<a-tag :color="selectedRecord.goodFlag === 1 ? 'success' : 'error'">
{{ selectedRecord.goodFlag === 1 ? '良品' : '不良品' }}
</a-tag>
</template>
<template v-else-if="key === 'processMemory'">
<a-tag :color="selectedRecord.processMemory === 1 ? 'success' : 'default'">
{{ selectedRecord.processMemory === 1 ? '已加工' : '未开工' }}
</a-tag>
</template>
<template v-else-if="key === 'functionSw'">
<a-tag :color="selectedRecord.functionSw === 1 ? 'success' : 'default'">
{{ selectedRecord.functionSw === 1 ? '有效' : '无效' }}
</a-tag>
</template>
<template v-else-if="key === 'visualResult'">
<a-tag :color="selectedRecord.visualResult === 1 ? 'success' : 'error'">
{{ selectedRecord.visualResult === 1 ? 'OK' : 'NG' }}
</a-tag>
</template> -->
<template v-else>
{{ value }}
</template>
</a-descriptions-item>
</a-descriptions>
</div>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from "vue";
import { message } from "ant-design-vue";
import { getL1Data } from "@/api/data";
import type { QueryParams } from "@/api/data/model";
import type { L1Data } from "./types";
import type { Dayjs } from "dayjs";
import { columns } from "./data";
type DateRangeType = [Dayjs, Dayjs] | undefined;
// 响应式数据
const loading = ref(false);
const tableData = ref<L1Data[]>([]);
const formData = reactive<{ dateRange: DateRangeType }>({
dateRange: undefined,
});
const detailVisible = ref(false);
const selectedRecord = ref<L1Data | null>(null);
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `${total}`,
pageSizeOptions: ["10", "20", "50", "100"],
});
// 获取数据
const fetchData = async () => {
loading.value = true;
try {
const params: QueryParams = {
pageNum: pagination.current,
pageSize: pagination.pageSize,
};
// 添加日期范围筛选
if (formData.dateRange && formData.dateRange.length === 2) {
params.createTimeBegin = formData.dateRange[0].format("YYYY-MM-DD hh:mm:ss");
params.createTimeEnd = formData.dateRange[1].format("YYYY-MM-DD hh:mm:ss");
}
const res = await getL1Data(params);
if (res.code === 200) {
tableData.value = res.rows || [];
pagination.total = res.total || 0;
} else {
message.error(res.msg || "获取数据失败");
}
} catch (error) {
console.error("获取L1数据失败:", error);
message.error("获取数据失败,请稍后重试");
} finally {
loading.value = false;
}
};
// 搜索处理
const handleSearch = () => {
pagination.current = 1;
fetchData();
};
// 重置处理
const handleReset = () => {
formData.dateRange = undefined;
pagination.current = 1;
fetchData();
};
// 表格变化处理(分页、排序等)
const handleTableChange = (pag: any, filters: any, sorter: any) => {
pagination.current = pag.current;
pagination.pageSize = pag.pageSize;
// 处理排序
if (sorter.field) {
// 这里可以根据需要添加排序逻辑
}
fetchData();
};
// 查看详情
const handleView = (record: L1Data) => {
selectedRecord.value = record;
detailVisible.value = true;
};
// 格式化日期时间
const formatDateTime = (dateTime: string | number) => {
if (!dateTime) return null;
return new Date(dateTime).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
};
// 组件挂载时获取数据
onMounted(() => {
fetchData();
});
</script>
<style scoped lang="scss">
@use "@/assets/styles/variables" as *;
.wrapper {
width: 100%;
height: 100%;
overflow: hidden;
}
.l1-data-container {
display: flex;
flex-direction: column;
height: 100vh;
padding: $spacing-lg;
.page-header {
margin-bottom: $spacing-lg;
flex-shrink: 0;
.page-title {
font-size: $title-font-size;
color: $text-dark;
margin: 0;
font-weight: 600;
}
}
.filter-section {
background: $bg-light;
padding: $spacing-lg;
border-radius: $border-radius-lg;
margin-bottom: $spacing-lg;
flex-shrink: 0;
.filter-label {
font-weight: 500;
color: $text-dark;
font-size: $content-font-size;
}
}
.table-section {
overflow: hidden;
display: flex;
flex-direction: column;
:deep(.ant-table-wrapper) {
height: 100%;
display: flex;
flex-direction: column;
.ant-spin-container,
.ant-table {
flex-grow: 1;
display: flex;
flex-direction: column;
}
.ant-table-container {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.ant-table-body {
overflow-y: auto !important;
}
}
}
.detail-content {
.ant-descriptions {
margin-top: $spacing-md;
}
}
}
:deep(.ant-btn-primary) {
background: $primary-color;
border-color: $primary-color;
&:hover {
background: $primary-light;
border-color: $primary-light;
}
}
@media (max-width: 768px) {
.l1-data-container {
padding: $spacing-md;
.filter-section {
.ant-row {
flex-direction: column;
gap: $spacing-md;
.ant-col {
width: 100% !important;
}
}
.date-range-picker {
width: 100%;
}
}
}
}
</style>

View File

@@ -0,0 +1,58 @@
export interface L1Data {
/** 加工信息ID */
processInfoId: number;
/** 产品状态: 0-无, 1-有 */
productStatus: 0 | 1;
/** 良品标记: 0-不良品, 1-良品 */
goodFlag: 0 | 1;
/** 加工记忆: 0-未开工, 1-已加工 */
processMemory: 0 | 1;
/** 功能SW: 0-无效, 1-有效 */
functionSw: 0 | 1;
/** 轴号 */
axisNumber: number;
/** 上骨架时间: 年, 00~99 */
loadSkeletonYear: number;
/** 上骨架时间: 月日, 0101~1231 */
loadSkeletonMonthDay: number;
/** 上骨架时间: 时分, 0000~2459 */
loadSkeletonHourMin: number;
/** 上骨架时间: 秒, 00~59 */
loadSkeletonSecond: number;
/** 插端子压力值 */
plugTerminalPressure: number;
/** 插端子高度值 */
plugTerminalHeight: number;
/** 电阻值 */
resistance: number;
/** 电感LS值 */
inductorLs: number;
/** 电感Q值 */
inductorQ: number;
/** 耐压R值 */
pressureResistanceR: number;
/** 耐压I值 */
pressureResistanceI: number;
/** 视觉结果: 0-NG, 1-OK */
visualResult: 0 | 1;
/** 创建时间 */
createTime: string; // ISO 8601 格式,如 "2025-09-23T12:34:56"
}

View File

@@ -0,0 +1,69 @@
import type { TableColumnType, TableColumnsType } from "ant-design-vue";
const INDEX_COLUMN: TableColumnType = {
key: "index",
title: "序号",
width: 60,
fixed: true,
align: "center",
} as const;
// 定义操作列配置
const ACTION_COLUMN: TableColumnType = {
key: "action",
title: "操作",
width: 120,
ellipsis: true,
fixed: "right",
align: "center",
} as const;
// 使用 Record 类型确保键值对的安全性
export const fields: Record<string, string> = {
processInfoId: "加工信息ID",
pltNo: "PLT No",
processF1: "加工F1",
processF2: "加工F2",
goodProductF1: "良品F1",
goodProductF2: "良品F2",
electricalResult: "电气检测结果",
engraveResult: "印字检测结果",
qrCode: "二维码",
qrCodeLevel: "二维码等级",
pressure15Riveting: "压力1_5#_铆接",
height15Riveting: "高度1_5#_铆接",
pressure25Magnet1: "压力2_5#_磁石1",
height25Magnet1: "高度2_5#_磁石1",
pressure36Magnet2: "压力3_6#_磁石2",
height36Magnet2: "高度3_6#_磁石2",
torque47AxisInsert: "扭矩4_7#_轴旋入",
height47AxisInsert: "高度4_7#_轴旋入",
pressure58LowerCase: "压力5_8#_下壳装入",
height58LowerCase: "高度5_8#_下壳装入",
pressure69UpperCase: "压力6_9#_上壳装入",
height69UpperCase: "高度6_9#_上壳装入",
height79HeightCheck: "高度7_9#_高度检测",
pressure79Laser: "压力7_9#_激光",
height89Laser: "高度8_9#_激光",
value19DcrUpper: "数値1_9#_DCR(上)",
value29DcrLower: "数値2_9#_DCR(下)",
value39LcrUpperLs: "数値3_9#_LCR(上)LS",
value49LcrLowerQ: "数値4_9#_LCR(下)Q",
value59LcrLowerLs: "数値5_9#_LCR(下)LS",
value69LcrLowerQ: "数値6_9#_LCR(下)Q",
value79IrR: "数値7_9#_IR R",
value89IrI: "数値8_9#_IR I",
createTime: "创建时间"
} as const;
// 导出完整的列配置
export const columns: TableColumnsType = [
INDEX_COLUMN,
...Object.entries(fields).map(([key, title]) => ({
key,
title,
width: 150,
ellipsis: true,
})),
ACTION_COLUMN,
];

View File

@@ -0,0 +1,336 @@
<template>
<div class="l4-data-container">
<div class="page-header">
<h1 class="page-title">L4数据列表</h1>
</div>
<!-- 筛选区域 -->
<div class="filter-section">
<a-form layout="inline" :model="formData">
<a-form-item label="日期范围">
<a-range-picker
v-model:value="formData.dateRange"
:placeholder="['开始日期', '结束日期']"
format="YYYY-MM-DD hh:mm:ss"
class="date-range-picker"
show-time
/>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleSearch" :loading="loading">
<template #icon>
<i-lucide-search />
</template>
搜索
</a-button>
<a-button @click="handleReset">
<template #icon>
<i-lucide-refresh-cw />
</template>
重置
</a-button>
</a-space>
</a-form-item>
</a-form>
</div>
<!-- 数据表格 -->
<div class="table-section">
<a-table
:columns="columns"
:data-source="tableData"
:loading="loading"
:pagination="pagination"
@change="handleTableChange"
row-key="id"
size="middle"
:scroll="{ x: 1200 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'createTime'">
{{ formatDateTime(record.createTime) }}
</template>
<!-- <template v-else-if="column.key === 'processF1'">
<a-tag :color="record.processF1 === 1 ? 'green' : 'red'">
{{ record.processF1 === 1 ? '已加工' : '未加工' }}
</a-tag>
</template>
<template v-else-if="column.key === 'processF2'">
<a-tag :color="record.processF2 === 1 ? 'green' : 'red'">
{{ record.processF2 === 1 ? '已加工' : '未加工' }}
</a-tag>
</template>
<template v-else-if="column.key === 'goodProductF1'">
<a-tag :color="record.goodProductF1 === 1 ? 'green' : 'red'">
{{ record.goodProductF1 === 1 ? '良品' : '不良品' }}
</a-tag>
</template>
<template v-else-if="column.key === 'goodProductF2'">
<a-tag :color="record.goodProductF2 === 1 ? 'green' : 'red'">
{{ record.goodProductF2 === 1 ? '良品' : '不良品' }}
</a-tag>
</template>
<template v-else-if="column.key === 'electricalResult'">
<a-tag :color="getElectricalResultColor(record.electricalResult)">
{{ getElectricalResultText(record.electricalResult) }}
</a-tag>
</template>
<template v-else-if="column.key === 'engraveResult'">
<a-tag :color="record.engraveResult === 0 ? 'green' : 'red'">
{{ record.engraveResult === 0 ? '良品' : '不良品' }}
</a-tag>
</template> -->
<template v-else-if="column.key === 'action'">
<a-button type="link" size="small" @click="handleView(record as L4Data)">
查看详情
</a-button>
</template>
</template>
</a-table>
</div>
<!-- 详情弹窗 -->
<a-modal
v-model:open="detailModalVisible"
title="L4数据详情"
width="800px"
:footer="null"
>
<div v-if="selectedRecord" class="detail-content">
<a-descriptions :column="2" bordered>
<a-descriptions-item
v-for="(value, key) in selectedRecord"
:key="key"
:label="columns.find((col) => col.key === key)?.title || key"
>
<template v-if="key === 'createTime'">
{{ formatDateTime(value) }}
</template>
<!-- <template v-else-if="key === 'processF1'">
<a-tag :color="value === 1 ? 'green' : 'red'">
{{ value === 1 ? '已加工' : '未加工' }}
</a-tag>
</template>
<template v-else-if="key === 'processF2'">
<a-tag :color="value === 1 ? 'green' : 'red'">
{{ value === 1 ? '已加工' : '未加工' }}
</a-tag>
</template>
<template v-else-if="key === 'goodProductF1'">
<a-tag :color="value === 1 ? 'green' : 'red'">
{{ value === 1 ? '良品' : '不良品' }}
</a-tag>
</template>
<template v-else-if="key === 'goodProductF2'">
<a-tag :color="value === 1 ? 'green' : 'red'">
{{ value === 1 ? '良品' : '不良品' }}
</a-tag>
</template>
<template v-else-if="key === 'electricalResult'">
<a-tag :color="getElectricalResultColor(value)">
{{ getElectricalResultText(value) }}
</a-tag>
</template>
<template v-else-if="key === 'engraveResult'">
<a-tag :color="value === 0 ? 'green' : 'red'">
{{ value === 0 ? '良品' : '不良品' }}
</a-tag>
</template> -->
<template v-else>
{{ value }}
</template>
</a-descriptions-item>
</a-descriptions>
</div>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import { getL4Data } from '@/api/data';
import { columns } from './data';
import type { QueryParams } from '@/api/data/model';
import type { L4Data } from './types';
import type { Dayjs } from 'dayjs';
type DateRangeType = [Dayjs, Dayjs] | undefined;
// 响应式数据
const loading = ref(false);
const tableData = ref<L4Data[]>([]);
const detailModalVisible = ref(false);
const selectedRecord = ref<L4Data | null>(null);
// 筛选参数
const formData = reactive<{ dateRange: DateRangeType }>({
dateRange: undefined,
});
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number, range: [number, number]) =>
`${total} 条记录,当前显示第 ${range[0]}-${range[1]}`,
});
// 电气检测结果颜色
const getElectricalResultColor = (result: number) => {
switch (result) {
case 7: return 'green';
case 0: return 'red';
case 1: return 'orange';
case 3: return 'purple';
default: return 'default';
}
};
// 电气检测结果文本
const getElectricalResultText = (result: number) => {
switch (result) {
case 0: return 'DCR不良';
case 1: return 'LCR不良';
case 3: return 'IR不良';
case 7: return '良品';
default: return '未知';
}
};
// 获取数据
const fetchData = async () => {
loading.value = true;
try {
const params: QueryParams = {
pageNum: pagination.current,
pageSize: pagination.pageSize,
};
// 添加日期范围筛选
if (formData.dateRange && formData.dateRange.length === 2) {
params.createTimeBegin = formData.dateRange[0].format("YYYY-MM-DD hh:mm:ss");
params.createTimeEnd = formData.dateRange[1].format("YYYY-MM-DD hh:mm:ss");
}
const response = await getL4Data(params);
if (response.code === 200) {
tableData.value = response.rows || [];
pagination.total = response.total || 0;
} else {
message.error(response.msg || "获取数据失败");
}
} catch (error) {
console.error("获取L4数据失败:", error);
message.error("获取数据失败,请稍后重试");
} finally {
loading.value = false;
}
};
// 搜索处理
const handleSearch = () => {
pagination.current = 1;
fetchData();
};
// 重置处理
const handleReset = () => {
formData.dateRange = undefined;
pagination.current = 1;
fetchData();
};
// 表格变化处理(分页、排序等)
const handleTableChange = (paginationInfo: any) => {
pagination.current = paginationInfo.current;
pagination.pageSize = paginationInfo.pageSize;
fetchData();
};
// 查看详情
const handleView = (record: L4Data) => {
selectedRecord.value = record;
detailModalVisible.value = true;
};
// 日期时间格式化
const formatDateTime = (dateTime: string | number) => {
if (!dateTime) return '-';
return new Date(dateTime).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
};
// 组件挂载时获取数据
onMounted(() => {
fetchData();
});
</script>
<style scoped>
.l4-data-container {
padding: 20px;
background: #f5f5f5;
min-height: 100vh;
}
.page-header {
margin-bottom: 20px;
}
.page-title {
font-size: 24px;
font-weight: 600;
color: #1f2937;
margin: 0;
}
.filter-section {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
}
.date-range-picker {
width: 300px;
}
.table-section {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.detail-content {
max-height: 500px;
overflow-y: auto;
}
:deep(.ant-table-thead > tr > th) {
background: #f8fafc;
font-weight: 600;
}
:deep(.ant-table-tbody > tr:hover > td) {
background: #f0f9ff;
}
:deep(.ant-descriptions-item-label) {
font-weight: 600;
background: #f8fafc;
}
</style>

View File

@@ -0,0 +1,106 @@
export interface L4Data {
/** 加工信息ID */
processInfoId: number;
/** PLT No */
pltNo: number;
/** 加工F1, 0未加工 */
processF1: 0 | 1;
/** 加工F2, 1已加工 */
processF2: 0 | 1;
/** 良品F1, 0不良品 */
goodProductF1: 0 | 1;
/** 良品F2, 1良品 */
goodProductF2: 0 | 1;
/**
* 电气检测结果
* 0-DCR不良, 1-LCR不良, 3-IR不良, 7-良品
*/
electricalResult: 0 | 1 | 3 | 7;
/** 印字检测结果, 0-良品, 1-不良品 */
engraveResult: 0 | 1;
/** 二维码 */
qrCode: string;
/** 二维码等级 */
qrCodeLevel: string;
/** 压力1_5#_铆接 */
pressure15Riveting: number;
/** 高度1_5#_铆接 */
height15Riveting: number;
/** 压力2_5#_磁石1 */
pressure25Magnet1: number;
/** 高度2_5#_磁石1 */
height25Magnet1: number;
/** 压力3_6#_磁石2 */
pressure36Magnet2: number;
/** 高度3_6#_磁石2 */
height36Magnet2: number;
/** 扭矩4_7#_轴旋入 */
torque47AxisInsert: number;
/** 高度4_7#_轴旋入 */
height47AxisInsert: number;
/** 压力5_8#_下壳装入 */
pressure58LowerCase: number;
/** 高度5_8#_下壳装入 */
height58LowerCase: number;
/** 压力6_9#_上壳装入 */
pressure69UpperCase: number;
/** 高度6_9#_上壳装入 */
height69UpperCase: number;
/** 高度7_9#_高度检测 */
height79HeightCheck: number;
/** 压力7_9#_激光 */
pressure79Laser: number;
/** 高度8_9#_激光 */
height89Laser: number;
/** 数値1_9#_DCR(上) */
value19DcrUpper: number;
/** 数値2_9#_DCR(下) */
value29DcrLower: number;
/** 数値3_9#_LCR(上)LS */
value39LcrUpperLs: number;
/** 数値4_9#_LCR(下)Q */
value49LcrLowerQ: number;
/** 数値5_9#_LCR(下)LS */
value59LcrLowerLs: number;
/** 数値6_9#_LCR(下)Q */
value69LcrLowerQ: number;
/** 数値7_9#_IR R */
value79IrR: number;
/** 数値8_9#_IR I */
value89IrI: number;
/** 创建时间 (ISO 8601) */
createTime: string;
}

View File

@@ -3,6 +3,7 @@ import { ref, onMounted, onBeforeUnmount, reactive, nextTick } from 'vue';
import { useRealTime } from '@/utils/dateUtils'; import { useRealTime } from '@/utils/dateUtils';
import { checkOrderNumberApi, addProcessInfoApi } from '@/api/detect'; import { checkOrderNumberApi, addProcessInfoApi } from '@/api/detect';
import type { Rule } from 'ant-design-vue/es/form'; import type { Rule } from 'ant-design-vue/es/form';
import { message } from 'ant-design-vue';
// 检测设备表单规则 // 检测设备表单规则
const rules: Record<string, Rule[]> = { const rules: Record<string, Rule[]> = {
@@ -44,23 +45,9 @@ const detectFormRef = ref();
const packageFormRef = ref(); const packageFormRef = ref();
// 执行结果日志 // 执行结果日志
const executionLogs = ref([ const executionLogs = ref<string[]>([]);
'14:25:32 - 开始检测流程 SN-A538-09-2023-00018',
'14:25:33 - 读取产品参数完成',
'14:25:36 - 尺寸检测: 通过 (0.15mm)',
'14:25:36 - 电压检测: 通过 (3.25V)',
'14:25:37 - 精度检测: 通过 (98.7%)',
'14:25:38 - 检测结果: 合格'
]);
const packageLogs = ref([ const packageLogs = ref<string[]>([]);
'14:25:45 - 包装流程: SN-A538-09-2023-00018',
'14:25:46 - 读取产品标签完成',
'14:25:49 - 包装材料准备完成',
'14:25:52 - 产品包装完成',
'14:25:53 - 更新库存: 已包装 (18/25)',
'14:25:54 - 包装结果: 正常'
]);
// 滚动到检测设备日志底部的函数 // 滚动到检测设备日志底部的函数
const scrollToExecutionBottom = () => { const scrollToExecutionBottom = () => {
@@ -262,6 +249,7 @@ const saveDetectingEdit = () => {
fixtureCode: detectForm.fixtureCode fixtureCode: detectForm.fixtureCode
}).then(res => { }).then(res => {
console.log(res) console.log(res)
message.success('保存成功');
}) })
}).catch(() => { }).catch(() => {
console.log('检测设备表单验证失败'); console.log('检测设备表单验证失败');
@@ -350,6 +338,9 @@ onBeforeUnmount(() => {
:on-mes-event="handleMESEvent" :on-mes-event="handleMESEvent"
:on-sse-message="handleSseMessage" :on-sse-message="handleSseMessage"
/> />
<!-- LMS 状态 -->
<LmsStatus />
</div> </div>
<SettingsModal /> <SettingsModal />
<!-- <div class="right-info" @click="showSettings"> <!-- <div class="right-info" @click="showSettings">
@@ -569,7 +560,7 @@ onBeforeUnmount(() => {
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
@import '@/assets/styles/_variables.scss'; @use "@/assets/styles/variables" as *;
.hmi-container { .hmi-container {
width: 100vw; width: 100vw;

View File

@@ -8,6 +8,7 @@
"composite": true, "composite": true,
/* Bundler mode */ /* Bundler mode */
"noEmit": true,
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
@@ -21,6 +22,7 @@
}, },
"include": [ "include": [
"vite.config.ts", "vite.config.ts",
"src/**/*.d.ts" "src/**/*.d.ts",
"src/shim-vue.d.ts"
] ]
} }

View File

@@ -1,4 +1,4 @@
import { defineConfig, loadEnv } from 'vite'; import { defineConfig } from 'vite';
import Components from "unplugin-vue-components/vite"; // 按需组件自动导入 import Components from "unplugin-vue-components/vite"; // 按需组件自动导入
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers"; import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
@@ -8,19 +8,38 @@ import path from 'path';
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
},
},
server: {
proxy: {
"/api": {
target: "http://192.168.1.38:18081",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
"/prod-api": {
target: "http://127.0.0.1:18081",
changeOrigin: true,
},
},
},
plugins: [ plugins: [
vue(), vue(),
Components({ Components({
dts: true, //生成components.d.ts 全局定义文件 dts: true, //生成components.d.ts 全局定义文件
resolvers: [ resolvers: [
AntDesignVueResolver({ //对使用到的全局ant design vue组件进行类型导入 AntDesignVueResolver({
//对使用到的全局ant design vue组件进行类型导入
importStyle: false, // 不动态引入css,这个不强求 importStyle: false, // 不动态引入css,这个不强求
}), }),
// 自动导入 lucide 图标 // 自动导入 lucide 图标
IconsResolver({ IconsResolver({
prefix: 'i', // 比如用 <i-a-arrow-down /> prefix: "i", // 比如用 <i-a-arrow-down />
enabledCollections: ['lucide'] enabledCollections: ["lucide"],
}) }),
], ],
include: [/\.vue$/, /\.vue\?vue/, /\.md$/, /\.tsx$/], //包含的文件类型 include: [/\.vue$/, /\.vue\?vue/, /\.md$/, /\.tsx$/], //包含的文件类型
}), }),
@@ -28,18 +47,4 @@ export default defineConfig({
autoInstall: true, // 没安装的图标库会自动下载 autoInstall: true, // 没安装的图标库会自动下载
}), }),
], ],
resolve: { });
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
server: {
proxy: {
'/api': {
target: 'http://192.168.1.38:18081',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
})