feat: 添加测试框架和覆盖率报告功能
feat(测试): 新增Playwright和Vitest测试配置 feat(测试): 添加测试覆盖率报告生成功能 feat(测试): 实现前后端测试脚本集成 fix(测试): 修复测试密码不匹配问题 fix(测试): 修正URL等待策略 fix(测试): 调整错误消息选择器 refactor(测试): 重构测试目录结构 refactor(测试): 优化测试用例组织方式 docs: 更新测试报告文档 docs: 添加测试覆盖率报告模板 ci: 添加Docker测试环境配置 ci: 实现测试自动化脚本 chore: 更新依赖版本 chore: 添加测试相关配置文件
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import request from '@/utils/request'
|
||||
import type { PageResponse } from './user.api'
|
||||
import { RoleStatus } from '@/constants/status'
|
||||
|
||||
export interface Role {
|
||||
id: number
|
||||
name: string
|
||||
code: string
|
||||
description: string
|
||||
status: 'ACTIVE' | 'INACTIVE'
|
||||
roleName: string
|
||||
roleKey: string
|
||||
roleSort: number
|
||||
status: RoleStatus
|
||||
permissions: Permission[]
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
@@ -21,24 +22,25 @@ export interface Permission {
|
||||
}
|
||||
|
||||
export interface CreateRoleRequest {
|
||||
name: string
|
||||
code: string
|
||||
description: string
|
||||
roleName: string
|
||||
roleKey: string
|
||||
roleSort: number
|
||||
permissions: number[]
|
||||
}
|
||||
|
||||
export interface UpdateRoleRequest {
|
||||
name?: string
|
||||
description?: string
|
||||
status?: 'ACTIVE' | 'INACTIVE'
|
||||
roleName?: string
|
||||
roleKey?: string
|
||||
roleSort?: number
|
||||
status?: RoleStatus
|
||||
permissions?: number[]
|
||||
}
|
||||
|
||||
export interface RolePageRequest {
|
||||
page: number
|
||||
size: number
|
||||
name?: string
|
||||
code?: string
|
||||
roleName?: string
|
||||
roleKey?: string
|
||||
status?: string
|
||||
sortBy?: string
|
||||
sortOrder?: 'asc' | 'desc'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import request from '@/utils/request'
|
||||
import { UserStatus } from '@/constants/status'
|
||||
|
||||
export interface User {
|
||||
id: number
|
||||
@@ -7,7 +8,7 @@ export interface User {
|
||||
email: string
|
||||
phone: string
|
||||
avatar: string
|
||||
status: 'ACTIVE' | 'INACTIVE' | 'LOCKED'
|
||||
status: UserStatus
|
||||
roles: string[]
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
@@ -27,7 +28,7 @@ export interface UpdateUserRequest {
|
||||
email?: string
|
||||
phone?: string
|
||||
avatar?: string
|
||||
status?: 'ACTIVE' | 'INACTIVE' | 'LOCKED'
|
||||
status?: UserStatus
|
||||
roles?: string[]
|
||||
}
|
||||
|
||||
@@ -76,7 +77,7 @@ export const userApi = {
|
||||
resetPassword: (id: number) =>
|
||||
request.post<void>(`/users/${id}/reset-password`),
|
||||
|
||||
updateStatus: (id: number, status: 'ACTIVE' | 'INACTIVE' | 'LOCKED') =>
|
||||
updateStatus: (id: number, status: UserStatus) =>
|
||||
request.put<void>(`/users/${id}/status`, { status }),
|
||||
|
||||
assignRoles: (id: number, roleIds: number[]) =>
|
||||
|
||||
@@ -32,3 +32,61 @@ body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.el-message {
|
||||
--el-message-bg-color: var(--el-color-success-dark-2);
|
||||
--el-message-border-color: var(--el-color-success-dark-2);
|
||||
--el-message-text-color: #ffffff;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.el-message--success {
|
||||
--el-message-bg-color: var(--el-color-success-dark-2);
|
||||
--el-message-border-color: var(--el-color-success-dark-2);
|
||||
--el-message-text-color: #ffffff;
|
||||
}
|
||||
|
||||
.el-message--error {
|
||||
--el-message-bg-color: var(--el-color-danger-dark-2);
|
||||
--el-message-border-color: var(--el-color-danger-dark-2);
|
||||
--el-message-text-color: #ffffff;
|
||||
}
|
||||
|
||||
.el-message--warning {
|
||||
--el-message-bg-color: var(--el-color-warning-dark-2);
|
||||
--el-message-border-color: var(--el-color-warning-dark-2);
|
||||
--el-message-text-color: #ffffff;
|
||||
}
|
||||
|
||||
.el-message--info {
|
||||
--el-message-bg-color: var(--el-color-info-dark-2);
|
||||
--el-message-border-color: var(--el-color-info-dark-2);
|
||||
--el-message-text-color: #ffffff;
|
||||
}
|
||||
|
||||
.el-tag.el-tag--light {
|
||||
color: #ffffff !important;
|
||||
background-color: var(--el-color-danger-light-9);
|
||||
border-color: var(--el-color-danger-light-9);
|
||||
}
|
||||
|
||||
.el-tag.el-tag--light.el-tag--success {
|
||||
background-color: var(--el-color-success-light-9);
|
||||
border-color: var(--el-color-success-light-9);
|
||||
}
|
||||
|
||||
.el-tag.el-tag--light.el-tag--warning {
|
||||
background-color: var(--el-color-warning-light-9);
|
||||
border-color: var(--el-color-warning-light-9);
|
||||
}
|
||||
|
||||
.el-tag.el-tag--light.el-tag--info {
|
||||
background-color: var(--el-color-info-light-9);
|
||||
border-color: var(--el-color-info-light-9);
|
||||
}
|
||||
|
||||
.el-tag.el-tag--light.el-tag--danger {
|
||||
background-color: var(--el-color-danger-light-9);
|
||||
border-color: var(--el-color-danger-light-9);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 系统状态值常量定义
|
||||
*
|
||||
* 统一前后端状态值,避免不一致导致的功能问题
|
||||
*
|
||||
* @author 张翔
|
||||
* @date 2026-03-24
|
||||
*/
|
||||
|
||||
/**
|
||||
* 用户状态枚举
|
||||
*/
|
||||
export enum UserStatus {
|
||||
/** 正常 */
|
||||
ACTIVE = 1,
|
||||
/** 禁用 */
|
||||
INACTIVE = 0,
|
||||
/** 锁定 */
|
||||
LOCKED = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色状态枚举
|
||||
*/
|
||||
export enum RoleStatus {
|
||||
/** 正常 */
|
||||
ACTIVE = 1,
|
||||
/** 禁用 */
|
||||
INACTIVE = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单状态枚举
|
||||
*/
|
||||
export enum MenuStatus {
|
||||
/** 正常 */
|
||||
ACTIVE = 1,
|
||||
/** 禁用 */
|
||||
INACTIVE = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知状态枚举
|
||||
*/
|
||||
export enum NoticeStatus {
|
||||
/** 正常 */
|
||||
ACTIVE = '1',
|
||||
/** 禁用 */
|
||||
INACTIVE = '0'
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态值映射工具类
|
||||
*/
|
||||
export class StatusHelper {
|
||||
/**
|
||||
* 判断状态是否为正常
|
||||
*/
|
||||
static isActive(status: number | string): boolean {
|
||||
return status === 1 || status === '1' || status === 'ACTIVE'
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断状态是否为禁用
|
||||
*/
|
||||
static isInactive(status: number | string): boolean {
|
||||
return status === 0 || status === '0' || status === 'INACTIVE'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态显示文本
|
||||
*/
|
||||
static getStatusText(status: number | string): string {
|
||||
if (this.isActive(status)) return '正常'
|
||||
if (this.isInactive(status)) return '禁用'
|
||||
return '未知'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态标签类型
|
||||
*/
|
||||
static getStatusType(status: number | string): 'success' | 'danger' | 'warning' {
|
||||
if (this.isActive(status)) return 'success'
|
||||
if (this.isInactive(status)) return 'danger'
|
||||
return 'warning'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import ConfigManagement from '@/views/config/ConfigManagement.vue'
|
||||
|
||||
vi.mock('vue-router')
|
||||
vi.mock('element-plus', () => ({
|
||||
ElMessage: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
ElMessageBox: {
|
||||
confirm: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/request', () => {
|
||||
const mockRequest = {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
put: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
}
|
||||
|
||||
mockRequest.get.mockResolvedValue([])
|
||||
mockRequest.post.mockResolvedValue({})
|
||||
mockRequest.put.mockResolvedValue({})
|
||||
mockRequest.delete.mockResolvedValue({})
|
||||
|
||||
return {
|
||||
default: mockRequest,
|
||||
}
|
||||
})
|
||||
|
||||
describe('ConfigManagement Component', () => {
|
||||
let router: any
|
||||
let wrapper: any
|
||||
|
||||
beforeEach(() => {
|
||||
router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: { template: '<div>Home</div>' } },
|
||||
],
|
||||
})
|
||||
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (wrapper) {
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
describe('component initialization', () => {
|
||||
it('should render config management container', () => {
|
||||
wrapper = mount(ConfigManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.find('.config-management').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should initialize with empty data source', () => {
|
||||
wrapper = mount(ConfigManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.dataSource).toBeDefined()
|
||||
expect(Array.isArray(wrapper.vm.dataSource)).toBe(true)
|
||||
})
|
||||
|
||||
it('should initialize with loading state false', () => {
|
||||
wrapper = mount(ConfigManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.loading).toBeDefined()
|
||||
expect(typeof wrapper.vm.loading).toBe('boolean')
|
||||
})
|
||||
|
||||
it('should initialize with modal visible false', () => {
|
||||
wrapper = mount(ConfigManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.modalVisible).toBe(false)
|
||||
})
|
||||
|
||||
it('should initialize with empty form state', () => {
|
||||
wrapper = mount(ConfigManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.formState.configName).toBe('')
|
||||
expect(wrapper.vm.formState.configKey).toBe('')
|
||||
expect(wrapper.vm.formState.configValue).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('add config functionality', () => {
|
||||
it('should have handleAdd method', () => {
|
||||
wrapper = mount(ConfigManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleAdd).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('edit config functionality', () => {
|
||||
it('should have handleEdit method', () => {
|
||||
wrapper = mount(ConfigManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleEdit).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('delete config functionality', () => {
|
||||
it('should have handleDelete method', () => {
|
||||
wrapper = mount(ConfigManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleDelete).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('form submission', () => {
|
||||
it('should have handleModalOk method', () => {
|
||||
wrapper = mount(ConfigManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleModalOk).toBe('function')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,261 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import Dashboard from '@/views/system/Dashboard.vue'
|
||||
|
||||
vi.mock('vue-router')
|
||||
vi.mock('@/api/user.api.ts', () => ({
|
||||
getUserStats: vi.fn(),
|
||||
getRecentLogins: vi.fn(),
|
||||
}))
|
||||
|
||||
describe('Dashboard Component', () => {
|
||||
let router: any
|
||||
let wrapper: any
|
||||
|
||||
beforeEach(() => {
|
||||
router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: { template: '<div>Dashboard</div>' } },
|
||||
],
|
||||
})
|
||||
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (wrapper) {
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
describe('component initialization', () => {
|
||||
it('should render dashboard container', () => {
|
||||
wrapper = mount(Dashboard, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-row': true,
|
||||
'el-col': true,
|
||||
'el-card': true,
|
||||
'el-statistic': true,
|
||||
'el-icon': true,
|
||||
'el-timeline': true,
|
||||
'el-timeline-item': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.find('.dashboard').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should initialize with loading state', () => {
|
||||
wrapper = mount(Dashboard, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-row': true,
|
||||
'el-col': true,
|
||||
'el-card': true,
|
||||
'el-statistic': true,
|
||||
'el-icon': true,
|
||||
'el-timeline': true,
|
||||
'el-timeline-item': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.loading).toBe(true)
|
||||
})
|
||||
|
||||
it('should initialize with empty stats', () => {
|
||||
wrapper = mount(Dashboard, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-row': true,
|
||||
'el-col': true,
|
||||
'el-card': true,
|
||||
'el-statistic': true,
|
||||
'el-icon': true,
|
||||
'el-timeline': true,
|
||||
'el-timeline-item': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.stats).toEqual({
|
||||
userCount: 0,
|
||||
roleCount: 0,
|
||||
todayLogin: 0,
|
||||
operationLog: 0,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('statistics cards', () => {
|
||||
it('should render user count card', () => {
|
||||
wrapper = mount(Dashboard, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-row': true,
|
||||
'el-col': true,
|
||||
'el-card': true,
|
||||
'el-statistic': true,
|
||||
'el-icon': true,
|
||||
'el-timeline': true,
|
||||
'el-timeline-item': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.stats.userCount).toBeDefined()
|
||||
})
|
||||
|
||||
it('should render role count card', () => {
|
||||
wrapper = mount(Dashboard, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-row': true,
|
||||
'el-col': true,
|
||||
'el-card': true,
|
||||
'el-statistic': true,
|
||||
'el-icon': true,
|
||||
'el-timeline': true,
|
||||
'el-timeline-item': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.stats.roleCount).toBeDefined()
|
||||
})
|
||||
|
||||
it('should render today login card', () => {
|
||||
wrapper = mount(Dashboard, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-row': true,
|
||||
'el-col': true,
|
||||
'el-card': true,
|
||||
'el-statistic': true,
|
||||
'el-icon': true,
|
||||
'el-timeline': true,
|
||||
'el-timeline-item': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.stats.todayLogin).toBeDefined()
|
||||
})
|
||||
|
||||
it('should render operation log card', () => {
|
||||
wrapper = mount(Dashboard, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-row': true,
|
||||
'el-col': true,
|
||||
'el-card': true,
|
||||
'el-statistic': true,
|
||||
'el-icon': true,
|
||||
'el-timeline': true,
|
||||
'el-timeline-item': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.stats.operationLog).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('recent logins', () => {
|
||||
it('should initialize with empty recent logins', () => {
|
||||
wrapper = mount(Dashboard, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-row': true,
|
||||
'el-col': true,
|
||||
'el-card': true,
|
||||
'el-statistic': true,
|
||||
'el-icon': true,
|
||||
'el-timeline': true,
|
||||
'el-timeline-item': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.recentLogins).toEqual([])
|
||||
})
|
||||
|
||||
it('should display empty state when no recent logins', () => {
|
||||
wrapper = mount(Dashboard, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-row': true,
|
||||
'el-col': true,
|
||||
'el-card': true,
|
||||
'el-statistic': true,
|
||||
'el-icon': true,
|
||||
'el-timeline': true,
|
||||
'el-timeline-item': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.recentLogins.length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('data loading', () => {
|
||||
it('should set loading to false after data loaded', async () => {
|
||||
wrapper = mount(Dashboard, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-row': true,
|
||||
'el-col': true,
|
||||
'el-card': true,
|
||||
'el-statistic': true,
|
||||
'el-icon': true,
|
||||
'el-timeline': true,
|
||||
'el-timeline-item': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.loading).toBe(true)
|
||||
|
||||
wrapper.vm.loading = false
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.vm.loading).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('document title', () => {
|
||||
it('should have dashboard component mounted', () => {
|
||||
wrapper = mount(Dashboard, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-row': true,
|
||||
'el-col': true,
|
||||
'el-card': true,
|
||||
'el-statistic': true,
|
||||
'el-icon': true,
|
||||
'el-timeline': true,
|
||||
'el-timeline-item': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,286 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import DictManagement from '@/views/config/DictManagement.vue'
|
||||
|
||||
vi.mock('vue-router')
|
||||
vi.mock('element-plus', () => ({
|
||||
ElMessage: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
ElMessageBox: {
|
||||
confirm: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/request', () => {
|
||||
const mockRequest = {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
put: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
}
|
||||
|
||||
mockRequest.get.mockResolvedValue([])
|
||||
mockRequest.post.mockResolvedValue({})
|
||||
mockRequest.put.mockResolvedValue({})
|
||||
mockRequest.delete.mockResolvedValue({})
|
||||
|
||||
return {
|
||||
default: mockRequest,
|
||||
}
|
||||
})
|
||||
|
||||
describe('DictManagement Component', () => {
|
||||
let router: any
|
||||
let wrapper: any
|
||||
|
||||
beforeEach(() => {
|
||||
router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: { template: '<div>Home</div>' } },
|
||||
],
|
||||
})
|
||||
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (wrapper) {
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
describe('component initialization', () => {
|
||||
it('should render dict management container', () => {
|
||||
wrapper = mount(DictManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.find('.dict-management').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should initialize with empty data source', () => {
|
||||
wrapper = mount(DictManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.dataSource).toBeDefined()
|
||||
expect(Array.isArray(wrapper.vm.dataSource)).toBe(true)
|
||||
})
|
||||
|
||||
it('should initialize with loading state false', () => {
|
||||
wrapper = mount(DictManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.loading).toBeDefined()
|
||||
expect(typeof wrapper.vm.loading).toBe('boolean')
|
||||
})
|
||||
|
||||
it('should initialize with modal visible false', () => {
|
||||
wrapper = mount(DictManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.modalVisible).toBe(false)
|
||||
})
|
||||
|
||||
it('should initialize with empty form state', () => {
|
||||
wrapper = mount(DictManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.formState.dictName).toBe('')
|
||||
expect(wrapper.vm.formState.dictType).toBe('')
|
||||
expect(wrapper.vm.formState.status).toBe('0')
|
||||
expect(wrapper.vm.formState.remark).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('add dict functionality', () => {
|
||||
it('should have handleAdd method', () => {
|
||||
wrapper = mount(DictManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleAdd).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('edit dict functionality', () => {
|
||||
it('should have handleEdit method', () => {
|
||||
wrapper = mount(DictManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleEdit).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('delete dict functionality', () => {
|
||||
it('should have handleDelete method', () => {
|
||||
wrapper = mount(DictManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleDelete).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('form submission', () => {
|
||||
it('should have handleModalOk method', () => {
|
||||
wrapper = mount(DictManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleModalOk).toBe('function')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,181 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import Login from '@/views/system/Login.vue'
|
||||
|
||||
vi.mock('vue-router')
|
||||
vi.mock('element-plus', () => ({
|
||||
ElMessage: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/request', () => ({
|
||||
default: {
|
||||
post: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
describe('Login Component', () => {
|
||||
let router: any
|
||||
let wrapper: any
|
||||
|
||||
beforeEach(() => {
|
||||
router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: { template: '<div>Dashboard</div>' } },
|
||||
{ path: '/login', component: { template: '<div>Login</div>' } },
|
||||
],
|
||||
})
|
||||
|
||||
vi.clearAllMocks()
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (wrapper) {
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
describe('component rendering', () => {
|
||||
it('should render login form', () => {
|
||||
wrapper = mount(Login, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.find('.login-container').exists()).toBe(true)
|
||||
expect(wrapper.find('.login-card').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should initialize with empty form state', () => {
|
||||
wrapper = mount(Login, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.formState.username).toBe('')
|
||||
expect(wrapper.vm.formState.password).toBe('')
|
||||
})
|
||||
|
||||
it('should initialize loading as false', () => {
|
||||
wrapper = mount(Login, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.loading).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('form state management', () => {
|
||||
it('should update username when input changes', async () => {
|
||||
wrapper = mount(Login, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
wrapper.vm.formState.username = 'testuser'
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.vm.formState.username).toBe('testuser')
|
||||
})
|
||||
|
||||
it('should update password when input changes', async () => {
|
||||
wrapper = mount(Login, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
wrapper.vm.formState.password = 'password123'
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.vm.formState.password).toBe('password123')
|
||||
})
|
||||
})
|
||||
|
||||
describe('form submission', () => {
|
||||
it('should have onFinish method', () => {
|
||||
wrapper = mount(Login, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.onFinish).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('document title', () => {
|
||||
it('should set document title on mount', () => {
|
||||
const originalTitle = document.title
|
||||
|
||||
wrapper = mount(Login, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(document.title).toBe('登录 - Novalon 管理系统')
|
||||
|
||||
document.title = originalTitle
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,279 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import MenuManagement from '@/views/system/MenuManagement.vue'
|
||||
|
||||
vi.mock('vue-router')
|
||||
vi.mock('element-plus', () => ({
|
||||
ElMessage: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
ElMessageBox: {
|
||||
confirm: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/api/menu.api', () => ({
|
||||
menuApi: {
|
||||
getAll: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/request', () => {
|
||||
const mockRequest = {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
put: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
}
|
||||
|
||||
mockRequest.get.mockResolvedValue([])
|
||||
mockRequest.post.mockResolvedValue({})
|
||||
mockRequest.put.mockResolvedValue({})
|
||||
mockRequest.delete.mockResolvedValue({})
|
||||
|
||||
return {
|
||||
default: mockRequest,
|
||||
}
|
||||
})
|
||||
|
||||
describe('MenuManagement Component', () => {
|
||||
let router: any
|
||||
let wrapper: any
|
||||
|
||||
beforeEach(() => {
|
||||
router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: { template: '<div>Home</div>' } },
|
||||
],
|
||||
})
|
||||
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (wrapper) {
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
describe('component initialization', () => {
|
||||
it('should render menu management container', () => {
|
||||
wrapper = mount(MenuManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-input-number': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.find('.menu-management').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should initialize with empty data source', () => {
|
||||
wrapper = mount(MenuManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-input-number': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.dataSource).toBeDefined()
|
||||
expect(Array.isArray(wrapper.vm.dataSource)).toBe(true)
|
||||
})
|
||||
|
||||
it('should initialize with loading state false', () => {
|
||||
wrapper = mount(MenuManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-input-number': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.loading).toBeDefined()
|
||||
expect(typeof wrapper.vm.loading).toBe('boolean')
|
||||
})
|
||||
|
||||
it('should initialize with modal visible false', () => {
|
||||
wrapper = mount(MenuManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-input-number': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.modalVisible).toBe(false)
|
||||
})
|
||||
|
||||
it('should initialize with empty form state', () => {
|
||||
wrapper = mount(MenuManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-input-number': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.formState.menuName).toBe('')
|
||||
expect(wrapper.vm.formState.menuType).toBe('C')
|
||||
expect(wrapper.vm.formState.perms).toBe('')
|
||||
expect(wrapper.vm.formState.component).toBe('')
|
||||
expect(wrapper.vm.formState.orderNum).toBe(0)
|
||||
expect(wrapper.vm.formState.status).toBe('0')
|
||||
})
|
||||
})
|
||||
|
||||
describe('add menu functionality', () => {
|
||||
it('should have handleAdd method', () => {
|
||||
wrapper = mount(MenuManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-input-number': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleAdd).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('edit menu functionality', () => {
|
||||
it('should have handleEdit method', () => {
|
||||
wrapper = mount(MenuManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-input-number': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleEdit).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('delete menu functionality', () => {
|
||||
it('should have handleDelete method', () => {
|
||||
wrapper = mount(MenuManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-input': true,
|
||||
'el-input-number': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleDelete).toBe('function')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,383 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import RoleManagement from '@/views/system/RoleManagement.vue'
|
||||
|
||||
vi.mock('vue-router')
|
||||
vi.mock('element-plus', () => ({
|
||||
ElMessage: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
ElMessageBox: {
|
||||
confirm: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/api/role.api', () => ({
|
||||
roleApi: {
|
||||
getPage: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
getAll: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/api/permission.api', () => ({
|
||||
permissionApi: {
|
||||
getAll: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
describe('RoleManagement Component', () => {
|
||||
let router: any
|
||||
let wrapper: any
|
||||
|
||||
beforeEach(() => {
|
||||
router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: { template: '<div>Home</div>' } },
|
||||
],
|
||||
})
|
||||
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (wrapper) {
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
describe('component initialization', () => {
|
||||
it('should render role management container', () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.find('.role-management').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should initialize with empty search keyword', () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.searchKeyword).toBe('')
|
||||
})
|
||||
|
||||
it('should initialize with empty data source', () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.dataSource).toEqual([])
|
||||
})
|
||||
|
||||
it('should initialize with pagination on page 1', () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.pagination.current).toBe(1)
|
||||
})
|
||||
|
||||
it('should initialize with modal visible false', () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.modalVisible).toBe(false)
|
||||
})
|
||||
|
||||
it('should initialize with empty form state', () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.formState.roleName).toBe('')
|
||||
expect(wrapper.vm.formState.roleKey).toBe('')
|
||||
expect(wrapper.vm.formState.roleSort).toBe(1)
|
||||
expect(wrapper.vm.formState.status).toBe(1)
|
||||
expect(wrapper.vm.formState.permissions).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('search functionality', () => {
|
||||
it('should have handleSearch method', () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleSearch).toBe('function')
|
||||
})
|
||||
|
||||
it('should update search keyword when input changes', async () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
wrapper.vm.searchKeyword = 'admin'
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.vm.searchKeyword).toBe('admin')
|
||||
})
|
||||
})
|
||||
|
||||
describe('add role functionality', () => {
|
||||
it('should have handleAdd method', () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleAdd).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('pagination functionality', () => {
|
||||
it('should have handleTableChange method', () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleTableChange).toBe('function')
|
||||
})
|
||||
|
||||
it('should have handleSizeChange method', () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleSizeChange).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('sort functionality', () => {
|
||||
it('should have handleSortChange method', () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleSortChange).toBe('function')
|
||||
})
|
||||
|
||||
it('should initialize with default sort info', () => {
|
||||
wrapper = mount(RoleManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-tree': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.sortInfo.sortBy).toBe('id')
|
||||
expect(wrapper.vm.sortInfo.sortOrder).toBe('asc')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,423 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
||||
import UserManagement from '@/views/system/UserManagement.vue'
|
||||
|
||||
vi.mock('vue-router')
|
||||
vi.mock('element-plus', () => ({
|
||||
ElMessage: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
ElMessageBox: {
|
||||
confirm: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/api/user.api', () => ({
|
||||
userApi: {
|
||||
getPage: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
assignRoles: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/api/role.api', () => ({
|
||||
roleApi: {
|
||||
getAll: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
describe('UserManagement Component', () => {
|
||||
let router: any
|
||||
let wrapper: any
|
||||
|
||||
beforeEach(() => {
|
||||
router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/', component: { template: '<div>Home</div>' } },
|
||||
],
|
||||
})
|
||||
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (wrapper) {
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
describe('component initialization', () => {
|
||||
it('should render user management container', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.find('.user-management').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should initialize with empty search keyword', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.searchKeyword).toBe('')
|
||||
})
|
||||
|
||||
it('should initialize with loading state false before data fetch', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.loading).toBeDefined()
|
||||
expect(typeof wrapper.vm.loading).toBe('boolean')
|
||||
})
|
||||
|
||||
it('should initialize with empty data source', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.dataSource).toEqual([])
|
||||
})
|
||||
|
||||
it('should initialize with pagination on page 1', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.pagination.current).toBe(1)
|
||||
})
|
||||
|
||||
it('should initialize with modal visible false', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.modalVisible).toBe(false)
|
||||
})
|
||||
|
||||
it('should initialize with empty form state', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.formState.username).toBe('')
|
||||
expect(wrapper.vm.formState.password).toBe('')
|
||||
expect(wrapper.vm.formState.nickname).toBe('')
|
||||
expect(wrapper.vm.formState.email).toBe('')
|
||||
expect(wrapper.vm.formState.phone).toBe('')
|
||||
expect(wrapper.vm.formState.roles).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('search functionality', () => {
|
||||
it('should have handleSearch method', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleSearch).toBe('function')
|
||||
})
|
||||
|
||||
it('should update search keyword when input changes', async () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
wrapper.vm.searchKeyword = 'testuser'
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.vm.searchKeyword).toBe('testuser')
|
||||
})
|
||||
})
|
||||
|
||||
describe('add user functionality', () => {
|
||||
it('should have handleAdd method', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleAdd).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('pagination functionality', () => {
|
||||
it('should have handleTableChange method', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleTableChange).toBe('function')
|
||||
})
|
||||
|
||||
it('should have handleSizeChange method', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleSizeChange).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
describe('sort functionality', () => {
|
||||
it('should have handleSortChange method', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(typeof wrapper.vm.handleSortChange).toBe('function')
|
||||
})
|
||||
|
||||
it('should initialize with default sort info', () => {
|
||||
wrapper = mount(UserManagement, {
|
||||
global: {
|
||||
plugins: [router],
|
||||
stubs: {
|
||||
'el-card': true,
|
||||
'el-input': true,
|
||||
'el-button': true,
|
||||
'el-table': true,
|
||||
'el-table-column': true,
|
||||
'el-tag': true,
|
||||
'el-pagination': true,
|
||||
'el-dialog': true,
|
||||
'el-form': true,
|
||||
'el-form-item': true,
|
||||
'el-select': true,
|
||||
'el-option': true,
|
||||
'el-icon': true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.vm.sortInfo.sortBy).toBe('id')
|
||||
expect(wrapper.vm.sortInfo.sortOrder).toBe('asc')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
describe('Vitest Configuration Test', () => {
|
||||
it('should run a simple test', () => {
|
||||
expect(1 + 1).toBe(2)
|
||||
})
|
||||
|
||||
it('should handle async operations', async () => {
|
||||
const result = await Promise.resolve(42)
|
||||
expect(result).toBe(42)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,88 @@
|
||||
export const mockUser = {
|
||||
id: 1,
|
||||
username: 'testuser',
|
||||
nickname: 'Test User',
|
||||
email: 'test@example.com',
|
||||
phone: '13800138000',
|
||||
avatar: 'https://example.com/avatar.jpg',
|
||||
roles: ['admin'],
|
||||
permissions: ['user:view', 'user:create', 'user:edit', 'user:delete'],
|
||||
}
|
||||
|
||||
export const mockRole = {
|
||||
id: 1,
|
||||
roleName: '测试角色',
|
||||
roleKey: 'test_role',
|
||||
roleSort: 1,
|
||||
status: '1',
|
||||
remark: '测试角色备注',
|
||||
createTime: new Date().toISOString(),
|
||||
updateTime: new Date().toISOString(),
|
||||
}
|
||||
|
||||
export const mockMenu = {
|
||||
id: 1,
|
||||
menuName: '系统管理',
|
||||
parentId: 0,
|
||||
orderNum: 1,
|
||||
menuType: 'M',
|
||||
component: 'system',
|
||||
perms: 'system:view',
|
||||
status: '1',
|
||||
createTime: new Date().toISOString(),
|
||||
updateTime: new Date().toISOString(),
|
||||
}
|
||||
|
||||
export const mockDict = {
|
||||
id: 1,
|
||||
dictName: '用户状态',
|
||||
dictType: 'user_status',
|
||||
status: '1',
|
||||
remark: '用户状态字典',
|
||||
createTime: new Date().toISOString(),
|
||||
updateTime: new Date().toISOString(),
|
||||
}
|
||||
|
||||
export const mockConfig = {
|
||||
id: 1,
|
||||
configName: '系统名称',
|
||||
configKey: 'sys.name',
|
||||
configValue: 'Novalon管理系统',
|
||||
configType: 'Y',
|
||||
status: '1',
|
||||
remark: '系统名称配置',
|
||||
createTime: new Date().toISOString(),
|
||||
updateTime: new Date().toISOString(),
|
||||
}
|
||||
|
||||
export const mockNotice = {
|
||||
id: 1,
|
||||
noticeTitle: '系统通知',
|
||||
noticeType: '1',
|
||||
noticeContent: '这是一条测试通知',
|
||||
status: '0',
|
||||
createTime: new Date().toISOString(),
|
||||
updateTime: new Date().toISOString(),
|
||||
}
|
||||
|
||||
export const mockLoginRequest = {
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
}
|
||||
|
||||
export const mockLoginResponse = {
|
||||
token: 'mock-jwt-token',
|
||||
user: mockUser,
|
||||
}
|
||||
|
||||
export const mockApiResponse = <T>(data: T, code = 200, message = 'success') => ({
|
||||
code,
|
||||
message,
|
||||
data,
|
||||
})
|
||||
|
||||
export const mockErrorResponse = (code = 500, message = 'Internal Server Error') => ({
|
||||
code,
|
||||
message,
|
||||
data: null,
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import { vi } from 'vitest'
|
||||
import { config } from '@vue/test-utils'
|
||||
|
||||
config.global.stubs = {
|
||||
transition: false,
|
||||
'transition-group': false,
|
||||
}
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
})
|
||||
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: {
|
||||
getItem: vi.fn(),
|
||||
setItem: vi.fn(),
|
||||
removeItem: vi.fn(),
|
||||
clear: vi.fn(),
|
||||
},
|
||||
})
|
||||
|
||||
Object.defineProperty(window, 'sessionStorage', {
|
||||
value: {
|
||||
getItem: vi.fn(),
|
||||
setItem: vi.fn(),
|
||||
removeItem: vi.fn(),
|
||||
clear: vi.fn(),
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
import { VueWrapper } from '@vue/test-utils'
|
||||
import { ComponentPublicInstance } from 'vue'
|
||||
|
||||
export interface TestHelpers {
|
||||
findByText: (text: string) => HTMLElement | null
|
||||
findByTestId: (testId: string) => HTMLElement | null
|
||||
clickByText: (text: string) => Promise<void>
|
||||
clickByTestId: (testId: string) => Promise<void>
|
||||
fillByTestId: (testId: string, value: string) => Promise<void>
|
||||
}
|
||||
|
||||
export function createTestHelpers(wrapper: VueWrapper<ComponentPublicInstance>): TestHelpers {
|
||||
return {
|
||||
findByText: (text: string) => {
|
||||
return wrapper.element.textContent?.includes(text) ? wrapper.element : null
|
||||
},
|
||||
findByTestId: (testId: string) => {
|
||||
return wrapper.element.querySelector(`[data-testid="${testId}"]`)
|
||||
},
|
||||
clickByText: async (text: string) => {
|
||||
const element = wrapper.element.textContent?.includes(text) ? wrapper.element : null
|
||||
if (element) {
|
||||
element.click()
|
||||
await wrapper.vm.$nextTick()
|
||||
}
|
||||
},
|
||||
clickByTestId: async (testId: string) => {
|
||||
const element = wrapper.element.querySelector(`[data-testid="${testId}"]`)
|
||||
if (element) {
|
||||
element.click()
|
||||
await wrapper.vm.$nextTick()
|
||||
}
|
||||
},
|
||||
fillByTestId: async (testId: string, value: string) => {
|
||||
const element = wrapper.element.querySelector(`[data-testid="${testId}"]`) as HTMLInputElement
|
||||
if (element) {
|
||||
element.value = value
|
||||
element.dispatchEvent(new Event('input', { bubbles: true }))
|
||||
await wrapper.vm.$nextTick()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function waitFor(condition: () => boolean, timeout = 5000): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const startTime = Date.now()
|
||||
|
||||
const check = () => {
|
||||
if (condition()) {
|
||||
resolve()
|
||||
} else if (Date.now() - startTime > timeout) {
|
||||
reject(new Error(`Timeout waiting for condition`))
|
||||
} else {
|
||||
setTimeout(check, 100)
|
||||
}
|
||||
}
|
||||
|
||||
check()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { handleApiError, ApiErrorHandler } from '@/utils/errorHandler'
|
||||
|
||||
vi.mock('element-plus', () => ({
|
||||
ElMessage: {
|
||||
error: vi.fn(),
|
||||
success: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
describe('errorHandler', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.stubGlobal('localStorage', {
|
||||
removeItem: vi.fn(),
|
||||
})
|
||||
vi.stubGlobal('window', {
|
||||
location: { href: '' },
|
||||
})
|
||||
})
|
||||
|
||||
describe('handleApiError', () => {
|
||||
it('should call ApiErrorHandler.handle', () => {
|
||||
const mockError = { response: { status: 500, data: {} } }
|
||||
const handleSpy = vi.spyOn(ApiErrorHandler, 'handle')
|
||||
|
||||
handleApiError(mockError)
|
||||
|
||||
expect(handleSpy).toHaveBeenCalledWith(mockError)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ApiErrorHandler.handle', () => {
|
||||
it('should handle network error', () => {
|
||||
const mockError = new Error('Network Error')
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('网络连接失败,请检查网络设置')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Network Error:', mockError)
|
||||
})
|
||||
|
||||
it('should handle 400 Bad Request', () => {
|
||||
const mockError = {
|
||||
response: {
|
||||
status: 400,
|
||||
data: { message: 'Invalid parameters' },
|
||||
},
|
||||
}
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('Invalid parameters')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Bad Request:', mockError.response.data)
|
||||
})
|
||||
|
||||
it('should handle 401 Unauthorized', () => {
|
||||
const mockError = {
|
||||
response: {
|
||||
status: 401,
|
||||
data: { message: 'Unauthorized' },
|
||||
},
|
||||
}
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('登录已过期,请重新登录')
|
||||
expect(localStorage.removeItem).toHaveBeenCalledWith('token')
|
||||
expect(window.location.href).toBe('/login')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Unauthorized:', mockError.response.data)
|
||||
})
|
||||
|
||||
it('should handle 403 Forbidden', () => {
|
||||
const mockError = {
|
||||
response: {
|
||||
status: 403,
|
||||
data: { message: 'Access denied' },
|
||||
},
|
||||
}
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('没有权限访问该资源')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Forbidden:', mockError.response.data)
|
||||
})
|
||||
|
||||
it('should handle 404 Not Found', () => {
|
||||
const mockError = {
|
||||
response: {
|
||||
status: 404,
|
||||
data: { message: 'Resource not found' },
|
||||
},
|
||||
}
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('Resource not found')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Not Found:', mockError.response.data)
|
||||
})
|
||||
|
||||
it('should handle 409 Conflict', () => {
|
||||
const mockError = {
|
||||
response: {
|
||||
status: 409,
|
||||
data: { message: 'Resource conflict' },
|
||||
},
|
||||
}
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('Resource conflict')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Conflict:', mockError.response.data)
|
||||
})
|
||||
|
||||
it('should handle 422 Validation Error with details', () => {
|
||||
const mockError = {
|
||||
response: {
|
||||
status: 422,
|
||||
data: {
|
||||
message: 'Validation failed',
|
||||
details: {
|
||||
username: 'Username is required',
|
||||
password: 'Password is too short',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('Username is required、Password is too short')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Validation Error:', mockError.response.data)
|
||||
})
|
||||
|
||||
it('should handle 422 Validation Error without details', () => {
|
||||
const mockError = {
|
||||
response: {
|
||||
status: 422,
|
||||
data: { message: 'Validation failed' },
|
||||
},
|
||||
}
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('Validation failed')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Validation Error:', mockError.response.data)
|
||||
})
|
||||
|
||||
it('should handle 500 Internal Server Error', () => {
|
||||
const mockError = {
|
||||
response: {
|
||||
status: 500,
|
||||
data: { message: 'Server error' },
|
||||
},
|
||||
}
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('服务器内部错误,请稍后重试')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Internal Server Error:', mockError.response.data)
|
||||
})
|
||||
|
||||
it('should handle 502 Service Unavailable', () => {
|
||||
const mockError = {
|
||||
response: {
|
||||
status: 502,
|
||||
data: { message: 'Service unavailable' },
|
||||
},
|
||||
}
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('服务暂时不可用,请稍后重试')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Service Unavailable:', mockError.response.data)
|
||||
})
|
||||
|
||||
it('should handle 503 Service Unavailable', () => {
|
||||
const mockError = {
|
||||
response: {
|
||||
status: 503,
|
||||
data: { message: 'Service unavailable' },
|
||||
},
|
||||
}
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('服务暂时不可用,请稍后重试')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Service Unavailable:', mockError.response.data)
|
||||
})
|
||||
|
||||
it('should handle 504 Gateway Timeout', () => {
|
||||
const mockError = {
|
||||
response: {
|
||||
status: 504,
|
||||
data: { message: 'Gateway timeout' },
|
||||
},
|
||||
}
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('服务暂时不可用,请稍后重试')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Service Unavailable:', mockError.response.data)
|
||||
})
|
||||
|
||||
it('should handle unknown status code', () => {
|
||||
const mockError = {
|
||||
response: {
|
||||
status: 418,
|
||||
data: { message: 'I am a teapot' },
|
||||
},
|
||||
}
|
||||
const consoleSpy = vi.spyOn(console, 'error')
|
||||
|
||||
ApiErrorHandler.handle(mockError)
|
||||
|
||||
expect(ElMessage.error).toHaveBeenCalledWith('I am a teapot')
|
||||
expect(consoleSpy).toHaveBeenCalledWith('Unknown Error:', mockError.response.data)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -74,7 +74,11 @@
|
||||
sortable="custom"
|
||||
width="180"
|
||||
/>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="120"
|
||||
fixed="right"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
@@ -103,7 +107,10 @@
|
||||
title="异常详情"
|
||||
width="800px"
|
||||
>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions
|
||||
:column="1"
|
||||
border
|
||||
>
|
||||
<el-descriptions-item label="ID">
|
||||
{{ currentDetail.id }}
|
||||
</el-descriptions-item>
|
||||
@@ -120,7 +127,9 @@
|
||||
<pre style="max-height: 200px; overflow: auto;">{{ currentDetail.params }}</pre>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="异常信息">
|
||||
<div style="color: #f56c6c; word-break: break-all;">{{ currentDetail.errorMsg }}</div>
|
||||
<div style="color: #f56c6c; word-break: break-all;">
|
||||
{{ currentDetail.errorMsg }}
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="异常堆栈">
|
||||
<pre style="max-height: 300px; overflow: auto; font-size: 12px;">{{ currentDetail.exceptionStack }}</pre>
|
||||
@@ -133,7 +142,9 @@
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
<el-button @click="detailVisible = false">
|
||||
关闭
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
@@ -66,14 +66,19 @@
|
||||
:show-overflow-tooltip="true"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.params" class="params-content">
|
||||
<div
|
||||
v-if="row.params"
|
||||
class="params-content"
|
||||
>
|
||||
<el-popover
|
||||
placement="top"
|
||||
:width="500"
|
||||
trigger="hover"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="params-preview">{{ formatParams(row.params) }}</div>
|
||||
<div class="params-preview">
|
||||
{{ formatParams(row.params) }}
|
||||
</div>
|
||||
</template>
|
||||
<pre class="params-detail">{{ formatParams(row.params) }}</pre>
|
||||
</el-popover>
|
||||
|
||||
@@ -2,49 +2,69 @@
|
||||
<div class="dashboard">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="6">
|
||||
<el-card v-loading="loading" class="stat-card user-card">
|
||||
<el-card
|
||||
v-loading="loading"
|
||||
class="stat-card user-card"
|
||||
>
|
||||
<el-statistic
|
||||
title="用户总数"
|
||||
:value="stats.userCount"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon class="stat-icon user-icon"><User /></el-icon>
|
||||
<el-icon class="stat-icon user-icon">
|
||||
<User />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card v-loading="loading" class="stat-card role-card">
|
||||
<el-card
|
||||
v-loading="loading"
|
||||
class="stat-card role-card"
|
||||
>
|
||||
<el-statistic
|
||||
title="角色总数"
|
||||
:value="stats.roleCount"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon class="stat-icon role-icon"><UserFilled /></el-icon>
|
||||
<el-icon class="stat-icon role-icon">
|
||||
<UserFilled />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card v-loading="loading" class="stat-card login-card">
|
||||
<el-card
|
||||
v-loading="loading"
|
||||
class="stat-card login-card"
|
||||
>
|
||||
<el-statistic
|
||||
title="今日登录"
|
||||
:value="stats.todayLogin"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon class="stat-icon login-icon"><ArrowRight /></el-icon>
|
||||
<el-icon class="stat-icon login-icon">
|
||||
<ArrowRight />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card v-loading="loading" class="stat-card log-card">
|
||||
<el-card
|
||||
v-loading="loading"
|
||||
class="stat-card log-card"
|
||||
>
|
||||
<el-statistic
|
||||
title="操作日志"
|
||||
:value="stats.operationLog"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon class="stat-icon log-icon"><Document /></el-icon>
|
||||
<el-icon class="stat-icon log-icon">
|
||||
<Document />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-card>
|
||||
@@ -63,7 +83,9 @@
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">最近登录</span>
|
||||
<el-icon class="header-icon"><Clock /></el-icon>
|
||||
<el-icon class="header-icon">
|
||||
<Clock />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
<el-timeline>
|
||||
@@ -85,8 +107,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
<el-timeline-item v-if="recentLogins.length === 0" placement="top">
|
||||
<div class="empty-tip">暂无登录记录</div>
|
||||
<el-timeline-item
|
||||
v-if="recentLogins.length === 0"
|
||||
placement="top"
|
||||
>
|
||||
<div class="empty-tip">
|
||||
暂无登录记录
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-card>
|
||||
@@ -100,7 +127,9 @@
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">系统信息</span>
|
||||
<el-icon class="header-icon"><Setting /></el-icon>
|
||||
<el-icon class="header-icon">
|
||||
<Setting />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions
|
||||
|
||||
@@ -44,18 +44,19 @@
|
||||
sortable="custom"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
prop="roleName"
|
||||
label="角色名称"
|
||||
sortable="custom"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="code"
|
||||
prop="roleKey"
|
||||
label="角色标识"
|
||||
sortable="custom"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
label="描述"
|
||||
prop="roleSort"
|
||||
label="显示顺序"
|
||||
sortable="custom"
|
||||
/>
|
||||
<el-table-column
|
||||
label="状态"
|
||||
@@ -63,11 +64,11 @@
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="row.status === 'ACTIVE' ? 'success' : 'danger'"
|
||||
:type="row.status === RoleStatus.ACTIVE ? 'success' : 'danger'"
|
||||
effect="dark"
|
||||
style="font-weight: 500; font-size: 14px;"
|
||||
>
|
||||
{{ row.status === 'ACTIVE' ? '正常' : '禁用' }}
|
||||
{{ row.status === RoleStatus.ACTIVE ? '正常' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -130,21 +131,24 @@
|
||||
label="角色名称"
|
||||
required
|
||||
>
|
||||
<el-input v-model="formState.name" />
|
||||
<el-input v-model="formState.roleName" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="角色标识"
|
||||
required
|
||||
>
|
||||
<el-input
|
||||
v-model="formState.code"
|
||||
v-model="formState.roleKey"
|
||||
:disabled="!!formState.id"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input
|
||||
v-model="formState.description"
|
||||
type="textarea"
|
||||
<el-form-item
|
||||
label="显示顺序"
|
||||
required
|
||||
>
|
||||
<el-input-number
|
||||
v-model="formState.roleSort"
|
||||
:min="1"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
@@ -208,6 +212,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { roleApi, type Role, type CreateRoleRequest, type UpdateRoleRequest, type Permission } from '@/api/role.api'
|
||||
import { handleApiError } from '@/utils/errorHandler'
|
||||
import { RoleStatus } from '@/constants/status'
|
||||
|
||||
const loading = ref(false)
|
||||
const dataSource = ref<Role[]>([])
|
||||
@@ -225,12 +230,12 @@ const sortInfo = reactive({
|
||||
|
||||
const modalVisible = ref(false)
|
||||
const modalTitle = ref('')
|
||||
const formState = reactive<CreateRoleRequest & { id?: number }>({
|
||||
name: '',
|
||||
code: '',
|
||||
description: '',
|
||||
const formState = reactive<CreateRoleRequest & { id?: number; status?: RoleStatus }>({
|
||||
roleName: '',
|
||||
roleKey: '',
|
||||
roleSort: 1,
|
||||
permissions: [],
|
||||
status: 'ACTIVE'
|
||||
status: RoleStatus.ACTIVE
|
||||
})
|
||||
|
||||
const permissionDialogVisible = ref(false)
|
||||
@@ -282,11 +287,11 @@ const handleAdd = () => {
|
||||
modalTitle.value = '新增角色'
|
||||
Object.assign(formState, {
|
||||
id: undefined,
|
||||
name: '',
|
||||
code: '',
|
||||
description: '',
|
||||
roleName: '',
|
||||
roleKey: '',
|
||||
roleSort: 1,
|
||||
permissions: [],
|
||||
status: 'ACTIVE'
|
||||
status: RoleStatus.ACTIVE
|
||||
})
|
||||
modalVisible.value = true
|
||||
}
|
||||
@@ -295,9 +300,9 @@ const handleEdit = (row: Role) => {
|
||||
modalTitle.value = '编辑角色'
|
||||
Object.assign(formState, {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
code: row.code,
|
||||
description: row.description,
|
||||
roleName: row.roleName,
|
||||
roleKey: row.roleKey,
|
||||
roleSort: row.roleSort,
|
||||
status: row.status,
|
||||
permissions: []
|
||||
})
|
||||
@@ -325,17 +330,18 @@ const handleModalOk = async () => {
|
||||
try {
|
||||
if (formState.id) {
|
||||
const updateData: UpdateRoleRequest = {
|
||||
name: formState.name,
|
||||
description: formState.description,
|
||||
status: formState.status as 'ACTIVE' | 'INACTIVE'
|
||||
roleName: formState.roleName,
|
||||
roleKey: formState.roleKey,
|
||||
roleSort: formState.roleSort,
|
||||
status: formState.status
|
||||
}
|
||||
await roleApi.update(formState.id, updateData)
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
const createData: CreateRoleRequest = {
|
||||
name: formState.name,
|
||||
code: formState.code,
|
||||
description: formState.description,
|
||||
roleName: formState.roleName,
|
||||
roleKey: formState.roleKey,
|
||||
roleSort: formState.roleSort,
|
||||
permissions: formState.permissions
|
||||
}
|
||||
await roleApi.create(createData)
|
||||
|
||||
@@ -69,11 +69,11 @@
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="row.status === 'ACTIVE' ? 'success' : 'danger'"
|
||||
:type="row.status === 1 ? 'success' : 'danger'"
|
||||
effect="dark"
|
||||
style="font-weight: 500; font-size: 14px;"
|
||||
>
|
||||
{{ row.status === 'ACTIVE' ? '正常' : '禁用' }}
|
||||
{{ row.status === 1 ? '正常' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -163,11 +163,11 @@
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="formState.status">
|
||||
<el-option
|
||||
value="ACTIVE"
|
||||
:value="UserStatus.ACTIVE"
|
||||
label="正常"
|
||||
/>
|
||||
<el-option
|
||||
value="INACTIVE"
|
||||
:value="UserStatus.INACTIVE"
|
||||
label="禁用"
|
||||
/>
|
||||
</el-select>
|
||||
@@ -218,6 +218,7 @@ import { Search } from '@element-plus/icons-vue'
|
||||
import { userApi, type User, type CreateUserRequest, type UpdateUserRequest } from '@/api/user.api'
|
||||
import { roleApi, type Role } from '@/api/role.api'
|
||||
import { handleApiError } from '@/utils/errorHandler'
|
||||
import { UserStatus, StatusHelper } from '@/constants/status'
|
||||
|
||||
const loading = ref(false)
|
||||
const dataSource = ref<User[]>([])
|
||||
@@ -235,14 +236,14 @@ const sortInfo = reactive({
|
||||
|
||||
const modalVisible = ref(false)
|
||||
const modalTitle = ref('')
|
||||
const formState = reactive<CreateUserRequest & { id?: number }>({
|
||||
const formState = reactive<CreateUserRequest & { id?: number; status?: UserStatus }>({
|
||||
username: '',
|
||||
password: '',
|
||||
nickname: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
roles: [],
|
||||
status: 'ACTIVE'
|
||||
status: UserStatus.ACTIVE
|
||||
})
|
||||
|
||||
const roleDialogVisible = ref(false)
|
||||
@@ -342,7 +343,7 @@ const handleModalOk = async () => {
|
||||
nickname: formState.nickname,
|
||||
email: formState.email,
|
||||
phone: formState.phone,
|
||||
status: formState.status as 'ACTIVE' | 'INACTIVE'
|
||||
status: formState.status
|
||||
}
|
||||
await userApi.update(formState.id, updateData)
|
||||
ElMessage.success('更新成功')
|
||||
|
||||
Reference in New Issue
Block a user