bzd_lxf 2 місяців тому
батько
коміт
60cb56ba83

+ 8 - 0
pm-admin/pom.xml

@@ -95,6 +95,14 @@
                     <warName>${project.artifactId}</warName>
                 </configuration>
            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>10</source>
+                    <target>10</target>
+                </configuration>
+            </plugin>
         </plugins>
         <finalName>${project.artifactId}</finalName>
     </build>

+ 147 - 0
pm-admin/src/main/java/com/pm/web/controller/accessControl/AccessControlController.java

@@ -0,0 +1,147 @@
+package com.pm.web.controller.accessControl;
+
+import com.pm.common.config.PageData;
+import com.pm.common.core.controller.BaseController;
+import com.pm.interfaceInfo.service.InterfaceInfoService;
+import okhttp3.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 门禁管理
+ */
+@RestController
+@RequestMapping("/accessControl/info")
+public class AccessControlController  extends BaseController {
+
+
+    /**
+     * 查询url 访问接口
+     */
+    @Autowired
+    private InterfaceInfoService interfaceInfoService;
+
+    /**
+     * 查询门禁管理列表
+     * @return
+     */
+    @GetMapping("/list")
+    public String list()
+    {
+        PageData pd = this.getPageData();
+        Response response = null;
+        try{
+            String name = "";
+            String appoint_time = "";
+            if (pd.containsKey("search")){
+                name = pd.getString("search");
+            }
+            if (pd.containsKey("appoint_time")){
+                appoint_time = pd.getString("appoint_time");
+            }
+            String info = "{\n" +
+                    "  \"date\": \""+pd.getString("date")+"\",\n" +
+                    "  \"search\": \""+name+"\",\n" +
+                    "  \"locationId\": [],\n" +
+                    "  \"capacity\": [],\n" +
+                    "  \"deviceType\": [],\n" +
+                    "  \"roomType\": [\n" +
+                    "    -1\n" +
+                    "  ],\n" +
+                    "  \"appoint_time\": \""+appoint_time+"\",\n" +
+                    "  \"status\": 1,\n" +
+                    "  \"page\": "+pd.getInteger("pageNum")+",\n" +
+                    "  \"page_size\": "+pd.getInteger("pageSize")+"\n" +
+                    "}";
+            PageData interface_info = interfaceInfoService.selectInterfaceInfoByName(pd.getString("interfaceName"));
+
+            pd.put("url",interface_info.get("url"));
+            OkHttpClient client = new OkHttpClient().newBuilder()
+                    .build();
+            MediaType mediaType = MediaType.parse("application/json");
+            RequestBody body = RequestBody.create(mediaType, info);
+            Request request = new Request.Builder()
+                    .url(interface_info.getString("url"))
+                    .method("POST", body)
+                    .addHeader("Content-Type", "application/json")
+                    .build();
+            response = client.newCall(request).execute();
+        } catch (Exception e){
+            e.printStackTrace();
+        }
+        try {
+            return response.body().string();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 查询历史记录
+     * @return
+     */
+    @GetMapping("/listHistory")
+    public String listHistory() throws Exception {
+        PageData pd = this.getPageData();
+        // 获取接口地址
+        PageData interface_info = interfaceInfoService.selectInterfaceInfoByName(pd.getString("interfaceName"));
+        String url = interface_info.getString("url");
+
+        // 构造请求参数
+        Map<String, String> params = new HashMap<>();
+        params.put("date", pd.getString("date"));
+        params.put("search", pd.getString("search"));
+        //params.put("appoint_time", );
+        params.put("status", String.valueOf(pd.getInteger("status")));
+        params.put("page", String.valueOf(pd.getInteger("pageNum")));
+        params.put("pageSize", String.valueOf(pd.getInteger("pageSize")));
+        params.put("locationId", ""); // 空数组
+        params.put("capacity", "");  // 空数组
+        params.put("deviceType", ""); // 空数组
+        params.put("roomType", "-1");
+        params.put("is_external", String.valueOf(pd.getInteger("is_external")));
+
+        // 构造完整的 URL
+        StringBuilder urlBuilder = new StringBuilder(url);
+        boolean firstParam = true;
+        for (Map.Entry<String, String> entry : params.entrySet()) {
+            if (entry.getValue() != null && !entry.getValue().isEmpty()) {
+                if (firstParam) {
+                    urlBuilder.append("?");
+                    firstParam = false;
+                } else {
+                    urlBuilder.append("&");
+                }
+                urlBuilder.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8))
+                        .append("=")
+                        .append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
+            }
+        }
+
+        // 使用 OkHttpClient 发送 GET 请求
+        OkHttpClient client = new OkHttpClient().newBuilder()
+                .build();
+
+        Request request = new Request.Builder()
+                .url(urlBuilder.toString())
+                .method("GET", null)
+                .build();
+
+        try (Response response = client.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                throw new RuntimeException("External API call failed with status code: " + response.code());
+            }
+            return response.body().string();
+        } catch (IOException e) {
+            throw new RuntimeException("Error calling external API", e);
+        }
+    }
+}

