diff --git a/.env.development b/.env.development index 11728ea..695ffb3 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,8 @@ VITE_APP_API_BASE_URL='http://10.97.245.96' +VITE_APP_API_INT_BASE_URL='http://127.0.0.1' +VITE_APP_API_EXT_BASE_URL= 'http://8.134.12.216:3005' # 32字节密钥(AES-256) -VITE_APP_KEY='cms.SBU4.PE$20@2aXz3kL9pR7sQ4fT1vB' +VITE_APP_KEY='cms.SBU4.PE$20@2aXz3kL9pR7sQ4fT1' # 16字节向量 VITE_APP_IV='m8Zp2x7cK9bF3dS5' \ No newline at end of file diff --git a/.env.production b/.env.production index eb8add6..341d4f0 100644 --- a/.env.production +++ b/.env.production @@ -1,7 +1,8 @@ -VITE_APP_AUTH_API='http://192.168.8.8:9000' -VITE_APP_API_BASE_URL='http://192.168.8.8:9002' +VITE_APP_API_BASE_URL='http://10.97.245.96' +VITE_APP_API_INT_BASE_URL='http://10.97.245.96' +VITE_APP_API_EXT_BASE_URL= 'http://8.134.12.216:3005' # 32字节密钥(AES-256) -VITE_APP_KEY='cms.SBU4.PE$20@2aXz3kL9pR7sQ4fT1vB' +VITE_APP_KEY='cms.SBU4.PE$20@2aXz3kL9pR7sQ4fT1' # 16字节向量 VITE_APP_IV='m8Zp2x7cK9bF3dS5' \ No newline at end of file diff --git a/auto-imports.d.ts b/auto-imports.d.ts index 9d24007..76e1ee6 100644 --- a/auto-imports.d.ts +++ b/auto-imports.d.ts @@ -6,5 +6,5 @@ // biome-ignore lint: disable export {} declare global { - + const ElMessage: typeof import('element-plus/es')['ElMessage'] } diff --git a/components.d.ts b/components.d.ts index 17f34fd..6ab28a9 100644 --- a/components.d.ts +++ b/components.d.ts @@ -9,35 +9,27 @@ export {} declare module 'vue' { export interface GlobalComponents { AuthPage: typeof import('./src/components/authentication/AuthPage.vue')['default'] - ElAlert: typeof import('element-plus/es')['ElAlert'] - ElAside: typeof import('element-plus/es')['ElAside'] - ElAvatar: typeof import('element-plus/es')['ElAvatar'] - ElBadge: typeof import('element-plus/es')['ElBadge'] - ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] - ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] ElButton: typeof import('element-plus/es')['ElButton'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] - ElContainer: typeof import('element-plus/es')['ElContainer'] - ElDropdown: typeof import('element-plus/es')['ElDropdown'] - ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] - ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] - ElFooter: typeof import('element-plus/es')['ElFooter'] ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] - ElHeader: typeof import('element-plus/es')['ElHeader'] ElIcon: typeof import('element-plus/es')['ElIcon'] ElInput: typeof import('element-plus/es')['ElInput'] - ElMain: typeof import('element-plus/es')['ElMain'] ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElOption: typeof import('element-plus/es')['ElOption'] + ElPagination: typeof import('element-plus/es')['ElPagination'] ElSelect: typeof import('element-plus/es')['ElSelect'] - ElSkeleton: typeof import('element-plus/es')['ElSkeleton'] ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSwitch: typeof import('element-plus/es')['ElSwitch'] LoginForm: typeof import('./src/components/authentication/LoginForm.vue')['default'] RegisterForm: typeof import('./src/components/authentication/RegisterForm.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + Sidebar: typeof import('./src/components/menu/Sidebar.vue')['default'] + SidebarItem: typeof import('./src/components/menu/SidebarItem.vue')['default'] + } + export interface GlobalDirectives { + vLoading: typeof import('element-plus/es')['ElLoadingDirective'] } } diff --git a/index.html b/index.html index b19040a..3df5019 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Vite App + SBU4 I4.0
diff --git a/public/favicon.ico b/public/favicon.ico index df36fcf..6640bfe 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/src/components/authentication/AuthPage.vue b/src/components/authentication/AuthPage.vue index fc7da43..6130076 100644 --- a/src/components/authentication/AuthPage.vue +++ b/src/components/authentication/AuthPage.vue @@ -80,7 +80,6 @@ - diff --git a/src/components/menu/SidebarItem.vue b/src/components/menu/SidebarItem.vue new file mode 100644 index 0000000..6d70a3f --- /dev/null +++ b/src/components/menu/SidebarItem.vue @@ -0,0 +1,72 @@ + + + diff --git a/src/i18n/lang/en-US.js b/src/i18n/lang/en-US.js index 3e5d34d..8c13ef6 100644 --- a/src/i18n/lang/en-US.js +++ b/src/i18n/lang/en-US.js @@ -10,8 +10,8 @@ export default { passwordMinLength: 'Password must be at least 6 characters', confirmPassword: 'Confirm Password', passwordMismatch: 'Passwords do not match', - usernamePlaceholder: 'User name', - passwordPlaceholder: 'Password', + usernamePlaceholder: 'Enter username', + passwordPlaceholder: 'Enter password', loginBtn: 'Login', confirmPasswordPlaceholder: 'Please confirm your password', registerBtn: 'Register', @@ -21,16 +21,95 @@ export default { invalidCredentials: 'Invalid username or password', serverError: 'Server error, please try again later', networkError: 'Network error, please check your connection', - unknownError: 'An unknown error occurred, please try again' + unknownError: 'An unknown error occurred, please try again', + requestTimeout: 'Request timed out, please try again later', + unauthorized: 'Unauthorized access, please login again', + forbidden: 'Access forbidden, insufficient permissions', + notFound: 'Requested resource not found', + requestFailed: 'Request processing failed', + usernameExists: 'Username already exists', + emailExists: 'Email already registered', + invalidUsername: 'Invalid username format', + invalidPassword: 'Password format does not meet requirements', + registerSuccess: 'Registration successful', + registerFailed: 'Registration failed, please try again' }, common: { login: 'Login', register: 'Register', - switchLang: 'Switch Language' + switchLang: 'Switch Language', + languageChanged: 'Language switched successfully', + close: 'Close', + confirm: 'Confirm', + cancel: 'Cancel', + refresh: 'Refresh', + collapseMenu: 'Collapse Menu', + expandMenu: 'Expand Menu', + operationSuccess: 'Operation successful', + operationFailed: 'Operation failed', + pleaseWait: 'Please wait...', + back: 'Back', + save: 'Save', + delete: 'Delete', + edit: 'Edit', + add: 'Add', + search: 'Search', + reset: 'Reset' }, el: { form: { required: 'Please fill in the required field' } + }, + menu: { + settings: 'Settings', + manage: 'Management', + equipment: 'Equipment', + purchase: 'Purchase', + spare: 'Spare Parts', + maintenance: 'Maintenance', + report: 'Reports', + capacity: 'Capacity', + applet: 'Mini Program', + useringroup: 'User Groups', + userindepartment: 'User Departments', + passwordreset: 'Password Reset', + users: 'Users', + groups: 'Groups', + departments: 'Departments', + instruments: 'Instruments', + fixtures: 'Fixtures', + racks: 'Aging Racks', + testers: 'Test Stations', + purlist: 'Purchase List', + splist: 'Spare Parts List', + dashboard: 'Dashboard', + home: 'Home', + help: 'Help Center', + dailyReport: 'Daily Report', + monthlyReport: 'Monthly Report', + annualReport: 'Annual Report', + faultReport: 'Fault Report', + systemBasic: 'Basic Settings', + security: 'Security Settings', + notifications: 'Notification Settings', + logs: 'System Logs', + fetchFailed: 'Failed to fetch menu', + noMenuData: 'No menu data available', + loadFailed: 'Failed to load menu information', + noPermission: 'No access permission', + menuTooDeep: "Menu level too deep" + }, + main: { + welcome: 'Welcome to the system', + contentDescription: 'Please select a menu item on the left to proceed' + }, + user: { + profile: "Profile", + logout: "Logout", + notifications: "Notifications", + defaultName: "User", + defaultRole: "Regular User" } } + \ No newline at end of file diff --git a/src/i18n/lang/zh-CN.js b/src/i18n/lang/zh-CN.js index 4f91c2a..de3ceff 100644 --- a/src/i18n/lang/zh-CN.js +++ b/src/i18n/lang/zh-CN.js @@ -1,4 +1,10 @@ export default { + theme: { + light: '亮色主题', + dark: '暗色主题', + custom: '自定义主题', + themeSwitched: '已切换到{theme}主题' + }, auth: { loginTitle: '用户登录', registerTitle: '用户注册', @@ -10,7 +16,7 @@ export default { passwordMinLength: '密码长度不能少于6个字符', confirmPassword: '确认密码', passwordMismatch: '两次输入的密码不一致', - usernamePlaceholder: '请输入用户名', + usernamePlaceholder: '请输入用户名', passwordPlaceholder: '请输入密码', loginBtn: '登录', confirmPasswordPlaceholder: '请再次输入密码', @@ -24,17 +30,20 @@ export default { serverError: '服务器错误,请稍后再试', networkError: '网络连接错误,请检查网络', unknownError: '发生未知错误,请重试', - // 新增错误类型翻译 - requestTimeout: '请求超时,请稍后重试', - unauthorized: '未授权访问,请重新登录', - forbidden: '禁止访问,没有权限', + requestTimeout: '请求超时超时,请稍后重试', + unauthorized: '未授权访问,请,请重新登录', + forbidden: '禁止访问访问,没有权限', notFound: '请求的资源不存在', requestFailed: '请求处理失败', - // 可能需要的其他注册相关提示 usernameExists: '用户名已存在', emailExists: '邮箱已被注册', - invalidUsername: '用户名格式不正确', - invalidPassword: '密码格式不符合要求' + invalidUsername: '用户名格式名格式不正确', + invalidPassword: '密码格式不符合要求', + sessionExpired: '登录状态已失效,请重新登录', + loginSuccess: '登录成功', + welcomeBack: '欢迎回来', + logoutSuccess: '退出登录成功', + logoutFailed: '退出登录失败' }, common: { login: '登录', @@ -43,12 +52,63 @@ export default { languageChanged: '语言已切换', close: '关闭', confirm: '确认', - cancel: '取消' + cancel: '取消', + refresh: '刷新', + collapseMenu: '折叠菜单', + expandMenu: '展开菜单', + unknownError: '未知错误', + networkError: '网络异常', + languageChanged: '语言已切换' }, el: { form: { required: '请输入必填项' } + }, + menu: { + settings: '设置', + manage: '管理', + equipment: '设备', + purchase: '采购', + spare: '备件', + maintenance: '维护', + report: '报告', + capacity: '容量', + applet: '小程序', + useringroup: '用户组别', + userindepartment: '用户部门', + passwordreset: '密码重置', + users: '用户', + groups: '组别', + departments: '部门', + instruments: '仪器', + fixtures: '夹具', + racks: '老化架', + testers: '测试位', + purlist: '采购清单', + splist: '备件清单', + fetchFailed: '获取菜单失败', + menuTooDeep: "菜单层级过深" + }, + user: { + profile: "个人信息", + logout: "退出登录", + notifications: "通知", + defaultName: "用户", + defaultRole: "普通用户", + unknown: '未知用户', + unauthorized: '未授权', + defaultRole: '普通用户' + }, + error: { + noUserInfo: '未找到用户信息', + invalidBase64: '加密数据格式无效', + decryptFailed: '用户信息解密失败', + jsonParseFailed: 'JSON解析失败', + invalidUserInfo: '用户信息格式无效', + loadUserFailed: '加载用户信息失败' + }, + notification: { + openCenter: '打开通知中心' } } - \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index 1e85329..cfc9038 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,58 +1,84 @@ import { createRouter, createWebHistory } from 'vue-router'; +import Cookies from 'js-cookie'; import AuthPage from '../components/authentication/AuthPage.vue'; import MainPage from '../views/mainpage/index.vue'; +import UutReport from '../views/uut/Report.vue'; +import UutHistory from '../views/uut/History.vue'; +import DashboardTest from '../views/dashboard/test.vue'; +import Profile from '../views/user/Profile.vue'; +import Testers from '../views/equipment/Testers.vue'; -// 路由守卫:检查是否登录 - 优化版 -const requireAuth = (to, from, next) => { - // 更可靠的登录状态检查 +// 登录状态检查工具函数 +const checkLoginStatus = () => { const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true'; const hasUserInfo = !!localStorage.getItem('userInfo'); - - if (isLoggedIn && hasUserInfo) { + const hasToken = !!Cookies.get('access_token'); + return isLoggedIn && hasUserInfo && hasToken; +}; + +// 清除登录状态方法 +const clearLoginState = () => { + localStorage.removeItem('isLoggedIn'); + localStorage.removeItem('userInfo'); + localStorage.removeItem('rememberedUser'); + Cookies.remove('access_token'); +}; + +// 路由守卫:需要登录的路由 +const requireAuth = (to, from, next) => { + if (checkLoginStatus()) { next(); } else { - // 清除无效的登录状态 - localStorage.removeItem('isLoggedIn'); - next('/'); + clearLoginState(); + next('/auth'); } }; +// 路由配置 const routes = [ { - path: '/', + path: '/auth', name: 'Auth', component: AuthPage, beforeEnter: (to, from, next) => { - const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true'; - const hasUserInfo = !!localStorage.getItem('userInfo'); - - if (isLoggedIn && hasUserInfo) { - next('/main'); + if (checkLoginStatus()) { + next('/'); } else { next(); } } }, { - path: '/main', + path: '/', name: 'Main', component: MainPage, - beforeEnter: requireAuth + beforeEnter: requireAuth, + children: [ + { path: '', redirect: '/dashboard' }, + { path: 'dashboard', name: 'Dashboard', component: DashboardTest }, + { path: 'uut/report', name: 'UutReport', component: UutReport }, + { path: 'uut/history', name: 'UutHistory', component: UutHistory }, + { path: 'profile', component: Profile }, + { path: 'testers', component: Testers } + ] }, { path: '/:pathMatch(.*)*', - redirect: '/' + name: 'NotFound', + redirect: (to) => checkLoginStatus() ? '/' : '/auth' } ]; const router = createRouter({ - history: createWebHistory(), - routes + history: createWebHistory('/sbu4i4/'), + routes, + scrollBehavior(to, from, savedPosition) { + return savedPosition || { top: 0 }; + } }); -// 全局路由守卫,处理可能的登录状态异常 +// 全局路由守卫 router.beforeEach((to, from, next) => { - // 对所有需要授权的路由进行检查 if (to.matched.some(record => record.beforeEnter === requireAuth)) { requireAuth(to, from, next); } else { @@ -60,4 +86,5 @@ router.beforeEach((to, from, next) => { } }); -export default router; +// 导出路由和工具函数 +export { router as default, clearLoginState, checkLoginStatus }; diff --git a/src/utils/api/api.js b/src/utils/api/api.js new file mode 100644 index 0000000..5e9d887 --- /dev/null +++ b/src/utils/api/api.js @@ -0,0 +1,53 @@ +// 所有API接口路径集中管理 +export const ApiPaths = { + // 仪器相关接口 + machine: { + list: '/api/i4/v1/get/machines.php', // 获取仪器列表 + update: '/api/i4/v1/update/machine.php', // 更新仪器信息 + create: '/api/i4/v1/create/machine.php', // 创建仪器 + export: '/download.php?x=Machine List' // 导出仪器列表 + }, + + // 用户相关接口 + user: { + list: '/api/i4/v1/get/users.php', // 获取用户列表 + listWithDisabled: '/api/i4/v1/get/users.php?include_disabled=1' // 包含禁用用户 + }, + + // 品牌相关接口 + brand: { + list: '/api/i4/v1/get/brands.php' // 获取品牌列表 + }, + + // 客户相关接口 + customer: { + list: '/api/i4/v1/get/customers.php' // 获取客户列表 + }, + + // 来源类型相关接口 + sourceType: { + list: '/api/i4/v1/get/eq_sources.php' // 获取来源类型列表 + }, + + // 其他来源类型相关接口 + otherSource: { + list: '/api/i4/v1/get/eq_source_other.php' // 获取其他来源类型列表 + }, + + // 状态相关接口 + status: { + list: '/api/i4/v1/get/eq_status.php' // 获取状态列表 + }, + + // 位置相关接口 + location: { + list: '/api/i4/v1/get/locations.php' // 获取位置列表 + }, + + // 部门相关接口 + department: { + list: '/api/i4/v1/get/departments.php' // 获取部门列表 + } +}; + +export default ApiPaths; diff --git a/src/utils/api/index.js b/src/utils/api/index.js index 9430696..f712338 100644 --- a/src/utils/api/index.js +++ b/src/utils/api/index.js @@ -2,17 +2,37 @@ import axios from "axios"; import crypto from "@/utils/crypto"; import Cookies from 'js-cookie'; import { ElMessage, ElLoading } from 'element-plus'; +import router, { clearLoginState } from '@/router'; // 修正路由导入路径 // 获取基础 API 地址 const API_BASE_URL = import.meta.env.VITE_APP_API_BASE_URL; -if (!API_BASE_URL) { +// 获取内网 API 地址 +const VITE_APP_API_INT_BASE_URL = import.meta.env.VITE_APP_API_INT_BASE_URL; + +// 获取外网 API 地址 +const VITE_APP_API_EXT_BASE_URL = import.meta.env.VITE_APP_API_EXT_BASE_URL; + + +if (!API_BASE_URL || !VITE_APP_API_INT_BASE_URL || !VITE_APP_API_EXT_BASE_URL) { console.warn('API基础地址未配置,请在环境变量中设置VITE_APP_API_BASE_URL'); } +// 设置基础 API 地址 +const hostname = `${window.location.hostname}`; +var auth_api_url = '' +if(hostname == '192.168.6.104') { + auth_api_url = API_BASE_URL +}else if (hostname == '8.134.12.216') { + auth_api_url = VITE_APP_API_EXT_BASE_URL +}else if (hostname == '10.97.245.96') { + auth_api_url = VITE_APP_API_INT_BASE_URL +} + + // 创建axios实例 const api = axios.create({ - baseURL: API_BASE_URL, + baseURL: auth_api_url, timeout: 15000, // 请求超时时间 headers: { 'Content-Type': 'application/json' @@ -59,16 +79,16 @@ api.interceptors.request.use( config.headers['Authorization'] = `Bearer ${accessToken}`; } - // 处理POST请求加密 + // 处理POST请求加密(不加密请求会标记isEncrypted: true) if (config.method?.toLowerCase() === 'post' && !config.isEncrypted) { try { // 添加用户信息 let requestData = { ...config.data }; - const storedUserInfo = localStorage.getItem('info'); + const storedUserInfo = localStorage.getItem('userInfo'); // 修正存储键名 if (storedUserInfo) { - const info = JSON.parse(crypto.decrypt(storedUserInfo)); - requestData['user_id'] = info.id; + const userInfo = JSON.parse(crypto.decrypt(storedUserInfo)); + requestData['user_id'] = userInfo.userId; // 匹配后端返回的userId字段 } // 加密数据 @@ -99,13 +119,12 @@ api.interceptors.response.use( // 处理业务逻辑错误 const { data } = response; - - // 根据实际后端规范调整 - if (data.code && data.code !== 200) { - ElMessage.error(data.message || '操作失败'); - return Promise.reject(new Error(data.message || '请求失败')); + // 根据实际后端规范调整(确保与后端code一致) + if (data.code == null || ![0, 200].includes(data.code)) { + ElMessage.error(data.message || '操作失败'); + return Promise.reject(new Error(data.message || '请求失败')); } - + return data; }, async (error) => { @@ -123,13 +142,10 @@ api.interceptors.response.use( switch (status) { case 401: // 未授权,处理token过期逻辑 - ElMessage.error('登录已过期,请重新登录'); - // 清除登录状态 - Cookies.remove('access_token'); - localStorage.removeItem('info'); - // 跳转到登录页(假设使用vue-router) - if (window.router) { - window.router.push('/'); + if (!router.currentRoute.value.path.includes('/auth')) { + ElMessage.error('登录已过期,请重新登录'); + clearLoginState(); // 使用路由文件中的清除方法 + router.push('/auth'); } break; case 403: @@ -216,7 +232,7 @@ const request = { }, /** - * 不加密的POST请求 + * 不加密的POST请求(用于登录等不需要加密的场景) * @param {string} url 请求地址 * @param {object} data 请求数据 * @param {object} config 额外配置 @@ -228,6 +244,7 @@ const request = { method: 'post', data, isEncrypted: true, // 标记为已加密,跳过加密处理 + showLoading: config.showLoading !== false, // 默认为显示加载状态 ...config }); } diff --git a/src/utils/crypto/index.js b/src/utils/crypto/index.js index 3afd7a1..aa98ccc 100644 --- a/src/utils/crypto/index.js +++ b/src/utils/crypto/index.js @@ -1,30 +1,103 @@ -import CryptoJS from 'crypto-js'; +import CryptoJS from 'crypto-js'; // 假设使用crypto-js库 -const key = CryptoJS.enc.Utf8.parse(import.meta.env.VITE_APP_KEY); -const iv = CryptoJS.enc.Utf8.parse(import.meta.env.VITE_APP_IV); +// 密钥必须与加密时完全一致(建议从环境变量读取,避免硬编码) +const SECRET_KEY = import.meta.env.VITE_APP_KEY || 'your-secret-key-32bytes'; // 32字节密钥(AES-256) +const IV = import.meta.env.VITE_APP_IV || '16bytesIvValue!'; // 16字节IV -const crypto = { - encrypt: (str) => { - const encrypted = CryptoJS.AES.encrypt(str, key, { +// 验证密钥和IV长度(关键修复) +if (SECRET_KEY.length !== 32) { + console.error(`加密密钥长度错误:需要32字节,实际${SECRET_KEY.length}字节`); +} +if (IV.length !== 16) { + console.error(`IV长度错误:需要16字节,实际${IV.length}字节`); +} + +/** + * 加密函数 + * @param {string} data - 要加密的字符串(需确保是有效的UTF-8) + * @returns {string} 加密后的Base64字符串 + */ +export const encrypt = (data) => { + try { + // 确保输入是字符串,且是有效的UTF-8 + if (typeof data !== 'string') { + throw new Error('加密数据必须是字符串'); + } + + // 验证UTF-8有效性 + new TextDecoder().decode(new TextEncoder().encode(data)); + + const key = CryptoJS.enc.Utf8.parse(SECRET_KEY); + const iv = CryptoJS.enc.Utf8.parse(IV); + + const encrypted = CryptoJS.AES.encrypt( + data, + key, + { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 - }); - // 将加密结果转换为字节数组,然后转换为十六进制字符串 - const hexString = CryptoJS.enc.Hex.stringify(encrypted.ciphertext); - return hexString; - }, - decrypt: (hexString) => { - // 将十六进制字符串转换回字节数组 - const ciphertext = CryptoJS.enc.Hex.parse(hexString); - const encryptedParams = CryptoJS.lib.CipherParams.create({ ciphertext }); - const decrypted = CryptoJS.AES.decrypt(encryptedParams, key, { - iv: iv, - mode: CryptoJS.mode.CBC, - padding: CryptoJS.pad.Pkcs7 - }); - return decrypted.toString(CryptoJS.enc.Utf8); + } + ); + + // 返回Base64格式的密文(避免特殊字符问题) + return encrypted.ciphertext.toString(CryptoJS.enc.Base64); + } catch (error) { + console.error('加密失败:', error); + throw new Error(`加密错误: ${error.message}`); } }; -export default crypto; +/** + * 解密函数 + * @param {string} encryptedData - 加密后的Base64字符串 + * @returns {string} 解密后的UTF-8字符串 + */ +export const decrypt = (encryptedData) => { + try { + // 验证输入是否为字符串 + if (typeof encryptedData !== 'string' || encryptedData.trim() === '') { + throw new Error('解密数据必须是非空字符串'); + } + + // 验证Base64有效性(关键修复) + if (!/^[A-Za-z0-9+/=]+$/.test(encryptedData)) { + throw new Error('解密数据不是有效的Base64格式'); + } + + const key = CryptoJS.enc.Utf8.parse(SECRET_KEY); + const iv = CryptoJS.enc.Utf8.parse(IV); + + // 先将Base64转换为CipherParams对象 + const cipherParams = CryptoJS.lib.CipherParams.create({ + ciphertext: CryptoJS.enc.Base64.parse(encryptedData) + }); + + const decrypted = CryptoJS.AES.decrypt( + cipherParams, + key, + { + iv: iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7 + } + ); + + // 转换为UTF-8字符串(处理可能的编码错误) + const result = decrypted.toString(CryptoJS.enc.Utf8); + + if (!result) { + throw new Error('解密结果为空,可能是密钥不匹配或数据损坏'); + } + + // 再次验证UTF-8有效性 + new TextDecoder().decode(new TextEncoder().encode(result)); + + return result; + } catch (error) { + console.error('解密失败:', error); + throw new Error(`解密错误: ${error.message}`); + } +}; + +export default { encrypt, decrypt }; diff --git a/src/views/dashboard/test.vue b/src/views/dashboard/test.vue new file mode 100644 index 0000000..d9eb155 --- /dev/null +++ b/src/views/dashboard/test.vue @@ -0,0 +1,6 @@ + + diff --git a/src/views/equipment/Testers.vue b/src/views/equipment/Testers.vue new file mode 100644 index 0000000..60a0292 --- /dev/null +++ b/src/views/equipment/Testers.vue @@ -0,0 +1,1000 @@ + + + + + + \ No newline at end of file diff --git a/src/views/mainpage/index.vue b/src/views/mainpage/index.vue index 8f60e71..5c1599a 100644 --- a/src/views/mainpage/index.vue +++ b/src/views/mainpage/index.vue @@ -1,17 +1,43 @@ \ No newline at end of file diff --git a/src/views/mainpage/layout/ContentHeader.vue b/src/views/mainpage/layout/ContentHeader.vue deleted file mode 100644 index 30cbbcf..0000000 --- a/src/views/mainpage/layout/ContentHeader.vue +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/views/mainpage/layout/MainMenu.vue b/src/views/mainpage/layout/MainMenu.vue index 6735080..1a31553 100644 --- a/src/views/mainpage/layout/MainMenu.vue +++ b/src/views/mainpage/layout/MainMenu.vue @@ -1,188 +1,85 @@ \ No newline at end of file diff --git a/src/views/mainpage/layout/MenuItem.vue b/src/views/mainpage/layout/MenuItem.vue deleted file mode 100644 index 752aad0..0000000 --- a/src/views/mainpage/layout/MenuItem.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/views/mainpage/layout/UserInfoBar.vue b/src/views/mainpage/layout/UserInfoBar.vue index 513f39a..6f64f31 100644 --- a/src/views/mainpage/layout/UserInfoBar.vue +++ b/src/views/mainpage/layout/UserInfoBar.vue @@ -1,58 +1,82 @@ \ No newline at end of file diff --git a/src/views/user/Profile.vue b/src/views/user/Profile.vue new file mode 100644 index 0000000..fbd0d0f --- /dev/null +++ b/src/views/user/Profile.vue @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/src/views/uut/History.vue b/src/views/uut/History.vue new file mode 100644 index 0000000..1b43459 --- /dev/null +++ b/src/views/uut/History.vue @@ -0,0 +1,6 @@ + + diff --git a/src/views/uut/Report.vue b/src/views/uut/Report.vue new file mode 100644 index 0000000..15f1719 --- /dev/null +++ b/src/views/uut/Report.vue @@ -0,0 +1,6 @@ + + diff --git a/vite.config.js b/vite.config.js index c596d35..5879c56 100644 --- a/vite.config.js +++ b/vite.config.js @@ -6,6 +6,7 @@ import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ + base: '/sbu4i4/', plugins: [ vue(), AutoImport({