dev01 1 year ago
parent
commit
de58f29749

+ 15 - 1
src/client/reports.client.ts

@@ -3,7 +3,7 @@
 import { withPrefix } from "@/libs/url.lib";
 import httpClient from "@/services/httpClient";
 import type { CompanyAgg } from "@/types/company.types";
-import type { ReportsResponse } from "@/types/report.types";
+import type { ReportsResponse, ReportSaveRequest } from "@/types/report.types";
 import type { Response } from "@/types/response.types";
 
 const _url = withPrefix("/api/srUserReport")
@@ -33,3 +33,17 @@ export async function upper(id: number): Promise<Blob> {
   const resp = await httpClient.get<File>(_url("/seveText"), { params: { id } })
   return resp.data;
 }
+
+/**
+ * 通过报告ID获取报告数据
+ * @param id 报告ID
+ * @returns Promise
+ */
+export async function get(id: number): Promise<ReportSaveRequest> {
+  const resp = await httpClient.get<Response<ReportSaveRequest>>(_url(`/${id}`))
+  return resp.data.data;
+}
+
+export function link(reportId: number, token: string): string {
+  return `/api/logo/${reportId}?token=${token}`
+}

+ 0 - 51
src/libs/reportExport.lib.ts

@@ -1,51 +0,0 @@
-import { useAuthStore } from "@/stores/auth.store"
-
-export function useReportExport() {
-  const authStore = useAuthStore()
-
-  // async function download(name: string, id: number) {
-  //   return reportExportService.download(id).then((resp) => {
-  //     if (!resp) {
-  //       return
-  //     }
-  //     const url = window.URL.createObjectURL(new Blob([resp]))
-  //     const a = document.createElement('a')
-  //     a.style.display = 'none'
-  //     a.setAttribute('download', name + '.docx')
-  //     a.href = url
-  //     document.body.appendChild(a)
-  //     a.click()
-  //     window.URL.revokeObjectURL(a.href)
-  //     document.body.removeChild(a)
-  //   })
-  // }
-
-  function download(name: string, id: number) {
-    const href = link(id, authStore.token)
-    if (!href) {
-      return
-    }
-    const a = document.createElement("a")
-    a.setAttribute("download", name + '.docx')
-    a.setAttribute("href", href)
-    a.style.display = 'none'
-    document.body.appendChild(a)
-    a.click()
-    document.body.removeChild(a)
-  }
-
-  /**
-   * 生成报告下载链接
-   * @param id 报告ID
-   * @param token 用户token
-   * @returns 报告下载的链接字符串或者null
-   */
-  function link(id: number, token: string | null) {
-    if (!token || !id) {
-      return null
-    }
-    return `/gw/user/reportExport/${id}?token=${token}`
-  }
-
-  return { download, link }
-}

+ 5 - 0
src/router/index.ts

@@ -42,6 +42,11 @@ export const router = createRouter({
           name: "RList",
           component: () => import("../views/report/ReportListView.vue"),
         },
+        {
+          path: "detail/:id",
+          name: "ReportDetail",
+          component: () => import("../views/report/ReportDetailView.vue"),
+        },
       ],
     },
   ],

+ 23 - 23
src/services/report.service.ts

@@ -2,31 +2,31 @@ import * as reportClient from "@/client/report.client";
 import * as duplicateCheckClient from "@/client/duplicateCheck.client";
 import type { ReportCreateRequest, ReportSaveRequest, DuplicateCheckRequest } from "@/types/report.types";
 
-export async function get(id: number): Promise<ReportSaveRequest> {
-  const resp = await reportClient.get(id);
-  if (!resp.metadata) {
-    resp.metadata = [];
-  }
-  return resp;
-}
+// export async function get(id: number): Promise<ReportSaveRequest> {
+//   const resp = await reportClient.get(id);
+//   if (!resp.metadata) {
+//     resp.metadata = [];
+//   }
+//   return resp;
+// }
 
-export async function create(request: ReportCreateRequest): Promise<number> {
-  const id = await reportClient.create(request);
-  return id;
-}
+// export async function create(request: ReportCreateRequest): Promise<number> {
+//   const id = await reportClient.create(request);
+//   return id;
+// }
 
-export async function save(reportSaveRequest: ReportSaveRequest) {
-  return reportClient.save(reportSaveRequest);
-}
+// export async function save(reportSaveRequest: ReportSaveRequest) {
+//   return reportClient.save(reportSaveRequest);
+// }
 
-export async function updateStatus(id: number){
-  return reportClient.updateStatus(id);
-}
+// export async function updateStatus(id: number){
+//   return reportClient.updateStatus(id);
+// }
 
-export async function remove(id: number) {
-  return reportClient.remove(id);
-}
+// export async function remove(id: number) {
+//   return reportClient.remove(id);
+// }
 
-export function duplicateCheck(duplicateCheckRequest: DuplicateCheckRequest){
-  return duplicateCheckClient.duplicateCheck(duplicateCheckRequest);
-}
+// export function duplicateCheck(duplicateCheckRequest: DuplicateCheckRequest){
+//   return duplicateCheckClient.duplicateCheck(duplicateCheckRequest);
+// }

+ 13 - 0
src/services/reports.service.ts

@@ -1,4 +1,5 @@
 import * as reportsClient from "@/client/reports.client"
