Explorar el Código

提交后勤管理中报告厅模块的代码

zlwu hace 1 año
padre
commit
5a47183ce3

+ 84 - 0
src/api/hq/hallAudit/index.ts

@@ -0,0 +1,84 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { HallReservationVO, HallReservationForm, HallReservationQuery } from '@/api/hq/hallReservation/types';
+
+/**
+ * 查询报告厅预约列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listHallReservation = (query?: HallReservationQuery): AxiosPromise<HallReservationVO[]> => {
+  return request({
+    url: '/hq/hallReservation/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询报告厅预约详细
+ * @param id
+ */
+export const getHallReservation = (id: string | number): AxiosPromise<HallReservationVO> => {
+  return request({
+    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({
+    url: '/hq/hallReservation/audit',
+    method: 'put',
+    data: data
+  });
+};

+ 201 - 0
src/api/hq/hallAudit/types.ts

@@ -0,0 +1,201 @@
+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;
+
+}
+
+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 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;
+}
+
+
+

+ 63 - 0
src/api/hq/hallInfo/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { HallInfoVO, HallInfoForm, HallInfoQuery } from '@/api/hq/hallInfo/types';
+
+/**
+ * 查询报告厅信息列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listHallInfo = (query?: HallInfoQuery): AxiosPromise<HallInfoVO[]> => {
+  return request({
+    url: '/hq/hallInfo/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询报告厅信息详细
+ * @param id
+ */
+export const getHallInfo = (id: string | number): AxiosPromise<HallInfoVO> => {
+  return request({
+    url: '/hq/hallInfo/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增报告厅信息
+ * @param data
+ */
+export const addHallInfo = (data: HallInfoForm) => {
+  return request({
+    url: '/hq/hallInfo',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改报告厅信息
+ * @param data
+ */
+export const updateHallInfo = (data: HallInfoForm) => {
+  return request({
+    url: '/hq/hallInfo',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除报告厅信息
+ * @param id
+ */
+export const delHallInfo = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/hq/hallInfo/' + id,
+    method: 'delete'
+  });
+};

+ 80 - 0
src/api/hq/hallInfo/types.ts

@@ -0,0 +1,80 @@
+export interface HallInfoVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 报告厅名称
+   */
+  hallName: string;
+
+  /**
+   * 报告厅图片
+   */
+  hallImage: string;
+
+  /**
+   * 报告厅图片Url
+   */
+  hallImageUrl: string;
+  /**
+   * 报告厅状态
+   */
+  hallStatus: string;
+
+  /**
+   * 报告厅描述
+   */
+  hallDescription: string;
+
+}
+
+export interface HallInfoForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 报告厅名称
+   */
+  hallName?: string;
+
+  /**
+   * 报告厅图片
+   */
+  hallImage?: string;
+
+  /**
+   * 报告厅状态
+   */
+  hallStatus?: string;
+
+  /**
+   * 报告厅描述
+   */
+  hallDescription?: string;
+
+}
+
+export interface HallInfoQuery extends PageQuery {
+
+  /**
+   * 报告厅名称
+   */
+  hallName?: string;
+
+  /**
+   * 报告厅状态
+   */
+  hallStatus?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/hq/hallReservation/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { HallReservationVO, HallReservationForm, HallReservationQuery } from '@/api/hq/hallReservation/types';
+
+/**
+ * 查询报告厅预约列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listHallReservation = (query?: HallReservationQuery): AxiosPromise<HallReservationVO[]> => {
+  return request({
+    url: '/hq/hallReservation/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询报告厅预约详细
+ * @param id
+ */
+export const getHallReservation = (id: string | number): AxiosPromise<HallReservationVO> => {
+  return request({
+    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'
+  });
+};

+ 201 - 0
src/api/hq/hallReservation/types.ts

@@ -0,0 +1,201 @@
+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;
+
+  /**
+   * 审核人
+   */
+  auditBy: number;
+
+  /**
+   * 审核人
+   */
+  auditName?: string;
+
+  /**
+   * 审核时间
+   */
+  auditTime: 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 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;
+}
+
+
+

+ 32 - 10
src/components/ImageUpload/index.vue

@@ -53,9 +53,9 @@ const props = defineProps({
     default: () => []
   },
   // 图片数量限制
-  limit: propTypes.number.def(5),
+  limit: propTypes.number.def(15),
   // 大小限制(MB)
-  fileSize: propTypes.number.def(5),
+  fileSize: propTypes.number.def(10),
   // 文件类型, 例如['png', 'jpg', 'jpeg']
   fileType: propTypes.array.def(['png', 'jpg', 'jpeg']),
   // 是否显示提示
@@ -99,19 +99,40 @@ watch(
       let list: OssVO[] = [];
       if (Array.isArray(val)) {
         list = val as OssVO[];
-      } else {
-        const res = await listByIds(val);
-        list = res.data;
+      } else if (typeof val === 'string') {
+        // 检查是否包含逗号分隔的URL
+        if (val.includes(',')) {
+          // 多个URL用逗号分隔
+          const urls = val.split(',').filter((url) => url.trim());
+          list = urls.map((url, index) => ({
+            ossId: url,
+            url: url.trim(),
+            originalName: `image_${index + 1}`
+          })) as any;
+        } else if (val.startsWith('http://') || val.startsWith('https://')) {
+          // 单个URL
+          list = [{ ossId: val, url: val, originalName: 'image' }] as any;
+        } else {
+          // 如果是OSS ID,调用接口获取详细信息
+          try {
+            const res = await listByIds(val);
+            list = res.data;
+          } catch (error) {
+            console.error('调用listByIds接口失败:', error);
+            list = [];
+          }
+        }
       }
       // 然后将数组转为对象数组
-      fileList.value = list.map((item) => {
+      fileList.value = list.map((item, index) => {
         // 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
         let itemData;
         if (typeof item === 'string') {
           itemData = { name: item, url: item };
         } else {
-          // 此处name使用ossId 防止删除出现重名
-          itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
+          // 此处name使用url 防止删除出现重名,对于URL格式使用url_index的格式
+          const name = item.url && (item.url.startsWith('http://') || item.url.startsWith('https://')) ? `${item.url}_${index}` : item.ossId;
+          itemData = { name: name, url: item.url, ossId: item.ossId };
         }
         return itemData;
       });
@@ -226,8 +247,9 @@ const listToString = (list: any[], separator?: string) => {
   let strs = '';
   separator = separator || ',';
   for (const i in list) {
-    if (undefined !== list[i].ossId && list[i].url.indexOf('blob:') !== 0) {
-      strs += list[i].ossId + separator;
+    if (undefined !== list[i].url && list[i].url.indexOf('blob:') !== 0) {
+      // 始终返回URL而不是ossId
+      strs += list[i].url + separator;
     }
   }
   return strs != '' ? strs.substring(0, strs.length - 1) : '';

+ 1352 - 0
src/views/hq/hallAudit/index.vue

@@ -0,0 +1,1352 @@
+<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]">
+        <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>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never" class="button-card table-card">
+      <template #header>
+        <el-row ref="toolbarRef" :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Check" :disabled="multiple" @click="handleApprove()" v-hasPermi="['hq:hallReservation:audit']"
+              >批准</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Close" :disabled="multiple" @click="handleReject()" v-hasPermi="['hq:hallReservation:audit']"
+              >拒绝</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain 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"
+        :height="tableHeight"
+        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" />
+        <el-table-column label="申请人" align="center" prop="applicantName" />
+        <el-table-column label="申请人电话" align="center" prop="applicantPhone" />
+        <el-table-column label="预约开始时间" align="center" prop="startTime" width="200">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="预约结束时间" align="center" prop="endTime" width="200">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="预约用途" align="center" prop="reservationPurpose" min-width="120">
+          <template #default="scope">
+            <el-tooltip
+              :content="scope.row.reservationPurpose"
+              placement="top"
+              :disabled="!scope.row.reservationPurpose || scope.row.reservationPurpose.length <= 100"
+              popper-class="purpose-tooltip"
+              :show-after="300"
+              :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>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <el-table-column label="预约状态" align="center" prop="status">
+          <template #default="scope">
+            <dict-tag :options="hall_reserv_status" :value="scope.row.status" />
+          </template>
+        </el-table-column>
+        <el-table-column label="审核人" align="center" prop="auditName" />
+        <el-table-column label="审核时间" align="center" prop="auditTime" width="200">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.auditTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="审核备注" align="center" prop="auditRemark" min-width="120">
+          <template #default="scope">
+            <el-tooltip
+              :content="scope.row.auditRemark"
+              placement="top"
+              :disabled="!scope.row.auditRemark || scope.row.auditRemark.length <= 100"
+              popper-class="audit-remark-tooltip"
+              :show-after="300"
+              :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>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220">
+          <template #default="scope">
+            <!-- 查看按钮,所有记录都可以查看 -->
+            <el-tooltip content="查看" placement="top">
+              <el-button
+                link
+                type="info"
+                icon="View"
+                size="default"
+                style="font-size: 16px; margin-right: 8px"
+                @click="handleView(scope.row)"
+              ></el-button>
+            </el-tooltip>
+
+            <!-- 只有审核中的记录可以进行审核操作 -->
+            <template v-if="scope.row.status === '0'">
+              <el-tooltip content="批准" placement="top">
+                <el-button
+                  link
+                  type="success"
+                  icon="Check"
+                  size="default"
+                  style="font-size: 16px; margin-right: 8px"
+                  @click="handleApprove(scope.row)"
+                  v-hasPermi="['hq:hallReservation:audit']"
+                ></el-button>
+              </el-tooltip>
+              <el-tooltip content="拒绝" placement="top">
+                <el-button
+                  link
+                  type="danger"
+                  icon="Close"
+                  size="default"
+                  style="font-size: 16px"
+                  @click="handleReject(scope.row)"
+                  v-hasPermi="['hq:hallReservation:audit']"
+                ></el-button>
+              </el-tooltip>
+            </template>
+          </template>
+        </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"
+      />
+    </el-card>
+
+    <!-- 查看报告厅预约详情对话框 -->
+    <el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="700px" append-to-body>
+      <el-form :model="viewForm" label-width="120px" class="view-form">
+        <el-form-item label="报告厅ID">
+          <span class="form-text">{{ viewForm.hallId }}</span>
+        </el-form-item>
+        <el-form-item label="报告厅名称">
+          <span class="form-text">{{ viewForm.hallName }}</span>
+        </el-form-item>
+        <!-- 报告厅图片展示区域 -->
+        <el-form-item label="报告厅图片" v-if="viewHallImages.length > 0">
+          <div class="view-hall-images">
+            <div class="image-gallery">
+              <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">
+                  <el-icon class="preview-icon"><ZoomIn /></el-icon>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-form-item>
+        <el-form-item label="预约开始时间">
+          <span class="form-text">{{ parseTime(viewForm.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </el-form-item>
+        <el-form-item label="预约结束时间">
+          <span class="form-text">{{ parseTime(viewForm.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </el-form-item>
+        <el-form-item label="预约用途">
+          <div class="purpose-view">
+            {{ viewForm.reservationPurpose }}
+          </div>
+        </el-form-item>
+        <el-form-item label="申请人信息">
+          <div class="applicant-info">
+            <p><strong>姓名:</strong>{{ viewForm.applicantName }}</p>
+            <p><strong>电话:</strong>{{ viewForm.applicantPhone }}</p>
+          </div>
+        </el-form-item>
+        <el-form-item label="预约状态">
+          <dict-tag :options="hall_reserv_status" :value="viewForm.status" />
+        </el-form-item>
+        <el-form-item label="审核人" v-if="viewForm.auditName">
+          <span class="form-text">{{ viewForm.auditName }}</span>
+        </el-form-item>
+        <el-form-item label="审核时间" v-if="viewForm.auditTime">
+          <span class="form-text">{{ parseTime(viewForm.auditTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </el-form-item>
+        <el-form-item label="审核备注" v-if="viewForm.auditRemark">
+          <div class="audit-remark">
+            {{ viewForm.auditRemark }}
+          </div>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeView">关 闭</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 审核对话框 -->
+    <el-dialog :title="auditDialog.title" v-model="auditDialog.visible" width="800px" append-to-body>
+      <el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="120px" class="audit-form">
+        <!-- 按报告厅分组显示预约信息 -->
+        <el-form-item label="审核详情">
+          <div class="audit-details">
+            <div v-for="(hallGroup, hallName) in groupedReservations" :key="`hall-group-${hallName}`" class="hall-group">
+              <!-- 报告厅信息 -->
+              <div class="hall-info-section">
+                <div class="hall-header">
+                  <div class="hall-name">{{ hallName }}</div>
+                  <div class="hall-id">ID: {{ hallGroup.hallId }}</div>
+                </div>
+                <div class="hall-images" v-if="getHallImagesByName(hallName).length > 0">
+                  <div class="image-gallery">
+                    <div v-for="(image, index) in getHallImagesByName(hallName)" :key="index" class="image-item" @click="previewImage(image, index)">
+                      <img :src="image" class="gallery-image" @error="handleImageError" />
+                      <div class="image-overlay">
+                        <el-icon class="preview-icon"><ZoomIn /></el-icon>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <div v-else class="no-images">暂无图片</div>
+              </div>
+
+              <!-- 该报告厅的预约申请列表 -->
+              <div class="reservation-list">
+                <div class="reservation-list-title">预约申请({{ hallGroup.reservations.length }}个)</div>
+                <div v-for="(record, index) in hallGroup.reservations" :key="record.id" class="reservation-item">
+                  <div class="reservation-index">申请 {{ index + 1 }}</div>
+                  <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>
+                    </div>
+                    <div class="detail-row">
+                      <span class="detail-label">预约时间:</span>
+                      <span class="detail-value">
+                        {{ parseTime(record.startTime, '{y}-{m}-{d} {h}:{i}') }} - {{ parseTime(record.endTime, '{y}-{m}-{d} {h}:{i}') }}
+                      </span>
+                    </div>
+                    <div class="detail-row">
+                      <span class="detail-label">预约用途:</span>
+                      <span class="detail-value purpose-content">{{ record.reservationPurpose }}</span>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-form-item>
+
+        <!-- 审核信息 -->
+        <el-form-item label="审核信息">
+          <div class="audit-info">
+            <div class="audit-status">
+              <span class="audit-label">审核状态:</span>
+              <el-tag :type="auditForm.status === '1' ? 'success' : 'danger'" size="large">
+                {{ auditForm.status === '1' ? '批准' : '拒绝' }}
+              </el-tag>
+            </div>
+            <div class="audit-remark-section">
+              <span class="audit-label">审核备注:</span>
+              <el-input
+                v-model="auditForm.auditRemark"
+                type="textarea"
+                :placeholder="auditForm.status === '1' ? '请输入审核备注(选填)' : '请输入拒绝原因'"
+                class="audit-remark-textarea"
+                resize="none"
+                maxlength="500"
+                show-word-limit
+              />
+            </div>
+          </div>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="auditLoading" type="primary" @click="confirmAudit">确 定</el-button>
+          <el-button @click="cancelAudit">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="HallAudit" lang="ts">
+import { listHallReservation, getHallReservation, auditHallReservation, HallReservationAuditForm } from '@/api/hq/hallAudit';
+import { listHallInfo } from '@/api/hq/hallInfo';
+import { HallReservationVO, HallReservationQuery, HallReservationForm } from '@/api/hq/hallReservation/types';
+import { HallInfoVO } from '@/api/hq/hallInfo/types';
+import { useUserStore } from '@/store/modules/user';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { hall_reserv_status } = toRefs<any>(proxy?.useDict('hall_reserv_status'));
+const userStore = useUserStore();
+
+const hallReservationList = ref<HallReservationVO[]>([]);
+const hallList = ref<HallInfoVO[]>([]);
+const viewHallImages = ref<string[]>([]);
+const loading = ref(true);
+const showSearch = ref(true);
+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 auditFormRef = ref<ElFormInstance>();
+
+// 审核表单验证规则
+const auditRules = computed(() => ({
+  auditRemark: auditForm.value.status === '2' ? [{ required: true, message: '拒绝原因不能为空', trigger: 'blur' }] : []
+}));
+
+const viewDialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const auditDialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const viewForm = ref<HallReservationForm>({
+  id: undefined,
+  hallId: undefined,
+  hallName: undefined,
+  startTime: undefined,
+  endTime: undefined,
+  reservationPurpose: undefined,
+  applicantId: undefined,
+  applicantName: undefined,
+  applicantPhone: undefined,
+  status: undefined,
+  auditBy: undefined,
+  auditName: undefined,
+  auditTime: undefined,
+  auditRemark: undefined
+});
+
+const auditForm = ref({
+  status: '1', // 1-批准, 2-拒绝
+  auditRemark: '',
+  ids: [] as Array<string | number>
+});
+
+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,
+  hallId: undefined,
+  hallName: undefined,
+  startTime: undefined,
+  endTime: undefined,
+  applicantId: undefined, // 审核界面显示所有用户的预约记录
+  applicantName: undefined, // 申请人姓名搜索条件
+  status: '0', // 默认查询审核中的记录
+  params: {
+    orderByColumn: 'create_time',
+    isAsc: 'desc'
+  }
+});
+
+/** 计算搜索栏实际高度 */
+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 {
+    const res = await listHallInfo({
+      pageNum: 1,
+      pageSize: 1000,
+      hallStatus: '1'
+    }); // 获取正常状态的报告厅
+    hallList.value = res.rows || [];
+
+    // 调试信息:检查每个报告厅的图片信息
+    console.log('报告厅列表数据:', hallList.value);
+    hallList.value.forEach((hall, index) => {
+      console.log(`报告厅 ${index + 1} - ${hall.hallName}:`, {
+        id: hall.id,
+        hallImage: hall.hallImage,
+        images: getHallImages(hall.hallImage || '')
+      });
+    });
+  } catch (error) {
+    console.error('获取报告厅列表失败:', error);
+    hallList.value = [];
+  }
+};
+
+/** 处理报告厅图片 - 可能有多张图片,用逗号分隔 */
+const getHallImages = (imageUrl: string): string[] => {
+  if (!imageUrl || imageUrl.trim() === '') return [];
+  // 支持多种分隔符:逗号、分号、空格
+  const separators = /[,;|\s]+/;
+  const images = imageUrl
+    .split(separators)
+    .map((url) => url.trim())
+    .filter((url) => url && url !== 'null' && url !== 'undefined');
+
+  console.log('处理图片URL:', imageUrl, '结果:', images);
+  return images;
+};
+
+/** 获取记录对应的报告厅图片 */
+const getRecordHallImages = (record: HallReservationVO): string[] => {
+  const selectedHall = hallList.value.find((hall) => hall.hallName === record.hallName);
+  if (selectedHall && selectedHall.hallImage) {
+    return getHallImages(selectedHall.hallImage);
+  }
+  return [];
+};
+
+/** 根据报告厅名称获取图片 */
+const getHallImagesByName = (hallName: string): string[] => {
+  const selectedHall = hallList.value.find((hall) => hall.hallName === hallName);
+  if (selectedHall && selectedHall.hallImage) {
+    return getHallImages(selectedHall.hallImage);
+  }
+  return [];
+};
+
+/** 获取选中记录中的唯一报告厅名称 */
+const uniqueHallNames = computed(() => {
+  return [...new Set(selectedRecords.value.map((record) => record.hallName))];
+});
+
+/** 按报告厅分组的预约记录 */
+const groupedReservations = computed(() => {
+  const groups: Record<string, { hallId: string; reservations: HallReservationVO[] }> = {};
+
+  selectedRecords.value.forEach((record) => {
+    const hallName = record.hallName;
+    if (!groups[hallName]) {
+      groups[hallName] = {
+        hallId: String(record.hallId || ''),
+        reservations: []
+      };
+    }
+    groups[hallName].reservations.push(record);
+  });
+
+  return groups;
+});
+
+/** 处理图片加载成功 */
+const handleImageLoad = (event: Event) => {
+  const img = event.target as HTMLImageElement;
+  console.log('图片加载成功:', img.src);
+};
+
+/** 处理图片加载错误 */
+const handleImageError = (event: Event) => {
+  const img = event.target as HTMLImageElement;
+  console.log('图片加载失败:', img.src);
+  img.style.display = 'none';
+};
+
+/** 预览图片 */
+const previewImage = (imageUrl: string, index: number) => {
+  // 创建一个临时的图片预览对话框
+  const imageViewer = document.createElement('div');
+  imageViewer.style.cssText = `
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(0, 0, 0, 0.8);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 9999;
+    cursor: pointer;
+  `;
+
+  const img = document.createElement('img');
+  img.src = imageUrl;
+  img.style.cssText = `
+    max-width: 90%;
+    max-height: 90%;
+    object-fit: contain;
+  `;
+
+  imageViewer.appendChild(img);
+  document.body.appendChild(imageViewer);
+
+  // 点击关闭预览
+  imageViewer.addEventListener('click', () => {
+    document.body.removeChild(imageViewer);
+  });
+};
+
+/** 查询报告厅预约列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    // 构建查询参数,处理时间格式
+    const params = { ...queryParams.value };
+
+    // 审核界面显示所有用户的预约记录,不限制申请人
+    // 注释掉原来的用户限制逻辑
+    // params.applicantId = userStore.userId;
+
+    console.log('查询参数:', params);
+
+    // 将时间参数转换为字符串格式的参数
+    if (params.startTime) {
+      params.startTimeStr = params.startTime + ' 00:00:00';
+      params.startTime = undefined; // 清除原始的Date参数
+    }
+
+    if (params.endTime) {
+      params.endTimeStr = params.endTime + ' 23:59:59';
+      params.endTime = undefined; // 清除原始的Date参数
+    }
+
+    console.log('最终查询参数:', params);
+
+    const res = await listHallReservation(params);
+    console.log('查询结果:', res);
+
+    hallReservationList.value = res.rows;
+    total.value = res.total;
+  } catch (error) {
+    console.error('查询报告厅预约列表失败:', error);
+    hallReservationList.value = [];
+    total.value = 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 监听搜索栏显示状态变化 */
+watch(showSearch, () => {
+  // 搜索栏显示状态变化时重新计算高度
+  calculateSearchHeight();
+});
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  // 审核界面重置后不限制申请人
+  queryParams.value.applicantId = undefined;
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: HallReservationVO[]) => {
+  ids.value = selection.map((item) => item.id);
+
+  // 只有审核中的记录可以进行编辑和删除操作
+  const editableSelection = selection.filter((item) => item.status === '0');
+
+  single.value = editableSelection.length != 1;
+  multiple.value = !editableSelection.length;
+};
+
+/** 查看按钮操作 */
+const handleView = async (row: HallReservationVO) => {
+  const res = await getHallReservation(row.id);
+  Object.assign(viewForm.value, res.data);
+
+  // 查找对应的报告厅并显示图片
+  if (viewForm.value.hallName) {
+    const selectedHall = hallList.value.find((hall) => hall.hallName === viewForm.value.hallName);
+    if (selectedHall && selectedHall.hallImage) {
+      viewHallImages.value = getHallImages(selectedHall.hallImage);
+    } else {
+      viewHallImages.value = [];
+    }
+  } else {
+    viewHallImages.value = [];
+  }
+
+  viewDialog.visible = true;
+  viewDialog.title = '查看报告厅预约详情';
+};
+
+/** 关闭查看对话框 */
+const closeView = () => {
+  viewDialog.visible = false;
+  viewHallImages.value = [];
+};
+
+/** 批准按钮操作 */
+const handleApprove = async (row?: HallReservationVO) => {
+  let _ids;
+  let records: HallReservationVO[] = [];
+
+  if (row) {
+    // 单个批准时检查状态
+    if (row.status !== '0') {
+      proxy?.$modal.msgWarning('该记录已审核,不能重复操作');
+      return;
+    }
+    _ids = [row.id];
+    records = [row];
+  } else {
+    // 批量批准时过滤出可操作的记录
+    const auditableRecords = hallReservationList.value.filter((item) => ids.value.includes(item.id) && item.status === '0');
+
+    if (auditableRecords.length === 0) {
+      proxy?.$modal.msgWarning('所选记录已审核,不能重复操作');
+      return;
+    }
+    _ids = auditableRecords.map((item) => item.id);
+    records = auditableRecords;
+  }
+
+  // 显示审核对话框
+  openAuditDialog('1', _ids, records, '批准预约申请');
+};
+
+/** 拒绝按钮操作 */
+const handleReject = async (row?: HallReservationVO) => {
+  let _ids;
+  let records: HallReservationVO[] = [];
+
+  if (row) {
+    // 单个拒绝时检查状态
+    if (row.status !== '0') {
+      proxy?.$modal.msgWarning('该记录已审核,不能重复操作');
+      return;
+    }
+    _ids = [row.id];
+    records = [row];
+  } else {
+    // 批量拒绝时过滤出可操作的记录
+    const auditableRecords = hallReservationList.value.filter((item) => ids.value.includes(item.id) && item.status === '0');
+
+    if (auditableRecords.length === 0) {
+      proxy?.$modal.msgWarning('所选记录已审核,不能重复操作');
+      return;
+    }
+    _ids = auditableRecords.map((item) => item.id);
+    records = auditableRecords;
+  }
+
+  // 显示审核对话框
+  openAuditDialog('2', _ids, records, '拒绝预约申请');
+};
+
+/** 打开审核对话框 */
+const openAuditDialog = (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;
+};
+
+/** 确认审核操作 */
+const confirmAudit = async () => {
+  auditFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      auditLoading.value = true;
+      try {
+        // 构建审核数据
+        const auditData: HallReservationAuditForm = {
+          ids: auditForm.value.ids,
+          status: auditForm.value.status,
+          auditRemark: auditForm.value.auditRemark || ''
+        };
+
+        console.log('提交审核数据:', auditData);
+
+        // 调用审核接口
+        await auditHallReservation(auditData);
+
+        const actionText = auditForm.value.status === '1' ? '批准' : '拒绝';
+        proxy?.$modal.msgSuccess(`${actionText}成功`);
+
+        // 关闭对话框并刷新列表
+        auditDialog.visible = false;
+        await getList();
+
+        // 清空选中状态
+        ids.value = [];
+        selectedRecords.value = [];
+      } catch (error) {
+        console.error('审核失败:', error);
+        const actionText = auditForm.value.status === '1' ? '批准' : '拒绝';
+        proxy?.$modal.msgError(`${actionText}失败,请稍后重试`);
+      } finally {
+        auditLoading.value = false;
+      }
+    }
+  });
+};
+
+/** 取消审核操作 */
+const cancelAudit = () => {
+  // 重置表单数据
+  auditForm.value.auditRemark = '';
+  auditForm.value.ids = [];
+  selectedRecords.value = [];
+
+  // 清除表单验证状态
+  auditFormRef.value?.resetFields();
+
+  // 关闭对话框
+  auditDialog.visible = false;
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'hq/hallReservation/export',
+    {
+      ...queryParams.value
+    },
+    `hallReservation_${new Date().getTime()}.xlsx`
+  );
+};
+
+/** 窗口大小变化监听 */
+const handleResize = () => {
+  windowHeight.value = window.innerHeight;
+  // 窗口大小变化时重新计算搜索栏和工具栏高度
+  calculateSearchHeight();
+  calculateToolbarHeight();
+};
+
+onMounted(async () => {
+  // 审核界面不需要限制用户信息
+
+  getList();
+  getHallList(); // 获取报告厅列表
+  // 初始计算搜索栏和工具栏高度
+  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 */
+}
+
+/* 表格卡片布局 */
+.table-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  min-height: 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 */
+}
+
+/* 移除按钮栏底部边框 */
+.button-card :deep(.el-card__header) {
+  border-bottom: none !important;
+}
+
+.hall-option {
+  padding: 8px 0;
+  line-height: 1.4;
+}
+
+.hall-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 4px;
+}
+
+.hall-name {
+  font-weight: 600;
+  color: #303133;
+  margin-right: 8px;
+}
+
+.hall-id {
+  font-size: 12px;
+  color: #909399;
+}
+
+.hall-description {
+  font-size: 12px;
+  color: #606266;
+  margin-bottom: 4px;
+  line-height: 1.3;
+  max-height: 36px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+
+.hall-images {
+  display: flex;
+  gap: 4px;
+  flex-wrap: wrap;
+}
+
+.hall-image {
+  width: 40px;
+  height: 30px;
+  object-fit: cover;
+  border-radius: 4px;
+  border: 1px solid #dcdfe6;
+}
+
+.no-image-text {
+  font-size: 11px;
+  color: #c0c4cc;
+  font-style: italic;
+}
+
+/* 预约用途固定宽高 */
+.purpose-textarea :deep(.el-textarea__inner) {
+  width: 100%;
+  max-width: 450px;
+  height: 120px;
+  resize: none;
+  min-height: 120px;
+  max-height: 120px;
+}
+
+/* 表格中预约用途文本样式 */
+.purpose-text {
+  display: inline-block;
+  max-width: 100%;
+  word-break: break-all;
+  word-wrap: break-word;
+  line-height: 1.4;
+  cursor: pointer;
+}
+
+/* 表格中审核备注文本样式 */
+.audit-remark-text {
+  display: inline-block;
+  max-width: 100%;
+  word-break: break-all;
+  word-wrap: break-word;
+  line-height: 1.4;
+  cursor: pointer;
+}
+
+/* 查看详情表单样式 */
+.view-form .form-text {
+  color: #303133;
+  font-size: 14px;
+  line-height: 1.6;
+}
+
+.purpose-view {
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  padding: 12px;
+  width: 450px;
+  height: 120px;
+  line-height: 1.6;
+  color: #303133;
+  white-space: pre-wrap;
+  word-break: break-word;
+  overflow-y: auto;
+}
+
+.applicant-info p {
+  margin: 0 0 8px 0;
+  color: #303133;
+  line-height: 1.6;
+}
+
+.applicant-info p:last-child {
+  margin-bottom: 0;
+}
+
+.audit-remark {
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  padding: 12px;
+  width: 450px;
+  height: 80px;
+  line-height: 1.6;
+  color: #303133;
+  white-space: pre-wrap;
+  word-break: break-word;
+  overflow-y: auto;
+}
+
+.view-hall-images {
+  width: 100%;
+}
+
+/* 选中报告厅的图片展示区域 */
+.selected-hall-images {
+  width: 100%;
+}
+
+.image-gallery {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+}
+
+.image-item {
+  position: relative;
+  width: 120px;
+  height: 90px;
+  border-radius: 8px;
+  overflow: hidden;
+  cursor: pointer;
+  transition: transform 0.2s ease;
+  border: 2px solid #e4e7ed;
+}
+
+.image-item:hover {
+  transform: scale(1.05);
+  border-color: #409eff;
+}
+
+.gallery-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  display: block;
+}
+
+.image-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  opacity: 0;
+  transition: opacity 0.2s ease;
+}
+
+.image-item:hover .image-overlay {
+  opacity: 1;
+}
+
+.preview-icon {
+  color: white;
+  font-size: 20px;
+}
+
+/* 审核对话框样式 */
+.audit-form .form-text {
+  color: #303133;
+  font-size: 14px;
+  line-height: 1.6;
+}
+
+/* 审核详情分组样式 */
+.audit-details {
+  width: 100%;
+}
+
+.hall-group {
+  border-radius: 12px;
+  padding: 20px;
+  margin-bottom: 20px;
+  border: 2px solid #e9ecef;
+  background: #fafbfc;
+}
+
+.hall-group:last-child {
+  margin-bottom: 0;
+}
+
+/* 报告厅信息区域 */
+.hall-info-section {
+  background: #fff;
+  border-radius: 8px;
+  padding: 16px;
+  margin-bottom: 16px;
+  border: 1px solid #e4e7ed;
+}
+
+.hall-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 12px;
+}
+
+.hall-name {
+  font-size: 18px;
+  font-weight: 600;
+  color: #1e40af;
+  margin-right: 12px;
+}
+
+.hall-id {
+  font-size: 14px;
+  color: #909399;
+  background: #f0f2f5;
+  padding: 4px 8px;
+  border-radius: 4px;
+}
+
+.hall-images {
+  margin-top: 8px;
+}
+
+.no-images {
+  color: #9ca3af;
+  font-style: italic;
+  text-align: center;
+  padding: 16px;
+  background: #f9fafb;
+  border-radius: 6px;
+  border: 2px dashed #d1d5db;
+  margin-top: 8px;
+}
+
+/* 预约申请列表 */
+.reservation-list {
+  background: #fff;
+  border-radius: 8px;
+  border: 1px solid #e4e7ed;
+}
+
+.reservation-list-title {
+  background: #f5f7fa;
+  padding: 12px 16px;
+  font-weight: 600;
+  color: #606266;
+  border-bottom: 1px solid #e4e7ed;
+  border-radius: 8px 8px 0 0;
+}
+
+.reservation-item {
+  padding: 16px;
+  border-bottom: 1px solid #f0f2f5;
+  position: relative;
+}
+
+.reservation-item:last-child {
+  border-bottom: none;
+  border-radius: 0 0 8px 8px;
+}
+
+.reservation-index {
+  position: absolute;
+  top: 12px;
+  right: 16px;
+  background: #1e40af;
+  color: white;
+  padding: 4px 8px;
+  border-radius: 12px;
+  font-size: 12px;
+  font-weight: 600;
+}
+
+.reservation-details {
+  line-height: 1.8;
+  padding-right: 80px; /* 为右上角标签留出空间 */
+}
+
+.detail-row {
+  display: flex;
+  margin-bottom: 8px;
+  align-items: flex-start;
+}
+
+.detail-row:last-child {
+  margin-bottom: 0;
+}
+
+.detail-label {
+  font-weight: 600;
+  color: #606266;
+  min-width: 80px;
+  flex-shrink: 0;
+}
+
+.detail-value {
+  color: #303133;
+  flex: 1;
+  word-break: break-word;
+}
+
+.detail-phone {
+  color: #909399;
+  margin-left: 4px;
+}
+
+.purpose-content {
+  line-height: 1.6;
+  max-height: 60px;
+  overflow-y: auto;
+  padding: 4px 0;
+}
+
+/* 审核信息样式 */
+.audit-info {
+  width: 100%;
+}
+
+.audit-status {
+  display: flex;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.audit-label {
+  font-weight: 600;
+  color: #606266;
+  margin-right: 12px;
+}
+
+.audit-remark-section {
+  display: flex;
+  flex-direction: column;
+}
+
+.audit-remark-section .audit-label {
+  margin-bottom: 8px;
+  margin-right: 0;
+}
+
+.audit-remark-textarea :deep(.el-textarea__inner) {
+  width: 100%;
+  height: 120px !important;
+  min-height: 120px !important;
+  max-height: 120px !important;
+  line-height: 1.6;
+  font-size: 14px;
+  resize: none !important;
+}
+</style>
+
+<style>
+/* 全局样式,用于美化下拉框 */
+.hall-select-dropdown .el-select-dropdown__item.hall-option-item {
+  height: auto !important;
+  padding: 12px 20px !important;
+  line-height: normal !important;
+}
+
+.hall-select-dropdown .el-select-dropdown__item.hall-option-item:hover {
+  background-color: #f5f7fa;
+}
+
+.hall-select-dropdown .el-select-dropdown__item.hall-option-item.selected {
+  background-color: #ecf5ff;
+}
+
+.hall-select-dropdown {
+  max-height: 400px;
+}
+
+/* 预约用途tooltip美化 */
+.purpose-tooltip {
+  max-width: 400px !important;
+  width: auto !important;
+}
+
+.purpose-tooltip .el-tooltip__content {
+  max-width: 400px !important;
+  white-space: pre-wrap !important;
+  word-break: break-word !important;
+  line-height: 1.6 !important;
+  padding: 12px 16px !important;
+  font-size: 14px !important;
+  border-radius: 8px !important;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
+}
+
+/* 审核备注tooltip美化 */
+.audit-remark-tooltip {
+  max-width: 400px !important;
+  width: auto !important;
+}
+
+.audit-remark-tooltip .el-tooltip__content {
+  max-width: 400px !important;
+  white-space: pre-wrap !important;
+  word-break: break-word !important;
+  line-height: 1.6 !important;
+  padding: 12px 16px !important;
+  font-size: 14px !important;
+  border-radius: 8px !important;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
+}
+</style>

+ 529 - 0
src/views/hq/hallInfo/index.vue

@@ -0,0 +1,529 @@
+<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="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>
+        </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="['hq:hallInfo:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:hallInfo:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:hallInfo:remove']"
+              >删除</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['hq:hallInfo: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="hallInfoList"
+        @selection-change="handleSelectionChange"
+        :height="tableHeight"
+        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" />
+        <el-table-column label="报告厅图片" align="center" prop="hallImage" width="100">
+          <template #default="scope">
+            <div v-if="scope.row.hallImage" class="table-image">
+              <image-preview :src="scope.row.hallImage" :width="50" :height="50" />
+            </div>
+            <div v-else class="table-no-image">
+              <span class="table-no-image-text">暂无图片</span>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="报告厅状态" align="center" prop="hallStatus">
+          <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">
+          <template #default="scope">
+            <el-tooltip content="查看" placement="top">
+              <el-button
+                link
+                type="info"
+                icon="View"
+                size="default"
+                style="font-size: 16px; margin-right: 8px"
+                @click="handleView(scope.row)"
+                v-hasPermi="['hq:hallInfo:query']"
+              ></el-button>
+            </el-tooltip>
+            <el-tooltip content="修改" placement="top">
+              <el-button
+                link
+                type="primary"
+                icon="Edit"
+                size="default"
+                style="font-size: 16px; margin-right: 8px"
+                @click="handleUpdate(scope.row)"
+                v-hasPermi="['hq:hallInfo:edit']"
+              ></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button
+                link
+                type="danger"
+                icon="Delete"
+                size="default"
+                style="font-size: 16px"
+                @click="handleDelete(scope.row)"
+                v-hasPermi="['hq:hallInfo:remove']"
+              ></el-button>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+    <!-- 添加或修改报告厅信息对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="1000px" append-to-body>
+      <el-form ref="hallInfoFormRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="报告厅名称" prop="hallName">
+          <el-input v-model="form.hallName" placeholder="请输入报告厅名称" />
+        </el-form-item>
+        <el-form-item label="报告厅图片" prop="hallImage">
+          <image-upload v-model="form.hallImage" />
+        </el-form-item>
+        <el-form-item label="报告厅状态" prop="hallStatus">
+          <el-radio-group v-model="form.hallStatus">
+            <el-radio v-for="dict in hall_status" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="报告厅描述" prop="hallDescription">
+          <el-input
+            v-model="form.hallDescription"
+            type="textarea"
+            placeholder="请输入内容"
+            resize="none"
+            style="height: 200px"
+            :autosize="false"
+            :rows="10"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 查看报告厅详细信息对话框 -->
+    <el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="1000px" append-to-body>
+      <el-form :model="viewForm" label-width="100px">
+        <el-form-item label="报告厅名称">
+          <span class="view-text">{{ viewForm.hallName || '暂无' }}</span>
+        </el-form-item>
+        <el-form-item label="报告厅图片">
+          <div v-if="viewImageList.length > 0" class="image-gallery">
+            <div v-for="(image, index) in viewImageList" :key="index" class="image-item">
+              <image-preview :src="image" :width="120" :height="80" />
+            </div>
+          </div>
+          <div v-else class="no-image">
+            <span class="no-image-text">暂无图片</span>
+          </div>
+        </el-form-item>
+        <el-form-item label="报告厅状态">
+          <dict-tag :options="hall_status" :value="viewForm.hallStatus" />
+        </el-form-item>
+        <el-form-item label="报告厅描述">
+          <div class="view-description">
+            {{ viewForm.hallDescription || '暂无描述' }}
+          </div>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancelView">关 闭</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="HallInfo" lang="ts">
+import { listHallInfo, getHallInfo, delHallInfo, addHallInfo, updateHallInfo } from '@/api/hq/hallInfo';
+import { HallInfoVO, HallInfoQuery, HallInfoForm } from '@/api/hq/hallInfo/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { hall_status } = toRefs<any>(proxy?.useDict('hall_status'));
+
+const hallInfoList = ref<HallInfoVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+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>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const viewDialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const viewForm = reactive<HallInfoForm>({
+  id: undefined,
+  hallName: undefined,
+  hallImage: undefined,
+  hallStatus: '1',
+  hallDescription: undefined
+});
+
+const viewImageList = ref<string[]>([]);
+
+const initFormData: HallInfoForm = {
+  id: undefined,
+  hallName: undefined,
+  hallImage: undefined,
+  hallStatus: '1',
+  hallDescription: undefined
+};
+const data = reactive<PageData<HallInfoForm, HallInfoQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    hallName: undefined,
+    hallStatus: undefined,
+    params: {}
+  },
+  rules: {
+    id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
+    hallName: [{ required: true, message: '报告厅名称不能为空', trigger: 'blur' }],
+    hallStatus: [{ required: true, message: '报告厅状态不能为空', trigger: 'change' }]
+  }
+});
+
+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;
+  const res = await listHallInfo(queryParams.value);
+  hallInfoList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  hallInfoFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: HallInfoVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = '添加报告厅信息';
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: HallInfoVO) => {
+  reset();
+  console.log('row', row);
+  const _id = row?.id || ids.value[0];
+  console.log('报告厅id', _id);
+  const res = await getHallInfo(_id);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = '修改报告厅信息';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  hallInfoFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateHallInfo(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addHallInfo(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: HallInfoVO) => {
+  const _ids = row?.id || ids.value;
+  let confirmMessage = '';
+
+  if (row) {
+    // 单个删除
+    confirmMessage = `是否确认删除报告厅"${row.hallName}(${row.id})"的数据项?`;
+  } else {
+    // 批量删除
+    const selectedItems = hallInfoList.value.filter((item) => ids.value.includes(item.id));
+    if (selectedItems.length === 1) {
+      confirmMessage = `是否确认删除报告厅"${selectedItems[0].hallName}(${selectedItems[0].id})"的数据项?`;
+    } else {
+      const names = selectedItems.map((item) => `${item.hallName}(${item.id})`).join('、');
+      confirmMessage = `是否确认删除报告厅"${names}"的数据项?`;
+    }
+  }
+
+  await proxy?.$modal.confirm(confirmMessage).finally(() => (loading.value = false));
+  await delHallInfo(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'hq/hallInfo/export',
+    {
+      ...queryParams.value
+    },
+    `hallInfo_${new Date().getTime()}.xlsx`
+  );
+};
+
+/** 窗口大小变化监听 */
+const handleResize = () => {
+  windowHeight.value = window.innerHeight;
+};
+
+/** 查看按钮操作 */
+const handleView = async (row: HallInfoVO) => {
+  Object.assign(viewForm, row);
+  // 处理图片列表,如果有多个图片用逗号分隔
+  if (row.hallImage) {
+    viewImageList.value = row.hallImage.split(',').filter((img) => img.trim() !== '');
+  } else {
+    viewImageList.value = [];
+  }
+  viewDialog.visible = true;
+  viewDialog.title = '查看报告厅信息';
+};
+
+/** 取消查看对话框 */
+const cancelView = () => {
+  viewDialog.visible = false;
+};
+
+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; /* 顶部和左右边距,底部无边距 */
+}
+
+.image-gallery {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+  padding: 8px;
+}
+
+.image-item {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: 1px solid #dcdfe6;
+  border-radius: 6px;
+  background-color: #fff;
+  padding: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  transition: all 0.3s ease;
+}
+
+.image-item:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+}
+
+.no-image {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 120px;
+  height: 80px;
+  border: 1px dashed #dcdfe6;
+  border-radius: 4px;
+  background-color: #fafafa;
+}
+
+.no-image-text {
+  color: #909399;
+  font-size: 12px;
+  text-align: center;
+}
+
+.table-no-image {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 50px;
+  height: 50px;
+  border: 1px dashed #dcdfe6;
+  border-radius: 4px;
+  background-color: #fafafa;
+  margin: 0 auto;
+}
+
+.table-no-image-text {
+  color: #909399;
+  font-size: 10px;
+  text-align: center;
+}
+
+.view-text {
+  font-size: 14px;
+  color: #303133;
+  line-height: 1.5;
+}
+
+.view-description {
+  padding: 12px;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  width: 100%;
+  height: 200px;
+  font-size: 14px;
+  color: #303133;
+  line-height: 1.6;
+  white-space: pre-wrap;
+  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;
+}
+</style>

+ 1170 - 0
src/views/hq/hallReservation/index.vue

@@ -0,0 +1,1170 @@
+<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]">
+        <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>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never" class="button-card table-card">
+      <template #header>
+        <el-row ref="toolbarRef" :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <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 type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['hq:hallReservation:edit']"
+              >修改</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['hq:hallReservation:remove']"
+              >删除</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain 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"
+        :height="tableHeight"
+        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" />
+        <el-table-column label="预约开始时间" align="center" prop="startTime" width="200">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="预约结束时间" align="center" prop="endTime" width="200">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="预约用途" align="center" prop="reservationPurpose" min-width="120">
+          <template #default="scope">
+            <el-tooltip
+              :content="scope.row.reservationPurpose"
+              placement="top"
+              :disabled="!scope.row.reservationPurpose || scope.row.reservationPurpose.length <= 100"
+              popper-class="purpose-tooltip"
+              :show-after="300"
+              :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>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <el-table-column label="预约状态" align="center" prop="status">
+          <template #default="scope">
+            <dict-tag :options="hall_reserv_status" :value="scope.row.status" />
+          </template>
+        </el-table-column>
+        <el-table-column label="审核人" align="center" prop="auditName" />
+        <el-table-column label="审核时间" align="center" prop="auditTime" width="200">
+          <template #default="scope">
+            <span>{{ parseTime(scope.row.auditTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="审核备注" align="center" prop="auditRemark" min-width="120">
+          <template #default="scope">
+            <el-tooltip
+              :content="scope.row.auditRemark"
+              placement="top"
+              :disabled="!scope.row.auditRemark || scope.row.auditRemark.length <= 100"
+              popper-class="audit-remark-tooltip"
+              :show-after="300"
+              :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>
+            </el-tooltip>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220">
+          <template #default="scope">
+            <!-- 查看按钮,所有记录都可以查看 -->
+            <el-tooltip content="查看" placement="top">
+              <el-button
+                link
+                type="info"
+                icon="View"
+                size="default"
+                style="font-size: 16px; margin-right: 8px"
+                @click="handleView(scope.row)"
+              ></el-button>
+            </el-tooltip>
+
+            <!-- 只有审核中的记录可以编辑和删除 -->
+            <template v-if="scope.row.status === '0'">
+              <el-tooltip content="修改" placement="top">
+                <el-button
+                  link
+                  type="primary"
+                  icon="Edit"
+                  size="default"
+                  style="font-size: 16px; margin-right: 8px"
+                  @click="handleUpdate(scope.row)"
+                  v-hasPermi="['hq:hallReservation:edit']"
+                ></el-button>
+              </el-tooltip>
+              <el-tooltip content="删除" placement="top">
+                <el-button
+                  link
+                  type="danger"
+                  icon="Delete"
+                  size="default"
+                  style="font-size: 16px"
+                  @click="handleDelete(scope.row)"
+                  v-hasPermi="['hq:hallReservation:remove']"
+                ></el-button>
+              </el-tooltip>
+            </template>
+          </template>
+        </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"
+      />
+    </el-card>
+    <!-- 添加或修改报告厅预约对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body>
+      <el-form ref="hallReservationFormRef" :model="form" :rules="rules" label-width="120px">
+        <el-form-item label="报告厅名称" prop="hallName">
+          <el-select
+            v-model="form.hallName"
+            placeholder="请选择报告厅"
+            clearable
+            @change="handleHallChange"
+            style="width: 100%"
+            popper-class="hall-select-dropdown"
+          >
+            <el-option v-for="hall in hallList" :key="hall.id" :label="hall.hallName" :value="hall.hallName" class="hall-option-item">
+              <div class="hall-option">
+                <div class="hall-header">
+                  <span class="hall-name">{{ hall.hallName }}</span>
+                  <span class="hall-id">(ID: {{ hall.id }})</span>
+                </div>
+                <div class="hall-description" v-if="hall.hallDescription">
+                  {{ hall.hallDescription }}
+                </div>
+                <div class="hall-images">
+                  <template v-if="getHallImages(hall.hallImage || '').length > 0">
+                    <img
+                      v-for="(image, index) in getHallImages(hall.hallImage || '')"
+                      :key="index"
+                      :src="image"
+                      class="hall-image"
+                      @error="handleImageError"
+                      @load="handleImageLoad"
+                    />
+                  </template>
+                  <span v-else class="no-image-text">暂无图片</span>
+                </div>
+              </div>
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <!-- 报告厅图片展示区域 -->
+        <el-form-item label="报告厅图片" v-if="selectedHallImages.length > 0">
+          <div class="selected-hall-images">
+            <div class="image-gallery">
+              <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">
+                  <el-icon class="preview-icon"><ZoomIn /></el-icon>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-form-item>
+        <el-form-item label="预约开始时间" prop="startTime">
+          <el-date-picker
+            clearable
+            v-model="form.startTime"
+            type="datetime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            placeholder="请选择预约开始时间"
+            style="width: 100%"
+          >
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="预约结束时间" prop="endTime">
+          <el-date-picker
+            clearable
+            v-model="form.endTime"
+            type="datetime"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            placeholder="请选择预约结束时间"
+            style="width: 100%"
+          >
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="预约用途" prop="reservationPurpose">
+          <el-input
+            v-model="form.reservationPurpose"
+            type="textarea"
+            placeholder="请输入预约用途"
+            class="purpose-textarea"
+            :autosize="false"
+            resize="none"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 查看报告厅预约详情对话框 -->
+    <el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="700px" append-to-body>
+      <el-form :model="viewForm" label-width="120px" class="view-form">
+        <el-form-item label="报告厅ID">
+          <span class="form-text">{{ viewForm.hallId }}</span>
+        </el-form-item>
+        <el-form-item label="报告厅名称">
+          <span class="form-text">{{ viewForm.hallName }}</span>
+        </el-form-item>
+        <!-- 报告厅图片展示区域 -->
+        <el-form-item label="报告厅图片" v-if="viewHallImages.length > 0">
+          <div class="view-hall-images">
+            <div class="image-gallery">
+              <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">
+                  <el-icon class="preview-icon"><ZoomIn /></el-icon>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-form-item>
+        <el-form-item label="预约开始时间">
+          <span class="form-text">{{ parseTime(viewForm.startTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </el-form-item>
+        <el-form-item label="预约结束时间">
+          <span class="form-text">{{ parseTime(viewForm.endTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </el-form-item>
+        <el-form-item label="预约用途">
+          <div class="purpose-view">
+            {{ viewForm.reservationPurpose }}
+          </div>
+        </el-form-item>
+        <el-form-item label="申请人信息">
+          <div class="applicant-info">
+            <p><strong>姓名:</strong>{{ viewForm.applicantName }}</p>
+            <p><strong>电话:</strong>{{ viewForm.applicantPhone }}</p>
+          </div>
+        </el-form-item>
+        <el-form-item label="预约状态">
+          <dict-tag :options="hall_reserv_status" :value="viewForm.status" />
+        </el-form-item>
+        <el-form-item label="审核人" v-if="viewForm.auditName">
+          <span class="form-text">{{ viewForm.auditName }}</span>
+        </el-form-item>
+        <el-form-item label="审核时间" v-if="viewForm.auditTime">
+          <span class="form-text">{{ parseTime(viewForm.auditTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </el-form-item>
+        <el-form-item label="审核备注" v-if="viewForm.auditRemark">
+          <div class="audit-remark">
+            {{ viewForm.auditRemark }}
+          </div>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeView">关 闭</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<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 { HallReservationVO, HallReservationQuery, HallReservationForm } from '@/api/hq/hallReservation/types';
+import { HallInfoVO } from '@/api/hq/hallInfo/types';
+import { useUserStore } from '@/store/modules/user';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { hall_reserv_status } = toRefs<any>(proxy?.useDict('hall_reserv_status'));
+const userStore = useUserStore();
+
+const hallReservationList = ref<HallReservationVO[]>([]);
+const hallList = ref<HallInfoVO[]>([]);
+const selectedHallImages = ref<string[]>([]);
+const viewHallImages = ref<string[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const showSearch = ref(true);
+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>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const viewDialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const viewForm = ref<HallReservationForm>({
+  id: undefined,
+  hallId: undefined,
+  hallName: undefined,
+  startTime: undefined,
+  endTime: undefined,
+  reservationPurpose: undefined,
+  applicantId: undefined,
+  applicantName: undefined,
+  applicantPhone: undefined,
+  status: undefined,
+  auditBy: undefined,
+  auditTime: undefined,
+  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,
+  hallName: undefined,
+  startTime: undefined,
+  endTime: undefined,
+  reservationPurpose: undefined,
+  applicantId: undefined,
+  applicantName: undefined,
+  applicantPhone: undefined,
+  status: '0', // 默认为审核中
+  auditBy: undefined,
+  auditTime: undefined,
+  auditRemark: undefined
+};
+const data = reactive<PageData<HallReservationForm, HallReservationQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    hallId: undefined,
+    hallName: undefined,
+    startTime: undefined,
+    endTime: undefined,
+    applicantId: userStore.userId, // 默认设置当前用户ID
+    status: undefined,
+    params: {
+      orderByColumn: 'create_time',
+      isAsc: 'desc'
+    }
+  },
+  rules: {
+    hallName: [{ required: true, message: '报告厅名称不能为空', trigger: 'change' }],
+    startTime: [{ required: true, message: '预约开始时间不能为空', trigger: 'blur' }],
+    endTime: [{ required: true, message: '预约结束时间不能为空', trigger: 'blur' }],
+    reservationPurpose: [{ required: true, message: '预约用途不能为空', trigger: 'blur' }]
+  }
+});
+
+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 {
+    const res = await listHallInfo({
+      pageNum: 1,
+      pageSize: 1000,
+      hallStatus: '1'
+    }); // 获取正常状态的报告厅
+    hallList.value = res.rows || [];
+
+    // 调试信息:检查每个报告厅的图片信息
+    console.log('报告厅列表数据:', hallList.value);
+    hallList.value.forEach((hall, index) => {
+      console.log(`报告厅 ${index + 1} - ${hall.hallName}:`, {
+        id: hall.id,
+        hallImage: hall.hallImage,
+        images: getHallImages(hall.hallImage || '')
+      });
+    });
+  } catch (error) {
+    console.error('获取报告厅列表失败:', error);
+    hallList.value = [];
+  }
+};
+
+/** 报告厅选择变化 */
+const handleHallChange = (hallName: string) => {
+  const selectedHall = hallList.value.find((hall) => hall.hallName === hallName);
+  if (selectedHall) {
+    form.value.hallId = selectedHall.id;
+    // 更新选中的报告厅图片
+    selectedHallImages.value = getHallImages(selectedHall.hallImage || '');
+  } else {
+    selectedHallImages.value = [];
+  }
+};
+
+/** 处理报告厅图片 - 可能有多张图片,用逗号分隔 */
+const getHallImages = (imageUrl: string): string[] => {
+  if (!imageUrl || imageUrl.trim() === '') return [];
+  // 支持多种分隔符:逗号、分号、空格
+  const separators = /[,;|\s]+/;
+  const images = imageUrl
+    .split(separators)
+    .map((url) => url.trim())
+    .filter((url) => url && url !== 'null' && url !== 'undefined');
+
+  console.log('处理图片URL:', imageUrl, '结果:', images);
+  return images;
+};
+
+/** 处理图片加载成功 */
+const handleImageLoad = (event: Event) => {
+  const img = event.target as HTMLImageElement;
+  console.log('图片加载成功:', img.src);
+};
+
+/** 处理图片加载错误 */
+const handleImageError = (event: Event) => {
+  const img = event.target as HTMLImageElement;
+  console.log('图片加载失败:', img.src);
+  img.style.display = 'none';
+};
+
+/** 预览图片 */
+const previewImage = (imageUrl: string, index: number) => {
+  // 创建一个临时的图片预览对话框
+  const imageViewer = document.createElement('div');
+  imageViewer.style.cssText = `
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(0, 0, 0, 0.8);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 9999;
+    cursor: pointer;
+  `;
+
+  const img = document.createElement('img');
+  img.src = imageUrl;
+  img.style.cssText = `
+    max-width: 90%;
+    max-height: 90%;
+    object-fit: contain;
+  `;
+
+  imageViewer.appendChild(img);
+  document.body.appendChild(imageViewer);
+
+  // 点击关闭预览
+  imageViewer.addEventListener('click', () => {
+    document.body.removeChild(imageViewer);
+  });
+};
+
+/** 设置当前用户信息 */
+const setCurrentUserInfo = async () => {
+  try {
+    // 获取当前用户的完整信息
+    const res = await getUserProfile();
+    const user = res.data.user;
+
+    form.value.applicantId = user.userId;
+    form.value.applicantName = user.nickName || user.userName;
+    form.value.applicantPhone = user.phonenumber || '';
+  } catch (error) {
+    console.error('获取用户信息失败:', error);
+    // 如果获取失败,使用store中的基本信息
+    form.value.applicantId = userStore.userId;
+    form.value.applicantName = userStore.nickname;
+    form.value.applicantPhone = '';
+  }
+};
+
+/** 查询报告厅预约列表 */
+const getList = async () => {
+  loading.value = true;
+  try {
+    // 构建查询参数,处理时间格式
+    const params = { ...queryParams.value };
+
+    // 确保只查询当前登录用户的预约记录
+    params.applicantId = userStore.userId;
+
+    // 调试信息:检查当前用户ID是否正确获取
+    console.log('当前用户ID:', userStore.userId);
+    console.log('查询参数:', params);
+
+    // 如果用户ID为空,则不进行查询
+    if (!params.applicantId) {
+      console.warn('用户ID为空,无法查询预约记录');
+      hallReservationList.value = [];
+      total.value = 0;
+      loading.value = false;
+      return;
+    }
+
+    // 将时间参数转换为字符串格式的参数
+    if (params.startTime) {
+      params.startTimeStr = params.startTime + ' 00:00:00';
+      params.startTime = undefined; // 清除原始的Date参数
+    }
+
+    if (params.endTime) {
+      params.endTimeStr = params.endTime + ' 23:59:59';
+      params.endTime = undefined; // 清除原始的Date参数
+    }
+
+    console.log('最终查询参数:', params);
+
+    const res = await listHallReservation(params);
+    console.log('查询结果:', res);
+
+    hallReservationList.value = res.rows;
+    total.value = res.total;
+  } catch (error) {
+    console.error('查询报告厅预约列表失败:', error);
+    hallReservationList.value = [];
+    total.value = 0;
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+};
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  selectedHallImages.value = [];
+  hallReservationFormRef.value?.resetFields();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 监听搜索栏显示状态变化 */
+watch(showSearch, () => {
+  // 搜索栏显示状态变化时重新计算高度
+  calculateSearchHeight();
+});
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  // 重置后确保applicantId仍然是当前用户
+  queryParams.value.applicantId = userStore.userId;
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: HallReservationVO[]) => {
+  ids.value = selection.map((item) => item.id);
+
+  // 只有审核中的记录可以进行编辑和删除操作
+  const editableSelection = selection.filter((item) => item.status === '0');
+
+  single.value = editableSelection.length != 1;
+  multiple.value = !editableSelection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = async () => {
+  reset();
+  await setCurrentUserInfo(); // 设置当前用户信息
+  dialog.visible = true;
+  dialog.title = '添加报告厅预约';
+};
+
+/** 查看按钮操作 */
+const handleView = async (row: HallReservationVO) => {
+  const res = await getHallReservation(row.id);
+  Object.assign(viewForm.value, res.data);
+
+  // 查找对应的报告厅并显示图片
+  if (viewForm.value.hallName) {
+    const selectedHall = hallList.value.find((hall) => hall.hallName === viewForm.value.hallName);
+    if (selectedHall && selectedHall.hallImage) {
+      viewHallImages.value = getHallImages(selectedHall.hallImage);
+    } else {
+      viewHallImages.value = [];
+    }
+  } else {
+    viewHallImages.value = [];
+  }
+
+  viewDialog.visible = true;
+  viewDialog.title = '查看报告厅预约详情';
+};
+
+/** 关闭查看对话框 */
+const closeView = () => {
+  viewDialog.visible = false;
+  viewHallImages.value = [];
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: HallReservationVO) => {
+  // 检查单个记录的状态
+  if (row && row.status !== '0') {
+    proxy?.$modal.msgWarning('该记录已审核,不能修改');
+    return;
+  }
+
+  // 检查批量选择的记录
+  if (!row) {
+    const selectedRecord = hallReservationList.value.find((item) => item.id === ids.value[0]);
+    if (selectedRecord && selectedRecord.status !== '0') {
+      proxy?.$modal.msgWarning('该记录已审核,不能修改');
+      return;
+    }
+  }
+
+  reset();
+  const _id = row?.id || ids.value[0];
+  const res = await getHallReservation(_id);
+  Object.assign(form.value, res.data);
+
+  // 如果有报告厅名称,找到对应的报告厅并显示图片
+  if (form.value.hallName) {
+    const selectedHall = hallList.value.find((hall) => hall.hallName === form.value.hallName);
+    if (selectedHall && selectedHall.hallImage) {
+      selectedHallImages.value = getHallImages(selectedHall.hallImage);
+    }
+  }
+
+  dialog.visible = true;
+  dialog.title = '修改报告厅预约';
+};
+
+/** 提交按钮 */
+const submitForm = () => {
+  hallReservationFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.id) {
+        await updateHallReservation(form.value).finally(() => (buttonLoading.value = false));
+      } else {
+        await addHallReservation(form.value).finally(() => (buttonLoading.value = false));
+      }
+      proxy?.$modal.msgSuccess('操作成功');
+      dialog.visible = false;
+      await getList();
+    }
+  });
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: HallReservationVO) => {
+  // 单个删除时检查状态
+  if (row && row.status !== '0') {
+    proxy?.$modal.msgWarning('该记录已审核,不能删除');
+    return;
+  }
+
+  // 批量删除时过滤出可删除的记录
+  let _ids;
+  if (row) {
+    _ids = row.id;
+  } else {
+    const editableIds = hallReservationList.value.filter((item) => ids.value.includes(item.id) && item.status === '0').map((item) => item.id);
+
+    if (editableIds.length === 0) {
+      proxy?.$modal.msgWarning('所选记录已审核,不能删除');
+      return;
+    }
+    _ids = editableIds;
+  }
+
+  await proxy?.$modal.confirm('是否确认删除报告厅预约编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delHallReservation(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'hq/hallReservation/export',
+    {
+      ...queryParams.value
+    },
+    `hallReservation_${new Date().getTime()}.xlsx`
+  );
+};
+
+/** 窗口大小变化监听 */
+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;
+
+  getList();
+  getHallList(); // 获取报告厅列表
+  // 初始计算搜索栏和工具栏高度
+  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 */
+}
+
+/* 表格卡片布局 */
+.table-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  min-height: 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 */
+}
+
+/* 移除按钮栏底部边框 */
+.button-card :deep(.el-card__header) {
+  border-bottom: none !important;
+}
+
+.hall-option {
+  padding: 8px 0;
+  line-height: 1.4;
+}
+
+.hall-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 4px;
+}
+
+.hall-name {
+  font-weight: 600;
+  color: #303133;
+  margin-right: 8px;
+}
+
+.hall-id {
+  font-size: 12px;
+  color: #909399;
+}
+
+.hall-description {
+  font-size: 12px;
+  color: #606266;
+  margin-bottom: 4px;
+  line-height: 1.3;
+  max-height: 36px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+
+.hall-images {
+  display: flex;
+  gap: 4px;
+  flex-wrap: wrap;
+}
+
+.hall-image {
+  width: 40px;
+  height: 30px;
+  object-fit: cover;
+  border-radius: 4px;
+  border: 1px solid #dcdfe6;
+}
+
+.no-image-text {
+  font-size: 11px;
+  color: #c0c4cc;
+  font-style: italic;
+}
+
+/* 预约用途固定宽高 */
+.purpose-textarea :deep(.el-textarea__inner) {
+  width: 100%;
+  max-width: 450px;
+  height: 120px;
+  resize: none;
+  min-height: 120px;
+  max-height: 120px;
+}
+
+/* 表格中预约用途文本样式 */
+.purpose-text {
+  display: inline-block;
+  max-width: 100%;
+  word-break: break-all;
+  word-wrap: break-word;
+  line-height: 1.4;
+  cursor: pointer;
+}
+
+/* 表格中审核备注文本样式 */
+.audit-remark-text {
+  display: inline-block;
+  max-width: 100%;
+  word-break: break-all;
+  word-wrap: break-word;
+  line-height: 1.4;
+  cursor: pointer;
+}
+
+/* 查看详情表单样式 */
+.view-form .form-text {
+  color: #303133;
+  font-size: 14px;
+  line-height: 1.6;
+}
+
+.purpose-view {
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  padding: 12px;
+  width: 450px;
+  height: 120px;
+  line-height: 1.6;
+  color: #303133;
+  white-space: pre-wrap;
+  word-break: break-word;
+  overflow-y: auto;
+}
+
+.applicant-info p {
+  margin: 0 0 8px 0;
+  color: #303133;
+  line-height: 1.6;
+}
+
+.applicant-info p:last-child {
+  margin-bottom: 0;
+}
+
+.audit-remark {
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  padding: 12px;
+  width: 450px;
+  height: 80px;
+  line-height: 1.6;
+  color: #303133;
+  white-space: pre-wrap;
+  word-break: break-word;
+  overflow-y: auto;
+}
+
+.view-hall-images {
+  width: 100%;
+}
+
+/* 选中报告厅的图片展示区域 */
+.selected-hall-images {
+  width: 100%;
+}
+
+.image-gallery {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+}
+
+.image-item {
+  position: relative;
+  width: 120px;
+  height: 90px;
+  border-radius: 8px;
+  overflow: hidden;
+  cursor: pointer;
+  transition: transform 0.2s ease;
+  border: 2px solid #e4e7ed;
+}
+
+.image-item:hover {
+  transform: scale(1.05);
+  border-color: #409eff;
+}
+
+.gallery-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  display: block;
+}
+
+.image-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  opacity: 0;
+  transition: opacity 0.2s ease;
+}
+
+.image-item:hover .image-overlay {
+  opacity: 1;
+}
+
+.preview-icon {
+  color: white;
+  font-size: 20px;
+}
+</style>
+
+<style>
+/* 全局样式,用于美化下拉框 */
+.hall-select-dropdown .el-select-dropdown__item.hall-option-item {
+  height: auto !important;
+  padding: 12px 20px !important;
+  line-height: normal !important;
+}
+
+.hall-select-dropdown .el-select-dropdown__item.hall-option-item:hover {
+  background-color: #f5f7fa;
+}
+
+.hall-select-dropdown .el-select-dropdown__item.hall-option-item.selected {
+  background-color: #ecf5ff;
+}
+
+.hall-select-dropdown {
+  max-height: 400px;
+}
+
+/* 预约用途tooltip美化 */
+.purpose-tooltip {
+  max-width: 400px !important;
+  width: auto !important;
+}
+
+.purpose-tooltip .el-tooltip__content {
+  max-width: 400px !important;
+  white-space: pre-wrap !important;
+  word-break: break-word !important;
+  line-height: 1.6 !important;
+  padding: 12px 16px !important;
+  font-size: 14px !important;
+  border-radius: 8px !important;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
+}
+
+/* 审核备注tooltip美化 */
+.audit-remark-tooltip {
+  max-width: 400px !important;
+  width: auto !important;
+}
+
+.audit-remark-tooltip .el-tooltip__content {
+  max-width: 400px !important;
+  white-space: pre-wrap !important;
+  word-break: break-word !important;
+  line-height: 1.6 !important;
+  padding: 12px 16px !important;
+  font-size: 14px !important;
+  border-radius: 8px !important;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
+}
+</style>