新增readme
This commit is contained in:
315
README.md
315
README.md
@@ -1,5 +1,314 @@
|
|||||||
# Vue 3 + Vite
|
# 🍦 蜜雪冰城管理系统 (Mixue Management System)
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
**蜜雪冰城管理系统**是一个面向企业内部的 Web 管理后台,基于 Vue 3 + Element Plus 构建。用于管理客户、合同、售后、产品、供应商、员工及系统用户等核心业务模块,为蜜雪冰城品牌运营提供一体化管理支持。
|
||||||
|
|
||||||
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
|
> 前端项目地址:`backmanagerweb`
|
||||||
|
> 后端项目地址:`backmanager-server`(Express + MySQL)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
- [功能概览](#功能概览)
|
||||||
|
- [技术栈](#技术栈)
|
||||||
|
- [快速开始](#快速开始)
|
||||||
|
- [项目结构](#项目结构)
|
||||||
|
- [页面与模块说明](#页面与模块说明)
|
||||||
|
- [权限体系](#权限体系)
|
||||||
|
- [与后端 API 的关系](#与后端-api-的关系)
|
||||||
|
- [开发命令](#开发命令)
|
||||||
|
- [构建部署](#构建部署)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 功能概览
|
||||||
|
|
||||||
|
| 模块 | 说明 | 权限标识 |
|
||||||
|
|------|------|---------|
|
||||||
|
| 🏠 **首页** | 欢迎页,展示蜜雪冰城品牌视频与雪王简介 | 登录即可访问 |
|
||||||
|
| 👥 **客户管理** | 客户信息 CRUD、客户列表搜索、详情查看 | `customer:read` / `customer:create` / `customer:update` / `customer:delete` |
|
||||||
|
| 📄 **合同管理** | 合同 CRUD,关联客户,支持金额与签约日期管理 | `contract:read` / `contract:*` |
|
||||||
|
| 🔧 **售后管理** | 售后服务记录管理 | `after_sale:read` / `after_sale:*` |
|
||||||
|
| 🧋 **产品管理** | 产品(饮品)信息管理 | `product:read` / `product:*` |
|
||||||
|
| 📦 **供应商管理** | 供应商信息管理 | `supplier:read` / `supplier:*` |
|
||||||
|
| 👤 **员工管理** | 企业员工信息(花名册)管理,含薪资可见范围控制 | `employee:read` / `employee:create` / `employee:update` / `employee:delete` |
|
||||||
|
| 🔐 **用户管理** | 系统登录用户管理(管理员专有模块) | `user:manage` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
| 技术 | 版本 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| **Vue 3** | ^3.5 | 前端框架(Composition API + `<script setup>`) |
|
||||||
|
| **Vite** | ^8.0 | 构建工具 |
|
||||||
|
| **Element Plus** | ^2.14 | UI 组件库 |
|
||||||
|
| **@element-plus/icons-vue** | ^2.3 | 图标库 |
|
||||||
|
| **Vue Router 4** | ^4.x | 前端路由 |
|
||||||
|
| **Axios** | ^1.18 | HTTP 请求库 |
|
||||||
|
| **element-china-area-data** | ^5.0 | 中国省市区数据(合同模块使用) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 前置条件
|
||||||
|
|
||||||
|
- Node.js >= 18
|
||||||
|
- 后端项目 `backmanager-server` 已启动运行(默认端口 3000)
|
||||||
|
|
||||||
|
### 安装与运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 克隆项目
|
||||||
|
git clone <repo-url>
|
||||||
|
cd backmanagerweb
|
||||||
|
|
||||||
|
# 2. 安装依赖
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 3. 启动开发服务器(默认端口 5173)
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
浏览器打开 `http://localhost:5173` 即可访问。
|
||||||
|
|
||||||
|
> 开发环境下,`/api` 请求会自动代理到 `http://localhost:3000`(参见 `vite.config.js`)。
|
||||||
|
|
||||||
|
### 登录
|
||||||
|
|
||||||
|
项目默认对接后端认证系统,使用后端预置的管理员账号登录:
|
||||||
|
|
||||||
|
| 用户名 | 密码 | 角色 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `admin` | 请联系管理员 | 信息技术部 · 超管 |
|
||||||
|
|
||||||
|
具体账号信息由后端 `db.js` 中的种子数据定义。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
backmanagerweb/
|
||||||
|
├── public/ # 静态资源
|
||||||
|
│ ├── logo.png # 系统 Logo
|
||||||
|
│ ├── mixue.png # 雪王头像(首页)
|
||||||
|
│ ├── 首页顶图.mp4 # 首页视频横幅
|
||||||
|
│ └── 登陆界面背景.mp4 # 登录页背景视频
|
||||||
|
├── src/
|
||||||
|
│ ├── api/ # API 请求层
|
||||||
|
│ │ ├── request.js # Axios 实例与响应拦截器
|
||||||
|
│ │ ├── employee.js # 员工相关 API
|
||||||
|
│ │ ├── user.js # 用户相关 API
|
||||||
|
│ │ ├── customer.js # 客户相关 API
|
||||||
|
│ │ ├── contract.js # 合同相关 API
|
||||||
|
│ │ ├── service.js # 售后相关 API
|
||||||
|
│ │ ├── products.js # 产品相关 API
|
||||||
|
│ │ ├── suppliers.js # 供应商相关 API
|
||||||
|
│ │ └── mock/ # Mock 数据(开发参考)
|
||||||
|
│ ├── components/ # 页面组件
|
||||||
|
│ │ ├── Login.vue # 登录页
|
||||||
|
│ │ ├── panel.vue # 主布局(顶栏 + 侧栏 + 内容区)
|
||||||
|
│ │ ├── home.vue # 首页
|
||||||
|
│ │ ├── employee.vue # 员工管理
|
||||||
|
│ │ ├── user.vue # 用户管理
|
||||||
|
│ │ ├── customer.vue # 客户管理
|
||||||
|
│ │ ├── contract.vue # 合同管理
|
||||||
|
│ │ ├── service.vue # 售后管理
|
||||||
|
│ │ ├── products.vue # 产品管理
|
||||||
|
│ │ └── supplier.vue # 供应商管理
|
||||||
|
│ ├── constants/ # 常量定义
|
||||||
|
│ │ └── departments.js # 部门列表常量
|
||||||
|
│ ├── store/
|
||||||
|
│ │ └── user.js # 用户状态管理(登录/登出/权限检查)
|
||||||
|
│ ├── utils/
|
||||||
|
│ │ └── date.js # 日期格式化工具
|
||||||
|
│ ├── router.js # 路由配置
|
||||||
|
│ ├── main.js # 应用入口
|
||||||
|
│ ├── App.vue # 根组件
|
||||||
|
│ └── style.css # 全局样式
|
||||||
|
├── index.html # HTML 入口
|
||||||
|
├── vite.config.js # Vite 配置
|
||||||
|
├── package.json
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 页面与模块说明
|
||||||
|
|
||||||
|
### 登录页 (`Login.vue`)
|
||||||
|
|
||||||
|
- 背景播放品牌视频,半透明遮罩,右侧展示登录卡片
|
||||||
|
- 支持用户名/密码表单验证
|
||||||
|
- "记住我" 复选框(当前为前端状态,可扩展持久化)
|
||||||
|
- 登录成功后跳转到 `/panel/home`
|
||||||
|
|
||||||
|
### 主布局 (`panel.vue`)
|
||||||
|
|
||||||
|
- **顶栏**:Logo + 系统名称 + 当前用户信息(姓名 / 部门)+ 退出登录按钮
|
||||||
|
- **侧栏**:根据当前用户的权限动态展示可访问的菜单项(通过 `hasPermission` 控制)
|
||||||
|
- **内容区**:嵌套 `<router-view>` 渲染子页面
|
||||||
|
- 侧栏可收缩
|
||||||
|
|
||||||
|
### 首页 (`home.vue`)
|
||||||
|
|
||||||
|
- 品牌视频横幅 + 雪王简介卡片
|
||||||
|
- 展示雪王的基本信息(生日、性格、职位等)
|
||||||
|
|
||||||
|
### 员工管理 (`employee.vue`)
|
||||||
|
|
||||||
|
- **列表**:支持按 ID、姓名、部门搜索,按在职/离职状态筛选
|
||||||
|
- **分页**:服务端分页(向后端发送 `page`/`pageSize` 参数)
|
||||||
|
- **CRUD**:新增、编辑、删除员工信息
|
||||||
|
- **薪资可见范围**:仅"总经理办公室"和"财务部"用户可见薪资字段
|
||||||
|
- **创建用户联动**:新增员工后可选择同步创建系统用户
|
||||||
|
- **部门**:部门列表与后端数据库 6 个部门保持一致
|
||||||
|
|
||||||
|
### 用户管理 (`user.vue`)
|
||||||
|
|
||||||
|
- **权限**:仅 `user:manage` 权限的用户(信息技术部管理员)可访问
|
||||||
|
- **列表**:支持按用户名、真实姓名、ID 搜索,按部门/状态筛选
|
||||||
|
- **分页**:服务端分页
|
||||||
|
- **CRUD**:系统用户的创建、编辑、启用/禁用、删除
|
||||||
|
- **关联员工**:创建用户时可关联已有员工(员工下拉列表使用 `/api/employees/simple` 接口,仅展示在职员工基本信息)
|
||||||
|
- **安全保护**:后端禁止删除自身账号或最后一个超级管理员
|
||||||
|
|
||||||
|
### 其它模块
|
||||||
|
|
||||||
|
| 模块 | 关键特性 |
|
||||||
|
|------|---------|
|
||||||
|
| **客户管理** | 客户信息维护,支持按名称/联系方式搜索 |
|
||||||
|
| **合同管理** | 合同信息管理,关联客户,支持省市区选择 |
|
||||||
|
| **售后管理** | 售后服务记录管理 |
|
||||||
|
| **产品管理** | 产品(饮品)信息管理 |
|
||||||
|
| **供应商管理** | 供应商基本信息管理 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 权限体系
|
||||||
|
|
||||||
|
### 权限模型
|
||||||
|
|
||||||
|
系统采用**基于资源+操作的权限模型**,每个权限表示为 `resource:action` 格式的字符串。用户的权限列表在登录时从后端获取,存储在 Vue Store 中。
|
||||||
|
|
||||||
|
### 资源与操作
|
||||||
|
|
||||||
|
| 资源 | 操作 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `customer` | `read` / `create` / `update` / `delete` | 客户管理 |
|
||||||
|
| `contract` | `read` / `create` / `update` / `delete` | 合同管理 |
|
||||||
|
| `after_sale` | `read` / `create` / `update` / `delete` | 售后管理 |
|
||||||
|
| `product` | `read` / `create` / `update` / `delete` | 产品管理 |
|
||||||
|
| `supplier` | `read` / `create` / `update` / `delete` | 供应商管理 |
|
||||||
|
| `employee` | `read` / `create` / `update` / `delete` | 员工管理 |
|
||||||
|
| `user` | `manage` | 用户管理(仅超管角色) |
|
||||||
|
|
||||||
|
### 权限检查
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { hasPermission } from '../store/user'
|
||||||
|
|
||||||
|
// 检查当前用户是否拥有某个权限
|
||||||
|
hasPermission('employee', 'create') // → true/false
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据范围
|
||||||
|
|
||||||
|
员工列表受**数据范围(data scope)** 控制:
|
||||||
|
- **信息技术部、总经理办公室**:查看全部员工
|
||||||
|
- **招商部、运营部、采购部**:仅查看本部门员工
|
||||||
|
- **财务部**:查看全部员工(同时可查看薪资)
|
||||||
|
|
||||||
|
### 菜单可见性
|
||||||
|
|
||||||
|
侧栏菜单项根据当前用户的权限动态渲染(在 `panel.vue` 中通过 `v-if="checkPermission(...)"` 控制)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与后端 API 的关系
|
||||||
|
|
||||||
|
前端通过 Axios 请求后端 RESTful API,所有请求以 `/api` 为前缀。
|
||||||
|
|
||||||
|
| 端点 | 方法 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `/api/auth/login` | POST | 登录认证 |
|
||||||
|
| `/api/auth/userinfo` | GET | 获取当前用户信息与权限 |
|
||||||
|
| `/api/employees` | GET/POST/PUT/DELETE | 员工 CRUD |
|
||||||
|
| `/api/employees/simple` | GET | 简易员工列表(下拉专用,仅在职) |
|
||||||
|
| `/api/users` | GET/POST/PUT/DELETE | 用户 CRUD |
|
||||||
|
| `/api/user/list` | GET | 简易用户列表(下拉专用) |
|
||||||
|
| `/api/customers` | GET/POST/PUT/DELETE | 客户 CRUD |
|
||||||
|
| `/api/contracts` | GET/POST/PUT/DELETE | 合同 CRUD |
|
||||||
|
| `/api/after-sales` | GET/POST/PUT/DELETE | 售后 CRUD |
|
||||||
|
| `/api/products` | GET/POST/PUT/DELETE | 产品 CRUD |
|
||||||
|
| `/api/suppliers` | GET/POST/PUT/DELETE | 供应商 CRUD |
|
||||||
|
|
||||||
|
> 详细 API 文档请参见后端项目的 `API.md`。
|
||||||
|
|
||||||
|
### 请求配置
|
||||||
|
|
||||||
|
基础请求配置在 `src/api/request.js` 中定义,包括:
|
||||||
|
- 自动附加 `Authorization: Bearer <token>` 请求头
|
||||||
|
- 响应拦截器统一处理错误(401 跳转登录、403 无权限提示等)
|
||||||
|
- 自动解析 `res.data` 数据
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 开发命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动开发服务器(热更新)
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 构建生产版本
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 预览生产构建
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 构建部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 构建产物在 dist/ 目录
|
||||||
|
# 将 dist/ 内容部署到任意静态文件服务器或 Nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nginx 反向代理示例
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
root /path/to/backmanagerweb/dist;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# SPA 路由重写
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# API 反向代理到后端
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://localhost:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 品牌说明
|
||||||
|
|
||||||
|
本项目是为 **蜜雪冰城(Mixue Ice Cream & Tea)** 量身定制的管理系统,品牌视觉元素(Logo、雪王 IP、品牌色 `#E60012` 等)均归属蜜雪冰城所有。
|
||||||
|
|
||||||
|
> "你爱我,我爱你,蜜雪冰城甜蜜蜜" 🎵
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import request from './request'
|
import request from './request'
|
||||||
|
import { DEPARTMENT_LIST } from '../constants/departments'
|
||||||
|
|
||||||
// 获取员工列表
|
// 获取员工列表
|
||||||
export const getEmployeeList = (params, config) => {
|
export const getEmployeeList = (params, config) => {
|
||||||
@@ -29,3 +30,15 @@ export const updateEmployee = (id, data) => {
|
|||||||
export const deleteEmployeeAPI = (id) => {
|
export const deleteEmployeeAPI = (id) => {
|
||||||
return request.delete(`/employees/${id}`)
|
return request.delete(`/employees/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取部门列表
|
||||||
|
*
|
||||||
|
* 当前返回本地常量(与后端数据库一致)。
|
||||||
|
* 当后端部署了 GET /api/departments 接口后,将下方注释替换为:
|
||||||
|
*
|
||||||
|
* export const getDepartmentList = () => request.get('/departments')
|
||||||
|
*/
|
||||||
|
export const getDepartmentList = () => {
|
||||||
|
return Promise.resolve({ code: 0, message: 'ok', data: DEPARTMENT_LIST })
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ import {
|
|||||||
getEmployeeList,
|
getEmployeeList,
|
||||||
createEmployee,
|
createEmployee,
|
||||||
updateEmployee,
|
updateEmployee,
|
||||||
deleteEmployeeAPI
|
deleteEmployeeAPI,
|
||||||
|
getDepartmentList,
|
||||||
|
getSimpleEmployeeList
|
||||||
} from '../api/employee'
|
} from '../api/employee'
|
||||||
import { hasPermission, getUserInfo } from '../store/user'
|
import { hasPermission, getUserInfo } from '../store/user'
|
||||||
import { formatDateTime, formatDate } from '../utils/date'
|
import { formatDateTime, formatDate } from '../utils/date'
|
||||||
import { createUser } from '../api/user'
|
import { createUser } from '../api/user'
|
||||||
|
import { DEPARTMENT_NAME_LIST } from '../constants/departments'
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
@@ -43,6 +46,8 @@ const currentDetail = ref(null)
|
|||||||
// 分页相关
|
// 分页相关
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const pageSize = ref(10)
|
const pageSize = ref(10)
|
||||||
|
const total = ref(0)
|
||||||
|
const totalPages = ref(0)
|
||||||
|
|
||||||
// 新增用户相关状态
|
// 新增用户相关状态
|
||||||
const showCreateUserForm = ref(false)
|
const showCreateUserForm = ref(false)
|
||||||
@@ -57,24 +62,15 @@ const userForm = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 部门列表
|
// 部门列表
|
||||||
const departmentOptions = [
|
const departmentOptions = ref([...DEPARTMENT_NAME_LIST])
|
||||||
'信息技术部', '总经理办公室', '招商部', '采购部', '运营部', '财务部',
|
|
||||||
'销售部', '技术部', '市场部', '仓储部', '人事部', '行政部'
|
|
||||||
]
|
|
||||||
|
|
||||||
// 学历列表
|
// 学历列表
|
||||||
const educationOptions = ['高中', '专科', '本科', '硕士', '博士']
|
const educationOptions = ['高中', '专科', '本科', '硕士', '博士']
|
||||||
|
|
||||||
// 分页后的数据
|
// 分页后的数据(服务端已分页,searchResults 即为当前页数据)
|
||||||
const paginatedData = computed(() => {
|
const paginatedData = computed(() => searchResults.value)
|
||||||
const start = (currentPage.value - 1) * pageSize.value
|
|
||||||
const end = start + pageSize.value
|
|
||||||
return searchResults.value.slice(start, end)
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(() => {
|
// onMounted 已在下方合并(同时加载部门列表)
|
||||||
fetchData()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听筛选变化
|
// 监听筛选变化
|
||||||
watch([filterStatus], () => {
|
watch([filterStatus], () => {
|
||||||
@@ -82,17 +78,28 @@ watch([filterStatus], () => {
|
|||||||
handleSearch()
|
handleSearch()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听搜索结果变化,重置页码
|
// 监听筛选变化时重置页码(服务端分页:已由 filterStatus watch 处理)
|
||||||
watch(searchResults, () => {
|
// watch(searchResults 不再重置页码,避免翻页后被重置回第 1 页
|
||||||
currentPage.value = 1
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取员工列表
|
// 获取员工列表
|
||||||
const fetchData = async () => {
|
const fetchData = async (page) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const res = await getEmployeeList()
|
const params = {
|
||||||
|
page: page || currentPage.value,
|
||||||
|
pageSize: pageSize.value
|
||||||
|
}
|
||||||
|
const res = await getEmployeeList(params)
|
||||||
if (res.code === 0 || res.code === 200) {
|
if (res.code === 0 || res.code === 200) {
|
||||||
searchResults.value = res.data.list || res.data
|
if (res.data && Array.isArray(res.data.list)) {
|
||||||
|
// 服务端分页响应
|
||||||
|
searchResults.value = res.data.list
|
||||||
|
total.value = res.data.total
|
||||||
|
totalPages.value = res.data.totalPages
|
||||||
|
} else if (Array.isArray(res.data)) {
|
||||||
|
// 兼容直接返回数组的场景(如 mock)
|
||||||
|
searchResults.value = res.data
|
||||||
|
total.value = res.data.length
|
||||||
|
}
|
||||||
}
|
}
|
||||||
showSearchResults.value = true
|
showSearchResults.value = true
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -106,8 +113,11 @@ const handleSearch = async () => {
|
|||||||
cancelCreateUser()
|
cancelCreateUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentPage.value = 1
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const params = {
|
const params = {
|
||||||
|
page: 1,
|
||||||
|
pageSize: pageSize.value,
|
||||||
status: filterStatus.value !== '' ? filterStatus.value : undefined
|
status: filterStatus.value !== '' ? filterStatus.value : undefined
|
||||||
}
|
}
|
||||||
if (search.value) {
|
if (search.value) {
|
||||||
@@ -121,7 +131,14 @@ const handleSearch = async () => {
|
|||||||
}
|
}
|
||||||
const res = await getEmployeeList(params)
|
const res = await getEmployeeList(params)
|
||||||
if (res.code === 0 || res.code === 200) {
|
if (res.code === 0 || res.code === 200) {
|
||||||
searchResults.value = res.data.list || res.data
|
if (res.data && Array.isArray(res.data.list)) {
|
||||||
|
searchResults.value = res.data.list
|
||||||
|
total.value = res.data.total
|
||||||
|
totalPages.value = res.data.totalPages
|
||||||
|
} else if (Array.isArray(res.data)) {
|
||||||
|
searchResults.value = res.data
|
||||||
|
total.value = res.data.length
|
||||||
|
}
|
||||||
}
|
}
|
||||||
showSearchResults.value = true
|
showSearchResults.value = true
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -134,7 +151,7 @@ const resetSearch = () => {
|
|||||||
showSearchResults.value = false
|
showSearchResults.value = false
|
||||||
showAddForm.value = false
|
showAddForm.value = false
|
||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
fetchData()
|
fetchData(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空搜索
|
// 清空搜索
|
||||||
@@ -234,7 +251,7 @@ const saveEmployee = async () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ElMessage.error(e?.message || '操作失败,请稍后重试')
|
ElMessage.error(e?.response?.data?.message || e?.message || '操作失败,请稍后重试')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +267,7 @@ const deleteEmployee = async (id) => {
|
|||||||
ElMessage.success('删除成功')
|
ElMessage.success('删除成功')
|
||||||
fetchData()
|
fetchData()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ElMessage.error(e?.message || '删除失败,请稍后重试')
|
ElMessage.error(e?.response?.data?.message || e?.message || '删除失败,请稍后重试')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -274,9 +291,10 @@ const closeDetail = () => {
|
|||||||
currentDetail.value = null
|
currentDetail.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页码变化
|
// 页码变化(服务端分页:重新请求后端)
|
||||||
const handleCurrentChange = (val) => {
|
const handleCurrentChange = (val) => {
|
||||||
currentPage.value = val
|
currentPage.value = val
|
||||||
|
fetchData(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开用户创建表单(从员工新增后调用)
|
// 打开用户创建表单(从员工新增后调用)
|
||||||
@@ -293,6 +311,15 @@ const openUserForm = (employee) => {
|
|||||||
userForm.employee_id = employee.id
|
userForm.employee_id = employee.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载部门列表 + 员工列表
|
||||||
|
onMounted(async () => {
|
||||||
|
const res = await getDepartmentList()
|
||||||
|
if (res.code === 0 || res.code === 200) {
|
||||||
|
departmentOptions.value = res.data.map(d => d.description) || DEPARTMENT_NAME_LIST
|
||||||
|
}
|
||||||
|
fetchData(1)
|
||||||
|
})
|
||||||
|
|
||||||
// 保存用户(从员工页面创建)
|
// 保存用户(从员工页面创建)
|
||||||
const saveUserFromEmployee = async () => {
|
const saveUserFromEmployee = async () => {
|
||||||
if (!userForm.username) {
|
if (!userForm.username) {
|
||||||
@@ -382,7 +409,7 @@ const showSalary = computed(() => ['总经理办公室', '财务部'].includes(g
|
|||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="list-header">
|
<div class="list-header">
|
||||||
<span class="total-count">共找到 {{ searchResults.length }} 条记录</span>
|
<span class="total-count">共找到 {{ total }} 条记录</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :data="paginatedData" v-loading="loading" border style="width: 100%" @row-dblclick="handleRowDblClick" row-class-name="clickable-row">
|
<el-table :data="paginatedData" v-loading="loading" border style="width: 100%" @row-dblclick="handleRowDblClick" row-class-name="clickable-row">
|
||||||
@@ -428,7 +455,7 @@ const showSalary = computed(() => ['总经理办公室', '财务部'].includes(g
|
|||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="currentPage"
|
v-model:current-page="currentPage"
|
||||||
:page-size="pageSize"
|
:page-size="pageSize"
|
||||||
:total="searchResults.length"
|
:total="total"
|
||||||
layout="prev, pager, next"
|
layout="prev, pager, next"
|
||||||
prev-text="上一页"
|
prev-text="上一页"
|
||||||
next-text="下一页"
|
next-text="下一页"
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import {
|
|||||||
updateUser,
|
updateUser,
|
||||||
deleteUserAPI
|
deleteUserAPI
|
||||||
} from '../api/user'
|
} from '../api/user'
|
||||||
import { getEmployeeList } from '../api/employee'
|
import { getSimpleEmployeeList, getDepartmentList } from '../api/employee'
|
||||||
import { hasPermission } from '../store/user'
|
import { hasPermission } from '../store/user'
|
||||||
import { formatDateTime } from '../utils/date'
|
import { formatDateTime } from '../utils/date'
|
||||||
|
import { DEPARTMENT_LIST } from '../constants/departments'
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
username: '',
|
username: '',
|
||||||
@@ -37,16 +38,11 @@ const currentDetail = ref(null)
|
|||||||
// 分页相关
|
// 分页相关
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const pageSize = ref(10)
|
const pageSize = ref(10)
|
||||||
|
const total = ref(0)
|
||||||
|
const totalPages = ref(0)
|
||||||
|
|
||||||
// 部门列表
|
// 部门列表(动态加载,回退到常量)
|
||||||
const deptList = ref([
|
const deptList = ref([...DEPARTMENT_LIST])
|
||||||
{ id: 1, name: 'admin', description: '信息技术部' },
|
|
||||||
{ id: 2, name: 'general_manager', description: '总经理办公室' },
|
|
||||||
{ id: 3, name: 'franchise_manager', description: '招商部' },
|
|
||||||
{ id: 4, name: 'operations_manager', description: '运营部' },
|
|
||||||
{ id: 5, name: 'procurement_manager', description: '采购部' },
|
|
||||||
{ id: 6, name: 'finance', description: '财务部' }
|
|
||||||
])
|
|
||||||
|
|
||||||
// 员工列表
|
// 员工列表
|
||||||
const employeeList = ref([])
|
const employeeList = ref([])
|
||||||
@@ -54,40 +50,53 @@ const employeeList = ref([])
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchData()
|
fetchData()
|
||||||
fetchEmployees()
|
fetchEmployees()
|
||||||
|
loadDepartments()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 加载部门列表
|
||||||
|
const loadDepartments = async () => {
|
||||||
|
const res = await getDepartmentList()
|
||||||
|
if (res.code === 0 || res.code === 200) {
|
||||||
|
deptList.value = res.data || DEPARTMENT_LIST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 监听筛选变化
|
// 监听筛选变化
|
||||||
watch([filterType, filterStatus], () => {
|
watch([filterType, filterStatus], () => {
|
||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
handleSearch()
|
handleSearch()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听搜索结果变化
|
// 监听搜索结果变化(服务端分页:不重置页码,避免翻页后被重置回第 1 页)
|
||||||
watch(searchResults, () => {
|
|
||||||
currentPage.value = 1
|
|
||||||
})
|
|
||||||
|
|
||||||
// 分页后的数据
|
// 分页后的数据(服务端已分页)
|
||||||
const paginatedData = computed(() => {
|
const paginatedData = computed(() => searchResults.value)
|
||||||
const start = (currentPage.value - 1) * pageSize.value
|
|
||||||
const end = start + pageSize.value
|
|
||||||
return searchResults.value.slice(start, end)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取员工列表
|
// 获取员工列表(用于下拉选择,改用 simple 接口:无数据范围限制,仅返还在职员工基本信息)
|
||||||
const fetchEmployees = async () => {
|
const fetchEmployees = async () => {
|
||||||
const res = await getEmployeeList()
|
const res = await getSimpleEmployeeList()
|
||||||
if (res.code === 0 || res.code === 200) {
|
if (res.code === 0 || res.code === 200) {
|
||||||
employeeList.value = res.data.list || res.data
|
employeeList.value = res.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户列表
|
// 获取用户列表
|
||||||
const fetchData = async () => {
|
const fetchData = async (page) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const res = await getUserList()
|
const params = {
|
||||||
|
page: page || currentPage.value,
|
||||||
|
pageSize: pageSize.value
|
||||||
|
}
|
||||||
|
const res = await getUserList(params)
|
||||||
if (res.code === 0 || res.code === 200) {
|
if (res.code === 0 || res.code === 200) {
|
||||||
searchResults.value = res.data.list || res.data
|
if (res.data && Array.isArray(res.data.list)) {
|
||||||
|
searchResults.value = res.data.list
|
||||||
|
total.value = res.data.total
|
||||||
|
totalPages.value = res.data.totalPages
|
||||||
|
} else if (Array.isArray(res.data)) {
|
||||||
|
searchResults.value = res.data
|
||||||
|
total.value = res.data.length
|
||||||
|
}
|
||||||
}
|
}
|
||||||
showSearchResults.value = true
|
showSearchResults.value = true
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -100,8 +109,11 @@ const handleSearch = async () => {
|
|||||||
cancelAdd()
|
cancelAdd()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentPage.value = 1
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const params = {
|
const params = {
|
||||||
|
page: 1,
|
||||||
|
pageSize: pageSize.value,
|
||||||
department_id: filterType.value || undefined,
|
department_id: filterType.value || undefined,
|
||||||
is_active: filterStatus.value !== '' ? filterStatus.value : undefined
|
is_active: filterStatus.value !== '' ? filterStatus.value : undefined
|
||||||
}
|
}
|
||||||
@@ -117,7 +129,14 @@ const handleSearch = async () => {
|
|||||||
}
|
}
|
||||||
const res = await getUserList(params)
|
const res = await getUserList(params)
|
||||||
if (res.code === 0 || res.code === 200) {
|
if (res.code === 0 || res.code === 200) {
|
||||||
searchResults.value = res.data.list || res.data
|
if (res.data && Array.isArray(res.data.list)) {
|
||||||
|
searchResults.value = res.data.list
|
||||||
|
total.value = res.data.total
|
||||||
|
totalPages.value = res.data.totalPages
|
||||||
|
} else if (Array.isArray(res.data)) {
|
||||||
|
searchResults.value = res.data
|
||||||
|
total.value = res.data.length
|
||||||
|
}
|
||||||
}
|
}
|
||||||
showSearchResults.value = true
|
showSearchResults.value = true
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -187,7 +206,7 @@ const saveUser = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cancelAdd()
|
cancelAdd()
|
||||||
fetchData()
|
fetchData(1)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error(error.response?.data?.message || error.message || '操作失败')
|
ElMessage.error(error.response?.data?.message || error.message || '操作失败')
|
||||||
}
|
}
|
||||||
@@ -203,7 +222,7 @@ const deleteUser = async (id) => {
|
|||||||
try {
|
try {
|
||||||
await deleteUserAPI(id)
|
await deleteUserAPI(id)
|
||||||
ElMessage.success('删除成功')
|
ElMessage.success('删除成功')
|
||||||
fetchData()
|
fetchData(1)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error(error.response?.data?.message || error.message || '删除失败')
|
ElMessage.error(error.response?.data?.message || error.message || '删除失败')
|
||||||
}
|
}
|
||||||
@@ -225,7 +244,7 @@ const clearSearch = () => {
|
|||||||
showSearchResults.value = false
|
showSearchResults.value = false
|
||||||
showAddForm.value = false
|
showAddForm.value = false
|
||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
fetchData()
|
fetchData(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 双击行查看详情
|
// 双击行查看详情
|
||||||
@@ -240,9 +259,10 @@ const closeDetail = () => {
|
|||||||
currentDetail.value = null
|
currentDetail.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页码变化
|
// 页码变化(服务端分页:重新请求后端)
|
||||||
const handleCurrentChange = (val) => {
|
const handleCurrentChange = (val) => {
|
||||||
currentPage.value = val
|
currentPage.value = val
|
||||||
|
fetchData(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查权限
|
// 检查权限
|
||||||
@@ -298,7 +318,7 @@ const canManage = computed(() => hasPermission('user', 'manage'))
|
|||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="list-header">
|
<div class="list-header">
|
||||||
<span class="total-count">共找到 {{ searchResults.length }} 条记录</span>
|
<span class="total-count">共找到 {{ total }} 条记录</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :data="paginatedData" v-loading="loading" border style="width: 100%"
|
<el-table :data="paginatedData" v-loading="loading" border style="width: 100%"
|
||||||
@@ -340,7 +360,7 @@ const canManage = computed(() => hasPermission('user', 'manage'))
|
|||||||
<el-pagination
|
<el-pagination
|
||||||
v-model:current-page="currentPage"
|
v-model:current-page="currentPage"
|
||||||
:page-size="pageSize"
|
:page-size="pageSize"
|
||||||
:total="searchResults.length"
|
:total="total"
|
||||||
layout="prev, pager, next"
|
layout="prev, pager, next"
|
||||||
prev-text="上一页"
|
prev-text="上一页"
|
||||||
next-text="下一页"
|
next-text="下一页"
|
||||||
|
|||||||
Reference in New Issue
Block a user