소스 검색

调整看板样式

Zimo 4 일 전
부모
커밋
d7ef7d3314
2개의 변경된 파일357개의 추가작업 그리고 38개의 파일을 삭제
  1. 1 1
      src/views/oli-connection/monitoring-board/chart.vue
  2. 356 37
      src/views/oli-connection/monitoring-board/index.vue

+ 1 - 1
src/views/oli-connection/monitoring-board/chart.vue

@@ -711,7 +711,7 @@ function handleDetailClick() {
 }
 </script>
 <template>
-  <div class="h-100 rounded-lg chart-container flex flex-col">
+  <div class="rounded-lg chart-container flex flex-col">
     <header class="chart-header justify-between">
       <div class="flex items-center">
         <div class="title-icon"></div>

+ 356 - 37
src/views/oli-connection/monitoring-board/index.vue

@@ -94,16 +94,16 @@ async function loadDeviceOptions() {
 
   deviceLoading.value = true
   try {
-    const data = await IotDeviceApi.getBoardDevice({
+    // const data = await IotDeviceApi.getBoardDevice({
+    //   deptId: [deviceQuery.value.deptId].flat().at(-1),
+    //   ifInline: deviceQuery.value.ifInline,
+    //   pageSize: 100
+    // })
+    const data = await IotDeviceApi.getIotDeviceTdPage({
       deptId: [deviceQuery.value.deptId].flat().at(-1),
       ifInline: deviceQuery.value.ifInline,
       pageSize: 100
     })
-    // const data = await IotDeviceApi.getIotDeviceTdPage({
-    //   deptId: deviceQuery.value.deptId,
-    //   ifInline: deviceQuery.value.ifInline,
-    //   pageSize: 100
-    // })
     deviceOptions.value = data.list.map((item: any) => ({
       label: item.deviceCode + '-' + item.deviceName,
       value: item.id,
@@ -122,6 +122,46 @@ async function loadDeviceOptions() {
 
 const deviceList = ref<DeviceData[]>([])
 const chartOption = ref<EChartsOption>({})
+const pageSize = 5
+const pageIndex = ref(0)
+const activeMainCardId = ref<number | null>(null)
+
+const totalPages = computed(() => Math.max(1, Math.ceil(deviceList.value.length / pageSize)))
+
+const currentPageCards = computed(() => {
+  const start = pageIndex.value * pageSize
+  return deviceList.value.slice(start, start + pageSize)
+})
+
+const mainCard = computed(() => {
+  const matchedCard = currentPageCards.value.find((item) => item.id === activeMainCardId.value)
+  return matchedCard ?? currentPageCards.value[0] ?? null
+})
+
+const sideCards = computed(() => {
+  if (!mainCard.value) return [] as DeviceData[]
+  return currentPageCards.value.filter((item) => item.id !== mainCard.value?.id)
+})
+
+function syncPageMainCard() {
+  activeMainCardId.value = currentPageCards.value[0]?.id ?? null
+}
+
+function setMainCard(card: DeviceData) {
+  activeMainCardId.value = card.id
+}
+
+function goPrevGroup() {
+  if (pageIndex.value <= 0) return
+  pageIndex.value -= 1
+  syncPageMainCard()
+}
+
+function goNextGroup() {
+  if (pageIndex.value >= totalPages.value - 1) return
+  pageIndex.value += 1
+  syncPageMainCard()
+}
 
 async function handleDeviceChange(selectedIds: number[]) {
   deviceList.value = deviceList.value.filter((d) => selectedIds.includes(d.id))
@@ -145,6 +185,15 @@ async function handleDeviceChange(selectedIds: number[]) {
       })
     }
   }
+
+  if (!deviceList.value.some((item) => item.id === activeMainCardId.value)) {
+    syncPageMainCard()
+  }
+
+  if (pageIndex.value > totalPages.value - 1) {
+    pageIndex.value = Math.max(totalPages.value - 1, 0)
+    syncPageMainCard()
+  }
 }
 
 onMounted(() => {
@@ -156,6 +205,8 @@ function handleDeptChange() {
   query.value = { ...originalQuery }
   deviceList.value = []
   chartOption.value = {}
+  pageIndex.value = 0
+  activeMainCardId.value = null
   loadDeviceOptions()
 }
 
@@ -166,6 +217,8 @@ const { toggle, isFullscreen } = useFullscreen(targetArea)
 function handleRest() {
   deviceQuery.value = { ...originalDeviceQuery }
   query.value = { ...originalQuery }
+  pageIndex.value = 0
+  activeMainCardId.value = null
   loadDeptOptions()
   loadDeviceOptions()
 }
@@ -175,6 +228,7 @@ function handleInlineChange() {
 }
 
 const token = ref('')
+const showSearchDialog = ref(false)
 
 async function getToken() {
   const res = await IotDeviceApi.getToken()
@@ -190,7 +244,7 @@ onMounted(() => {
 <template>
   <div
     ref="targetArea"
-    class="relative w-full rounded-lg bg-[#020408] overflow-hidden min-h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
+    class="relative flex flex-col w-full rounded-lg bg-[#020408] overflow-hidden h-[calc(100vh-20px-var(--top-tool-height)-var(--tags-view-height)-var(--app-footer-height))]"
   >
     <header
       class="relative w-full h-14 flex items-center justify-center select-none bg-[#0b1121] border-b border-white/5 shadow-lg"
@@ -204,13 +258,13 @@ onMounted(() => {
       <div class="absolute bottom-0 left-0 w-full h-[2px] bg-slate-800/50 overflow-hidden z-20">
         <div
           class="absolute top-0 bottom-0 w-[40%] bg-gradient-to-r from-transparent via-[#22d3ee] to-transparent shadow-[0_0_20px_#22d3ee] animate-scan-line"
-        ></div>
+        >
+        </div>
       </div>
 
       <div class="absolute left-6 top-1/2 -translate-y-1/2 flex items-center gap-1 opacity-80">
-        <div
-          class="w-1 h-4 bg-cyan-400 skew-x-[-12deg] shadow-[0_0_5px_rgba(34,211,238,0.8)]"
-        ></div>
+        <div class="w-1 h-4 bg-cyan-400 skew-x-[-12deg] shadow-[0_0_5px_rgba(34,211,238,0.8)]">
+        </div>
         <div class="w-1 h-3 bg-cyan-700 skew-x-[-12deg]"></div>
         <div class="w-1 h-2 bg-cyan-900 skew-x-[-12deg]"></div>
       </div>
@@ -230,13 +284,97 @@ onMounted(() => {
           监控看板
         </span>
       </h1>
+
+      <div class="absolute right-16 top-1/2 -translate-y-1/2 z-10 flex items-center gap-2">
+        <el-button size="default" class="custom-btn primary-btn" @click="showSearchDialog = true">
+          筛选
+        </el-button>
+        <el-button
+          size="default"
+          class="custom-btn primary-btn"
+          :type="isFullscreen ? 'info' : 'primary'"
+          :icon="isFullscreen ? Crop : FullScreen"
+          @click="toggle"
+        >
+          {{ isFullscreen ? '退出全屏' : '全屏' }}
+        </el-button>
+      </div>
     </header>
-    <div class="p-4">
-      <el-form size="default" class="search-container grid grid-cols-6 gap-6">
+
+    <div class="px-4 pt-4 pb-4 monitor-board-shell">
+      <div class="monitor-board-toolbar">
+        <div class="monitor-board-status">
+          <span>当前第 {{ pageIndex + 1 }} 组 / 共 {{ totalPages }} 组</span>
+          <span v-if="mainCard">主看板:{{ mainCard.deviceCode }}-{{ mainCard.deviceName }}</span>
+        </div>
+        <div class="flex items-center gap-3">
+          <el-button class="custom-btn reset-btn" :disabled="pageIndex === 0" @click="goPrevGroup">
+            上 5 个
+          </el-button>
+          <el-button
+            class="custom-btn reset-btn"
+            :disabled="pageIndex >= totalPages - 1"
+            @click="goNextGroup"
+          >
+            下 5 个
+          </el-button>
+        </div>
+      </div>
+
+      <div v-if="mainCard" class="monitor-board-scroll-area">
+        <div class="monitor-board-layout">
+          <div
+            v-if="sideCards[0]"
+            class="monitor-card-shell monitor-card-side monitor-card-left-top"
+            @click="setMainCard(sideCards[0])"
+          >
+            <chart v-bind="sideCards[0]" :date="query.time" :token="token" />
+          </div>
+
+          <div
+            v-if="sideCards[1]"
+            class="monitor-card-shell monitor-card-side monitor-card-left-bottom"
+            @click="setMainCard(sideCards[1])"
+          >
+            <chart v-bind="sideCards[1]" :date="query.time" :token="token" />
+          </div>
+
+          <div class="monitor-card-shell monitor-card-main" @click="setMainCard(mainCard)">
+            <chart v-bind="mainCard" :date="query.time" :token="token" />
+          </div>
+
+          <div
+            v-if="sideCards[2]"
+            class="monitor-card-shell monitor-card-side monitor-card-right-top"
+            @click="setMainCard(sideCards[2])"
+          >
+            <chart v-bind="sideCards[2]" :date="query.time" :token="token" />
+          </div>
+
+          <div
+            v-if="sideCards[3]"
+            class="monitor-card-shell monitor-card-side monitor-card-right-bottom"
+            @click="setMainCard(sideCards[3])"
+          >
+            <chart v-bind="sideCards[3]" :date="query.time" :token="token" />
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <el-dialog
+      v-model="showSearchDialog"
+      title="筛选条件"
+      width="1120px"
+      class="monitor-search-dialog"
+      append-to-body
+      destroy-on-close
+    >
+      <el-form size="default" class="search-container grid grid-cols-12 gap-6">
         <div
           class="absolute left-0 top-0 w-[2px] h-full bg-gradient-to-b from-transparent via-cyan-500 to-transparent"
         ></div>
-        <el-form-item class="col-span-1" label="部门">
+        <el-form-item class="col-span-3" label="部门">
           <el-cascader
             v-model="deviceQuery.deptId"
             :options="deptOptions"
@@ -249,7 +387,7 @@ onMounted(() => {
             placeholder="请选择部门"
           />
         </el-form-item>
-        <el-form-item class="col-span-1" label="在线状态">
+        <el-form-item class="col-span-3" label="在线状态">
           <el-select
             v-model="deviceQuery.ifInline"
             placeholder="请选择状态"
@@ -268,7 +406,7 @@ onMounted(() => {
             />
           </el-select>
         </el-form-item>
-        <el-form-item class="col-span-3" label="时间范围">
+        <el-form-item class="col-span-6 time-range-item" label="时间范围">
           <el-date-picker
             v-model="query.time"
             value-format="YYYY-MM-DD HH:mm:ss"
@@ -279,10 +417,10 @@ onMounted(() => {
             :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
             :teleported="false"
             popper-class="poper"
-            class="w-full"
+            class="w-full time-range-picker"
           />
         </el-form-item>
-        <el-form-item class="col-span-5 col-start-1" label="设备">
+        <el-form-item class="col-span-12" label="设备">
           <el-select
             v-model="query.deviceCodes"
             :options="deviceOptions"
@@ -296,27 +434,23 @@ onMounted(() => {
             @change="handleDeviceChange"
           />
         </el-form-item>
-        <el-form-item class="col-span-1 flex justify-end">
-          <div class="flex gap-3 w-full justify-end">
-            <el-button
-              class="custom-btn primary-btn"
-              :type="isFullscreen ? 'info' : 'primary'"
-              :icon="isFullscreen ? Crop : FullScreen"
-              @click="toggle"
-            >
-              {{ isFullscreen ? '退出全屏' : '全屏' }}
-            </el-button>
-            <el-button @click="handleRest" class="custom-btn reset-btn">重置</el-button>
-          </div>
-        </el-form-item>
       </el-form>
-    </div>
 
-    <div class="p-4 grid grid-cols-2 3xl:grid-cols-3 gap-6">
-      <template v-for="item in deviceList" :key="item.id">
-        <chart v-bind="item" :date="query.time" :token="token" />
+      <template #footer>
+        <div class="flex justify-end gap-3">
+          <el-button size="default" @click="handleRest" class="custom-btn reset-btn">
+            重置
+          </el-button>
+          <el-button
+            size="default"
+            class="custom-btn primary-btn"
+            @click="showSearchDialog = false"
+          >
+            关闭
+          </el-button>
+        </div>
       </template>
-    </div>
+    </el-dialog>
   </div>
 </template>
 
@@ -362,10 +496,125 @@ onMounted(() => {
   animation: scan-line 3s cubic-bezier(0.4, 0, 0.2, 1) infinite alternate;
 }
 
+.monitor-board-shell {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 100%;
+  flex: 1;
+  min-height: 0;
+}
+
+.monitor-board-toolbar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 16px;
+  gap: 16px;
+}
+
+.monitor-board-status {
+  display: flex;
+  font-size: 14px;
+  color: rgb(165 243 252 / 90%);
+  flex-wrap: wrap;
+  gap: 16px;
+}
+
+.monitor-board-layout {
+  display: grid;
+  width: max-content;
+  height: 100%;
+  padding-bottom: 8px;
+  flex: 1;
+  grid-template-columns: 760px 1500px 760px;
+  grid-template-rows: repeat(2, minmax(0, 1fr));
+  gap: 18px;
+}
+
+.monitor-board-scroll-area {
+  height: 100%;
+  min-height: 0;
+  padding: 8px;
+  overflow: auto hidden;
+  flex: 1 1 0;
+}
+
+.monitor-board-scroll-area::-webkit-scrollbar {
+  height: 10px;
+}
+
+.monitor-board-scroll-area::-webkit-scrollbar-thumb {
+  background: rgb(34 211 238 / 70%);
+  border-radius: 999px;
+}
+
+.monitor-board-scroll-area::-webkit-scrollbar-track {
+  background: rgb(255 255 255 / 6%);
+}
+
+.monitor-card-shell {
+  height: 100%;
+  min-width: 0;
+  min-height: 0;
+  cursor: pointer;
+  transition:
+    transform 0.25s ease,
+    box-shadow 0.25s ease,
+    border-color 0.25s ease;
+}
+
+.monitor-card-shell:hover {
+  transform: translateY(-2px);
+}
+
+.monitor-card-main {
+  grid-column: 2;
+  grid-row: 1 / span 2;
+  flex: 1;
+  height: 100%;
+}
+
+.monitor-card-left-top {
+  grid-column: 1;
+  grid-row: 1;
+}
+
+.monitor-card-left-bottom {
+  grid-column: 1;
+  grid-row: 2;
+}
+
+.monitor-card-right-top {
+  grid-column: 3;
+  grid-row: 1;
+}
+
+.monitor-card-right-bottom {
+  grid-column: 3;
+  grid-row: 2;
+}
+
+.monitor-card-shell :deep(.h-100) {
+  height: 100%;
+}
+
+.monitor-card-shell :deep(.chart-container) {
+  height: 100%;
+}
+
+.monitor-board-layout :deep(.chart-container) {
+  min-width: 0;
+}
+
+.monitor-card-shell :deep(.chart-header) {
+  cursor: pointer;
+}
+
 .search-container {
   position: relative;
   padding: 24px;
-  background: linear-gradient(135deg, rgb(11 17 33 / 90%) 0%, rgb(6 9 18 / 95%) 100%);
+  background: linear-gradient(135deg, rgb(11 17 33 / 96%) 0%, rgb(6 9 18 / 98%) 100%);
   border: 1px solid rgb(34 211 238 / 60%);
   border-radius: 4px;
   box-shadow: 0 10px 40px rgb(0 0 0 / 60%);
@@ -510,3 +759,73 @@ onMounted(() => {
   border-color: rgb(255 255 255 / 50%);
 }
 </style>
+
+<style>
+.monitor-search-dialog.el-dialog {
+  background: linear-gradient(180deg, rgb(6 9 18 / 98%) 0%, rgb(11 17 33 / 98%) 100%);
+  border: 1px solid rgb(34 211 238 / 35%);
+  box-shadow:
+    0 0 0 1px rgb(34 211 238 / 12%),
+    0 24px 80px rgb(0 0 0 / 75%);
+}
+
+.monitor-search-dialog .el-dialog__header {
+  padding: 18px 24px 14px;
+  margin-right: 0;
+  border-bottom: 1px solid rgb(34 211 238 / 16%);
+}
+
+.monitor-search-dialog .el-dialog__title {
+  font-weight: 600;
+  letter-spacing: 0.08em;
+  color: rgb(165 243 252);
+}
+
+.monitor-search-dialog .el-dialog__headerbtn .el-dialog__close {
+  color: rgb(148 163 184);
+}
+
+.monitor-search-dialog .el-dialog__body {
+  padding: 20px 24px 16px;
+  background: transparent;
+}
+
+.monitor-search-dialog .el-dialog__footer {
+  padding: 0 24px 12px;
+  background: transparent;
+}
+
+.monitor-search-dialog .el-form-item__label {
+  white-space: nowrap;
+}
+
+.time-range-item {
+  min-width: 0;
+}
+
+.time-range-picker {
+  min-width: 0;
+}
+
+.monitor-search-dialog .el-date-editor.el-input__wrapper,
+.monitor-search-dialog .el-select__wrapper,
+.monitor-search-dialog .el-cascader .el-input__wrapper {
+  width: 100%;
+  min-width: 0;
+}
+
+.monitor-search-dialog .el-date-editor--datetimerange {
+  width: 100%;
+  min-width: 0;
+}
+
+.monitor-search-dialog .el-date-editor .el-range-separator {
+  flex: 0 0 auto;
+  padding: 0 8px;
+  color: rgb(165 243 252);
+}
+
+.monitor-search-dialog .el-date-editor .el-range-input {
+  min-width: 0;
+}
+</style>