Bläddra i källkod

Merge branch 'master' of http://gcls-git.helxsoft.cn/GCLS/eep_page

dusenyao 2 dagar sedan
förälder
incheckning
70e3e53df9

+ 28 - 1
src/api/book.js

@@ -162,4 +162,31 @@ export function MangerUpdateResourceInfo(data) {
  */
 export function MangerMoveResource(data) {
   return http.post(`${process.env.VUE_APP_EepServer}?MethodName=project_resource_manager-MoveResource`, data);
-}
+}
+
+/**
+ * @description 得到教材思维导图
+ * @param {object} data
+ */
+export function MangerGetBookMindMap(data) {
+  return http.post(`${process.env.VUE_APP_EepServer}?MethodName=book_content_manager-GetBookMindMap`, data);
+}
+
+/**
+ * @description 根据教材内容生成思维导图
+ * @param {object} data
+ */
+export function MangerGenerateMindMapByBookContent(data) {
+  return http.post(
+    `${process.env.VUE_APP_EepServer}?MethodName=book_content_manager-GenerateMindMapByBookContent`,
+    data,
+  );
+}
+
+/**
+ * @description 保存教材思维导图
+ * @param {object} data
+ */
+export function MangerSaveBookMindMap(data) {
+  return http.post(`${process.env.VUE_APP_EepServer}?MethodName=book_content_manager-SaveBookMindMap`, data);
+}

+ 6 - 0
src/router/modules/project.js

@@ -115,6 +115,12 @@ const projectPage = {
       name: 'ProjectManagePreview',
       component: () => import('@/views/project_manage/project/ProjectPreview.vue'),
     },
