提交已修改的代码
This commit is contained in:
1 parent
65d2d71469
commit
60cb1b09f8
32 files changed
+10359
-4786
No files matched your search
@@ -11,11 +11,18 @@ const clientId = import.meta.env.VITE_APP_CLIENT_ID;
|
||||
* @returns
|
||||
*/
|
||||
export function login(data: LoginData): AxiosPromise<LoginResult> {
|
||||
// 构建完全不包含验证码字段的登录参数
|
||||
const params = {
|
||||
...data,
|
||||
clientId: data.clientId || clientId,
|
||||
tenantId: data.tenantId,
|
||||
username: data.username,
|
||||
password: data.password,
|
||||
clientId: data.clientId || clientId || 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
grantType: data.grantType || 'password'
|
||||
};
|
||||
|
||||
// 调试:打印登录参数(生产环境应移除)
|
||||
console.log('登录请求参数:', params);
|
||||
|
||||
return request({
|
||||
url: '/auth/login',
|
||||
headers: {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 物业报修模块 API 接口入口
|
||||
*/
|
||||
|
||||
// 报修工单相关接口
|
||||
export * from './repairOrder';
|
||||
|
||||
// 维修类型相关接口
|
||||
export * from './repairCategory';
|
||||
|
||||
// 类型定义
|
||||
export * from './types';
|
||||
@@ -0,0 +1,513 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { RepairCategoryVO, RepairCategoryForm, RepairCategoryQuery } from '@/api/pm/types';
|
||||
|
||||
/**
|
||||
* 查询维修类型列表
|
||||
* @param query 查询参数
|
||||
*/
|
||||
export const listRepairCategory = (query?: RepairCategoryQuery): AxiosPromise<RepairCategoryVO[]> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
categoryName: '电路维修',
|
||||
categoryCode: 'ELECTRIC',
|
||||
categoryDescription: '电路故障、插座开关维修等',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 3,
|
||||
requiredSkills: '电工基础知识、安全操作规范',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 60,
|
||||
standardCost: 50.00,
|
||||
iconUrl: '/icons/electric.svg',
|
||||
colorCode: '#FFB020',
|
||||
sortOrder: 1,
|
||||
status: '1',
|
||||
usageCount: 45,
|
||||
lastUsedTime: '2025-01-22 15:30:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:00:00',
|
||||
children: [
|
||||
{
|
||||
id: '11',
|
||||
categoryName: '照明设备',
|
||||
categoryCode: 'ELECTRIC_LIGHT',
|
||||
categoryDescription: '灯具、照明线路维修',
|
||||
parentId: '1',
|
||||
ancestorIds: '0,1',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '电工基础、照明设备知识',
|
||||
skillLevel: '1',
|
||||
estimatedDuration: 30,
|
||||
standardCost: 30.00,
|
||||
iconUrl: '/icons/light.svg',
|
||||
colorCode: '#FFB020',
|
||||
sortOrder: 1,
|
||||
status: '1',
|
||||
usageCount: 18,
|
||||
lastUsedTime: '2025-01-22 09:15:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:30:00'
|
||||
},
|
||||
{
|
||||
id: '12',
|
||||
categoryName: '插座开关',
|
||||
categoryCode: 'ELECTRIC_SOCKET',
|
||||
categoryDescription: '插座、开关面板维修更换',
|
||||
parentId: '1',
|
||||
ancestorIds: '0,1',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '电工基础、安全操作',
|
||||
skillLevel: '1',
|
||||
estimatedDuration: 20,
|
||||
standardCost: 25.00,
|
||||
iconUrl: '/icons/socket.svg',
|
||||
colorCode: '#FFB020',
|
||||
sortOrder: 2,
|
||||
status: '1',
|
||||
usageCount: 15,
|
||||
lastUsedTime: '2025-01-21 14:20:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:45:00'
|
||||
},
|
||||
{
|
||||
id: '13',
|
||||
categoryName: '配电设备',
|
||||
categoryCode: 'ELECTRIC_POWER',
|
||||
categoryDescription: '配电箱、电表等设备维修',
|
||||
parentId: '1',
|
||||
ancestorIds: '0,1',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '高级电工、配电知识',
|
||||
skillLevel: '3',
|
||||
estimatedDuration: 120,
|
||||
standardCost: 80.00,
|
||||
iconUrl: '/icons/power.svg',
|
||||
colorCode: '#FFB020',
|
||||
sortOrder: 3,
|
||||
status: '1',
|
||||
usageCount: 12,
|
||||
lastUsedTime: '2025-01-20 16:45:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:00:00'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
categoryName: '水暖维修',
|
||||
categoryCode: 'PLUMBING',
|
||||
categoryDescription: '管道、水龙头、暖气等维修',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 3,
|
||||
requiredSkills: '管工基础知识、焊接技能',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 90,
|
||||
standardCost: 60.00,
|
||||
iconUrl: '/icons/plumbing.svg',
|
||||
colorCode: '#4FC3F7',
|
||||
sortOrder: 2,
|
||||
status: '1',
|
||||
usageCount: 38,
|
||||
lastUsedTime: '2025-01-23 10:30:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:15:00',
|
||||
children: [
|
||||
{
|
||||
id: '21',
|
||||
categoryName: '给水设备',
|
||||
categoryCode: 'PLUMBING_WATER',
|
||||
categoryDescription: '水龙头、阀门、管道维修',
|
||||
parentId: '2',
|
||||
ancestorIds: '0,2',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '管工基础、维修工具使用',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 60,
|
||||
standardCost: 40.00,
|
||||
iconUrl: '/icons/water.svg',
|
||||
colorCode: '#4FC3F7',
|
||||
sortOrder: 1,
|
||||
status: '1',
|
||||
usageCount: 22,
|
||||
lastUsedTime: '2025-01-23 10:30:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:30:00'
|
||||
},
|
||||
{
|
||||
id: '22',
|
||||
categoryName: '排水设备',
|
||||
categoryCode: 'PLUMBING_DRAIN',
|
||||
categoryDescription: '下水道、地漏疏通维修',
|
||||
parentId: '2',
|
||||
ancestorIds: '0,2',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '管道疏通、清洁设备使用',
|
||||
skillLevel: '1',
|
||||
estimatedDuration: 45,
|
||||
standardCost: 35.00,
|
||||
iconUrl: '/icons/drain.svg',
|
||||
colorCode: '#4FC3F7',
|
||||
sortOrder: 2,
|
||||
status: '1',
|
||||
usageCount: 10,
|
||||
lastUsedTime: '2025-01-22 08:45:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:45:00'
|
||||
},
|
||||
{
|
||||
id: '23',
|
||||
categoryName: '供暖设备',
|
||||
categoryCode: 'PLUMBING_HEATING',
|
||||
categoryDescription: '暖气片、供暖管道维修',
|
||||
parentId: '2',
|
||||
ancestorIds: '0,2',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '供暖系统知识、焊接技能',
|
||||
skillLevel: '3',
|
||||
estimatedDuration: 150,
|
||||
standardCost: 100.00,
|
||||
iconUrl: '/icons/heating.svg',
|
||||
colorCode: '#4FC3F7',
|
||||
sortOrder: 3,
|
||||
status: '1',
|
||||
usageCount: 6,
|
||||
lastUsedTime: '2025-01-18 13:20:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:00:00'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
categoryName: '土建维修',
|
||||
categoryCode: 'CONSTRUCTION',
|
||||
categoryDescription: '墙面、地面、门窗等土建维修',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 2,
|
||||
requiredSkills: '泥工、油漆工基础技能',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 180,
|
||||
standardCost: 80.00,
|
||||
iconUrl: '/icons/construction.svg',
|
||||
colorCode: '#8BC34A',
|
||||
sortOrder: 3,
|
||||
status: '1',
|
||||
usageCount: 28,
|
||||
lastUsedTime: '2025-01-21 11:15:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:30:00',
|
||||
children: [
|
||||
{
|
||||
id: '31',
|
||||
categoryName: '墙面修缮',
|
||||
categoryCode: 'CONSTRUCTION_WALL',
|
||||
categoryDescription: '墙面粉刷、裂缝修补',
|
||||
parentId: '3',
|
||||
ancestorIds: '0,3',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '油漆工、简单泥工技能',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 120,
|
||||
standardCost: 60.00,
|
||||
iconUrl: '/icons/wall.svg',
|
||||
colorCode: '#8BC34A',
|
||||
sortOrder: 1,
|
||||
status: '1',
|
||||
usageCount: 16,
|
||||
lastUsedTime: '2025-01-21 11:15:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:15:00'
|
||||
},
|
||||
{
|
||||
id: '32',
|
||||
categoryName: '门窗维修',
|
||||
categoryCode: 'CONSTRUCTION_DOOR',
|
||||
categoryDescription: '门窗锁具、玻璃更换维修',
|
||||
parentId: '3',
|
||||
ancestorIds: '0,3',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '木工基础、五金安装',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 90,
|
||||
standardCost: 45.00,
|
||||
iconUrl: '/icons/door.svg',
|
||||
colorCode: '#8BC34A',
|
||||
sortOrder: 2,
|
||||
status: '1',
|
||||
usageCount: 12,
|
||||
lastUsedTime: '2025-01-19 15:30:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:30:00'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
categoryName: '设备维修',
|
||||
categoryCode: 'EQUIPMENT',
|
||||
categoryDescription: '各类设备故障维修',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 2,
|
||||
requiredSkills: '机电维修、设备操作',
|
||||
skillLevel: '3',
|
||||
estimatedDuration: 120,
|
||||
standardCost: 100.00,
|
||||
iconUrl: '/icons/equipment.svg',
|
||||
colorCode: '#FF7043',
|
||||
sortOrder: 4,
|
||||
status: '1',
|
||||
usageCount: 32,
|
||||
lastUsedTime: '2025-01-23 09:15:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:45:00',
|
||||
children: [
|
||||
{
|
||||
id: '41',
|
||||
categoryName: '空调设备',
|
||||
categoryCode: 'EQUIPMENT_AC',
|
||||
categoryDescription: '空调安装、维修、清洗',
|
||||
parentId: '4',
|
||||
ancestorIds: '0,4',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '制冷设备维修、安全操作',
|
||||
skillLevel: '3',
|
||||
estimatedDuration: 150,
|
||||
standardCost: 120.00,
|
||||
iconUrl: '/icons/ac.svg',
|
||||
colorCode: '#FF7043',
|
||||
sortOrder: 1,
|
||||
status: '1',
|
||||
usageCount: 20,
|
||||
lastUsedTime: '2025-01-22 17:30:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:45:00'
|
||||
},
|
||||
{
|
||||
id: '42',
|
||||
categoryName: '教学设备',
|
||||
categoryCode: 'EQUIPMENT_TEACHING',
|
||||
categoryDescription: '投影仪、音响、电脑等设备维修',
|
||||
parentId: '4',
|
||||
ancestorIds: '0,4',
|
||||
levelDepth: 2,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '电子设备维修、计算机知识',
|
||||
skillLevel: '3',
|
||||
estimatedDuration: 90,
|
||||
standardCost: 80.00,
|
||||
iconUrl: '/icons/teaching.svg',
|
||||
colorCode: '#FF7043',
|
||||
sortOrder: 2,
|
||||
status: '1',
|
||||
usageCount: 12,
|
||||
lastUsedTime: '2025-01-23 09:15:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 12:00:00'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
categoryName: '园林维修',
|
||||
categoryCode: 'GARDEN',
|
||||
categoryDescription: '绿化养护、园林设施维修',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '园艺知识、植物养护',
|
||||
skillLevel: '1',
|
||||
estimatedDuration: 240,
|
||||
standardCost: 40.00,
|
||||
iconUrl: '/icons/garden.svg',
|
||||
colorCode: '#66BB6A',
|
||||
sortOrder: 5,
|
||||
status: '1',
|
||||
usageCount: 8,
|
||||
lastUsedTime: '2025-01-15 14:20:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:00:00'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
categoryName: '清洁维护',
|
||||
categoryCode: 'CLEANING',
|
||||
categoryDescription: '清洁卫生、环境维护',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 0,
|
||||
requiredSkills: '清洁知识、环保意识',
|
||||
skillLevel: '1',
|
||||
estimatedDuration: 120,
|
||||
standardCost: 30.00,
|
||||
iconUrl: '/icons/cleaning.svg',
|
||||
colorCode: '#42A5F5',
|
||||
sortOrder: 6,
|
||||
status: '1',
|
||||
usageCount: 5,
|
||||
lastUsedTime: '2025-01-16 10:45:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 11:15:00'
|
||||
}
|
||||
]
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询维修类型详细信息
|
||||
* @param id 类型ID
|
||||
*/
|
||||
export const getRepairCategory = (id: string | number): AxiosPromise<RepairCategoryVO> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: {
|
||||
id: id,
|
||||
categoryName: '电路维修',
|
||||
categoryCode: 'ELECTRIC',
|
||||
categoryDescription: '电路故障、插座开关维修等',
|
||||
parentId: '0',
|
||||
ancestorIds: '0',
|
||||
levelDepth: 1,
|
||||
childrenCount: 3,
|
||||
requiredSkills: '电工基础知识、安全操作规范',
|
||||
skillLevel: '2',
|
||||
estimatedDuration: 60,
|
||||
standardCost: 50.00,
|
||||
iconUrl: '/icons/electric.svg',
|
||||
colorCode: '#FFB020',
|
||||
sortOrder: 1,
|
||||
status: '1',
|
||||
usageCount: 45,
|
||||
lastUsedTime: '2025-01-22 15:30:00',
|
||||
remark: null,
|
||||
createTime: '2024-12-01 10:00:00'
|
||||
}
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 200);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增维修类型
|
||||
* @param data 类型数据
|
||||
*/
|
||||
export const addRepairCategory = (data: RepairCategoryForm) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '新增成功',
|
||||
data: {
|
||||
id: Date.now(),
|
||||
categoryCode: data.categoryCode
|
||||
}
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 600);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改维修类型
|
||||
* @param data 类型数据
|
||||
*/
|
||||
export const updateRepairCategory = (data: RepairCategoryForm) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '修改成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除维修类型
|
||||
* @param id 类型ID
|
||||
*/
|
||||
export const delRepairCategory = (id: string | number | Array<string | number>) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '删除成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 400);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取维修类型树形结构
|
||||
*/
|
||||
export const getRepairCategoryTree = (): AxiosPromise<RepairCategoryVO[]> => {
|
||||
return listRepairCategory();
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取维修类型下拉选项
|
||||
*/
|
||||
export const getRepairCategoryOptions = (): AxiosPromise<Array<{value: string | number, label: string}>> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: [
|
||||
{ value: '1', label: '电路维修' },
|
||||
{ value: '11', label: '- 照明设备' },
|
||||
{ value: '12', label: '- 插座开关' },
|
||||
{ value: '13', label: '- 配电设备' },
|
||||
{ value: '2', label: '水暖维修' },
|
||||
{ value: '21', label: '- 给水设备' },
|
||||
{ value: '22', label: '- 排水设备' },
|
||||
{ value: '23', label: '- 供暖设备' },
|
||||
{ value: '3', label: '土建维修' },
|
||||
{ value: '31', label: '- 墙面修缮' },
|
||||
{ value: '32', label: '- 门窗维修' },
|
||||
{ value: '4', label: '设备维修' },
|
||||
{ value: '41', label: '- 空调设备' },
|
||||
{ value: '42', label: '- 教学设备' },
|
||||
{ value: '5', label: '园林维修' },
|
||||
{ value: '6', label: '清洁维护' }
|
||||
]
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 200);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,650 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import {
|
||||
RepairOrderVO,
|
||||
RepairOrderForm,
|
||||
RepairOrderQuery,
|
||||
RepairAttachmentVO,
|
||||
RepairProgressVO,
|
||||
AssignOrderForm,
|
||||
ProgressUpdateForm,
|
||||
RepairStatisticsVO
|
||||
} from '@/api/pm/types';
|
||||
|
||||
/**
|
||||
* 查询报修工单列表
|
||||
* @param query 查询参数
|
||||
*/
|
||||
// 生成模拟数据的函数
|
||||
const generateMockData = () => {
|
||||
const baseData = [
|
||||
{
|
||||
id: '1',
|
||||
tenantId: '000000',
|
||||
orderNo: 'RO20250123001',
|
||||
title: '宿舍A304水龙头漏水',
|
||||
description: '水龙头一直滴水,影响正常使用,需要及时维修',
|
||||
repairLocation: '学生宿舍A栋304室',
|
||||
categoryId: '2',
|
||||
categoryName: '水暖维修',
|
||||
priorityLevel: 2,
|
||||
expectedFinishTime: '2025-01-24 18:00:00',
|
||||
reporterId: '1001',
|
||||
reporterName: '张小明',
|
||||
reporterPhone: '13800138001',
|
||||
reporterDept: '计算机学院',
|
||||
orderStatus: '1',
|
||||
assignTime: null,
|
||||
assigneeId: null,
|
||||
assigneeName: null,
|
||||
assignRemark: null,
|
||||
startTime: null,
|
||||
finishTime: null,
|
||||
totalDuration: null,
|
||||
repairResult: null,
|
||||
usedMaterials: null,
|
||||
followSuggestions: null,
|
||||
evaluationScore: null,
|
||||
evaluationTime: null,
|
||||
attachmentCount: 2,
|
||||
progressCount: 1,
|
||||
cancelReason: null,
|
||||
remark: null,
|
||||
createTime: '2025-01-23 10:30:00',
|
||||
updateTime: '2025-01-23 10:30:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
tenantId: '000000',
|
||||
orderNo: 'RO20250123002',
|
||||
title: '教学楼B201投影仪故障',
|
||||
description: '投影仪无法正常开机,影响教学活动',
|
||||
repairLocation: '教学楼B栋201教室',
|
||||
categoryId: '4',
|
||||
categoryName: '设备维修',
|
||||
priorityLevel: 1,
|
||||
expectedFinishTime: '2025-01-23 16:00:00',
|
||||
reporterId: '1002',
|
||||
reporterName: '李老师',
|
||||
reporterPhone: '13800138002',
|
||||
reporterDept: '电子工程学院',
|
||||
orderStatus: '2',
|
||||
assignTime: '2025-01-23 11:00:00',
|
||||
assigneeId: '2001',
|
||||
assigneeName: '王师傅',
|
||||
assignRemark: '紧急处理,明天有重要课程',
|
||||
startTime: null,
|
||||
finishTime: null,
|
||||
totalDuration: null,
|
||||
repairResult: null,
|
||||
usedMaterials: null,
|
||||
followSuggestions: null,
|
||||
evaluationScore: null,
|
||||
evaluationTime: null,
|
||||
attachmentCount: 1,
|
||||
progressCount: 2,
|
||||
cancelReason: null,
|
||||
remark: null,
|
||||
createTime: '2025-01-23 09:15:00',
|
||||
updateTime: '2025-01-23 11:00:00'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
tenantId: '000000',
|
||||
orderNo: 'RO20250122003',
|
||||
title: '图书馆空调不制热',
|
||||
description: '阅览室空调无法制热,温度较低影响学习',
|
||||
repairLocation: '图书馆一楼阅览室',
|
||||
categoryId: '4',
|
||||
categoryName: '设备维修',
|
||||
priorityLevel: 3,
|
||||
expectedFinishTime: '2025-01-24 12:00:00',
|
||||
reporterId: '1003',
|
||||
reporterName: '赵管理员',
|
||||
reporterPhone: '13800138003',
|
||||
reporterDept: '图书馆',
|
||||
orderStatus: '4',
|
||||
assignTime: '2025-01-22 14:30:00',
|
||||
assigneeId: '2002',
|
||||
assigneeName: '刘师傅',
|
||||
assignRemark: '检查空调系统',
|
||||
startTime: '2025-01-22 15:00:00',
|
||||
finishTime: '2025-01-22 17:30:00',
|
||||
totalDuration: 150,
|
||||
repairResult: '空调滤网堵塞,已清洗并更换新滤网,制热功能恢复正常',
|
||||
usedMaterials: '空调滤网 x1',
|
||||
followSuggestions: '建议每季度清洗一次滤网',
|
||||
evaluationScore: null,
|
||||
evaluationTime: null,
|
||||
attachmentCount: 3,
|
||||
progressCount: 5,
|
||||
cancelReason: null,
|
||||
remark: null,
|
||||
createTime: '2025-01-22 13:45:00',
|
||||
updateTime: '2025-01-22 17:30:00'
|
||||
}
|
||||
];
|
||||
|
||||
// 模拟数据生成器参数
|
||||
const categories = [
|
||||
{ id: '1', name: '电路维修' },
|
||||
{ id: '2', name: '水暖维修' },
|
||||
{ id: '3', name: '土建维修' },
|
||||
{ id: '4', name: '设备维修' },
|
||||
{ id: '5', name: '园林维修' },
|
||||
{ id: '6', name: '清洁维护' }
|
||||
];
|
||||
|
||||
const locations = [
|
||||
'学生宿舍A栋', '学生宿舍B栋', '学生宿舍C栋', '教学楼A栋', '教学楼B栋', '教学楼C栋',
|
||||
'图书馆', '实验楼', '行政楼', '食堂', '体育馆', '医务室', '校园广场', '停车场',
|
||||
'学术报告厅', '会议室', '办公楼', '研究生楼', '国际交流中心', '创新创业中心'
|
||||
];
|
||||
|
||||
const reporters = [
|
||||
{ name: '张三', phone: '13800138001', dept: '计算机学院' },
|
||||
{ name: '李四', phone: '13800138002', dept: '电子工程学院' },
|
||||
{ name: '王五', phone: '13800138003', dept: '机械工程学院' },
|
||||
{ name: '赵六', phone: '13800138004', dept: '化学工程学院' },
|
||||
{ name: '钱七', phone: '13800138005', dept: '材料科学学院' },
|
||||
{ name: '孙八', phone: '13800138006', dept: '环境工程学院' },
|
||||
{ name: '周九', phone: '13800138007', dept: '生物工程学院' },
|
||||
{ name: '吴十', phone: '13800138008', dept: '数学学院' },
|
||||
{ name: '郑管理员', phone: '13800138009', dept: '后勤管理处' },
|
||||
{ name: '王老师', phone: '13800138010', dept: '物理学院' }
|
||||
];
|
||||
|
||||
const assignees = [
|
||||
{ id: '2001', name: '王师傅' },
|
||||
{ id: '2002', name: '刘师傅' },
|
||||
{ id: '2003', name: '张师傅' },
|
||||
{ id: '2004', name: '李师傅' }
|
||||
];
|
||||
|
||||
const titles = [
|
||||
'水龙头漏水', '灯泡不亮', '门锁损坏', '窗户关不严', '空调不制冷', '投影仪故障',
|
||||
'网络故障', '桌椅损坏', '黑板擦不干净', '卫生间堵塞', '暖气不热', '电梯故障',
|
||||
'监控设备故障', '音响设备异常', '打印机故障', '饮水机不工作', '垃圾桶损坏',
|
||||
'路灯不亮', '草坪需修剪', '花坛清理', '墙面脱落', '地板破损', '天花板漏水'
|
||||
];
|
||||
|
||||
// 生成100条新数据
|
||||
const additionalData = [];
|
||||
for (let i = 4; i <= 103; i++) {
|
||||
const category = categories[Math.floor(Math.random() * categories.length)];
|
||||
const location = locations[Math.floor(Math.random() * locations.length)];
|
||||
const reporter = reporters[Math.floor(Math.random() * reporters.length)];
|
||||
const title = titles[Math.floor(Math.random() * titles.length)];
|
||||
const priorityLevel = Math.floor(Math.random() * 5) + 1;
|
||||
const status = ['1', '2', '3', '4', '5', '6', '9'][Math.floor(Math.random() * 7)];
|
||||
|
||||
// 根据状态生成相应的时间和其他字段
|
||||
const createTime = new Date(2025, 0, Math.floor(Math.random() * 23) + 1, Math.floor(Math.random() * 24), Math.floor(Math.random() * 60));
|
||||
const assignTime = status !== '1' ? new Date(createTime.getTime() + Math.random() * 24 * 60 * 60 * 1000) : null;
|
||||
const startTime = ['3', '4', '5', '6'].includes(status) ? new Date(assignTime!.getTime() + Math.random() * 4 * 60 * 60 * 1000) : null;
|
||||
const finishTime = ['4', '5', '6'].includes(status) ? new Date(startTime!.getTime() + Math.random() * 8 * 60 * 60 * 1000) : null;
|
||||
const assignee = status !== '1' ? assignees[Math.floor(Math.random() * assignees.length)] : null;
|
||||
|
||||
const roomNumber = Math.floor(Math.random() * 500) + 100;
|
||||
const fullLocation = `${location}${roomNumber}室`;
|
||||
|
||||
additionalData.push({
|
||||
id: i.toString(),
|
||||
tenantId: '000000',
|
||||
orderNo: `RO${createTime.getFullYear()}${String(createTime.getMonth() + 1).padStart(2, '0')}${String(createTime.getDate()).padStart(2, '0')}${String(i).padStart(3, '0')}`,
|
||||
title: `${fullLocation}${title}`,
|
||||
description: `${fullLocation}发生${title}问题,需要及时处理。${Math.random() > 0.5 ? '影响正常使用。' : '请尽快安排维修。'}`,
|
||||
repairLocation: fullLocation,
|
||||
categoryId: category.id,
|
||||
categoryName: category.name,
|
||||
priorityLevel,
|
||||
expectedFinishTime: new Date(createTime.getTime() + Math.random() * 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 19).replace('T', ' '),
|
||||
reporterId: (1000 + (i % 50)).toString(),
|
||||
reporterName: reporter.name,
|
||||
reporterPhone: reporter.phone,
|
||||
reporterDept: reporter.dept,
|
||||
orderStatus: status,
|
||||
assignTime: assignTime?.toISOString().slice(0, 19).replace('T', ' ') || null,
|
||||
assigneeId: assignee?.id || null,
|
||||
assigneeName: assignee?.name || null,
|
||||
assignRemark: status !== '1' ? (Math.random() > 0.5 ? '请及时处理' : '优先处理此工单') : null,
|
||||
startTime: startTime?.toISOString().slice(0, 19).replace('T', ' ') || null,
|
||||
finishTime: finishTime?.toISOString().slice(0, 19).replace('T', ' ') || null,
|
||||
totalDuration: finishTime && startTime ? Math.floor((finishTime.getTime() - startTime.getTime()) / 60000) : null,
|
||||
repairResult: ['4', '5', '6'].includes(status) ? `已完成${title}维修,功能恢复正常` : null,
|
||||
usedMaterials: ['4', '5', '6'].includes(status) && Math.random() > 0.5 ? '维修材料若干' : null,
|
||||
followSuggestions: ['4', '5', '6'].includes(status) && Math.random() > 0.7 ? '建议定期检查维护' : null,
|
||||
evaluationScore: status === '5' ? Math.round((Math.random() * 2 + 3) * 10) / 10 : null,
|
||||
evaluationTime: status === '5' ? new Date(finishTime!.getTime() + Math.random() * 24 * 60 * 60 * 1000).toISOString().slice(0, 19).replace('T', ' ') : null,
|
||||
attachmentCount: Math.floor(Math.random() * 4),
|
||||
progressCount: status === '1' ? 1 : Math.floor(Math.random() * 5) + 1,
|
||||
cancelReason: status === '9' ? '用户取消申请' : null,
|
||||
remark: Math.random() > 0.8 ? '备注信息' : null,
|
||||
createTime: createTime.toISOString().slice(0, 19).replace('T', ' '),
|
||||
updateTime: (finishTime || assignTime || createTime).toISOString().slice(0, 19).replace('T', ' ')
|
||||
});
|
||||
}
|
||||
|
||||
return [...baseData, ...additionalData];
|
||||
};
|
||||
|
||||
export const listRepairOrder = (query?: RepairOrderQuery): AxiosPromise<RepairOrderVO[]> => {
|
||||
// 暂时返回模拟数据
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const allData = generateMockData();
|
||||
|
||||
// 应用查询过滤
|
||||
let filteredData = allData;
|
||||
|
||||
if (query?.orderNo) {
|
||||
filteredData = filteredData.filter(item => item.orderNo.includes(query.orderNo!));
|
||||
}
|
||||
if (query?.title) {
|
||||
filteredData = filteredData.filter(item => item.title.includes(query.title!));
|
||||
}
|
||||
if (query?.orderStatus) {
|
||||
filteredData = filteredData.filter(item => item.orderStatus === query.orderStatus);
|
||||
}
|
||||
if (query?.priorityLevel) {
|
||||
filteredData = filteredData.filter(item => item.priorityLevel === query.priorityLevel);
|
||||
}
|
||||
if (query?.reporterName) {
|
||||
filteredData = filteredData.filter(item => item.reporterName.includes(query.reporterName!));
|
||||
}
|
||||
if (query?.assigneeName) {
|
||||
filteredData = filteredData.filter(item => item.assigneeName?.includes(query.assigneeName!));
|
||||
}
|
||||
if (query?.categoryId) {
|
||||
filteredData = filteredData.filter(item => item.categoryId === query.categoryId);
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
const pageNum = query?.pageNum || 1;
|
||||
const pageSize = query?.pageSize || 10;
|
||||
const startIndex = (pageNum - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
const pageData = filteredData.slice(startIndex, endIndex);
|
||||
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: pageData,
|
||||
total: filteredData.length
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询报修工单详细信息
|
||||
* @param id 工单ID
|
||||
*/
|
||||
export const getRepairOrder = (id: string | number): AxiosPromise<RepairOrderVO> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: {
|
||||
id: id,
|
||||
tenantId: '000000',
|
||||
orderNo: 'RO20250123001',
|
||||
title: '宿舍A304水龙头漏水',
|
||||
description: '水龙头一直滴水,影响正常使用,需要及时维修',
|
||||
repairLocation: '学生宿舍A栋304室',
|
||||
categoryId: '2',
|
||||
categoryName: '水暖维修',
|
||||
priorityLevel: 2,
|
||||
expectedFinishTime: '2025-01-24 18:00:00',
|
||||
reporterId: '1001',
|
||||
reporterName: '张小明',
|
||||
reporterPhone: '13800138001',
|
||||
reporterDept: '计算机学院',
|
||||
orderStatus: '1',
|
||||
assignTime: null,
|
||||
assigneeId: null,
|
||||
assigneeName: null,
|
||||
assignRemark: null,
|
||||
startTime: null,
|
||||
finishTime: null,
|
||||
totalDuration: null,
|
||||
repairResult: null,
|
||||
usedMaterials: null,
|
||||
followSuggestions: null,
|
||||
evaluationScore: null,
|
||||
evaluationTime: null,
|
||||
attachmentCount: 2,
|
||||
progressCount: 1,
|
||||
cancelReason: null,
|
||||
remark: null,
|
||||
createTime: '2025-01-23 10:30:00',
|
||||
updateTime: '2025-01-23 10:30:00'
|
||||
}
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增报修工单
|
||||
* @param data 工单数据
|
||||
*/
|
||||
export const addRepairOrder = (data: RepairOrderForm) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '报修申请提交成功',
|
||||
data: {
|
||||
id: Date.now(),
|
||||
orderNo: `RO${new Date().toISOString().slice(0, 10).replace(/-/g, '')}${String(Date.now()).slice(-3)}`
|
||||
}
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 800);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改报修工单
|
||||
* @param data 工单数据
|
||||
*/
|
||||
export const updateRepairOrder = (data: RepairOrderForm) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '修改成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 600);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除报修工单
|
||||
* @param id 工单ID
|
||||
*/
|
||||
export const delRepairOrder = (id: string | number | Array<string | number>) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '删除成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 分派工单
|
||||
* @param data 分派数据
|
||||
*/
|
||||
export const assignRepairOrder = (data: AssignOrderForm) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '工单分派成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 700);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 接收工单
|
||||
* @param orderId 工单ID
|
||||
*/
|
||||
export const acceptRepairOrder = (orderId: string | number) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '工单接收成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 拒绝工单
|
||||
* @param orderId 工单ID
|
||||
* @param reason 拒绝原因
|
||||
*/
|
||||
export const rejectRepairOrder = (orderId: string | number, reason: string) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '工单拒绝成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新维修进度
|
||||
* @param data 进度数据
|
||||
*/
|
||||
export const updateRepairProgress = (data: ProgressUpdateForm) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '进度更新成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 600);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 完成工单
|
||||
* @param orderId 工单ID
|
||||
* @param data 完成数据
|
||||
*/
|
||||
export const completeRepairOrder = (orderId: string | number, data: any) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '工单完成成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 700);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 取消工单
|
||||
* @param orderId 工单ID
|
||||
* @param reason 取消原因
|
||||
*/
|
||||
export const cancelRepairOrder = (orderId: string | number, reason: string) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '工单取消成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取工单附件列表
|
||||
* @param orderId 工单ID
|
||||
*/
|
||||
export const getRepairAttachments = (orderId: string | number): AxiosPromise<RepairAttachmentVO[]> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
orderId: orderId,
|
||||
fileName: '水龙头漏水现场图1.jpg',
|
||||
filePath: '/uploads/repair/images/2025/01/23/leak1.jpg',
|
||||
fileSize: 245678,
|
||||
fileType: 'image',
|
||||
fileExtension: 'jpg',
|
||||
fileMimeType: 'image/jpeg',
|
||||
uploadStage: '1',
|
||||
uploaderId: '1001',
|
||||
uploaderName: '张小明',
|
||||
thumbnailPath: '/uploads/repair/thumbs/2025/01/23/leak1_thumb.jpg',
|
||||
duration: null,
|
||||
resolution: '1920x1080',
|
||||
description: '水龙头漏水现场照片',
|
||||
sortOrder: 1,
|
||||
createTime: '2025-01-23 10:30:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
orderId: orderId,
|
||||
fileName: '水龙头漏水视频.mp4',
|
||||
filePath: '/uploads/repair/videos/2025/01/23/leak_video.mp4',
|
||||
fileSize: 5234567,
|
||||
fileType: 'video',
|
||||
fileExtension: 'mp4',
|
||||
fileMimeType: 'video/mp4',
|
||||
uploadStage: '1',
|
||||
uploaderId: '1001',
|
||||
uploaderName: '张小明',
|
||||
thumbnailPath: '/uploads/repair/thumbs/2025/01/23/leak_video_thumb.jpg',
|
||||
duration: 15,
|
||||
resolution: '1920x1080',
|
||||
description: '漏水情况视频记录',
|
||||
sortOrder: 2,
|
||||
createTime: '2025-01-23 10:32:00'
|
||||
}
|
||||
]
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取工单进度列表
|
||||
* @param orderId 工单ID
|
||||
*/
|
||||
export const getRepairProgress = (orderId: string | number): AxiosPromise<RepairProgressVO[]> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
orderId: orderId,
|
||||
progressType: '1',
|
||||
progressStatus: '工单已创建',
|
||||
progressDetail: '用户提交报修申请,工单已生成,等待管理员分派',
|
||||
operatorId: '1001',
|
||||
operatorName: '张小明',
|
||||
operatorRole: 'user',
|
||||
locationInfo: '学生宿舍A栋304室',
|
||||
gpsLatitude: 31.230416,
|
||||
gpsLongitude: 121.473701,
|
||||
operationTime: '2025-01-23 10:30:00',
|
||||
estimatedDuration: null,
|
||||
attachmentIds: '1,2',
|
||||
nextStep: '等待管理员分派维修员',
|
||||
issuesEncountered: null,
|
||||
materialsNeeded: null,
|
||||
remark: null,
|
||||
createTime: '2025-01-23 10:30:00'
|
||||
}
|
||||
]
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取报修统计数据
|
||||
*/
|
||||
export const getRepairStatistics = (): AxiosPromise<RepairStatisticsVO> => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '操作成功',
|
||||
data: {
|
||||
totalOrders: 156,
|
||||
pendingAssign: 8,
|
||||
inProgress: 12,
|
||||
completed: 132,
|
||||
cancelled: 4,
|
||||
avgProcessTime: 4.5,
|
||||
avgEvaluationScore: 4.3,
|
||||
monthlyNewOrders: 23,
|
||||
monthlyCompletedOrders: 19,
|
||||
statusDistribution: [
|
||||
{ status: '1', count: 8, percentage: 5.1 },
|
||||
{ status: '2', count: 3, percentage: 1.9 },
|
||||
{ status: '3', count: 9, percentage: 5.8 },
|
||||
{ status: '4', count: 2, percentage: 1.3 },
|
||||
{ status: '5', count: 1, percentage: 0.6 },
|
||||
{ status: '6', count: 129, percentage: 82.7 },
|
||||
{ status: '9', count: 4, percentage: 2.6 }
|
||||
],
|
||||
categoryDistribution: [
|
||||
{ categoryName: '电路维修', count: 45, percentage: 28.8 },
|
||||
{ categoryName: '水暖维修', count: 38, percentage: 24.4 },
|
||||
{ categoryName: '土建维修', count: 28, percentage: 17.9 },
|
||||
{ categoryName: '设备维修', count: 32, percentage: 20.5 },
|
||||
{ categoryName: '园林维修', count: 8, percentage: 5.1 },
|
||||
{ categoryName: '清洁维护', count: 5, percentage: 3.2 }
|
||||
],
|
||||
priorityDistribution: [
|
||||
{ priorityLevel: 1, count: 12, percentage: 7.7 },
|
||||
{ priorityLevel: 2, count: 28, percentage: 17.9 },
|
||||
{ priorityLevel: 3, count: 89, percentage: 57.1 },
|
||||
{ priorityLevel: 4, count: 21, percentage: 13.5 },
|
||||
{ priorityLevel: 5, count: 6, percentage: 3.8 }
|
||||
]
|
||||
}
|
||||
};
|
||||
resolve(mockData as any);
|
||||
}, 400);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出工单数据
|
||||
* @param query 查询参数
|
||||
*/
|
||||
export const exportRepairOrders = (query?: RepairOrderQuery) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
const mockData = {
|
||||
code: 200,
|
||||
msg: '导出成功',
|
||||
data: null
|
||||
};
|
||||
resolve(mockData);
|
||||
}, 1000);
|
||||
});
|
||||
};
|
||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
|
Before Width: | Height: | Size: 2.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 25 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 25 MiB After Width: | Height: | Size: 2.3 MiB |
@@ -243,4 +243,8 @@ defineExpose({
|
||||
margin: -3px 8px 0px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-color-picker__color) {
|
||||
border: 0px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -42,7 +42,6 @@
|
||||
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
:height="tableHeight"
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="satSurveyList"
|
||||
@@ -1832,161 +1831,30 @@ const cancelQuestionSelector = () => {
|
||||
selectedQuestionIds.value = [];
|
||||
};
|
||||
|
||||
const toolbarRef = ref<any>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏DOM元素,toolbarRef.value是组件实例,需要通过$el获取DOM元素
|
||||
const toolbarDom = toolbarRef.value.$el || toolbarRef.value;
|
||||
|
||||
// 检查是否是有效的DOM元素
|
||||
if (toolbarDom && typeof toolbarDom.closest === 'function') {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarDom.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarDom.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
} else {
|
||||
// 如果无法获取DOM元素,使用默认高度
|
||||
actualToolbarHeight.value = 70;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 45;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 自定义页面滚动条样式 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.page-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.page-container::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.page-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格容器 - 支持横向滚动 */
|
||||
.table-container {
|
||||
width: 100%;
|
||||
/* overflow: hidden; */
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 横向滚动表格样式 */
|
||||
@@ -2062,14 +1930,13 @@ onUnmounted(() => {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
|
||||
@@ -53,14 +53,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="hallReservationList"
|
||||
@selection-change="handleSelectionChange"
|
||||
:height="tableHeight"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<el-table v-loading="loading" border :data="hallReservationList" @selection-change="handleSelectionChange" style="margin-bottom: 0">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="报告厅ID" align="center" prop="hallId" />
|
||||
<el-table-column label="报告厅名称" align="center" prop="hallName" />
|
||||
@@ -172,14 +165,9 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
class="pagination-container"
|
||||
/>
|
||||
<div class="pagination-container">
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 查看报告厅预约详情对话框 -->
|
||||
@@ -397,11 +385,6 @@ const auditForm = ref({
|
||||
const selectedRecords = ref<HallReservationVO[]>([]);
|
||||
const auditLoading = ref(false);
|
||||
|
||||
const toolbarRef = ref<HTMLElement>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
|
||||
const queryParams = ref<HallReservationQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
@@ -418,64 +401,6 @@ const queryParams = ref<HallReservationQuery>({
|
||||
}
|
||||
});
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarRef.value.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarRef.value.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 30;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
/** 获取报告厅列表 */
|
||||
const getHallList = async () => {
|
||||
try {
|
||||
@@ -650,12 +575,6 @@ const handleQuery = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 监听搜索栏显示状态变化 */
|
||||
watch(showSearch, () => {
|
||||
// 搜索栏显示状态变化时重新计算高度
|
||||
calculateSearchHeight();
|
||||
});
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
@@ -834,86 +753,25 @@ const handleExport = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// 审核界面不需要限制用户信息
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getHallList(); // 获取报告厅列表
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
getHallList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
@@ -921,6 +779,15 @@ onUnmounted(() => {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.hall-option {
|
||||
padding: 8px 0;
|
||||
line-height: 1.4;
|
||||
|
||||
@@ -42,14 +42,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="hallInfoList"
|
||||
@selection-change="handleSelectionChange"
|
||||
:height="tableHeight"
|
||||
style="margin-bottom: 20px"
|
||||
>
|
||||
<el-table v-loading="loading" border :data="hallInfoList" @selection-change="handleSelectionChange" style="margin-bottom: 20px">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" /> -->
|
||||
<el-table-column label="报告厅名称" align="center" prop="hallName" />
|
||||
@@ -108,7 +101,9 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
<div class="pagination-container">
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- 添加或修改报告厅信息对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="1000px" append-to-body>
|
||||
@@ -193,7 +188,6 @@ const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const hallInfoFormRef = ref<ElFormInstance>();
|
||||
@@ -243,17 +237,6 @@ const data = reactive<PageData<HallInfoForm, HallInfoQuery>>({
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 窗口高度 - 页面边距 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 其他边距
|
||||
const searchHeight = showSearch.value ? 120 : 0; // 搜索栏高度
|
||||
const toolbarHeight = 50; // 工具栏高度
|
||||
const paginationHeight = 60; // 分页栏高度
|
||||
const otherHeight = 90; // 其他边距和间距
|
||||
|
||||
return windowHeight.value - searchHeight - toolbarHeight - paginationHeight - otherHeight - 10;
|
||||
});
|
||||
|
||||
/** 查询报告厅信息列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
@@ -366,11 +349,6 @@ const handleExport = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: HallInfoVO) => {
|
||||
Object.assign(viewForm, row);
|
||||
@@ -391,24 +369,27 @@ const cancelView = () => {
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
.button-card :deep(.el-card__header) {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.image-gallery {
|
||||
@@ -489,41 +470,13 @@ onUnmounted(() => {
|
||||
word-break: break-word;
|
||||
overflow-y: auto;
|
||||
}
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
/* margin-top: auto; */
|
||||
/* padding: 8px 0 4px 0; */
|
||||
/* background-color: #fff; */
|
||||
/* border-top: 1px solid #f0f0f0; */
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
.button-card :deep(.el-card__header) {
|
||||
border-bottom: none !important;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -53,14 +53,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="hallReservationList"
|
||||
@selection-change="handleSelectionChange"
|
||||
:height="tableHeight"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<el-table v-loading="loading" border :data="hallReservationList" @selection-change="handleSelectionChange" style="margin-bottom: 0">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="报告厅ID" align="center" prop="hallId" />
|
||||
<el-table-column label="报告厅名称" align="center" prop="hallName" />
|
||||
@@ -170,14 +163,9 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
class="pagination-container"
|
||||
/>
|
||||
<div class="pagination-container">
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- 添加或修改报告厅预约对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body>
|
||||
@@ -357,7 +345,6 @@ const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const hallReservationFormRef = ref<ElFormInstance>();
|
||||
@@ -388,11 +375,6 @@ const viewForm = ref<HallReservationForm>({
|
||||
auditRemark: undefined
|
||||
});
|
||||
|
||||
const toolbarRef = ref<HTMLElement>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
|
||||
const initFormData: HallReservationForm = {
|
||||
id: undefined,
|
||||
hallId: undefined,
|
||||
@@ -434,64 +416,6 @@ const data = reactive<PageData<HallReservationForm, HallReservationQuery>>({
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarRef.value.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarRef.value.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 30;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
/** 获取报告厅列表 */
|
||||
const getHallList = async () => {
|
||||
try {
|
||||
@@ -679,12 +603,6 @@ const handleQuery = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 监听搜索栏显示状态变化 */
|
||||
watch(showSearch, () => {
|
||||
// 搜索栏显示状态变化时重新计算高度
|
||||
calculateSearchHeight();
|
||||
});
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
@@ -829,94 +747,25 @@ const handleExport = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// 确保用户信息已加载
|
||||
if (!userStore.userId) {
|
||||
console.warn('用户信息尚未加载完成,等待加载...');
|
||||
// 如果用户信息尚未加载,可以等待一下再查询
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
// 再次确保queryParams中的applicantId是最新的
|
||||
queryParams.value.applicantId = userStore.userId;
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getHallList(); // 获取报告厅列表
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
getHallList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
@@ -924,6 +773,15 @@ onUnmounted(() => {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.hall-option {
|
||||
padding: 8px 0;
|
||||
line-height: 1.4;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div ref="searchRef" v-show="showSearch" class="mb-[10px]">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<!-- <el-form-item label="删除标志" prop="deleted">
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row ref="toolbarRef" :gutter="10" class="mb8">
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:satCategory:add']">新增</el-button>
|
||||
</el-col>
|
||||
@@ -59,7 +59,7 @@
|
||||
</template>
|
||||
|
||||
<div class="table-container">
|
||||
<el-table v-loading="loading" border :data="satCategoryList" :height="tableHeight" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="satCategoryList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" /> -->
|
||||
<!-- <el-table-column label="删除标志" align="center" prop="deleted">
|
||||
@@ -299,151 +299,42 @@ const handleExport = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const toolbarRef = ref<HTMLElement>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarRef.value.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarRef.value.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 35;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
.table-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
.button-card :deep(.el-card__header) {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<div class="p-2 page-container">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
||||
<el-form-item label="删除标志" prop="deleted">
|
||||
<el-select v-model="queryParams.deleted" placeholder="请选择删除标志" clearable >
|
||||
<el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="dict.value"/>
|
||||
<el-select v-model="queryParams.deleted" placeholder="请选择删除标志" clearable>
|
||||
<el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="答题记录ID" prop="recordId">
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
@@ -37,7 +37,9 @@
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:satDetail:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satDetail:remove']">删除</el-button>
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:satDetail:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['hq:satDetail:export']">导出</el-button>
|
||||
@@ -46,43 +48,42 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="satDetailList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="主键ID" align="center" prop="id" v-if="true" />
|
||||
<el-table-column label="删除标志" align="center" prop="deleted">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_item_delete" :value="scope.row.deleted"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="答题记录ID" align="center" prop="recordId" />
|
||||
<el-table-column label="题目ID" align="center" prop="questionId" />
|
||||
<el-table-column label="答案分数" align="center" prop="answerScore" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:satDetail:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:satDetail:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="table-container">
|
||||
<el-table v-loading="loading" border :data="satDetailList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="主键ID" align="center" prop="id" v-if="true" />
|
||||
<el-table-column label="删除标志" align="center" prop="deleted">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_item_delete" :value="scope.row.deleted" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="答题记录ID" align="center" prop="recordId" />
|
||||
<el-table-column label="题目ID" align="center" prop="questionId" />
|
||||
<el-table-column label="答案分数" align="center" prop="answerScore" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['hq:satDetail:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['hq:satDetail:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
<div class="pagination-container">
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- 添加或修改答题详情对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="satDetailFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="删除标志" prop="deleted">
|
||||
<el-select v-model="form.deleted" placeholder="请选择删除标志">
|
||||
<el-option
|
||||
v-for="dict in sys_item_delete"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="parseInt(dict.value)"
|
||||
></el-option>
|
||||
<el-option v-for="dict in sys_item_delete" :key="dict.value" :label="dict.label" :value="parseInt(dict.value)"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="答题记录ID" prop="recordId">
|
||||
@@ -95,7 +96,7 @@
|
||||
<el-input v-model="form.answerScore" placeholder="请输入答案分数" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -139,9 +140,9 @@ const initFormData: SatDetailForm = {
|
||||
questionId: undefined,
|
||||
answerScore: undefined,
|
||||
remark: undefined
|
||||
}
|
||||
};
|
||||
const data = reactive<PageData<SatDetailForm, SatDetailQuery>>({
|
||||
form: {...initFormData},
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
@@ -149,19 +150,12 @@ const data = reactive<PageData<SatDetailForm, SatDetailQuery>>({
|
||||
recordId: undefined,
|
||||
questionId: undefined,
|
||||
answerScore: undefined,
|
||||
params: {
|
||||
}
|
||||
params: {}
|
||||
},
|
||||
rules: {
|
||||
id: [
|
||||
{ required: true, message: "主键ID不能为空", trigger: "blur" }
|
||||
],
|
||||
recordId: [
|
||||
{ required: true, message: "答题记录ID不能为空", trigger: "blur" }
|
||||
],
|
||||
questionId: [
|
||||
{ required: true, message: "题目ID不能为空", trigger: "blur" }
|
||||
],
|
||||
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
|
||||
recordId: [{ required: true, message: '答题记录ID不能为空', trigger: 'blur' }],
|
||||
questionId: [{ required: true, message: '题目ID不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
@@ -174,55 +168,55 @@ const getList = async () => {
|
||||
satDetailList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = {...initFormData};
|
||||
form.value = { ...initFormData };
|
||||
satDetailFormRef.value?.resetFields();
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
}
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
}
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: SatDetailVO[]) => {
|
||||
ids.value = selection.map(item => item.id);
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
}
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = "添加答题详情";
|
||||
}
|
||||
dialog.title = '添加答题详情';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: SatDetailVO) => {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0]
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getSatDetail(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = "修改答题详情";
|
||||
}
|
||||
dialog.title = '修改答题详情';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
@@ -230,34 +224,73 @@ const submitForm = () => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
if (form.value.id) {
|
||||
await updateSatDetail(form.value).finally(() => buttonLoading.value = false);
|
||||
await updateSatDetail(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addSatDetail(form.value).finally(() => buttonLoading.value = false);
|
||||
await addSatDetail(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess("操作成功");
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: SatDetailVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除答题详情编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
|
||||
await proxy?.$modal.confirm('是否确认删除答题详情编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
|
||||
await delSatDetail(_ids);
|
||||
proxy?.$modal.msgSuccess("删除成功");
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
}
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download('hq/satDetail/export', {
|
||||
...queryParams.value
|
||||
}, `satDetail_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
proxy?.download(
|
||||
'hq/satDetail/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`satDetail_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格容器 */
|
||||
.table-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
.button-card :deep(.el-card__header) {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -53,7 +53,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
<div class="table-container">
|
||||
<el-table :height="tableHeight" v-loading="loading" border :data="satOptionList" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" border :data="satOptionList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" />
|
||||
<el-table-column label="删除标志" align="center" prop="deleted">
|
||||
@@ -291,144 +291,38 @@ const handleExport = () => {
|
||||
|
||||
const toolbarRef = ref<HTMLElement>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarRef.value.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarRef.value.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 35;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
/* 表格容器 */
|
||||
.table-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
<div class="table-container">
|
||||
<el-table v-loading="loading" border :data="satQuestionList" @selection-change="handleSelectionChange" :height="tableHeight">
|
||||
<el-table v-loading="loading" border :data="satQuestionList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<!-- <el-table-column label="主键ID" align="center" prop="id" v-if="true" /> -->
|
||||
<!-- <el-table-column label="删除标志" align="center" prop="deleted">
|
||||
@@ -378,145 +378,39 @@ const handleExport = () => {
|
||||
|
||||
const toolbarRef = ref<HTMLElement>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarRef.value.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarRef.value.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 35;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getCategoryList();
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
/* 表格容器 */
|
||||
.table-container {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
@@ -524,12 +418,12 @@ onUnmounted(() => {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* 文本省略号样式 */
|
||||
/* 文字省略样式 */
|
||||
.text-ellipsis {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
/* cursor: help; */
|
||||
max-width: 200px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@@ -82,7 +82,6 @@
|
||||
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
:height="tableHeight"
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="satSurveyList"
|
||||
@@ -2054,161 +2053,30 @@ const exportRawData = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const toolbarRef = ref<any>();
|
||||
const searchRef = ref<HTMLElement>();
|
||||
const actualToolbarHeight = ref(70); // 默认工具栏高度
|
||||
const actualSearchHeight = ref(160); // 默认搜索栏高度
|
||||
const windowHeight = ref(window.innerHeight);
|
||||
|
||||
/** 计算搜索栏实际高度 */
|
||||
const calculateSearchHeight = () => {
|
||||
nextTick(() => {
|
||||
if (searchRef.value && showSearch.value) {
|
||||
// 获取搜索栏元素的完整高度,包括边距和内边距
|
||||
const rect = searchRef.value.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(searchRef.value);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualSearchHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果搜索栏隐藏,高度为0
|
||||
actualSearchHeight.value = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 计算工具栏实际高度 */
|
||||
const calculateToolbarHeight = () => {
|
||||
nextTick(() => {
|
||||
if (toolbarRef.value) {
|
||||
// 获取工具栏DOM元素,toolbarRef.value是组件实例,需要通过$el获取DOM元素
|
||||
const toolbarDom = toolbarRef.value.$el || toolbarRef.value;
|
||||
|
||||
// 检查是否是有效的DOM元素
|
||||
if (toolbarDom && typeof toolbarDom.closest === 'function') {
|
||||
// 获取工具栏元素的完整高度,包括边距和内边距
|
||||
const toolbarElement = toolbarDom.closest('.el-card__header');
|
||||
if (toolbarElement) {
|
||||
const rect = toolbarElement.getBoundingClientRect();
|
||||
const computedStyle = window.getComputedStyle(toolbarElement);
|
||||
const marginTop = parseFloat(computedStyle.marginTop);
|
||||
const marginBottom = parseFloat(computedStyle.marginBottom);
|
||||
actualToolbarHeight.value = rect.height + marginTop + marginBottom;
|
||||
} else {
|
||||
// 如果找不到父元素,直接使用工具栏元素高度
|
||||
const rect = toolbarDom.getBoundingClientRect();
|
||||
actualToolbarHeight.value = rect.height + 20; // 添加一些边距
|
||||
}
|
||||
} else {
|
||||
// 如果无法获取DOM元素,使用默认高度
|
||||
actualToolbarHeight.value = 70;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 窗口大小变化监听 */
|
||||
const handleResize = () => {
|
||||
windowHeight.value = window.innerHeight;
|
||||
// 窗口大小变化时重新计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
};
|
||||
|
||||
/** 计算表格高度 */
|
||||
const tableHeight = computed(() => {
|
||||
// 精确计算:窗口高度 - 搜索栏高度 - 工具栏高度 - 分页栏高度 - 页面边距 - 分页栏底部边距
|
||||
const searchHeight = actualSearchHeight.value; // 实际搜索栏高度
|
||||
const toolbarHeight = actualToolbarHeight.value; // 实际工具栏高度
|
||||
const pageTopPadding = 8; // 页面顶部边距
|
||||
const pageBottomMargin = 10; // 分页栏距离底部的边距
|
||||
const paginationOwnHeight = 32; // 分页组件自身高度
|
||||
const paginationPadding = 20; // 分页栏上下内边距
|
||||
const cardPadding = 40; // 卡片内边距(上下各20px)
|
||||
|
||||
// 总的固定高度 = 搜索栏 + 工具栏 + 分页栏自身高度 + 分页栏内边距 + 页面边距 + 底部边距 + 卡片边距
|
||||
const fixedHeight = searchHeight + toolbarHeight + paginationOwnHeight + paginationPadding + pageTopPadding + pageBottomMargin + cardPadding;
|
||||
|
||||
const calculatedHeight = windowHeight.value - fixedHeight - 45;
|
||||
|
||||
// 确保至少有300px的表格高度,避免过小
|
||||
return Math.max(calculatedHeight, 300);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
// 初始计算搜索栏和工具栏高度
|
||||
calculateSearchHeight();
|
||||
calculateToolbarHeight();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 移除监听器
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
height: calc(100vh - 85px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 8px 0 8px !important; /* 顶部和左右边距,底部无边距 */
|
||||
}
|
||||
|
||||
/* 自定义页面滚动条样式 */
|
||||
.page-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.page-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.page-container::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.page-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* 表格卡片布局 */
|
||||
.table-card {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.table-card :deep(.el-card__body) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0px 20px 0 20px !important;
|
||||
min-height: 0;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 表格主体 */
|
||||
.table-main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 0px 20px 20px 20px !important;
|
||||
}
|
||||
|
||||
/* 表格容器 - 支持横向滚动 */
|
||||
.table-container {
|
||||
width: 100%;
|
||||
/* overflow: hidden; */
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 横向滚动表格样式 */
|
||||
@@ -2284,14 +2152,13 @@ onUnmounted(() => {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
/* 分页容器固定在底部 */
|
||||
/* 分页容器样式 */
|
||||
.pagination-container {
|
||||
flex-shrink: 0;
|
||||
padding-top: 10px 0 0 0 !important;
|
||||
margin: 0 !important;
|
||||
/* padding-bottom: 10px !important; 距离底部10px */
|
||||
/* margin: 0 !important; */
|
||||
/* margin-bottom: 10px !important; 确保距离视窗底部10px */
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/* 移除按钮栏底部边框 */
|
||||
|
||||
+9
-75
@@ -32,15 +32,8 @@
|
||||
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="captchaEnabled" prop="code">
|
||||
<el-input v-model="loginForm.code" auto-complete="off" :placeholder="proxy.$t('login.code')" style="width: 63%" @keyup.enter="handleLogin">
|
||||
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
|
||||
</el-input>
|
||||
<div class="login-code">
|
||||
<img :src="codeUrl" class="login-code-img" @click="getCode" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">{{ proxy.$t('login.rememberPassword') }}</el-checkbox>
|
||||
|
||||
<!-- <el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">{{ proxy.$t('login.rememberPassword') }}</el-checkbox> -->
|
||||
<!-- <el-form-item style="float: right">
|
||||
<el-button circle :title="proxy.$t('login.social.wechat')" @click="doSocialLogin('wechat')">
|
||||
<svg-icon icon-class="wechat" />
|
||||
@@ -76,7 +69,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCodeImg, getTenantList } from '@/api/login';
|
||||
import { getTenantList } from '@/api/login';
|
||||
import { authBinding } from '@/api/system/social/auth';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { LoginData, TenantVO } from '@/api/types';
|
||||
@@ -102,22 +95,17 @@ const loginForm = ref<LoginData>({
|
||||
tenantId: '000000',
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
rememberMe: false,
|
||||
code: '',
|
||||
uuid: ''
|
||||
clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
|
||||
grantType: 'password'
|
||||
} as LoginData);
|
||||
|
||||
const loginRules: ElFormRules = {
|
||||
tenantId: [{ required: true, trigger: 'blur', message: t('login.rule.tenantId.required') }],
|
||||
username: [{ required: true, trigger: 'blur', message: t('login.rule.username.required') }],
|
||||
password: [{ required: true, trigger: 'blur', message: t('login.rule.password.required') }],
|
||||
code: [{ required: true, trigger: 'change', message: t('login.rule.code.required') }]
|
||||
password: [{ required: true, trigger: 'blur', message: t('login.rule.password.required') }]
|
||||
};
|
||||
|
||||
const codeUrl = ref('');
|
||||
const loading = ref(false);
|
||||
// 验证码开关
|
||||
const captchaEnabled = ref(true);
|
||||
// 租户开关
|
||||
const tenantEnabled = ref(true);
|
||||
|
||||
@@ -173,19 +161,7 @@ const handleLogin = () => {
|
||||
loginRef.value?.validate(async (valid: boolean, fields: any) => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
// 勾选了需要记住密码设置在 localStorage 中设置记住用户名和密码
|
||||
if (loginForm.value.rememberMe) {
|
||||
localStorage.setItem('tenantId', String(loginForm.value.tenantId));
|
||||
localStorage.setItem('username', String(loginForm.value.username));
|
||||
localStorage.setItem('password', String(loginForm.value.password));
|
||||
localStorage.setItem('rememberMe', String(loginForm.value.rememberMe));
|
||||
} else {
|
||||
// 否则移除
|
||||
localStorage.removeItem('tenantId');
|
||||
localStorage.removeItem('username');
|
||||
localStorage.removeItem('password');
|
||||
localStorage.removeItem('rememberMe');
|
||||
}
|
||||
|
||||
// 调用action的登录方法
|
||||
const [err] = await to(userStore.login(loginForm.value));
|
||||
if (!err) {
|
||||
@@ -194,10 +170,6 @@ const handleLogin = () => {
|
||||
loading.value = false;
|
||||
} else {
|
||||
loading.value = false;
|
||||
// 重新获取验证码
|
||||
if (captchaEnabled.value) {
|
||||
await getCode();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('error submit!', fields);
|
||||
@@ -205,30 +177,9 @@ const handleLogin = () => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*/
|
||||
const getCode = async () => {
|
||||
const res = await getCodeImg();
|
||||
const { data } = res;
|
||||
captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
|
||||
if (captchaEnabled.value) {
|
||||
codeUrl.value = 'data:image/gif;base64,' + data.img;
|
||||
loginForm.value.uuid = data.uuid;
|
||||
}
|
||||
};
|
||||
|
||||
const getLoginData = () => {
|
||||
const tenantId = localStorage.getItem('tenantId');
|
||||
const username = localStorage.getItem('username');
|
||||
const password = localStorage.getItem('password');
|
||||
const rememberMe = localStorage.getItem('rememberMe');
|
||||
loginForm.value = {
|
||||
tenantId: tenantId === null ? String(loginForm.value.tenantId) : tenantId,
|
||||
username: username === null ? String(loginForm.value.username) : username,
|
||||
password: password === null ? String(loginForm.value.password) : String(password),
|
||||
rememberMe: rememberMe === null ? false : Boolean(rememberMe)
|
||||
} as LoginData;
|
||||
// 由于已去除记住密码功能,保持默认的登录数据
|
||||
// 如需要其他初始化逻辑,可在此处添加
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -261,7 +212,6 @@ const doSocialLogin = (type: string) => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getCode();
|
||||
initTenantList();
|
||||
getLoginData();
|
||||
|
||||
@@ -418,17 +368,6 @@ onMounted(() => {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.login-code {
|
||||
width: 33%;
|
||||
height: 32px;
|
||||
float: right;
|
||||
|
||||
img {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.el-login-footer {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
@@ -442,11 +381,6 @@ onMounted(() => {
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.login-code-img {
|
||||
height: 32px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
// 针对公司选择下拉框的特殊样式
|
||||
.el_select_coopr {
|
||||
:deep(.el-select__wrapper) {
|
||||
|
||||
@@ -0,0 +1,728 @@
|
||||
<template>
|
||||
<div class="p-2 page-container">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
|
||||
<el-form-item label="类型名称" prop="categoryName">
|
||||
<el-input v-model="queryParams.categoryName" placeholder="请输入类型名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型编码" prop="categoryCode">
|
||||
<el-input v-model="queryParams.categoryCode" placeholder="请输入类型编码" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="技能等级" prop="skillLevel">
|
||||
<el-select v-model="queryParams.skillLevel" placeholder="请选择技能等级" clearable>
|
||||
<el-option v-for="dict in pm_skill_level" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
||||
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card shadow="never" class="button-card table-card">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['pm:category:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="Sort" @click="toggleExpandAll">展开/折叠</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
v-if="refreshTable"
|
||||
v-loading="loading"
|
||||
:data="categoryList"
|
||||
row-key="id"
|
||||
:default-expand-all="isExpandAll"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
border
|
||||
style="width: 100%"
|
||||
class="tree-table"
|
||||
>
|
||||
<el-table-column prop="categoryName" label="类型名称" min-width="260" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span class="category-name-cell">
|
||||
<el-icon v-if="scope.row.iconUrl" class="category-icon" :style="{ color: scope.row.colorCode }">
|
||||
<component :is="getCategoryIcon(scope.row.categoryCode)" />
|
||||
</el-icon>
|
||||
<span class="category-name">{{ scope.row.categoryName }}</span>
|
||||
<el-tag v-if="scope.row.childrenCount > 0" size="small" type="info" class="children-count-tag">
|
||||
{{ scope.row.childrenCount }}个子类
|
||||
</el-tag>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="categoryCode" label="类型编码" width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="categoryDescription" label="类型描述" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="skillLevel" label="技能等级" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getSkillLevelTagType(scope.row.skillLevel)" size="small">
|
||||
{{ getSkillLevelText(scope.row.skillLevel) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="estimatedDuration" label="预估时长" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.estimatedDuration">{{ formatDuration(scope.row.estimatedDuration) }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="standardCost" label="标准成本" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.standardCost">¥{{ scope.row.standardCost }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="usageCount" label="使用次数" width="100" align="center" />
|
||||
<el-table-column prop="sortOrder" label="排序" width="80" align="center" />
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.status" active-value="1" inactive-value="0" @change="handleStatusChange(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ proxy.parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" fixed="right" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['pm:category:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['pm:category:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="新增子类" placement="top">
|
||||
<el-button link type="success" icon="Plus" @click="handleAddChild(scope.row)" v-hasPermi="['pm:category:add']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['pm:category:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改维修类型对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
|
||||
<el-form ref="categoryFormRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="上级类型" prop="parentId">
|
||||
<el-tree-select
|
||||
v-model="form.parentId"
|
||||
:data="categoryTreeOptions"
|
||||
:props="{ value: 'id', label: 'categoryName', children: 'children' }"
|
||||
value-key="id"
|
||||
placeholder="选择上级类型"
|
||||
check-strictly
|
||||
:render-after-expand="false"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="类型名称" prop="categoryName">
|
||||
<el-input v-model="form.categoryName" placeholder="请输入类型名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="类型编码" prop="categoryCode">
|
||||
<el-input v-model="form.categoryCode" placeholder="请输入类型编码" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="技能等级" prop="skillLevel">
|
||||
<el-select v-model="form.skillLevel" placeholder="请选择技能等级" style="width: 100%">
|
||||
<el-option v-for="dict in pm_skill_level" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="预估时长" prop="estimatedDuration">
|
||||
<el-input-number v-model="form.estimatedDuration" :min="0" :max="9999" placeholder="分钟" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="标准成本" prop="standardCost">
|
||||
<el-input-number v-model="form.standardCost" :min="0" :precision="2" placeholder="元" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="分类颜色" prop="colorCode">
|
||||
<el-color-picker v-model="form.colorCode" :predefine="predefineColors" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排序顺序" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="0" placeholder="数字越小越靠前" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="类型描述" prop="categoryDescription">
|
||||
<el-input v-model="form.categoryDescription" type="textarea" :rows="3" placeholder="请输入类型描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所需技能" prop="requiredSkills">
|
||||
<el-input v-model="form.requiredSkills" type="textarea" :rows="3" placeholder="请输入所需的专业技能要求" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer-right">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看维修类型详情对话框 -->
|
||||
<el-dialog title="维修类型详情" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<div v-if="viewData" class="category-detail">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="类型名称">
|
||||
<div class="category-name-view">
|
||||
<el-icon v-if="viewData.iconUrl" class="category-icon" :style="{ color: viewData.colorCode }">
|
||||
<component :is="getCategoryIcon(viewData.categoryCode)" />
|
||||
</el-icon>
|
||||
<span class="category-name">{{ viewData.categoryName }}</span>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="类型编码">{{ viewData.categoryCode }}</el-descriptions-item>
|
||||
<el-descriptions-item label="上级类型">{{ getParentName(viewData.parentId) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="技能等级">
|
||||
<el-tag :type="getSkillLevelTagType(viewData.skillLevel)" size="small">
|
||||
{{ getSkillLevelText(viewData.skillLevel) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="预估时长">
|
||||
{{ viewData.estimatedDuration ? formatDuration(viewData.estimatedDuration) : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="标准成本">
|
||||
{{ viewData.standardCost ? `¥${viewData.standardCost}` : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="使用次数">{{ viewData.usageCount }}</el-descriptions-item>
|
||||
<el-descriptions-item label="最后使用时间">
|
||||
{{ viewData.lastUsedTime ? proxy.parseTime(viewData.lastUsedTime, '{y}-{m}-{d} {h}:{i}') : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="排序顺序">{{ viewData.sortOrder }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="viewData.status === '1' ? 'success' : 'danger'" size="small">
|
||||
{{ viewData.status === '1' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间" :span="2">
|
||||
{{ proxy.parseTime(viewData.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="类型描述" :span="2">{{ viewData.categoryDescription || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所需技能" :span="2">{{ viewData.requiredSkills || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注信息" :span="2">{{ viewData.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer-right">
|
||||
<el-button @click="viewDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="RepairCategory">
|
||||
import {
|
||||
listRepairCategory,
|
||||
getRepairCategory,
|
||||
delRepairCategory,
|
||||
addRepairCategory,
|
||||
updateRepairCategory,
|
||||
getRepairCategoryTree
|
||||
} from '@/api/pm/repairCategory';
|
||||
import { RepairCategoryVO, RepairCategoryForm, RepairCategoryQuery } from '@/api/pm/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const categoryList = ref<RepairCategoryVO[]>([]);
|
||||
const categoryTreeOptions = ref<RepairCategoryVO[]>([]);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const isExpandAll = ref(false);
|
||||
const refreshTable = ref(true);
|
||||
|
||||
// 字典数据
|
||||
const pm_skill_level = ref([
|
||||
{ value: '1', label: '初级' },
|
||||
{ value: '2', label: '中级' },
|
||||
{ value: '3', label: '高级' }
|
||||
]);
|
||||
|
||||
const sys_normal_disable = ref([
|
||||
{ value: '1', label: '正常' },
|
||||
{ value: '0', label: '停用' }
|
||||
]);
|
||||
|
||||
// 预定义颜色
|
||||
const predefineColors = ref([
|
||||
'#FFB020', // 橙色
|
||||
'#4FC3F7', // 蓝色
|
||||
'#8BC34A', // 绿色
|
||||
'#FF7043', // 红橙色
|
||||
'#66BB6A', // 浅绿色
|
||||
'#42A5F5', // 天蓝色
|
||||
'#AB47BC', // 紫色
|
||||
'#26A69A', // 青色
|
||||
'#FFCA28', // 黄色
|
||||
'#EC407A' // 粉色
|
||||
]);
|
||||
|
||||
const queryParams = ref<RepairCategoryQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
categoryName: '',
|
||||
categoryCode: '',
|
||||
parentId: '',
|
||||
skillLevel: '',
|
||||
status: ''
|
||||
});
|
||||
|
||||
const form = ref<RepairCategoryForm>({});
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const categoryFormRef = ref<ElFormInstance>();
|
||||
|
||||
const rules = reactive<ElFormRules>({
|
||||
parentId: [{ required: true, message: '上级类型不能为空', trigger: 'change' }],
|
||||
categoryName: [{ required: true, message: '类型名称不能为空', trigger: 'blur' }],
|
||||
categoryCode: [{ required: true, message: '类型编码不能为空', trigger: 'blur' }],
|
||||
skillLevel: [{ required: true, message: '技能等级不能为空', trigger: 'change' }],
|
||||
sortOrder: [{ required: true, message: '排序顺序不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
});
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewData = ref<RepairCategoryVO>();
|
||||
const buttonLoading = ref(false);
|
||||
|
||||
/** 查询维修类型列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await listRepairCategory(queryParams.value);
|
||||
categoryList.value = res.data;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 获取维修类型树形结构 */
|
||||
const getCategoryTree = async () => {
|
||||
try {
|
||||
const res = await getRepairCategoryTree();
|
||||
const rootNode = {
|
||||
id: '0',
|
||||
categoryName: '主类型',
|
||||
categoryCode: '',
|
||||
categoryDescription: '',
|
||||
parentId: '0',
|
||||
ancestorIds: '',
|
||||
levelDepth: 0,
|
||||
childrenCount: res.data.length,
|
||||
requiredSkills: '',
|
||||
skillLevel: '',
|
||||
estimatedDuration: 0,
|
||||
standardCost: 0,
|
||||
iconUrl: '',
|
||||
colorCode: '',
|
||||
sortOrder: 0,
|
||||
status: '1',
|
||||
remark: '',
|
||||
usageCount: 0,
|
||||
lastUsedTime: '',
|
||||
createBy: '',
|
||||
createTime: '',
|
||||
updateBy: '',
|
||||
updateTime: '',
|
||||
tenantId: '',
|
||||
children: res.data
|
||||
} as RepairCategoryVO;
|
||||
categoryTreeOptions.value = [rootNode];
|
||||
} catch (error) {
|
||||
console.error('获取维修类型树形结构失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
getCategoryTree();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加维修类型';
|
||||
};
|
||||
|
||||
/** 新增子类按钮操作 */
|
||||
const handleAddChild = (row: RepairCategoryVO) => {
|
||||
reset();
|
||||
getCategoryTree();
|
||||
form.value.parentId = row.id;
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加子类型';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row: RepairCategoryVO) => {
|
||||
reset();
|
||||
getCategoryTree();
|
||||
const res = await getRepairCategory(row.id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改维修类型';
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: RepairCategoryVO) => {
|
||||
const res = await getRepairCategory(row.id);
|
||||
viewData.value = res.data;
|
||||
viewDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
categoryFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
if (form.value.id) {
|
||||
await updateRepairCategory(form.value);
|
||||
proxy?.$modal.msgSuccess('修改成功');
|
||||
} else {
|
||||
await addRepairCategory(form.value);
|
||||
proxy?.$modal.msgSuccess('新增成功');
|
||||
}
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: RepairCategoryVO) => {
|
||||
if (row.childrenCount > 0) {
|
||||
proxy?.$modal.msgError('存在子类型,不允许删除');
|
||||
return;
|
||||
}
|
||||
await proxy?.$modal.confirm('是否确认删除维修类型"' + row.categoryName + '"?');
|
||||
await delRepairCategory(row.id);
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 状态修改 */
|
||||
const handleStatusChange = async (row: RepairCategoryVO) => {
|
||||
const text = row.status === '1' ? '启用' : '停用';
|
||||
try {
|
||||
await proxy?.$modal.confirm('确认要' + text + '"' + row.categoryName + '"类型吗?');
|
||||
await updateRepairCategory({ id: row.id, status: row.status });
|
||||
proxy?.$modal.msgSuccess(text + '成功');
|
||||
} catch {
|
||||
row.status = row.status === '1' ? '0' : '1';
|
||||
}
|
||||
};
|
||||
|
||||
/** 展开/折叠操作 */
|
||||
const toggleExpandAll = () => {
|
||||
refreshTable.value = false;
|
||||
isExpandAll.value = !isExpandAll.value;
|
||||
nextTick(() => {
|
||||
refreshTable.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = {
|
||||
id: undefined,
|
||||
categoryName: '',
|
||||
categoryCode: '',
|
||||
categoryDescription: '',
|
||||
parentId: '0',
|
||||
requiredSkills: '',
|
||||
skillLevel: '',
|
||||
estimatedDuration: undefined,
|
||||
standardCost: undefined,
|
||||
iconUrl: '',
|
||||
colorCode: '#FFB020',
|
||||
sortOrder: 0,
|
||||
status: '1',
|
||||
remark: ''
|
||||
};
|
||||
categoryFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
dialog.visible = false;
|
||||
reset();
|
||||
};
|
||||
|
||||
/** 获取技能等级标签类型 */
|
||||
const getSkillLevelTagType = (level: string): 'success' | 'warning' | 'danger' | 'info' => {
|
||||
const types: Record<string, 'success' | 'warning' | 'danger' | 'info'> = {
|
||||
'1': 'success',
|
||||
'2': 'warning',
|
||||
'3': 'danger'
|
||||
};
|
||||
return types[level] || 'info';
|
||||
};
|
||||
|
||||
/** 获取技能等级文本 */
|
||||
const getSkillLevelText = (level: string) => {
|
||||
const dict = pm_skill_level.value.find((item) => item.value === level);
|
||||
return dict?.label || level;
|
||||
};
|
||||
|
||||
/** 格式化时长 */
|
||||
const formatDuration = (minutes: number) => {
|
||||
if (minutes < 60) {
|
||||
return `${minutes}分钟`;
|
||||
} else {
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const remainingMinutes = minutes % 60;
|
||||
return remainingMinutes > 0 ? `${hours}小时${remainingMinutes}分钟` : `${hours}小时`;
|
||||
}
|
||||
};
|
||||
|
||||
/** 获取分类图标 */
|
||||
const getCategoryIcon = (categoryCode: string) => {
|
||||
const iconMap: Record<string, string> = {
|
||||
'ELECTRIC': 'Lightning',
|
||||
'ELECTRIC_LIGHT': 'Sunny',
|
||||
'ELECTRIC_SOCKET': 'Operation',
|
||||
'ELECTRIC_POWER': 'Grid',
|
||||
'PLUMBING': 'Memo',
|
||||
'PLUMBING_WATER': 'Drizzling',
|
||||
'PLUMBING_DRAIN': 'Filter',
|
||||
'PLUMBING_HEATING': 'Cherry',
|
||||
'CONSTRUCTION': 'OfficeBuilding',
|
||||
'CONSTRUCTION_WALL': 'Wall',
|
||||
'CONSTRUCTION_DOOR': 'Key',
|
||||
'EQUIPMENT': 'Monitor',
|
||||
'EQUIPMENT_AC': 'WindPower',
|
||||
'EQUIPMENT_TEACHING': 'VideoCamera',
|
||||
'GARDEN': 'Grape',
|
||||
'CLEANING': 'Brush'
|
||||
};
|
||||
return iconMap[categoryCode] || 'Tools';
|
||||
};
|
||||
|
||||
/** 获取上级类型名称 */
|
||||
const getParentName = (parentId: string | number) => {
|
||||
if (parentId === '0' || parentId === 0) {
|
||||
return '主类型';
|
||||
}
|
||||
|
||||
const findParent = (list: RepairCategoryVO[], id: string | number): string => {
|
||||
for (const item of list) {
|
||||
if (item.id === id) {
|
||||
return item.categoryName;
|
||||
}
|
||||
if (item.children && item.children.length > 0) {
|
||||
const result = findParent(item.children, id);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
return findParent(categoryList.value, parentId) || `未知(${parentId})`;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
.table-container {
|
||||
.tree-table {
|
||||
:deep(.el-table__header-wrapper) {
|
||||
.el-table__header {
|
||||
th {
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
.el-table__row {
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-name-cell {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
gap: 8px;
|
||||
.children-count-tag {
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.category-name-view {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
|
||||
.category-icon {
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.children-count-tag {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.category-detail {
|
||||
:deep(.el-descriptions__label) {
|
||||
font-weight: 600;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer-right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
// 修复颜色选择器的灰色背景问题
|
||||
:deep(.el-color-picker) {
|
||||
.el-color-picker__trigger {
|
||||
border: 0px !important;
|
||||
background-color: #fff;
|
||||
|
||||
&:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-color-picker__color {
|
||||
background-color: transparent;
|
||||
border: 0px !important;
|
||||
|
||||
&.is-alpha {
|
||||
background-image: none;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-color-picker__color-inner {
|
||||
background-color: transparent;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
&::after {
|
||||
display: none; // 移除默认的灰色背景图案
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 固定在右下角的分页样式(预留)*/
|
||||
.fixed-pagination {
|
||||
position: fixed;
|
||||
right: 15px;
|
||||
bottom: 8px;
|
||||
z-index: 999;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-color-picker__color) {
|
||||
border: 0px !important;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user