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

添加查找参考文献展示层

Kevin Jiang лет назад: 3
Родитель
Сommit
32145866d5

+ 5 - 7
src/models/report.model.ts

@@ -1,12 +1,6 @@
-import { ChapterTypeLabels, type ChapterType } from "@/types/report.types";
+import { ChapterTypeLabels, type Chapter, type ChapterType } from "@/types/report.types";
 import Nzh from "nzh";
 
-export interface Chapter {
-  title: string;
-  fullTitle?: string;
-  content: string;
-}
-
 /**
  * 章节管理器
  */
@@ -31,6 +25,10 @@ export class ChapterManager {
     return this.chapters[index];
   }
 
+  public setChapter(index: number, data: Chapter) {
+    this.chapters[index] = data;
+  }
+
   public getChapters(): Chapter[] {
     return this.chapters.map((chapter, index) => {
       const fullTitle = Nzh.cn.encodeS(index+1) + '、' + chapter.title;

+ 6 - 0
src/types/report.types.ts

@@ -37,3 +37,9 @@ export const ChapterTypeLabels = [
     label: '自定义章节'
   },
 ]
+
+export interface Chapter {
+  title: string;
+  fullTitle?: string;
+  content: string;
+}

+ 0 - 6
src/views/history/history-component.type.ts

@@ -1,6 +0,0 @@
-export interface HistoryItem {
-  id: number;
-  label: string;
-  text: string;
-  link: string;
-}

+ 18 - 15
src/views/report/ReportCreateView.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ref, type Ref } from "vue";
+import { ref, onMounted, type Ref } from "vue";
 import { PlusOutlined } from "@ant-design/icons-vue";
 import PageHeader from '@/components/PageHeader.vue';
 import RelationCompanyInput from './components/report-create/RelationCompanyInput.vue';
@@ -7,24 +7,27 @@ import ReportCategory from './components/report-create/ReportCategory.vue';
 import DynamicMeta from './components/dynamic-meta/DynamicMeta.vue';
 import ChapterTypeSelect from "./components/ChapterTypeSelect.vue";
 import LogoComponent from "./components/LogoComponent.vue";
-import type { ChapterType } from "@/types/report.types";
+import type { Chapter, ChapterType } from "@/types/report.types";
 import { ChapterManager } from "@/models/report.model";
-import ContentEditor from "./components/ContentEditor.vue";
+import ChapterEditor from "./components/ChapterEditor.vue";
 
+// 报告元数据
 const metadata: Ref<any[]> = ref([]);
+// 章节类型选择器可见状态
+const chapterTypeSelectVisible = ref(false);
+// 章节管理对象
+const chapterManager = ref(new ChapterManager());
 
 function onMetaChange(data: any[]) {
   metadata.value = data;
-  console.log('metadata', metadata.value);
 }
 
-const chapterTypeSelectVisible = ref(false);
-
-const chapterManager = ref(new ChapterManager());
-
 function onChapterTypeSelected(type: ChapterType) {
   chapterManager.value.addChapter(type);
-  console.log('chapterManager', chapterManager);
+}
+
+function onChapterChange(index: number, data: Chapter) {
+  chapterManager.value.setChapter(index, data);
 }
 </script>
 
@@ -44,12 +47,12 @@ function onChapterTypeSelected(type: ChapterType) {
   </a-row>
   <a-divider />
   <div class="chapter-wrap">
-    <div v-for="(item, index) in chapterManager.getChapters()" :key="index">
-      <h1>{{ item.fullTitle }}</h1>
-      <p>
-        <ContentEditor />
-      </p>
-    </div>
+    <ChapterEditor
+      v-for="(item, index) in chapterManager.getChapters()"
+      :key="index"
+      :data="item"
+      @change="onChapterChange(index, $event)"
+    />
   </div>
   <div class="add-chapter-wrap">
     <a-button @click="() => chapterTypeSelectVisible = true">

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

@@ -0,0 +1,59 @@
+<!-- 章节编辑器 -->
+
+<script setup lang="ts">
+import { ref, type PropType } from 'vue';
+import type { Chapter } from '@/types/report.types';
+import ContentEditor from "../components/ContentEditor.vue";
+import ReferenceDraw from "../components/reference/ReferenceDraw.vue";
+
+const props = defineProps({
+  data: {
+    type: Object as PropType<Chapter>,
+    required: true
+  }
+});
+
+const referenceDraw = ref<InstanceType<typeof ReferenceDraw> | null>(null);
+
+const content = ref('');
+
+const emits = defineEmits<{
+  (e: "update:data", data: Chapter): void;
+  (e: "change", data: Chapter): void;
+}>();
+
+function onContentChange() {
+  console.log('parent content', content.value);
+  emits("change", {...props.data, content: content.value});
+}
+
+function search() {
+  referenceDraw.value?.show();
+}
+</script>
+
+<template>
+  <div class="chapter-item">
+    <h1>{{ data.fullTitle }}</h1>
+    <p>
+      <ContentEditor v-model:data="content" @change="onContentChange" />
+    </p>
+    <div class="chapter-button-group">
+      <a-space>
+        <a-button @click="search">查找参考文献</a-button>
+        <a-button>自动生成</a-button>
+      </a-space>
+    </div>
+  </div>
+  <ReferenceDraw ref="referenceDraw" />
+</template>
+
+<style scoped lang="scss">
+.chapter-item {
+  margin-top: 2em;
+
+  .chapter-button-group {
+    text-align: right;
+  }
+}
+</style>

+ 5 - 0
src/views/report/components/ChapterTypeSelect.vue

@@ -20,12 +20,17 @@ const category: Ref<ChapterType> = ref(ChapterType.UNKNOWN);
 function onOk() {
   emits("ok", category.value);
   emits("update:visible", false);
+  reset();
 }
 
 function onCancel() {
   emits("update:visible", false);
+  reset();
 }
 
+function reset() {
+  category.value = ChapterType.UNKNOWN;
+}
 </script>
 
 <template>

+ 52 - 17
src/views/report/components/ContentEditor.vue

@@ -1,8 +1,17 @@
 <script setup lang="ts">
-import { ref } from "vue";
-import Editor from '@tinymce/tinymce-vue'
+import { onMounted, ref } from "vue";
+import Editor from "@tinymce/tinymce-vue";
 
-const toolbar = ref([
+const props = defineProps({
+  data: {
+    type: String,
+    required: true,
+  }
+});
+
+const content = ref("");
+
+const toolbar = [
   'undo redo',
   // 'blocks',
   'bold italic',
@@ -11,9 +20,29 @@ const toolbar = ref([
   'table',
   'image',
   'fullscreen'
-]);
+];
+
+const editInit = {
+  language: 'zh_CN',
+  skin: 'small',
+  icons: 'small',
+  plugins: 'lists link image table code help wordcount fullscreen',
+  toolbar: toolbar.join(' | '),
+  menubar: false,
+  branding: false,
+  resize: true,
+  file_picker_types: 'image',
+  file_picker_callback: filePickerCallback,
+  height: 260,
+}
 
-function filePickerCallback(callback: any, value: any, meta: any) {
+/**
+ * TinyMCE选择文件回调处理函数
+ *
+ * 参考 https://www.tiny.cloud/docs/tinymce/6/file-image-upload/#file_picker_callback
+ * @param callback 回调函数
+ */
+function filePickerCallback(callback: any) {
   const input = document.createElement('input');
   input.setAttribute('type', 'file');
   input.setAttribute('accept', 'image/*');
@@ -30,22 +59,28 @@ function filePickerCallback(callback: any, value: any, meta: any) {
 
   input.click();
 }
+
+const emits = defineEmits<{
+  (e: "update:data", content: string): void;
+  (e: "change", content: string): void;
+}>();
+
+function onChange() {
+  emits("update:data", content.value);
+  emits("change", content.value);
+}
+
+onMounted(() => {
+  content.value = props.data;
+});
 </script>
 
 <template>
   <Editor
     api-key="ybn5lke39bgjhdehsamztepaxlch6nja25ekokjjav5nqdlq"
-    :init="{
-      language: 'zh_CN',
-      skin: 'small',
-      icons: 'small',
-      plugins: 'lists link image table code help wordcount fullscreen',
-      toolbar: toolbar.join(' | '),
-      menubar: false,
-      branding: false,
-      resize: false,
-      file_picker_types: 'image',
-      file_picker_callback: filePickerCallback,
-    }"
+    :init="editInit"
+    v-model="content"
+    output-format="html"
+    @change="onChange"
   />
 </template>

+ 62 - 0
src/views/report/components/reference/ReferenceDraw.vue

@@ -0,0 +1,62 @@
+<!-- 参考文献抽屉组件 -->
+
+<script setup lang="ts">
+import { ref, type Ref } from "vue";
+import ReferenceSearchBox from "./ReferenceSearchBox.vue";
+import ReferenceSearchResultItem from "./ReferenceSearchResultItem.vue";
+import type { RefereceSearchResultItem } from "./reference.type";
+
+const visible = ref<boolean>(false);
+
+const referenceSearchResult: Ref<RefereceSearchResultItem[]> = ref([
+  {
+    title: '标题',
+    author: '作者',
+    unit: '作者单位',
+    content: '内容'
+  },
+  {
+    title: '标题',
+    author: '作者',
+    unit: '作者单位',
+    content: '内容'
+  },
+]);
+
+const onClose = () => {
+  visible.value = false;
+};
+
+const show = () => {
+  visible.value = true;
+};
+
+defineExpose({
+  show
+});
+</script>
+
+<template>
+  <a-drawer
+    :width="'60%'"
+    title="检索参考文献"
+    placement="right"
+    :visible="visible"
+    :destroyOnClose="true"
+    @close="onClose"
+    class="reference-drawer"
+  >
+    <ReferenceSearchBox />
+    <a-divider />
+    <div v-for="(item, index) in referenceSearchResult" :key="index">
+      <ReferenceSearchResultItem :data="item" />
+      <a-divider class="result-item-divider" v-if="index < referenceSearchResult.length - 1" />
+    </div>
+  </a-drawer>
+</template>
+
+<style scoped lang="scss">
+.result-item-divider {
+  margin: 1.2em 0;
+}
+</style>

+ 32 - 0
src/views/report/components/reference/ReferenceSearchBox.vue

@@ -0,0 +1,32 @@
+<script setup lang="ts">
+import { ref } from "vue";
+
+const keyword = ref('');
+
+const emits = defineEmits<{
+  (e: "search", keyword: string): void;
+  (e: "searchWithIn", keyword: string): void;
+}>();
+
+function onSearch() {
+  emits("search", keyword.value);
+}
+
+function onSearchWithIn() {
+  emits("searchWithIn", keyword.value);
+}
+</script>
+
+<template>
+  <a-row type="flex" :gutter="8">
+    <a-col flex="1">
+      <a-input v-model:value="keyword" />
+    </a-col>
+    <a-col>
+      <a-space>
+        <a-button type="primary" @click="onSearch">重新检索</a-button>
+        <a-button @click="onSearchWithIn">在结果中检索</a-button>
+      </a-space>
+    </a-col>
+  </a-row>
+</template>

+ 40 - 0
src/views/report/components/reference/ReferenceSearchResultItem.vue

@@ -0,0 +1,40 @@
+<script setup lang="ts">
+import type { PropType } from "vue";
+import type { RefereceSearchResultItem } from "./reference.type";
+
+defineProps({
+  data: {
+    type: Object as PropType<RefereceSearchResultItem>,
+    required: true
+  }
+});
+</script>
+
+<template>
+  <h3>{{ data.title }}</h3>
+  <div class="meta">
+    <a-space>
+      <span>{{ data.author }}</span>
+      <span>{{ data.unit }}</span>
+    </a-space>
+  </div>
+  <div class="content">
+    {{ data.content }}
+  </div>
+  <div class="btn-group">
+    <a-radio-group>
+      <a-radio-button value="large">标记使用</a-radio-button>
+      <a-radio-button value="default">复制</a-radio-button>
+      <a-radio-button value="small">插入</a-radio-button>
+    </a-radio-group>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.content {
+  margin-top: 0.5em;
+}
+.btn-group {
+  margin-top: 0.8em;
+}
+</style>

+ 7 - 0
src/views/report/components/reference/reference.type.ts

@@ -0,0 +1,7 @@
+
+export interface RefereceSearchResultItem {
+  title: string;
+  author: string;
+  unit: string;
+  content: string;
+}