diff --git a/src/api/contract.js b/src/api/contract.js
index 16f7c0f..dd8fdc4 100644
--- a/src/api/contract.js
+++ b/src/api/contract.js
@@ -1,41 +1,26 @@
-import axios from 'axios'
-import { mockContractAPI } from './mock/contract'
-
-// 开关:true 用 mock,false 用真实接口
-const USE_MOCK = true
-
-// axios 实例
-const request = axios.create({
- baseURL: '/api',
- timeout: 5000
-})
+import request from './request'
// 获取合同列表
export const getContractList = (params) => {
- if (USE_MOCK) return mockContractAPI.getList(params)
- return request.get('/contracts', { params }).then(r => r.data)
+ return request.get('/contracts', { params })
}
// 获取合同详情
export const getContractDetail = (id) => {
- if (USE_MOCK) return mockContractAPI.getDetail(id)
- return request.get(`/contracts/${id}`).then(r => r.data)
+ return request.get(`/contracts/${id}`)
}
// 新增合同
export const createContract = (data) => {
- if (USE_MOCK) return mockContractAPI.create(data)
- return request.post('/contracts', data).then(r => r.data)
+ return request.post('/contracts', data)
}
// 更新合同
export const updateContract = (id, data) => {
- if (USE_MOCK) return mockContractAPI.update(id, data)
- return request.put(`/contracts/${id}`, data).then(r => r.data)
+ return request.put(`/contracts/${id}`, data)
}
// 删除合同
export const deleteContractAPI = (id) => {
- if (USE_MOCK) return mockContractAPI.delete(id)
- return request.delete(`/contracts/${id}`).then(r => r.data)
+ return request.delete(`/contracts/${id}`)
}
diff --git a/src/api/customer.js b/src/api/customer.js
index 26747e2..179811f 100644
--- a/src/api/customer.js
+++ b/src/api/customer.js
@@ -1,41 +1,26 @@
-import axios from 'axios'
-import { mockCustomerAPI } from './mock/customer'
-
-// 开关:true 用 mock,false 用真实接口
-const USE_MOCK = true
-
-// axios 实例
-const request = axios.create({
- baseURL: '/api',
- timeout: 5000
-})
+import request from './request'
// 获取客户列表
-export const getCustomerList = (params) => {
- if (USE_MOCK) return mockCustomerAPI.getList(params)
- return request.get('/customers', { params }).then(r => r.data)
+export const getCustomerList = (params, config) => {
+ return request.get('/customers', { params, ...config })
}
// 获取客户详情
export const getCustomerDetail = (id) => {
- if (USE_MOCK) return mockCustomerAPI.getDetail(id)
- return request.get(`/customers/${id}`).then(r => r.data)
+ return request.get(`/customers/${id}`)
}
// 新增客户
export const createCustomer = (data) => {
- if (USE_MOCK) return mockCustomerAPI.create(data)
- return request.post('/customers', data).then(r => r.data)
+ return request.post('/customers', data)
}
// 更新客户
export const updateCustomer = (id, data) => {
- if (USE_MOCK) return mockCustomerAPI.update(id, data)
- return request.put(`/customers/${id}`, data).then(r => r.data)
+ return request.put(`/customers/${id}`, data)
}
// 删除客户
export const deleteCustomerAPI = (id) => {
- if (USE_MOCK) return mockCustomerAPI.delete(id)
- return request.delete(`/customers/${id}`).then(r => r.data)
+ return request.delete(`/customers/${id}`)
}
diff --git a/src/api/employee.js b/src/api/employee.js
index 69646b4..4a2d414 100644
--- a/src/api/employee.js
+++ b/src/api/employee.js
@@ -1,41 +1,31 @@
-import axios from 'axios'
-import { mockEmployeeAPI } from './mock/employee'
-
-// 开关:true 用 mock,false 用真实接口
-const USE_MOCK = true
-
-// axios 实例
-const request = axios.create({
- baseURL: '/api',
- timeout: 5000
-})
+import request from './request'
// 获取员工列表
-export const getEmployeeList = (params) => {
- if (USE_MOCK) return mockEmployeeAPI.getList(params)
- return request.get('/employees', { params }).then(r => r.data)
+export const getEmployeeList = (params, config) => {
+ return request.get('/employees', { params, ...config })
+}
+
+// 获取简易员工列表(无数据范围限制,支持按部门过滤)
+export const getSimpleEmployeeList = (params, config) => {
+ return request.get('/employees/simple', { params, ...config })
}
// 获取员工详情
export const getEmployeeDetail = (id) => {
- if (USE_MOCK) return mockEmployeeAPI.getDetail(id)
- return request.get(`/employees/${id}`).then(r => r.data)
+ return request.get(`/employees/${id}`)
}
// 新增员工
export const createEmployee = (data) => {
- if (USE_MOCK) return mockEmployeeAPI.create(data)
- return request.post('/employees', data).then(r => r.data)
+ return request.post('/employees', data)
}
// 更新员工
export const updateEmployee = (id, data) => {
- if (USE_MOCK) return mockEmployeeAPI.update(id, data)
- return request.put(`/employees/${id}`, data).then(r => r.data)
+ return request.put(`/employees/${id}`, data)
}
// 删除员工
export const deleteEmployeeAPI = (id) => {
- if (USE_MOCK) return mockEmployeeAPI.delete(id)
- return request.delete(`/employees/${id}`).then(r => r.data)
+ return request.delete(`/employees/${id}`)
}
diff --git a/src/api/mock/contract.js b/src/api/mock/contract.js
index 0099c2a..3de1c94 100644
--- a/src/api/mock/contract.js
+++ b/src/api/mock/contract.js
@@ -2,7 +2,6 @@
const mockContracts = [
{
id: 1,
- contract_no: 'HT-2025-001',
contract_name: '蜜雪冰城加盟合同',
type: '加盟合同',
party_a: '蜜雪冰城股份有限公司',
@@ -17,7 +16,6 @@ const mockContracts = [
},
{
id: 2,
- contract_no: 'HT-2025-002',
contract_name: '原材料供货合同',
type: '供货合同',
party_a: '蜜雪冰城股份有限公司',
@@ -32,7 +30,6 @@ const mockContracts = [
},
{
id: 3,
- contract_no: 'HT-2024-010',
contract_name: '门店租赁合同',
type: '租赁合同',
party_a: '蜜雪冰城股份有限公司',
@@ -47,7 +44,6 @@ const mockContracts = [
},
{
id: 4,
- contract_no: 'HT-2025-003',
contract_name: '设备维护服务合同',
type: '服务合同',
party_a: '蜜雪冰城股份有限公司',
@@ -76,7 +72,7 @@ export const mockContractAPI = {
if (params.keyword) {
const keyword = params.keyword.toLowerCase()
result = result.filter(item =>
- item.contract_no.toLowerCase().includes(keyword) ||
+ String(item.id).includes(keyword) ||
item.contract_name.toLowerCase().includes(keyword) ||
item.party_b.toLowerCase().includes(keyword)
)
diff --git a/src/api/mock/customer.js b/src/api/mock/customer.js
index f6cb27f..ee2b97f 100644
--- a/src/api/mock/customer.js
+++ b/src/api/mock/customer.js
@@ -9,7 +9,6 @@ const mockCustomers = [
district: '南山区',
address: '科技园路1号',
email: 'zhangsan@example.com',
- customer_type: 'VIP',
create_time: '2025-01-15 10:30:00'
},
{
@@ -21,7 +20,6 @@ const mockCustomers = [
district: '西湖区',
address: '文三路100号',
email: 'lisi@example.com',
- customer_type: 'Normal',
create_time: '2025-02-20 14:20:00'
},
{
@@ -33,7 +31,6 @@ const mockCustomers = [
district: '武侯区',
address: '天府大道200号',
email: 'wangwu@example.com',
- customer_type: 'VIP',
create_time: '2025-03-10 09:15:00'
},
{
@@ -45,7 +42,6 @@ const mockCustomers = [
district: '岳麓区',
address: '麓山南路100号',
email: 'zhaoliu@example.com',
- customer_type: 'Normal',
create_time: '2025-04-05 16:45:00'
}
]
@@ -88,11 +84,6 @@ export const mockCustomerAPI = {
}
}
- // 客户类型筛选
- if (params.customer_type) {
- result = result.filter(item => item.customer_type === params.customer_type)
- }
-
// 地区筛选(省/市/区)
if (params.province) {
result = result.filter(item => item.province === params.province)
diff --git a/src/api/mock/employee.js b/src/api/mock/employee.js
index c6440c1..fcdc919 100644
--- a/src/api/mock/employee.js
+++ b/src/api/mock/employee.js
@@ -1,5 +1,5 @@
-// 模拟员工数据
-const mockEmployees = [
+// 模拟员工数据(单一数据源,同时包含用户字段)
+export const mockEmployees = [
{
id: 1,
name: '张伟',
@@ -14,6 +14,11 @@ const mockEmployees = [
email: 'zhangwei@mxbc.com',
status: 1,
remark: '负责门店日常运营',
+ // 用户字段
+ username: 'zhangwei',
+ password: '123456',
+ user_type: 'admin',
+ user_status: '1',
created_at: '2023-03-15 09:00:00',
updated_at: '2023-03-15 09:00:00'
},
@@ -31,6 +36,11 @@ const mockEmployees = [
email: 'lina@mxbc.com',
status: 1,
remark: '',
+ // 用户字段
+ username: 'lina',
+ password: '123456',
+ user_type: 'user',
+ user_status: '1',
created_at: '2023-06-01 09:00:00',
updated_at: '2023-06-01 09:00:00'
},
@@ -48,6 +58,11 @@ const mockEmployees = [
email: 'wangqiang@mxbc.com',
status: 1,
remark: '负责系统架构设计',
+ // 用户字段(无用户账号)
+ username: null,
+ password: null,
+ user_type: null,
+ user_status: null,
created_at: '2022-11-10 09:00:00',
updated_at: '2022-11-10 09:00:00'
},
@@ -65,6 +80,11 @@ const mockEmployees = [
email: 'liuyang@mxbc.com',
status: 0,
remark: '已离职',
+ // 用户字段(无用户账号)
+ username: null,
+ password: null,
+ user_type: null,
+ user_status: null,
created_at: '2024-01-20 09:00:00',
updated_at: '2025-03-01 17:00:00'
},
@@ -82,6 +102,11 @@ const mockEmployees = [
email: 'zhaomin@mxbc.com',
status: 1,
remark: '',
+ // 用户字段
+ username: 'zhaomin',
+ password: '123456',
+ user_type: 'user',
+ user_status: '0',
created_at: '2023-09-05 09:00:00',
updated_at: '2023-09-05 09:00:00'
},
@@ -99,6 +124,11 @@ const mockEmployees = [
email: 'chengang@mxbc.com',
status: 1,
remark: '负责原料入库出库',
+ // 用户字段(无用户账号)
+ username: null,
+ password: null,
+ user_type: null,
+ user_status: null,
created_at: '2022-04-12 09:00:00',
updated_at: '2022-04-12 09:00:00'
}
@@ -169,6 +199,11 @@ export const mockEmployeeAPI = {
id: maxId + 1,
status: 1,
...data,
+ // 用户字段初始为空
+ username: null,
+ password: null,
+ user_type: null,
+ user_status: null,
created_at: now,
updated_at: now
}
diff --git a/src/api/mock/user.js b/src/api/mock/user.js
index 21dfe29..46c401e 100644
--- a/src/api/mock/user.js
+++ b/src/api/mock/user.js
@@ -1,61 +1,26 @@
-// 模拟用户数据
-const mockUsers = [
- {
- id: 1,
- username: 'admin',
- password: '123456',
- name: '系统管理员',
- user_type: 'admin',
- status: '1',
- createdate: '2025-01-10 08:00:00'
- },
- {
- id: 2,
- username: 'zhangsan',
- password: '123456',
- name: '张三',
- user_type: 'user',
- status: '1',
- createdate: '2025-02-15 10:30:00'
- },
- {
- id: 3,
- username: 'lisi',
- password: '123456',
- name: '李四',
- user_type: 'user',
- status: '0',
- createdate: '2025-03-20 14:00:00'
- },
- {
- id: 4,
- username: 'wangwu',
- password: '123456',
- name: '王五',
- user_type: 'admin',
- status: '1',
- createdate: '2025-04-05 09:15:00'
- },
- {
- id: 5,
- username: 'zhaoliu',
- password: '123456',
- name: '赵六',
- user_type: 'user',
- status: '0',
- createdate: '2025-05-10 16:45:00'
- }
-]
+import { mockEmployees } from './employee'
// 模拟网络延迟
const delay = (ms = 300) => new Promise(r => setTimeout(r, ms))
-// Mock API
+// 将员工记录转换为用户格式(字段名映射)
+const toUserFormat = (employee) => ({
+ id: employee.id,
+ username: employee.username,
+ name: employee.name,
+ user_type: employee.user_type,
+ status: employee.user_status,
+ employee_id: employee.id,
+ createdate: employee.created_at
+})
+
+// Mock API(操作同一数据源 mockEmployees)
export const mockUserAPI = {
- // 获取列表
+ // 获取用户列表(返回有用户账号的员工记录)
async getList(params = {}) {
await delay()
- let result = [...mockUsers]
+ // 筛选有用户名的记录(即已创建用户的员工)
+ let result = mockEmployees.filter(item => item.username).map(toUserFormat)
// 关键词搜索
if (params.keyword) {
@@ -67,16 +32,15 @@ export const mockUserAPI = {
result = result.filter(item => String(item.id) === keyword)
} else if (searchField === '1') {
// 用户名:模糊匹配
- result = result.filter(item => item.username.includes(keyword))
+ result = result.filter(item => item.username && item.username.includes(keyword))
} else if (searchField === '3') {
// 真实姓名:模糊匹配
- result = result.filter(item => item.name.includes(keyword))
+ result = result.filter(item => item.name.toLowerCase().includes(keyword))
} else {
- // 默认:模糊匹配所有字段
result = result.filter(item =>
String(item.id).includes(keyword) ||
- item.username.includes(keyword) ||
- item.name.includes(keyword)
+ (item.username && item.username.includes(keyword)) ||
+ item.name.toLowerCase().includes(keyword)
)
}
}
@@ -94,47 +58,80 @@ export const mockUserAPI = {
return { code: 200, data: result, total: result.length }
},
- // 获取详情
+ // 获取用户详情
async getDetail(id) {
await delay()
- const user = mockUsers.find(item => item.id === id)
- if (user) {
- return { code: 200, data: user }
+ const employee = mockEmployees.find(item => item.id === id && item.username)
+ if (employee) {
+ return { code: 200, data: toUserFormat(employee) }
}
return { code: 404, message: '用户不存在' }
},
- // 新增
+ // 创建用户(实际上是更新已有员工记录,添加用户字段)
async create(data) {
await delay()
- const maxId = mockUsers.length > 0 ? Math.max(...mockUsers.map(item => item.id)) : 0
- const newUser = {
- id: maxId + 1,
- ...data,
- createdate: new Date().toISOString().replace('T', ' ').slice(0, 19)
+ // 查找对应的员工记录
+ const employeeIndex = mockEmployees.findIndex(item => item.id === data.id || item.id === data.employee_id)
+ if (employeeIndex === -1) {
+ return { code: 404, message: '关联员工不存在' }
}
- mockUsers.push(newUser)
- return { code: 200, data: newUser, message: '新增成功' }
+
+ // 检查是否已有用户账号
+ if (mockEmployees[employeeIndex].username) {
+ return { code: 400, message: '该员工已有用户账号' }
+ }
+
+ // 更新员工记录,添加用户字段
+ mockEmployees[employeeIndex] = {
+ ...mockEmployees[employeeIndex],
+ username: data.username,
+ password: data.password,
+ user_type: data.user_type || 'user',
+ user_status: data.status || '1',
+ updated_at: new Date().toISOString().replace('T', ' ').slice(0, 19)
+ }
+
+ return { code: 200, data: toUserFormat(mockEmployees[employeeIndex]), message: '用户创建成功' }
},
- // 更新
+ // 更新用户(更新用户字段)
async update(id, data) {
await delay()
- const index = mockUsers.findIndex(item => item.id === id)
+ const index = mockEmployees.findIndex(item => item.id === id && item.username)
if (index !== -1) {
- mockUsers[index] = { ...mockUsers[index], ...data }
- return { code: 200, data: mockUsers[index], message: '更新成功' }
+ // 只更新用户相关字段
+ const updateFields = {}
+ if (data.username !== undefined) updateFields.username = data.username
+ if (data.password !== undefined) updateFields.password = data.password
+ if (data.user_type !== undefined) updateFields.user_type = data.user_type
+ if (data.status !== undefined) updateFields.user_status = data.status
+
+ mockEmployees[index] = {
+ ...mockEmployees[index],
+ ...updateFields,
+ updated_at: new Date().toISOString().replace('T', ' ').slice(0, 19)
+ }
+ return { code: 200, data: toUserFormat(mockEmployees[index]), message: '更新成功' }
}
return { code: 404, message: '用户不存在' }
},
- // 删除
+ // 删除用户(清除用户字段,保留员工记录)
async delete(id) {
await delay()
- const index = mockUsers.findIndex(item => item.id === id)
+ const index = mockEmployees.findIndex(item => item.id === id && item.username)
if (index !== -1) {
- mockUsers.splice(index, 1)
- return { code: 200, message: '删除成功' }
+ // 清除用户字段,而不是删除记录
+ mockEmployees[index] = {
+ ...mockEmployees[index],
+ username: null,
+ password: null,
+ user_type: null,
+ user_status: null,
+ updated_at: new Date().toISOString().replace('T', ' ').slice(0, 19)
+ }
+ return { code: 200, message: '用户已删除' }
}
return { code: 404, message: '用户不存在' }
}
diff --git a/src/api/products.js b/src/api/products.js
index 2f94dba..c5764ec 100644
--- a/src/api/products.js
+++ b/src/api/products.js
@@ -1,41 +1,26 @@
-import axios from 'axios'
-import { mockProductsAPI } from './mock/products'
-
-// 开关:true 用 mock,false 用真实接口
-const USE_MOCK = true
-
-// axios 实例
-const request = axios.create({
- baseURL: '/api',
- timeout: 5000
-})
+import request from './request'
// 获取产品列表
export const getProductsList = (params) => {
- if (USE_MOCK) return mockProductsAPI.getList(params)
- return request.get('/products', { params }).then(r => r.data)
+ return request.get('/products', { params })
}
// 获取产品详情
export const getProductsDetail = (id) => {
- if (USE_MOCK) return mockProductsAPI.getDetail(id)
- return request.get(`/products/${id}`).then(r => r.data)
+ return request.get(`/products/${id}`)
}
// 新增产品
export const createProducts = (data) => {
- if (USE_MOCK) return mockProductsAPI.create(data)
- return request.post('/products', data).then(r => r.data)
+ return request.post('/products', data)
}
// 更新产品
export const updateProducts = (id, data) => {
- if (USE_MOCK) return mockProductsAPI.update(id, data)
- return request.put(`/products/${id}`, data).then(r => r.data)
+ return request.put(`/products/${id}`, data)
}
// 删除产品
export const deleteProductsAPI = (id) => {
- if (USE_MOCK) return mockProductsAPI.delete(id)
- return request.delete(`/products/${id}`).then(r => r.data)
+ return request.delete(`/products/${id}`)
}
diff --git a/src/api/request.js b/src/api/request.js
new file mode 100644
index 0000000..f4791c4
--- /dev/null
+++ b/src/api/request.js
@@ -0,0 +1,53 @@
+import axios from 'axios'
+import { ElMessage } from 'element-plus'
+import router from '../router'
+
+// 创建 axios 实例
+const request = axios.create({
+ baseURL: '/api',
+ timeout: 10000
+})
+
+// 请求拦截器:自动添加 token
+request.interceptors.request.use(
+ (config) => {
+ const token = localStorage.getItem('token')
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`
+ }
+ return config
+ },
+ (error) => {
+ return Promise.reject(error)
+ }
+)
+
+// 响应拦截器:统一处理错误(config.quiet = true 时不弹窗)
+request.interceptors.response.use(
+ (response) => {
+ return response.data
+ },
+ (error) => {
+ const status = error.response?.status
+ const message = error.response?.data?.message || '请求失败'
+ const quiet = error.config?.quiet
+ const isLogin = error.config?.url?.includes('/user/login')
+
+ if (!quiet) {
+ if (status === 401) {
+ localStorage.removeItem('token')
+ localStorage.removeItem('userInfo')
+ router.push('/login')
+ ElMessage.error('登录已过期,请重新登录')
+ } else if (status === 403 && !isLogin) {
+ ElMessage.error('权限不足')
+ } else {
+ ElMessage.error(message)
+ }
+ }
+
+ return Promise.reject(error)
+ }
+)
+
+export default request
diff --git a/src/api/service.js b/src/api/service.js
index 5ef0292..c13b0d0 100644
--- a/src/api/service.js
+++ b/src/api/service.js
@@ -1,41 +1,26 @@
-import axios from 'axios'
-import { mockServiceAPI } from './mock/service'
-
-// 开关:true 用 mock,false 用真实接口
-const USE_MOCK = true
-
-// axios 实例
-const request = axios.create({
- baseURL: '/api',
- timeout: 5000
-})
+import request from './request'
// 获取售后列表
export const getServiceList = (params) => {
- if (USE_MOCK) return mockServiceAPI.getList(params)
- return request.get('/after-sales', { params }).then(r => r.data)
+ return request.get('/after-sales', { params })
}
// 获取售后详情
export const getServiceDetail = (id) => {
- if (USE_MOCK) return mockServiceAPI.getDetail(id)
- return request.get(`/after-sales/${id}`).then(r => r.data)
+ return request.get(`/after-sales/${id}`)
}
// 新增售后
export const createService = (data) => {
- if (USE_MOCK) return mockServiceAPI.create(data)
- return request.post('/after-sales', data).then(r => r.data)
+ return request.post('/after-sales', data)
}
// 更新售后
export const updateService = (id, data) => {
- if (USE_MOCK) return mockServiceAPI.update(id, data)
- return request.put(`/after-sales/${id}`, data).then(r => r.data)
+ return request.put(`/after-sales/${id}`, data)
}
// 删除售后
export const deleteServiceAPI = (id) => {
- if (USE_MOCK) return mockServiceAPI.delete(id)
- return request.delete(`/after-sales/${id}`).then(r => r.data)
+ return request.delete(`/after-sales/${id}`)
}
diff --git a/src/api/suppliers.js b/src/api/suppliers.js
new file mode 100644
index 0000000..86c92fc
--- /dev/null
+++ b/src/api/suppliers.js
@@ -0,0 +1,26 @@
+import request from './request'
+
+// 获取供应商列表
+export const getSupplierList = (params, config) => {
+ return request.get('/suppliers', { params, ...config })
+}
+
+// 获取供应商详情
+export const getSupplierDetail = (id) => {
+ return request.get(`/suppliers/${id}`)
+}
+
+// 新增供应商
+export const createSupplier = (data) => {
+ return request.post('/suppliers', data)
+}
+
+// 更新供应商
+export const updateSupplier = (id, data) => {
+ return request.put(`/suppliers/${id}`, data)
+}
+
+// 删除供应商
+export const deleteSupplierAPI = (id) => {
+ return request.delete(`/suppliers/${id}`)
+}
diff --git a/src/api/user.js b/src/api/user.js
index 76eecfa..975cb75 100644
--- a/src/api/user.js
+++ b/src/api/user.js
@@ -1,41 +1,31 @@
-import axios from 'axios'
-import { mockUserAPI } from './mock/user'
+import request from './request'
-// 开关:true 用 mock,false 用真实接口
-const USE_MOCK = true
+// 获取用户列表(管理员)
+export const getUserList = (params, config) => {
+ return request.get('/users', { params, ...config })
+}
-// axios 实例
-const request = axios.create({
- baseURL: '/api',
- timeout: 5000
-})
-
-// 获取用户列表
-export const getUserList = (params) => {
- if (USE_MOCK) return mockUserAPI.getList(params)
- return request.get('/users', { params }).then(r => r.data)
+// 获取简易用户列表(用于下拉选择,仅需登录)
+export const getSimpleUserList = (config) => {
+ return request.get('/user/list', config)
}
// 获取用户详情
export const getUserDetail = (id) => {
- if (USE_MOCK) return mockUserAPI.getDetail(id)
- return request.get(`/users/${id}`).then(r => r.data)
+ return request.get(`/users/${id}`)
}
-// 新增用户
+// 创建用户
export const createUser = (data) => {
- if (USE_MOCK) return mockUserAPI.create(data)
- return request.post('/users', data).then(r => r.data)
+ return request.post('/users', data)
}
// 更新用户
export const updateUser = (id, data) => {
- if (USE_MOCK) return mockUserAPI.update(id, data)
- return request.put(`/users/${id}`, data).then(r => r.data)
+ return request.put(`/users/${id}`, data)
}
// 删除用户
export const deleteUserAPI = (id) => {
- if (USE_MOCK) return mockUserAPI.delete(id)
- return request.delete(`/users/${id}`).then(r => r.data)
+ return request.delete(`/users/${id}`)
}
diff --git a/src/components/Login.vue b/src/components/Login.vue
index 782cecb..4584503 100644
--- a/src/components/Login.vue
+++ b/src/components/Login.vue
@@ -67,6 +67,7 @@ import {reactive, ref} from 'vue'
import {ElMessage} from 'element-plus'
import {Lock, User} from '@element-plus/icons-vue'
import {useRouter} from 'vue-router'
+import { login } from '../store/user'
const router = useRouter()
@@ -93,9 +94,13 @@ const handleLogin = async () => {
try {
await loginFormRef.value.validate()
loading.value = true
- await new Promise((r) => setTimeout(r, 800))
- ElMessage.success('登录成功(模拟)')
- router.push('/panel')
+ const res = await login(loginForm.username, loginForm.password)
+ if (res.code === 0 || res.code === 200) {
+ ElMessage.success('登录成功')
+ router.push('/panel/home')
+ } else {
+ ElMessage.error(res.message || '登录失败')
+ }
} catch {
// 校验失败,element-plus 会自动显示红色提示
} finally {
diff --git a/src/components/contract.vue b/src/components/contract.vue
index 1a986c1..3745011 100644
--- a/src/components/contract.vue
+++ b/src/components/contract.vue
@@ -1,5 +1,5 @@
@@ -242,21 +349,21 @@ const formatDate = (date) => {
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
- 新增合同
+ 新增合同
重置
@@ -265,15 +372,8 @@ const formatDate = (date) => {
筛选条件:
-
+
@@ -292,12 +392,30 @@ const formatDate = (date) => {
共找到 {{ searchResults.length }} 条合同
-
-
-
+
+
-
-
+
+
+ {{ row.type === 'franchise' ? '加盟' : '采购' }}
+
+
+
+
+ {{ row.customer_name || '-' }}
+
+
+
+
+ {{ row.supplier_name || '-' }}
+
+
+
+
+ {{ row.employee_name || '-' }}
+
+
¥{{ row.amount?.toLocaleString() }}
@@ -308,15 +426,36 @@ const formatDate = (date) => {
{{ row.status }}
-
-
+
+
+ {{ formatDate(row.effective_date) }}
+
+
+
+
+ {{ formatDate(row.expiry_date) }}
+
+
- 编辑
- 删除
+ 编辑
+ 删除
+
+
+
@@ -326,24 +465,34 @@ const formatDate = (date) => {
-
-
+
+
-
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
@@ -357,22 +506,35 @@ const formatDate = (date) => {
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -404,29 +566,34 @@ const formatDate = (date) => {
- {{ currentDetail.contract_no }}
- {{ currentDetail.contract_name }}
- {{ currentDetail.customer_id }}
- {{ currentDetail.employee_id }}
+ {{ currentDetail.id }}
+ {{ currentDetail.contract_name }}
+ {{ currentDetail.type === 'franchise' ? '加盟合同' : '采购合同' }}
+ {{ currentDetail.customer_name || currentDetail.customer_id }}
+ {{ currentDetail.supplier_name || currentDetail.supplier_id }}
+ {{ currentDetail.employee_name || currentDetail.employee_id }}
¥{{ currentDetail.amount?.toLocaleString() }}
{{ currentDetail.status }}
- {{ currentDetail.effective_date }}
- {{ currentDetail.expiry_date }}
+ {{ formatDate(currentDetail.effective_date) }}
+ {{ formatDate(currentDetail.expiry_date) }}
{{ currentDetail.contract_content || '无' }}
{{ currentDetail.remark || '无' }}
+
+ {{ currentDetail.responsible_user_name || currentDetail.responsible_user_id || '无' }}
+
关闭
- 编辑
+ { const d = currentDetail; closeDetail(); editContract(d) })()">编辑
@@ -481,7 +648,8 @@ const formatDate = (date) => {
font-size: 14px;
}
-.loading-container, .empty-container {
+.loading-container,
+.empty-container {
padding: 40px 0;
}
@@ -505,4 +673,12 @@ const formatDate = (date) => {
max-height: 120px;
overflow-y: auto;
}
+
+.pagination-container {
+ display: flex;
+ justify-content: center;
+ margin-top: 15px;
+ padding-top: 15px;
+ border-top: 1px solid #eee;
+}
\ No newline at end of file
diff --git a/src/components/customer.vue b/src/components/customer.vue
index 78841f1..e8cca28 100644
--- a/src/components/customer.vue
+++ b/src/components/customer.vue
@@ -10,6 +10,10 @@ import {
updateCustomer,
deleteCustomerAPI
} from '../api/customer'
+import { getSimpleUserList } from '../api/user'
+import { hasPermission, getUserInfo } from '../store/user'
+
+const isAdmin = computed(() => getUserInfo()?.roleName === 'admin')
const form = reactive({
id: '',
@@ -18,8 +22,8 @@ const form = reactive({
region: [],
address: '',
email: '',
- customer_type: 'Normal',
- remark: ''
+ remark: '',
+ responsible_user_id: ''
})
const search = ref('')
@@ -34,6 +38,11 @@ let searchSeq = 0
const showDetailDialog = ref(false)
const currentDetail = ref(null)
const filterRegion = ref([])
+const userList = ref([])
+
+// 分页相关
+const currentPage = ref(1)
+const pageSize = ref(10)
// 只保留省市两级的地区数据
const regionDataTwoLevel = computed(() => {
@@ -46,38 +55,60 @@ const regionDataTwoLevel = computed(() => {
}))
})
-onMounted(() => {
- fetchData()
+// 分页后的数据
+const paginatedData = computed(() => {
+ const start = (currentPage.value - 1) * pageSize.value
+ const end = start + pageSize.value
+ return searchResults.value.slice(start, end)
})
-// 切换代理商类型时清空已选地区
-watch(() => form.customer_type, () => {
- form.region = []
+onMounted(() => {
+ fetchData()
+ // 仅管理员可加载用户列表用于选择负责人
+ if (isAdmin.value) {
+ getSimpleUserList({ quiet: true }).then(res => {
+ userList.value = res.data?.list || res.data || []
+ }).catch(() => {})
+ }
+})
+
+// 监听搜索结果变化,重置页码
+watch(searchResults, () => {
+ currentPage.value = 1
})
// 获取客户列表
const fetchData = async () => {
loading.value = true
const res = await getCustomerList()
- searchResults.value = res.data
+ if (res.code === 0 || res.code === 200) {
+ searchResults.value = res.data.list || res.data
+ }
showSearchResults.value = true
loading.value = false
}
// 搜索
const handleSearch = async () => {
+ // 如果在表单页面,先关闭表单
+ if (showAddForm.value) {
+ cancelAdd()
+ }
+
const seq = ++searchSeq
loading.value = true
try {
const params = {
- keyword: search.value,
- searchField: select.value,
- province: filterRegion.value?.[0] ? CodeToText[filterRegion.value[0]] : '',
- city: filterRegion.value?.[1] ? CodeToText[filterRegion.value[1]] : ''
+ name: select.value === '1' ? search.value : undefined,
+ id: select.value === '2' ? Number(search.value) : undefined,
+ province: filterRegion.value?.[0] ? CodeToText[filterRegion.value[0]] : undefined,
+ city: filterRegion.value?.[1] ? CodeToText[filterRegion.value[1]] : undefined
}
const res = await getCustomerList(params)
if (seq !== searchSeq) return
- searchResults.value = res.data
+ if (res.code === 0 || res.code === 200) {
+ searchResults.value = res.data.list || res.data
+ }
showSearchResults.value = true
} finally {
if (seq === searchSeq) loading.value = false
@@ -92,8 +123,8 @@ const resetForm = () => {
form.region = []
form.address = ''
form.email = ''
- form.customer_type = 'Normal'
form.remark = ''
+ form.responsible_user_id = ''
isEditMode.value = false
currentCustomerId.value = null
}
@@ -118,8 +149,8 @@ const editCustomer = (row) => {
]
form.address = row.address
form.email = row.email
- form.customer_type = row.customer_type
form.remark = row.remark
+ form.responsible_user_id = row.responsible_user_id || ''
showAddForm.value = true
showSearchResults.value = false
}
@@ -133,29 +164,39 @@ const saveCustomer = async () => {
district: CodeToText[form.region[2]] || '',
address: form.address,
email: form.email,
- customer_type: form.customer_type,
- remark: form.remark
- }
- if (isEditMode.value) {
- await updateCustomer(currentCustomerId.value, formData)
- ElMessage.success('更新成功')
- } else {
- await createCustomer(formData)
- ElMessage.success('新增成功')
+ remark: form.remark,
+ responsible_user_id: form.responsible_user_id !== '' ? form.responsible_user_id : null
}
+ try {
+ if (isEditMode.value) {
+ await updateCustomer(currentCustomerId.value, formData)
+ ElMessage.success('更新成功')
+ } else {
+ await createCustomer(formData)
+ ElMessage.success('新增成功')
+ }
- cancelAdd()
- fetchData()
+ cancelAdd()
+ fetchData()
+ } catch (error) {
+ ElMessage.error(error.response?.data?.message || error.message || '操作失败')
+ }
}
// 删除
const deleteCustomer = async (id) => {
ElMessageBox.confirm('确定删除该客户吗?', '提示', {
+ confirmButtonText: '确认',
+ cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
- await deleteCustomerAPI(id)
- ElMessage.success('删除成功')
- fetchData()
+ try {
+ await deleteCustomerAPI(id)
+ ElMessage.success('删除成功')
+ fetchData()
+ } catch (error) {
+ ElMessage.error(error.response?.data?.message || error.message || '删除失败')
+ }
})
}
@@ -172,6 +213,7 @@ const clearSearch = () => {
filterRegion.value = []
showSearchResults.value = false
showAddForm.value = false
+ currentPage.value = 1
fetchData()
}
@@ -187,33 +229,41 @@ const closeDetail = () => {
currentDetail.value = null
}
+// 页码变化
+const handleCurrentChange = (val) => {
+ currentPage.value = val
+}
+
+// 检查权限
+const canCreate = computed(() => hasPermission('customer', 'create'))
+const canUpdate = computed(() => hasPermission('customer', 'update'))
+const canDelete = computed(() => hasPermission('customer', 'delete'))
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
- 重置
-
-
- 新增客户
-
+
+ 新增客户
+ 重置
+
+
筛选条件:
@@ -239,31 +289,40 @@ const closeDetail = () => {
+
+
-
-
+
-
+
-
- {{ row.customer_type === 'VIP' ? '地区总代理' : '加盟商' }}
-
+ {{ row.responsible_user_name || '-' }}
- 编辑
- 删除
+ 编辑
+ 删除
-
- 找到 {{ searchResults.length }} 条结果
- 清空搜索
+
+
@@ -278,31 +337,30 @@ const closeDetail = () => {
-
-
-
+
+
-
+
-
-
- 地区总代理
- 加盟商
-
+
+
+
+
@@ -324,15 +382,11 @@ const closeDetail = () => {
{{ currentDetail.name }}
{{ currentDetail.phone }}
{{ currentDetail.email }}
-
-
- {{ currentDetail.customer_type === 'VIP' ? '地区总代理' : '加盟商' }}
-
-
{{ [currentDetail.province, currentDetail.city, currentDetail.district].filter(Boolean).join(' ') }}
{{ currentDetail.address || '无' }}
+ {{ currentDetail.responsible_user_name || '-' }}
{{ currentDetail.remark || '无' }}
@@ -340,10 +394,79 @@ const closeDetail = () => {
关闭
- 编辑
+ { const d = currentDetail; closeDetail(); editCustomer(d) })()">编辑
-
+
diff --git a/src/components/employee.vue b/src/components/employee.vue
index 8d40991..f222547 100644
--- a/src/components/employee.vue
+++ b/src/components/employee.vue
@@ -1,7 +1,7 @@
-
@@ -220,17 +355,13 @@ const closeDetail = () => {
- 新增员工
+ 新增员工
重置
-
筛选条件:
-
-
-
@@ -247,8 +378,12 @@ const closeDetail = () => {
-
-
+
+
+
+
@@ -266,7 +401,11 @@ const closeDetail = () => {
-
+
+
+ {{ formatDate(row.entry_date) }}
+
+
@@ -276,15 +415,23 @@ const closeDetail = () => {
- 编辑
- 删除
+ 编辑
+ 删除
-
- 找到 {{ searchResults.length }} 条结果
- 清空搜索
+
+
@@ -383,6 +530,62 @@ const closeDetail = () => {
+
+
+ 为员工创建系统用户
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 启用
+ 禁用
+
+
+
+
+
+
+ 保存用户
+ 暂不创建
+
+
+
+
@@ -399,7 +602,7 @@ const closeDetail = () => {
¥{{ currentDetail.salary?.toLocaleString() }}
- {{ currentDetail.entry_date }}
+ {{ formatDate(currentDetail.entry_date) }}
{{ currentDetail.phone }}
{{ currentDetail.email }}
@@ -407,7 +610,7 @@ const closeDetail = () => {
{{ currentDetail.status === 1 ? '在职' : '离职' }}
- {{ currentDetail.created_at }}
+ {{ formatDateTime(currentDetail.created_at) }}
{{ currentDetail.remark || '无' }}
@@ -415,7 +618,7 @@ const closeDetail = () => {
关闭
- 编辑
+ { const d = currentDetail; closeDetail(); editEmployee(d) })()" v-if="canUpdate">编辑
@@ -423,5 +626,78 @@ const closeDetail = () => {
diff --git a/src/components/panel.vue b/src/components/panel.vue
index 5400ef5..784125b 100644
--- a/src/components/panel.vue
+++ b/src/components/panel.vue
@@ -1,13 +1,36 @@
@@ -26,11 +49,17 @@ const switchFold = () => {
蜜雪冰城管理系统
-
-
-
- 退出登录
-
+
+
@@ -48,32 +77,37 @@ const switchFold = () => {
首页
-
+
客户管理
-
+
合同管理
-
+
售后管理
-
+
产品管理
-
+
+
+ 供应商管理
+
+
+
员工管理
-
+
用户管理
@@ -142,8 +176,22 @@ const switchFold = () => {
white-space: nowrap;
}
+.header-right {
+ display: flex;
+ align-items: center;
+ margin-left: auto;
+}
+
+.user-info {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-right: 20px;
+ color: #606266;
+ font-size: 14px;
+}
+
.logout-item {
- margin-left: auto !important;
color: #909399;
}
diff --git a/src/components/products.vue b/src/components/products.vue
index 140fcf2..4d7f4a6 100644
--- a/src/components/products.vue
+++ b/src/components/products.vue
@@ -1,7 +1,7 @@
-
+
-
@@ -214,13 +277,12 @@ const getTypeTag = (type) => {
- 新增产品
+ 新增产品
重置
-
-
+
筛选条件:
@@ -232,7 +294,7 @@ const getTypeTag = (type) => {
-
+
@@ -241,8 +303,13 @@ const getTypeTag = (type) => {
-
-
+
+
+
+
@@ -263,15 +330,23 @@ const getTypeTag = (type) => {
- 编辑
- 删除
+ 编辑
+ 删除
-
- 找到 {{ searchResults.length }} 条结果
- 清空搜索
+
+
@@ -285,7 +360,7 @@ const getTypeTag = (type) => {
-
+
@@ -299,22 +374,24 @@ const getTypeTag = (type) => {
-
+
-
+
-
+
+
+
-
+
@@ -350,7 +427,7 @@ const getTypeTag = (type) => {
关闭
- 编辑
+ { const d = currentDetail; closeDetail(); editProduct(d) })()">编辑
@@ -358,5 +435,78 @@ const getTypeTag = (type) => {
diff --git a/src/components/service.vue b/src/components/service.vue
index 0d4c6ed..4652f77 100644
--- a/src/components/service.vue
+++ b/src/components/service.vue
@@ -1,7 +1,7 @@
-
+
-
-
+
+
@@ -221,12 +285,11 @@ const closeDetail = () => {
- 新增售后
+ 新增售后
重置
-
筛选条件:
@@ -246,9 +309,14 @@ const closeDetail = () => {
-
-
-
+
+
+
+
+
@@ -263,19 +331,31 @@ const closeDetail = () => {
-
+
+
+ {{ formatDate(row.service_date) }}
+
+
- 编辑
- 删除
+ 编辑
+ 删除
-
- 找到 {{ searchResults.length }} 条结果
- 清空搜索
+
+
@@ -303,7 +383,8 @@ const closeDetail = () => {
-
+
@@ -333,7 +414,7 @@ const closeDetail = () => {
{{ currentDetail.id }}
- {{ currentDetail.service_date }}
+ {{ formatDate(currentDetail.service_date) }}
{{ currentDetail.customer_id }}
{{ currentDetail.employee_id || '未分配' }}
@@ -341,7 +422,7 @@ const closeDetail = () => {
{{ currentDetail.handle_status }}
- {{ currentDetail.created_at }}
+ {{ formatDateTime(currentDetail.created_at) }}
{{ currentDetail.feedback }}
@@ -355,7 +436,7 @@ const closeDetail = () => {
关闭
- 编辑
+ { const d = currentDetail; closeDetail(); editService(d) })()">编辑
@@ -363,5 +444,79 @@ const closeDetail = () => {
diff --git a/src/components/supplier.vue b/src/components/supplier.vue
new file mode 100644
index 0000000..2068907
--- /dev/null
+++ b/src/components/supplier.vue
@@ -0,0 +1,466 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新增供应商
+ 重置
+
+
+
+
+ 筛选条件:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.type || '-' }}
+
+
+
+
+
+
+ {{ row.status === 1 ? '正常' : '停用' }}
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+ {{ isEditMode ? '编辑供应商信息' : '新增供应商' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 正常
+ 停用
+
+
+
+
+ 保存
+ 取消
+
+
+
+
+
+
+
+
+ {{ currentDetail.id }}
+ {{ currentDetail.name }}
+ {{ currentDetail.contact || '无' }}
+ {{ currentDetail.phone || '无' }}
+
+ {{ currentDetail.type || '无' }}
+
+
+
+ {{ currentDetail.status === 1 ? '正常' : '停用' }}
+
+
+ {{ currentDetail.address || '无' }}
+ {{ currentDetail.content || '无' }}
+
+
+
+ 关闭
+ { const d = currentDetail; closeDetail(); editSupplier(d) })()" v-if="canUpdate">编辑
+
+
+
+
+
+
+
diff --git a/src/components/user.vue b/src/components/user.vue
index edf400c..2d713f5 100644
--- a/src/components/user.vue
+++ b/src/components/user.vue
@@ -1,7 +1,7 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- 重置
-
-
- 新增用户
-
+
+ 新增用户
+ 重置
+
+
筛选条件:
-
-
-
+
+
-
-
+
+
-
@@ -210,28 +308,43 @@ const closeDetail = () => {
+
+
-
-
+
-
-
-
+
-
- {{ row.user_type === 'admin' ? '管理员' : '普通用户' }}
+ {{ row.real_name || '-' }}
+
+
+
+
+ {{ row.role_description || row.role_name }}
+
+
+
+
+ {{ row.department || '-' }}
+
+
+
+
+
+ {{ row.is_active === 1 ? '启用' : '禁用' }}
-
+
-
- {{ row.status === '0' ? '封禁中' : '启用' }}
-
+ {{ formatDateTime(row.created_at) }}
-
+
编辑
删除
@@ -239,42 +352,55 @@ const closeDetail = () => {
-
- 找到 {{ searchResults.length }} 条结果
- 清空搜索
+
+
-
{{ isEditMode ? '编辑用户信息' : '新增用户' }}
-
+
-
+
-
-
+
+
+
+
-
-
- 管理员
- 普通用户
-
+
+
+
+
+
+
+
+
-
- 禁用
- 启用
+
+ 启用
+ 禁用
@@ -292,23 +418,21 @@ const closeDetail = () => {
{{ currentDetail.id }}
{{ currentDetail.username }}
- {{ currentDetail.name }}
- {{ currentDetail.createdate }}
-
-
- {{ currentDetail.user_type === 'admin' ? '管理员' : '普通用户' }}
-
-
-
-
- {{ currentDetail.status === '0' ? '封禁中' : '启用' }}
+ {{ currentDetail.real_name || '-' }}
+ {{ currentDetail.role_description || currentDetail.role_name }}
+ {{ currentDetail.department || '-' }}
+ {{ currentDetail.employee_id || '-' }}
+
+
+ {{ currentDetail.is_active === 1 ? '启用' : '禁用' }}
+ {{ formatDateTime(currentDetail.created_at) }}
关闭
- 编辑
+ { const d = currentDetail; closeDetail(); editUser(d) })()" v-if="canManage">编辑
@@ -316,5 +440,72 @@ const closeDetail = () => {
diff --git a/src/router.js b/src/router.js
index cffabea..c64a1bd 100644
--- a/src/router.js
+++ b/src/router.js
@@ -8,6 +8,7 @@ import Service from "./components/service.vue";
import Products from "./components/products.vue";
import Employee from "./components/employee.vue";
import User from "./components/user.vue";
+import Supplier from "./components/supplier.vue";
const routes = [
{ path: "/", redirect: "/login" },
@@ -17,13 +18,14 @@ const routes = [
component: Panel,
redirect: "/panel/home",
children: [
- { path: "home", component:Home},
- { path: "customer", component: Customer },
- { path: "contract", component: Contract },
- { path: "service", component: Service },
- { path: "products", component: Products },
- { path: "employee", component: Employee },
- { path: "user", component: User }
+ { path: "home", component: Home },
+ { path: "customer", component: Customer, meta: { permission: 'customer:read' } },
+ { path: "contract", component: Contract, meta: { permission: 'contract:read' } },
+ { path: "service", component: Service, meta: { permission: 'after_sale:read' } },
+ { path: "products", component: Products, meta: { permission: 'product:read' } },
+ { path: "employee", component: Employee, meta: { permission: 'employee:read' } },
+ { path: "user", component: User, meta: { permission: 'user:manage' } },
+ { path: "supplier", component: Supplier, meta: { permission: 'supplier:read' } }
],
},
]
@@ -32,4 +34,39 @@ const router = createRouter({
history: createWebHistory(),
routes,
})
-export default router;
\ No newline at end of file
+
+// 全局前置守卫
+router.beforeEach((to, from, next) => {
+ const token = localStorage.getItem('token')
+
+ // 登录页
+ if (to.path === '/login') {
+ if (token) {
+ // 已登录,跳转到首页
+ next('/panel/home')
+ } else {
+ next()
+ }
+ return
+ }
+
+ // 其他页面需要登录
+ if (!token) {
+ next('/login')
+ return
+ }
+
+ // 检查路由权限
+ if (to.meta.permission) {
+ const [resource, action] = to.meta.permission.split(':')
+ const userInfo = JSON.parse(localStorage.getItem('userInfo') || 'null')
+ if (!userInfo?.permissions?.includes(to.meta.permission)) {
+ next('/panel/home')
+ return
+ }
+ }
+
+ next()
+})
+
+export default router;
diff --git a/src/store/user.js b/src/store/user.js
new file mode 100644
index 0000000..d537200
--- /dev/null
+++ b/src/store/user.js
@@ -0,0 +1,72 @@
+import { reactive, ref } from 'vue'
+import request from '../api/request'
+
+// 用户状态
+const state = reactive({
+ token: localStorage.getItem('token') || '',
+ userInfo: JSON.parse(localStorage.getItem('userInfo') || 'null')
+})
+
+// 是否已登录
+const isLoggedIn = ref(!!state.token)
+
+// 登录
+export const login = async (username, password) => {
+ const res = await request.post('/user/login', { username, password })
+ if (res.code === 0 || res.code === 200) {
+ state.token = res.data.token
+ state.userInfo = res.data.userInfo
+ isLoggedIn.value = true
+ localStorage.setItem('token', res.data.token)
+ localStorage.setItem('userInfo', JSON.stringify(res.data.userInfo))
+ }
+ return res
+}
+
+// 登出
+export const logout = async () => {
+ try {
+ await request.post('/user/logout')
+ } catch (e) {
+ // 即使请求失败也清除本地状态
+ }
+ state.token = ''
+ state.userInfo = null
+ isLoggedIn.value = false
+ localStorage.removeItem('token')
+ localStorage.removeItem('userInfo')
+}
+
+// 获取当前用户信息
+export const fetchUserInfo = async () => {
+ const res = await request.get('/user/info')
+ if (res.code === 0 || res.code === 200) {
+ state.userInfo = res.data
+ localStorage.setItem('userInfo', JSON.stringify(res.data))
+ }
+ return res
+}
+
+// 检查是否有某个权限
+export const hasPermission = (resource, action) => {
+ if (!state.userInfo?.permissions) return false
+ const permission = `${resource}:${action}`
+ return state.userInfo.permissions.includes(permission)
+}
+
+// 获取当前用户信息
+export const getUserInfo = () => state.userInfo
+
+// 获取 token
+export const getToken = () => state.token
+
+export default {
+ state,
+ isLoggedIn,
+ login,
+ logout,
+ fetchUserInfo,
+ hasPermission,
+ getUserInfo,
+ getToken
+}
diff --git a/src/utils/date.js b/src/utils/date.js
new file mode 100644
index 0000000..0ba93b2
--- /dev/null
+++ b/src/utils/date.js
@@ -0,0 +1,32 @@
+// 格式化日期时间(带时间)
+export const formatDateTime = (dateStr) => {
+ if (!dateStr) return '-'
+ try {
+ const date = new Date(dateStr)
+ if (isNaN(date.getTime())) return dateStr
+ const year = date.getFullYear()
+ const month = String(date.getMonth() + 1).padStart(2, '0')
+ const day = String(date.getDate()).padStart(2, '0')
+ const hours = String(date.getHours()).padStart(2, '0')
+ const minutes = String(date.getMinutes()).padStart(2, '0')
+ const seconds = String(date.getSeconds()).padStart(2, '0')
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+ } catch {
+ return dateStr
+ }
+}
+
+// 格式化日期(只显示日期部分)
+export const formatDate = (dateStr) => {
+ if (!dateStr) return '-'
+ try {
+ const date = new Date(dateStr)
+ if (isNaN(date.getTime())) return dateStr
+ const year = date.getFullYear()
+ const month = String(date.getMonth() + 1).padStart(2, '0')
+ const day = String(date.getDate()).padStart(2, '0')
+ return `${year}-${month}-${day}`
+ } catch {
+ return dateStr
+ }
+}
diff --git a/vite.config.js b/vite.config.js
index 2155fb3..a99f281 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -4,4 +4,13 @@ import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
+ server: {
+ port: 5173,
+ proxy: {
+ '/api': {
+ target: 'http://localhost:3000',
+ changeOrigin: true
+ }
+ }
+ }
})
diff --git a/yarn.lock b/yarn.lock
index 2d77a42..56243b1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -72,6 +72,16 @@
resolved "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz"
integrity sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==
+"@rolldown/binding-linux-x64-gnu@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz"
+ integrity sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==
+
+"@rolldown/binding-linux-x64-musl@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz"
+ integrity sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==
+
"@rolldown/binding-win32-x64-msvc@1.0.3":
version "1.0.3"
resolved "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz"
@@ -279,6 +289,16 @@ fdir@^6.5.0:
resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz"
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
+lightningcss-linux-x64-gnu@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz"
+ integrity sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==
+
+lightningcss-linux-x64-musl@1.32.0:
+ version "1.32.0"
+ resolved "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz"
+ integrity sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==
+
lightningcss-win32-x64-msvc@1.32.0:
version "1.32.0"
resolved "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz"