vue开发思维导图,节点关联视频课程,根据剧情选择播放节点视频。

前段时间在开发企业后台管理系统时接到一个需求,需求大致就是做一个互动课程,类似一个思维导图,每一个节点都可以关联一个视频课程,节点可以设置名称和关联的视频名称,当视频观看完根据用户所选择的节点播放不同的节点视频,我先放一张图片和一段视频可能大家更容易明白。

 

vue开发思维导图,节点添加视频课程,根据剧情播放视频

我大概梳理下需求要实现的功能:

  1. 左侧素材上传区域,实现可以将视频批量上传到阿里云OSS,上传过程中实时显示上传进度以及上传转态,可以对上传队列中的视频进行管理。
  2. 中间的画布区域是剧情的管理区域,类似思维导图,可对画布和当前节点进行操作:
  •  可为当前节点添加子节点,节点层级没有限制可无限添加,节点的名称可以修改,节点右键可选择删除。
  • 支持素材拖动至节点时,节点自动关联该视频,并使用视频的第一帧作为封面展示
  • 画布支持拖动,并可以按照一定比例进行放大缩小方便对剧情树的操作,画布支持一键归位功能。

    3. 右侧为节点的详细编辑区域,在画布中点击节点,弹出编辑层,可以设置当前节点以及关联素材的名称,可以重新选择当前节点管理的素材。同时也可以编辑当前节点的子节点的名称,以及拖动子节点进行顺序。

难点分析:在开发过程中左侧的视频批量上传功能是上传阿里云OSS,这个没什么难度,有一定难度的是中间的思维导图剧情数编辑。由于是能支持无限层级结构所以我们数据结构是NodeList数据结构

 

 这就要求我们我们封装一个自引用组件,只需要给NodeList Data 就可以将思维导图渲染出来。

封装画布组件  

           <content-map
            ref="contentMap"
            class="content-map"
            :data="dataList"
            :checking="checking"
            :zoomVal="zoomVal"
            @hideTransiton="hideTransiton"
            @showMenuCom="showMenuCom"
            @addOnePlot="addOnePlot"
            @mouseenterIn="mouseenterIn"
            @mouseenterOut="mouseenterOut"
            @clickPlot="clickPlot"
            @delOneModel="delOneModel"
            @checkingModel="checkModel"
            @clickPlotTitle="clickPlotTitle"
          />

content-map.vue

<template>
  <div class="my-chart " @click="showDelMemu = false">
    <div
      class="content-item"
      :class="{
        hasChildrens: data.Nodes && data.Nodes.length > 1,
        pl1: data.ParentID == 0,
        checkDisable: data.checkDisable,
        isJump: data.Type == 1,
      }"
      @mouseenter="mouseenter(data)"
      @mouseleave="mouseleave"
      @contextmenu.prevent="openMenu($event, data)"
    >
      <p
        class="title one-hang"
        v-if="data.ParentID != 0"
        @click="clickTitle(data)"
      >
        <!-- <span class="optionAD">{{ optionAD[index] }}:</span> -->
        <span class="one-hang data-name">{{
          data.OptionName ? _setOptionName(data.OptionName) : "点此编辑选项"
        }}</span>
      </p>
      <div class="content-left-all" @click.stop="clickItem($event, data)">
        <div class="content-left" v-if="data.CoverURL && data.PlotVideoID != 0">
          <img :src="data.CoverURL" />
        </div>
        <div class="tag-name one-hang">
          <el-tag
            size="small"
            :type="data.JumpPlotModuleID ? '' : 'info'"
            v-if="data.Type == 1"
            class="tage isJump one-hang"
            effect="dark"
          >
            {{
              data.ModuleName ? `跳转至:${data.ModuleName}` : "点击设置跳转内容"
            }}
          </el-tag>
          <el-tag
            size="small"
            v-if="data.Type == 2"
            class="tage"
            effect="dark"
            type="danger"
            >结局</el-tag
          >
          <span
            v-if="data.Type != 1"
            class="course-name"
            :class="{ lh60: data.Type == 0 }"
            >{{ data.ModuleName ? data.ModuleName : "拖拽素材至此" }}</span
          >
        </div>
      </div>

      <p class="icon-wrapper" v-if="data.Type == 0">
        <el-popover
          placement="right"
          v-model="plotVisible"
          :disabled="data.Nodes && data.Nodes.length >= 4"
        >
          <el-button-group class="">
            <el-button
              plain
              size="small"
              @click="addPlot(data, 0)"
              icon="el-icon-video-camera-solid"
              >创建剧情模块</el-button
            >
            <el-button plain size="small" @click="addPlot(data, 1)"
              ><i class="el-icon-s-promotion"></i>创建跳转模块</el-button
            >
          </el-button-group>
          <el-button
            size="mini"
            circle
            slot="reference"
            :icon="
              data.Nodes && data.Nodes.length > 1
                ? 'el-icon-share'
                : 'el-icon-plus'
            "
            :type="data.Nodes && data.Nodes.length > 1 ? 'primary' : ''"
            @click.stop="showMenu(data)"
          ></el-button>
        </el-popover>
      </p>
    </div>
    <transition-group
      tag="ul"
      name="list"
      class="my-chart-con"
      v-if="data.Nodes"
    >
      <li
        class="list-item"
        v-for="(item, index) in data.Nodes"
        :key="item.PlotModuleID"
        :class="{ onlyOne: data.Nodes.length == 1 }"
      >
        <content-item
          :data="item"
          :index="index"
          v-bind="$attrs"
          v-on="$listeners"
          @showMenuCom="openMenu"
        />
      </li>
    </transition-group>
  </div>
</template>

<script>
/**
 * @description 内容组件
 * @author lee TODO:注意本组件为循环套嵌组件,当子组件层大于2级时事件无法正常传递,解决方案在中间层组件上加上  v-bind="$attrs"  v-on="$listeners" ,父级正常监听
 */
import { BaseTip } from "@/api/baseConfig";
import _ from "lodash";
export default {
  name: "content-item",
  components: {},
  props: {
    data: {
      type: Object,
      defalut: {},
    },
    index: {
      type: Number,
      default: 0,
    },
    drag: { type: Boolean, defalut: false },
    checking: {
      type: Boolean,
      default: false,
    },
    zoomVal: {
      type: Number,
      default: 0,
    },
  },
  data() {
    return {
      optionAD: ["A", "B", "C", "D"],
      plotVisible: false,
      showDelMemu: false,
      styleMemu: {},
      Type: "",
      showDel: true,
    };
  },
  watch: {},
  computed: {},
  methods: {
    _setOptionName(name) {
      return _.truncate(name, {
        length: 16,
      });
    },
    showMenu(data) {
      if (data.Nodes.length > 3) {
        BaseTip(0, "一个模块下最多创建4个子模块!");
        this.plotVisible = false;
        this.visible = false;
        return;
      }
      this.visible = false;
    },
    /**
     * @description 在item上鼠标右键
     * @param {Object} e 事件对象
     * @param {Object} data 当前数据
     * @param {Boolean} isClick 是否是点击
     */
    openMenu(e, data, isClick = false) {
      if (this.checking) return;
      this.$emit("hideTransiton");
      if (this.zoomVal != 0) return;
      this.showDel = data.ParentID != 0;
      if (isClick) {
        this.showDel = false;
      }
      this.visible = false;
      Object.assign(this.styleMemu, {
        top: `${e.pageY - e.view.scrollY}px`,
        left: `${e.pageX}px`,
      });
      console.log();
      this.Type = data.Type;
      this.showDelMemu = true;
      this.$emit("showMenuCom", e, data);
    },
    // 点击了选择跳转模块
    checkModel() {
      this.showDelMemu = false;
      this.$emit("checkingModel");
    },
    // 鼠标移入事件
    mouseenter(item) {
      this.$emit("mouseenterIn", item);
    },
    // 鼠标移出事件
    mouseleave() {
      this.$emit("mouseenterOut", false);
    },
    // 点击外层div一个剧情
    clickItem(e, item) {
      if (item.Type == 1) {
        // 跳转模块点击要显示选择跳转得模块  不显示删除
        this.openMenu(e, item, true);
        return;
      }
      this.showDelMemu = false;
      this.$forceUpdate();
      this.$emit("clickPlot", item);
    },
    // 点击了剧情的title 需要修改上级
    clickTitle(item) {
      this.$emit("clickPlotTitle", item);
    },
    // 点击组件的一个剧情
    clickPlotItemCon() {
      this.$emit("clickPlot");
    },
    /**
     * @description  添加一个素材
     * @param {Object} item 要添加的对象
     * @param {Number} type 1 创建剧情模块 2 创建跳转模块
     */
    addPlot(item, type) {
      this.plotVisible = false;
      this.$emit("addOnePlot", item, type);
    },
    /**
     * @description 删除一个素材
     * @param {Number} type 1 清空模块 2 删除模块
     */
    delModel(type) {
      this.showDelMemu = false;
      const tip =
        type == 1
          ? "改操作将删除该素材以及对应剧情中的视频内容,是否继续?"
          : "改操作将删除所有素材与剧情树,是否继续?";
      this.$confirm(tip, type == 1 ? "清空" : "删除素材", {
        type: "warning",
      }).then((res) => {
        this.$emit("delOneModel", type);
      });
    },
    // 将数据排序
    sortList(item) {
      // console.log("sort----------------", item);
    },
  },
  created() {},
  mounted() {
    document.addEventListener("scroll", () => {
      this.showDelMemu = false;
    });
  },
};
</script>

至此用vue 开发类似一套流程图的功能就开发完了,于此配套使用的是2C的移动端,在移动端呈现的效果是这样的:

 当当前视频播放完毕后就会显示当前节点的子节点,选择不同的节点就播放不同的关联视频,这样这个流程就可以一直走下去了。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值