resolve: 解决合并冲突
This commit is contained in:
commit
80dface225
97 files changed
+4789
-1686
No files matched your search
@@ -1,84 +1,39 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { HallReservationVO, HallReservationForm, HallReservationQuery } from '@/api/hq/hallReservation/types';
|
||||
import { HallReservationVO, HallReservationQuery } from '../hallReservation/types';
|
||||
import { HallReservationAuditForm, TimeCheckParams, TimeCheckResult } from './types';
|
||||
import { TableDataInfo, R } from '@/api/types';
|
||||
|
||||
/**
|
||||
* 查询报告厅预约列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listHallReservation = (query?: HallReservationQuery): AxiosPromise<HallReservationVO[]> => {
|
||||
return request({
|
||||
// 查询报告厅预约列表
|
||||
export function listHallReservation(query: HallReservationQuery) {
|
||||
return request<TableDataInfo<HallReservationVO>>({
|
||||
url: '/hq/hallReservation/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询报告厅预约详细
|
||||
* @param id
|
||||
*/
|
||||
export const getHallReservation = (id: string | number): AxiosPromise<HallReservationVO> => {
|
||||
return request({
|
||||
// 获取报告厅预约详细信息
|
||||
export function getHallReservation(id: string | number) {
|
||||
return request<R<HallReservationVO>>({
|
||||
url: '/hq/hallReservation/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增报告厅预约
|
||||
* @param data
|
||||
*/
|
||||
export const addHallReservation = (data: HallReservationForm) => {
|
||||
return request({
|
||||
url: '/hq/hallReservation',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改报告厅预约
|
||||
* @param data
|
||||
*/
|
||||
export const updateHallReservation = (data: HallReservationForm) => {
|
||||
return request({
|
||||
url: '/hq/hallReservation',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除报告厅预约
|
||||
* @param id
|
||||
*/
|
||||
export const delHallReservation = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/hq/hallReservation/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 审核报告厅预约(批准/拒绝)
|
||||
* @param data
|
||||
*/
|
||||
export interface HallReservationAuditForm {
|
||||
/** 预约记录ID列表 */
|
||||
ids: Array<string | number>;
|
||||
/** 审核状态:1-批准,2-拒绝 */
|
||||
status: string;
|
||||
/** 审核备注 */
|
||||
auditRemark?: string;
|
||||
}
|
||||
|
||||
export const auditHallReservation = (data: HallReservationAuditForm) => {
|
||||
return request({
|
||||
// 审核报告厅预约
|
||||
export function auditHallReservation(data: HallReservationAuditForm) {
|
||||
return request<R<any>>({
|
||||
url: '/hq/hallReservation/audit',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// 检查报告厅预约时间是否存在冲突
|
||||
export function checkTimeConflict(data: TimeCheckParams) {
|
||||
return request<R<TimeCheckResult>>({
|
||||
url: '/hq/hallReservation/checkTimeConflict',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
@@ -1,200 +1,39 @@
|
||||
export interface HallReservationVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 报告厅ID
|
||||
*/
|
||||
hallId: string | number;
|
||||
|
||||
/**
|
||||
* 报告厅名称
|
||||
*/
|
||||
hallName: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startTime: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime: string;
|
||||
|
||||
/**
|
||||
* 预约用途
|
||||
*/
|
||||
reservationPurpose: string;
|
||||
|
||||
/**
|
||||
* 申请人id
|
||||
*/
|
||||
applicantId: string | number;
|
||||
|
||||
/**
|
||||
* 申请人姓名
|
||||
*/
|
||||
applicantName: string;
|
||||
|
||||
/**
|
||||
* 申请人电话
|
||||
*/
|
||||
applicantPhone: string;
|
||||
|
||||
/**
|
||||
* 预约状态
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 审核人id
|
||||
*/
|
||||
auditBy: number;
|
||||
|
||||
/**
|
||||
* 审核人
|
||||
*/
|
||||
auditName: string;
|
||||
|
||||
/**
|
||||
* 审核时间
|
||||
*/
|
||||
auditTime: string;
|
||||
|
||||
/**
|
||||
* 审核备注
|
||||
*/
|
||||
auditRemark: string;
|
||||
import { HallReservationVO } from '../hallReservation/types';
|
||||
|
||||
/**
|
||||
* 报告厅预约审核表单
|
||||
*/
|
||||
export interface HallReservationAuditForm {
|
||||
/** 预约ID列表 */
|
||||
ids: Array<string | number>;
|
||||
/** 审核状态 */
|
||||
status: string;
|
||||
/** 审核备注 */
|
||||
auditRemark?: string;
|
||||
}
|
||||
|
||||
export interface HallReservationForm extends BaseEntity {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 报告厅ID
|
||||
*/
|
||||
hallId?: string | number;
|
||||
|
||||
/**
|
||||
* 报告厅名称
|
||||
*/
|
||||
hallName?: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startTime?: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime?: string;
|
||||
|
||||
/**
|
||||
* 预约用途
|
||||
*/
|
||||
reservationPurpose?: string;
|
||||
|
||||
/**
|
||||
* 申请人id
|
||||
*/
|
||||
applicantId?: string | number;
|
||||
|
||||
/**
|
||||
* 申请人姓名
|
||||
*/
|
||||
applicantName?: string;
|
||||
|
||||
/**
|
||||
* 申请人电话
|
||||
*/
|
||||
applicantPhone?: string;
|
||||
|
||||
/**
|
||||
* 预约状态
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 审核人id
|
||||
*/
|
||||
auditBy?: number;
|
||||
|
||||
/**
|
||||
* 审核人
|
||||
*/
|
||||
auditName?: string;
|
||||
|
||||
/**
|
||||
* 审核时间
|
||||
*/
|
||||
auditTime?: string;
|
||||
|
||||
/**
|
||||
* 审核备注
|
||||
*/
|
||||
auditRemark?: string;
|
||||
|
||||
/**
|
||||
* 时间冲突检查参数
|
||||
*/
|
||||
export interface TimeCheckParams {
|
||||
/** 报告厅ID */
|
||||
hallId: string | number;
|
||||
/** 预约开始时间 */
|
||||
startTime: string;
|
||||
/** 预约结束时间 */
|
||||
endTime: string;
|
||||
/** 排除的预约ID */
|
||||
excludeId?: string | number;
|
||||
}
|
||||
|
||||
export interface HallReservationQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 报告厅ID
|
||||
*/
|
||||
hallId?: string | number;
|
||||
|
||||
/**
|
||||
* 报告厅名称
|
||||
*/
|
||||
hallName?: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startTime?: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endTime?: string;
|
||||
|
||||
/**
|
||||
* 开始时间查询参数(字符串格式)
|
||||
*/
|
||||
startTimeStr?: string;
|
||||
|
||||
/**
|
||||
* 结束时间查询参数(字符串格式)
|
||||
*/
|
||||
endTimeStr?: string;
|
||||
|
||||
/**
|
||||
* 申请人ID
|
||||
*/
|
||||
applicantId?: string | number;
|
||||
|
||||
/**
|
||||
* 申请人姓名
|
||||
*/
|
||||
applicantName?: string;
|
||||
|
||||
/**
|
||||
* 预约状态
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
/**
|
||||
* 时间冲突检查结果
|
||||
*/
|
||||
export interface TimeCheckResult {
|
||||
/** 是否存在冲突 */
|
||||
hasConflict: boolean;
|
||||
/** 冲突的预约列表 */
|
||||
conflictList: HallReservationVO[];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { RepairCategoryVO, RepairCategoryForm, RepairCategoryQuery } from '@/api/hq/hq/pm/types';
|
||||
import { RepairCategoryVO, RepairCategoryForm, RepairCategoryQuery } from '@/api/hq/pm/types';
|
||||
|
||||
/**
|
||||
* 查询维修类型列表
|
||||
|
||||
@@ -209,6 +209,19 @@ export const deptTreeSelect = (): AxiosPromise<DeptTreeVO[]> => {
|
||||
});
|
||||
};
|
||||
|
||||
/** 获取具有指定权限的用户列表 */
|
||||
export function listUsersByPermission(permission: string, tenantId?: string): AxiosPromise<UserVO[]> {
|
||||
return request({
|
||||
url: '/system/user/list-by-permission',
|
||||
method: 'get',
|
||||
params: {
|
||||
permission,
|
||||
tenantId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
listUser,
|
||||
getUser,
|
||||
@@ -225,5 +238,6 @@ export default {
|
||||
getAuthRole,
|
||||
updateAuthRole,
|
||||
deptTreeSelect,
|
||||
listUserByDeptId
|
||||
listUserByDeptId,
|
||||
listUsersByPermission
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1752052172719" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8134" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M756.01248 336.814072l-20.336164 0 0 510.696308-67.101257 0L668.575058 336.814072 556.73383 336.814072l0 510.696308-67.106374 0L489.627457 336.814072 377.791345 336.814072l0 510.696308-67.102281 0L310.689065 336.814072l-42.702568 0c-50.532907 0-91.50711-0.727571-91.50711 0l0 533.587668c0 49.123814 40.974203 88.944751 91.50711 88.944751l488.019843 0c50.544163 0 91.50097-39.820937 91.50097-88.944751L847.50731 336.814072C847.512427 333.065701 806.562782 336.814072 756.01248 336.814072L756.01248 336.814072zM891.467521 176.488597 690.943508 176.488597l0-54.57292c0-31.630395-56.529481-57.263192-89.479941-57.263192L422.525176 64.652486c-32.933063 0-89.468684 25.632797-89.468684 57.263192l0 54.57292L132.566248 176.488597c-25.160029 0-45.548382 20.033265-45.548382 44.740994 0 24.695448 20.382213 44.727691 45.548382 44.727691L891.467521 265.957281c25.137517 0 45.514613-20.032242 45.514613-44.727691C936.975994 196.521862 916.593781 176.488597 891.467521 176.488597L891.467521 176.488597zM601.468684 176.488597 422.530292 176.488597l0-22.367427 178.938392 0L601.468684 176.488597 601.468684 176.488597zM601.468684 176.488597" fill="#272636" p-id="8135"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1752052134276" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5447" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M220.094304 322.742967l247.380709-82.457849c33.513211-11.171411 70.457561-2.450813 95.438462 22.529064l198.273366 198.270297c24.979878 24.977831 33.704569 61.924228 22.534181 95.439485l-82.463989 247.376616a93.251661 93.251661 0 0 1-64.445649 60.616444L63.720126 1017.340337c-24.88164 6.634081-50.436615-8.158804-57.070696-33.040445a46.613551 46.613551 0 0 1 0-24.028205L159.471719 387.186569a93.250638 93.250638 0 0 1 60.622585-64.443602m125.403877 420.158452a91.065883 91.065883 0 0 1 70.947724-26.441156c1.682312 0.12075 3.266387 0.181125 4.754271 0.181125 62.869761 0 113.84054-50.966687 113.84054-113.842587 0-62.87283-50.97078-113.841564-113.84054-113.841564-62.87283 0-113.84361 50.968733-113.84361 113.841564 0 1.490954 0.061398 3.072982 0.182148 4.752224a91.080209 91.080209 0 0 1-26.437063 70.94977L156.663773 802.936627c-17.781956 17.781956-17.781956 46.617644 0 64.398577 17.781956 17.784003 46.615597 17.784003 64.401647 0l124.432761-124.433785zM579.671104 80.436823c100.483351-100.484375 263.404629-100.484375 363.89105 0 100.484375 100.485398 100.484375 263.404629 0 363.890027-28.784523 28.784523-75.449239 28.784523-104.229668 0L579.671104 184.666491c-28.783499-28.781453-28.783499-75.448215 0-104.229668" fill="#040000" p-id="5448"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1752052118757" class="icon" viewBox="0 0 1829 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4426" xmlns:xlink="http://www.w3.org/1999/xlink" width="357.2265625" height="200"><path d="M914.297945 0C314.456393 0 0 441.928431 0 512.019026c0 70.052544 314.456393 511.980974 914.297945 511.980974 599.8035 0 914.297945-441.928431 914.297945-511.980974C1828.633942 441.928431 1514.101446 0 914.297945 0zM914.297945 905.888596c-224.46494 0-406.388466-176.329977-406.388466-393.86957 0-217.539593 181.923526-393.86957 406.388466-393.86957 224.426889 0 406.388466 176.291925 406.388466 393.86957C1320.686411 729.558619 1138.762885 905.888596 914.297945 905.888596zM914.297945 512.019026c-37.252276-40.867155 60.653859-196.953811 0-196.953811-112.213444 0-203.194233 88.20304-203.194233 196.953811s90.980789 196.915759 203.194233 196.915759c112.175393 0 203.194233-88.164988 203.194233-196.915759C1117.492178 462.057597 945.918621 546.759912 914.297945 512.019026z" fill="#090909" p-id="4427"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1686,3 +1686,8 @@ body {
|
||||
outline: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏图片上传列表的删除按钮
|
||||
.el-upload-list--picture-card .el-upload-list__item .el-icon--close {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -441,12 +441,13 @@ img {
|
||||
}
|
||||
|
||||
// 禁用状态
|
||||
.disabled,
|
||||
[disabled] {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
// 禁用状态取消,让分页组件可以点击跳转
|
||||
// .disabled,
|
||||
// [disabled] {
|
||||
// opacity: 0.6;
|
||||
// cursor: not-allowed;
|
||||
// pointer-events: none;
|
||||
// }
|
||||
|
||||
// 加载状态
|
||||
.loading {
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
:root {
|
||||
// Theme color
|
||||
// --art-primary: 50, 50, 222;
|
||||
--art-primary: 93, 135, 255;
|
||||
// --art-primary: 93, 135, 255;
|
||||
--art-primary: 0, 58, 255;
|
||||
--art-secondary: 73, 190, 255;
|
||||
--art-error: 250, 137, 107;
|
||||
--art-info: 0, 75, 178;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
:to="{ path: tag.path ? tag.path : '', query: tag.query }"
|
||||
class="tags-view-item"
|
||||
:style="activeStyle(tag)"
|
||||
@click="handleTagClick(tag, $event)"
|
||||
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
||||
@contextmenu.prevent="openMenu(tag, $event)"
|
||||
>
|
||||
@@ -232,6 +233,27 @@ const handleScroll = () => {
|
||||
closeMenu();
|
||||
};
|
||||
|
||||
// 点击标签时,如果标签已激活,则执行刷新操作
|
||||
const handleTagClick = (tag: RouteLocationNormalized, e: MouseEvent) => {
|
||||
// 阻止 router-link 默认行为,统一由脚本控制导航和刷新
|
||||
e.preventDefault();
|
||||
|
||||
if (isActive(tag)) {
|
||||
// 当前已激活,直接刷新
|
||||
refreshSelectedTag(tag);
|
||||
} else {
|
||||
// 尚未激活,先导航到对应路由,导航成功后再刷新
|
||||
router
|
||||
.push({ path: tag.path || '', query: tag.query })
|
||||
.then(() => {
|
||||
refreshSelectedTag(tag);
|
||||
})
|
||||
.catch(() => {
|
||||
/* 忽略重复导航错误 */
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initTags();
|
||||
addTags();
|
||||
|
||||
@@ -3,31 +3,65 @@
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div ref="searchRef" 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="报告厅ID" prop="hallId">
|
||||
<el-input v-model="queryParams.hallId" placeholder="请输入报告厅ID" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="报告厅名称" prop="hallName">
|
||||
<el-input v-model="queryParams.hallName" placeholder="请输入报告厅名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预约开始时间" prop="startTime">
|
||||
<el-date-picker clearable v-model="queryParams.startTime" type="date" placeholder="请选择开始时间" value-format="YYYY-MM-DD" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预约结束时间" prop="endTime">
|
||||
<el-date-picker clearable v-model="queryParams.endTime" type="date" placeholder="请选择结束时间" value-format="YYYY-MM-DD" />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请人姓名" prop="applicantName">
|
||||
<el-input v-model="queryParams.applicantName" placeholder="请输入申请人姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预约状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择预约状态" clearable>
|
||||
<el-option v-for="dict in hall_reserv_status" :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 ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<!-- 第一行:4个检索元素 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="报告厅名称" prop="hallName">
|
||||
<el-input v-model="queryParams.hallName" placeholder="请输入报告厅名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预约开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.startTime"
|
||||
type="date"
|
||||
placeholder="请选择开始时间"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预约结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.endTime"
|
||||
type="date"
|
||||
placeholder="请选择结束时间"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申请人姓名" prop="applicantName">
|
||||
<el-input v-model="queryParams.applicantName" placeholder="请输入申请人姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第二行:剩余检索元素 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预约状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择预约状态" clearable style="width: 100%">
|
||||
<el-option v-for="dict in hall_reserv_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 操作按钮行 -->
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<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-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -53,7 +87,14 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="hallReservationList" @selection-change="handleSelectionChange" style="margin-bottom: 0">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="hallReservationList"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
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" />
|
||||
@@ -80,13 +121,9 @@
|
||||
:hide-after="100"
|
||||
effect="dark"
|
||||
>
|
||||
<span class="purpose-text">
|
||||
{{
|
||||
scope.row.reservationPurpose && scope.row.reservationPurpose.length > 100
|
||||
? scope.row.reservationPurpose.substring(0, 100) + '...'
|
||||
: scope.row.reservationPurpose
|
||||
}}
|
||||
</span>
|
||||
<div class="purpose-text line-clamp-3">
|
||||
{{ scope.row.reservationPurpose }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -112,17 +149,13 @@
|
||||
:hide-after="100"
|
||||
effect="dark"
|
||||
>
|
||||
<span class="audit-remark-text">
|
||||
{{
|
||||
scope.row.auditRemark && scope.row.auditRemark.length > 100
|
||||
? scope.row.auditRemark.substring(0, 100) + '...'
|
||||
: scope.row.auditRemark
|
||||
}}
|
||||
</span>
|
||||
<div class="audit-remark-text line-clamp-3">
|
||||
{{ scope.row.auditRemark }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220">
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220" fixed="right">
|
||||
<template #default="scope">
|
||||
<!-- 查看按钮,所有记录都可以查看 -->
|
||||
<el-tooltip content="查看" placement="top">
|
||||
@@ -180,9 +213,9 @@
|
||||
<span class="form-text">{{ viewForm.hallName }}</span>
|
||||
</el-form-item>
|
||||
<!-- 报告厅图片展示区域 -->
|
||||
<el-form-item label="报告厅图片" v-if="viewHallImages.length > 0">
|
||||
<el-form-item label="报告厅图片">
|
||||
<div class="view-hall-images">
|
||||
<div class="image-gallery">
|
||||
<div class="image-gallery" v-if="viewHallImages.length > 0">
|
||||
<div v-for="(image, index) in viewHallImages" :key="index" class="image-item" @click="previewImage(image, index)">
|
||||
<img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div class="image-overlay">
|
||||
@@ -190,6 +223,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-images-placeholder">
|
||||
<el-icon><Picture /></el-icon>
|
||||
<span>暂无图片</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="预约开始时间">
|
||||
@@ -265,8 +302,7 @@
|
||||
<div class="reservation-details">
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">申请人:</span>
|
||||
<span class="detail-value">{{ record.applicantName }}</span>
|
||||
<span class="detail-phone">({{ record.applicantPhone || '手机号暂无' }})</span>
|
||||
<span class="detail-value"> {{ record.applicantName }}({{ record.applicantPhone || '手机号暂无' }}) </span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">预约时间:</span>
|
||||
@@ -278,6 +314,46 @@
|
||||
<span class="detail-label">预约用途:</span>
|
||||
<span class="detail-value purpose-content">{{ record.reservationPurpose }}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">审核建议:</span>
|
||||
<span class="detail-value audit-suggestion-content" :class="getConflictStatusClass(record)">
|
||||
<template v-if="getConflictCheckStatus(record).isLoading">
|
||||
<el-icon class="rotate-loading"><Loading /></el-icon> 正在检查时间冲突...
|
||||
</template>
|
||||
<template v-else-if="getConflictCheckStatus(record).hasError">
|
||||
<el-icon class="text-warning"><Warning /></el-icon> 冲突检查失败,请手动核实
|
||||
</template>
|
||||
<template v-else-if="hasTimeConflict(record)">
|
||||
<el-icon class="text-danger"><Warning /></el-icon> 发现时间冲突,建议拒绝
|
||||
<div class="conflict-details" v-if="getConflictReservations(record).length > 0">
|
||||
<small>冲突详情:</small>
|
||||
<ul class="conflict-list">
|
||||
<li v-for="conflict in getConflictReservations(record)" :key="conflict.id">
|
||||
<el-tag type="warning" size="small" v-if="conflict.internal">本次批次</el-tag>
|
||||
<el-tag type="info" size="small" v-else>已审批</el-tag>
|
||||
{{ conflict.applicantName }} ({{ parseTime(conflict.startTime, '{h}:{i}') }}-{{
|
||||
parseTime(conflict.endTime, '{h}:{i}')
|
||||
}})
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="getConflictCheckStatus(record).isChecked">
|
||||
<el-icon class="text-success"><Check /></el-icon> 无时间冲突,可以批准
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-icon class="text-info"><InfoFilled /></el-icon> 未检查时间冲突
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="manualCheckConflict(record)"
|
||||
:loading="getConflictCheckStatus(record).isLoading"
|
||||
>
|
||||
立即检查
|
||||
</el-button>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -294,6 +370,22 @@
|
||||
{{ auditForm.status === '1' ? '批准' : '拒绝' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 批准时显示冲突检查统计 -->
|
||||
<div v-if="auditForm.status === '1'" class="conflict-summary">
|
||||
<span class="audit-label">冲突检查统计:</span>
|
||||
<div class="summary-stats">
|
||||
<el-tag type="info" size="small"> 总计:{{ selectedRecords.length }} 个预约 </el-tag>
|
||||
<el-tag type="success" size="small"> 无冲突:{{ getNoConflictCount() }} 个 </el-tag>
|
||||
<el-tag type="danger" size="small" v-if="getConflictCount() > 0"> 有冲突:{{ getConflictCount() }} 个 </el-tag>
|
||||
<el-tag type="warning" size="small" v-if="getUncheckCount() > 0"> 未检查:{{ getUncheckCount() }} 个 </el-tag>
|
||||
</div>
|
||||
<div v-if="getConflictCount() > 0" class="conflict-warning-text">
|
||||
<el-icon><Warning /></el-icon>
|
||||
建议先处理时间冲突的预约,或选择拒绝有冲突的申请
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="audit-remark-section">
|
||||
<span class="audit-label">审核备注:</span>
|
||||
<el-input
|
||||
@@ -320,7 +412,8 @@
|
||||
</template>
|
||||
|
||||
<script setup name="HallAudit" lang="ts">
|
||||
import { listHallReservation, getHallReservation, auditHallReservation, HallReservationAuditForm } from '@/api/hq/hallAudit';
|
||||
import { listHallReservation, getHallReservation, auditHallReservation, HallReservationAuditForm, checkTimeConflict } from '@/api/hq/hallAudit';
|
||||
import dayjs from 'dayjs';
|
||||
import { listHallInfo } from '@/api/hq/hallInfo';
|
||||
import { HallReservationVO, HallReservationQuery, HallReservationForm } from '@/api/hq/hallReservation/types';
|
||||
import { HallInfoVO } from '@/api/hq/hallInfo/types';
|
||||
@@ -385,6 +478,10 @@ const auditForm = ref({
|
||||
const selectedRecords = ref<HallReservationVO[]>([]);
|
||||
const auditLoading = ref(false);
|
||||
|
||||
// 审核建议相关状态
|
||||
const conflictCheckResults = reactive(new Map<string | number, any>());
|
||||
const conflictCheckLoading = reactive(new Set<string | number>());
|
||||
|
||||
const queryParams = ref<HallReservationQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
@@ -430,7 +527,7 @@ const getHallList = async () => {
|
||||
const getHallImages = (imageUrl: string): string[] => {
|
||||
if (!imageUrl || imageUrl.trim() === '') return [];
|
||||
// 支持多种分隔符:逗号、分号、空格
|
||||
const separators = /[,;|\s]+/;
|
||||
const separators = /[,;|\\s]+/;
|
||||
const images = imageUrl
|
||||
.split(separators)
|
||||
.map((url) => url.trim())
|
||||
@@ -680,13 +777,22 @@ const handleReject = async (row?: HallReservationVO) => {
|
||||
};
|
||||
|
||||
/** 打开审核对话框 */
|
||||
const openAuditDialog = (status: string, ids: Array<string | number>, records: HallReservationVO[], title: string) => {
|
||||
const openAuditDialog = async (status: string, ids: Array<string | number>, records: HallReservationVO[], title: string) => {
|
||||
auditForm.value.status = status;
|
||||
auditForm.value.auditRemark = '';
|
||||
auditForm.value.ids = ids;
|
||||
selectedRecords.value = records;
|
||||
auditDialog.title = title;
|
||||
auditDialog.visible = true;
|
||||
|
||||
// 在打开审核对话框时统一检查时间冲突(无论批准/拒绝)
|
||||
// 清除选中记录旧的缓存(尤其是 internal 冲突信息)
|
||||
records.forEach((rec) => conflictCheckResults.delete(rec.id));
|
||||
|
||||
if (records.length > 0) {
|
||||
console.log('审核对话框打开,开始检查时间冲突...');
|
||||
await batchCheckTimeConflict(records);
|
||||
}
|
||||
};
|
||||
|
||||
/** 确认审核操作 */
|
||||
@@ -753,6 +859,242 @@ const handleExport = () => {
|
||||
);
|
||||
};
|
||||
|
||||
/** 检查单个记录的时间冲突 */
|
||||
const checkRecordTimeConflict = async (record: HallReservationVO) => {
|
||||
const recordId = record.id;
|
||||
|
||||
// 如果已经在检查中,跳过
|
||||
if (conflictCheckLoading.has(recordId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已有缓存结果,直接返回
|
||||
if (conflictCheckResults.has(recordId)) {
|
||||
return conflictCheckResults.get(recordId);
|
||||
}
|
||||
|
||||
try {
|
||||
conflictCheckLoading.add(recordId);
|
||||
|
||||
const res = await checkTimeConflict({
|
||||
hallId: record.hallId,
|
||||
startTime: dayjs(record.startTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
endTime: dayjs(record.endTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
excludeId: record.id
|
||||
});
|
||||
|
||||
const result = {
|
||||
hasConflict: res.data.hasConflict,
|
||||
conflictList: (res.data.conflictList || []).map((item: any) => ({ ...item, internal: false })),
|
||||
conflictMessage: res.data.conflictMessage || '',
|
||||
checkTime: new Date().getTime()
|
||||
};
|
||||
|
||||
// 缓存结果
|
||||
conflictCheckResults.set(recordId, result);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('检查时间冲突失败:', error);
|
||||
const errorResult = {
|
||||
hasConflict: false,
|
||||
conflictList: [],
|
||||
conflictMessage: '冲突检查失败',
|
||||
checkTime: new Date().getTime(),
|
||||
error: true
|
||||
};
|
||||
conflictCheckResults.set(recordId, errorResult);
|
||||
return errorResult;
|
||||
} finally {
|
||||
conflictCheckLoading.delete(recordId);
|
||||
}
|
||||
};
|
||||
|
||||
/** 批量检查选中记录的时间冲突 */
|
||||
const batchCheckTimeConflict = async (records: HallReservationVO[]) => {
|
||||
console.log('开始批量检查时间冲突,记录数:', records.length);
|
||||
|
||||
const promises = records.map((record) => checkRecordTimeConflict(record));
|
||||
await Promise.all(promises);
|
||||
|
||||
// 前端额外校验勾选记录之间的时间交叉冲突(同一报告厅)
|
||||
addInternalConflictInfo(records);
|
||||
|
||||
console.log('批量时间冲突检查完成');
|
||||
};
|
||||
|
||||
/** 判断两个时间段是否重叠 */
|
||||
const isTimeOverlap = (startA: any, endA: any, startB: any, endB: any) => {
|
||||
const sA = dayjs(startA);
|
||||
const eA = dayjs(endA);
|
||||
const sB = dayjs(startB);
|
||||
const eB = dayjs(endB);
|
||||
return sA.isBefore(eB) && sB.isBefore(eA); // 开始 < 另一结束 且 另一开始 < 结束
|
||||
};
|
||||
|
||||
/** 在选中记录之间标记内部冲突 */
|
||||
const addInternalConflictInfo = (records: HallReservationVO[]) => {
|
||||
// 若不足两条记录无需内部冲突检测
|
||||
if (records.length < 2) return;
|
||||
|
||||
const hallGroups: Record<string, HallReservationVO[]> = {};
|
||||
records.forEach((rec) => {
|
||||
const key = `${rec.hallId || ''}`;
|
||||
hallGroups[key] = hallGroups[key] || [];
|
||||
hallGroups[key].push(rec);
|
||||
});
|
||||
|
||||
Object.values(hallGroups).forEach((list) => {
|
||||
// 对同一报告厅的申请做两两比较
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
for (let j = i + 1; j < list.length; j++) {
|
||||
const r1 = list[i];
|
||||
const r2 = list[j];
|
||||
if (isTimeOverlap(r1.startTime, r1.endTime, r2.startTime, r2.endTime)) {
|
||||
// r1 冲突信息
|
||||
const res1 = conflictCheckResults.get(r1.id) || {
|
||||
hasConflict: false,
|
||||
conflictList: [],
|
||||
conflictMessage: '',
|
||||
checkTime: new Date().getTime()
|
||||
};
|
||||
res1.hasConflict = true;
|
||||
res1.conflictList.push({ ...r2, internal: true });
|
||||
conflictCheckResults.set(r1.id, res1);
|
||||
|
||||
// r2 冲突信息
|
||||
const res2 = conflictCheckResults.get(r2.id) || {
|
||||
hasConflict: false,
|
||||
conflictList: [],
|
||||
conflictMessage: '',
|
||||
checkTime: new Date().getTime()
|
||||
};
|
||||
res2.hasConflict = true;
|
||||
res2.conflictList.push({ ...r1, internal: true });
|
||||
conflictCheckResults.set(r2.id, res2);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 获取记录的冲突检查结果 */
|
||||
const getConflictResult = (record: HallReservationVO) => {
|
||||
return (
|
||||
conflictCheckResults.get(record.id) || {
|
||||
hasConflict: false,
|
||||
conflictList: [],
|
||||
conflictMessage: '未检查',
|
||||
checkTime: 0
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/** 检查是否存在时间冲突(同步方法) */
|
||||
const hasTimeConflict = (record: HallReservationVO): boolean => {
|
||||
const result = getConflictResult(record);
|
||||
return result.hasConflict;
|
||||
};
|
||||
|
||||
/** 获取时间冲突的预约列表(同步方法) */
|
||||
const getConflictReservations = (record: HallReservationVO) => {
|
||||
const result = getConflictResult(record);
|
||||
return result.conflictList || [];
|
||||
};
|
||||
|
||||
/** 获取冲突检查状态 */
|
||||
const getConflictCheckStatus = (record: HallReservationVO) => {
|
||||
const isLoading = conflictCheckLoading.has(record.id);
|
||||
const result = getConflictResult(record);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
isChecked: result.checkTime > 0,
|
||||
hasError: result.error || false,
|
||||
result
|
||||
};
|
||||
};
|
||||
|
||||
/** 获取冲突状态对应的CSS类 */
|
||||
const getConflictStatusClass = (record: HallReservationVO) => {
|
||||
const status = getConflictCheckStatus(record);
|
||||
|
||||
if (status.isLoading) {
|
||||
return 'checking-conflict';
|
||||
}
|
||||
|
||||
if (status.hasError) {
|
||||
return 'check-error';
|
||||
}
|
||||
|
||||
if (status.isChecked) {
|
||||
return hasTimeConflict(record) ? 'has-conflict' : 'no-conflict';
|
||||
}
|
||||
|
||||
return 'not-checked';
|
||||
};
|
||||
|
||||
/** 获取审核建议文本 */
|
||||
const getAuditSuggestionText = (record: HallReservationVO) => {
|
||||
const status = getConflictCheckStatus(record);
|
||||
|
||||
if (status.isLoading) {
|
||||
return '正在检查时间冲突...';
|
||||
}
|
||||
|
||||
if (status.hasError) {
|
||||
return '冲突检查失败,请手动核实';
|
||||
}
|
||||
|
||||
if (status.isChecked) {
|
||||
return hasTimeConflict(record) ? '发现时间冲突,建议拒绝' : '无时间冲突,可以批准';
|
||||
}
|
||||
|
||||
return '未检查时间冲突';
|
||||
};
|
||||
|
||||
/** 手动触发时间冲突检查 */
|
||||
const manualCheckConflict = async (record: HallReservationVO) => {
|
||||
// 清除缓存,强制重新检查
|
||||
conflictCheckResults.delete(record.id);
|
||||
await checkRecordTimeConflict(record);
|
||||
};
|
||||
|
||||
/** 获取无冲突的预约数量 */
|
||||
const getNoConflictCount = () => {
|
||||
return selectedRecords.value.filter((record) => {
|
||||
const status = getConflictCheckStatus(record);
|
||||
return status.isChecked && !hasTimeConflict(record);
|
||||
}).length;
|
||||
};
|
||||
|
||||
/** 获取有冲突的预约数量 */
|
||||
const getConflictCount = () => {
|
||||
return selectedRecords.value.filter((record) => {
|
||||
const status = getConflictCheckStatus(record);
|
||||
return status.isChecked && hasTimeConflict(record);
|
||||
}).length;
|
||||
};
|
||||
|
||||
/** 获取未检查的预约数量 */
|
||||
const getUncheckCount = () => {
|
||||
return selectedRecords.value.filter((record) => {
|
||||
const status = getConflictCheckStatus(record);
|
||||
return !status.isChecked && !status.isLoading;
|
||||
}).length;
|
||||
};
|
||||
|
||||
/** 获取预约状态文本 */
|
||||
const getStatusText = (status: string) => {
|
||||
const statusMap: { [key: string]: string } = {
|
||||
'0': '审核中',
|
||||
'1': '已批准',
|
||||
'2': '已拒绝'
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
};
|
||||
|
||||
/** 页面加载完成后的初始化 */
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getHallList();
|
||||
@@ -855,22 +1197,31 @@ onMounted(() => {
|
||||
|
||||
/* 表格中预约用途文本样式 */
|
||||
.purpose-text {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
display: -webkit-box;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.4;
|
||||
cursor: pointer;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
/* 表格中审核备注文本样式 */
|
||||
.audit-remark-text {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
display: -webkit-box;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.4;
|
||||
cursor: pointer;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
/* 三行省略样式 */
|
||||
.line-clamp-3 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
max-height: 4.2em;
|
||||
}
|
||||
|
||||
/* 查看详情表单样式 */
|
||||
@@ -920,11 +1271,6 @@ onMounted(() => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 选中报告厅的图片展示区域 */
|
||||
.selected-hall-images {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-gallery {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -977,6 +1323,30 @@ onMounted(() => {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* 暂无图片占位样式 */
|
||||
.no-images-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
height: 120px;
|
||||
background-color: #f5f7fa;
|
||||
border: 2px dashed #dcdfe6;
|
||||
border-radius: 8px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.no-images-placeholder .el-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-images-placeholder span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 审核对话框样式 */
|
||||
.audit-form .form-text {
|
||||
color: #303133;
|
||||
@@ -1026,7 +1396,7 @@ onMounted(() => {
|
||||
.hall-id {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
background: #f0f2f5;
|
||||
/* background: #f0f2f5; */
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
@@ -1161,6 +1531,136 @@ onMounted(() => {
|
||||
font-size: 14px;
|
||||
resize: none !important;
|
||||
}
|
||||
|
||||
/* 审核建议样式 - 增强版 */
|
||||
.audit-suggestion-content {
|
||||
line-height: 1.6;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.audit-suggestion-content .el-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* 冲突详情在新行展示 */
|
||||
.audit-suggestion-content .conflict-details {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
/* 不同状态的颜色 */
|
||||
.audit-suggestion-content.checking-conflict {
|
||||
color: #409eff; /* 检查中 - 蓝色 */
|
||||
}
|
||||
|
||||
.audit-suggestion-content.check-error {
|
||||
color: #e6a23c; /* 检查错误 - 橙色 */
|
||||
}
|
||||
|
||||
.audit-suggestion-content.has-conflict {
|
||||
color: #f56c6c; /* 有冲突 - 红色 */
|
||||
}
|
||||
|
||||
.audit-suggestion-content.no-conflict {
|
||||
color: #67c23a; /* 无冲突 - 绿色 */
|
||||
}
|
||||
|
||||
.audit-suggestion-content.not-checked {
|
||||
color: #909399; /* 未检查 - 灰色 */
|
||||
}
|
||||
|
||||
/* 冲突详情样式 */
|
||||
.conflict-details {
|
||||
margin-top: 8px;
|
||||
padding: 8px;
|
||||
background-color: #fef2f2;
|
||||
border-left: 3px solid #f56c6c;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.conflict-list {
|
||||
margin: 4px 0 0 0;
|
||||
padding-left: 16px;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.conflict-list li {
|
||||
margin: 2px 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 文本颜色工具类 */
|
||||
.text-success {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.text-info {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.rotate-loading {
|
||||
animation: rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 冲突检查统计样式 */
|
||||
.conflict-summary {
|
||||
margin: 0px 0;
|
||||
padding: 0px;
|
||||
/* background-color: #f9fafe; */
|
||||
border-radius: 6px;
|
||||
/* border: 1px solid #e4e7ed; */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap; /* 超出时自动换行 */
|
||||
}
|
||||
|
||||
.summary-stats {
|
||||
display: inline-flex; /* 与标签同行 */
|
||||
gap: 8px;
|
||||
margin-top: 0; /* 移除顶部间距,保持同一行 */
|
||||
flex-wrap: wrap; /* 达到最大宽度时自身也可换行 */
|
||||
}
|
||||
|
||||
.conflict-warning-text {
|
||||
margin-top: 8px;
|
||||
color: #e6a23c;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -3,19 +3,30 @@
|
||||
<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="hallName">
|
||||
<el-input v-model="queryParams.hallName" placeholder="请输入报告厅名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="报告厅状态" prop="hallStatus">
|
||||
<el-select v-model="queryParams.hallStatus" placeholder="请选择报告厅状态" clearable>
|
||||
<el-option v-for="dict in hall_status" :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 ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="报告厅名称" prop="hallName">
|
||||
<el-input v-model="queryParams.hallName" placeholder="请输入报告厅名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="报告厅状态" prop="hallStatus">
|
||||
<el-select v-model="queryParams.hallStatus" placeholder="请选择报告厅状态" clearable style="width: 100%">
|
||||
<el-option v-for="dict in hall_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<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-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -40,11 +51,11 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="hallInfoList" @selection-change="handleSelectionChange" style="margin-bottom: 20px">
|
||||
<el-table v-loading="loading" border :data="hallInfoList" @selection-change="handleSelectionChange" @row-dblclick="handleView">
|
||||
<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" />
|
||||
<el-table-column label="报告厅图片" align="center" prop="hallImage" width="100">
|
||||
<el-table-column label="报告厅图片" align="center" prop="hallImage" width="200">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.hallImage" class="table-image">
|
||||
<image-preview :src="scope.row.hallImage" :width="50" :height="50" />
|
||||
@@ -54,21 +65,25 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报告厅状态" align="center" prop="hallStatus">
|
||||
<el-table-column label="报告厅状态" align="center" prop="hallStatus" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="hall_status" :value="scope.row.hallStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报告厅描述" align="center" prop="hallDescription" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160">
|
||||
<el-table-column label="报告厅描述" align="center" prop="hallDescription">
|
||||
<template #default="scope">
|
||||
<div class="description-cell">{{ scope.row.hallDescription }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="info"
|
||||
type="primary"
|
||||
icon="View"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px"
|
||||
style="font-size: 16px; margin-right: 8px; color: blue"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['hq:hallInfo:query']"
|
||||
></el-button>
|
||||
@@ -477,4 +492,68 @@ onMounted(() => {
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.el-popper.description-tooltip-popper {
|
||||
width: 300px !important;
|
||||
max-width: 300px !important;
|
||||
}
|
||||
|
||||
.el-popper.description-tooltip-popper .el-popper__content {
|
||||
width: 300px !important;
|
||||
max-width: 300px !important;
|
||||
}
|
||||
|
||||
.el-popper.description-tooltip-popper .el-tooltip__content {
|
||||
width: 100% !important;
|
||||
max-height: 200px !important;
|
||||
overflow-y: auto !important;
|
||||
white-space: pre-wrap !important;
|
||||
word-break: break-all !important;
|
||||
text-align: left !important;
|
||||
line-height: 1.5 !important;
|
||||
padding: 8px 12px !important;
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 3px;
|
||||
background-color: rgba(144, 147, 153, 0.3);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(144, 147, 153, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
border-radius: 3px;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.description-cell {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.5;
|
||||
max-height: 4.5em;
|
||||
text-align: left;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,28 +3,54 @@
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div ref="searchRef" 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="报告厅ID" prop="hallId">
|
||||
<el-input v-model="queryParams.hallId" placeholder="请输入报告厅ID" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="报告厅名称" prop="hallName">
|
||||
<el-input v-model="queryParams.hallName" placeholder="请输入报告厅名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预约开始时间" prop="startTime">
|
||||
<el-date-picker clearable v-model="queryParams.startTime" type="date" placeholder="请选择开始时间" value-format="YYYY-MM-DD" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预约结束时间" prop="endTime">
|
||||
<el-date-picker clearable v-model="queryParams.endTime" type="date" placeholder="请选择结束时间" value-format="YYYY-MM-DD" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预约状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择预约状态" clearable>
|
||||
<el-option v-for="dict in hall_reserv_status" :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 ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="报告厅名称" prop="hallName">
|
||||
<el-input v-model="queryParams.hallName" placeholder="请输入报告厅名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预约开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.startTime"
|
||||
type="date"
|
||||
placeholder="请选择开始时间"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预约结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.endTime"
|
||||
type="date"
|
||||
placeholder="请选择结束时间"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="预约状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择预约状态" clearable style="width: 100%">
|
||||
<el-option v-for="dict in hall_reserv_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<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-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -37,23 +63,26 @@
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['hq:hallReservation:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:hallReservation:edit']"
|
||||
>修改</el-button
|
||||
>
|
||||
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:hallReservation:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:hallReservation:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:hallReservation:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:hallReservation:export']">导出</el-button>
|
||||
<el-button icon="Download" @click="handleExport" v-hasPermi="['hq:hallReservation:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="hallReservationList" @selection-change="handleSelectionChange" style="margin-bottom: 0">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
border
|
||||
:data="hallReservationList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="margin-bottom: 0"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<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" />
|
||||
@@ -78,13 +107,9 @@
|
||||
:hide-after="100"
|
||||
effect="dark"
|
||||
>
|
||||
<span class="purpose-text">
|
||||
{{
|
||||
scope.row.reservationPurpose && scope.row.reservationPurpose.length > 100
|
||||
? scope.row.reservationPurpose.substring(0, 100) + '...'
|
||||
: scope.row.reservationPurpose
|
||||
}}
|
||||
</span>
|
||||
<div class="purpose-text">
|
||||
{{ scope.row.reservationPurpose }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -110,17 +135,13 @@
|
||||
:hide-after="100"
|
||||
effect="dark"
|
||||
>
|
||||
<span class="audit-remark-text">
|
||||
{{
|
||||
scope.row.auditRemark && scope.row.auditRemark.length > 100
|
||||
? scope.row.auditRemark.substring(0, 100) + '...'
|
||||
: scope.row.auditRemark
|
||||
}}
|
||||
</span>
|
||||
<div class="audit-remark-text">
|
||||
{{ scope.row.auditRemark }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220">
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220" fixed="right">
|
||||
<template #default="scope">
|
||||
<!-- 查看按钮,所有记录都可以查看 -->
|
||||
<el-tooltip content="查看" placement="top">
|
||||
@@ -186,7 +207,7 @@
|
||||
<span class="hall-id">(ID: {{ hall.id }})</span>
|
||||
</div>
|
||||
<div class="hall-description" v-if="hall.hallDescription">
|
||||
{{ hall.hallDescription }}
|
||||
{{ displayHallDescription(hall.hallDescription) }}
|
||||
</div>
|
||||
<div class="hall-images">
|
||||
<template v-if="getHallImages(hall.hallImage || '').length > 0">
|
||||
@@ -206,9 +227,9 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 报告厅图片展示区域 -->
|
||||
<el-form-item label="报告厅图片" v-if="selectedHallImages.length > 0">
|
||||
<el-form-item label="报告厅图片">
|
||||
<div class="selected-hall-images">
|
||||
<div class="image-gallery">
|
||||
<div class="image-gallery" v-if="selectedHallImages.length > 0">
|
||||
<div v-for="(image, index) in selectedHallImages" :key="index" class="image-item" @click="previewImage(image, index)">
|
||||
<img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div class="image-overlay">
|
||||
@@ -216,6 +237,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-images-placeholder">
|
||||
<el-icon><Picture /></el-icon>
|
||||
<span>暂无图片</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="预约开始时间" prop="startTime">
|
||||
@@ -226,6 +251,7 @@
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择预约开始时间"
|
||||
style="width: 100%"
|
||||
@change="handleStartTimeChange"
|
||||
>
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
@@ -269,9 +295,9 @@
|
||||
<span class="form-text">{{ viewForm.hallName }}</span>
|
||||
</el-form-item>
|
||||
<!-- 报告厅图片展示区域 -->
|
||||
<el-form-item label="报告厅图片" v-if="viewHallImages.length > 0">
|
||||
<el-form-item label="报告厅图片">
|
||||
<div class="view-hall-images">
|
||||
<div class="image-gallery">
|
||||
<div class="image-gallery" v-if="viewHallImages.length > 0">
|
||||
<div v-for="(image, index) in viewHallImages" :key="index" class="image-item" @click="previewImage(image, index)">
|
||||
<img :src="image" class="gallery-image" @error="handleImageError" />
|
||||
<div class="image-overlay">
|
||||
@@ -279,6 +305,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-images-placeholder">
|
||||
<el-icon><Picture /></el-icon>
|
||||
<span>暂无图片</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="预约开始时间">
|
||||
@@ -301,6 +331,19 @@
|
||||
<el-form-item label="预约状态">
|
||||
<dict-tag :options="hall_reserv_status" :value="viewForm.status" />
|
||||
</el-form-item>
|
||||
<!-- 当状态为审核中时显示可审核人员列表 -->
|
||||
<el-form-item label="可审核人员" v-if="viewForm.status === '0' && auditorList.length > 0">
|
||||
<div class="auditor-list">
|
||||
<div v-for="auditor in auditorList" :key="auditor.userId" class="auditor-item">
|
||||
<el-avatar :size="32" :src="auditor.avatar">{{ auditor.nickName?.substring(0, 1) }}</el-avatar>
|
||||
<div class="auditor-info">
|
||||
<div class="auditor-name">{{ auditor.nickName || auditor.userName }}</div>
|
||||
<div class="auditor-dept">{{ auditor.dept?.deptName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="auditorList.length === 0" class="no-auditor">暂无可审核人员</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核人" v-if="viewForm.auditName">
|
||||
<span class="form-text">{{ viewForm.auditName }}</span>
|
||||
</el-form-item>
|
||||
@@ -325,7 +368,7 @@
|
||||
<script setup name="HallReservation" lang="ts">
|
||||
import { listHallReservation, getHallReservation, delHallReservation, addHallReservation, updateHallReservation } from '@/api/hq/hallReservation';
|
||||
import { listHallInfo } from '@/api/hq/hallInfo';
|
||||
import { getUserProfile } from '@/api/system/user';
|
||||
import { getUserProfile, listUsersByPermission } from '@/api/system/user';
|
||||
import { HallReservationVO, HallReservationQuery, HallReservationForm } from '@/api/hq/hallReservation/types';
|
||||
import { HallInfoVO } from '@/api/hq/hallInfo/types';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
@@ -375,6 +418,44 @@ const viewForm = ref<HallReservationForm>({
|
||||
auditRemark: undefined
|
||||
});
|
||||
|
||||
// 添加审核人员列表
|
||||
const auditorList = ref<any[]>([]);
|
||||
|
||||
/** 获取具有审核权限的人员列表 */
|
||||
const getAuditorList = async () => {
|
||||
try {
|
||||
const res = await listUsersByPermission('hq:hallAudit:list');
|
||||
auditorList.value = res.data || [];
|
||||
console.log('审核人员列表:', auditorList.value);
|
||||
} catch (error) {
|
||||
console.error('获取审核人员列表失败:', error);
|
||||
auditorList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/** 验证结束时间 */
|
||||
const validateEndTime = (rule: any, value: any, callback: any) => {
|
||||
if (value && form.value.startTime) {
|
||||
const startTime = new Date(form.value.startTime).getTime();
|
||||
const endTime = new Date(value).getTime();
|
||||
if (endTime <= startTime) {
|
||||
callback(new Error('结束时间必须晚于开始时间'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
/** 开始时间变化时触发结束时间验证 */
|
||||
const handleStartTimeChange = () => {
|
||||
// 如果结束时间已经填写,则触发验证
|
||||
if (data.form.endTime) {
|
||||
hallReservationFormRef.value?.validateField('endTime');
|
||||
}
|
||||
};
|
||||
|
||||
const initFormData: HallReservationForm = {
|
||||
id: undefined,
|
||||
hallId: undefined,
|
||||
@@ -390,6 +471,7 @@ const initFormData: HallReservationForm = {
|
||||
auditTime: undefined,
|
||||
auditRemark: undefined
|
||||
};
|
||||
|
||||
const data = reactive<PageData<HallReservationForm, HallReservationQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
@@ -409,7 +491,25 @@ const data = reactive<PageData<HallReservationForm, HallReservationQuery>>({
|
||||
rules: {
|
||||
hallName: [{ required: true, message: '报告厅名称不能为空', trigger: 'change' }],
|
||||
startTime: [{ required: true, message: '预约开始时间不能为空', trigger: 'blur' }],
|
||||
endTime: [{ required: true, message: '预约结束时间不能为空', trigger: 'blur' }],
|
||||
endTime: [
|
||||
{ required: true, message: '预约结束时间不能为空', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (value && data.form.startTime) {
|
||||
const startTime = new Date(data.form.startTime).getTime();
|
||||
const endTime = new Date(value).getTime();
|
||||
if (endTime <= startTime) {
|
||||
callback(new Error('结束时间必须晚于开始时间'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
reservationPurpose: [{ required: true, message: '预约用途不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
@@ -635,6 +735,11 @@ const handleView = async (row: HallReservationVO) => {
|
||||
const res = await getHallReservation(row.id);
|
||||
Object.assign(viewForm.value, res.data);
|
||||
|
||||
// 如果状态是审核中,获取审核人员列表
|
||||
if (viewForm.value.status === '0') {
|
||||
await getAuditorList();
|
||||
}
|
||||
|
||||
// 查找对应的报告厅并显示图片
|
||||
if (viewForm.value.hallName) {
|
||||
const selectedHall = hallList.value.find((hall) => hall.hallName === viewForm.value.hallName);
|
||||
@@ -747,6 +852,13 @@ const handleExport = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// 在 <script setup> 部分适当位置添加工具函数
|
||||
/** 将文本截断到指定长度(默认20个字符),超出部分以省略号结尾 */
|
||||
const displayHallDescription = (desc: string, maxLen = 20) => {
|
||||
if (!desc) return '';
|
||||
return desc.length > maxLen ? desc.slice(0, maxLen) + '...' : desc;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getHallList();
|
||||
@@ -808,19 +920,19 @@ onMounted(() => {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.3;
|
||||
max-height: 36px;
|
||||
max-width: 240px; /* 新增:限制最大宽度,防止下拉框过宽 */
|
||||
overflow: hidden;
|
||||
white-space: nowrap; /* 单行显示 */
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 下拉框图片容器:每行最多 6 张图片 */
|
||||
.hall-images {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 40px); /* 6 列,每列宽 40px */
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hall-image {
|
||||
@@ -849,22 +961,28 @@ onMounted(() => {
|
||||
|
||||
/* 表格中预约用途文本样式 */
|
||||
.purpose-text {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.4;
|
||||
cursor: pointer;
|
||||
max-height: 4.2em;
|
||||
}
|
||||
|
||||
/* 表格中审核备注文本样式 */
|
||||
.audit-remark-text {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.4;
|
||||
cursor: pointer;
|
||||
max-height: 4.2em;
|
||||
}
|
||||
|
||||
/* 查看详情表单样式 */
|
||||
@@ -923,6 +1041,7 @@ onMounted(() => {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
max-width: 450px; /* 与预约用途保持一致 */
|
||||
}
|
||||
|
||||
.image-item {
|
||||
@@ -970,6 +1089,88 @@ onMounted(() => {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* 暂无图片占位样式 */
|
||||
.no-images-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 450px; /* 与预约用途保持一致 */
|
||||
height: 120px;
|
||||
background-color: #f5f7fa;
|
||||
border: 2px dashed #dcdfe6;
|
||||
border-radius: 8px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.no-images-placeholder .el-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-images-placeholder span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 审核人员列表样式 */
|
||||
.auditor-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.auditor-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.auditor-item:hover {
|
||||
background-color: #ecf5ff;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.auditor-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.auditor-name {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.auditor-dept {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.no-auditor {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 页面容器样式 */
|
||||
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -4,27 +4,44 @@
|
||||
<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 ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<!-- 第一行:4个检索元素 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="类型名称" prop="categoryName">
|
||||
<el-input v-model="queryParams.categoryName" placeholder="请输入类型名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="类型编码" prop="categoryCode">
|
||||
<el-input v-model="queryParams.categoryCode" placeholder="请输入类型编码" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="技能等级" prop="skillLevel">
|
||||
<el-select v-model="queryParams.skillLevel" placeholder="请选择技能等级" clearable 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-col :span="6">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 100%">
|
||||
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 操作按钮行 -->
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<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-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -53,15 +70,17 @@
|
||||
:default-expand-all="isExpandAll"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
border
|
||||
header-align="center"
|
||||
@row-click="handleRowClick"
|
||||
style="width: 100%"
|
||||
class="tree-table"
|
||||
>
|
||||
<el-table-column prop="categoryName" label="类型名称" min-width="260" show-overflow-tooltip>
|
||||
<el-table-column align="left" header-align="center" 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 }">
|
||||
<!-- <el-icon v-if="scope.row.iconUrl" class="category-icon" :style="{ color: scope.row.colorCode }">
|
||||
<component :is="getCategoryIcon(scope.row.categoryCode)" />
|
||||
</el-icon>
|
||||
</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 }}个子类
|
||||
@@ -69,8 +88,8 @@
|
||||
</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="categoryCode" label="类型编码" width="150" align="left" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="categoryDescription" label="类型描述" min-width="200" align="left" header-align="center" 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">
|
||||
@@ -78,7 +97,7 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="estimatedDuration" label="预估时长" width="120" align="center">
|
||||
<!-- <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>
|
||||
@@ -90,7 +109,7 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="usageCount" label="使用次数" width="100" align="center" />
|
||||
<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">
|
||||
@@ -170,7 +189,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<!-- <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%" />
|
||||
@@ -181,13 +200,13 @@
|
||||
<el-input-number v-model="form.standardCost" :min="0" :precision="2" placeholder="元" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-row> -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="分类颜色" prop="colorCode">
|
||||
<el-color-picker v-model="form.colorCode" :predefine="predefineColors" :clearable="false" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</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%" />
|
||||
@@ -231,9 +250,9 @@
|
||||
<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 }">
|
||||
<!-- <el-icon v-if="viewData.iconUrl" class="category-icon" :style="{ color: viewData.colorCode }">
|
||||
<component :is="getCategoryIcon(viewData.categoryCode)" />
|
||||
</el-icon>
|
||||
</el-icon> -->
|
||||
<span class="category-name">{{ viewData.categoryName }}</span>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
@@ -244,7 +263,7 @@
|
||||
{{ getSkillLevelText(viewData.skillLevel) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="预估时长">
|
||||
<!-- <el-descriptions-item label="预估时长">
|
||||
{{ viewData.estimatedDuration ? formatDuration(viewData.estimatedDuration) : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="标准成本">
|
||||
@@ -253,7 +272,7 @@
|
||||
<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> -->
|
||||
<el-descriptions-item label="排序顺序">{{ viewData.sortOrder }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="viewData.status === '1' ? 'success' : 'danger'" size="small">
|
||||
@@ -286,6 +305,8 @@ import {
|
||||
updateRepairCategory,
|
||||
getRepairCategoryTree
|
||||
} from '@/api/hq/pm/repairCategory';
|
||||
import { ref, reactive, getCurrentInstance, nextTick, onMounted, type ComponentInternalInstance } from 'vue';
|
||||
import type { FormInstance as ElFormInstance, FormRules as ElFormRules } from 'element-plus';
|
||||
import { RepairCategoryVO, RepairCategoryForm, RepairCategoryQuery } from '@/api/hq/pm/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
@@ -296,6 +317,8 @@ const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const isExpandAll = ref(false);
|
||||
const refreshTable = ref(true);
|
||||
// 标记首次加载,用于控制默认展示主分类树
|
||||
const initialLoad = ref(true);
|
||||
|
||||
// 字典数据
|
||||
const pm_skill_level = ref([
|
||||
@@ -363,24 +386,20 @@ const buttonLoading = ref(false);
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 根据是否有搜索条件决定使用哪个接口
|
||||
let res;
|
||||
const hasSearchConditions =
|
||||
queryParams.value.categoryName || queryParams.value.categoryCode || queryParams.value.skillLevel || queryParams.value.status;
|
||||
|
||||
if (hasSearchConditions) {
|
||||
// 有搜索条件时使用列表接口,然后手动构建树形结构
|
||||
res = await listRepairCategory(queryParams.value);
|
||||
if (res.code === 200 && res.rows) {
|
||||
categoryList.value = buildTreeFromList(res.rows);
|
||||
// 首次加载时使用树形接口获取所有分类
|
||||
if (initialLoad.value) {
|
||||
const res = await getRepairCategoryTree();
|
||||
if (res.code === 200 && res.data) {
|
||||
categoryList.value = res.data;
|
||||
} else {
|
||||
categoryList.value = [];
|
||||
}
|
||||
initialLoad.value = false; // 首次加载完成后,后续操作都使用列表接口
|
||||
} else {
|
||||
// 无搜索条件时直接使用树形接口
|
||||
res = await getRepairCategoryTree();
|
||||
if (res.code === 200 && res.data) {
|
||||
categoryList.value = res.data;
|
||||
// 后续操作使用列表接口,并根据搜索条件过滤
|
||||
const res = await listRepairCategory(queryParams.value);
|
||||
if (res.code === 200 && res.rows) {
|
||||
categoryList.value = buildTreeFromList(res.rows);
|
||||
} else {
|
||||
categoryList.value = [];
|
||||
}
|
||||
@@ -468,6 +487,10 @@ const handleUpdate = async (row: RepairCategoryVO) => {
|
||||
getCategoryTree();
|
||||
const res = await getRepairCategory(row.id);
|
||||
Object.assign(form.value, res.data);
|
||||
// 若后端未返回技能等级,默认设置为中级
|
||||
if (!form.value.skillLevel) {
|
||||
form.value.skillLevel = '2';
|
||||
}
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改维修类型';
|
||||
};
|
||||
@@ -479,6 +502,11 @@ const handleView = async (row: RepairCategoryVO) => {
|
||||
viewDialog.visible = true;
|
||||
};
|
||||
|
||||
/** 行点击预览 */
|
||||
const handleRowClick = (row: RepairCategoryVO) => {
|
||||
handleView(row);
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
categoryFormRef.value?.validate(async (valid: boolean) => {
|
||||
@@ -515,13 +543,17 @@ const handleDelete = async (row: RepairCategoryVO) => {
|
||||
|
||||
/** 状态修改 */
|
||||
const handleStatusChange = async (row: RepairCategoryVO) => {
|
||||
const originalStatus = row.status;
|
||||
const text = row.status === '1' ? '启用' : '停用';
|
||||
try {
|
||||
await proxy?.$modal.confirm('确认要' + text + '"' + row.categoryName + '"类型吗?');
|
||||
await updateRepairCategory({ id: row.id, status: row.status });
|
||||
await proxy?.$modal.confirm(`确认要${text}"${row.categoryName}"类型吗?`);
|
||||
// 移除前端辅助字段,只保留后端需要的核心字段
|
||||
const { children, ...updateData } = row as any;
|
||||
await updateRepairCategory(updateData);
|
||||
proxy?.$modal.msgSuccess(text + '成功');
|
||||
} catch {
|
||||
row.status = row.status === '1' ? '0' : '1';
|
||||
} catch (e) {
|
||||
// 如果请求失败或被取消,则还原原状态
|
||||
row.status = originalStatus;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -543,7 +575,7 @@ const reset = () => {
|
||||
categoryDescription: '',
|
||||
parentId: 0, // 使用数字类型,确保与树形选择器数据类型一致
|
||||
requiredSkills: '',
|
||||
skillLevel: '',
|
||||
skillLevel: '2', // 默认中级
|
||||
estimatedDuration: undefined,
|
||||
standardCost: undefined,
|
||||
iconUrl: '',
|
||||
@@ -840,4 +872,14 @@ onMounted(() => {
|
||||
:deep(.el-color-picker__color) {
|
||||
border: 0px !important;
|
||||
}
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user