Kaynağa Gözat

feat: incident card for detail page

Jiang, Wim 3 yıl önce
ebeveyn
işleme
f39d2de272

+ 5 - 0
src/components/Card/index.scss

@@ -1,5 +1,9 @@
 @import '../../styles/utils.scss';
 
+.el-loading-mask {
+  background-color: rgba(255, 255, 255, 0.3);
+}
+
 .card-container {
   width: 470px;
   height: 472px;
@@ -18,6 +22,7 @@
     overflow-y: auto;
     &::-webkit-scrollbar {
       width: 5px;
+      height: 5px;
     }
     &::-webkit-scrollbar-track {
       background: transparent;

+ 4 - 1
src/components/Card/index.tsx

@@ -1,3 +1,4 @@
+import { useIncidentStore } from '@/store';
 import clsx from 'clsx';
 import { defineComponent, PropType } from 'vue-demi';
 import './index.scss';
@@ -22,8 +23,10 @@ export default defineComponent({
     const className = clsx('card-container', {
       [`card-container-bg-${props.cardType}`]: props.cardType,
     });
+    const store = useIncidentStore();
+
     return () => (
-      <div class={className}>{ctx.slots.default && ctx.slots.default()}</div>
+      <div v-loading={store.loading} class={className}>{ctx.slots.default && ctx.slots.default()}</div>
     );
   },
 });

+ 25 - 22
src/views/HomePage/MessageCard/index.tsx

@@ -3,6 +3,7 @@ import Card from '@/components/Card';
 import { useCommonStore } from '@/store';
 import useIncidentStore from '@/store/useIncidentStore';
 import { computed, defineComponent, PropType } from 'vue-demi';
+import { RouterLink } from 'vue-router';
 
 export default defineComponent({
   name: 'MessageCard',
@@ -17,29 +18,31 @@ export default defineComponent({
       <Card cardType="message-list">
         <div class="message-card-container">
           {store.incidents.rows?.map((item, idx) => (
-            <div class="message-item">
-              <div class="title">
-                <span class="index">{idx + 1}、</span>
-                <span>
-                  {item.createTime +
-                    ',在' +
-                    item.addr +
-                    '处发生了' +
-                    (commonStore.globalDict['zhdd_incident_type']?.find(
-                      (i) => i.dictValue.toString() === `${item.type}`,
-                    )?.dictLabel ?? '预警') +
-                    '事件'}
-                </span>
+            <RouterLink to={`/incidentDetail?id=${item.id}`}>
+              <div class="message-item">
+                <div class="title">
+                  <span class="index">{idx + 1}、</span>
+                  <span>
+                    {item.createTime +
+                      ',在' +
+                      item.addr +
+                      '处发生了' +
+                      (commonStore.globalDict['zhdd_incident_type']?.find(
+                        (i) => i.dictValue.toString() === `${item.type}`,
+                      )?.dictLabel ?? '预警') +
+                      '事件'}
+                  </span>
+                </div>
+                <div class="desc">
+                  {item.source}
+                  {
+                    commonStore.globalDict['zhdd_incident_source']?.find(
+                      (i) => i.dictValue.toString() === `${item.source}`,
+                    )?.dictLabel
+                  }
+                </div>
               </div>
-              <div class="desc">
-                {item.source}
-                {
-                  commonStore.globalDict['zhdd_incident_source']?.find(
-                    (i) => i.dictValue.toString() === `${item.source}`,
-                  )?.dictLabel
-                }
-              </div>
-            </div>
+            </RouterLink>
           ))}
         </div>
       </Card>

+ 11 - 0
src/views/HomePage/index.scss

@@ -24,6 +24,15 @@
 .message-card-container {
   color: #fff;
   overflow-y: auto;
+  > a {
+    color: #fff;
+    &:hover {
+      .title {
+        transition: color 0.2s;
+        color: #7cb3ff;
+      }
+    }
+  }
   .message-item {
     padding-bottom: px2rem(30px);
     .title {
@@ -31,7 +40,9 @@
       display: flex;
       align-items: top;
       padding-bottom: px2rem(13px);
+      color: #fff;
       .index {
+        color: #fff;
         width: px2rem(20px);
         width: 20px;
       }

+ 13 - 8
src/views/IncidentDetail/CommandChainCard/index.tsx

@@ -1,38 +1,43 @@
 import Card from '@/components/Card';
-import { defineComponent } from 'vue-demi';
+import { computed, defineComponent } from 'vue-demi';
 // @ts-ignore
 import command from '@/assets/icons/detail/command@2x.png';
 // @ts-ignore
 import co_organized from '@/assets/icons/detail/co_organized@2x.png';
 // @ts-ignore
 import host from '@/assets/icons/detail/host@2x.png';
-import { useIncidentStore } from '@/store';
+import { useCommonStore, useIncidentStore } from '@/store';
 
 export default defineComponent({
   name: 'CommandChainCard',
   setup() {
     const store = useIncidentStore();
+    const commonStore = useCommonStore();
+
+    const getDept = (value: string = '') =>
+      commonStore.globalDict['zhdd_org_upload']?.find(
+        (i) => i.dictValue.toString() === value.toString(),
+      )?.dictLabel ?? '-';
 
     return () => (
       <Card cardType="command-chain">
         <div class="command-chain-container">
           <div class="chain-item">
             <span class="title">指挥部门</span>
-            <span class="dessc">市交通运输局</span>
+            <span class="desc">市交通运输局</span>
             <img class="logo" src={command} alt="" />
           </div>
           <div class="chain-item">
             <span class="title">协办部门</span>
-            <span class="dessc">
-              {store.incidentDetail?.baseInfo?.assistDept ?? '应急安全处'}
+            <span class="desc">
+              {getDept(store.incidentDetail?.baseInfo?.madinDept)}
             </span>
             <img class="logo" src={co_organized} alt="" />
           </div>
           <div class="chain-item">
             <span class="title">主办部门</span>
-            <span class="dessc">
-              {store.incidentDetail?.baseInfo?.assistDept ??
-                '市公路事业发展中心'}
+            <span class="desc">
+              {getDept(store.incidentDetail?.baseInfo?.assistDept)}
             </span>
             <img class="logo" src={host} alt="" />
           </div>

+ 37 - 0
src/views/IncidentDetail/ExecutionLogCard/index.tsx

@@ -0,0 +1,37 @@
+import Card from '@/components/Card';
+import { useIncidentStore } from '@/store';
+import clsx from 'clsx';
+import { computed, defineComponent, ref } from 'vue-demi';
+import { Management } from '@element-plus/icons';
+export default defineComponent({
+  name: 'ExecutionLogCard',
+  setup(props) {
+    const store = useIncidentStore();
+
+    const medias = computed(() =>
+      (store.incidentDetail?.baseInfo?.pic?.split(',') ?? []).concat(
+        store.incidentDetail?.baseInfo?.video?.split(',') ?? [],
+      ),
+    );
+
+    return () => (
+      <Card cardType="execution-log">
+        <div class="execution-log-container">
+          {store.incidentDetail?.process?.map((item, idx) => (
+            <div class={clsx('log-item', `log-item-${idx % 4}`)}>
+              <span class="log-label">{item.des}</span>
+              <span class="log-time">
+                {item.createTime}
+                <span class="logo-icon">
+                  <el-icon>
+                    <Management />
+                  </el-icon>
+                </span>
+              </span>
+            </div>
+          ))}
+        </div>
+      </Card>
+    );
+  },
+});

+ 43 - 7
src/views/IncidentDetail/IncidentInfoCard/index.tsx

@@ -1,5 +1,5 @@
 import Card from '@/components/Card';
-import { defineComponent } from 'vue-demi';
+import { computed, defineComponent } from 'vue-demi';
 
 // @ts-ignore
 import IconLevel from '@/assets/icons/detail/level@2x.png';
@@ -9,9 +9,9 @@ import IconSource from '@/assets/icons/detail/source@2x.png';
 import IconStyle from '@/assets/icons/detail/style@2x.png';
 // @ts-ignore
 import IconTitle from '@/assets/icons/detail/title@2x.png';
-import { useIncidentStore } from '@/store';
+import { useCommonStore, useIncidentStore } from '@/store';
 
-const list = [
+const listss = [
   { label: '事件标题', icon: IconTitle, prop: 'name' as const },
   { label: '事件类型', icon: IconStyle, prop: 'type' as const },
   { label: '事件来源', icon: IconSource, prop: 'source' as const },
@@ -22,19 +22,55 @@ export default defineComponent({
   name: 'IncidentInfoCard',
   setup(props) {
     const store = useIncidentStore();
+    const commonStore = useCommonStore();
+    const list = computed(() => [
+      {
+        label: '事件标题',
+        icon: IconTitle,
+        prop: 'name' as const,
+        value: store.incidentDetail?.baseInfo?.name,
+      },
+      {
+        label: '事件类型',
+        icon: IconStyle,
+        prop: 'type' as const,
+        dict: 'zhdd_incident_type' as const,
+      },
+      {
+        label: '事件来源',
+        icon: IconSource,
+        prop: 'source' as const,
+        dict: 'zhdd_incident_source' as const,
+      },
+      {
+        label: '事件等级',
+        icon: IconLevel,
+        prop: 'level' as const,
+        dict: 'zhdd_incident_level' as const,
+      },
+    ]);
 
     return () => (
       <Card cardType="incident-info">
         <div class="info-container">
-          {list.map((item, idx) => (
+          {list.value.map((item, idx) => (
             <div class="info-item">
               <div class="info-item-lebel" data-idx={idx}>
                 <img src={item.icon} alt={item.label} />
-                <span>事件标题</span>
+                <span>{item.label}</span>
               </div>
               <div class="info-item-value">
-                {store.incidentDetail?.baseInfo?.[item.prop] ??
-                  item.label + 'xxxx x x '}
+                {(item.value
+                  ? item.value
+                  : item.dict &&
+                    commonStore.globalDict[item.dict]?.find(
+                      (i) =>
+                        i.dictValue?.toString() ===
+                        (
+                          store.incidentDetail?.baseInfo
+                            ?.type as unknown as string
+                        )?.toString(),
+                    )?.dictLabel) ?? '-'}
               </div>
             </div>
           ))}

+ 39 - 0
src/views/IncidentDetail/LiveMonitoringCard/index.tsx

@@ -0,0 +1,39 @@
+import Card from '@/components/Card';
+import { useIncidentStore } from '@/store';
+import { computed, defineComponent, ref } from 'vue-demi';
+
+export default defineComponent({
+  name: 'LiveMonitoringCard',
+  setup(props) {
+    const store = useIncidentStore();
+
+    const medias = computed(() =>
+      (store.incidentDetail?.baseInfo?.pic?.split(',') ?? []).concat(
+        store.incidentDetail?.baseInfo?.video?.split(',') ?? [],
+      ),
+    );
+
+    return () => (
+      <Card cardType="live-monitoring">
+        <div class="live-container">
+          <div style={{width: 'calc((1.0911458333rem + 10px) * 6) '}}>
+            {(store.incidentDetail?.baseInfo?.pic?.split(',') ?? []).map(
+              (item) => (
+                <div class="live-item">
+                  <img src={item} />
+                </div>
+              ),
+            )}
+            {(store.incidentDetail?.baseInfo?.video?.split(',') ?? []).map(
+              (item) => (
+                <div class="live-item">
+                  <video src={item} controls />
+                </div>
+              ),
+            )}
+          </div>
+        </div>
+      </Card>
+    );
+  },
+});

+ 187 - 1
src/views/IncidentDetail/index.scss

@@ -3,7 +3,27 @@
 
 .incident-detail-page-container {
   flex: 1;
-  position: relative;
+  // position: absolute;
+  // width: 100%;
+  // height: 100vh;
+  // .detail-left,
+  // .detail-right {
+  //   position: absolute;
+  //   top: 50%;
+  //   transform: translateY(-50%);
+  //   z-index: 1;
+  // }
+
+  // .detail-left {
+  //   left: 0;
+  //   width: calc(px2rem(470px) * 2 + px2rem(21px));
+  //   height: calc(px2rem(472px) * 2 + px2rem(21px));
+  // }
+  // .detail-right {
+  //   right: 0;
+  //   width: px2rem(939px);
+  //   height: calc(px2rem(554px) + px2rem(390px) + px2rem(21px));
+  // }
   .echarts {
     width: px2rem(642px);
     height: px2rem(887px);
@@ -98,6 +118,9 @@
             color: #feb459;
             padding-bottom: px2rem(13px);
           }
+          .desc {
+            text-align: center;
+          }
           .logo {
             position: absolute;
             width: px2rem(40px);
@@ -217,10 +240,173 @@
     }
     &-bg-execution-log {
       right: px2rem(22px);
+      &::before {
+        content: '';
+        position: absolute;
+        width: 14px;
+        height: calc(100% - px2rem(80px) - px2rem(30px));
+
+        background: #112692;
+        top: px2rem(80px);
+        left: 50%;
+        transform: translateX(-50%);
+        z-index: -1;
+      }
+      .execution-log-container {
+        padding: 20px 0;
+        .log-item {
+          position: relative;
+          margin-bottom: 40px;
+          min-height: px2rem(48px);
+          .log-label {
+            font-size: px2rem(18px);
+            font-weight: 400;
+            display: -webkit-box;
+            -webkit-box-orient: vertical;
+            -webkit-line-clamp: 2;
+            overflow: hidden;
+            text-align: right;
+            width: calc(50% - 42px);
+            position: absolute;
+            left: 0;
+          }
+
+          .log-time {
+            height: px2rem(48px);
+            line-height: px2rem(48px);
+            background-color: #3db3ea;
+            font-size: px2rem(24px);
+            font-family: DIN Alternate, DIN Alternate-Bold;
+            font-weight: 700;
+            position: absolute;
+            left: 50%;
+            top: 50%;
+            transform: translateY(-50%) translateX(px2rem(-24px));
+            border-top-left-radius: px2rem(48px);
+            border-bottom-left-radius: px2rem(48px);
+            padding-left: px2rem(58px);
+            padding-right: px2rem(10px);
+            .logo-icon {
+              position: absolute;
+              height: px2rem(44px);
+              width: px2rem(44px);
+              background-color: #092093;
+              top: px2rem(2px);
+              left: px2rem(2px);
+              border-radius: 44px;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+            }
+            &::before,
+            &::after {
+              content: '';
+              right: px2rem(-18px);
+              position: absolute;
+              border-left: px2rem(18px) solid transparent;
+              border-right: px2rem(18px) solid transparent;
+            }
+            &::before {
+              border-top: px2rem(24px) solid #3db3ea;
+            }
+            &::after {
+              bottom: 0;
+              border-bottom: px2rem(24px) solid #3db3ea;
+            }
+          }
+          &.log-item-3,
+          &.log-item-1 {
+            .log-label {
+              left: unset;
+              right: 0;
+              text-align: left;
+            }
+            .log-time {
+              left: unset;
+              right: 50%;
+              top: 50%;
+              transform: translateY(-50%) translateX(px2rem(24px));
+              border-top-left-radius: unset;
+              border-bottom-left-radius: unset;
+
+              border-top-right-radius: px2rem(48px);
+              border-bottom-right-radius: px2rem(48px);
+
+              padding-left: px2rem(10px);
+              padding-right: px2rem(58px);
+              background-color: #09b283;
+              .logo-icon {
+                top: px2rem(2px);
+                left: unset;
+                right: px2rem(2px);
+                border-radius: 44px;
+              }
+              &::before,
+              &::after {
+                content: '';
+                right: unset;
+                left: px2rem(-18px);
+              }
+              &::before {
+                border-top-color: #09b283;
+              }
+              &::after {
+                border-bottom-color: #09b283;
+              }
+            }
+          }
+          &.log-item-3 {
+            .log-time {
+              background-color: #eaa33f;
+              &::before {
+                border-top-color: #eaa33f;
+              }
+              &::after {
+                border-bottom-color: #eaa33f;
+              }
+            }
+          }
+          &.log-item-2 {
+            .log-time {
+              background-color: #c172f9;
+              &::before {
+                border-top-color: #c172f9;
+              }
+              &::after {
+                border-bottom-color: #c172f9;
+              }
+            }
+          }
+        }
+      }
     }
     &-bg-live-monitoring {
       top: px2rem(564px + 83px);
       right: px2rem(22px);
+      .live-container {
+        overflow-x: auto;
+        overflow-y: hidden;
+        > div {
+          width: calc(px2rem(419px) + 10px);
+          height: 100%;
+          display: flex;
+        }
+        .live-item {
+          width: px2rem(419px);
+          // height: px2rem(285px);
+          text-align: center;
+          margin-right: 10px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          flex: 1;
+          img,
+          video {
+            max-width: 100%;
+            max-height: 100%;
+          }
+        }
+      }
     }
   }
 }

+ 25 - 14
src/views/IncidentDetail/index.tsx

@@ -1,30 +1,41 @@
-import { onMounted, reactive, defineComponent } from 'vue';
-import Card from '@/components/Card';
-import './index.scss';
+import { onMounted, onUnmounted, defineComponent } from 'vue';
+import { useRoute } from 'vue-router';
+import { useCommonStore, useIncidentStore } from '@/store';
 import IncidentInfoCard from './IncidentInfoCard';
 import CommandChainCard from './CommandChainCard';
 import EmergencyLinkageCard from './EmergencyLinkageCard';
 import IncidentPlanCard from './IncidentPlanCard';
-import { useRoute } from 'vue-router';
-import { useIncidentStore } from '@/store';
+import LiveMonitoringCard from './LiveMonitoringCard';
+import ExecutionLogCard from './ExecutionLogCard';
+import './index.scss';
 
 export default defineComponent({
   name: 'IncidentDetail',
   provide: {},
   setup() {
     const store = useIncidentStore();
+    const commonStore = useCommonStore();
     const route = useRoute();
-    onMounted(() => store.getIncidentItem(route.query.id as string));
+    onMounted(() => {
+      commonStore.getGlobalDict('zhdd_incident_type');
+      commonStore.getGlobalDict('zhdd_incident_source');
+      commonStore.getGlobalDict('zhdd_org_upload');
+      commonStore.getGlobalDict('zhdd_incident_level');
+      store.getIncidentItem(route.query.id as string);
+    });
+    onUnmounted(() => (store.incidentDetail = {}));
     return () => (
       <div class="incident-detail-page-container">
-        <IncidentInfoCard />
-        <CommandChainCard />
-        <IncidentPlanCard />
-        <EmergencyLinkageCard />
-        <Card cardType="live-monitoring" />
-        <Card cardType="execution-log" />
-        {/* <Map /> */}
-        {/* <MarkerMap /> */}
+        <div class="detail-left">
+          <IncidentInfoCard />
+          <CommandChainCard />
+          <IncidentPlanCard />
+          <EmergencyLinkageCard />
+        </div>
+        <div class="detail-right">
+          <ExecutionLogCard />
+          <LiveMonitoringCard />
+        </div>
       </div>
     );
   },