+ 65 - 0
pm-admin/src/main/java/com/pm/web/controller/meeting/MeetingController.java

@@ -16,8 +16,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 会议管理
@@ -95,4 +99,65 @@ public class MeetingController  extends BaseController
             throw new RuntimeException(e);
         }
     }
+
+    /**
+     * 获取历史会议
+     * @return
+     */
+    @GetMapping("/listHistory")
+    public String listHistory() throws Exception {
+        PageData pd = this.getPageData();
+        // 获取接口地址
+        PageData interface_info = interfaceInfoService.selectInterfaceInfoByName(pd.getString("interfaceName"));
+        String url = interface_info.getString("url");
+
+        // 构造请求参数
+        Map<String, String> params = new HashMap<>();
+        params.put("date", pd.getString("date"));
+        params.put("search", pd.getString("search"));
+        //params.put("appoint_time", );
+        params.put("status", String.valueOf(pd.getInteger("status")));
+        params.put("page", String.valueOf(pd.getInteger("pageNum")));
+        params.put("pageSize", String.valueOf(pd.getInteger("pageSize")));
+        params.put("locationId", ""); // 空数组
+        params.put("capacity", "");  // 空数组
+        params.put("deviceType", ""); // 空数组
+        params.put("roomType", "-1");
+        params.put("is_external", String.valueOf(pd.getInteger("is_external")));
+
+        // 构造完整的 URL
+        StringBuilder urlBuilder = new StringBuilder(url);
+        boolean firstParam = true;
+        for (Map.Entry<String, String> entry : params.entrySet()) {
+            if (entry.getValue() != null && !entry.getValue().isEmpty()) {
+                if (firstParam) {
+                    urlBuilder.append("?");
+                    firstParam = false;
+                } else {
+                    urlBuilder.append("&");
+                }
+                urlBuilder.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8))
+                        .append("=")
+                        .append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
+            }
+        }
+
+        // 使用 OkHttpClient 发送 GET 请求
+        OkHttpClient client = new OkHttpClient().newBuilder()
+                .build();
+
+        Request request = new Request.Builder()
+                .url(urlBuilder.toString())
+                .method("GET", null)
+                .build();
+
+        try (Response response = client.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                throw new RuntimeException("External API call failed with status code: " + response.code());
+            }
+            return response.body().string();
+        } catch (IOException e) {
+            throw new RuntimeException("Error calling external API", e);
+        }
+    }
 }

+ 1 - 1
pm-admin/src/main/resources/application.yml

@@ -52,7 +52,7 @@ spring:
     # 国际化资源文件路径
     basename: i18n/messages
   profiles:
-    active: druid
+    active: dev
   # 文件上传
   servlet:
     multipart:

+ 8 - 0
pm_ui/src/api/meeting/meeting.js

@@ -8,4 +8,12 @@ export function listMeeting(query) {
         params: query
     })
 }
+// 查询设备告警列表
+export function listMeetingHistory(query) {
+    return request({
+        url: '/meeting/info/listHistory',
+        method: 'get',
+        params: query
+    })
+}
 

BIN
pm_ui/src/assets/logo/logo.png


+ 159 - 261
pm_ui/src/views/meeting/index.vue

@@ -1,6 +1,5 @@
 <template>
   <div class="app-container">
-    <!-- 搜索栏 -->
     <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
       <el-form-item label="状态" prop="status">
         <el-radio-group v-model="queryParams.status">
@@ -46,320 +45,219 @@
       </el-form-item>
     </el-form>
 
-    <!-- 状态筛选 -->
-    <div class="status-filter">
-      <el-tag type="success" size="small">可用</el-tag>
-      <el-tag type="warning" size="small">占用</el-tag>
-      <el-tag type="info" size="small">禁用</el-tag>
-    </div>
-
-    <!-- 会议室列表 -->
-    <div class="meeting-list">
-      <div v-for="meeting in waringList" :key="meeting.id" class="meeting-card">
-        <div class="card-header">
-          <img :src="getImageUrl(meeting.images)" alt="会议室图片" class="meeting-image"/>
-          <div class="meeting-info">
-            <h3>{{ meeting.name }}</h3>
-            <p>
-              {{ meeting.seats === 0 ? '无限制' : meeting.seats }}人 |
-              {{ meeting.room_type_name[0] }}
-            </p>
-            <div v-if="meeting.device_name_arr.length > 0">
-              <el-tag v-for="device in meeting.device_name_arr" :key="device" size="small">{{ device }}</el-tag>
-            </div>
-            <div v-else>暂无设备信息</div>
-            <p></p>
-            <div v-if="meeting.usage && meeting.usage.appointTime && meeting.usage.appointTime.length > 0">
-              已预订时间段:
-              <span v-for="(appointment, index) in [...meeting.usage.appointTime].reverse()">
-                  {{ appointment.start_time }} - {{ appointment.end_time }}<span v-if="index < meeting.usage.appointTime.length - 1">&nbsp;|&nbsp;</span>
-              </span>
-            </div>
-            <div v-else>
-              <span>暂无预订时间段</span>
-            </div>
-          </div>
-          <!--          <el-button type="primary" size="small" @click="handleReserve(meeting)">预定</el-button>-->
-        </div>
-        <div class="time-slots">
-          <div class="slot-container">
-            <div v-for="(slot, index) in timeSlots" :key="index"
-                 :class="['slot', {
-                  current: isCurrent(slot),
-                  booked: isBookedBetween(meeting, slot, timeSlots[index+1])
-                }]">
-              {{ slot }}
-            </div>
+    <el-row :gutter="10" class="mb8">
+
+    </el-row>
+
+    <el-table v-loading="loading" :data="waringList" @selection-change="handleSelectionChange">
+      <el-table-column label="会议名称" align="center" prop="name" />
+      <el-table-column label="会议类型" align="center" prop="room_type_name" />
+      <el-table-column label="容纳人数" align="center" prop="seats">
+        <template #default="scope">
+          <span>{{ scope.row.seats === 0 ? '无限制' : scope.row.seats }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="预定时间段" align="center">
+        <template #default="scope">
+          <div v-if="scope.row.usage.appointTime.length > 0">
+        <span v-for="(time, index) in scope.row.usage.appointTime" :key="index">
+          {{ time.start_time }} - {{ time.end_time }}
+          <br v-if="index < scope.row.usage.appointTime.length - 1" />
+        </span>
           </div>
-        </div>
-      </div>
-    </div>
+          <span v-else>无预定</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="设备" align="center" prop="device_names" />
+      <el-table-column label="创建时间" align="center" prop="created_at" width="180">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.created_at, '{y}-{m}-{d} {h}:{i}') }}</span>
+        </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"
+    />
   </div>
 </template>
 
 <script setup name="Waring">
-import {listMeeting} from "@/api/meeting/meeting";
-import {ElMessage} from "element-plus";
-import {ref, reactive, toRefs, onMounted, nextTick} from 'vue';
+import { listMeeting } from "@/api/meeting/meeting";
+import { ElMessage } from "element-plus";
+
+const { proxy } = getCurrentInstance();
+const {alarm_level} = proxy.useDict('alarm_level');
 
 const waringList = ref([]);
+const open = ref(false);
 const loading = ref(true);
 const showSearch = ref(true);
+const ids = ref([]);
+const single = ref(true);
+const multiple = ref(true);
 const total = ref(0);
-const url_str = "";
+const title = ref("");
 
-// 获取当前时间
-const currentTime = getCurrentHour(); // 当前整点时间
 const data = reactive({
   form: {},
   queryParams: {
     pageNum: 1,
-    pageSize: 10000,
+    pageSize: 10,
     status: '', // 默认选中"全部"
     date: new Date().toISOString().split('T')[0], // 格式化为YYYY-MM-DD
     appoint_time: "", // 重置为空
     start_time: "",
     end_time: "",
-    interfaceName:"会议",
+    interfaceName:"会议预订",
     search: '' // 搜索文本默认为空
   },
-  timeSlots: generateTimeSlots(), // 动态生成时间轴
-});
 
-const {queryParams, timeSlots} = toRefs(data);
+});
 
