店铺模块+书品管理+仓储管理+用户权限管理
This commit is contained in:
parent
b61e5ebafe
commit
5e38aa11b6
BIN
public/templates/depotRule.doc
Normal file
BIN
public/templates/depotRule.doc
Normal file
Binary file not shown.
@ -9,3 +9,5 @@ setupResponseInterceptors(instance)
|
||||
// 导出所有模块接口
|
||||
export { adminApi } from './modules/admin'
|
||||
export { invitationApi } from './modules/invitation'
|
||||
export { depotApi } from './modules/depot'
|
||||
export { userApi } from './modules/user'
|
||||
@ -7,7 +7,7 @@ export function setupRequestInterceptors(instance) {
|
||||
// 判断 token 是否存在
|
||||
if (token) {
|
||||
// 如果存在,则将 token 添加到请求头中的 Authorization 字段
|
||||
config.headers.Authorization = `${token}`
|
||||
config.headers.Authorization = token
|
||||
}
|
||||
// 返回修改后的请求配置,继续发送请求
|
||||
return config
|
||||
|
||||
@ -42,8 +42,12 @@ export function setupResponseInterceptors(instance) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
// 创建表单数据
|
||||
const formData = new FormData();
|
||||
formData.append('refreshToken', refreshToken);
|
||||
|
||||
// 调用刷新token接口
|
||||
const response = await axios.post('/admin/getAccessToken', { refreshToken });
|
||||
const response = await axios.post('/admin/getAccessToken', formData);
|
||||
|
||||
// 获取新的token
|
||||
const { accessToken, refreshToken: newRefreshToken } = response.data.data;
|
||||
|
||||
@ -4,8 +4,7 @@ import instance from '../../utils/axios.js'
|
||||
const adminApi = {
|
||||
register: (data) => instance.post('/admin/register', data),
|
||||
login: (data) => instance.post('/admin/login', data),
|
||||
getAdmin: (data) => instance.post('/admin/getAdmin', data),
|
||||
|
||||
getAdmin: () => instance.get('/admin/getAdmin'),
|
||||
};
|
||||
|
||||
// 若需要模块导出
|
||||
|
||||
21
src/api/modules/baseInfo.js
Normal file
21
src/api/modules/baseInfo.js
Normal file
@ -0,0 +1,21 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 分页查询书籍基础信息列表
|
||||
* @param {Object} query 查询参数
|
||||
* @returns {Promise} 请求Promise对象
|
||||
*/
|
||||
export function getBaseInfoList(query) {
|
||||
return request({
|
||||
url: '/baseInfo/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
pageNum: query.pageNum || 1,
|
||||
pageSize: query.pageSize || 10,
|
||||
bookName: query.bookName,
|
||||
isbn: query.isbn,
|
||||
author: query.author,
|
||||
publisher: query.publisher
|
||||
}
|
||||
})
|
||||
}
|
||||
35
src/api/modules/bookBaseInfo.js
Normal file
35
src/api/modules/bookBaseInfo.js
Normal file
@ -0,0 +1,35 @@
|
||||
import instance from '../../utils/axios.js'
|
||||
|
||||
const bookBaseInfoApi = {
|
||||
// 获取图书基本信息列表
|
||||
getBookBaseInfoList: (params) => instance.get('/baseInfo/list', { params }),
|
||||
|
||||
// 获取图书基本信息详情
|
||||
getBookBaseInfoDetail: (id) => instance.get(`/book/baseInfo/detail/${id}`),
|
||||
|
||||
// 添加图书基本信息
|
||||
addBookBaseInfo: (data) => instance.post('/book/baseInfo/add', data),
|
||||
|
||||
// 更新图书基本信息
|
||||
updateBookBaseInfo: (data) => instance.put('/book/baseInfo/update', data),
|
||||
|
||||
// 删除图书基本信息
|
||||
deleteBookBaseInfo: (id) => instance.delete(`/book/baseInfo/delete/${id}`),
|
||||
|
||||
// 批量删除图书基本信息
|
||||
batchDeleteBookBaseInfo: (ids) => instance.post('/book/baseInfo/batchDelete', { ids }),
|
||||
|
||||
// 更新图书违规状态
|
||||
updateBookViolationStatus: (data) => instance.put('/book/baseInfo/updateViolation', data),
|
||||
|
||||
// 批量设置违规信息
|
||||
updateViolationConfig: (data) => instance.put('/baseInfo/batchUpdateIll', data),
|
||||
|
||||
// 图书价格调整
|
||||
adjustBookPrice: (data) => instance.put('/book/baseInfo/adjustPrice', data),
|
||||
|
||||
// 统计图书数据
|
||||
getBookStatistics: () => instance.get('/book/baseInfo/statistics')
|
||||
}
|
||||
|
||||
export { bookBaseInfoApi }
|
||||
72
src/api/modules/depot.js
Normal file
72
src/api/modules/depot.js
Normal file
@ -0,0 +1,72 @@
|
||||
import instance from '../../utils/axios.js'
|
||||
|
||||
/**
|
||||
* 货区管理相关接口
|
||||
*/
|
||||
const depotApi = {
|
||||
/**
|
||||
* 获取货区列表
|
||||
* @param {Object} params - 查询参数
|
||||
* @returns {Promise} - 请求Promise
|
||||
*/
|
||||
getDepotList: (params) => instance.get('/depot/list', { params }),
|
||||
|
||||
/**
|
||||
* 创建货区
|
||||
* @param {Object} data - 货区信息
|
||||
* @returns {Promise} - 请求Promise
|
||||
*/
|
||||
createDepot: (data) => instance.post('/depot/create', data),
|
||||
|
||||
/**
|
||||
* 更新货区
|
||||
* @param {Object} data - 货区信息
|
||||
* @returns {Promise} - 请求Promise
|
||||
*/
|
||||
updateDepot: (data) => instance.put('/depot/update', data),
|
||||
|
||||
/**
|
||||
* 删除货区
|
||||
* @param {Number|String} id - 货区ID
|
||||
* @returns {Promise} - 请求Promise
|
||||
*/
|
||||
deleteDepot: (id) => instance.delete(`/depot/delete/${id}`),
|
||||
|
||||
/**
|
||||
* 获取货区详情
|
||||
* @param {Number|String} id - 货区ID
|
||||
* @returns {Promise} - 请求Promise
|
||||
*/
|
||||
getDepotById: (id) => instance.get(`/depot/get/${id}`),
|
||||
|
||||
/**
|
||||
* 根据用户ID获取货区
|
||||
* @param {Number|String} userId - 用户ID
|
||||
* @returns {Promise} - 请求Promise
|
||||
*/
|
||||
getDepotsByUserId: (userId) => instance.get(`/depot/user/${userId}`),
|
||||
|
||||
/**
|
||||
* 根据货区编码获取货区
|
||||
* @param {String} code - 货区编码
|
||||
* @returns {Promise} - 请求Promise
|
||||
*/
|
||||
getDepotByCode: (code) => instance.get(`/depot/code/${code}`),
|
||||
|
||||
/**
|
||||
* 根据一级货区ID获取二级货架信息
|
||||
* @param {Number|String} id - 一级货区ID
|
||||
* @returns {Promise} - 请求Promise
|
||||
*/
|
||||
getShelvesByDepotId: (id) => instance.get(`/depot/shelves/${id}`),
|
||||
|
||||
/**
|
||||
* 根据二级货架ID获取三级货位信息
|
||||
* @param {Number|String} id - 二级货架ID
|
||||
* @returns {Promise} - 请求Promise
|
||||
*/
|
||||
getFreightByShelveId: (id) => instance.get(`/depot/freight/${id}`)
|
||||
}
|
||||
|
||||
// 导出模块
|
||||
export { depotApi }
|
||||
23
src/api/modules/user.js
Normal file
23
src/api/modules/user.js
Normal file
@ -0,0 +1,23 @@
|
||||
import instance from '../../utils/axios.js'
|
||||
|
||||
// 用户相关API
|
||||
const userApi = {
|
||||
// 获取用户列表
|
||||
getUserList: () => instance.get('/user/list'),
|
||||
|
||||
// 获取单个用户信息
|
||||
getUserById: (id) => instance.get(`/user/get/${id}`),
|
||||
|
||||
// 注册/添加用户
|
||||
register: (data) => instance.post('/user/register', data),
|
||||
|
||||
// 更新用户信息
|
||||
updateUser: (data) => instance.put('/user/update', data),
|
||||
|
||||
// 删除用户
|
||||
deleteUser: (id) => instance.delete(`/user/delete/${id}`),
|
||||
|
||||
};
|
||||
|
||||
// 导出模块
|
||||
export { userApi };
|
||||
43
src/api/permission.js
Normal file
43
src/api/permission.js
Normal file
@ -0,0 +1,43 @@
|
||||
import axios from '@/utils/axios'
|
||||
|
||||
/**
|
||||
* 获取权限树
|
||||
*/
|
||||
export function getPermissionTree() {
|
||||
return axios({
|
||||
url: '/admin/permission/tree',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加权限
|
||||
*/
|
||||
export function addPermission(data) {
|
||||
return axios({
|
||||
url: '/admin/permission/add',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新权限
|
||||
*/
|
||||
export function updatePermission(data) {
|
||||
return axios({
|
||||
url: '/admin/permission/update',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除权限
|
||||
*/
|
||||
export function deletePermission(id) {
|
||||
return axios({
|
||||
url: `/admin/permission/delete/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
64
src/api/role.js
Normal file
64
src/api/role.js
Normal file
@ -0,0 +1,64 @@
|
||||
import axios from '@/utils/axios'
|
||||
|
||||
/**
|
||||
* 获取角色列表
|
||||
*/
|
||||
export function getRoleList() {
|
||||
return axios({
|
||||
url: '/admin/role/list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加角色
|
||||
*/
|
||||
export function addRole(data) {
|
||||
return axios({
|
||||
url: '/admin/role/add',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色
|
||||
*/
|
||||
export function updateRole(data) {
|
||||
return axios({
|
||||
url: '/admin/role/update',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除角色
|
||||
*/
|
||||
export function deleteRole(id) {
|
||||
return axios({
|
||||
url: `/admin/role/delete/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色权限
|
||||
*/
|
||||
export function getRolePermissions(roleId) {
|
||||
return axios({
|
||||
url: `/admin/role/permissions/${roleId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色权限
|
||||
*/
|
||||
export function updateRolePermissions(roleId, permissionIds) {
|
||||
return axios({
|
||||
url: `/admin/role/permissions/${roleId}`,
|
||||
method: 'put',
|
||||
data: permissionIds
|
||||
})
|
||||
}
|
||||
33
src/api/user.js
Normal file
33
src/api/user.js
Normal file
@ -0,0 +1,33 @@
|
||||
import axios from '@/utils/axios'
|
||||
|
||||
/**
|
||||
* 获取管理员角色
|
||||
*/
|
||||
export function getAdminRoles(adminId) {
|
||||
return axios({
|
||||
url: `/admin/user/roles/${adminId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新管理员角色
|
||||
*/
|
||||
export function updateAdminRoles(adminId, roleIds) {
|
||||
return axios({
|
||||
url: `/admin/user/roles/${adminId}`,
|
||||
method: 'put',
|
||||
data: roleIds
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
*/
|
||||
export function getUserList(params) {
|
||||
return axios({
|
||||
url: '/admin/user/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { shallowRef } from 'vue'
|
||||
import {Document as DocIcon,Setting,User,Message,ShoppingCart,Shop,Connection } from '@element-plus/icons-vue'
|
||||
import {Document as DocIcon,Setting,User,Message,ShoppingCart,Shop,Connection,Notebook,Box,TrendCharts } from '@element-plus/icons-vue'
|
||||
|
||||
const menuData = shallowRef([{
|
||||
title: '系统管理',
|
||||
@ -53,6 +53,14 @@
|
||||
{
|
||||
title: '角色管理',
|
||||
path: '/user/role'
|
||||
},
|
||||
{
|
||||
title: '权限管理',
|
||||
path: '/user/permission'
|
||||
},
|
||||
{
|
||||
title: '用户角色管理',
|
||||
path: '/user/userRole'
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -93,6 +101,32 @@
|
||||
}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
title: '书品管理',
|
||||
path: '/book',
|
||||
icon: Notebook,
|
||||
children: [{
|
||||
title: '选品中心',
|
||||
path: '/book/selection',
|
||||
children: [{
|
||||
title: '选品中心',
|
||||
path: '/book/selection/center'
|
||||
}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
title: '仓储管理',
|
||||
path: '/warehouse',
|
||||
icon: Box,
|
||||
children: [{
|
||||
title: '货区管理',
|
||||
path: '/warehouse/depot',
|
||||
children: [{
|
||||
title: '货区列表',
|
||||
path: '/warehouse/depot/list'
|
||||
}]
|
||||
}]
|
||||
},
|
||||
{
|
||||
title: '工具管理',
|
||||
path: '/tools',
|
||||
|
||||
@ -33,6 +33,21 @@ const routes = [{
|
||||
component: () => import('@/views/User/Edit.vue'),
|
||||
meta: { title: '新增用户' }
|
||||
},
|
||||
{
|
||||
path: '/user/role',
|
||||
component: () => import('@/views/User/Role.vue'),
|
||||
meta: { title: '角色管理' }
|
||||
},
|
||||
{
|
||||
path: '/user/permission',
|
||||
component: () => import('@/views/User/Permission.vue'),
|
||||
meta: { title: '权限管理' }
|
||||
},
|
||||
{
|
||||
path: '/user/userRole',
|
||||
component: () => import('@/views/User/UserRole.vue'),
|
||||
meta: { title: '用户角色管理' }
|
||||
},
|
||||
{
|
||||
path: '/shop/list',
|
||||
component: () => import('@/views/Shop/index.vue'),
|
||||
@ -72,6 +87,16 @@ const routes = [{
|
||||
path: '/websocket/demo',
|
||||
component: () => import('@/views/websocket/index.vue'),
|
||||
meta: { title: 'WebSocket演示' }
|
||||
},
|
||||
{
|
||||
path: '/book/selection/center',
|
||||
component: () => import('@/views/baseInfo/index.vue'),
|
||||
meta: { title: '选品中心' }
|
||||
},
|
||||
{
|
||||
path: '/warehouse/depot/list',
|
||||
component: () => import('@/views/Warehouse/Depot/List.vue'),
|
||||
meta: { title: '货区管理' }
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
||||
@ -5,7 +5,7 @@ export default createStore({
|
||||
state: {
|
||||
accessToken: localStorage.getItem('accessToken') || '', // 短令牌
|
||||
refreshToken: localStorage.getItem('refreshToken') || '', // 长令牌
|
||||
userInfo: null // 用户信息
|
||||
userInfo: JSON.parse(localStorage.getItem('userInfo')) || null // 用户信息
|
||||
},
|
||||
mutations: {
|
||||
SET_TOKEN(state, data) {
|
||||
@ -16,7 +16,7 @@ export default createStore({
|
||||
// 缓存到本地
|
||||
localStorage.setItem('accessToken', state.accessToken)
|
||||
// 缓存到本地
|
||||
localStorage.setItem('refreshToken', state.localStorage)
|
||||
localStorage.setItem('refreshToken', state.refreshToken)
|
||||
},
|
||||
CLEAR_AUTH(state) {
|
||||
// 清空 短令牌
|
||||
@ -44,12 +44,34 @@ export default createStore({
|
||||
try {
|
||||
// 调用登录接口
|
||||
const response = await adminApi.login(data)
|
||||
|
||||
// 检查响应状态
|
||||
if (response.code !== 200) {
|
||||
throw new Error(response.message || '登录失败')
|
||||
}
|
||||
|
||||
// 设置 状态
|
||||
commit('SET_TOKEN', response.data)
|
||||
// 调用查询接口
|
||||
|
||||
try {
|
||||
// 调用查询接口获取用户信息
|
||||
const admin = await adminApi.getAdmin()
|
||||
|
||||
// 检查响应状态
|
||||
if (admin.code !== 200) {
|
||||
console.error('获取用户信息失败:', admin.message)
|
||||
// 不清空认证信息,仅返回警告
|
||||
return Promise.resolve('登录成功,但获取用户信息失败')
|
||||
}
|
||||
|
||||
// 设置 管理员信息
|
||||
commit('SET_USER_INFO', admin.data)
|
||||
} catch (adminError) {
|
||||
// 记录错误但不清空认证信息
|
||||
console.error('获取用户信息出错:', adminError)
|
||||
return Promise.resolve('登录成功,但获取用户信息失败')
|
||||
}
|
||||
|
||||
// 抛出 消息
|
||||
return Promise.resolve('登录成功')
|
||||
} catch (error) {
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref,reactive,getCurrentInstance } from 'vue'
|
||||
import { ref, reactive, getCurrentInstance, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import store from '../../store'
|
||||
@ -36,9 +36,9 @@
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
username: 'testUser1',
|
||||
password: 'admin123*',
|
||||
captcha: '5555'
|
||||
username: '',
|
||||
password: '',
|
||||
captcha: ''
|
||||
})
|
||||
|
||||
// 验证规则
|
||||
@ -48,17 +48,29 @@
|
||||
captcha: [{required: true, message: '验证码不能为空', trigger: 'blur'}]
|
||||
})
|
||||
|
||||
// 验证码URL(示例需要替换真实验证码接口)
|
||||
const captchaUrl = ref('https://newadmin.buzhiyushu.cn/admin/generateCaptcha?t=' + Date.now())
|
||||
// 表单引用
|
||||
const formRef = ref(null)
|
||||
|
||||
// 验证码URL
|
||||
const captchaUrl = ref('')
|
||||
|
||||
// 防止重复提交
|
||||
const loading = ref(false)
|
||||
|
||||
// 路由
|
||||
const router = useRouter()
|
||||
|
||||
// 刷新验证码
|
||||
const refreshCaptcha = () => { captchaUrl.value = `https://newadmin.buzhiyushu.cn/admin/generateCaptcha?t=${Date.now()}` }
|
||||
const refreshCaptcha = () => {
|
||||
captchaUrl.value = `https://newadmin.buzhiyushu.cn/admin/generateCaptcha?t=${Date.now()}`
|
||||
}
|
||||
|
||||
// 处理登录
|
||||
const handleLogin = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
// 锁定按钮
|
||||
loading.value = true
|
||||
@ -68,22 +80,33 @@
|
||||
sendForm.append('username', form.username)
|
||||
// 填充数据
|
||||
sendForm.append('password', form.password)
|
||||
sendForm.append('code', form.captcha) // 添加验证码参数
|
||||
// 调用接口
|
||||
await store.dispatch('login', sendForm)
|
||||
// 定义 跳转地址
|
||||
const redirect = router.currentRoute.value.query.redirect || '/'
|
||||
// 执行跳转
|
||||
router.replace(redirect)
|
||||
ElMessage.success('登录成功')
|
||||
} catch (error) {
|
||||
// 重置 验证码
|
||||
console.error('登录错误详情:', error)
|
||||
refreshCaptcha()
|
||||
// 置 错误消息到用户
|
||||
ElMessage.error(error.message || '登录失败')
|
||||
} finally {
|
||||
// 释放按钮
|
||||
loading.value = false
|
||||
}
|
||||
} else {
|
||||
// 验证失败,刷新验证码
|
||||
refreshCaptcha()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 组件挂载时刷新验证码
|
||||
onMounted(() => {
|
||||
refreshCaptcha()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -1,9 +1,444 @@
|
||||
<template>
|
||||
用户列表
|
||||
<div class="user-list-container">
|
||||
<div class="header-actions">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入用户名搜索"
|
||||
clearable
|
||||
class="search-input"
|
||||
@clear="loadUserList"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="handleSearch">
|
||||
<el-icon><Search /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-button type="primary" @click="openUserDialog()">
|
||||
<el-icon><Plus /></el-icon>新增用户
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="userList"
|
||||
border
|
||||
style="width: 100%"
|
||||
row-key="id"
|
||||
>
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="username" label="用户名" />
|
||||
<el-table-column prop="nickname" label="昵称" />
|
||||
<el-table-column prop="phone" label="手机号" />
|
||||
<el-table-column prop="email" label="邮箱" />
|
||||
<el-table-column prop="createTime" label="创建时间" :formatter="formatDate" />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="openUserDialog(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 用户表单对话框 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="isEdit ? '编辑用户' : '新增用户'"
|
||||
width="500px"
|
||||
@closed="resetForm"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
class="user-form"
|
||||
>
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="form.username" placeholder="请输入用户名" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="password" v-if="!isEdit">
|
||||
<el-input v-model="form.password" type="password" placeholder="请输入密码" show-password />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="确认密码" prop="confirmPassword" v-if="!isEdit">
|
||||
<el-input v-model="form.confirmPassword" type="password" placeholder="请确认密码" show-password />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="昵称" prop="nickname">
|
||||
<el-input v-model="form.nickname" placeholder="请输入昵称" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="form.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色" prop="roleId">
|
||||
<el-select v-model="form.roleId" placeholder="请选择角色">
|
||||
<el-option
|
||||
v-for="role in roleList"
|
||||
:key="role.id"
|
||||
:label="role.roleName"
|
||||
:value="role.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="submitForm">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, Plus } from '@element-plus/icons-vue'
|
||||
import { userApi } from '@/api'
|
||||
import { getRoleList } from '@/api/role'
|
||||
|
||||
// 表格数据
|
||||
const userList = ref([])
|
||||
const loading = ref(false)
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 分页参数
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
|
||||
// 对话框相关
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const roleList = ref([])
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
id: null,
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
nickname: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
roleId: null // 角色ID
|
||||
})
|
||||
|
||||
// 表单校验规则
|
||||
const validatePass = (rule, value, callback) => {
|
||||
if (!isEdit.value && !value) {
|
||||
callback(new Error('请输入密码'))
|
||||
} else {
|
||||
if (form.confirmPassword !== '') {
|
||||
formRef.value?.validateField('confirmPassword')
|
||||
}
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const validateConfirmPass = (rule, value, callback) => {
|
||||
if (!isEdit.value && !value) {
|
||||
callback(new Error('请再次输入密码'))
|
||||
} else if (value !== form.password) {
|
||||
callback(new Error('两次输入密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const rules = reactive({
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ validator: validatePass, trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ validator: validateConfirmPass, trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
|
||||
],
|
||||
roleId: [
|
||||
{ required: true, message: '请选择角色', trigger: 'change' }
|
||||
]
|
||||
})
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref(null)
|
||||
|
||||
// 加载用户列表
|
||||
const loadUserList = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await userApi.getUserList()
|
||||
|
||||
if (res.code === 200) {
|
||||
userList.value = res.data || []
|
||||
total.value = res.data?.length || 0
|
||||
|
||||
// 如果有搜索关键词,进行前端过滤
|
||||
if (searchKeyword.value) {
|
||||
userList.value = userList.value.filter(user =>
|
||||
user.username?.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||||
user.nickname?.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取用户列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户列表出错:', error)
|
||||
ElMessage.error(error.message || '获取用户列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
currentPage.value = 1
|
||||
loadUserList()
|
||||
}
|
||||
|
||||
// 加载角色列表
|
||||
const loadRoleList = async () => {
|
||||
try {
|
||||
const res = await getRoleList()
|
||||
console.log(res)
|
||||
if (res.code === 200) {
|
||||
roleList.value = res.data || []
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取角色列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取角色列表出错:', error)
|
||||
ElMessage.error(error.message || '获取角色列表失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 打开用户对话框
|
||||
const openUserDialog = (row) => {
|
||||
resetForm()
|
||||
|
||||
if (row) {
|
||||
// 编辑模式
|
||||
isEdit.value = true
|
||||
getUserDetail(row.id)
|
||||
} else {
|
||||
// 新增模式
|
||||
isEdit.value = false
|
||||
}
|
||||
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 获取用户详情
|
||||
const getUserDetail = async (id) => {
|
||||
try {
|
||||
submitLoading.value = true
|
||||
const res = await userApi.getUserById(id)
|
||||
|
||||
if (res.code === 200 && res.data) {
|
||||
// 填充表单数据
|
||||
Object.keys(form).forEach(key => {
|
||||
if (key !== 'password' && key !== 'confirmPassword' && res.data[key] !== undefined) {
|
||||
form[key] = res.data[key]
|
||||
}
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取用户信息失败')
|
||||
dialogVisible.value = false
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户信息出错:', error)
|
||||
ElMessage.error(error.message || '获取用户信息失败')
|
||||
dialogVisible.value = false
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
submitLoading.value = true
|
||||
|
||||
// 移除确认密码字段
|
||||
const submitData = { ...form }
|
||||
delete submitData.confirmPassword
|
||||
|
||||
// 如果是编辑模式且没有设置密码,则移除密码字段
|
||||
if (isEdit.value && !submitData.password) {
|
||||
delete submitData.password
|
||||
}
|
||||
|
||||
let res
|
||||
if (isEdit.value) {
|
||||
console.log(submitData)
|
||||
res = await userApi.updateUser(submitData)
|
||||
} else {
|
||||
res = await userApi.register(submitData)
|
||||
}
|
||||
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(`${isEdit.value ? '更新' : '添加'}成功`)
|
||||
dialogVisible.value = false
|
||||
loadUserList()
|
||||
} else {
|
||||
ElMessage.error(res.message || `${isEdit.value ? '更新' : '添加'}失败`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`${isEdit.value ? '更新' : '添加'}用户出错:`, error)
|
||||
ElMessage.error(error.message || `${isEdit.value ? '更新' : '添加'}失败`)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields()
|
||||
}
|
||||
|
||||
// 重置表单数据
|
||||
Object.keys(form).forEach(key => {
|
||||
form[key] = key === 'id' ? null : ''
|
||||
})
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
const handleDelete = (row) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要删除用户 "${row.username || row.nickname || row.id}" 吗?`,
|
||||
'删除确认',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(async () => {
|
||||
try {
|
||||
const res = await userApi.deleteUser(row.id)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('删除成功')
|
||||
loadUserList()
|
||||
} else {
|
||||
ElMessage.error(res.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除用户出错:', error)
|
||||
ElMessage.error(error.message || '删除失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消删除
|
||||
})
|
||||
}
|
||||
|
||||
// 分页大小变化
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
loadUserList()
|
||||
}
|
||||
|
||||
// 页码变化
|
||||
const handleCurrentChange = (val) => {
|
||||
currentPage.value = val
|
||||
loadUserList()
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (row, column) => {
|
||||
const dateValue = row[column.property]
|
||||
if (!dateValue) return '-'
|
||||
|
||||
try {
|
||||
const date = new Date(dateValue)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
} catch (e) {
|
||||
return dateValue
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
loadUserList()
|
||||
loadRoleList() //
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
.user-list-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.user-form {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
324
src/views/User/Permission.vue
Normal file
324
src/views/User/Permission.vue
Normal file
@ -0,0 +1,324 @@
|
||||
<template>
|
||||
<div class="permission-manage">
|
||||
<div class="permission-tree">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="header">
|
||||
<span>权限管理</span>
|
||||
<el-button type="primary" @click="handleAddPermission(null)">新增权限</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-tree
|
||||
:data="permissionTree"
|
||||
:props="{ label: 'name' }"
|
||||
node-key="id"
|
||||
default-expand-all
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<div class="custom-tree-node">
|
||||
<div>
|
||||
<span>{{ data.name }}</span>
|
||||
<el-tag size="small" class="ml-10">{{ data.code }}</el-tag>
|
||||
<el-tag size="small" type="success" class="ml-10">
|
||||
{{ getTypeText(data.type) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div>
|
||||
<el-button type="primary" size="small" @click.stop="handleAddPermission(data)">
|
||||
添加子权限
|
||||
</el-button>
|
||||
<el-button type="warning" size="small" @click.stop="handleEditPermission(data)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-popconfirm title="确认删除?" @confirm="handleDeletePermission(data.id)">
|
||||
<template #reference>
|
||||
<el-button type="danger" size="small" @click.stop>删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 权限表单对话框 -->
|
||||
<el-dialog v-model="permissionDialogVisible" :title="isEdit ? '编辑权限' : '新增权限'">
|
||||
<el-form :model="permissionForm" label-width="100px" :rules="permissionRules" ref="permissionFormRef">
|
||||
<el-form-item label="上级权限">
|
||||
<el-input v-model="parentPermissionName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="permissionForm.type !== 1" label="权限名称" prop="permissionName">
|
||||
<el-input v-model="permissionForm.permissionName" placeholder="请输入权限名称" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="permissionForm.type !== 1" label="权限编码" prop="permissionCode">
|
||||
<el-input v-model="permissionForm.permissionCode" placeholder="请输入权限编码" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="permissionForm.type === 1" label="目录名称" prop="permissionName">
|
||||
<el-input v-model="permissionForm.permissionName" placeholder="请输入目录名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限类型" prop="type">
|
||||
<el-select v-model="permissionForm.type" placeholder="请选择权限类型" style="width: 100%">
|
||||
<el-option :value="1" label="目录" />
|
||||
<el-option :value="2" label="菜单" />
|
||||
<el-option :value="3" label="接口" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="permissionForm.type === 1 || permissionForm.type === 2" label="路由路径" prop="path">
|
||||
<el-input v-model="permissionForm.path" placeholder="请输入路由路径" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="permissionForm.type === 1 || permissionForm.type === 2" label="组件路径" prop="component">
|
||||
<el-input v-model="permissionForm.component" placeholder="请输入组件路径" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="permissionForm.type === 1 || permissionForm.type === 2" label="图标" prop="icon">
|
||||
<el-input v-model="permissionForm.icon" placeholder="请输入图标" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="permissionForm.sort" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限描述" prop="description">
|
||||
<el-input v-model="permissionForm.description" placeholder="请输入权限描述" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-switch v-model="permissionForm.status" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="permissionDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitPermissionForm">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getPermissionTree, addPermission, updatePermission, deletePermission } from '@/api/permission'
|
||||
|
||||
export default {
|
||||
name: 'PermissionManage',
|
||||
setup() {
|
||||
// 权限树数据
|
||||
const loading = ref(false)
|
||||
const permissionTree = ref([])
|
||||
|
||||
// 权限表单数据
|
||||
const permissionFormRef = ref(null)
|
||||
const permissionDialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const parentPermissionName = ref('')
|
||||
const permissionForm = reactive({
|
||||
id: '',
|
||||
parentId: null,
|
||||
permissionName: '',
|
||||
permissionCode: '',
|
||||
description: '',
|
||||
type: 1,
|
||||
path: '',
|
||||
component: '',
|
||||
icon: '',
|
||||
sort: 0,
|
||||
status: 1
|
||||
})
|
||||
const permissionRules = {
|
||||
permissionName: [{ required: true, message: '请输入权限名称', trigger: 'blur' }],
|
||||
permissionCode: [{ required: true, message: '请输入权限编码', trigger: 'blur', validator: (rule, value, callback) => {
|
||||
if (permissionForm.type === 1) {
|
||||
callback(); // 目录类型不验证编码
|
||||
} else if (!value) {
|
||||
callback(new Error('请输入权限编码'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}}],
|
||||
type: [{ required: true, message: '请选择权限类型', trigger: 'change' }]
|
||||
}
|
||||
|
||||
// 获取权限树
|
||||
const fetchPermissionTree = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getPermissionTree()
|
||||
permissionTree.value = res.data || []
|
||||
} catch (error) {
|
||||
console.error('获取权限树失败', error)
|
||||
ElMessage.error('获取权限树失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取权限类型文本
|
||||
const getTypeText = (type) => {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return '目录'
|
||||
case 2:
|
||||
return '菜单'
|
||||
case 3:
|
||||
return '接口'
|
||||
default:
|
||||
return '未知'
|
||||
}
|
||||
}
|
||||
|
||||
// 新增权限
|
||||
const handleAddPermission = (parentNode) => {
|
||||
isEdit.value = false
|
||||
permissionForm.id = ''
|
||||
permissionForm.parentId = parentNode ? parentNode.id : null
|
||||
permissionForm.permissionName = ''
|
||||
permissionForm.permissionCode = ''
|
||||
permissionForm.description = ''
|
||||
permissionForm.type = 1
|
||||
permissionForm.path = ''
|
||||
permissionForm.component = ''
|
||||
permissionForm.icon = ''
|
||||
permissionForm.sort = 0
|
||||
permissionForm.status = 1
|
||||
permissionForm.isDel = 0
|
||||
|
||||
parentPermissionName.value = parentNode ? parentNode.name : '无(根权限)'
|
||||
|
||||
permissionDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑权限
|
||||
const handleEditPermission = (node) => {
|
||||
isEdit.value = true
|
||||
permissionForm.id = node.id
|
||||
permissionForm.parentId = node.parentId
|
||||
permissionForm.permissionName = node.name
|
||||
permissionForm.permissionCode = node.code
|
||||
permissionForm.description = node.description
|
||||
permissionForm.type = node.type
|
||||
permissionForm.path = node.path || ''
|
||||
permissionForm.component = node.component || ''
|
||||
permissionForm.icon = node.icon || ''
|
||||
permissionForm.sort = node.sort
|
||||
permissionForm.status = node.status
|
||||
|
||||
// 查找父节点名称
|
||||
if (node.parentId) {
|
||||
const findParentName = (nodes, id) => {
|
||||
for (const item of nodes) {
|
||||
if (item.id === id) {
|
||||
return item.name
|
||||
}
|
||||
if (item.children && item.children.length > 0) {
|
||||
const name = findParentName(item.children, id)
|
||||
if (name) return name
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
parentPermissionName.value = findParentName(permissionTree.value, node.parentId) || '未知'
|
||||
} else {
|
||||
parentPermissionName.value = '无(根权限)'
|
||||
}
|
||||
|
||||
permissionDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 提交权限表单
|
||||
const submitPermissionForm = async () => {
|
||||
if (!permissionFormRef.value) return
|
||||
|
||||
await permissionFormRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
// 如果是目录类型,自动设置一个空的编码值
|
||||
if (permissionForm.type === 1) {
|
||||
permissionForm.permissionCode = '';
|
||||
}
|
||||
|
||||
const data = {
|
||||
id: permissionForm.id,
|
||||
parentId: permissionForm.parentId,
|
||||
permissionName: permissionForm.permissionName,
|
||||
permissionCode: permissionForm.permissionCode,
|
||||
description: permissionForm.description,
|
||||
type: permissionForm.type,
|
||||
path: permissionForm.path,
|
||||
component: permissionForm.component,
|
||||
icon: permissionForm.icon,
|
||||
sort: permissionForm.sort,
|
||||
status: permissionForm.status
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
await updatePermission(data)
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
await addPermission(data)
|
||||
ElMessage.success('添加成功')
|
||||
}
|
||||
permissionDialogVisible.value = false
|
||||
fetchPermissionTree()
|
||||
} catch (error) {
|
||||
console.error(isEdit.value ? '更新权限失败' : '添加权限失败', error)
|
||||
ElMessage.error(isEdit.value ? '更新权限失败' : '添加权限失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除权限
|
||||
const handleDeletePermission = async (id) => {
|
||||
try {
|
||||
await deletePermission(id)
|
||||
ElMessage.success('删除成功')
|
||||
fetchPermissionTree()
|
||||
} catch (error) {
|
||||
console.error('删除权限失败', error)
|
||||
ElMessage.error('删除权限失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchPermissionTree()
|
||||
})
|
||||
|
||||
return {
|
||||
loading,
|
||||
permissionTree,
|
||||
permissionFormRef,
|
||||
permissionDialogVisible,
|
||||
isEdit,
|
||||
parentPermissionName,
|
||||
permissionForm,
|
||||
permissionRules,
|
||||
getTypeText,
|
||||
handleAddPermission,
|
||||
handleEditPermission,
|
||||
submitPermissionForm,
|
||||
handleDeletePermission
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.permission-manage {
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.custom-tree-node {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 8px;
|
||||
}
|
||||
.ml-10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
275
src/views/User/Role.vue
Normal file
275
src/views/User/Role.vue
Normal file
@ -0,0 +1,275 @@
|
||||
<template>
|
||||
<div class="role-manage">
|
||||
<div class="role-list">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="header">
|
||||
<span>角色列表</span>
|
||||
<el-button type="primary" @click="handleAddRole">新增角色</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="roleList" style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="roleName" label="角色名称" />
|
||||
<el-table-column prop="roleCode" label="角色编码" />
|
||||
<el-table-column prop="description" label="角色描述" />
|
||||
<el-table-column label="状态">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
|
||||
{{ scope.row.status === 1 ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="280">
|
||||
<template #default="scope">
|
||||
<el-button @click="handleEditRole(scope.row)" type="primary" size="small">编辑</el-button>
|
||||
<el-button @click="handleSetPermission(scope.row)" type="warning" size="small">权限设置</el-button>
|
||||
<el-popconfirm title="确认删除?" @confirm="handleDeleteRole(scope.row.id)">
|
||||
<template #reference>
|
||||
<el-button type="danger" size="small">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 角色表单对话框 -->
|
||||
<el-dialog v-model="roleDialogVisible" :title="isEdit ? '编辑角色' : '新增角色'">
|
||||
<el-form :model="roleForm" label-width="80px" :rules="roleRules" ref="roleFormRef">
|
||||
<el-form-item label="角色名称" prop="roleName">
|
||||
<el-input v-model="roleForm.roleName" placeholder="请输入角色名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色编码" prop="roleCode">
|
||||
<el-input v-model="roleForm.roleCode" placeholder="请输入角色编码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色描述" prop="description">
|
||||
<el-input v-model="roleForm.description" placeholder="请输入角色描述" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-switch v-model="roleForm.status" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="roleDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitRoleForm">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 权限设置对话框 -->
|
||||
<el-dialog v-model="permissionDialogVisible" title="权限设置">
|
||||
<el-tree
|
||||
ref="permissionTree"
|
||||
:data="permissionTreeData"
|
||||
show-checkbox
|
||||
node-key="id"
|
||||
:props="{ label: 'name' }"
|
||||
/>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="permissionDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveRolePermissions">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getRoleList, addRole, updateRole, deleteRole, getRolePermissions, updateRolePermissions } from '@/api/role'
|
||||
import { getPermissionTree } from '@/api/permission'
|
||||
|
||||
export default {
|
||||
name: 'RoleManage',
|
||||
setup() {
|
||||
// 角色列表数据
|
||||
const loading = ref(false)
|
||||
const roleList = ref([])
|
||||
|
||||
// 角色表单数据
|
||||
const roleFormRef = ref(null)
|
||||
const roleDialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const roleForm = reactive({
|
||||
id: '',
|
||||
roleName: '',
|
||||
roleCode: '',
|
||||
description: '',
|
||||
status: 1
|
||||
})
|
||||
const roleRules = {
|
||||
roleName: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
|
||||
roleCode: [{ required: true, message: '请输入角色编码', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
// 权限设置数据
|
||||
const permissionTree = ref(null)
|
||||
const permissionTreeData = ref([])
|
||||
const permissionDialogVisible = ref(false)
|
||||
const currentRoleId = ref('')
|
||||
|
||||
// 获取角色列表
|
||||
const fetchRoleList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getRoleList()
|
||||
roleList.value = res.data || []
|
||||
} catch (error) {
|
||||
console.error('获取角色列表失败', error)
|
||||
ElMessage.error('获取角色列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取权限树
|
||||
const fetchPermissionTree = async () => {
|
||||
try {
|
||||
const res = await getPermissionTree()
|
||||
permissionTreeData.value = res.data || []
|
||||
} catch (error) {
|
||||
console.error('获取权限树失败', error)
|
||||
ElMessage.error('获取权限树失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取角色权限
|
||||
const fetchRolePermissions = async (roleId) => {
|
||||
try {
|
||||
const res = await getRolePermissions(roleId)
|
||||
if (permissionTree.value) {
|
||||
permissionTree.value.setCheckedKeys(res.data || [])
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取角色权限失败', error)
|
||||
ElMessage.error('获取角色权限失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 新增角色
|
||||
const handleAddRole = () => {
|
||||
isEdit.value = false
|
||||
roleForm.id = ''
|
||||
roleForm.roleName = ''
|
||||
roleForm.roleCode = ''
|
||||
roleForm.description = ''
|
||||
roleForm.status = 1
|
||||
roleDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑角色
|
||||
const handleEditRole = (row) => {
|
||||
isEdit.value = true
|
||||
roleForm.id = row.id
|
||||
roleForm.roleName = row.roleName
|
||||
roleForm.roleCode = row.roleCode
|
||||
roleForm.description = row.description
|
||||
roleForm.status = row.status
|
||||
roleDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 提交角色表单
|
||||
const submitRoleForm = async () => {
|
||||
if (!roleFormRef.value) return
|
||||
|
||||
await roleFormRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
await updateRole(roleForm)
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
await addRole(roleForm)
|
||||
ElMessage.success('添加成功')
|
||||
}
|
||||
roleDialogVisible.value = false
|
||||
fetchRoleList()
|
||||
} catch (error) {
|
||||
console.error(isEdit.value ? '更新角色失败' : '添加角色失败', error)
|
||||
ElMessage.error(isEdit.value ? '更新角色失败' : '添加角色失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除角色
|
||||
const handleDeleteRole = async (id) => {
|
||||
try {
|
||||
await deleteRole(id)
|
||||
ElMessage.success('删除成功')
|
||||
fetchRoleList()
|
||||
} catch (error) {
|
||||
console.error('删除角色失败', error)
|
||||
ElMessage.error('删除角色失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 设置角色权限
|
||||
const handleSetPermission = async (row) => {
|
||||
currentRoleId.value = row.id
|
||||
await fetchPermissionTree()
|
||||
permissionDialogVisible.value = true
|
||||
await fetchRolePermissions(row.id)
|
||||
}
|
||||
|
||||
// 保存角色权限
|
||||
const saveRolePermissions = async () => {
|
||||
if (!permissionTree.value || !currentRoleId.value) return
|
||||
|
||||
try {
|
||||
const checkedKeys = permissionTree.value.getCheckedKeys()
|
||||
const halfCheckedKeys = permissionTree.value.getHalfCheckedKeys()
|
||||
const allKeys = [...checkedKeys, ...halfCheckedKeys]
|
||||
|
||||
await updateRolePermissions(currentRoleId.value, allKeys)
|
||||
ElMessage.success('权限设置成功')
|
||||
permissionDialogVisible.value = false
|
||||
} catch (error) {
|
||||
console.error('权限设置失败', error)
|
||||
ElMessage.error('权限设置失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchRoleList()
|
||||
})
|
||||
|
||||
return {
|
||||
loading,
|
||||
roleList,
|
||||
roleFormRef,
|
||||
roleDialogVisible,
|
||||
isEdit,
|
||||
roleForm,
|
||||
roleRules,
|
||||
permissionTree,
|
||||
permissionTreeData,
|
||||
permissionDialogVisible,
|
||||
handleAddRole,
|
||||
handleEditRole,
|
||||
submitRoleForm,
|
||||
handleDeleteRole,
|
||||
handleSetPermission,
|
||||
saveRolePermissions
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.role-manage {
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.dialog-footer {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
208
src/views/User/UserRole.vue
Normal file
208
src/views/User/UserRole.vue
Normal file
@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<div class="user-role-manage">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="header">
|
||||
<span>用户角色管理</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :inline="true" class="search-form">
|
||||
<el-form-item>
|
||||
<el-input v-model="searchUsername" placeholder="请输入用户名" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="userList" style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="username" label="用户名" />
|
||||
<el-table-column prop="phone" label="手机号" />
|
||||
<el-table-column prop="email" label="邮箱" />
|
||||
<el-table-column label="是否超级管理员">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.isSuperAdmin === 1 ? 'danger' : 'info'">
|
||||
{{ scope.row.isSuperAdmin === 1 ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<el-button @click="handleSetUserRole(scope.row)" type="primary" size="small">
|
||||
分配角色
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-if="total > 0"
|
||||
class="pagination"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<!-- 角色分配对话框 -->
|
||||
<el-dialog v-model="roleDialogVisible" title="分配角色">
|
||||
<div v-if="currentUser">
|
||||
<p>当前用户:{{ currentUser.username }}</p>
|
||||
<el-checkbox-group v-model="selectedRoleIds">
|
||||
<el-checkbox v-for="role in roleList" :key="role.id" :label="role.id">
|
||||
{{ role.roleName }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="roleDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveUserRoles">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getRoleList } from '@/api/role'
|
||||
import { getAdminRoles, updateAdminRoles, getUserList } from '@/api/user'
|
||||
|
||||
export default {
|
||||
name: 'UserRoleManage',
|
||||
setup() {
|
||||
// 用户列表数据
|
||||
const loading = ref(false)
|
||||
const userList = ref([])
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const searchUsername = ref('')
|
||||
|
||||
// 角色数据
|
||||
const roleList = ref([])
|
||||
const roleDialogVisible = ref(false)
|
||||
const currentUser = ref(null)
|
||||
const selectedRoleIds = ref([])
|
||||
|
||||
// 获取用户列表
|
||||
const fetchUserList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getUserList({
|
||||
page: currentPage.value,
|
||||
size: pageSize.value,
|
||||
username: searchUsername.value
|
||||
})
|
||||
userList.value = res.data || []
|
||||
total.value = res.total || 0
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败', error)
|
||||
ElMessage.error('获取用户列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取角色列表
|
||||
const fetchRoleList = async () => {
|
||||
try {
|
||||
const res = await getRoleList()
|
||||
roleList.value = res.data || []
|
||||
} catch (error) {
|
||||
console.error('获取角色列表失败', error)
|
||||
ElMessage.error('获取角色列表失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户角色
|
||||
const fetchUserRoles = async (adminId) => {
|
||||
try {
|
||||
const res = await getAdminRoles(adminId)
|
||||
selectedRoleIds.value = res.data || []
|
||||
} catch (error) {
|
||||
console.error('获取用户角色失败', error)
|
||||
ElMessage.error('获取用户角色失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
currentPage.value = 1
|
||||
fetchUserList()
|
||||
}
|
||||
|
||||
// 页码变化
|
||||
const handleCurrentChange = (page) => {
|
||||
currentPage.value = page
|
||||
fetchUserList()
|
||||
}
|
||||
|
||||
// 设置用户角色
|
||||
const handleSetUserRole = async (user) => {
|
||||
currentUser.value = user
|
||||
await fetchRoleList()
|
||||
await fetchUserRoles(user.id)
|
||||
roleDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 保存用户角色
|
||||
const saveUserRoles = async () => {
|
||||
if (!currentUser.value) return
|
||||
|
||||
try {
|
||||
await updateAdminRoles(currentUser.value.id, selectedRoleIds.value)
|
||||
ElMessage.success('角色分配成功')
|
||||
roleDialogVisible.value = false
|
||||
} catch (error) {
|
||||
console.error('角色分配失败', error)
|
||||
ElMessage.error('角色分配失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchUserList()
|
||||
})
|
||||
|
||||
return {
|
||||
loading,
|
||||
userList,
|
||||
total,
|
||||
currentPage,
|
||||
pageSize,
|
||||
searchUsername,
|
||||
roleList,
|
||||
roleDialogVisible,
|
||||
currentUser,
|
||||
selectedRoleIds,
|
||||
handleSearch,
|
||||
handleCurrentChange,
|
||||
handleSetUserRole,
|
||||
saveUserRoles
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-role-manage {
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.search-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
676
src/views/Warehouse/Depot/List.vue
Normal file
676
src/views/Warehouse/Depot/List.vue
Normal file
@ -0,0 +1,676 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-area">
|
||||
<div class="search-row">
|
||||
<div class="search-item">
|
||||
<span class="search-label">一级货区编号</span>
|
||||
<el-input v-model="searchForm.code" placeholder="请输入一级货区编号" clearable></el-input>
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<span class="search-label">一级货区名称</span>
|
||||
<el-input v-model="searchForm.name" placeholder="请输入名称" clearable></el-input>
|
||||
</div>
|
||||
<div class="search-item btn-item">
|
||||
<el-button type="primary" @click="handleSearch" :icon="Search">搜索</el-button>
|
||||
<el-button @click="handleReset" :icon="Refresh">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-bar">
|
||||
<div class="action-left">
|
||||
<el-button type="primary" @click="handleAdd" :icon="Plus">创建一级货区</el-button>
|
||||
<el-button @click="handleRules" :icon="Setting">仓库规则设置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 树形表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
row-key="id"
|
||||
border
|
||||
lazy
|
||||
:load="loadNode"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
@selection-change="handleSelectionChange"
|
||||
empty-text="暂无数据"
|
||||
stripe
|
||||
highlight-current-row
|
||||
:header-cell-style="{ backgroundColor: '#f5f7fa', color: '#606266', textAlign: 'center' }"
|
||||
:row-class-name="tableRowClassName"
|
||||
height="500"
|
||||
max-height="500"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="货区名称">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.name }}{{ scope.row.unit ? ' (' + scope.row.unit + ')' : '' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="code" label="货区编号" />
|
||||
<el-table-column prop="categoryNumber" label="书品类别" />
|
||||
<el-table-column prop="inventory" label="库存数量" />
|
||||
<el-table-column prop="userId" label="用户" />
|
||||
<el-table-column prop="status" label="货区状态" >
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === '0' || scope.row.status === 0 ? 'success' : 'danger'">
|
||||
{{ scope.row.status === '0' || scope.row.status === 0 ? '正常' : '异常(未选择运费模板)' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="运费模板选择" width="150">
|
||||
<template #default="scope">
|
||||
<div class="template-select">
|
||||
<span v-if="scope.row.templateName">{{ scope.row.templateName }}</span>
|
||||
<span v-else class="template-link">
|
||||
<el-link type="primary" @click="selectTemplate(scope.row)">未选择</el-link>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<!-- 只在一级节点显示这些操作按钮 -->
|
||||
<template v-if="!scope.row.level || scope.row.level === 1">
|
||||
<el-button type="primary" link :icon="Plus" @click="handleAddFreight(scope.row)"></el-button>
|
||||
<el-button type="primary" link :icon="Edit" @click="handleEdit(scope.row)"></el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
link
|
||||
:icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-if="scope.row.id && !scope.row.noData"
|
||||
></el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 添加/编辑弹窗 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '创建货区' : '编辑货区'"
|
||||
width="500px"
|
||||
>
|
||||
<el-form :model="form" label-width="120px" :rules="rules" ref="formRef">
|
||||
<el-form-item label="货区名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入货区名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="货区编号" prop="code">
|
||||
<el-input v-model="form.code" placeholder="请输入货区编号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="书品类别" prop="categoryNumber">
|
||||
<el-input v-model="form.categoryNumber" placeholder="请输入书品类别"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户" prop="userId">
|
||||
<el-input v-model="form.userId" placeholder="请输入用户ID"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="货区状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :label="0">正常</el-radio>
|
||||
<el-radio :label="1">异常</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 选择运费模板弹窗 -->
|
||||
<el-dialog v-model="templateDialogVisible" title="选择运费模板" width="500px">
|
||||
<el-form :model="templateForm" label-width="120px">
|
||||
<el-form-item label="运费模板">
|
||||
<el-select v-model="templateForm.templateId" placeholder="请选择运费模板">
|
||||
<el-option v-for="item in templateOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="templateDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitTemplateForm">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, Refresh, Plus, Edit, Delete, Setting } from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { depotApi } from '@/api'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
// 不设置默认值
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref([])
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const multipleSelection = ref([])
|
||||
|
||||
// 弹窗表单
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref('add')
|
||||
const formRef = ref(null)
|
||||
const form = reactive({
|
||||
id: null,
|
||||
name: '',
|
||||
code: '',
|
||||
categoryNumber: '',
|
||||
userId: '',
|
||||
status: 0
|
||||
})
|
||||
|
||||
// 运费模板相关
|
||||
const templateDialogVisible = ref(false)
|
||||
const templateForm = reactive({
|
||||
depotId: null,
|
||||
templateId: null
|
||||
})
|
||||
const templateOptions = ref([
|
||||
{ value: '1', label: '南宁' },
|
||||
{ value: '2', label: '南门坡街' }
|
||||
])
|
||||
const currentDepot = ref(null)
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入货区名称', trigger: 'blur' }],
|
||||
code: [{ required: true, message: '请输入货区编号', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 选择运费模板
|
||||
const selectTemplate = (row) => {
|
||||
currentDepot.value = row
|
||||
templateForm.depotId = row.id
|
||||
templateForm.templateId = null
|
||||
templateDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 提交运费模板选择
|
||||
const submitTemplateForm = async () => {
|
||||
try {
|
||||
// 这里应该调用API保存运费模板选择
|
||||
// const res = await depotApi.setDepotTemplate(templateForm)
|
||||
|
||||
// 模拟API调用成功
|
||||
const selectedTemplate = templateOptions.value.find(item => item.value === templateForm.templateId)
|
||||
if (currentDepot.value && selectedTemplate) {
|
||||
currentDepot.value.templateName = selectedTemplate.label
|
||||
ElMessage.success('运费模板设置成功')
|
||||
templateDialogVisible.value = false
|
||||
} else {
|
||||
ElMessage.error('请选择运费模板')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('设置运费模板失败', error)
|
||||
ElMessage.error('设置运费模板失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化加载数据
|
||||
onMounted(() => {
|
||||
fetchDepotData()
|
||||
})
|
||||
|
||||
// 获取货区数据
|
||||
const fetchDepotData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
pageNum: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
...searchForm
|
||||
}
|
||||
const res = await depotApi.getDepotList(params)
|
||||
if (res.code === 200) {
|
||||
const depotList = res.data.list || []
|
||||
total.value = res.data.total || 0
|
||||
|
||||
// 为每个一级货区添加hasChildren标记和level属性
|
||||
depotList.forEach(depot => {
|
||||
depot.hasChildren = true // 默认都有子节点
|
||||
depot.level = 1 // 设置为一级节点
|
||||
})
|
||||
|
||||
tableData.value = depotList
|
||||
} else {
|
||||
ElMessage.error(res.message || '获取货区数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取货区数据失败', error)
|
||||
ElMessage.error('获取货区数据失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
currentPage.value = 1
|
||||
fetchDepotData()
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
const handleReset = () => {
|
||||
Object.keys(searchForm).forEach(key => {
|
||||
searchForm[key] = undefined;
|
||||
})
|
||||
currentPage.value = 1
|
||||
fetchDepotData()
|
||||
}
|
||||
|
||||
// 添加货区
|
||||
const handleAdd = () => {
|
||||
dialogType.value = 'add'
|
||||
Object.keys(form).forEach(key => {
|
||||
form[key] = key === 'status' ? 0 : ''
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑货区
|
||||
const handleEdit = (row) => {
|
||||
dialogType.value = 'edit'
|
||||
Object.keys(form).forEach(key => {
|
||||
form[key] = row[key]
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 删除货区
|
||||
const handleDelete = (row) => {
|
||||
if (!row.id) {
|
||||
ElMessage.error('无效的ID,无法删除')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm('确认删除该货区?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
console.log(row)
|
||||
// 确保ID不为空
|
||||
if (!row.id || row.id === 'null' || row.noData) {
|
||||
ElMessage.error('无效的ID,无法删除')
|
||||
return
|
||||
}
|
||||
|
||||
const res = await depotApi.deleteDepot(row.id)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('删除成功')
|
||||
fetchDepotData()
|
||||
} else {
|
||||
ElMessage.error(res.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败', error)
|
||||
ElMessage.error('删除失败: ' + (error.response?.data?.message || error.message || '未知错误'))
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 添加运费模板
|
||||
const handleAddFreight = (row) => {
|
||||
ElMessage.info('添加运费模板功能待实现')
|
||||
}
|
||||
|
||||
// 仓库规则设置
|
||||
const handleRules = () => {
|
||||
// 创建一个a标签用于下载
|
||||
const link = document.createElement('a')
|
||||
link.href = '/templates/depotRule.doc'
|
||||
link.download = '仓库规则设置.doc'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
let res
|
||||
if (dialogType.value === 'add') {
|
||||
res = await depotApi.createDepot(form)
|
||||
} else {
|
||||
res = await depotApi.updateDepot(form)
|
||||
}
|
||||
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(dialogType.value === 'add' ? '创建成功' : '更新成功')
|
||||
dialogVisible.value = false
|
||||
fetchDepotData()
|
||||
} else {
|
||||
ElMessage.error(res.message || (dialogType.value === 'add' ? '创建失败' : '更新失败'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(dialogType.value === 'add' ? '创建失败' : '更新失败', error)
|
||||
ElMessage.error(dialogType.value === 'add' ? '创建失败' : '更新失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理多选
|
||||
const handleSelectionChange = (selection) => {
|
||||
multipleSelection.value = selection
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (size) => {
|
||||
pageSize.value = size
|
||||
fetchDepotData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (page) => {
|
||||
currentPage.value = page
|
||||
fetchDepotData()
|
||||
}
|
||||
|
||||
// 加载节点数据
|
||||
const loadNode = async (row, treeNode, resolve) => {
|
||||
if (row.level === 1) {
|
||||
// 加载二级货架数据
|
||||
try {
|
||||
const res = await depotApi.getShelvesByDepotId(row.id)
|
||||
if (res.code === 200) {
|
||||
const shelves = res.data || []
|
||||
if (shelves.length === 0) {
|
||||
// 如果没有二级数据,返回一个特殊的空数据提示
|
||||
resolve([{ id: `empty-${row.id}`, name: '暂无数据', noData: true, hasChildren: false }])
|
||||
} else {
|
||||
shelves.forEach(shelf => {
|
||||
shelf.hasChildren = true
|
||||
shelf.level = 2
|
||||
})
|
||||
resolve(shelves)
|
||||
}
|
||||
} else {
|
||||
resolve([{ id: `empty-${row.id}`, name: '暂无数据', noData: true, hasChildren: false }])
|
||||
ElMessage.error(res.message || '获取二级货架数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取二级货架数据失败', error)
|
||||
ElMessage.error('获取二级货架数据失败')
|
||||
resolve([{ id: `empty-${row.id}`, name: '暂无数据', noData: true, hasChildren: false }])
|
||||
}
|
||||
} else if (row.level === 2) {
|
||||
// 加载三级货位数据
|
||||
try {
|
||||
const res = await depotApi.getFreightByShelveId(row.id)
|
||||
if (res.code === 200) {
|
||||
const freights = res.data || []
|
||||
if (freights.length === 0) {
|
||||
// 如果没有三级数据,返回一个特殊的空数据提示
|
||||
resolve([{ id: `empty-${row.id}`, name: '暂无数据', noData: true, hasChildren: false }])
|
||||
} else {
|
||||
freights.forEach(freight => {
|
||||
freight.level = 3
|
||||
freight.hasChildren = false
|
||||
})
|
||||
resolve(freights)
|
||||
}
|
||||
} else {
|
||||
resolve([{ id: `empty-${row.id}`, name: '暂无数据', noData: true, hasChildren: false }])
|
||||
ElMessage.error(res.message || '获取三级货位数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取三级货位数据失败', error)
|
||||
ElMessage.error('获取三级货位数据失败')
|
||||
resolve([{ id: `empty-${row.id}`, name: '暂无数据', noData: true, hasChildren: false }])
|
||||
}
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
}
|
||||
|
||||
// 表格行样式
|
||||
const tableRowClassName = ({ row }) => {
|
||||
if (row.noData) {
|
||||
return 'empty-row'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 搜索区域样式优化 */
|
||||
.search-area {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 15px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.search-label {
|
||||
width: 90px;
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-item .el-input {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.search-item .el-select {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.btn-item {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 操作按钮区域优化 */
|
||||
.action-bar {
|
||||
margin-bottom: 15px;
|
||||
background-color: #fff;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.action-left {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
background-color: #fff;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.template-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.template-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-table__header-wrapper) {
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper::-webkit-scrollbar) {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper::-webkit-scrollbar-thumb) {
|
||||
border-radius: 3px;
|
||||
background: #c0c4cc;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper::-webkit-scrollbar-track) {
|
||||
border-radius: 3px;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background-color: #f5f7fa;
|
||||
color: #606266;
|
||||
text-align: center;
|
||||
height: 50px;
|
||||
padding: 8px 0;
|
||||
font-weight: 500;
|
||||
border-bottom: 2px solid #EBEEF5;
|
||||
}
|
||||
|
||||
:deep(.el-table td) {
|
||||
padding: 12px 8px;
|
||||
height: 55px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
:deep(.el-table__row:hover td) {
|
||||
background-color: #f0f7ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__row.empty-row td) {
|
||||
color: #909399;
|
||||
font-style: italic;
|
||||
background-color: #f9f9f9;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
:deep(.el-table__expand-icon) {
|
||||
color: #409EFF;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
:deep(.el-table__indent) {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
:deep(.el-table .cell) {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* 表格中的标签样式优化 */
|
||||
:deep(.el-tag) {
|
||||
border-radius: 4px;
|
||||
padding: 0 8px;
|
||||
height: 28px;
|
||||
line-height: 26px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.el-tag--success) {
|
||||
background-color: #f0f9eb;
|
||||
border-color: #e1f3d8;
|
||||
}
|
||||
|
||||
:deep(.el-tag--danger) {
|
||||
background-color: #fef0f0;
|
||||
border-color: #fde2e2;
|
||||
}
|
||||
|
||||
/* 链接按钮样式优化 */
|
||||
:deep(.el-button.is-link) {
|
||||
padding: 4px 8px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
:deep(.el-button.is-link:hover) {
|
||||
background-color: #ecf5ff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 对话框样式 */
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 暂无数据的样式 */
|
||||
:deep(.el-table__empty-text) {
|
||||
padding: 30px 0;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
1457
src/views/baseInfo/index.vue
Normal file
1457
src/views/baseInfo/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user