Просмотр исходного кода

feat: 添加交通管理与通知的查看页

wyf 8 месяцев назад
Родитель
Сommit
4005bea110

+ 8 - 2
src/views/traffic/regulation/RegulationItem.vue

@@ -4,7 +4,7 @@
       <BreadcrumbBack />
       <BreadcrumbBack />
       <span class="breadcrumb-title">{{ headerTitle }}</span>
       <span class="breadcrumb-title">{{ headerTitle }}</span>
     </header>
     </header>
-    <component :is="dynamicComponent" ref="dynamicComponentRef" @record-submitted="handleRecordSubmitted" />
+    <component :is="dynamicComponent" :id="id" ref="dynamicComponentRef" @record-submitted="handleRecordSubmitted" />
   </div>
   </div>
 </template>
 </template>
 
 
@@ -14,6 +14,7 @@
 
 
   const route = useRoute();
   const route = useRoute();
   const operate = route.query.operate;
   const operate = route.query.operate;
+  const id = Number(route.query.id);
   const headerTitle = computed(() => {
   const headerTitle = computed(() => {
     switch (operate) {
     switch (operate) {
       case 'regulation-create':
       case 'regulation-create':
@@ -24,6 +25,8 @@
         return `创建通知`;
         return `创建通知`;
       case 'notice-edit':
       case 'notice-edit':
         return `编辑通知`;
         return `编辑通知`;
+      case 'notice-view':
+        return `通知详情`;
       default:
       default:
         return '未知操作';
         return '未知操作';
     }
     }
@@ -36,9 +39,12 @@
       case 'regulation-edit':
       case 'regulation-edit':
         return defineAsyncComponent(() => import('./components/RegulationEdit.vue'));
         return defineAsyncComponent(() => import('./components/RegulationEdit.vue'));
       case 'notice-create':
       case 'notice-create':
-        return defineAsyncComponent(() => import('./components/NoticeCreate.vue'));
+        return defineAsyncComponent(() => import('./components/NoticeView.vue'));
+      // return defineAsyncComponent(() => import('./components/NoticeCreate.vue'));
       case 'notice-edit':
       case 'notice-edit':
         return defineAsyncComponent(() => import('./components/NoticeEdit.vue'));
         return defineAsyncComponent(() => import('./components/NoticeEdit.vue'));
+      case 'notice-view':
+        return defineAsyncComponent(() => import('./components/NoticeView.vue'));
       default:
       default:
         return '';
         return '';
     }
     }

+ 87 - 0
src/views/traffic/regulation/components/NoticeView.vue

@@ -0,0 +1,87 @@
+<template>
+  <main class="safety-platform-container__main">
+    <header class="info-container__header">
+      <el-tooltip
+        :content="NoticeDetail?.name"
+        placement="bottom-start"
+        effect="light"
+        :hide-after="0"
+        v-if="NoticeDetail?.name && NoticeDetail.name.length > 50"
+      >
+        <span class="title">{{ NoticeDetail?.name }}</span>
+      </el-tooltip>
+      <span class="title" v-else>{{ NoticeDetail?.name }}</span>
+      <div class="notice">
+        <p class="info-item">
+          创建人:<span class="info-content">{{ NoticeDetail?.realname }}</span>
+        </p>
+        <p class="info-item">
+          发布时间:<span class="info-content">{{ NoticeDetail?.releaseTime }}</span>
+        </p>
+      </div>
+    </header>
+    <section class="divider"></section>
+    <section class="content">
+      <div v-html="NoticeDetail?.content"></div>
+    </section>
+    <section class="file" v-if="NoticeDetail?.noticeFiles.length">
+      <span class="info-content" style="font-size: 14px">文件({{ NoticeDetail?.noticeFiles.length }})</span>
+      &nbsp;
+      <a @click="downloadAll">下载全部</a>
+      <div class="file-list">
+        <div
+          class="file-item"
+          v-for="item in NoticeDetail?.noticeFiles"
+          :key="item.fileId"
+          @click="previewOnline(item.fileUrl, item.fileType as keyof typeof FILE_TYPE_ICON)"
+        >
+          <span class="file-item--name">{{ item.fileName }}</span>
+          <div class="file-item--footer">
+            <div class="info">
+              <img :src="FILE_TYPE_ICON[item.fileType as keyof typeof FILE_TYPE_ICON]" />
+              <span>{{ item.fileSize }}</span>
+            </div>
+            <a @click.stop="downloadFile(item.fileUrl, item.fileName)">下载</a>
+          </div>
+        </div>
+      </div>
+    </section>
+  </main>
+
+  <PreviewOnline ref="previewOnlineRef" />
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted } from 'vue';
+  import PreviewOnline from '@/views/disaster/components/PreviewOnline.vue';
+  import { downloadFile } from '@/views/disaster/utils';
+  import { FILE_TYPE_ICON } from '../constants';
+  import { NoticeDetailResponse } from '../types';
+
+  const props = defineProps<{
+    id: number;
+  }>();
+
+  const previewOnlineRef = ref<InstanceType<typeof PreviewOnline>>();
+
+  const previewOnline = (url: string | undefined, type: keyof typeof FILE_TYPE_ICON) => {
+    if (url) {
+      previewOnlineRef.value?.open(url, type);
+    }
+  };
+
+  const NoticeDetail = ref<NoticeDetailResponse>();
+
+  const downloadAll = () => {
+    NoticeDetail.value?.noticeFiles.forEach((item) => {
+      downloadFile(item.fileUrl, item.fileName);
+    });
+  };
+
+  onMounted(() => {});
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '../style/view-page.scss' as *;
+</style>

+ 14 - 0
src/views/traffic/regulation/constants.ts

@@ -13,3 +13,17 @@ export enum IS_PUSH {
   NOT_PUSH = 0,
   NOT_PUSH = 0,
   PUSH,
   PUSH,
 }
 }