-// 动态生成时间轴,从 00:00 到 23:00
-function generateTimeSlots() {
-  const slots = [];
-  for (let i = 0; i <= 23; i++) {
-    slots.push(`${i.toString().padStart(2, '0')}:00`);
+const { queryParams, form, rules } = toRefs(data);
+watch(() => queryParams.value.status, (newStatus) => {
+  if(newStatus === "1") { // 当前可用
+    queryParams.value.appoint_time = getCurrentTimeRange();
+  } else if(newStatus === "2") { // 一小时后可用
+    queryParams.value.appoint_time = getCurrentTimeRange(1, true);
+  } else {
+    queryParams.value.appoint_time = ""; // 选择"全部"时清空
   }
-  return slots;
-}
-
-// 获取当前整点时间
-function getCurrentHour() {
-  const now = new Date();
-  const hour = now.getHours();
-  return `${hour.toString().padStart(2, '0')}:00`;
-}
-
-// 获取会议室列表
-function getList() {
-  loading.value = true;
-  listMeeting(queryParams.value).then(response => {
-    console.log(response);
-    waringList.value = response.data.list;
-    total.value = response.data.total;
-    loading.value = false;
-  });
-}
-
-// 更新预约时间
+});
 function updateAppointTime() {
-  if (queryParams.value.start_time && queryParams.value.end_time) {
+  if(queryParams.value.start_time && queryParams.value.end_time) {
     queryParams.value.appoint_time = `${queryParams.value.start_time}~${queryParams.value.end_time}`;
   }
 }
+/** 查询设备告警列表 */
+const getList = async () => {
+  try {
+    loading.value = true;
+    listMeeting(queryParams.value).then(response => {
+      waringList.value = response.data.list;
+      total.value = response.data.total;
+      loading.value = false;
+    });
+  } catch (error) {
+    console.error('获取数据失败:', error);
+  } finally {
+    loading.value = false;
+  }
+};
 
-// 判断某个时间段区间是否被预订
-function isBookedBetween(meeting, startTime, endTime) {
-  if (!endTime) endTime = '24:00'; // 处理最后一个时间段
 
-  const appointments = meeting.usage?.appointTime || [];
-  return appointments.some(appointment => {
-    return (
-        (appointment.start_time < endTime) &&
-        (appointment.end_time > startTime)
-    );
-  });
+// 表单重置
+function reset() {
+  form.value = {
+    status: '', // 默认选中"全部"
+    date: new Date(), // 默认当天
+    appoint_time: '', // 时间默认为空
+    search: '' // 搜索文本默认为空
+  };
+  proxy.resetForm("waringRef");
 }
+function getCurrentTimeRange(hoursLater = 0, fullHour = false) {
+  const now = new Date();
+  now.setHours(now.getHours() + hoursLater);
+
+  const startHours = now.getHours().toString().padStart(2, '0');
+  const startMinutes = now.getMinutes().toString().padStart(2, '0');
+
+  let endHours, endMinutes;
+  if(fullHour) {
+    // 计算一小时后的时间
+    const endTime = new Date(now.getTime() + 60 * 60 * 1000);
+    endHours = endTime.getHours().toString().padStart(2, '0');
+    endMinutes = endTime.getMinutes().toString().padStart(2, '0');
+  } else {
+    // 只增加一分钟
+    endHours = startHours;
+    endMinutes = (now.getMinutes() + 1).toString().padStart(2, '0');
+  }
 
-// 搜索按钮操作
+  return `${startHours}:${startMinutes}~${endHours}:${endMinutes}`;
+}
+
+/** 搜索按钮操作 */
 function handleQuery() {
   queryParams.value.pageNum = 1;
   getList();
 }
 
-// 重置按钮操作
+/** 重置按钮操作 */
 function resetQuery() {
-  queryParams.value = {
-    pageNum: 1,
-    pageSize: 10000,
-    status: '',
-    date: new Date().toISOString().split('T')[0],
-    appoint_time: "",
-    interfaceName:"会议",
-    start_time: "",
-    end_time: "",
-    search: ''
-  };
+  proxy.resetForm("queryRef");
   handleQuery();
 }
 
-// 判断某个时间段是否被预订
-function isBooked(meeting, timeSlot) {
-  const appointments = meeting.usage?.appointTime || [];
-  return appointments.some(appointment => {
-    return appointment.start_time <= timeSlot && appointment.end_time >= timeSlot;
-  });
+// 多选框选中数据
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
 }
 
-// 判断是否是当前时间
-function isCurrent(timeSlot) {
-  return timeSlot === currentTime;
+/** 新增按钮操作 */
+function handleAdd() {
+  reset();
+  open.value = true;
+  title.value = "添加设备告警";
 }
 
-// 预定按钮操作
-function handleReserve(meeting) {
-  proxy.$modal.confirm('是否确认预定会议室 "' + meeting.name + '"?').then(() => {
-    // 这里可以添加预定逻辑
-    ElMessage.success('预定成功');
-  }).catch(() => {
+/** 修改按钮操作 */
+function handleUpdate(row) {
+  reset();
+  const _id = row.id || ids.value
+  getWaring(_id).then(response => {
+    form.value = response.data;
+    open.value = true;
+    title.value = "修改设备告警";
   });
 }
 
-// 处理图片URL
-function getImageUrl(url) {
-  if (!url || url.length === 0) {
-    return 'http://183.129.224.253:9998/assets/defRoomIng.3e28e55b.png';
-  }
-  return `http://183.129.224.253:9998${url}`;
-}
-
-onMounted(() => {
-  getList();
-
-  const checkReady = () => {
-    if (waringList.value.length > 0) {
-      // 添加双重延迟确保DOM完全渲染
-      setTimeout(() => {
-        scrollToCurrentTime();
-        // 二次检查确保滚动执行
-        //setTimeout(scrollToCurrentTime, 300);
-      }, 100);
-    } else {
-      setTimeout(checkReady, 200);
-    }
-  };
-
-  checkReady();
-});
-
-// 滚动到当前时间
-function scrollToCurrentTime() {
-  const currentSlots = document.querySelectorAll('.slot.current');
-  currentSlots.forEach(slot => {
-    const container = slot.closest('.time-slots');
-    if (container) {
-      const slotRect = slot.getBoundingClientRect();
-      const containerRect = container.getBoundingClientRect();
-      container.scrollLeft = slotRect.left - containerRect.left;// - (container.clientWidth) + (slotRect.width);
+/** 提交按钮 */
+function submitForm() {
+  proxy.$refs["waringRef"].validate(valid => {
+    if (valid) {
+      if (form.value.id != null) {
+        updateWaring(form.value).then(response => {
+          proxy.$modal.msgSuccess("修改成功");
+          open.value = false;
+          getList();
+        });
+      } else {
+        addWaring(form.value).then(response => {
+          proxy.$modal.msgSuccess("新增成功");
+          open.value = false;
+          getList();
+        });
+      }
     }
   });
 }
-</script>
-
-<style scoped>
-.time-slots {
-  overflow-x: auto;
-  width: 100%;
-  max-width: none;
-  margin-top: 10px;
-}
-
-.slot-container {
-  display: inline-flex;
-  /* min-width: calc(64px * 24 + 4px * 23);  24个时间槽+间隙 */
-  padding: 5px 0;
-  gap: 4px;
-}
-
-.app-container {
-  padding: 20px;
-}
-
-.status-filter {
-  margin-bottom: 20px;
-}
-
-.meeting-list {
-  display: grid;
-  grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));
-  gap: 20px;
-}
-
-@media (max-width: 768px) {
-  .meeting-list {
-    grid-template-columns: 1fr;
-  }
-}
-
-.meeting-card {
-  background-color: #fff;
-  border-radius: 8px;
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-  padding: 16px;
-}
 
-.card-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16px;
+/** 删除按钮操作 */
+function handleDelete(row) {
+  const _ids = row.id || ids.value;
+  proxy.$modal.confirm('是否确认删除设备告警编号为"' + _ids + '"的数据项?').then(function() {
+    return delWaring(_ids);
+  }).then(() => {
+    getList();
+    proxy.$modal.msgSuccess("删除成功");
+  }).catch(() => {});
 }
 
-.meeting-image {
-  width: 100px;
-  height: 100px;
-  object-fit: cover;
-  border-radius: 8px;
+/** 导出按钮操作 */
+function handleExport() {
+  proxy.download('waring/waring/export', {
+    ...queryParams.value
+  }, `waring_${new Date().getTime()}.xlsx`)
 }
 
-.meeting-info {
-  flex: 1;
-  margin-left: 16px;
-}
-
-.time-slots {
-  overflow-x: auto; /* 添加水平滚动条 */
-  white-space: nowrap;
-  margin-top: 10px;
-
-  @media (max-width: 768px) {
-    max-width: 300px; /* 小屏幕宽度 */
-  }
-
-  @media (min-width: 769px) and (max-width: 1024px) {
-    max-width: 400px; /* 中等屏幕宽度 */
-  }
-
-  @media (min-width: 1025px) {
-    max-width: 900px; /* 大屏幕宽度 */
-  }
-}
-
-.slot-container {
-  display: inline-flex;
-  gap: 4px;
-}
-
-.slot {
-  width: 60px;
-  height: 20px;
-  background-color: #f0f0f0;
-  text-align: center;
-  line-height: 20px;
-  border-radius: 4px;
-  cursor: pointer;
-}
-
-.slot.current {
-  background-color: #409EFF; /* 当前时间高亮 */
-  color: #fff;
-}
-
-.slot.booked {
-  background-color: #ff9900; /* 占用状态 */
-}
-</style>
+getList();
+</script>

+ 365 - 0
pm_ui/src/views/meeting/index2.vue

@@ -0,0 +1,365 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索栏 -->
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="状态" prop="status">
+        <el-radio-group v-model="queryParams.status">
+          <el-radio-button label="">全部</el-radio-button>
+          <el-radio-button label="1">当前可用</el-radio-button>
+          <el-radio-button label="2">一小时后可用</el-radio-button>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="日期" prop="date">
+        <el-date-picker
+            v-model="queryParams.date"
+            type="date"
+            value-format="YYYY-MM-DD"
+            placeholder="选择日期"
+        />
+      </el-form-item>
+      <el-form-item label="时间范围" prop="timeRange">
+        <el-time-picker style="width: 100px"
+                        v-model="queryParams.start_time"
+                        format="HH:mm"
+                        placeholder="开始时间"
+                        @change="updateAppointTime"
+        />
+        <span style="margin: 0 2px">至</span>
+        <el-time-picker style="width: 100px"
+                        v-model="queryParams.end_time"
+                        format="HH:mm"
+                        placeholder="结束时间"
+                        @change="updateAppointTime"
+        />
+      </el-form-item>
+      <el-form-item label="会议名称" prop="search">
+        <el-input
+            v-model="queryParams.search"
+            placeholder="请输入会议名称"
+            clearable
+            @keyup.enter="handleQuery"
+        />
+      </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>
+
+    <!-- 状态筛选 -->
+    <div class="status-filter">
+      <el-tag type="success" size="small">可用</el-tag>
+      <el-tag type="warning" size="small">占用</el-tag>
+      <el-tag type="info" size="small">禁用</el-tag>
+    </div>
+
+    <!-- 会议室列表 -->
+    <div class="meeting-list">
+      <div v-for="meeting in waringList" :key="meeting.id" class="meeting-card">
+        <div class="card-header">
+          <img :src="getImageUrl(meeting.images)" alt="会议室图片" class="meeting-image"/>
+          <div class="meeting-info">
+            <h3>{{ meeting.name }}</h3>
+            <p>
+              {{ meeting.seats === 0 ? '无限制' : meeting.seats }}人 |
+              {{ meeting.room_type_name[0] }}
+            </p>
+            <div v-if="meeting.device_name_arr.length > 0">
+              <el-tag v-for="device in meeting.device_name_arr" :key="device" size="small">{{ device }}</el-tag>
+            </div>
+            <div v-else>暂无设备信息</div>
+            <p></p>
+            <div v-if="meeting.usage && meeting.usage.appointTime && meeting.usage.appointTime.length > 0">
+              已预订时间段:
+              <span v-for="(appointment, index) in [...meeting.usage.appointTime].reverse()">
+                  {{ appointment.start_time }} - {{ appointment.end_time }}<span v-if="index < meeting.usage.appointTime.length - 1">&nbsp;|&nbsp;</span>
+              </span>
+            </div>
+            <div v-else>
+              <span>暂无预订时间段</span>
+            </div>
+          </div>
+          <!--          <el-button type="primary" size="small" @click="handleReserve(meeting)">预定</el-button>-->
+        </div>
+        <div class="time-slots">
+          <div class="slot-container">
+            <div v-for="(slot, index) in timeSlots" :key="index"
+                 :class="['slot', {
+                  current: isCurrent(slot),
+                  booked: isBookedBetween(meeting, slot, timeSlots[index+1])
+                }]">
+              {{ slot }}
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="Waring">
+import {listMeeting} from "@/api/meeting/meeting";
+import {ElMessage} from "element-plus";
+import {ref, reactive, toRefs, onMounted, nextTick} from 'vue';
+
+const waringList = ref([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const total = ref(0);
+const url_str = "";
+
+// 获取当前时间
+const currentTime = getCurrentHour(); // 当前整点时间
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10000,
+    status: '', // 默认选中"全部"
+    date: new Date().toISOString().split('T')[0], // 格式化为YYYY-MM-DD
+    appoint_time: "", // 重置为空
+    start_time: "",
+    end_time: "",
+    interfaceName:"会议",
+    search: '' // 搜索文本默认为空
+  },
+  timeSlots: generateTimeSlots(), // 动态生成时间轴
+});
+
+const {queryParams, timeSlots} = toRefs(data);
+
+// 动态生成时间轴,从 00:00 到 23:00
+function generateTimeSlots() {
+  const slots = [];
+  for (let i = 0; i <= 23; i++) {
+    slots.push(`${i.toString().padStart(2, '0')}:00`);
+  }
+  return slots;
+}
+
+// 获取当前整点时间
+function getCurrentHour() {
+  const now = new Date();
+  const hour = now.getHours();
+  return `${hour.toString().padStart(2, '0')}:00`;
+}
+
+// 获取会议室列表
+function getList() {
+  loading.value = true;
+  listMeeting(queryParams.value).then(response => {
+    console.log(response);
+    waringList.value = response.data.list;
+    total.value = response.data.total;
+    loading.value = false;
+  });
+}
+
+// 更新预约时间
+function updateAppointTime() {
+  if (queryParams.value.start_time && queryParams.value.end_time) {
+    queryParams.value.appoint_time = `${queryParams.value.start_time}~${queryParams.value.end_time}`;
+  }
+}
+
+// 判断某个时间段区间是否被预订
+function isBookedBetween(meeting, startTime, endTime) {
+  if (!endTime) endTime = '24:00'; // 处理最后一个时间段
+
+  const appointments = meeting.usage?.appointTime || [];
+  return appointments.some(appointment => {
+    return (
+        (appointment.start_time < endTime) &&
+        (appointment.end_time > startTime)
+    );
+  });
+}
+
+// 搜索按钮操作
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+// 重置按钮操作
+function resetQuery() {
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10000,
+    status: '',
+    date: new Date().toISOString().split('T')[0],
+    appoint_time: "",
+    interfaceName:"会议",
+    start_time: "",
+    end_time: "",
+    search: ''
+  };
+  handleQuery();
+}
+
+// 判断某个时间段是否被预订
+function isBooked(meeting, timeSlot) {
+  const appointments = meeting.usage?.appointTime || [];
+  return appointments.some(appointment => {
+    return appointment.start_time <= timeSlot && appointment.end_time >= timeSlot;
+  });
+}
+
+// 判断是否是当前时间
+function isCurrent(timeSlot) {
+  return timeSlot === currentTime;
+}
+
+// 预定按钮操作
+function handleReserve(meeting) {
+  proxy.$modal.confirm('是否确认预定会议室 "' + meeting.name + '"?').then(() => {
+    // 这里可以添加预定逻辑
+    ElMessage.success('预定成功');
+  }).catch(() => {
+  });
+}
+
+// 处理图片URL
+function getImageUrl(url) {
+  if (!url || url.length === 0) {
+    return 'http://183.129.224.253:9998/assets/defRoomIng.3e28e55b.png';
+  }
+  return `http://183.129.224.253:9998${url}`;
+}
+
+onMounted(() => {
+  getList();
+
+  const checkReady = () => {
+    if (waringList.value.length > 0) {
+      // 添加双重延迟确保DOM完全渲染
+      setTimeout(() => {
+        scrollToCurrentTime();
+        // 二次检查确保滚动执行
+        //setTimeout(scrollToCurrentTime, 300);
+      }, 100);
+    } else {
+      setTimeout(checkReady, 200);
+    }
+  };
+
+  checkReady();
+});
+
+// 滚动到当前时间
+function scrollToCurrentTime() {
+  const currentSlots = document.querySelectorAll('.slot.current');
+  currentSlots.forEach(slot => {
+    const container = slot.closest('.time-slots');
+    if (container) {
+      const slotRect = slot.getBoundingClientRect();
+      const containerRect = container.getBoundingClientRect();
+      container.scrollLeft = slotRect.left - containerRect.left;// - (container.clientWidth) + (slotRect.width);
+    }
+  });
+}
+</script>
+
+<style scoped>
+.time-slots {
+  overflow-x: auto;
+  width: 100%;
+  max-width: none;
+  margin-top: 10px;
+}
+
+.slot-container {
+  display: inline-flex;
+  /* min-width: calc(64px * 24 + 4px * 23);  24个时间槽+间隙 */
+  padding: 5px 0;
+  gap: 4px;
+}
+
+.app-container {
+  padding: 20px;
+}
+
+.status-filter {
+  margin-bottom: 20px;
+}
+
+.meeting-list {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));
+  gap: 20px;
+}
+
+@media (max-width: 768px) {
+  .meeting-list {
+    grid-template-columns: 1fr;
+  }
+}
+
+.meeting-card {
+  background-color: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  padding: 16px;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.meeting-image {
+  width: 100px;
+  height: 100px;
+  object-fit: cover;
+  border-radius: 8px;
+}
+
+.meeting-info {
+  flex: 1;
+  margin-left: 16px;
+}
+
+.time-slots {
+  overflow-x: auto; /* 添加水平滚动条 */
+  white-space: nowrap;
+  margin-top: 10px;
+
+  @media (max-width: 768px) {
+    max-width: 300px; /* 小屏幕宽度 */
+  }
+
+  @media (min-width: 769px) and (max-width: 1024px) {
+    max-width: 400px; /* 中等屏幕宽度 */
+  }
+
+  @media (min-width: 1025px) {
+    max-width: 900px; /* 大屏幕宽度 */
+  }
+}
+
+.slot-container {
+  display: inline-flex;
+  gap: 4px;
+}
+
+.slot {
+  width: 60px;
+  height: 20px;
+  background-color: #f0f0f0;
+  text-align: center;
+  line-height: 20px;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+.slot.current {
+  background-color: #409EFF; /* 当前时间高亮 */
+  color: #fff;
+}
+
+.slot.booked {
+  background-color: #ff9900; /* 占用状态 */
+}
+</style>

+ 199 - 0
pm_ui/src/views/meeting/index3.vue

@@ -0,0 +1,199 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="会议主题" prop="search">
+        <el-input
+            v-model="queryParams.search"
+            placeholder="会议主题/会议室/发起人"
+            clearable
+            @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="会议日期" prop="timeRange">
+        <el-date-picker
+            v-model="queryParams.timeRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始时间"
+            end-placeholder="结束时间"
+            value-format="YYYY-MM-DD"
+        />
+      </el-form-item>
+      <el-form-item label="会议状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" style="width: 180px;">
+          <el-option label="全部" value="100" />
+          <el-option label="审批中" value="0" />
+          <el-option label="待进行" value="3" />
+          <el-option label="进行中" value="4" />
+          <el-option label="已完成" value="5" />
+          <el-option label="已取消" value="2" />
+          <el-option label="审核拒绝" value="6" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="会议类型" prop="is_external">
+        <el-select v-model="queryParams.is_external" placeholder="请选择状态" style="width: 180px;">
+          <el-option label="全部" value="2" />
+          <el-option label="内部会议" value="0" />
+          <el-option label="外部会议" value="1" />
+        </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-table v-loading="loading" :data="meetingList" @selection-change="handleSelectionChange">
+      <el-table-column label="序号" type="index" align="center" width="50" />
+      <el-table-column label="会议主题" align="center" prop="subject" />
+      <el-table-column label="会议地点" align="center" prop="detail.room.name" />
+      <el-table-column label="会议时间" align="center">
+        <template #default="scope">
+          {{ formatTimeRange(scope.row.start_time, scope.row.end_time) }}
+        </template>
+      </el-table-column>
+      <el-table-column label="发起人" align="center" prop="creator_name" />
+      <el-table-column label="会议状态" align="center" width="100">
+        <template #default="scope">
+          <el-tag :type="getStatusType(scope.row.status)">
+            {{ getStatusText(scope.row.status) }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="会议类型" align="center" prop="template_name" />
+<!--      <el-table-column label="操作" align="center">
+        <template #default="scope">
+          <el-button type="text" size="small" @click="handleView(scope.row)">查看</el-button>
+          &lt;!&ndash; Add more buttons as needed &ndash;&gt;
+        </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"
+    />
+  </div>
+</template>
+
+<script setup name="Waring">
+import { listMeetingHistory } from "@/api/meeting/meeting";
+import { ref, reactive } from "vue";
+import dayjs from 'dayjs'; // 引入 dayjs 库用于日期格式化
+
+const queryRef = ref(null);
+
+const meetingList = ref([]);
+const loading = ref(true);
+const showSearch = ref(true);
+const total = ref(0);
+
+// 获取当前月份的第一天和最后一天(仅日期)
+const currentMonthStart = dayjs().startOf('month').format('YYYY-MM-DD');
+const currentMonthEnd = dayjs().endOf('month').format('YYYY-MM-DD');
+
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  status: '100',
+  timeRange: [currentMonthStart, currentMonthEnd], // 默认时间范围为当前月份
+  search: '',
+  interfaceName: "会议记录",
+  type: 0,
+  record: true,
+  is_external: '2'
+});
+
+/** 查询设备告警列表 */
+const getList = async () => {
+  try {
+    loading.value = true;
+    const params = { ...queryParams };
+    if (queryParams.timeRange && queryParams.timeRange.length === 2) {
+      params.date = `${queryParams.timeRange[0]}~${queryParams.timeRange[1]}`;
+      delete params.timeRange; // 删除原始的 timeRange 参数
+    }
+    listMeetingHistory(params).then(response => {
+      meetingList.value = response.data.list;
+      total.value = response.data.total;
+      loading.value = false;
+    });
+  } catch (error) {
+    console.error('获取数据失败:', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+/** 搜索按钮操作 */
+function handleQuery() {
+  queryParams.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+function resetQuery() {
+  queryRef.value.resetFields();
+  handleQuery();
+}
+
+/** 获取会议状态类型 */
+function getStatusType(status) {
+  switch (status) {
+    case 0:
+      return 'warning'; // 审批中
+    case 3:
+      return 'info'; // 待进行
+    case 4:
+      return 'success'; // 进行中
+    case 5:
+      return 'success'; // 已完成
+    case 2:
+      return 'danger'; // 已取消
+    case 6:
+      return 'danger'; // 审核拒绝
+    default:
+      return '';
+  }
+}
+
+/** 获取会议状态文本 */
+function getStatusText(status) {
+  switch (status) {
+    case 0:
+      return '审批中';
+    case 3:
+      return '待进行';
+    case 4:
+      return '进行中';
+    case 5:
+      return '已完成';
+    case 2:
+      return '已取消';
+    case 6:
+      return '审核拒绝';
+    default:
+      return '未知';
+  }
+}
+
+/** 格式化会议时间范围 */
+function formatTimeRange(startTime, endTime) {
+  if (!startTime || !endTime) return '';
+  const startFormatted = dayjs(startTime).format('YYYY-MM-DD HH:mm');
+  const endFormatted = dayjs(endTime).format('HH:mm');
+  return `${startFormatted}~${endFormatted}`;
+}
+
+getList();
+</script>
+
+<style scoped>
+.el-table .cell {
+  white-space: nowrap;
+}
+</style>