= [
{
path: 'borrow',
name: 'OpBorrow',
- // 借库页面
component: () => import('@/views/transaction/borrow.vue'),
meta: { title: '借库' }
},
{
path: 'repair',
name: 'OpRepair',
- // 维修页面 (指向 return.vue)
component: () => import('@/views/transaction/return.vue'),
meta: { title: '维修' }
},
{
path: 'scrap',
name: 'OpScrap',
- // 报废页面
component: () => import('@/views/transaction/scrap.vue'),
meta: { title: '报废' }
}
]
},
- // 5. [修改] 系统管理 (权限控制 + 用户创建)
+ // 6. 系统管理
{
path: '/system',
component: Layout,
meta: {
title: '系统管理',
icon: 'Setting',
- // 只有超级管理员和主管能看到此菜单
roles: ['super_admin', 'supervisor']
},
children: [
{
path: 'user-create',
name: 'UserCreate',
- // 指向我们之前创建的新增用户页面
component: () => import('@/views/system/UserCreate.vue'),
meta: { title: '账号开通', icon: 'User' }
- },
- // 原有的日志页面保留 (如果文件存在)
- // {
- // path: 'log',
- // name: 'OpLog',
- // component: () => import('@/views/system/log.vue'),
- // meta: { title: '操作日志', icon: 'Document' }
- // }
+ }
]
},
- /* * 暂时屏蔽 BOM
- */
- // {
- // path: '/bom',
- // component: Layout,
- // children: [
- // {
- // path: 'index',
- // name: 'BOM',
- // component: () => import('@/views/bom/index.vue'),
- // meta: { title: 'BOM管理', icon: 'List' }
- // }
- // ]
- // },
-
// 404 路由
{
path: '/:pathMatch(.*)*',
@@ -170,37 +137,45 @@ const router = createRouter({
})
// ==========================================
-// [新增] 全局路由守卫:处理登录拦截与权限验证
+// [关键修改] 全局路由守卫
// ==========================================
router.beforeEach((to, from, next) => {
const userStore = useUserStore()
- const token = userStore.token
- const userRole = userStore.role
- // 1. 白名单:如果是去登录页,直接放行
+ // 1. 实时获取 Token (优先取 store,防止 store 未初始化取 localStorage)
+ const token = userStore.token || localStorage.getItem('token')
+ const userRole = userStore.role || localStorage.getItem('role') || 'user'
+
+ // 2. 如果要去的是登录页
if (to.path === '/login') {
- next()
+ // 如果有 Token,说明已登录,踢回首页 (防止重复登录)
+ if (token) {
+ next('/')
+ } else {
+ // 没有 Token,允许访问登录页
+ next()
+ }
return
}
- // 2. 无 Token:强制跳转登录页
+ // 3. 如果去的不是登录页,但没有 Token
if (!token) {
- next('/login')
+ // 强制重定向到登录页
+ // 使用 replace 防止用户点击浏览器“返回”按钮时进入死循环
+ next({ path: '/login', replace: true })
return
}
- // 3. 权限判断:检查 meta.roles
+ // 4. 权限判断 (已有 Token)
if (to.meta.roles && Array.isArray(to.meta.roles)) {
- // 如果当前用户角色在允许列表中,放行
if (to.meta.roles.includes(userRole)) {
next()
} else {
- // 权限不足,重定向到首页或 403 页面 (这里简单跳回 dashboard)
- // 可以在这里触发一个 Element Plus 的 Message 提示
+ // 权限不足,跳回首页
next('/dashboard')
}
} else {
- // 没有定义权限要求的页面,默认放行
+ // 无特殊权限要求,放行
next()
}
})
diff --git a/inventory-web/src/stores/user.ts b/inventory-web/src/stores/user.ts
index 03fe9c7..c088754 100644
--- a/inventory-web/src/stores/user.ts
+++ b/inventory-web/src/stores/user.ts
@@ -3,44 +3,85 @@ import { login } from '@/api/auth'
import { ref } from 'vue'
export const useUserStore = defineStore('user', () => {
+ // 1. State: 初始化时优先从 localStorage 获取,防止刷新丢失
const token = ref(localStorage.getItem('token') || '')
- const role = ref(localStorage.getItem('role') || '') // 持久化角色
+ const role = ref(localStorage.getItem('role') || '')
const username = ref(localStorage.getItem('username') || '')
+ // 2. Actions
+ // 登录逻辑
const handleLogin = async (loginForm: any) => {
try {
const res = await login(loginForm)
- // res.data 结构: { access_token, user: { role, username, ... } }
- const data = res.data
+ // [调试日志] 查看实际返回的数据结构 (调试完成后可删除)
+ console.log('Login API Response:', res)
+
+ // ============================================================
+ // [关键修复] 兼容 Axios 拦截器的不同处理方式
+ // 如果拦截器已经返回了 response.data,那么 res 本身就是数据对象
+ // 如果拦截器返回的是原始 response,那么数据在 res.data 中
+ // ============================================================
+ const data = res.data || res
+
+ // 安全检查:确保 data 存在且包含 access_token
+ if (!data || !data.access_token) {
+ console.error('Login Error: 响应数据中缺少 access_token', data)
+ return false
+ }
+
+ // 更新 Pinia 状态 (内存)
token.value = data.access_token
- role.value = data.user.role
- username.value = data.user.username
- // 持久化存储 (简单处理,生产环境建议加密或仅存Token)
+ // 处理用户信息 (确保后端返回结构中有 user 字段)
+ if (data.user) {
+ role.value = data.user.role || 'user' // 默认给个 user 角色防止空
+ username.value = data.user.username || '用户'
+
+ // 持久化存储用户信息
+ localStorage.setItem('role', role.value)
+ localStorage.setItem('username', username.value)
+ }
+
+ // 持久化存储 Token
localStorage.setItem('token', data.access_token)
- localStorage.setItem('role', data.user.role)
- localStorage.setItem('username', data.user.username)
return true
} catch (error) {
- console.error(error)
+ console.error('Login failed:', error)
return false
}
}
+ // 退出逻辑
const logout = () => {
+ // 1. 清空 Pinia 状态 (内存)
token.value = ''
role.value = ''
username.value = ''
- localStorage.clear()
- window.location.reload()
+
+ // 2. 清空 LocalStorage (硬盘)
+ // 建议使用 removeItem 而不是 clear,避免误删该域名下其他非登录数据
+ localStorage.removeItem('token')
+ localStorage.removeItem('role')
+ localStorage.removeItem('username')
+
+ // 注意:这里不再执行 window.location.reload()
+ // 而是把跳转控制权交给调用者 (如 App.vue 中的 router.push)
}
- // 辅助函数:判断当前用户是否拥有某些角色
+ // 3. Getters / Helpers
+ // 判断当前用户是否拥有某些角色
const hasRole = (roles: string[]) => {
return roles.includes(role.value)
}
- return { token, role, username, handleLogin, logout, hasRole }
+ return {
+ token,
+ role,
+ username,
+ handleLogin,
+ logout,
+ hasRole
+ }
})
\ No newline at end of file
diff --git a/inventory-web/src/style.css b/inventory-web/src/style.css
index f691315..15a411f 100644
--- a/inventory-web/src/style.css
+++ b/inventory-web/src/style.css
@@ -1,18 +1,31 @@
+/* inventory-web/src/style.css */
+
+/* 1. 保留原有的字体定义,确保文字清晰好看 */
:root {
- font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
+ /* 颜色方案配置 */
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
+ /* 字体渲染优化 */
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
+/* 2. 针对亮色模式的颜色适配 (保留) */
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+}
+
+/* 3. 链接的基本样式 (保留,但通常 RouterLink 会覆盖) */
a {
font-weight: 500;
color: #646cff;
@@ -22,58 +35,44 @@ a:hover {
color: #535bf2;
}
-body {
+/* -------------------------------------------------
+ 【重要修改区域】
+ 下面的代码是为了修复“无法铺满全屏”的问题
+ -------------------------------------------------
+*/
+
+/* 4. 全局盒模型修复:防止 padding 撑大元素 */
+*, *::before, *::after {
+ box-sizing: border-box;
+}
+
+/* 5. 重置 body 和 html */
+html, body {
margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-.card {
- padding: 2em;
+ padding: 0;
+ width: 100%;
+ height: 100%; /* 强制高度占满 */
+
+ /* !!! 删除了原有的 display: flex; place-items: center;
+ 这是导致你页面缩在中间的罪魁祸首
+ */
+ display: block;
+
+ overflow: hidden; /* 防止最外层出现双滚动条 */
}
+/* 6. 重置 #app 挂载点 */
#app {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
+ /* !!! 删除了 max-width: 1280px; padding: 2rem; text-align: center;
+ 这是导致你页面两边留白、无法全屏的原因
+ */
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
}
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
+/* 注意:原文件中关于 button, .card 的样式已被删除,
+ 因为你的项目中引入了 Element Plus,
+ 保留原生 button 样式会和 Element Plus 组件产生冲突。
+*/
\ No newline at end of file
diff --git a/inventory-web/src/views/material/list.vue b/inventory-web/src/views/material/list.vue
index 22242c0..b206b90 100644
--- a/inventory-web/src/views/material/list.vue
+++ b/inventory-web/src/views/material/list.vue
@@ -5,7 +5,7 @@
+
@@ -103,7 +104,15 @@
style="width: 100%; margin-top: 15px"
>
-
+
+
+
+
+ {{ scope.row.commonName }}
+ -
+
+
+
{{ scope.row.category || '-' }}
@@ -162,9 +171,18 @@
>
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -254,6 +272,7 @@ import {
interface MaterialBaseVO {
id: number;
name: string;
+ commonName?: string; // ✅ 新增类型定义
category: string;
type: string;
spec: string;
@@ -284,6 +303,7 @@ const tableSize = ref<'large' | 'default' | 'small'>('large');
const columns = reactive({
id: { visible: true },
name: { visible: true },
+ commonName: { visible: true }, // ✅ 新增列控制
category: { visible: true },
type: { visible: true },
spec: { visible: true },
@@ -316,6 +336,7 @@ const formRef = ref();
const initForm = {
id: undefined,
name: '',
+ commonName: '', // ✅ 初始化新增字段
category: '',
type: '',
spec: '',
@@ -350,13 +371,10 @@ const extractDynamicOptions = (items: MaterialBaseVO[]) => {
typeOptions.value = Array.from(newTypes);
};
-// 【核心新增】Autocomplete 的建议查询方法
-// 格式化数据以适配 el-autocomplete 的回调参数格式 [{ value: 'abc' }]
const querySearchCategory = (queryString: string, cb: any) => {
const results = queryString
? categoryOptions.value.filter(item => item.toLowerCase().includes(queryString.toLowerCase()))
: categoryOptions.value;
- // el-autocomplete 默认只展示 value 属性
const formattedResults = results.map(item => ({ value: item }));
cb(formattedResults);
};