Compare commits
14 Commits
674c1c715d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f3ed5fe3c | ||
|
|
9f7ba7b18f | ||
|
|
e80b9c95dc | ||
|
|
ac35b819a7 | ||
|
|
f58d71ee05 | ||
|
|
3b68a6af49 | ||
|
|
29f80a48ef | ||
|
|
dc1ad04ccf | ||
|
|
3a10c71774 | ||
|
|
1b60395899 | ||
|
|
594ab0186c | ||
|
|
ea26dc64ab | ||
|
|
3097d01381 | ||
|
|
735c1ca6c3 |
@@ -2,4 +2,5 @@
|
||||
VITE_APP_BASE_API=/api
|
||||
|
||||
|
||||
VITE_APP_BASE_URL=http://192.168.1.38:18081
|
||||
VITE_APP_BASE_URL=http://192.168.1.143:18081
|
||||
# VITE_APP_BASE_URL=http://192.168.1.38:18081
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,3 +22,4 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
*.zip
|
||||
19
package-lock.json
generated
19
package-lock.json
generated
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "rd_mes_front_hmi",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "rd_mes_front_hmi",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"axios": "^1.10.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"pinia": "^3.0.3",
|
||||
"unplugin-vue-components": "^28.7.0",
|
||||
"vue": "^3.5.13",
|
||||
@@ -18,6 +19,7 @@
|
||||
"devDependencies": {
|
||||
"@iconify-json/lucide": "^1.2.66",
|
||||
"@types/event-source-polyfill": "^1.0.5",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^24.0.3",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
@@ -935,6 +937,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/file-saver": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/@types/file-saver/-/file-saver-2.0.7.tgz",
|
||||
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/js-cookie": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-3.0.6.tgz",
|
||||
@@ -1644,6 +1653,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/file-saver": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
|
||||
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"axios": "^1.10.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"pinia": "^3.0.3",
|
||||
"unplugin-vue-components": "^28.7.0",
|
||||
"vue": "^3.5.13",
|
||||
@@ -19,6 +20,7 @@
|
||||
"devDependencies": {
|
||||
"@iconify-json/lucide": "^1.2.66",
|
||||
"@types/event-source-polyfill": "^1.0.5",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^24.0.3",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
|
||||
1
src/api/common/model.d.ts
vendored
1
src/api/common/model.d.ts
vendored
@@ -4,4 +4,5 @@ export interface ApiResponse<T = any> {
|
||||
data?: T;
|
||||
rows?: T[];
|
||||
total?: number;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
30
src/api/line1/data/index.ts
Normal file
30
src/api/line1/data/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import request from '@/api/request';
|
||||
import type { ExportParams, QueryParams } from "./model";
|
||||
|
||||
// 获取 L1 数据
|
||||
export function getL1Data(params: QueryParams) {
|
||||
return request({
|
||||
url: "/jinghua/l1OneData/list",
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取 报警记录 数据
|
||||
export function getAlarmRecords(params: QueryParams) {
|
||||
return request({
|
||||
url: '/jinghua/alarmRecord/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
export function exportData(data: ExportParams) {
|
||||
return request({
|
||||
url: "/jinghua/l1OneData/export",
|
||||
method: "post",
|
||||
data,
|
||||
responseType: "blob",
|
||||
});
|
||||
}
|
||||
14
src/api/line1/data/model.d.ts
vendored
Normal file
14
src/api/line1/data/model.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface QueryParams {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
orderByColumn?: string;
|
||||
isAsc?: string;
|
||||
qrCode?: string;
|
||||
createTimeBegin?: string;
|
||||
createTimeEnd?: string;
|
||||
}
|
||||
|
||||
export interface ExportParams {
|
||||
createTimeBegin?: string;
|
||||
createTimeEnd?: string;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import request from '../request';
|
||||
import request from '@/api/request';
|
||||
import type { QueryParams } from './model';
|
||||
|
||||
// 获取 L1 数据
|
||||
@@ -17,4 +17,13 @@ export function getL4Data(params: QueryParams) {
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 报警记录 数据
|
||||
export function getAlarmRecords(params: QueryParams) {
|
||||
return request({
|
||||
url: '/jinghua/alarmRecord/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import axios from 'axios';
|
||||
import router from '@/router';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import { getToken, removeToken } from '@/utils/auth';
|
||||
import { Modal, notification } from 'ant-design-vue';
|
||||
import type { AxiosRequestConfig } from "axios";
|
||||
import type { AxiosInstance, AxiosRequestConfig } from "axios";
|
||||
import type { ApiResponse } from "@/api/common/model";
|
||||
|
||||
const errCodeMap: { [key: string]: string } = {
|
||||
@@ -12,7 +12,7 @@ const errCodeMap: { [key: string]: string } = {
|
||||
};
|
||||
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
const service: AxiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
timeout: 10000,
|
||||
});
|
||||
@@ -37,33 +37,39 @@ service.interceptors.request.use(
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
// console.log(response);
|
||||
if (response.config.responseType === "blob") {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
const code = response.data.code || 200;
|
||||
const data = response.data;
|
||||
|
||||
switch (code) {
|
||||
case 200:
|
||||
return data ?? response;
|
||||
return data;
|
||||
case 401:
|
||||
Modal.error({
|
||||
title: '系统提示',
|
||||
content: '登录状态已过期,请重新登录',
|
||||
title: "系统提示",
|
||||
content: "登录状态已过期,请重新登录",
|
||||
onOk: () => {
|
||||
router.push('/login')
|
||||
}
|
||||
})
|
||||
removeToken();
|
||||
router.replace('/login');
|
||||
},
|
||||
});
|
||||
return Promise.reject(data);
|
||||
default:
|
||||
const errMsg = errCodeMap[code] || data.msg || errCodeMap['default'];
|
||||
notification.error({
|
||||
message: '请求错误',
|
||||
description: errMsg,
|
||||
message: "请求错误",
|
||||
description: errCodeMap[code] || data?.msg || errCodeMap.default,
|
||||
});
|
||||
return Promise.reject(data);
|
||||
}
|
||||
},
|
||||
error => {
|
||||
error.message = error.code === "ECONNABORTED" ? '请求超时,请稍后重试' : error.message;
|
||||
(error) => {
|
||||
error.message =
|
||||
error.code === "ECONNABORTED"
|
||||
? "请求超时,请稍后重试"
|
||||
: error.message || "网络异常";
|
||||
|
||||
// 网络/服务器错误统一处理
|
||||
notification.error({
|
||||
@@ -80,4 +86,3 @@ function request<T = any>(config: AxiosRequestConfig): Promise<ApiResponse<T>> {
|
||||
}
|
||||
|
||||
export default request;
|
||||
// export default service;
|
||||
11
src/components/LmsStatus/data.ts
Normal file
11
src/components/LmsStatus/data.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const actions = {
|
||||
'line2': [
|
||||
{ title: "L1 数据", path: "/line2/L1-data" },
|
||||
{ title: "L4 数据", path: "/line2/L4-data" },
|
||||
{ title: "报警记录", path: "/line2/alarm-records" },
|
||||
],
|
||||
'line1': [
|
||||
{ title: "L1 数据", path: "/line1/L1-data" },
|
||||
{ title: "报警记录", path: "/line1/alarm-records" },
|
||||
],
|
||||
};
|
||||
@@ -20,8 +20,7 @@
|
||||
</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>
|
||||
<a-button v-for="act in actions[props.type as keyof typeof actions]" size="large" @click="redirectTo(act.path)">{{ act.title }}</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
@@ -32,6 +31,14 @@ import { useRouter } from 'vue-router';
|
||||
import { useDialog } from '@/utils/useDialog';
|
||||
import { fetchLmsWorkMode, updateLmsWorkMode } from '@/api/lms';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { actions } from './data';
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
// useDialog管理弹窗状态
|
||||
const { visible: dialogVisible, show, hide } = useDialog();
|
||||
@@ -76,8 +83,8 @@ async function initLmsStatus() {
|
||||
|
||||
|
||||
const router = useRouter();
|
||||
function redirectTo(type: string) {
|
||||
router.push(`/${type}-data`);
|
||||
function redirectTo(path: string) {
|
||||
router.push(path);
|
||||
}
|
||||
|
||||
// 初始化 LMS 状态
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useDialog } from '@/utils/useDialog';
|
||||
import { useUserStore } from '@/store';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
// useDialog管理弹窗状态
|
||||
const { visible: dialogVisible, show, hide } = useDialog();
|
||||
@@ -29,7 +32,7 @@ const modalTitle = ref('MES 登录状态');
|
||||
const modalItems = ref([
|
||||
{ label: 'MES 服务器', value: '192.168.1.100:8080'},
|
||||
{ label: '登录状态', value: mesStatus.value},
|
||||
{ label: '用户名', value: 'admin'},
|
||||
{ label: '用户名', value: userStore.username},
|
||||
{ label: '连接时间', value: '2024-01-15 09:30:25'},
|
||||
{ label: '最后活动', value: '2024-01-15 14:25:30'}
|
||||
]);
|
||||
|
||||
@@ -11,13 +11,19 @@
|
||||
<a-col :span="16" class="modal-value">{{ item.value }}</a-col>
|
||||
</template>
|
||||
</a-row>
|
||||
<div class="actions">
|
||||
<a-button size="large" @click="handleLogout">退出登录</a-button>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useDialog } from '@/utils/useDialog';
|
||||
import { useRealTime } from '@/utils/dateUtils';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { removeToken } from '@/utils/auth';
|
||||
|
||||
// 实时时间
|
||||
const { currentTime } = useRealTime('HH:mm:ss');
|
||||
@@ -38,6 +44,21 @@ const modalItems = ref([
|
||||
{ label: '内存使用', value: '2.1GB / 8GB' }
|
||||
]);
|
||||
|
||||
const router = useRouter();
|
||||
const handleLogout = () => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确认退出登录?',
|
||||
onOk() {
|
||||
removeToken();
|
||||
return router.push(`/login`);
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
onCancel() {},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
hide
|
||||
@@ -73,4 +94,15 @@ defineExpose({
|
||||
color: $text-light;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin: 20px 0 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: space-between;
|
||||
|
||||
.ant-btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -51,11 +51,7 @@ watch(() => props.logs, (newLogs) => {
|
||||
// 滚动到底部
|
||||
const scrollToBottom = () => {
|
||||
if (logContainer.value) {
|
||||
setTimeout(() => {
|
||||
if (logContainer.value) {
|
||||
logContainer.value.scrollTop = logContainer.value.scrollHeight;
|
||||
}
|
||||
}, 0);
|
||||
logContainer.value.scrollTop = logContainer.value.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,31 +1,63 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import { getToken } from "@/utils/auth";
|
||||
|
||||
const whiteList = ["/login"];
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Index',
|
||||
component: () => import('@/views/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('@/views/login.vue')
|
||||
},
|
||||
{
|
||||
path: '/package-station',
|
||||
name: 'PackageStation',
|
||||
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')
|
||||
}
|
||||
{
|
||||
path: "/",
|
||||
name: "Index",
|
||||
component: () => import("@/views/Line2/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "/lms",
|
||||
name: "Lms",
|
||||
component: () => import("@/views/Line1/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
name: "Login",
|
||||
component: () => import("@/views/login.vue"),
|
||||
},
|
||||
{
|
||||
path: "/package-station",
|
||||
name: "PackageStation",
|
||||
component: () => import("@/views/package-station/index.vue"),
|
||||
},
|
||||
// 一号线
|
||||
{
|
||||
path: "/line1",
|
||||
name: "Line1",
|
||||
children: [
|
||||
{
|
||||
path: "L1-data",
|
||||
name: "Line1L1Data",
|
||||
component: () => import("@/views/Line1/L1-data-list/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "alarm-records",
|
||||
name: "Line1AlarmRecords",
|
||||
component: () => import("@/views/Line1/alarm-records/index.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
// 二号线
|
||||
{
|
||||
path: "/line2",
|
||||
name: "Line2",
|
||||
children: [
|
||||
{
|
||||
path: "L1-data",
|
||||
name: "Line2L1Data",
|
||||
component: () => import("@/views/Line2/L1-data-list/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "L4-data",
|
||||
name: "Line2L4Data",
|
||||
component: () => import("@/views/Line2/L4-data-list/index.vue"),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
@@ -33,4 +65,29 @@ const router = createRouter({
|
||||
routes
|
||||
});
|
||||
|
||||
export default router;
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const token = getToken();
|
||||
// 已登录,访问 login → 跳首页
|
||||
if (token && to.path === "/login") {
|
||||
return next("/lms");
|
||||
}
|
||||
|
||||
// 白名单放行
|
||||
if (whiteList.includes(to.path)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// 未登录,访问受保护路由
|
||||
if (!token) {
|
||||
return next({
|
||||
path: "/login",
|
||||
query: { redirect: to.fullPath },
|
||||
});
|
||||
}
|
||||
|
||||
// 已登录,正常访问
|
||||
next();
|
||||
});
|
||||
|
||||
|
||||
export default router;
|
||||
|
||||
67
src/store/auth.ts
Normal file
67
src/store/auth.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import Cookies from 'js-cookie';
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useUserStore } from './user';
|
||||
import { login } from '@/api/system';
|
||||
import { TokenKey as TOKEN_KEY } from "@/utils/auth";
|
||||
import type { LoginInfo } from '@/api/system/model';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const loginLoading = ref(false);
|
||||
const token = ref(Cookies.get(TOKEN_KEY) || null);
|
||||
|
||||
function setToken(newToken: string) {
|
||||
token.value = newToken;
|
||||
Cookies.set(TOKEN_KEY, newToken);
|
||||
}
|
||||
|
||||
function clearToken() {
|
||||
token.value = null;
|
||||
Cookies.remove(TOKEN_KEY);
|
||||
}
|
||||
|
||||
async function authLogin(params: LoginInfo) {
|
||||
try {
|
||||
loginLoading.value = true;
|
||||
const res = await login(params);
|
||||
if (res.code === 200) {
|
||||
setToken(res.token as string);
|
||||
await userStore.fetchUserInfo();
|
||||
message.success('登录成功');
|
||||
const redirect = route.query.redirect || "/lms";
|
||||
router.replace(redirect as string);
|
||||
return res;
|
||||
} else {
|
||||
// 抛出错误,让调用方处理
|
||||
throw new Error(res.msg || '登录失败');
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
loginLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
// 在实际应用中,这里可以调用后端的退出登录接口
|
||||
// await doLogoutApi();
|
||||
clearToken();
|
||||
userStore.clearUserInfo();
|
||||
await router.push('/login');
|
||||
message.success('已成功退出');
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
loginLoading,
|
||||
authLogin,
|
||||
logout,
|
||||
};
|
||||
});
|
||||
2
src/store/index.ts
Normal file
2
src/store/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './auth';
|
||||
export * from './user';
|
||||
@@ -1,30 +1,22 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
export interface UserInfo {
|
||||
username: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
const USERNAME_KEY = 'username';
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
token: localStorage.getItem('token') || '',
|
||||
userInfo: null as UserInfo | null,
|
||||
username: Cookies.get(USERNAME_KEY) || null,
|
||||
}),
|
||||
getters: {
|
||||
isLogin: (state) => !!state.token,
|
||||
},
|
||||
actions: {
|
||||
setToken(token: string) {
|
||||
this.token = token;
|
||||
localStorage.setItem('token', token);
|
||||
async fetchUserInfo() {
|
||||
// Simulate API call
|
||||
const fetchedUsername = 'mock_user';
|
||||
this.username = fetchedUsername;
|
||||
Cookies.set(USERNAME_KEY, fetchedUsername);
|
||||
},
|
||||
setUserInfo(userInfo: UserInfo) {
|
||||
this.userInfo = userInfo;
|
||||
},
|
||||
reset() {
|
||||
this.token = '';
|
||||
this.userInfo = null;
|
||||
localStorage.removeItem('token');
|
||||
clearUserInfo() {
|
||||
this.username = null;
|
||||
Cookies.remove(USERNAME_KEY);
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
6
src/types/sselog.ts
Normal file
6
src/types/sselog.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
interface sseLog {
|
||||
code: number,
|
||||
message: string,
|
||||
data: any,
|
||||
timestamp: number
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
const TokenKey = 'Admin-Token'
|
||||
export const TokenKey = 'Admin-Token'
|
||||
|
||||
export function getToken() {
|
||||
return Cookies.get(TokenKey)
|
||||
|
||||
17
src/utils/download.ts
Normal file
17
src/utils/download.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
export async function download(data: Blob, fileName: string) {
|
||||
// 1️⃣ 后端返回的是错误 JSON
|
||||
if (data.type.includes("application/json")) {
|
||||
const text = await data.text();
|
||||
const err = JSON.parse(text);
|
||||
throw new Error(err.msg || "导出失败");
|
||||
}
|
||||
|
||||
// 2️⃣ 正常下载
|
||||
const blob = new Blob([data], {
|
||||
type: data.type || "application/octet-stream",
|
||||
});
|
||||
|
||||
saveAs(blob, fileName);
|
||||
}
|
||||
32
src/utils/table.ts
Normal file
32
src/utils/table.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 计算字符串“字数”
|
||||
* 中文 / 全角字符:1
|
||||
* 英文 / 数字 / 半角符号:0.5
|
||||
*/
|
||||
export function calcTextLength(text: string): number {
|
||||
let length = 0;
|
||||
|
||||
for (const char of text) {
|
||||
// charCode > 255 基本可以认为是中文或全角字符
|
||||
if (char.charCodeAt(0) > 255) {
|
||||
length += 1;
|
||||
} else {
|
||||
length += 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文本计算列宽
|
||||
*/
|
||||
export function calcColumnWidth(
|
||||
text: string,
|
||||
unitWidth = 24,
|
||||
minWidth = 50,
|
||||
maxWidth = 150
|
||||
) {
|
||||
const width = calcTextLength(text) * unitWidth;
|
||||
return Math.min(Math.max(width, minWidth), maxWidth);
|
||||
}
|
||||
54
src/views/Line1/L1-data-list/data.ts
Normal file
54
src/views/Line1/L1-data-list/data.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
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> = {
|
||||
flag: "标志位",
|
||||
processFlag: "加工标志",
|
||||
qualifiedFlag: "良品标志",
|
||||
barcode: "二维码",
|
||||
pressure1: "压力1",
|
||||
height1: "高度1",
|
||||
value19Dcr: "数値19#Dcr",
|
||||
value39LcrLs: "数値39#LcrLs",
|
||||
value49LcrQ: "数値49#LcrQ",
|
||||
value79IrR: "数値79#IrR",
|
||||
value89IrI: "数値89#IrI",
|
||||
skeletonCcdCheck: "骨架Ccd检查结果",
|
||||
assemblyCcdCheck: "组立Ccd检查结果",
|
||||
reserve1: "补充字段1",
|
||||
reserve2: "补充字段2",
|
||||
recordTime: "记录数据的时间",
|
||||
createTime: "创建时间",
|
||||
createBy: "创建人",
|
||||
updateTime: "修改时间",
|
||||
updateBy: "修改人",
|
||||
} as const;
|
||||
|
||||
// 导出完整的列配置
|
||||
export const columns: TableColumnsType = [
|
||||
INDEX_COLUMN,
|
||||
...Object.entries(fields).map(([key, title]) => ({
|
||||
key,
|
||||
title,
|
||||
ellipsis: true,
|
||||
})),
|
||||
ACTION_COLUMN,
|
||||
];
|
||||
267
src/views/Line1/L1-data-list/index.vue
Normal file
267
src/views/Line1/L1-data-list/index.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<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" 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-button @click="handleExport">
|
||||
<template #icon>
|
||||
<i-lucide-save />
|
||||
</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" table-layout="fixed" :scroll="{ x: 'max-content'}">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||
<template v-else-if="['createTime', 'updateTime', 'recordTime'].includes(column.key as string)">
|
||||
<span>{{ formatDateTime(record[column.key as string]) }}</span>
|
||||
</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="['createTime', 'updateTime', 'recordTime'].includes(key)">
|
||||
<span>{{ formatDateTime(value) }}</span>
|
||||
</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, exportData } from "@/api/line1/data";
|
||||
import type { QueryParams } from "@/api/line1/data/model";
|
||||
import type { L1Data } from "./types";
|
||||
import type { Dayjs } from "dayjs";
|
||||
import { download } from "@/utils/download";
|
||||
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 handleExport = async () => {
|
||||
try {
|
||||
let params = {}
|
||||
if (formData.dateRange && formData.dateRange.length === 2) {
|
||||
params = {
|
||||
createTimeBegin : formData.dateRange[0].format("YYYY-MM-DD HH:mm:ss"),
|
||||
createTimeEnd : formData.dateRange[1].format("YYYY-MM-DD HH:mm:ss"),
|
||||
}
|
||||
}
|
||||
const blob = await exportData(params)
|
||||
|
||||
download(blob as any, "导出数据.xlsx")
|
||||
|
||||
message.success("导出成功")
|
||||
} catch (err: any) {
|
||||
message.error(err.message)
|
||||
}
|
||||
};
|
||||
|
||||
// 表格变化处理(分页、排序等)
|
||||
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">
|
||||
.l1-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;
|
||||
}
|
||||
|
||||
.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>
|
||||
67
src/views/Line1/L1-data-list/types.ts
Normal file
67
src/views/Line1/L1-data-list/types.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
export interface L1Data {
|
||||
/** 主键 */
|
||||
id: number;
|
||||
|
||||
/** 标志位 */
|
||||
flag: number;
|
||||
|
||||
/** 加工标志 */
|
||||
processFlag: number;
|
||||
|
||||
/** 良品标志 */
|
||||
qualifiedFlag: number;
|
||||
|
||||
/** 二维码 */
|
||||
barcode: string;
|
||||
|
||||
/** 压力1 */
|
||||
pressure1: number;
|
||||
|
||||
/** 高度1 */
|
||||
height1: number;
|
||||
|
||||
/** 数値19#DCR */
|
||||
value19Dcr: number;
|
||||
|
||||
/** 数値39#LCR LS */
|
||||
value39LcrLs: number;
|
||||
|
||||
/** 数値49#LCR Q */
|
||||
value49LcrQ: number;
|
||||
|
||||
/** 数値79#IR R */
|
||||
value79IrR: number;
|
||||
|
||||
/** 数値89#IR I */
|
||||
value89IrI: number;
|
||||
|
||||
/** 骨架CCD检查结果 */
|
||||
skeletonCcdCheck: number;
|
||||
|
||||
/** 组立CCD检查结果 */
|
||||
assemblyCcdCheck: number;
|
||||
|
||||
/** 补充字段1 */
|
||||
reserve1: string;
|
||||
|
||||
/** 补充字段2 */
|
||||
reserve2: string;
|
||||
|
||||
/** 记录数据的时间 */
|
||||
recordTime: string;
|
||||
|
||||
/** 逻辑删除 */
|
||||
DelFlag: number;
|
||||
|
||||
/** 创建时间 */
|
||||
createTime: string;
|
||||
|
||||
/** 创建人 */
|
||||
createBy: string;
|
||||
|
||||
/** 修改时间 */
|
||||
updateTime: string;
|
||||
|
||||
/** 修改人 */
|
||||
updateBy: string;
|
||||
}
|
||||
42
src/views/Line1/alarm-records/data.ts
Normal file
42
src/views/Line1/alarm-records/data.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
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> = {
|
||||
alarmId: "报警ID",
|
||||
alarmCode: "报警代码",
|
||||
alarmMsg: "报警信息",
|
||||
alarmDatetime: "报警时间",
|
||||
updateTime: "更新时间",
|
||||
updateBy: "更新人",
|
||||
createTime: "创建时间",
|
||||
createBy: "创建人",
|
||||
} as const;
|
||||
|
||||
// 导出完整的列配置
|
||||
export const columns: TableColumnsType = [
|
||||
INDEX_COLUMN,
|
||||
...Object.entries(fields).map(([key, title]) => ({
|
||||
key,
|
||||
title,
|
||||
ellipsis: true,
|
||||
})),
|
||||
ACTION_COLUMN,
|
||||
];
|
||||
237
src/views/Line1/alarm-records/index.vue
Normal file
237
src/views/Line1/alarm-records/index.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div class="alarm-container">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">报警记录</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" 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="alarmId" size="middle" :scroll="{ x: 'max-content'}">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||
<template v-else-if="['alarmDatetime', 'updateTime', 'createTime'].includes(column.key as string)">
|
||||
<span>{{ formatDateTime(record[column.key as string]) }}</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-button type="link" size="small" @click="handleView(record as AlarmRecord)">
|
||||
查看详情
|
||||
</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="['alarmDatetime', 'updateTime', 'createTime'].includes(key)">
|
||||
{{ formatDateTime(value) }}
|
||||
</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 { getAlarmRecords } from "@/api/line1/data";
|
||||
import type { QueryParams } from "@/api/line1/data/model";
|
||||
import type { AlarmRecord } from "./types";
|
||||
import type { Dayjs } from "dayjs";
|
||||
import { columns } from "./data";
|
||||
|
||||
type DateRangeType = [Dayjs, Dayjs] | undefined;
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false);
|
||||
const tableData = ref<AlarmRecord[]>([]);
|
||||
const formData = reactive<{ dateRange: DateRangeType }>({
|
||||
dateRange: undefined,
|
||||
});
|
||||
const detailVisible = ref(false);
|
||||
const selectedRecord = ref<AlarmRecord | 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 getAlarmRecords(params);
|
||||
|
||||
if (res.code === 200) {
|
||||
tableData.value = res.rows || [];
|
||||
pagination.total = res.total || 0;
|
||||
} else {
|
||||
message.error(res.msg || "获取数据失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取报警记录失败:", 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: AlarmRecord) => {
|
||||
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">
|
||||
.alarm-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;
|
||||
}
|
||||
|
||||
.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>
|
||||
25
src/views/Line1/alarm-records/types.ts
Normal file
25
src/views/Line1/alarm-records/types.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export interface AlarmRecord {
|
||||
/** 报警ID */
|
||||
alarmId: number;
|
||||
|
||||
/** 报警代码 */
|
||||
alarmCode: number;
|
||||
|
||||
/** 报警信息 */
|
||||
alarmMsg: string;
|
||||
|
||||
/** 报警时间 */
|
||||
alarmDatetime: string; // ISO 8601 格式,如 "2025-09-23T12:34:56"
|
||||
|
||||
/** 更新时间 */
|
||||
updateTime: string; // ISO 8601 格式
|
||||
|
||||
/** 更新人 */
|
||||
updateBy: string;
|
||||
|
||||
/** 创建时间 */
|
||||
createTime: string; // ISO 8601 格式
|
||||
|
||||
/** 创建人 */
|
||||
createBy: string;
|
||||
}
|
||||
911
src/views/Line1/index.vue
Normal file
911
src/views/Line1/index.vue
Normal file
@@ -0,0 +1,911 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive } from 'vue';
|
||||
import { useRealTime } from '@/utils/dateUtils';
|
||||
import { checkOrderNumberApi, addProcessInfoApi } from '@/api/detect';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
// 检测设备表单规则
|
||||
const rules: Record<string, Rule[]> = {
|
||||
workOrderCode: [{ required: true, message: '请输入工单号', trigger: 'change' }],
|
||||
productCode: [{ required: true, message: '请输入产品编码', trigger: 'change' }],
|
||||
employeeCode: [{ required: true, message: '请输入员工号', trigger: 'change' }],
|
||||
processName: [{ required: true, message: '请输入工序名称', trigger: 'change' }],
|
||||
resourceName: [{ required: true, message: '请输入资源名称', trigger: 'change' }],
|
||||
equipmentCode: [{ required: true, message: '请输入设备编码', trigger: 'change' }],
|
||||
fixtureCode: [{ required: true, message: '请输入治具编码', trigger: 'change' }],
|
||||
};
|
||||
|
||||
// 包装设备表单规则
|
||||
const packageRules: Record<string, Rule[]> = {
|
||||
customerSelection: [{ required: true, message: '请选择客户', trigger: 'change' }],
|
||||
productModel: [{ required: true, message: '请输入产品型号', trigger: 'change' }],
|
||||
employeeCode: [{ required: true, message: '请输入员工号', trigger: 'change' }],
|
||||
processName: [{ required: true, message: '请输入工序名称', trigger: 'change' }],
|
||||
resourceName: [{ required: true, message: '请输入资源名称', trigger: 'change' }],
|
||||
equipmentCode: [{ required: true, message: '请输入设备编码', trigger: 'change' }],
|
||||
fixtureCode: [{ required: true, message: '请输入治具编码', trigger: 'change' }],
|
||||
packingResult: [{ required: true, message: '请选择装箱结果', trigger: 'change' }],
|
||||
};
|
||||
|
||||
// 实时时间
|
||||
const { currentTime } = useRealTime('HH:mm:ss');
|
||||
const currentDate = ref('');
|
||||
|
||||
// 可编辑表单状态管理
|
||||
const isDetectingEditMode = ref(false);
|
||||
const isPackageEditMode = ref(false);
|
||||
|
||||
// 初始副本存储
|
||||
let detectFormBackup: any = null;
|
||||
let packageFormBackup: any = null;
|
||||
|
||||
// 表单引用
|
||||
const detectFormRef = ref();
|
||||
const packageFormRef = ref();
|
||||
|
||||
// 执行结果日志
|
||||
const executionLogs = ref<string[]>([]);
|
||||
|
||||
const packageLogs = ref<string[]>([]);
|
||||
|
||||
// 底部操作按钮配置
|
||||
const actionButtons = ref([
|
||||
{ label: '工序过站', handler: 'handleProcessPass', type: 'primary' as const, status: 'idle' as const },
|
||||
{ label: '复检序号检查', handler: 'handleRecheckSequence', type: 'default' as const, status: 'idle' as const },
|
||||
{ label: '扫码', handler: 'handleScanCode', type: 'default' as const, status: 'idle' as const },
|
||||
{ label: '序号检查', handler: 'handleSequenceCheck', type: 'default' as const, status: 'idle' as const },
|
||||
{ label: '包装过站', handler: 'handlePackagePass', type: 'primary' as const, status: 'idle' as const }
|
||||
]);
|
||||
|
||||
// 执行按钮操作
|
||||
const executeButtonAction = (handlerName: string) => {
|
||||
const handlers: { [key: string]: () => void } = {
|
||||
handleOrderCheck,
|
||||
handleProcessPass,
|
||||
handleRecheckSequence,
|
||||
handleSequenceCheck,
|
||||
handlePackagePass
|
||||
};
|
||||
|
||||
const handler = handlers[handlerName];
|
||||
if (handler) {
|
||||
handler();
|
||||
}
|
||||
};
|
||||
|
||||
// 日志记录工具函数
|
||||
const addDetectionLog = (message: string) => {
|
||||
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
||||
const logEntry = `${timestamp} - ${message}`;
|
||||
executionLogs.value.push(logEntry);
|
||||
};
|
||||
|
||||
const addPackageLog = (message: string) => {
|
||||
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
||||
const logEntry = `${timestamp} - ${message}`;
|
||||
packageLogs.value.push(logEntry);
|
||||
};
|
||||
|
||||
// 清空检测设备执行日志
|
||||
const clearExecutionLogs = () => {
|
||||
executionLogs.value = [];
|
||||
};
|
||||
|
||||
// 清空自动包装执行日志
|
||||
const clearPackageLogs = () => {
|
||||
packageLogs.value = [];
|
||||
};
|
||||
|
||||
// 单号检查
|
||||
const handleOrderCheck = () => {
|
||||
checkOrderNumberApi({
|
||||
workOrderCode: detectForm.workOrderCode,
|
||||
productCode: detectForm.productCode
|
||||
}).then(res => {
|
||||
if (res) {
|
||||
addDetectionLog('单号检查成功');
|
||||
} else {
|
||||
addDetectionLog('单号检查失败');
|
||||
}
|
||||
})
|
||||
addDetectionLog('开始执行单号检查操作');
|
||||
console.log('单号检查');
|
||||
};
|
||||
|
||||
// 工序过站
|
||||
const handleProcessPass = () => {
|
||||
addDetectionLog('开始执行工序过站操作');
|
||||
console.log('工序过站');
|
||||
};
|
||||
|
||||
// 复检序号检查
|
||||
const handleRecheckSequence = () => {
|
||||
addDetectionLog('开始执行复检序号检查操作');
|
||||
console.log('复检序号检查');
|
||||
};
|
||||
|
||||
// 序号检查
|
||||
const handleSequenceCheck = () => {
|
||||
addDetectionLog('开始执行序号检查操作');
|
||||
console.log('序号检查');
|
||||
};
|
||||
|
||||
// 包装过站
|
||||
const handlePackagePass = () => {
|
||||
addPackageLog('开始执行包装过站操作');
|
||||
console.log('包装过站');
|
||||
};
|
||||
|
||||
// SSE事件处理函数
|
||||
const handleL1Event = (data: any) => {
|
||||
// L1_EVENT放到自动包装日志区
|
||||
addDetectionLog(`L1事件: ${data.message}`);
|
||||
};
|
||||
|
||||
const handleL4Event = (data: any) => {
|
||||
// L4_EVENT放到检测设备日志区
|
||||
addDetectionLog(`L4事件: ${data.message}`);
|
||||
if (data.code === 201) {
|
||||
detectForm.confirmSequence = data.data
|
||||
}
|
||||
};
|
||||
|
||||
const handleMESEvent = (data: any) => {
|
||||
// MES_EVENT放到检测设备日志区
|
||||
addDetectionLog(`MES事件: ${data.message}`);
|
||||
};
|
||||
|
||||
const handleSseMessage = (data: string) => {
|
||||
// 处理SSE原始消息(如果需要的话)
|
||||
console.log('收到SSE消息:', data);
|
||||
};
|
||||
|
||||
// 检测设备表单数据
|
||||
const detectForm = reactive({
|
||||
workOrderCode: '',
|
||||
productCode: '',
|
||||
employeeCode: '',
|
||||
processName: '',
|
||||
resourceName: '',
|
||||
equipmentCode: '',
|
||||
fixtureCode: '',
|
||||
confirmSequence: '',
|
||||
detectionData: '',
|
||||
sequenceCount: 1
|
||||
});
|
||||
|
||||
// 包装设备表单数据
|
||||
const packageForm = reactive({
|
||||
customerSelection: '',
|
||||
productModel: '',
|
||||
employeeCode: '',
|
||||
processName: '',
|
||||
resourceName: '',
|
||||
equipmentCode: '',
|
||||
fixtureCode: '',
|
||||
packingResult: '',
|
||||
sequenceNumber: '',
|
||||
passStationCount: '',
|
||||
fullBoxCount: '',
|
||||
passStationSequence: ''
|
||||
});
|
||||
|
||||
// 编辑模式切换
|
||||
const toggleDetectingEditMode = () => {
|
||||
if (!isDetectingEditMode.value) {
|
||||
// 进入编辑模式,保存初始副本
|
||||
detectFormBackup = JSON.parse(JSON.stringify(detectForm));
|
||||
}
|
||||
isDetectingEditMode.value = !isDetectingEditMode.value;
|
||||
};
|
||||
|
||||
const togglePackageEditMode = () => {
|
||||
return message.warn('功能未启用');
|
||||
if (!isPackageEditMode.value) {
|
||||
// 进入编辑模式,保存初始副本
|
||||
packageFormBackup = JSON.parse(JSON.stringify(packageForm));
|
||||
}
|
||||
isPackageEditMode.value = !isPackageEditMode.value;
|
||||
};
|
||||
|
||||
// 保存检测设备编辑
|
||||
const saveDetectingEdit = () => {
|
||||
detectFormRef.value.validate().then(() => {
|
||||
console.log('保存检测设备数据:', detectForm);
|
||||
isDetectingEditMode.value = false;
|
||||
// 保存成功,清除备份
|
||||
detectFormBackup = null;
|
||||
|
||||
addProcessInfoApi({
|
||||
workOrderCode: detectForm.workOrderCode,
|
||||
productCode: detectForm.productCode,
|
||||
employeeCode: detectForm.employeeCode,
|
||||
processName: detectForm.processName,
|
||||
resourceName: detectForm.resourceName,
|
||||
equipmentCode: detectForm.equipmentCode,
|
||||
fixtureCode: detectForm.fixtureCode
|
||||
}).then(res => {
|
||||
console.log(res)
|
||||
message.success('保存成功');
|
||||
})
|
||||
}).catch(() => {
|
||||
console.log('检测设备表单验证失败');
|
||||
});
|
||||
};
|
||||
|
||||
// 保存包装设备编辑
|
||||
const savePackageEdit = () => {
|
||||
packageFormRef.value.validate().then(() => {
|
||||
console.log('保存包装设备数据:', packageForm);
|
||||
isPackageEditMode.value = false;
|
||||
// 保存成功,清除备份
|
||||
packageFormBackup = null;
|
||||
}).catch(() => {
|
||||
console.log('包装设备表单验证失败');
|
||||
});
|
||||
};
|
||||
|
||||
// 取消检测设备编辑
|
||||
const cancelDetectingEdit = () => {
|
||||
isDetectingEditMode.value = false;
|
||||
if (detectFormBackup) {
|
||||
// 恢复到初始副本
|
||||
Object.assign(detectForm, detectFormBackup);
|
||||
detectFormBackup = null;
|
||||
}
|
||||
// 清除表单验证状态
|
||||
detectFormRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
// 取消包装设备编辑
|
||||
const cancelPackageEdit = () => {
|
||||
isPackageEditMode.value = false;
|
||||
if (packageFormBackup) {
|
||||
// 恢复到初始副本
|
||||
Object.assign(packageForm, packageFormBackup);
|
||||
packageFormBackup = null;
|
||||
}
|
||||
// 清除表单验证状态
|
||||
packageFormRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
// 设置当前日期
|
||||
const now = new Date();
|
||||
currentDate.value = `${now.getFullYear()}年${String(now.getMonth() + 1).padStart(2, '0')}月${String(now.getDate()).padStart(2, '0')}日`;
|
||||
|
||||
// SSE连接逻辑已迁移到SseStatus组件中
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="hmi-container">
|
||||
<!-- 顶部状态栏 -->
|
||||
<div class="header-status">
|
||||
<div class="left-info">
|
||||
<span class="app-title">
|
||||
<i-lucide-cpu class="app-icon" /> 工业控制系统 HMI
|
||||
</span>
|
||||
</div>
|
||||
<div class="center-status">
|
||||
<!-- MES状态 -->
|
||||
<MesStatus />
|
||||
|
||||
<!-- 网络通讯状态 -->
|
||||
<NetworkStatus />
|
||||
|
||||
<!-- PLC通讯状态 -->
|
||||
<PlcStatus />
|
||||
|
||||
<!-- 摄像头状态 -->
|
||||
<CameraStatus />
|
||||
|
||||
<!-- SSE 日志 -->
|
||||
<SseStatus :on-l1-event="handleL1Event" :on-l4-event="handleL4Event" :on-mes-event="handleMESEvent"
|
||||
:on-sse-message="handleSseMessage" />
|
||||
|
||||
<!-- LMS 状态 -->
|
||||
<LmsStatus type="line1" />
|
||||
</div>
|
||||
<SettingsModal />
|
||||
<!-- <div class="right-info" @click="showSettings">
|
||||
<span class="time-info">{{ currentTime }}</span>
|
||||
<span class="date-info">{{ currentDate }}</span>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="main-content">
|
||||
<!-- 检测设备区域 -->
|
||||
<div class="device-section detecting-section">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">
|
||||
<i-lucide-monitor class="title-icon" />
|
||||
检测设备
|
||||
</h3>
|
||||
<div class="edit-controls" v-if="!isDetectingEditMode">
|
||||
<a-button size="small" @click="toggleDetectingEditMode" class="edit-btn">
|
||||
<i-lucide-edit class="btn-icon" />
|
||||
编辑
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="edit-controls" v-else>
|
||||
<a-button size="small" type="primary" @click="saveDetectingEdit" class="save-btn">
|
||||
<i-lucide-save class="btn-icon" />
|
||||
保存
|
||||
</a-button>
|
||||
<a-button size="small" @click="cancelDetectingEdit" class="cancel-btn">
|
||||
<i-lucide-x class="btn-icon" />
|
||||
取消
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<a-form :model="detectForm" :rules="rules" class="device-info" ref="detectFormRef" :colon="false"
|
||||
hideRequiredMark :labelCol="{ span: 4 }" :wrapperCol="{ span: 20 }">
|
||||
<a-row :gutter="32">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="工单号" name="workOrderCode">
|
||||
<a-input-group compact>
|
||||
<a-input v-model:value="detectForm.workOrderCode" class="edit-input"
|
||||
:disabled="!isDetectingEditMode" />
|
||||
<a-button type="primary" @click="handleOrderCheck">单号检查</a-button>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="产品编码" name="productCode">
|
||||
<a-input v-model:value="detectForm.productCode" class="edit-input" :disabled="!isDetectingEditMode" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="员工工号" name="employeeCode">
|
||||
<a-input v-model:value="detectForm.employeeCode" class="edit-input" :disabled="!isDetectingEditMode" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="工序名称" name="processName">
|
||||
<a-input v-model:value="detectForm.processName" class="edit-input" :disabled="!isDetectingEditMode" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="资源名称" name="resourceName">
|
||||
<a-input v-model:value="detectForm.resourceName" class="edit-input" :disabled="!isDetectingEditMode" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="设备编码" name="equipmentCode">
|
||||
<a-input v-model:value="detectForm.equipmentCode" class="edit-input" :disabled="!isDetectingEditMode" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="治具编码" name="fixtureCode">
|
||||
<a-input v-model:value="detectForm.fixtureCode" class="edit-input" :disabled="!isDetectingEditMode" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="序号数量">
|
||||
<a-input v-model:value="detectForm.sequenceCount" class="edit-input" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="确认序号" name="confirmSequence" :labelCol="{ span: 2 }" :wrapperCol="{ span: 22 }">
|
||||
<a-input-group compact>
|
||||
<a-input v-model:value="detectForm.confirmSequence" class="edit-input" disabled />
|
||||
<a-button type="primary" @click="handleSequenceCheck">序号检查</a-button>
|
||||
<a-button type="primary" @click="handleRecheckSequence"
|
||||
style="border-left: 1px solid #8395B6;">复检序号检查</a-button>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="24">
|
||||
<a-form-item label="检测数据" name="fixtureCode" :labelCol="{ span: 2 }" :wrapperCol="{ span: 22 }">
|
||||
<a-input v-model:value="detectForm.detectionData" class="edit-input" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<ExecutionResult title="执行结果" :logs="executionLogs" @clear="clearExecutionLogs" />
|
||||
</div>
|
||||
|
||||
<!-- 自动包装区域 -->
|
||||
<div class="device-section package-section">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">
|
||||
<i-lucide-package class="title-icon" />
|
||||
自动包装
|
||||
</h3>
|
||||
<div class="edit-controls" v-if="!isPackageEditMode">
|
||||
<a-button size="small" @click="togglePackageEditMode" class="edit-btn">
|
||||
<i-lucide-square-pen class="btn-icon" />
|
||||
编辑
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="edit-controls" v-else>
|
||||
<a-button size="small" type="primary" @click="savePackageEdit" class="save-btn">
|
||||
<i-lucide-save class="btn-icon" />
|
||||
保存
|
||||
</a-button>
|
||||
<a-button size="small" @click="cancelPackageEdit" class="cancel-btn">
|
||||
<i-lucide-x class="btn-icon" />
|
||||
取消
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<a-form :model="packageForm" :rules="packageRules" class="device-info" ref="packageFormRef" :colon="false"
|
||||
hideRequiredMark :labelCol="{ span: 4 }" :wrapperCol="{ span: 20 }">
|
||||
<a-row :gutter="32">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="员工工号" name="employeeCode">
|
||||
<a-input v-model:value="packageForm.employeeCode" class="edit-input" :disabled="!isPackageEditMode" placeholder="未启用" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="工序名称" name="processName">
|
||||
<a-input v-model:value="packageForm.processName" class="edit-input" :disabled="!isPackageEditMode" placeholder="未启用" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="资源名称" name="resourceName">
|
||||
<a-input v-model:value="packageForm.resourceName" class="edit-input" :disabled="!isPackageEditMode" placeholder="未启用" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="设备编码" name="equipmentCode">
|
||||
<a-input v-model:value="packageForm.equipmentCode" class="edit-input" :disabled="!isPackageEditMode" placeholder="未启用" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="治具编码" name="fixtureCode">
|
||||
<a-input v-model:value="packageForm.fixtureCode" class="edit-input" :disabled="!isPackageEditMode" placeholder="未启用" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="装箱结果" name="packingResult">
|
||||
<a-select v-model:value="packageForm.packingResult" class="edit-input" :disabled="!isPackageEditMode">
|
||||
<a-select-option value="正常">正常</a-select-option>
|
||||
<a-select-option value="异常">异常</a-select-option>
|
||||
<a-select-option value="待检">待检</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="序号" name="fixtureCode">
|
||||
<a-input v-model:value="packageForm.sequenceNumber" class="edit-input" disabled placeholder="未启用" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="过站数量" name="fixtureCode">
|
||||
<a-input v-model:value="packageForm.passStationCount" class="edit-input" disabled placeholder="未启用" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="满箱数量" name="fixtureCode">
|
||||
<a-input v-model:value="packageForm.fullBoxCount" class="edit-input" disabled placeholder="未启用" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="过站序号" name="fixtureCode">
|
||||
<a-input v-model:value="packageForm.passStationSequence" class="edit-input" disabled placeholder="未启用" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<ExecutionResult title="报警信息" :logs="packageLogs" @clear="clearPackageLogs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作按钮区域 -->
|
||||
<!-- <ActionButtons :buttons="actionButtons" @execute="executeButtonAction" /> -->
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/assets/styles/variables" as *;
|
||||
|
||||
.hmi-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
|
||||
color: $white;
|
||||
font-family: $font-family;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
font-size: clamp(14px, 1.4vw, 18px);
|
||||
|
||||
/* 响应式字体大小 */
|
||||
@media (max-width: 1024px) {
|
||||
font-size: clamp(12px, 1.7vw, 16px);
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) and (max-width: 1366px) {
|
||||
font-size: clamp(14px, 1.5vw, 17px);
|
||||
}
|
||||
|
||||
@media (min-width: 1367px) {
|
||||
font-size: clamp(16px, 1.3vw, 20px);
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式字体大小的具体元素
|
||||
@media (max-width: 1024px) {
|
||||
.app-title {
|
||||
font-size: clamp(16px, 2.8vw, 20px) !important;
|
||||
}
|
||||
|
||||
.status-label,
|
||||
.status-value {
|
||||
font-size: clamp(12px, 1.4vw, 14px) !important;
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
font-size: clamp(16px, 2.0vw, 18px) !important;
|
||||
}
|
||||
|
||||
.info-item .label,
|
||||
.info-item .value {
|
||||
font-size: clamp(12px, 1.4vw, 14px) !important;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
font-size: clamp(12px, 1.4vw, 14px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1025px) and (max-width: 1366px) {
|
||||
.app-title {
|
||||
font-size: clamp(18px, 2.4vw, 20px) !important;
|
||||
}
|
||||
|
||||
.status-label,
|
||||
.status-value {
|
||||
font-size: clamp(13px, 1.3vw, 15px) !important;
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
font-size: clamp(17px, 1.8vw, 19px) !important;
|
||||
}
|
||||
|
||||
.info-item .label,
|
||||
.info-item .value {
|
||||
font-size: clamp(13px, 1.3vw, 15px) !important;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
font-size: clamp(14px, 1.3vw, 16px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1367px) {
|
||||
.app-title {
|
||||
font-size: clamp(20px, 2.0vw, 22px) !important;
|
||||
}
|
||||
|
||||
.status-label,
|
||||
.status-value {
|
||||
font-size: clamp(14px, 1.2vw, 16px) !important;
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
font-size: clamp(18px, 1.6vw, 20px) !important;
|
||||
}
|
||||
|
||||
.info-item .label,
|
||||
.info-item .value {
|
||||
font-size: clamp(14px, 1.2vw, 16px) !important;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
font-size: clamp(15px, 1.2vw, 17px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 顶部状态栏 */
|
||||
.header-status {
|
||||
height: 50px;
|
||||
background: $bg-dark;
|
||||
border-bottom: 2px solid $primary-color;
|
||||
@include flex-between;
|
||||
padding: 0 $spacing-lg;
|
||||
flex-shrink: 0;
|
||||
|
||||
.left-info {
|
||||
.app-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.center-status {
|
||||
display: flex;
|
||||
gap: $spacing-xxl;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-button {
|
||||
padding: $spacing-sm $spacing-md;
|
||||
@include button-base;
|
||||
@include button-hover($bg-overlay, rgba(255, 255, 255, 0.2), $white);
|
||||
}
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
gap: $spacing-lg;
|
||||
padding: $spacing-lg;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.device-section {
|
||||
flex: 1;
|
||||
background: $bg-overlay;
|
||||
border: 2px solid $primary-color;
|
||||
border-radius: $border-radius-lg;
|
||||
padding: $spacing-lg;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: $spacing-md;
|
||||
border-bottom: 1px solid $primary-color;
|
||||
padding-bottom: 6px;
|
||||
@include flex-between;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
color: $white;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
margin: 0;
|
||||
color: $white;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
filter: drop-shadow(0 2px 4px rgba(74, 144, 226, 0.3));
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: $primary-color;
|
||||
margin-right: $spacing-sm;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-right: 6px;
|
||||
color: $text-light;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: $spacing-xs;
|
||||
}
|
||||
|
||||
.rotate {
|
||||
animation: rotation 2s infinite linear;
|
||||
/* 添加旋转动画 */
|
||||
}
|
||||
|
||||
@keyframes rotation {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
/* 从 0 度开始 */
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
/* 旋转到 360 度 */
|
||||
}
|
||||
}
|
||||
|
||||
/* 编辑控制按钮 */
|
||||
.edit-controls {
|
||||
display: flex;
|
||||
gap: $spacing-sm;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
@include status-button($bg-primary, $bg-primary-hover, $primary-color, $primary-light, $text-light, $white);
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
@include status-button($success-bg, $success-bg-hover, $success-color, $success-light, $text-success, $white);
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
@include status-button($error-bg, $error-bg-hover, $error-color, $error-light, $text-error, $white);
|
||||
}
|
||||
|
||||
/* 编辑输入框样式 */
|
||||
.edit-input {
|
||||
background: $bg-input !important;
|
||||
border: $border-light !important;
|
||||
color: $white !important;
|
||||
width: 100%;
|
||||
|
||||
&.bn {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: $bg-input-focus !important;
|
||||
border-color: #fff !important;
|
||||
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2) !important;
|
||||
}
|
||||
|
||||
&.ant-input-disabled {
|
||||
color: $text-light !important;
|
||||
}
|
||||
|
||||
input {
|
||||
background: transparent !important;
|
||||
color: $white !important;
|
||||
}
|
||||
|
||||
&.ant-select.ant-select-in-form-item {
|
||||
border: none !important;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-select.ant-select-disabled) {
|
||||
.ant-select-selection-item {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.ant-select-selector {
|
||||
border: $border-light !important;
|
||||
}
|
||||
|
||||
.ant-select-arrow {
|
||||
color: #bdbdbd;
|
||||
}
|
||||
}
|
||||
|
||||
.device-info {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: $spacing-xl;
|
||||
|
||||
:deep(.ant-form-item-label > label) {
|
||||
color: $text-light;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
min-height: 20px;
|
||||
|
||||
|
||||
.value {
|
||||
color: $white;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
||||
&.success {
|
||||
color: $success-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* SSE日志对话框样式 */
|
||||
.sse-logs-dialog {
|
||||
:deep(.ant-modal-content) {
|
||||
background: rgba(30, 30, 45, 0.95);
|
||||
border: 1px solid $primary-color;
|
||||
}
|
||||
|
||||
.sse-controls {
|
||||
display: flex;
|
||||
gap: $spacing-sm;
|
||||
margin-bottom: $spacing-md;
|
||||
|
||||
.sse-control-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.btn-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 1600px) {
|
||||
.main-content {
|
||||
gap: $spacing-lg;
|
||||
padding: $spacing-lg;
|
||||
}
|
||||
|
||||
.device-section {
|
||||
padding: $spacing-lg;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
.label {
|
||||
min-width: 70px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1366px) {
|
||||
.center-status {
|
||||
gap: $spacing-xl;
|
||||
}
|
||||
|
||||
.status-label,
|
||||
.status-value {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.device-section {
|
||||
padding: $spacing-md;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.main-content {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.device-section {
|
||||
flex: 1;
|
||||
height: calc(50vh - 100px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dialog样式已迁移到各个子组件中 */
|
||||
|
||||
:deep(.ant-input-group.ant-input-group-compact) {
|
||||
display: flex;
|
||||
|
||||
.ant-btn[disabled] {
|
||||
border-color: #8396B7;
|
||||
color: #b4b4b4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -9,7 +9,7 @@
|
||||
<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" show-time />
|
||||
format="YYYY-MM-DD HH:mm:ss" show-time />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
@@ -33,7 +33,7 @@
|
||||
<!-- 数据表格 -->
|
||||
<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 }">
|
||||
@change="handleTableChange" row-key="id" size="middle" :scroll="{ x: 'max-content'}">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||
<template v-else-if="column.key === 'createTime'">
|
||||
@@ -126,8 +126,8 @@
|
||||
<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 { getL1Data } from "@/api/line2/data";
|
||||
import type { QueryParams } from "@/api/line2/data/model";
|
||||
import type { L1Data } from "./types";
|
||||
import type { Dayjs } from "dayjs";
|
||||
import { columns } from "./data";
|
||||
@@ -165,8 +165,8 @@ const fetchData = async () => {
|
||||
|
||||
// 添加日期范围筛选
|
||||
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");
|
||||
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);
|
||||
@@ -9,7 +9,7 @@
|
||||
<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" show-time />
|
||||
format="YYYY-MM-DD HH:mm:ss" show-time />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
@@ -33,7 +33,7 @@
|
||||
<!-- 数据表格 -->
|
||||
<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 }">
|
||||
@change="handleTableChange" row-key="id" size="middle" :scroll="{ x: 'max-content'}">
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||
<template v-if="column.key === 'createTime'">
|
||||
@@ -133,12 +133,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { getL4Data } from '@/api/data';
|
||||
import { getL4Data } from '@/api/line2/data';
|
||||
import { columns } from './data';
|
||||
import type { QueryParams } from '@/api/data/model';
|
||||
import type { QueryParams } from '@/api/line2/data/model';
|
||||
import type { L4Data } from './types';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import { idText } from 'typescript';
|
||||
|
||||
type DateRangeType = [Dayjs, Dayjs] | undefined;
|
||||
|
||||
@@ -199,8 +198,8 @@ const fetchData = async () => {
|
||||
|
||||
// 添加日期范围筛选
|
||||
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");
|
||||
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);
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount, reactive, nextTick } from 'vue';
|
||||
import { ref, onMounted, reactive } from 'vue';
|
||||
import { useRealTime } from '@/utils/dateUtils';
|
||||
import { checkOrderNumberApi, addProcessInfoApi } from '@/api/detect';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
@@ -49,14 +49,6 @@ const executionLogs = ref<string[]>([]);
|
||||
|
||||
const packageLogs = ref<string[]>([]);
|
||||
|
||||
// 滚动到检测设备日志底部的函数
|
||||
const scrollToExecutionBottom = () => {
|
||||
const logsContainer = document.querySelector('.execution-logs-content');
|
||||
if (logsContainer) {
|
||||
logsContainer.scrollTop = logsContainer.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
// 底部操作按钮配置
|
||||
const actionButtons = ref([
|
||||
{ label: '工序过站', handler: 'handleProcessPass', type: 'primary' as const, status: 'idle' as const },
|
||||
@@ -87,25 +79,12 @@ const addDetectionLog = (message: string) => {
|
||||
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
||||
const logEntry = `${timestamp} - ${message}`;
|
||||
executionLogs.value.push(logEntry);
|
||||
|
||||
// 自动滚动到底部
|
||||
nextTick(() => {
|
||||
scrollToExecutionBottom();
|
||||
});
|
||||
};
|
||||
|
||||
const addPackageLog = (message: string) => {
|
||||
const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
|
||||
const logEntry = `${timestamp} - ${message}`;
|
||||
packageLogs.value.push(logEntry);
|
||||
|
||||
// 自动滚动到底部
|
||||
nextTick(() => {
|
||||
const logContainers = document.querySelectorAll('.log-container');
|
||||
logContainers.forEach(container => {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 清空检测设备执行日志
|
||||
@@ -142,13 +121,13 @@ const handleProcessPass = () => {
|
||||
|
||||
// 复检序号检查
|
||||
const handleRecheckSequence = () => {
|
||||
addPackageLog('开始执行复检序号检查操作');
|
||||
addDetectionLog('开始执行复检序号检查操作');
|
||||
console.log('复检序号检查');
|
||||
};
|
||||
|
||||
// 序号检查
|
||||
const handleSequenceCheck = () => {
|
||||
addPackageLog('开始执行序号检查操作');
|
||||
addDetectionLog('开始执行序号检查操作');
|
||||
console.log('序号检查');
|
||||
};
|
||||
|
||||
@@ -161,12 +140,12 @@ const handlePackagePass = () => {
|
||||
// SSE事件处理函数
|
||||
const handleL1Event = (data: any) => {
|
||||
// L1_EVENT放到自动包装日志区
|
||||
addPackageLog(`L1事件: ${JSON.stringify(data)}`);
|
||||
addPackageLog(`【L1 事件】 ${data.message}`);
|
||||
};
|
||||
|
||||
const handleL4Event = (data: any) => {
|
||||
// L4_EVENT放到检测设备日志区
|
||||
addDetectionLog(`L4事件: ${JSON.stringify(data)}`);
|
||||
addDetectionLog(`【L4 事件】 ${data.message}`);
|
||||
if(data.code === 201) {
|
||||
detectForm.confirmSequence = data.data
|
||||
}
|
||||
@@ -174,7 +153,7 @@ const handleL4Event = (data: any) => {
|
||||
|
||||
const handleMESEvent = (data: any) => {
|
||||
// MES_EVENT放到检测设备日志区
|
||||
addDetectionLog(`MES事件: ${JSON.stringify(data)}`);
|
||||
addDetectionLog(`【MES 事件】 ${data.message}`);
|
||||
};
|
||||
|
||||
const handleSseMessage = (data: string) => {
|
||||
@@ -297,13 +276,6 @@ onMounted(() => {
|
||||
// 设置当前日期
|
||||
const now = new Date();
|
||||
currentDate.value = `${now.getFullYear()}年${String(now.getMonth() + 1).padStart(2, '0')}月${String(now.getDate()).padStart(2, '0')}日`;
|
||||
|
||||
// SSE连接逻辑已迁移到SseStatus组件中
|
||||
});
|
||||
|
||||
// 清理事件监听
|
||||
onBeforeUnmount(() => {
|
||||
// SSE断开连接逻辑已迁移到SseStatus组件中
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -338,7 +310,7 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
|
||||
<!-- LMS 状态 -->
|
||||
<LmsStatus />
|
||||
<LmsStatus type="line2" />
|
||||
</div>
|
||||
<SettingsModal />
|
||||
<!-- <div class="right-info" @click="showSettings">
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { login, getCaptcha } from '@/api/system'
|
||||
import { getCaptcha } from '@/api/system'
|
||||
import type { LoginInfo } from '@/api/system/model'
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import { setToken } from '@/utils/auth';
|
||||
import type { Rule } from 'ant-design-vue/es/form'
|
||||
import { useAuthStore } from '@/store'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
const { loginLoading } = storeToRefs(authStore)
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive<LoginInfo>({
|
||||
@@ -19,11 +20,11 @@ const formData = reactive<LoginInfo>({
|
||||
|
||||
// 验证码图片
|
||||
const captchaImg = ref('')
|
||||
const loading = ref(false)
|
||||
const captchaLoading = ref(false)
|
||||
const loadCaptchaFail = ref(false)
|
||||
|
||||
// 表单验证规则
|
||||
const rules: Record<string, Rule[]> = {
|
||||
const rules: Record<string, Rule[]> = reactive({
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'change' }
|
||||
],
|
||||
@@ -33,20 +34,26 @@ const rules: Record<string, Rule[]> = {
|
||||
code: [
|
||||
{ required: true, message: '请输入验证码', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
// 获取验证码
|
||||
const refreshCaptcha = async () => {
|
||||
try {
|
||||
captchaImg.value = ''
|
||||
captchaLoading.value = true
|
||||
await getCaptcha().then((res: any) => {
|
||||
loadCaptchaFail.value = false;
|
||||
if (res.captchaOnOff) {
|
||||
captchaImg.value = "data:image/gif;base64," + res.img
|
||||
rules.code[0].required = true;
|
||||
} else {
|
||||
rules.code[0].required = false;
|
||||
}
|
||||
formData.uuid = res.uuid
|
||||
formData.code = '' // 清空验证码输入
|
||||
})
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
loadCaptchaFail.value = true;
|
||||
message.error('获取验证码失败')
|
||||
console.error('获取验证码失败:', error)
|
||||
} finally {
|
||||
@@ -57,27 +64,10 @@ const refreshCaptcha = async () => {
|
||||
// 登录处理
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
await login(formData).then(async (res: any) => {
|
||||
if (res.code === 200) {
|
||||
message.success('登录成功')
|
||||
setToken(res.token)
|
||||
// 可以在这里保存用户信息到store
|
||||
// 跳转到主页
|
||||
router.push('/')
|
||||
} else {
|
||||
message.error(res.msg || '登录失败')
|
||||
// 登录失败后重新获取验证码
|
||||
await refreshCaptcha()
|
||||
}
|
||||
})
|
||||
await authStore.authLogin(formData);
|
||||
} catch (error: any) {
|
||||
message.error(error.message || error.msg || '登录失败,请检查网络连接')
|
||||
console.error('登录失败:', error)
|
||||
// 登录失败后重新获取验证码
|
||||
await refreshCaptcha()
|
||||
} finally {
|
||||
loading.value = false
|
||||
message.error(error.message || '登录失败');
|
||||
await refreshCaptcha();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +126,7 @@ refreshCaptcha()
|
||||
</a-form-item>
|
||||
|
||||
<!-- 验证码 -->
|
||||
<a-form-item name="code" class="form-item">
|
||||
<a-form-item name="code" class="form-item" v-if="rules.code[0].required">
|
||||
<div class="captcha-container">
|
||||
<a-input
|
||||
v-model:value="formData.code"
|
||||
@@ -155,9 +145,12 @@ refreshCaptcha()
|
||||
alt="验证码"
|
||||
class="captcha-image"
|
||||
/>
|
||||
<div v-else class="captcha-loading">
|
||||
<div v-else-if="captchaLoading" class="captcha-loading">
|
||||
<i-lucide-loader class="loading-icon" />
|
||||
</div>
|
||||
<div v-else-if="!captchaLoading && loadCaptchaFail" class="captcha-loading">
|
||||
获取失败
|
||||
</div>
|
||||
<div class="captcha-refresh-hint">
|
||||
<i-lucide-refresh-cw class="refresh-icon" />
|
||||
点击刷新
|
||||
@@ -172,11 +165,11 @@ refreshCaptcha()
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
:loading="loginLoading"
|
||||
class="login-button"
|
||||
block
|
||||
>
|
||||
{{ loading ? '登录中...' : '登录' }}
|
||||
{{ loginLoading ? '登录中...' : '登录' }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
Reference in New Issue
Block a user