+import type { ReportSaveRequest } from "@/types/report.types";
 
 export async function query(page?: number, companyIds?: number[], field?: string, keyword?: string, status?: number) {
   return reportsClient.query(page || 1, companyIds, field, keyword, status);
@@ -7,3 +8,15 @@ export async function query(page?: number, companyIds?: number[], field?: string
 export async function upper(id: number) {
   return reportsClient.upper(id);
 }
+
+export async function get(id: number): Promise<ReportSaveRequest> {
+  const resp = await reportsClient.get(id);
+  if (!resp.metadata) {
+    resp.metadata = [];
+  }
+  return resp;
+}
+
+export function link(reportId: number, token: string): string {
+  return reportsClient.link(reportId, token)
+}

+ 109 - 0
src/views/report/ReportDetailView.vue

@@ -0,0 +1,109 @@
+<script setup lang="ts">
+import { onMounted, onUnmounted, ref, type Ref } from "vue";
+import { useRoute } from "vue-router";
+import PageHeader from '@/components/PageHeader.vue';
+import ReportField from "./components/report-create/ReportField.vue"
+import * as reportService from "@/services/reports.service";
+import { CompLog } from "@/libs/log.lib";
+import { message } from "ant-design-vue";
+import { useAuthStore } from "@/stores/auth.store";
+
+const _componentName = 'ReportEditorView'
+const logError = CompLog.logErr(_componentName)
+
+// 当前路由
+const route = useRoute();
+// 权限store
+const authStore = useAuthStore()
+const logoUrl = ref<string | undefined>(undefined)
+
+const report: Ref<ReportSaveRequest | null> = ref(null);
+
+onMounted(() => {
+  const id = parseInt(route.params.id as string);
+  if (id && id > 0) {
+    reportService.get(id).then((data) => {
+      report.value = data;
+      // 设置Logo信息
+      if (report.value.id && authStore.token) {
+        logoUrl.value = reportService.link(report.value.id, authStore.token)
+      }
+    }).catch(logError);
+  }
+});
+
+onUnmounted(() => {
+
+})
+</script>
+
+<template>
+  <PageHeader title="报告详情" />
+  <a-row :gutter="24" v-if="report">
+    <a-col :span="12">
+      <div class="metadata-wrap">
+        <p>
+          <span>企业名称:</span><span>{{ report.companyName }}</span>
+        </p>
+        <p>
+          <span>报告类型:</span><span>{{ report.name }}</span>
+        </p>
+        <p>
+          <span>项目名称:</span><span>{{ report.reportName }}</span>
+        </p>
+        <p>
+          <span>项目负责人:</span><span>{{ report.supervisor }}</span>
+        </p>
+        <p>
+          <span>关键词:</span><span>{{ report.keywords }}</span>
+        </p>
+        <p v-for="(item, index) in report.metadata" :key="index">
+          <span>{{ item.name }}:</span><span>{{ item.value }}</span>
+        </p>
+      </div>
+    </a-col>
+    <a-col :span="12">
+      <a-image :width="200" :src="logoUrl" />
+      <!-- <LogoComponent @change="onLogoChange" @delete="onLogoDelete" :url="logoUrl" /> -->
+    </a-col>
+  </a-row>
+  <div class="chapter-wrap" v-if="report">
+    <div v-for="(it, ix) in report.chapters" :key="ix">
+      <h3>
+        <span>{{ it.fullTitle }}</span
+        >&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+        <span
+          >章节关键词:{{
+            !!it.keywords ? it.keywords.toString().replace(/,/g, ",") : ""
+          }}</span
+        >
+      </h3>
+      <p v-html="it.content"></p>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.metadata-wrap {
+  > * {
+    margin-top: 0.5em;
+  }
+}
+
+.chapter-wrap {
+  margin-top: 3em;
+
+  h3 {
+    font-weight: bold;
+  }
+
+  p {
+    margin-left: 2.5em;
+    margin-bottom: 2em;
+  }
+}
+
+.bottom-button-group {
+  margin-top: 5em;
+}
+</style>

+ 0 - 372
src/views/report/ReportEditorView.vue

@@ -1,372 +0,0 @@
-<script setup lang="ts">
-import { onMounted, onUnmounted, ref, type Ref } from "vue";
-import { useRoute } from "vue-router";
-import { PlusOutlined } from "@ant-design/icons-vue";
-import PageHeader from '@/components/PageHeader.vue';
-import RelationCompanyInput from './components/report-create/RelationCompanyInput.vue';
-import ReportField from "./components/report-create/ReportField.vue"
-import DynamicMeta from './components/dynamic-meta/DynamicMeta.vue';
-import ChapterTypeSelect from "./components/ChapterTypeSelect.vue";
-import LogoComponent from "./components/LogoComponent.vue";
-import type { Chapter, ChapterType, ReportMetadataItem, ReportSaveRequest, ReportTemplateSave, DuplicateCheckRequest, ReportChapterRequest } from "@/types/report.types";
-import { ChapterManager } from "@/models/report.model";
-import ChapterEditor from "./components/ChapterEditor.vue";
-import * as reportService from "@/services/report.service";
-import * as reportTemplateService from "@/services/reportTemplate.service"
-import * as logoService from "@/services/logo.service"
-import { CompLog } from "@/libs/log.lib";
-import { message } from "ant-design-vue";
-import TemplateSaveModal from "./components/TemplateSaveModal.vue";
-import type { ReportTemplateModal } from "./reportComponent.types";
-import { useReportExport } from "@/libs/reportExport.lib";
-import { routeToReportPreview } from "@/router";
-import { useAuthStore } from "@/stores/auth.store";
-
-const _componentName = 'ReportEditorView'
-const logError = CompLog.logErr(_componentName)
-
-// 当前路由
-const route = useRoute();
-// 报告下载函数库
-const reportExport = useReportExport()
-// 权限store
-const authStore = useAuthStore()
-
-const chapterEditors = ref<any | null>([])
-
-const report: Ref<ReportSaveRequest | null> = ref(null);
-const relationCompanyEl = ref<HTMLInputElement | null>(null);
-const reportCategoryEl = ref<HTMLInputElement | null>(null);
-const reportNameEl = ref<HTMLInputElement | null>(null);
-const supervisorEl = ref<HTMLInputElement | null>(null);
-const keywordsEl = ref<HTMLInputElement | null>(null);
-const duplicateCheckRequest: Ref<DuplicateCheckRequest> = ref({
-  reportId: "",
-  contents: []
-});
-
-const placeholders = ref(["请输入报告类型","请输入项目名称","请输入项目负责人","请输入报告关键词"])
-
-// 报告元数据
-const metadata: Ref<ReportMetadataItem[]> = ref([]);
-// 章节类型选择器可见状态
-const chapterTypeSelectVisible = ref(false);
-// 章节管理对象
-const chapterManager = new ChapterManager();
-// logo url
-const logoUrl = ref<string | undefined>(undefined)
-// 报告导出按钮是否在加载的状态
-const exporting = ref(false)
-// 定时保存handler
-const timeIntervalHandler = ref<number | null>(null)
-//报告状态
-const reportStatus = ref<number>(0)
-
-function onMetaChange(data: ReportMetadataItem[]) {
-  metadata.value = data;
-  if (report.value) {
-    report.value.metadata = metadata.value;
-  }
-}
-
-/**
- * Logo组件上传处理函数
- * @param file 图片文件
- */
-function onLogoChange(file: File) {
-  if (!report.value) {
-    return;
-  }
-  const formData = new FormData();
-  formData.append("file", file)
-  formData.append("reportId", report.value.id.toString())
-
-  logoService.upload(formData).then((resp) => {
-    if (resp && report.value && authStore.token) {
-      report.value.logoPath = resp;
-      logoUrl.value = logoService.link(report.value.id, authStore.token)
-      onSave()
-    }
-  }).catch(logError)
-}
-
-function onLogoDelete() {
-  if (!report.value || !report.value.logoPath) {
-    return;
-  }
-  logoService.remove(report.value.id).then((resp) => {
-    if (resp) {
-      if (report.value) {
-        report.value.logoPath = ''
-      }
-      logoUrl.value = undefined
-    }
-  })
-}
-
-function onChapterTypeSelected(types: ChapterType[]) {
-  chapterManager.addChapters(types);
-}
-
-function onChapterChange(index: number, data: Chapter) {
-  chapterManager.setChapter(index, data);
-}
-
-function onChapterMoveUp(index: number) {
-  chapterManager.moveUp(index)
-}
-
-function onChapterMoveDown(index: number) {
-  chapterManager.moveDown(index)
-}
-
-function onChapterRemove(index: number) {
-  chapterManager.remove(index)
-}
-
-function onCompanyChange(companyId: number) {
-  if (report.value) {
-    report.value.companyId = companyId;
-  }
-}
-
-const saveLoading = ref(false);
-
-function validateString(text: string, msg: string, el: Ref<HTMLInputElement | null>) {
-  if ((text || '').trim() == '') {
-    message.warning(msg);
-    el.value?.focus()
-    return false;
-  }
-  return true;
-}
-
-function onSave() {
-  if (report.value) {
-    if (!report.value.companyId) {
-      message.warning("请填写关联企业!")
-      relationCompanyEl.value?.focus();
-      return;
-    }
-    const validtions: [string, string, Ref<HTMLInputElement | null>][] = [
-      [report.value.name, "请填写报告类型!", reportCategoryEl],
-      [report.value.reportName, "请填写项目名称!", reportNameEl],
-      [report.value.supervisor, '请填写负责人!', supervisorEl],
-      [report.value.keywords, "请填写关键词!", keywordsEl],
-    ];
-    for (const [text, msg, el] of validtions) {
-      if (!validateString(text, msg, el)) {
-        return;
-      }
-    }
-    if (report.value.metadata) {
-      for (const item of report.value.metadata) {
-        if (item.name == '' || item.value == '') {
-          message.warning("有动态字段的名称或值为空,请填写!")
-          return;
-        }
-      }
-    }
-    saveLoading.value = true;
-    reportService.save(report.value).then(() => {
-      saveLoading.value = false;
-      message.success("保存成功")
-    }).catch((err) => {
-      saveLoading.value = false;
-      logError(err);
-    });
-  }
-}
-
-/**
- * 导出报告
- */
-function onExport() {
-  exporting.value = true
-  if (report.value) {
-    reportExport.download(report.value.name, report.value.id)
-    setTimeout(() => exporting.value = false, 5000)
-  } else {
-    message.warning("暂无数据可以导出")
-  }
-}
-
-/**
- * 报告预览
- */
-function onPreview() {
-  if (report.value) {
-    routeToReportPreview(report.value.id)
-  }
-}
-
-const templateSaveModal = ref<InstanceType<typeof TemplateSaveModal> | null>(null);
-
-function showTemplateSaveModal() {
-  templateSaveModal.value?.show();
-}
-
-function onSaveTemplate(modalData: ReportTemplateModal) {
-  let chapters = report.value?.chapters.map((chapter) => {
-    return {
-      title: chapter.title,
-      keywords: chapter.keywords,
-    } as Chapter
-  })
-  const reportTemplateSave: ReportTemplateSave = {
-    id: 0,
-    companyId: report.value?.companyId || 0,
-    name: modalData.templateName,
-    reportName: report.value?.reportName || '',
-    logoPath: report.value?.logoPath || '',
-    metadata: report.value?.metadata || [],
-    chapters: chapters || [],
-    tags: modalData.tags,
-  }
-
-  reportTemplateService.save(reportTemplateSave).then(() => {
-    templateSaveModal.value?.complete()
-    message.success("模板保存成功")
-  }).catch((err) => {
-    logError(err)
-    templateSaveModal.value?.complete()
-  })
-}
-
-function updateStatus() {
-  if (report.value) {
-    reportService.updateStatus(report.value.id)
-    getStatus();
-  }
-}
-
-function getStatus(){
-  reportStatus.value = reportStatus.value == 0 ? 1 : 0;
-}
-
-function duplicateCheck() {
-  if (report.value) {
-    extract(report.value.chapters, report.value.id +"")
-  }
- if (duplicateCheckRequest?.value) {
-  reportService.duplicateCheck(duplicateCheckRequest.value).then((data: any) => {
-    let index = 0;
-    for (let item of data.repetitiveRate) {
-      if (item && item > 0) {
-        chapterEditors.value[index]?.notifyRepetitiveWarning(true);
-      } else {
-        chapterEditors.value[index]?.notifyRepetitiveWarning(false);
-      }
-      index += 1;
-    }
-  })
- }
-}
-
-function extract(chapters: ReportChapterRequest[], id: string) {
-  duplicateCheckRequest.value.contents = []
-  for (var i = 0; i < chapters.length; i ++) {
-    duplicateCheckRequest.value?.contents.push(chapters[i].content)
-  }
-  if (duplicateCheckRequest?.value) {
-      duplicateCheckRequest.value.reportId = id;
-  }
-}
-
-onMounted(() => {
-  const id = parseInt(route.params.id as string);
-  if (id && id > 0) {
-    reportService.get(id).then((data) => {
-      report.value = data;
-      reportStatus.value = data.status
-      const chapters = report.value.chapters;
-      chapterManager.setChapters(chapters);
-      // 设置Logo信息
-      if (report.value.id && authStore.token) {
-        logoUrl.value = logoService.link(report.value.id, authStore.token)
-      }
-      // 数据加载成功后,设置自动保存
-      timeIntervalHandler.value = window.setInterval(() => {
-        onSave();
-      }, 60000);
-    }).catch(logError);
-  }
-});
-
-onUnmounted(() => {
-  if (timeIntervalHandler.value) {
-    window.clearInterval(timeIntervalHandler.value)
-  }
-})
-</script>
-
-<template>
-  <PageHeader title="报告撰写" />
-  <a-row :gutter="24" v-if="report">
-    <a-col :span="12">
-      <div class="metadata-wrap">
-        <RelationCompanyInput v-model:id="report.companyId" @change="onCompanyChange" ref="relationCompanyEl" />
-        <ReportField name="报告类型" v-model="report.name" :placeholder="placeholders[0]" ref="reportCategoryEl" />
-        <ReportField name="项目名称" v-model="report.reportName" :placeholder="placeholders[1]" ref="reportNameEl"/>
-        <ReportField name="项目负责人" v-model="report.supervisor" :placeholder="placeholders[2]" ref="supervisorEl" />
-        <ReportField name="关键词" v-model="report.keywords" :placeholder="placeholders[3]" ref="keywordsEl" />
-        <DynamicMeta v-model:data="report.metadata" @change="onMetaChange" />
-      </div>
-    </a-col>
-    <a-col :span="12">
-      <LogoComponent @change="onLogoChange" @delete="onLogoDelete" :url="logoUrl" />
-    </a-col>
-  </a-row>
-  <a-divider />
-  <div class="chapter-wrap" v-if="report">
-    <ChapterEditor
-      v-for="(item, index) in chapterManager.getChapters()"
-      :key="index"
-      :title="report.reportName"
-      :data="item"
-      ref="chapterEditors"
-      :reportKeywords="report.keywords"
-      @change="onChapterChange(index, $event)"
-      @move-up="onChapterMoveUp(index)"
-      @move-down="onChapterMoveDown(index)"
-      @remove="onChapterRemove(index)"
-    />
-  </div>
-  <div class="add-chapter-wrap">
-    <a-button @click="() => chapterTypeSelectVisible = true">
-      <plus-outlined />
-      添加章节
-    </a-button>
-  </div>
-  <div class="bottom-button-group">
-    <a-space v-if="report">
-      <a-checkbox @change="updateStatus()"  :checked="reportStatus == 1">
-        <span v-if="reportStatus == 0">未完成</span>
-        <span v-if="reportStatus == 1">已完成</span>
-      </a-checkbox>
-      <a-button type="primary" @click="onSave" :loading="saveLoading">保存</a-button>
-      <a-button @click="onPreview">预览</a-button>
-      <a-button @click="onExport" :loading="exporting">导出</a-button>
-      <a-button @click="showTemplateSaveModal">另保存为模板</a-button>
-      <a-button @click="duplicateCheck">查重</a-button>
-    </a-space>
-  </div>
-  <ChapterTypeSelect @ok="onChapterTypeSelected" v-model:visible="chapterTypeSelectVisible" />
-  <TemplateSaveModal ref="templateSaveModal" @ok="onSaveTemplate" />
-</template>
-
-<style scoped lang="scss">
-.metadata-wrap {
-  > * {
-    margin-top: 0.5em;
-  }
-}
-
-.add-chapter-wrap {
-  margin-top: 3em;
-}
-
-.bottom-button-group {
-  margin-top: 5em;
-}
-</style>

+ 0 - 265
src/views/report/components/ChapterEditor.vue

@@ -1,265 +0,0 @@
-<!-- 章节编辑器 -->
-
-<script setup lang="ts">
-import { ref, watch, type PropType } from 'vue';
-import { EditOutlined, DownOutlined } from '@ant-design/icons-vue';
-import { marked } from "marked";
-import type { Chapter } from '@/types/report.types';
-import ContentEditor from "../components/ContentEditor.vue";
-import ReferenceDraw from "../components/reference/ReferenceDraw.vue";
-import AIContentDraw from "../components/reference/AIContentDraw.vue";
-import * as aiService from "@/services/ai.service"
-import { message } from 'ant-design-vue';
-
-const props = defineProps({
-  title: {
-    type: String,
-    required: true,
-  },
-  reportKeywords: {
-    type: String,
-    required: false,
-  },
-  data: {
-    type: Object as PropType<Chapter>,
-    required: true
-  },
-  contentEditorVisible: {
-    type: Boolean,
-    required: false,
-    default: true,
-  }
-});
-
-const titleEditor = ref<HTMLInputElement | null>(null);
-
-const titleEditing = ref(false);
-
-const keywordsInChapter = ref('')
-
-const keywordsEditor = ref<HTMLInputElement | null>(null);
-
-const keywordsEditing = ref(false);
-
-const repetitiveWarning = ref(false);
-const LowRepetitionRate = ref(false);
-
-const fullTitle = ref('');
-// 参考文献抽屉组件ref
-const referenceDraw = ref<InstanceType<typeof ReferenceDraw> | null>(null);
-// 内容编辑器组件ref
-const contentEditor = ref<InstanceType<typeof ContentEditor> | null>(null)
-// AI内容展示组件ref
-const aiContentDraw = ref<InstanceType<typeof ReferenceDraw> | null>(null);
-
-const emits = defineEmits<{
-  (e: "update:data", data: Chapter): void;
-  (e: "change", data: Chapter): void;
-  (e: "moveUp"): void;
-  (e: "moveDown"): void;
-  (e: "remove"): void;
-  (e: "sectionRecordUse", sectionId: number): void;
-}>();
-
-const visible = ref<boolean>(false);
-
-const handleOk = () => {
-  emits("remove");
-  visible.value = false;
-};
-
-watch(
-  () => titleEditor.value,
-  (el: HTMLInputElement | null) => {
-    el?.focus();
-  }
-);
-
-function onEditTitle() {
-  fullTitle.value = props.data.title;
-  titleEditing.value = true;
-}
-
-function onEditKeywords() {
-  keywordsInChapter.value = props.data.keywords.join(",");
-  keywordsEditing.value = true;
-}
-
-function onTitleChange() {
-  titleEditing.value = false;
-  emits("change", { ...props.data, title: fullTitle.value })
-}
-
-function onKeywordsChange() {
-  keywordsEditing.value = false;
-  emits("change", { ...props.data, keywords: keywordsInChapter.value.split(",") })
-}
-
-function onTitleChangeCancel() {
-  fullTitle.value = props.data.title;
-  titleEditing.value = false;
-}
-
-function onKeywordsChangeCancel() {
-  keywordsInChapter.value = props.data.keywords.join(",");
-  keywordsEditing.value = false;
-}
-
-function onContentChange(value: string) {
-  emits("change", { ...props.data, content: value });
-}
-
-function onMenuClick(e: { key: string }) {
-  switch (e.key) {
-    case "UP":
-      emits("moveUp");
-      break;
-    case "DOWN":
-      emits("moveDown");
-      break;
-    case "REMOVE":
-      visible.value = true;
-      break;
-  }
-}
-
-function searchReference() {
-  referenceDraw.value?.show();
-}
-
-function onInsert(text: string) {
-  contentEditor.value?.insert(text)
-}
-
-function onReferenceDrawClose() {
-  contentEditor.value?.focus()
-}
-
-function onSectionRecordUse(sectionId: number) {
-  emits("sectionRecordUse", sectionId)
-}
-
-function notifyRepetitiveWarning(data: boolean) {
-  repetitiveWarning.value = data;
-  LowRepetitionRate.value = false
-  if(!data&&props.data.content) LowRepetitionRate.value = !data;
-}
-
-function test1() {
-  const loadingHandler = message.loading({ content: 'AI正在撰写章节内容,请稍等……', duration: 0 })
-  aiService.generate(props.title, props.reportKeywords || '', props.data.title).then((resp) => {
-    onContentChange(marked.parse(resp))
-    if (loadingHandler) {
-      loadingHandler()
-    }
-  }).catch((err) => {
-    console.log('err', err)
-    if (loadingHandler) {
-      loadingHandler()
-    }
-  })
-}
-
-defineExpose({
-  notifyRepetitiveWarning
-})
-</script>
-
-<template>
-  <div class="chapter-item">
-    <a-row type="flex" justify="space-between">
-      <a-col>
-        <a-space>
-          <a-input v-if="titleEditing" v-model:value="fullTitle" @blur="onTitleChange" @pressEnter="onTitleChange"
-            @keyup.esc="onTitleChangeCancel" ref="titleEditor" class="title-editor" />
-          <h1 v-else>
-            <span>{{ data.fullTitle }}</span>
-          </h1>
-          <a-button type="text" @click="onEditTitle" class="edit-btn"><edit-outlined /></a-button>
-        </a-space>
-      </a-col>
-      <a-col flex="1">
-        <!-- 关键词编辑 -->
-        <a-space class="keywords-wrap">
-          <h1>
-            <span>章节关键词:</span><span v-if="!keywordsEditing">{{ data.keywords.join(', ') }}</span>
-          </h1>
-          <a-input v-if="keywordsEditing" v-model:value="keywordsInChapter" @blur="onKeywordsChange"
-            @pressEnter="onKeywordsChange" @keyup.esc="onKeywordsChangeCancel" ref="keywordsEditor"
-            class="keywords-editor" />
-          <a-button type="text" @click="onEditKeywords" class="edit-btn"><edit-outlined /></a-button>
-        </a-space>
-      </a-col>
-      <a-col>
-        <a-dropdown>
-          <template #overlay>
-            <a-menu @click="onMenuClick">
-              <a-menu-item key="UP">上移</a-menu-item>
-              <a-menu-item key="DOWN">下移</a-menu-item>
-              <a-menu-item key="REMOVE">删除</a-menu-item>
-            </a-menu>
-          </template>
-          <a-button type="link" size="small">
-            操作
-            <DownOutlined />
-          </a-button>
-        </a-dropdown>
-      </a-col>
-    </a-row>
-    <p v-if="contentEditorVisible" :class="`${repetitiveWarning?'repetitiveWarning':LowRepetitionRate?'LowRepetitionRate':''}`">
-      <ContentEditor :data="data.content || ''" @change="onContentChange" ref="contentEditor" />
-    </p>
-    <div class="chapter-button-group" v-if="contentEditorVisible">
-      <a-space>
-        <a-button @click="test1">AI生成</a-button>
-        <a-button @click="searchReference">匹配文献</a-button>
-      </a-space>
-    </div>
-  </div>
-  <ReferenceDraw :report-name="title" :reportKeywords="reportKeywords || ''" :chapter-keywords="data.keywords || []"
-    ref="referenceDraw" @insert="onInsert" @close="onReferenceDrawClose" @recordUse="onSectionRecordUse" />
-  <AIContentDraw content="''" ref="aiContentDraw" />
-  <!--删除章节 start -->
-  <a-modal v-model:visible="visible" title="删除章节" @ok="handleOk">
-    <p>确认删除章节</p>
-  </a-modal>
-  <!-- end -->
-</template>
-
-<style scoped lang="scss">
-.chapter-item {
-  margin-top: 2em;
-
-  .title-editor {
-    width: 400px;
-  }
-
-  .keywords-wrap {
-    font-size: 0.9em;
-    display: flex;
-    align-items: baseline;
-    margin-left: 2em;
-  }
-
-  .keywords-editor {
-    width: 400px;
-  }
-
-  .edit-btn {
-    margin-left: 0.5em;
-  }
-
-  .chapter-button-group {
-    text-align: right;
-  }
-
-  .repetitiveWarning {
-    border: 1px solid red;
-    border-radius: 8px;
-  }
-  .LowRepetitionRate {
-    border: 1px solid green;
-    border-radius: 8px;
-  }
-}
-</style>

+ 0 - 105
src/views/report/components/TemplateSaveModal.vue

@@ -1,105 +0,0 @@
-<script setup lang="ts">
-import { onMounted, reactive, ref } from "vue"
-import type { ReportTemplateModal } from "../reportComponent.types";
-import * as reportTemplateService from "@/services/reportTemplate.service"
-import { CompLog } from "@/libs/log.lib";
-import { message, type FormInstance, type SelectProps } from "ant-design-vue";
-
-const logError = CompLog.logErr("TemplateSaveModal")
-
-// 弹窗组件显示状态
-const visible = ref(false)
-// 确定按钮加载状态
-const saving = ref(false);
-// 标签值列表
-const tagsOptions = ref<SelectProps['options']>([]);
-// 表单ref
-const templateSaveModalRef = ref<FormInstance>();
-// 模板名称输入框ref
-const templateNameElem = ref<HTMLInputElement | null>(null)
-
-const emits = defineEmits<{
-  (e: "ok", data: ReportTemplateModal): void
-}>();
-
-const formState = reactive<ReportTemplateModal>({
-  templateName: '',
-  tags: [],
-});
-
-function onTagsChange() {
-  formState.tags = formState.tags.map((item) => item.trim())
-}
-
-async function onOk() {
-  const templateName = (formState.templateName || '').trim()
-  formState.templateName = templateName
-  templateSaveModalRef.value?.validateFields()
-  if (templateName == '') {
-    templateNameElem.value?.focus()
-    message.warning("请填写模板名称!")
-    return
-  }
-  saving.value = true;
-  emits("ok", formState);
-}
-
-onMounted(() => {
-  reportTemplateService.tags().then((tags) => {
-    tagsOptions.value = tags.map((tag) => ({ value: tag }))
-  }).catch(logError)
-});
-
-function hide() {
-  visible.value = false
-  saving.value = false
-}
-
-function complete() {
-  visible.value = false
-  saving.value = false
-}
-
-defineExpose({
-  show: () => visible.value = true,
-  hide,
-  complete,
-})
-</script>
-
-<template>
-  <a-modal v-model:visible="visible" title="另存为模板" @ok="onOk">
-    <a-form
-      :model="formState"
-      name="templateSaveModal"
-      ref="templateSaveModalRef"
-      :label-col="{ span: 5 }"
-      :wrapper-col="{ span: 19 }"
-      autocomplete="off"
-      :ok-button-props="{ loading: saving }"
-    >
-      <a-form-item
-        label="模板名称"
-        name="templateName"
-        :rules="[{ required: true, message: '请输入模板名称!' }]"
-      >
-        <a-input v-model:value="formState.templateName" ref="templateNameElem" />
-      </a-form-item>
-
-      <a-form-item
-        label="标签"
-        name="tags"
-        :rules="[{ required: false, message: '请填写模板标签', type: 'array' }]"
-      >
-        <a-select
-          v-model:value="formState.tags"
-          mode="tags"
-          :token-separators="[',', ',', ' ']"
-          @change="onTagsChange"
-          :options="tagsOptions"
-          placeholder="模板标签,输入逗号、空格或者回车自动分割"
-        ></a-select>
-      </a-form-item>
-    </a-form>
-  </a-modal>
-</template>

+ 0 - 54
src/views/report/components/TemplateSelect.vue

@@ -1,54 +0,0 @@
-<!-- 报告模板选择  -->
-<script setup lang="ts">
-import { onMounted, ref } from "vue"
-import * as reportTemplateService from "@/services/reportTemplate.service"
-import type { ReportTemplatesResponse } from "@/types/report.types";
-
-const visible = ref(false)
-
-const selectedTemplate = ref<number | null>(null)
-
-const templates = ref<ReportTemplatesResponse>({total: 0, items: []})
-
-const emits = defineEmits<{
-  (e: "ok", id: number | null): void;
-}>();
-
-function fetchTemplates() {
-  reportTemplateService.list().then((resp) => {
-    templates.value = resp
-  })
-}
-
-function onOk() {
-  emits("ok", selectedTemplate.value)
-}
-
-onMounted(() => {
-  fetchTemplates()
-})
-
-defineExpose({
-  show: () => visible.value = true,
-  hide: () => visible.value = false,
-})
-</script>
-
-<template>
-  <a-modal v-model:visible="visible" title="报告模板选择" @ok="onOk">
-    <a-radio-group v-model:value="selectedTemplate">
-      <a-radio class="radio" :value="item.id" v-for="(item, index) in templates.items" :key="index">
-        {{ item.name }}
-      </a-radio>
-      <a-radio class="radio" :value="null">空白报告</a-radio>
-    </a-radio-group>
-  </a-modal>
-</template>
-
-<style scoped lang="scss">
-.radio {
-  display: flex;
-  height: 2em;
-  line-height: 2em;
-}
-</style>

+ 0 - 145
src/views/report/components/dynamic-meta/DynamicMeta.vue

@@ -1,145 +0,0 @@
-<!-- 报告元数据,封面上的动态字段 -->
-<script setup lang="ts">
-import { defineAsyncComponent, ref, markRaw, type Ref, type PropType, onMounted } from "vue";
-import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons-vue';
-import draggable from 'vuedraggable'
-
-import type ReportMetaTextInput from './ReportMetaTextInput.vue';
-import type { ReportMetadataItem } from "@/types/report.types";
-
-type ComponentType = typeof ReportMetaTextInput;
-
-const props = defineProps({
-  data: {
-    type: Array as PropType<ReportMetadataItem[]>,
-    required: true,
-  }
-});
-
-interface DynamicComponent {
-  id: number;
-  component: ComponentType;
-  prop: ReportMetadataItem;
-}
-
-const dynamicComponents: Ref<DynamicComponent[]> = ref([]);
-
-const emits = defineEmits<{
-  (e: "change", data: ReportMetadataItem[]): void;
-}>();
-
-function addTextComponent(index: number, prop: ReportMetadataItem = { name: '', value: '', type: 'text' }) {
-  dynamicComponents.value.push({
-    id: index,
-    component: markRaw(defineAsyncComponent(() => import('./ReportMetaTextInput.vue') )),
-    prop
-  });
-}
-
-function addDateComponent(index: number, prop: ReportMetadataItem = { name: '', value: '', type: 'date' }) {
-  dynamicComponents.value.push({
-    id: index,
-    component: markRaw(defineAsyncComponent(() => import('./ReportMetaDateInput.vue') )),
-    prop
-  });
-}
-
-function addDateRangeComponent(index: number, prop: ReportMetadataItem = { name: '', value: '', type: 'dateRange' }) {
-  dynamicComponents.value.push({
-    id: index,
-    component: markRaw(defineAsyncComponent(() => import('./ReportMetaDateRangeInput.vue') )),
-    prop
-  });
-}
-
-function handleMenuClick(e: {key: string}) {
-  const index = dynamicComponents.value.length+1;
-  if (e.key == 'text') {
-    addTextComponent(index);
-  } else if (e.key === 'date') {
-    addDateComponent(index);
-  } else if (e.key === 'dateRange') {
-    addDateRangeComponent(index,{ name: '起止时间', value: '', type: 'dateRange' });
-  }
-}
-
-function emitChange() {
-  const props = dynamicComponents.value.map((item) => item.prop);
-  emits("change", props);
-}
-
-function deleteComponent(index: number) {
-  dynamicComponents.value.splice(index, 1);
-  emitChange()
-}
-
-function onMetaCompChange() {
-  emitChange()
-}
-
-function onDragEnd() {
-  emitChange()
-}
-
-onMounted(() => {
-  if (props.data) {
-    props.data.forEach((metadataItem, index) => {
-      if (metadataItem.type == 'text') {
-        addTextComponent(index, metadataItem);
-      } else if (metadataItem.type == 'date') {
-        addDateComponent(index, metadataItem);
-      } else if (metadataItem.type == 'dateRange') {
-        addDateRangeComponent(index, metadataItem);
-      }
-    })
-  }
-});
-</script>
-
-<template>
-  <div class="dynamic-meta-wrap">
-    <draggable v-model="dynamicComponents" item-key="id" group="components" @end="onDragEnd">
-      <template #item="{element, index}">
-        <a-row type="flex" class="dynamic-meta-item-row">
-          <a-col flex="1">
-            <component
-              :is="element.component"
-              v-model:data="element.prop"
-              @update:data="onMetaCompChange"
-            />
-          </a-col>
-          <a-col>
-            <a-button type="text" @click="deleteComponent(index)">
-              <template #icon><minus-circle-outlined /></template>
-            </a-button>
-          </a-col>
-        </a-row>
-      </template>
-    </draggable>
-    <a-dropdown class="add-item-dropdown">
-      <template #overlay>
-        <a-menu @click="handleMenuClick">
-          <a-menu-item key="text">文本项</a-menu-item>
-          <a-menu-item key="date">日期项</a-menu-item>
-          <a-menu-item key="dateRange">起止时间</a-menu-item>
-        </a-menu>
-      </template>
-      <a-button>
-        <plus-outlined />
-        添加新项
-      </a-button>
-    </a-dropdown>
-  </div>
-</template>
-
-<style scoped lang="scss">
-.dynamic-meta-wrap {
-  .dynamic-meta-item-row {
-    margin-top: 0.5em;
-  }
-
-  .add-item-dropdown {
-    margin-top: 0.5em;
-  }
-}
-</style>

+ 0 - 67
src/views/report/components/dynamic-meta/ReportMetaDateInput.vue

@@ -1,67 +0,0 @@
-<!-- 报告元数据日期输入类型组件 -->
-
-<script setup lang="ts">
-import { onMounted, ref, type PropType } from "vue";
-import type { Dayjs } from 'dayjs';
-import dayjs from "dayjs";
-import type { ReportMetadataItem } from "@/types/report.types";
-
-const props = defineProps({
-  data: {
-    type: Object as PropType<ReportMetadataItem>,
-    required: true,
-  }
-});
-
-const value = ref<Dayjs>();
-
-onMounted(() => {
-  const date = dayjs(props.data.value);
-  if (date.isValid()) {
-    value.value = date;
-  }
-});
-
-const emits = defineEmits<{
-  (e: "update:data", data: ReportMetadataItem): void;
-}>();
-
-function onNameChange(e: InputEvent) {
-  change((e.target as HTMLInputElement).value, props.data.value);
-}
-
-function onValueChange(_: any, dateString: string) {
-  change(props.data.name, dateString);
-}
-
-function change(name: string, value: string) {
-  emits("update:data", { name, value, type: props.data.type });
-}
-</script>
-
-<template>
-  <a-row type="flex" style="align-items: center">
-    <a-col class="name-wrap">
-      <a-space>
-        <a-input :value="data.name" @change="onNameChange" placeholder="字段名称" class="name" />
-        <span>:</span>
-      </a-space>
-    </a-col>
-    <a-col flex="1">
-      <a-date-picker v-model:value="value" @change="onValueChange" class="value" />
-    </a-col>
-  </a-row>
-</template>
-
-<style scoped lang="scss">
-.name-wrap {
-  width: 120px;
-
-  .name {
-    width: 100%
-  }
-}
-.value {
-  width: 100%;
-}
-</style>

+ 0 - 79
src/views/report/components/dynamic-meta/ReportMetaDateRangeInput.vue

@@ -1,79 +0,0 @@
-<!-- 报告元数据日期输入类型组件 -->
-
-<script setup lang="ts">
-import { onMounted, ref, type PropType } from "vue";
-import type { Dayjs } from 'dayjs';
-import dayjs from "dayjs";
-import type { ReportMetadataItem } from "@/types/report.types";
-
-const props = defineProps({
-  data: {
-    type: Object as PropType<ReportMetadataItem>,
-    required: true,
-  }
-});
-
-type RangeValue = [Dayjs, Dayjs];
-
-const value = ref<RangeValue>();
-// 日期连接字符串
-const concatChar = ' 至 ';
-
-onMounted(() => {
-  const v = props.data.value;
-  let from = '', to = '';
-  if (v.includes(concatChar)) {
-    [from, to] = v.split(concatChar);
-  } else {
-    [from, to] = props.data.value.split(' ');
-  }
-  const dateFrom = dayjs(from);
-  const dateTo = dayjs(to);
-  if (dateFrom.isValid() && dateTo.isValid()) {
-    value.value = [dateFrom, dateTo]
-  }
-});
-
-const emits = defineEmits<{
-  (e: "update:data", data: ReportMetadataItem): void;
-}>();
-
-function onNameChange(e: InputEvent) {
-  change((e.target as HTMLInputElement).value, props.data.value);
-}
-
-function onValueChange(_: Dayjs | string, dateString: [string, string]) {
-  change(props.data.name, dateString.join(concatChar));
-}
-
-function change(name: string, value: string) {
-  emits("update:data", { name, value, type: props.data.type });
-}
-</script>
-
-<template>
-  <a-row type="flex" style="align-items: center">
-    <a-col class="name-wrap">
-      <a-space>
-        <a-input :value="data.name" @change="onNameChange" placeholder="字段名称" class="name" />
-        <span>:</span>
-      </a-space>
-    </a-col>
-    <a-col flex="1">
-      <a-range-picker v-model:value="value" @change="onValueChange" class="value" />
-    </a-col>
-  </a-row>
-</template>
-
-<style scoped lang="scss">
-.name-wrap {
-  width: 120px;
-
-  .name {
-    width: 100%
-  }
-}
-.value {
-  width: 100%;
-}
-</style>

+ 0 - 56
src/views/report/components/dynamic-meta/ReportMetaTextInput.vue

@@ -1,56 +0,0 @@
-<!-- 报告元数据文本输入类型组件 -->
-
-<script setup lang="ts">
-import type { ReportMetadataItem } from "@/types/report.types";
-import type { PropType } from "vue";
-
-const props = defineProps({
-  data: {
-    type: Object as PropType<ReportMetadataItem>,
-    required: true,
-  }
-});
-
-const emits = defineEmits<{
-  (e: "update:data", data: ReportMetadataItem): void;
-}>();
-
-function onNameChange(e: InputEvent) {
-  change((e.target as HTMLInputElement).value, props.data.value);
-}
-
-function onValueChange(e: InputEvent) {
-  change(props.data.name, (e.target as HTMLInputElement).value);
-}
-
-function change(name: string, value: string) {
-  emits("update:data", { name, value, type: props.data.type });
-}
-</script>
-
-<template>
-  <a-row type="flex" style="align-items: center">
-    <a-col class="name-wrap">
-      <a-space>
-        <a-input :value="data.name" @change="onNameChange" placeholder="字段名称" class="name" />
-        <span>:</span>
-      </a-space>
-    </a-col>
-    <a-col flex="1">
-      <a-input :value="data.value" @change="onValueChange" placeholder="请输入字段值" class="value" />
-    </a-col>
-  </a-row>
-</template>
-
-<style scoped lang="scss">
-.name-wrap {
-  width: 120px;
-
-  .name {
-    width: 100%
-  }
-}
-.value {
-  width: 100%;
-}
-</style>

+ 0 - 108
src/views/report/components/report-create/RelationCompanyInput.vue

@@ -1,108 +0,0 @@
-<script setup lang="ts">
-import { onMounted, ref, type Ref } from "vue";
-import { debounce } from "lodash";
-import { PlusOutlined } from '@ant-design/icons-vue';
-import * as companyService from "@/services/company.service";
-import type { SelectProps } from "ant-design-vue";
-import { CompLog } from "@/libs/log.lib";
-
-const logError = CompLog.logErr("RelationCompanyInput")
-
-const name: Ref<string> = ref('');
-const companyId: Ref<number | null> = ref(null);
-
-const props = defineProps({
-  id: {
-    type: Number,
-    required: true,
-  }
-});
-
-const options = ref<SelectProps['options']>([]);
-
-const selectEl = ref<HTMLInputElement | null>(null);
-
-const newName = ref('');
-
-const visible = ref(false);
-
-const emits = defineEmits<{
-  (e: "change", companyId: number): void;
-}>();
-
-const fetchCompany = debounce((val) => {
-  companyService.query(val, 10).then((companies) => {
-    const data = companies.map((company) => ({ value: company.id, label: company.name }));
-    options.value = data;
-  }).catch(logError);
-}, 300);
-
-function onFocus() {
-  fetchCompany(name.value);
-}
-
-function onChange(val: number) {
-  companyId.value = val;
-  emits("change", companyId.value);
-}
-
-function onModalOk() {
-  visible.value = false;
-  companyService.create(newName.value).then((company) => {
-    name.value = company.name;
-    companyId.value = company.id;
-    emits("change", companyId.value);
-  }).catch(logError);
-}
-
-function focus() {
-  selectEl.value?.focus();
-}
-
-onMounted(() => {
-  const id = props.id;
-  if (id > 0) {
-    companyService.get(id).then((company) => {
-      companyId.value = id;
-      name.value = company.name;
-    }).catch(logError);
-  }
-});
-
-defineExpose({
-  focus
-});
-</script>
-
-<template>
-  <a-row type="flex" style="align-items: center">
-    <a-col style="width: 120px; text-align: right;">
-      关联企业:
-    </a-col>
-    <a-col flex="1">
-      <a-select
-        v-model:value="name"
-        show-search
-        mode="combobox"
-        placeholder="input search text"
-        style="width: 100%"
-        :default-active-first-option="false"
-        :show-arrow="true"
-        :filter-option="false"
-        :not-found-content="null"
-        :options="options"
-        @focus="onFocus"
-        @search="fetchCompany"
-        @change="onChange"
-        ref="selectEl"
-      >
-      </a-select>
-    </a-col>
-    <a-col>
-      <a-button @click="visible = true"><plus-outlined />新增企业</a-button>
-    </a-col>
-  </a-row>
-  <a-modal v-model:visible="visible" title="新增企业名称" @ok="onModalOk">
-    <p><a-input v-model:value="newName" placeholder="请输入新企业名称" /></p>
-  </a-modal>
-</template>

+ 0 - 39
src/views/report/components/report-create/ReportField.vue

@@ -1,39 +0,0 @@
-<script setup lang="ts">
-import { ref } from "vue";
-
-defineProps({
-  name: String,
-  modelValue: String,
-  placeholder: String,
-});
-
-const emits = defineEmits<{
-  (e: "update:modelValue", value: string): void;
-}>();
-
-const input = ref<HTMLInputElement | null>(null);
-
-function focus() {
-  input.value?.focus();
-}
-
-defineExpose({
-  focus
-});
-</script>
-
-<template>
-  <a-row type="flex" style="align-items: center">
-    <a-col style="width: 120px; text-align: right;">
-      {{ name }}:
-    </a-col>
-    <a-col flex="1">
-      <a-input
-        :value="modelValue"
-        @input="emits('update:modelValue', $event.target.value)"
-        :placeholder="placeholder"
-        ref="input"
-      />
-    </a-col>
-  </a-row>
-</template>

+ 12 - 8
src/views/report/components/report-list/ReportItem.vue

@@ -1,14 +1,12 @@
 <!-- 报告列表的一项 -->
 <script setup lang="ts">
-import { useReportExport } from "@/libs/reportExport.lib";
 import { useAuthStore } from "@/stores/auth.store";
 import type { ReportBasic } from "@/types/report.types";
 import type { PropType } from "vue";
+// import { RouterLink } from "vue-router";
 
 // 用户权限store
 const authStore = useAuthStore();
-// 报告下载函数库
-const reportExport = useReportExport();
 
 defineProps({
   data: {
@@ -33,7 +31,10 @@ function onUpper(id: number) {
 <template>
   <a-card hoverable size="small">
     <template #title>
-      <a :href="data.tianheCloudUrl">
+      <RouterLink
+        class="title-link"
+        :to="{ name: 'ReportDetail', params: { id: data.id } }"
+      >
         <div class="title-link">
           <span class="text-black"> {{ serialNumber }}. </span>
           <span v-if="data.companyName" class="text-black"> {{ data.companyName }}</span>
@@ -44,11 +45,11 @@ function onUpper(id: number) {
           <span v-if="data.status == 1" class="text-black">&nbsp;&nbsp; 已完成</span>
           <span v-else class="text-black">&nbsp;&nbsp; 编写中</span>
         </div>
-      </a>
+      </RouterLink>
     </template>
 
     <template #extra>
-      <a :href="data.tianheCloudUrl">
+      <a :href="data.tianheCloudUrl" target="_blank">
         <a-button
           v-if="!data.tianheCloudId"
           type="primary"
@@ -60,7 +61,10 @@ function onUpper(id: number) {
       >
     </template>
 
-    <a :href="data.tianheCloudUrl">
+    <RouterLink
+      class="title-link"
+      :to="{ name: 'ReportDetail', params: { id: data.id } }"
+    >
       <div class="report-metadata-wrap">
         <p>
           <span>企业名称:</span><span>{{ data.companyName }}</span>
@@ -76,7 +80,7 @@ function onUpper(id: number) {
           <span>{{ item.value }}</span>
         </p>
       </div>
-    </a>
+    </RouterLink>
   </a-card>
 </template>
 

+ 0 - 4
src/views/report/reportComponent.types.ts

@@ -1,4 +0,0 @@
-export interface ReportTemplateModal {
-  templateName: string;
-  tags: string[];
-}