diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..cd9578a --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,6 @@ + + + diff --git a/frontend/src/assets/vite.svg b/frontend/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/frontend/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/frontend/src/assets/vue.svg b/frontend/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/frontend/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/HelloWorld.vue b/frontend/src/components/HelloWorld.vue new file mode 100644 index 0000000..c232865 --- /dev/null +++ b/frontend/src/components/HelloWorld.vue @@ -0,0 +1,95 @@ + + + diff --git a/frontend/src/components/PageHeader.vue b/frontend/src/components/PageHeader.vue new file mode 100644 index 0000000..aaa012d --- /dev/null +++ b/frontend/src/components/PageHeader.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/frontend/src/composables/useMockMode.ts b/frontend/src/composables/useMockMode.ts new file mode 100644 index 0000000..50d5396 --- /dev/null +++ b/frontend/src/composables/useMockMode.ts @@ -0,0 +1,12 @@ +import { computed } from 'vue' +import { useRoute } from 'vue-router' + +/** + * Mock模式检测 + * 根据route.path是否以/mock开头判断 + */ +export function useMockMode() { + const route = useRoute() + const isMock = computed(() => route.path.startsWith('/mock')) + return { isMock } +} diff --git a/frontend/src/composables/useMockPath.ts b/frontend/src/composables/useMockPath.ts new file mode 100644 index 0000000..f82e854 --- /dev/null +++ b/frontend/src/composables/useMockPath.ts @@ -0,0 +1,8 @@ +// 提供统一的 Mock 路径前缀判断与路径构建 helpers +export function useMockPath() { + const isMock = typeof window !== 'undefined' && window.location.pathname.startsWith('/mock') + function mockPath(path: string): string { + return isMock ? `/mock${path}` : path + } + return { isMock, mockPath } +} diff --git a/frontend/src/layouts/AdminLayout.vue b/frontend/src/layouts/AdminLayout.vue new file mode 100644 index 0000000..7c85e3b --- /dev/null +++ b/frontend/src/layouts/AdminLayout.vue @@ -0,0 +1,227 @@ + + + + + diff --git a/frontend/src/layouts/ScreenLayout.vue b/frontend/src/layouts/ScreenLayout.vue new file mode 100644 index 0000000..8afe66d --- /dev/null +++ b/frontend/src/layouts/ScreenLayout.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..1db65b4 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,19 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import zhCn from 'element-plus/es/locale/lang/zh-cn' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +import App from './App.vue' +import router from './router' +import './styles/admin.scss' + +const app = createApp(App) + +// 注册所有Element Plus图标 +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +app.use(ElementPlus, { locale: zhCn }) +app.use(router) +app.mount('#app') diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 0000000..0dfefb8 --- /dev/null +++ b/frontend/src/router/index.ts @@ -0,0 +1,129 @@ +import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' + +// 管理后台Layout +const AdminLayout = () => import('@/layouts/AdminLayout.vue') +// 大屏Layout +const ScreenLayout = () => import('@/layouts/ScreenLayout.vue') + +// 页面组件 +const LoginPage = () => import('@/views/login/LoginPage.vue') +const DashboardPage = () => import('@/views/dashboard/DashboardPage.vue') +const MachineListPage = () => import('@/views/machine/MachineListPage.vue') +const MachineDetailPage = () => import('@/views/machine/MachineDetailPage.vue') +const BrandListPage = () => import('@/views/brand/BrandListPage.vue') +const BrandEditPage = () => import('@/views/brand/BrandEditPage.vue') +const CollectAddressListPage = () => import('@/views/collect-address/CollectAddressListPage.vue') +const CollectAddressDetailPage = () => import('@/views/collect-address/CollectAddressDetailPage.vue') +const WorkerListPage = () => import('@/views/worker/WorkerListPage.vue') +const WorkerDetailPage = () => import('@/views/worker/WorkerDetailPage.vue') +const ProductionPage = () => import('@/views/production/ProductionPage.vue') +const AlertPage = () => import('@/views/alert/AlertPage.vue') +const SettingsPage = () => import('@/views/settings/SettingsPage.vue') +const LogPage = () => import('@/views/log/LogPage.vue') +const ScreenConfigPage = () => import('@/views/screen-config/ScreenConfigPage.vue') +const ScreenPage = () => import('@/views/screen/ScreenPage.vue') + +// 正常路由 +const normalRoutes: RouteRecordRaw[] = [ + { path: '/login', name: 'Login', component: LoginPage, meta: { title: '登录' } }, + { + path: '/', + component: AdminLayout, + redirect: '/dashboard', + children: [ + { path: 'dashboard', name: 'Dashboard', component: DashboardPage, meta: { title: '仪表盘' } }, + { path: 'machine', name: 'MachineList', component: MachineListPage, meta: { title: '设备管理' } }, + { path: 'machine/:id', name: 'MachineDetail', component: MachineDetailPage, meta: { title: '设备详情' } }, + { path: 'brand', name: 'BrandList', component: BrandListPage, meta: { title: '品牌模板' } }, + { path: 'brand/create', name: 'BrandCreate', component: BrandEditPage, meta: { title: '新增品牌' } }, + { path: 'brand/:id/edit', name: 'BrandEdit', component: BrandEditPage, meta: { title: '编辑品牌' } }, + { path: 'collect-address', name: 'CollectAddressList', component: CollectAddressListPage, meta: { title: '采集地址' } }, + { path: 'collect-address/:id', name: 'CollectAddressDetail', component: CollectAddressDetailPage, meta: { title: '采集地址详情' } }, + { path: 'worker', name: 'WorkerList', component: WorkerListPage, meta: { title: '员工管理' } }, + { path: 'worker/:id', name: 'WorkerDetail', component: WorkerDetailPage, meta: { title: '员工详情' } }, + { path: 'production', name: 'Production', component: ProductionPage, meta: { title: '产量报表' } }, + { path: 'alert', name: 'Alert', component: AlertPage, meta: { title: '告警中心' } }, + { path: 'settings', name: 'Settings', component: SettingsPage, meta: { title: '系统设置' } }, + { path: 'log', name: 'Log', component: LogPage, meta: { title: '操作日志' } }, + { path: 'screen-config', name: 'ScreenConfig', component: ScreenConfigPage, meta: { title: '大屏配置' } }, + ], + }, + { + path: '/screen', + component: ScreenLayout, + children: [ + { path: '', name: 'Screen', component: ScreenPage, meta: { title: '大屏看板' } }, + ], + }, +] + +// Mock路由:复制正常路由,添加/mock前缀 +function generateMockRoutes(routes: RouteRecordRaw[]): RouteRecordRaw[] { + const mockRoutes: RouteRecordRaw[] = [] + for (const route of routes) { + if (route.path === '/login') { + mockRoutes.push({ path: '/mock/login', name: 'MockLogin', component: route.component!, meta: { ...route.meta, mock: true } }) + } else if (route.path === '/') { + mockRoutes.push({ + path: '/mock', + component: route.component, + redirect: '/mock/dashboard', + children: (route.children || []).map(child => ({ + ...child, + path: child.path, + name: `Mock${String(child.name)}`, + meta: { ...child.meta, mock: true }, + })), + }) + } else if (route.path === '/screen') { + mockRoutes.push({ + path: '/mock/screen', + component: route.component, + children: (route.children || []).map(child => ({ + ...child, + path: child.path, + name: `Mock${String(child.name)}`, + meta: { ...child.meta, mock: true }, + })), + }) + } + } + return mockRoutes +} + +const router = createRouter({ + history: createWebHistory(), + routes: [...normalRoutes, ...generateMockRoutes(normalRoutes)], +}) + +// 路由守卫 +router.beforeEach((to, _from, next) => { + const token = localStorage.getItem('token') + const isMock = to.path.startsWith('/mock') + + // 大屏免认证 + if (to.path === '/screen' || to.path === '/mock/screen') { + next() + return + } + + // 访问登录页:已登录则跳转首页 + if (to.path === '/login' || to.path === '/mock/login') { + if (token) { + next(isMock ? '/mock/dashboard' : '/dashboard') + } else { + next() + } + return + } + + // 访问管理后台:未登录则跳转登录 + if (!token) { + next(isMock ? '/mock/login' : '/login') + return + } + + next() +}) + +export default router diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..527d4fb --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,296 @@ +:root { + --text: #6b6375; + --text-h: #08060d; + --bg: #fff; + --border: #e5e4e7; + --code-bg: #f4f3ec; + --accent: #aa3bff; + --accent-bg: rgba(170, 59, 255, 0.1); + --accent-border: rgba(170, 59, 255, 0.5); + --social-bg: rgba(244, 243, 236, 0.5); + --shadow: + rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; + + --sans: system-ui, 'Segoe UI', Roboto, sans-serif; + --heading: system-ui, 'Segoe UI', Roboto, sans-serif; + --mono: ui-monospace, Consolas, monospace; + + font: 18px/145% var(--sans); + letter-spacing: 0.18px; + color-scheme: light dark; + color: var(--text); + background: var(--bg); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + @media (max-width: 1024px) { + font-size: 16px; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --text: #9ca3af; + --text-h: #f3f4f6; + --bg: #16171d; + --border: #2e303a; + --code-bg: #1f2028; + --accent: #c084fc; + --accent-bg: rgba(192, 132, 252, 0.15); + --accent-border: rgba(192, 132, 252, 0.5); + --social-bg: rgba(47, 48, 58, 0.5); + --shadow: + rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; + } + + #social .button-icon { + filter: invert(1) brightness(2); + } +} + +body { + margin: 0; +} + +h1, +h2 { + font-family: var(--heading); + font-weight: 500; + color: var(--text-h); +} + +h1 { + font-size: 56px; + letter-spacing: -1.68px; + margin: 32px 0; + @media (max-width: 1024px) { + font-size: 36px; + margin: 20px 0; + } +} +h2 { + font-size: 24px; + line-height: 118%; + letter-spacing: -0.24px; + margin: 0 0 8px; + @media (max-width: 1024px) { + font-size: 20px; + } +} +p { + margin: 0; +} + +code, +.counter { + font-family: var(--mono); + display: inline-flex; + border-radius: 4px; + color: var(--text-h); +} + +code { + font-size: 15px; + line-height: 135%; + padding: 4px 8px; + background: var(--code-bg); +} + +.counter { + font-size: 16px; + padding: 5px 10px; + border-radius: 5px; + color: var(--accent); + background: var(--accent-bg); + border: 2px solid transparent; + transition: border-color 0.3s; + margin-bottom: 24px; + + &:hover { + border-color: var(--accent-border); + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } +} + +.hero { + position: relative; + + .base, + .framework, + .vite { + inset-inline: 0; + margin: 0 auto; + } + + .base { + width: 170px; + position: relative; + z-index: 0; + } + + .framework, + .vite { + position: absolute; + } + + .framework { + z-index: 1; + top: 34px; + height: 28px; + transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) + scale(1.4); + } + + .vite { + z-index: 0; + top: 107px; + height: 26px; + width: auto; + transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) + scale(0.8); + } +} + +#app { + width: 1126px; + max-width: 100%; + margin: 0 auto; + text-align: center; + border-inline: 1px solid var(--border); + min-height: 100svh; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +#center { + display: flex; + flex-direction: column; + gap: 25px; + place-content: center; + place-items: center; + flex-grow: 1; + + @media (max-width: 1024px) { + padding: 32px 20px 24px; + gap: 18px; + } +} + +#next-steps { + display: flex; + border-top: 1px solid var(--border); + text-align: left; + + & > div { + flex: 1 1 0; + padding: 32px; + @media (max-width: 1024px) { + padding: 24px 20px; + } + } + + .icon { + margin-bottom: 16px; + width: 22px; + height: 22px; + } + + @media (max-width: 1024px) { + flex-direction: column; + text-align: center; + } +} + +#docs { + border-right: 1px solid var(--border); + + @media (max-width: 1024px) { + border-right: none; + border-bottom: 1px solid var(--border); + } +} + +#next-steps ul { + list-style: none; + padding: 0; + display: flex; + gap: 8px; + margin: 32px 0 0; + + .logo { + height: 18px; + } + + a { + color: var(--text-h); + font-size: 16px; + border-radius: 6px; + background: var(--social-bg); + display: flex; + padding: 6px 12px; + align-items: center; + gap: 8px; + text-decoration: none; + transition: box-shadow 0.3s; + + &:hover { + box-shadow: var(--shadow); + } + .button-icon { + height: 18px; + width: 18px; + } + } + + @media (max-width: 1024px) { + margin-top: 20px; + flex-wrap: wrap; + justify-content: center; + + li { + flex: 1 1 calc(50% - 8px); + } + + a { + width: 100%; + justify-content: center; + box-sizing: border-box; + } + } +} + +#spacer { + height: 88px; + border-top: 1px solid var(--border); + @media (max-width: 1024px) { + height: 48px; + } +} + +.ticks { + position: relative; + width: 100%; + + &::before, + &::after { + content: ''; + position: absolute; + top: -4.5px; + border: 5px solid transparent; + } + + &::before { + left: 0; + border-left-color: var(--border); + } + &::after { + right: 0; + border-right-color: var(--border); + } +} diff --git a/frontend/src/styles/admin.scss b/frontend/src/styles/admin.scss new file mode 100644 index 0000000..2ae2117 --- /dev/null +++ b/frontend/src/styles/admin.scss @@ -0,0 +1,59 @@ +/* 管理后台全局样式 */ + +/* 重置默认样式 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body, #app { + height: 100%; + font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', + 'Microsoft YaHei', '微软雅黑', Arial, sans-serif; +} + +/* Element Plus表格统一样式 */ +.el-table { + --el-table-header-bg-color: #f5f7fa; +} + +/* 确认框警告图标颜色 */ +.el-message-box__status.el-icon { + color: var(--el-color-warning) !important; +} + +/* 行内表单中的下拉框最小宽度,确保文字正常显示 */ +.el-form--inline .el-select { + min-width: 120px; +} + +/* 分页组件间距 */ +.el-pagination { + margin-top: 16px; + justify-content: flex-end; +} + +/* 通用间距 */ +.mb-16 { margin-bottom: 16px; } +.mt-20 { margin-top: 20px; } +.gap-12 { gap: 12px; } + +/* Flex布局 */ +.flex-center { display: flex; align-items: center; } +.flex-between { display: flex; justify-content: space-between; align-items: center; } +.flex-gap { display: flex; align-items: center; gap: 12px; } + +/* 卡片间距 */ +.page-card { margin-bottom: 16px; } +.card + .card { margin-top: 20px; } + +/* 表单操作栏 */ +.action-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; } + +/* 页面标题 */ +.page-title { font-size: 16px; font-weight: bold; } + +/* 筛选栏 */ +.filter-bar { margin-bottom: 16px; } +.filter-bar .el-form-item { margin-bottom: 0; } diff --git a/frontend/src/styles/screen.scss b/frontend/src/styles/screen.scss new file mode 100644 index 0000000..e0b0f8d --- /dev/null +++ b/frontend/src/styles/screen.scss @@ -0,0 +1,42 @@ +/* 大屏看板深色主题 */ + +.screen-layout { + height: 100vh; + background-color: #0f0f1a; + color: #e0e0e0; + overflow: hidden; + font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif; + + /* 覆盖Element Plus默认样式 */ + .el-card { + background-color: #1a1a2e; + border-color: #2a2a4a; + color: #e0e0e0; + } + + .el-select { + --el-fill-color-blank: #1a1a2e; + --el-text-color-regular: #e0e0e0; + --el-border-color: #2a2a4a; + } + + .el-select__wrapper { + background-color: #1a1a2e !important; + box-shadow: 0 0 0 1px #2a2a4a inset !important; + } + + .el-select__selected-item { + color: #e0e0e0 !important; + } +} + +// 大屏通用辅助类(约定在大屏页面中复用) +.mb-16 { margin-bottom: 16px; } +.mt-20 { margin-top: 20px; } +.gap-12 { gap: 12px; } + +.flex-center { display: flex; align-items: center; } +.flex-between { display: flex; justify-content: space-between; align-items: center; } +.flex-gap { display: flex; align-items: center; gap: 12px; } + +.page-title { font-size: 16px; font-weight: bold; } diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts new file mode 100644 index 0000000..de82d58 --- /dev/null +++ b/frontend/src/types/index.ts @@ -0,0 +1,225 @@ +/** + * API通用响应类型 + * @template T + * data 的具体类型 + */ +export interface ApiResponse { + /** 响应码,0 表示成功,其他表示错误 */ + code: number + /** 响应信息描述 */ + message: string + /** 业务数据 */ + data: T +} + +/** + * 分页通用数据结构 + */ +export interface PaginatedResponse { + /** 结果项集合 */ + items: T[] + /** 总记录数 */ + total: number + /** 当前页码 */ + page: number + /** 每页条数 */ + pageSize: number +} + +// =================== 业务实体模型 =================== +/** 机床 */ +export interface Machine { + id: number + name: string + deviceCode: string + workshopName: string + brandName: string + ipAddress: string + isOnline: boolean + isEnabled: boolean + workerName?: string + collectAddressName?: string +} + +/** 机床状态 */ +export interface MachineStatus { + programName: string + partCount: number + runStatus: string + operationMode: string + spindleSpeed: number + feedRate: number + spindleLoad: number + machiningStatus: string + lastCollectTime: string +} + +/** 品牌 */ +export interface Brand { + id: number + brandName: string + deviceField: string + tagsPath: string + isEnabled: boolean + fieldCount: number +} + +/** 品牌字段映射 */ +export interface BrandFieldMapping { + id?: number + standardField: string + fieldName: string + matchMethod: string + dataType: string + required: boolean +} + +/** 采集地址 */ +export interface CollectAddress { + id: number + name: string + url: string + brandName: string + interval: number + isEnabled: boolean + lastCollectTime: string + machineCount: number + failCount: number +} + +/** 工人 */ +export interface Worker { + id: number + code: string + name: string + isEnabled: boolean + machineCount: number + machines?: Machine[] +} + +/** 告警 */ +export interface Alert { + id: number + alertType: string + machineName: string + message: string + isResolved: boolean + createdAt: string + resolvedAt?: string +} + +/** 产量记录 */ +export interface ProductionRecord { + id: number + date: string + machineName: string + programName: string + quantity: number + runTime: number + cuttingTime: number + dayStatus: string + adjusted: boolean +} + +/** 修正历史 */ +export interface AdjustmentHistory { + id: number + adjustedAt: string + beforeQuantity: number + afterQuantity: number + reason: string + operatorName: string +} + +/** 系统配置 */ +export interface SysConfig { + id: number + configKey: string + configValue: string + valueType: 'string' | 'number' + description: string + isSensitive: boolean +} + +/** 车间 */ +export interface Workshop { + id: number + name: string + description?: string +} + +/** 操作日志 */ +export interface OperationLog { + id: number + timestamp: string + level: string + source: string + message: string + stackTrace?: string + extraData?: string +} + +/** 大屏卡片配置 */ +export interface ScreenCard { + id: number + cardName: string + cardKey: string + cardType: string + metric: string + dimension?: string + sortOrder: number + isEnabled: boolean + chartConfig?: string +} + +/** 大屏筛选配置 */ +export interface ScreenFilter { + id: number + filterKey: string + filterType: string + filterValues: string + isDefault: boolean + sortOrder: number +} + +/** 仪表盘统计 */ +export interface DashboardSummary { + onlineCount: number + totalMachines: number + todayProduction: number + activeAlerts: number +} + +// 机床产量排行榜行数据 +export interface MachineRankRow { + rank?: number + id?: number + machineName: string + quantity?: number + status?: number + program?: string +} + +// 工人产量排行榜行数据 +export interface WorkerRankRow { + rank?: number + id?: number + workerName: string + machineCount?: number + totalQuantity?: number +} + +/** 采集服务状态 */ +export interface CollectorStatus { + status: string + uptimeSeconds: number + lastCollectTime?: string +} + +/** 产量看板汇总 */ +export interface ProductionDashboardSummary { + totalQuantity: number + activeMachineCount: number + totalCuttingTime: number + avgQuantityPerMachine: number +} diff --git a/frontend/src/utils/echarts.ts b/frontend/src/utils/echarts.ts new file mode 100644 index 0000000..b0646c8 --- /dev/null +++ b/frontend/src/utils/echarts.ts @@ -0,0 +1,11 @@ +// ECharts 按需导入工具,避免打包整个echarts库,提升构建体积 +// 只注册需要的组件,确保三个页面的图表渲染正常 +import * as echarts from 'echarts/core' +import { BarChart, LineChart } from 'echarts/charts' +import { GridComponent, TooltipComponent, TitleComponent } from 'echarts/components' +import { CanvasRenderer } from 'echarts/renderers' + +// 注册所需的图表/组件/渲染器 +echarts.use([BarChart, LineChart, GridComponent, TooltipComponent, TitleComponent, CanvasRenderer]) + +export default echarts diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts new file mode 100644 index 0000000..7fc9f1d --- /dev/null +++ b/frontend/src/utils/request.ts @@ -0,0 +1,66 @@ +import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from 'axios' +import { ElMessage } from 'element-plus' +import router from '@/router' + +// 创建axios实例 +const service = axios.create({ + timeout: 30000, +}) + +// 请求拦截器 +service.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + // 根据当前路由判断是否为Mock模式 + const isMock = window.location.pathname.startsWith('/mock') + config.baseURL = isMock ? '/mock-api' : '/api' + + // 自动添加Token + const token = localStorage.getItem('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => Promise.reject(error) +) + +// 响应拦截器 +service.interceptors.response.use( + (response: AxiosResponse) => { + const res = response.data + if (res.code === 0) { + return res + } + + // Token无效/过期 + if (res.code === 40101) { + localStorage.removeItem('token') + const isMock = window.location.pathname.startsWith('/mock') + router.push(isMock ? '/mock/login' : '/login') + ElMessage.warning('登录已过期') + return Promise.reject(new Error(res.message)) + } + + // 其他业务错误 + const errorMessages: Record = { + 40001: res.message || '参数校验失败', + 40002: res.message || '资源不存在', + 40003: res.message || '资源已存在', + 50001: '服务器错误', + 50002: '采集服务未响应', + } + const msg = errorMessages[res.code] || res.message || '请求失败' + ElMessage.error(msg) + return Promise.reject(new Error(msg)) + }, + (error) => { + if (error.response) { + ElMessage.error('网络异常') + } else if (error.code === 'ECONNABORTED') { + ElMessage.error('请求超时') + } + return Promise.reject(error) + } +) + +export default service diff --git a/frontend/src/views/alert/AlertPage.vue b/frontend/src/views/alert/AlertPage.vue new file mode 100644 index 0000000..8f5ae3d --- /dev/null +++ b/frontend/src/views/alert/AlertPage.vue @@ -0,0 +1,201 @@ + + + diff --git a/frontend/src/views/brand/BrandEditPage.vue b/frontend/src/views/brand/BrandEditPage.vue new file mode 100644 index 0000000..8f4cd3d --- /dev/null +++ b/frontend/src/views/brand/BrandEditPage.vue @@ -0,0 +1,56 @@ + + diff --git a/frontend/src/views/brand/BrandListPage.vue b/frontend/src/views/brand/BrandListPage.vue new file mode 100644 index 0000000..70c944c --- /dev/null +++ b/frontend/src/views/brand/BrandListPage.vue @@ -0,0 +1,52 @@ + + diff --git a/frontend/src/views/collect-address/CollectAddressDetailPage.vue b/frontend/src/views/collect-address/CollectAddressDetailPage.vue new file mode 100644 index 0000000..647776f --- /dev/null +++ b/frontend/src/views/collect-address/CollectAddressDetailPage.vue @@ -0,0 +1,84 @@ + + diff --git a/frontend/src/views/collect-address/CollectAddressListPage.vue b/frontend/src/views/collect-address/CollectAddressListPage.vue new file mode 100644 index 0000000..e5d751f --- /dev/null +++ b/frontend/src/views/collect-address/CollectAddressListPage.vue @@ -0,0 +1,71 @@ + + diff --git a/frontend/src/views/dashboard/DashboardPage.vue b/frontend/src/views/dashboard/DashboardPage.vue new file mode 100644 index 0000000..b292f66 --- /dev/null +++ b/frontend/src/views/dashboard/DashboardPage.vue @@ -0,0 +1,260 @@ + + + + + diff --git a/frontend/src/views/log/LogPage.vue b/frontend/src/views/log/LogPage.vue new file mode 100644 index 0000000..3da06be --- /dev/null +++ b/frontend/src/views/log/LogPage.vue @@ -0,0 +1,255 @@ + + + diff --git a/frontend/src/views/login/LoginPage.vue b/frontend/src/views/login/LoginPage.vue new file mode 100644 index 0000000..5a08c1e --- /dev/null +++ b/frontend/src/views/login/LoginPage.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/frontend/src/views/machine/MachineDetailPage.vue b/frontend/src/views/machine/MachineDetailPage.vue new file mode 100644 index 0000000..c3ab28c --- /dev/null +++ b/frontend/src/views/machine/MachineDetailPage.vue @@ -0,0 +1,107 @@ + + diff --git a/frontend/src/views/machine/MachineListPage.vue b/frontend/src/views/machine/MachineListPage.vue new file mode 100644 index 0000000..bd913e3 --- /dev/null +++ b/frontend/src/views/machine/MachineListPage.vue @@ -0,0 +1,298 @@ + + + diff --git a/frontend/src/views/production/ProductionPage.vue b/frontend/src/views/production/ProductionPage.vue new file mode 100644 index 0000000..8d9ad9c --- /dev/null +++ b/frontend/src/views/production/ProductionPage.vue @@ -0,0 +1,233 @@ + + diff --git a/frontend/src/views/screen-config/ScreenConfigPage.vue b/frontend/src/views/screen-config/ScreenConfigPage.vue new file mode 100644 index 0000000..2e60aab --- /dev/null +++ b/frontend/src/views/screen-config/ScreenConfigPage.vue @@ -0,0 +1,334 @@ + + + + + diff --git a/frontend/src/views/screen/ScreenPage.vue b/frontend/src/views/screen/ScreenPage.vue new file mode 100644 index 0000000..e6b4024 --- /dev/null +++ b/frontend/src/views/screen/ScreenPage.vue @@ -0,0 +1,529 @@ + + + + + diff --git a/frontend/src/views/settings/SettingsPage.vue b/frontend/src/views/settings/SettingsPage.vue new file mode 100644 index 0000000..276c47d --- /dev/null +++ b/frontend/src/views/settings/SettingsPage.vue @@ -0,0 +1,307 @@ + + + diff --git a/frontend/src/views/worker/WorkerDetailPage.vue b/frontend/src/views/worker/WorkerDetailPage.vue new file mode 100644 index 0000000..e935642 --- /dev/null +++ b/frontend/src/views/worker/WorkerDetailPage.vue @@ -0,0 +1,74 @@ + + diff --git a/frontend/src/views/worker/WorkerListPage.vue b/frontend/src/views/worker/WorkerListPage.vue new file mode 100644 index 0000000..84f04e8 --- /dev/null +++ b/frontend/src/views/worker/WorkerListPage.vue @@ -0,0 +1,98 @@ + +