+
+/**
+ * 文件类型图标
+ */
+import word from '@/assets/svg/word.svg';
+import excel from '@/assets/svg/excel.svg';
+import ppt from '@/assets/svg/ppt.svg';
+import pdf from '@/assets/svg/pdf.svg';
+export const FILE_TYPE_ICON = {
+  word: word,
+  excel: excel,
+  ppt: ppt,
+  pdf: pdf,
+};

+ 96 - 0
src/views/traffic/regulation/style/view-page.scss

@@ -0,0 +1,96 @@
+@use '@/styles/variables.scss' as *;
+.info-container__header {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+}
+.title {
+  font-size: 20px;
+  font-weight: 600;
+  color: rgba($text-color, 0.85);
+  margin-bottom: 18px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.notice {
+  display: flex;
+  gap: 370px;
+  margin-bottom: 16px;
+}
+.info-item {
+  color: rgba($text-color, 0.85);
+}
+.info-content,
+.content {
+  color: rgba($text-color, 0.65);
+}
+
+.content {
+  white-space: pre-wrap;
+  word-break: break-word;
+}
+
+.divider {
+  width: 100%;
+  height: 1px;
+  background-color: #979797;
+  opacity: 0.26;
+  margin-top: 18px;
+  margin-bottom: 18px;
+}
+
+  .file {
+    margin-top: 18px;
+    a {
+      font-size: 14px;
+      color: $primary-color;
+      cursor: pointer;
+    }
+  }
+  .file-list {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 10px;
+    margin-top: 10px;
+  }
+  .file-item {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    width: 220px;
+    height: 81px;
+    padding: 16px 10px;
+    background: #eee;
+    border-radius: 8px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    &:hover {
+      background: #ddd;
+      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+    }
+    &--name {
+      width: 100%;
+      font-size: 14px;
+      color: #333;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+    &--footer {
+      @include flex-center;
+      justify-content: space-between;
+      font-size: 14px;
+      .info {
+        @include flex-center;
+        gap: 2px;
+        img {
+          width: 16px;
+          height: 16px;
+        }
+        span {
+          color: #999;
+        }
+      }
+    }
+  }

+ 17 - 0
src/views/traffic/regulation/types.ts

@@ -25,3 +25,20 @@ export interface FileItem {
   fileUrl?: string;
   fileUrl?: string;
   file?: File;
   file?: File;
 }
 }
+
+export interface RegulationDetailResponse {
+  name: string;
+  regulationFiles: FileItem[];
+  memo?: string;
+  realname: string;
+  releaseTime: string;
+}
+
+export interface NoticeDetailResponse {
+  name: string;
+  content: string;
+  noticeFiles: FileItem[];
+  memo?: string;
+  realname: string;
+  releaseTime: string;
+}