+    // 机构用户 -> 项目管理 -> 项目 -> 思维导图
+    {
+      path: 'project/mind_map/:projectId',
+      name: 'ProjectMindMap',
+      component: () => import('@/views/project_manage/project/ProjectMindMap.vue'),
+    },
     // 机构用户 -> 项目管理 -> 已上架教材
     {
       path: 'book',

+ 1 - 1
src/views/book/courseware/create/components/base/divider/Divider.vue

@@ -32,7 +32,7 @@ export default {
       return {
         margin: `${this.data.property.height / 2}px 0`,
         width: `${this.data.property.width}px`,
-        height: '10px',
+        height: '11px',
         color: `${this.data.property.color}`,
         letterSpacing: `${this.data.property.width}px`,
       };

+ 58 - 5
src/views/personal_workbench/project/ProductionResourceManage.vue

@@ -53,10 +53,13 @@
             :key="index"
             @click="selectSourceNode(item)"
             class="sources-item"
-            :class="[select_sources_id === item.id ? 'active' : '']"
+            :class="[
+              select_sources_id === item.id ? 'active' : '',
+              type_index === 5 ? 'sources-item-txt' : type_index === 3 ? 'sources-item-zip' : '',
+            ]"
           >
             <template v-if="type_index === 0"> <el-image :src="item.file_url" fit="contain"></el-image></template>
-            <template v-if="type_index === 1">
+            <template v-else-if="type_index === 1">
               <AudioLine
                 ref="audioLine"
                 :audio-id="'resource-audio-' + index"
@@ -65,9 +68,28 @@
                 :width="200"
               />
             </template>
-            <template v-if="type_index === 2">
+            <template v-else-if="type_index === 2">
               <video controls :src="item.file_url" width="100%" height="140px"></video>
             </template>
+            <template v-else-if="type_index === 3">
+              <iframe
+                class="sources-item-border"
+                :src="item.new_path"
+                width="100%"
+                height="300px"
+                frameborder="0"
+              ></iframe>
+            </template>
+            <template v-else-if="type_index === 4"> </template>
+            <template v-else-if="type_index === 5">
+              <iframe
+                class="sources-item-border"
+                :src="item.new_path"
+                width="100%"
+                height="300px"
+                frameborder="0"
+              ></iframe>
+            </template>
 
             <el-popover placement="bottom" width="300" trigger="hover">
               <div class="sources-info">
@@ -181,6 +203,9 @@ import PaginationPage from '@/components/PaginationPage.vue';
 import { PageQueryProjectResourceList } from '@/api/list';
 import UploadFile from './components/UploadFile.vue';
 import AudioLine from './components/AudioLine.vue';
+const Base64 = require('js-base64').Base64;
+import { getConfig } from '@/utils/auth';
+import { H5StartupFile } from '@/api/app';
 
 export default {
   name: 'ProjectResourceManager',
@@ -252,7 +277,7 @@ export default {
       file_id_list: [],
       file_list: [],
       loading: false,
-      acceptFileTypeList: ['.jpg,.png,.jpeg', '.mp3', '.mp4', '*', '*', '*'],
+      acceptFileTypeList: ['.jpg,.png,.jpeg', '.mp3', '.mp4', '.zip', '*', '.txt'],
       limit: 10,
       uploadTip: '',
       boxLoading: false,
@@ -264,6 +289,7 @@ export default {
       },
       sourceMoveFlag: false,
       sourceMoveId: '',
+      file_preview_url: getConfig() ? getConfig().doc_preview_service_address : '',
     };
   },
   created() {
@@ -404,6 +430,7 @@ export default {
     // 查询列表
     queryList(data) {
       this.boxLoading = true;
+      this.list = [];
       if (data) {
         this.page_capacity = data.page_capacity;
         this.cur_page = data.cur_page;
@@ -427,8 +454,22 @@ export default {
       };
       PageQueryProjectResourceList(datas)
         .then(({ total_count, resource_list }) => {
-          this.boxLoading = false;
           this.total = total_count;
+          if (this.type_index === 5) {
+            resource_list.forEach((item) => {
+              item.new_path = `${this.file_preview_url}onlinePreview?url=${Base64.encode(item.file_url)}`;
+            });
+            this.boxLoading = false;
+          } else if (this.type_index === 3) {
+            resource_list.forEach((item) => {
+              H5StartupFile({ file_id: item.file_id, index_file_name: 'index.html' }).then((res) => {
+                item.new_path = res.file_url;
+                this.boxLoading = false;
+              });
+            });
+          } else {
+            this.boxLoading = false;
+          }
           this.list = resource_list;
         })
         .catch(() => {
@@ -642,6 +683,18 @@ export default {
         width: 200px;
         cursor: pointer;
 
+        &-txt {
+          width: 400px;
+        }
+
+        &-zip {
+          width: 500px;
+        }
+
+        &-border {
+          border: 1px solid #ccc;
+        }
+
         .el-image {
           width: 100%;
           height: 140px;

+ 137 - 0
src/views/project_manage/project/ProjectMindMap.vue

@@ -0,0 +1,137 @@
+<template>
+  <div class="project-mind-map">
+    <ProjectMenu cur-key="project" />
+    <div class="project-mind-map__header">
+      <span class="name">{{ project_info.name }}</span>
+      <div class="courseware">
+        <div class="operator flex">
+          <span class="link" @click="getMangerGenerateMindMapByBookContent">根据教材内容生成思维导图</span>
+          <span class="link" @click="saveBookMindMap">保存</span>
+          <span class="link" @click="goBackBookList">返回项目列表</span>
+        </div>
+      </div>
+    </div>
+    <p v-if="isWaiting" class="waiting">数据生成中...请等待</p>
+    <MindMap v-if="isChildDataLoad" ref="mindMapRef" :project-id="project_id" :mind-map-json-data="mindMapJsonData" />
+  </div>
+</template>
+
+<script>
+import ProjectMenu from '@/views/project_manage/common/ProjectMenu.vue';
+import MindMap from './components/MindMap.vue';
+import { GetProjectBaseInfo } from '@/api/project';
+import { MangerGetBookMindMap, MangerGenerateMindMapByBookContent, MangerSaveBookMindMap } from '@/api/book';
+
+export default {
+  name: 'ProjectMindMap',
+  components: {
+    ProjectMenu,
+    MindMap,
+  },
+  data() {
+    return {
+      project_id: this.$route.params.projectId || '',
+      book_id: '',
+      project_info: {}, // 项目基本信息
+      isWaiting: false,
+      isChildDataLoad: false,
+      mindMapJsonData: {}, // 思维导图json数据
+    };
+  },
+  created() {
+    this.getProjectBaseInfo();
+  },
+  methods: {
+    goBackBookList() {
+      this.$router.push({ path: `/project_manage/project` });
+    },
+    /**
+     * 获取项目基本信息
+     * @param {string} id - 项目ID
+     */
+    getProjectBaseInfo() {
+      GetProjectBaseInfo({ id: this.project_id }).then(({ project_info, book_info_PBE }) => {
+        this.project_info = project_info;
+        this.book_id = book_info_PBE.id;
+        this.getMangerGetBookMindMap(this.book_id);
+      });
+    },
+    /**
+     * 得到教材思维导图
+     * @param {string} id - 教材ID
+     */
+    getMangerGetBookMindMap(id) {
+      MangerGetBookMindMap({ book_id: id }).then(({ content }) => {
+        if (content) {
+          this.mindMapJsonData = JSON.parse(content);
+          this.isChildDataLoad = true;
+        }
+      });
+    },
+    /**
+     * 得到教材思维导图
+     * @param {string} id - 教材ID
+     */
+    getMangerGenerateMindMapByBookContent() {
+      this.isChildDataLoad = false;
+      this.isWaiting = true;
+      MangerGenerateMindMapByBookContent({ book_id: this.book_id }).then(({ content }) => {
+        if (content) {
+          this.mindMapJsonData = JSON.parse(content) || {};
+          this.isWaiting = false;
+          this.isChildDataLoad = true;
+        }
+      });
+    },
+    /**
+     * 保存教材思维导图
+     */
+    saveBookMindMap() {
+      let content = this.$refs?.mindMapRef.saveData();
+      content = { root: content };
+      const loading = this.$loading({
+        lock: true,
+        text: '保存中...',
+        spinner: 'el-icon-loading',
+        background: 'rgba(0, 0, 0, 0.7)',
+      });
+      MangerSaveBookMindMap({
+        book_id: this.book_id,
+        content: JSON.stringify(content),
+      }).then(() => {
+        this.$message.success('保存成功');
+        loading.close();
+      });
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+@use '@/styles/mixin.scss' as *;
+
+.project-mind-map {
+  @include page-content(true);
+
+  &__header {
+    .name {
+      width: 240px;
+      font-size: 16px;
+      font-weight: bold;
+      border-right: $border;
+    }
+
+    .operator {
+      display: flex;
+      flex: 1;
+      justify-content: flex-end;
+    }
+  }
+
+  .waiting {
+    display: flex;
+    align-items: center; /* 垂直居中 */
+    justify-content: center; /* 水平居中 */
+    height: calc(100vh - 250px);
+  }
+}
+</style>

+ 252 - 0
src/views/project_manage/project/components/MindMap.vue

@@ -0,0 +1,252 @@
+<template>
+  <div class="mind-map-container">
+    <div class="toolbar">
+      <button @click="addParentNode">添加父节点</button>
+      <button @click="addNode">添加节点</button>
+      <button @click="addChildNode">添加子节点</button>
+      <button @click="removeNode">删除节点</button>
+      <button @click="forward">前进</button>
+      <button @click="back">回退</button>
+      <button @click="zoomIn">放大</button>
+      <button @click="zoomOut">缩小</button>
+      <button @click="resetZoom">重置缩放</button>
+      <button @click="exportToPNG">导出PNG</button>
+      <!-- <button @click="exportToSvg">导出SVG</button> -->
+      <!-- <button @click="exportToJson">导出JSON</button> -->
+      <!-- <button @click="importFromJson">导入JSON</button> -->
+      <!-- <button @click="saveData">保存数据</button> -->
+    </div>
+    <div ref="mindMapContainer" class="mind-map"></div>
+  </div>
+</template>
+
+<script>
+import MindMap from 'simple-mind-map';
+import Export from 'simple-mind-map/src/plugins/Export.js';
+MindMap.usePlugin(Export);
+
+export default {
+  name: 'MindMap',
+  props: {
+    projectId: {
+      type: String,
+      required: true,
+    },
+    mindMapJsonData: {
+      type: Object,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      mindMap: null,
+      activeNodes: [],
+      activeNodeIsRoot: false,
+      isStart: true,
+      isEnd: true,
+      scale: 1,
+      defaultData: {
+        data: {
+          uid: '001',
+          text: '中心主题',
+        },
+        children: [],
+      },
+    };
+  },
+  mounted() {
+    this.initMindMap();
+  },
+  beforeDestroy() {
+    this.destroyMindMap();
+  },
+  methods: {
+    initMindMap() {
+      let rootData = this.mindMapJsonData;
+      rootData = { data: rootData?.root || this.defaultData };
+      this.mindMap = new MindMap({
+        el: this.$refs.mindMapContainer,
+        mousewheelAction: 'zoom(放大缩小)、move(上下移动)', // zoom(放大缩小)、move(上下移动)
+        // 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px
+        mousewheelMoveStep: 100,
+        // 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点
+        mouseScaleCenterUseMousePosition: true,
+        // 当mousewheelAction设为zoom时,或者按住Ctrl键时,默认向前滚动是缩小,向后滚动是放大,如果该属性设为true,那么会反过来
+        mousewheelZoomActionReverse: true,
+        // 禁止鼠标滚轮缩放,你仍旧可以使用api进行缩放
+        disableMouseWheelZoom: false,
+        data: rootData.data,
+        // theme: { template: 'default' },
+        // theme: {
+        //   name: 'classic',
+        //   palette: ['#5B8FF9', '#5AD8A6', '#5D7092', '#F6BD16', '#E86452'],
+        //   bgColor: '#fff',
+        //   color: '#333',
+        //   root: {
+        //     fillColor: '#5B8FF9',
+        //   },
+        //   second: {
+        //     fillColor: '#5AD8A6',
+        //     color: '#fff',
+        //     borderColor: '#5AD8A6',
+        //     borderWidth: 1,
+        //     fontSize: 14,
+        //   },
+        // },
+      });
+      // this.mindMap.renderer.activeNodeList
+      // 激活节点
+      this.mindMap.on('node_active', (node, activeNodeList) => {
+        this.activeNodes = activeNodeList;
+        this.activeNodeIsRoot = activeNodeList.some((p) => p.isRoot);
+      });
+      this.mindMap.on('back_forward', (index, len) => {
+        this.isStart = index <= 0;
+        this.isEnd = index >= len - 1;
+      });
+      this.mindMap.view.translateX(-400);
+      this.mindMap.view.translateY(-50);
+    },
+    destroyMindMap() {
+      if (this.mindMap) {
+        this.mindMap.destroy();
+      }
+    },
+    addParentNode() {
+      if (!this.mindMap) return;
+      if (this.activeNodeIsRoot) {
+        this.$message.error('根节点不能添加父节点!');
+        return;
+      }
+      this.mindMap.execCommand('INSERT_PARENT_NODE');
+    },
+    addNode() {
+      if (!this.mindMap) return;
+      if (this.activeNodeIsRoot) {
+        this.$message.error('只能有一个根节点!');
+        return;
+      }
+      this.mindMap.execCommand(
+        'INSERT_NODE',
+        true,
+        [],
+        {
+          uid: Math.random() * 100000,
+          text: '请添加内容',
+        },
+        [
+          {
+            data: {
+              text: '下级节点',
+            },
+            children: [],
+          },
+        ],
+      );
+    },
+    addChildNode() {
+      if (!this.mindMap) return;
+      this.mindMap.execCommand('INSERT_CHILD_NODE');
+    },
+    removeNode() {
+      if (!this.mindMap) return;
+      this.mindMap.execCommand('REMOVE_NODE');
+    },
+    forward() {
+      this.mindMap.execCommand('FORWARD');
+    },
+    back() {
+      this.mindMap.execCommand('BACK');
+    },
+    zoomIn() {
+      if (!this.mindMap) return;
+      this.mindMap.view.setScale((this.scale += 0.1));
+    },
+    zoomOut() {
+      if (!this.mindMap) return;
+      this.mindMap.view.setScale((this.scale -= 0.1));
+    },
+    resetZoom() {
+      if (!this.mindMap) return;
+      this.mindMap.view.reset();
+    },
+    exportToPNG() {
+      if (!this.mindMap) return;
+      this.mindMap.export('png', true, 'mind-map.png');
+    },
+    exportToSvg() {
+      if (!this.mindMap) return;
+      this.mindMap.export('svg', true, 'mind-map.svg');
+    },
+    exportToPDF() {
+      if (!this.mindMap) return;
+      this.mindMap.export('pdf', true, 'mind-map.pdf');
+    },
+    exportToJson() {
+      if (!this.mindMap) return;
+      this.mindMap.export('json', true, 'mind-map.json');
+    },
+    importFromJson() {
+      const input = document.createElement('input');
+      input.type = 'file';
+      input.accept = '.json';
+      input.onchange = (e) => {
+        const file = e.target.files[0];
+        const reader = new FileReader();
+        reader.onload = (event) => {
+          try {
+            const data = JSON.parse(event.target.result);
+            this.destroyMindMap();
+            this.$nextTick(() => {
+              this.initMindMap();
+              this.$nextTick(() => {
+                this.mindMap.setData(data);
+              });
+            });
+          } catch (error) {
+            alert(`导入失败:${error.message}`);
+          }
+        };
+        reader.readAsText(file);
+      };
+      input.click();
+    },
+    saveData() {
+      // 获取完整数据
+      const data = this.mindMap.getData();
+      console.info('data', data);
+      return data;
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.mind-map-container {
+  .toolbar {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+
+    // justify-content: center;
+    margin-bottom: 6px;
+
+    button {
+      padding: 6px 12px;
+      color: #fff;
+      cursor: pointer;
+      background-color: #2d9aff;
+      border: none;
+      border-radius: 4px;
+    }
+
+    button:hover {
+      background-color: #165dff;
+    }
+  }
+
+  .mind-map {
+    height: calc(100vh - 204px);
+    background-color: #fff !important;
+  }
+}
+</style>

+ 5 - 1
src/views/project_manage/project/index.vue

@@ -23,10 +23,11 @@
         </el-table-column>
         <el-table-column prop="version_desc_YSJ" label="已上架教材版本" header-align="center" />
 
-        <el-table-column label="操作" fixed="right" width="240" align="center" header-align="center">
+        <el-table-column label="操作" fixed="right" width="320" align="center" header-align="center">
           <template slot-scope="{ row }">
             <span class="link">查看信息</span>
             <span class="link" @click="previewProject(row.id)">预览项目</span>
+            <span class="link" @click="viewMindMap(row.id)">思维导图</span>
             <span class="link">预览历史版本</span>
           </template>
         </el-table-column>
@@ -69,6 +70,9 @@ export default {
     previewProject(projectId) {
       this.$router.push({ path: `/project_manage/project/preview/${projectId}` });
     },
+    viewMindMap(projectId) {
+      this.$router.push({ path: `/project_manage/project/mind_map/${projectId}` });
+    },
   },
 };
 </script>