Catelog.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. <template>
  2. <div
  3. v-loading="loading"
  4. class="he_tree_content he_tree_view"
  5. style="font-size: 14px"
  6. >
  7. <!-- <el-button
  8. @click="dialogFlag = true"
  9. class="new-add"
  10. icon="el-icon-plus"
  11. size="mini"
  12. style="margin-bottom: 10px"
  13. type="primary"
  14. >添加节点</el-button>-->
  15. <span
  16. class="new-add"
  17. style="margin-bottom: 10px"
  18. @click="
  19. dialogFlag = true;
  20. (dialogTitle = '添加节点'), (dialogDisabled = false);
  21. "
  22. >
  23. <img src="../../../assets/common/icon-add.png" />添加节点
  24. </span>
  25. <Tree ref="tree" :value="treeData" @drag="drag" @drop="drop">
  26. <div
  27. slot-scope="{ node, index, path, tree }"
  28. :style="{
  29. paddingLeft:
  30. node.is_courseware === 'false'
  31. ? node.nodexIndex * 22 + 8 + 'px'
  32. : '0px',
  33. }"
  34. class="tree_box"
  35. >
  36. <span
  37. v-if="node.is_courseware === 'false'"
  38. class="el-icon_box"
  39. @click="tree.toggleFold(node, path)"
  40. >
  41. <template
  42. v-if="
  43. node.children && node.children.length > 0 && node.$folded == true
  44. "
  45. >
  46. <img src="../../../assets/common/icon-tree-arrow-right.png" />
  47. </template>
  48. <template
  49. v-else-if="
  50. node.children && node.children.length > 0 && node.$folded == false
  51. "
  52. >
  53. <img src="../../../assets/common/icon-tree-arrow-bottom.png" />
  54. </template>
  55. </span>
  56. <span
  57. v-if="node.is_courseware === 'false'"
  58. :class="[
  59. 'tree_box_item',
  60. 'tree_box_' + node.nodexIndex,
  61. activeIndex !== '' && JSON.stringify(node).indexOf(activeIndex) > -1
  62. ? 'tree_box_item_light'
  63. : '',
  64. ]"
  65. @click="tree.toggleFold(node, path)"
  66. >
  67. <!-- <template>
  68. <i class="el-icon-folder-add"></i>
  69. </template>-->
  70. {{ node.name }}
  71. </span>
  72. <span
  73. v-else
  74. :class="[
  75. 'tree_box_item',
  76. 'tree_box_leaf',
  77. activeIndex == node.id ? 'tree_box_item_active' : '',
  78. ]"
  79. :style="{ paddingLeft: node.nodexIndex * 22 + 12 + 'px' }"
  80. @click="handleNodeClick(node, path)"
  81. >
  82. <template>
  83. <i class="el-icon-document" />
  84. </template>
  85. {{ node.name }}
  86. </span>
  87. <div class="btn_box">
  88. <i
  89. v-if="node.is_courseware === 'false'"
  90. class="el-icon-plus"
  91. @click="addNode(node, index)"
  92. />
  93. <el-popover placement="bottom" trigger="click" width="100">
  94. <div class="tree_other_btn">
  95. <p class="tree_rename" @click="rename(node, index)">重命名</p>
  96. <p class="tree_del" @click="remove(node, index)">删除</p>
  97. <p class="tree_free" v-if="node.is_courseware === 'true'">
  98. <el-checkbox
  99. @change="tryFree(node, index)"
  100. v-model="node.is_free_trial_bool"
  101. >免费试看</el-checkbox
  102. >
  103. </p>
  104. </div>
  105. <span slot="reference">···</span>
  106. </el-popover>
  107. </div>
  108. </div>
  109. </Tree>
  110. <el-dialog :visible.sync="dialogFlag" :title="dialogTitle" width="40%">
  111. <div>
  112. <el-form ref="form" :model="formDialog" label-width="80px">
  113. <el-form-item label="节点名称">
  114. <el-input v-model="formDialog.name" />
  115. </el-form-item>
  116. <el-form-item label="节点类型">
  117. <el-radio-group
  118. v-model="formDialog.radio"
  119. :disabled="dialogDisabled"
  120. >
  121. <el-radio label="1">章节</el-radio>
  122. <el-radio label="2">互动课件</el-radio>
  123. </el-radio-group>
  124. </el-form-item>
  125. </el-form>
  126. </div>
  127. <span slot="footer" class="dialog-footer">
  128. <el-button @click="closeNode">取 消</el-button>
  129. <el-button type="primary" @click="sureAddNode">确 定</el-button>
  130. </span>
  131. </el-dialog>
  132. </div>
  133. </template>
  134. <script>
  135. import "he-tree-vue/dist/he-tree-vue.css";
  136. import { getContent } from "@/api/ajax";
  137. import Cookies from "js-cookie";
  138. import { Tree, Fold, Draggable } from "he-tree-vue";
  139. import * as hp from "helper-js";
  140. export default {
  141. components: {
  142. Tree: Tree.mixPlugins([Fold, Draggable]),
  143. },
  144. props: [
  145. "changeId",
  146. "emptyQustion",
  147. "bookLevel",
  148. "bookNodeIndex",
  149. "changeTreeData",
  150. ],
  151. data() {
  152. return {
  153. treeData: [],
  154. foldAllAfterMounted: true,
  155. dialogFlag: false,
  156. formDialog: {
  157. name: "",
  158. radio: "1",
  159. is_courseware: "false",
  160. children: [],
  161. },
  162. curNode: null,
  163. curIndex: "",
  164. curINdexArr: [],
  165. ondragend: {},
  166. bookId: this.$route.query.bookId,
  167. oldLists: [], // 移动之前的数组
  168. oldPid: "", // 移动节点的父id
  169. oldId: "", // 移动节点的id
  170. destId: "", // 目标节点id
  171. destPosition: 0, // 目标位置0移动节点在前1后
  172. is_coursewareFlag: "false", // 移动的是否是课件节点
  173. activeIndex: "", // 高亮节点
  174. isDragFlag: false, // 是否可以移动
  175. type: "",
  176. isAddFlag: true, // 是否可以添加节点
  177. dialogTitle: "添加节点",
  178. dialogDisabled: false, // 是否禁用单选按钮
  179. loading: false, // 加载中
  180. };
  181. },
  182. mounted() {
  183. this.getList();
  184. // this.$refs.tree.foldAllAfterMounted = true;
  185. // 绑定enter事件
  186. this.BookenterKeyup();
  187. },
  188. created() {
  189. if (Cookies.get("bookIndex")) {
  190. this.activeIndex = Cookies.get("bookIndex");
  191. }
  192. if (this.bookNodeIndex) this.activeIndex = this.bookNodeIndex;
  193. },
  194. destroyed() {
  195. // 销毁enter事件
  196. this.enterKeyupDestroyed();
  197. },
  198. methods: {
  199. foldAll() {
  200. if (this.$refs.tree !== undefined) {
  201. this.$refs.tree.foldAll();
  202. }
  203. },
  204. // 拖拽节点
  205. drag(store) {
  206. const data = store.dragNode;
  207. this.oldPid = data.pid;
  208. this.oldId = data.id;
  209. this.is_coursewareFlag = data.is_courseware;
  210. },
  211. // 拖拽完成后执行
  212. drop(store) {
  213. this.type = "拖拽";
  214. this.onTreeDataChange();
  215. },
  216. onTreeDataChange() {
  217. this.handleFindId(this.treeData);
  218. if (this.isDragFlag) {
  219. const MethodName = "book-book_manager-MoveTreeNode_BookChapterStruct";
  220. const data = {
  221. id: this.oldId,
  222. dest_position: this.destPosition,
  223. dest_id: this.destId,
  224. };
  225. // if (this.is_coursewareFlag == "false") {
  226. // MethodName = "book-chapter_manager-MoveChapter";
  227. // data.dest_chapter_id = this.destId;
  228. // } else {
  229. // MethodName = "book-courseware_manager-MoveCourseware";
  230. // data.dest_courseware_id = this.destId;
  231. // }
  232. this.loading = true;
  233. getContent(MethodName, data)
  234. .then((res) => {
  235. this.loading = false;
  236. this.$message.success("移动成功");
  237. this.getList();
  238. this.dialogFlag = false;
  239. this.formDialog.name = "";
  240. this.formDialog.radio = "1";
  241. this.parent_id = "";
  242. })
  243. .catch(() => {
  244. this.loading = false;
  245. });
  246. } else {
  247. this.$nextTick(() => {
  248. this.foldAll();
  249. });
  250. }
  251. },
  252. // 找目标id
  253. handleFindId(list) {
  254. list.forEach((item, index) => {
  255. if (item.id == this.oldId) {
  256. if (list.length === 1) {
  257. this.isDragFlag = false;
  258. // 移动后只有这一个节点 不对
  259. this.treeData = this.oldLists;
  260. this.changeTreeData(this.oldLists);
  261. this.$message({
  262. type: "warning",
  263. message: "不能跨级移动!",
  264. });
  265. return false;
  266. } else {
  267. // 匹配和兄弟节点pid是否相同
  268. if (list[index - 1]) {
  269. if (item.pid === list[index - 1].pid) {
  270. // 父级相同 同级移动 可向下移动
  271. this.destId = list[index - 1].id;
  272. this.destPosition = 1;
  273. this.isDragFlag = true;
  274. } else {
  275. // if (item.pid === 'undefined')
  276. this.treeData = this.oldLists;
  277. this.changeTreeData(this.oldLists);
  278. this.isDragFlag = false;
  279. this.$message({
  280. type: "warning",
  281. message: "不能跨级移动!",
  282. });
  283. return false;
  284. }
  285. } else if (list[index + 1]) {
  286. if (item.pid === list[index + 1].pid) {
  287. // 父级相同 同级移动 可向上移动
  288. this.destId = list[index + 1].id;
  289. this.destPosition = 0;
  290. this.isDragFlag = true;
  291. // this.treeData.lists = list;
  292. } else {
  293. this.treeData = this.oldLists;
  294. this.changeTreeData(this.oldLists);
  295. this.isDragFlag = false;
  296. this.$message({
  297. type: "warning",
  298. message: "不能跨级移动!",
  299. });
  300. return false;
  301. }
  302. }
  303. }
  304. } else {
  305. if (item.children) {
  306. this.handleFindId(item.children);
  307. }
  308. // else {
  309. // this.treeData.lists = this.treeData.oldLists
  310. // this.$message({
  311. // type: 'warning',
  312. // message: '不能移动!3'
  313. // })
  314. // return false
  315. // }
  316. }
  317. });
  318. },
  319. addNode(node, index) {
  320. this.type = "添加";
  321. this.curNode = node;
  322. this.curIndex = node.level_index + "-0";
  323. this.parent_id = node.id;
  324. this.dialogTitle = "添加节点";
  325. this.dialogDisabled = false;
  326. this.dialogFlag = true;
  327. },
  328. sureAddNode() {
  329. this.formDialog.name = this.formDialog.name.trim();
  330. if (this.formDialog.name !== "") {
  331. if (this.dialogTitle == "添加节点") {
  332. let data = {
  333. name: this.formDialog.name,
  334. book_id: this.bookId,
  335. parent_id: this.parent_id,
  336. };
  337. let MethodName = "book-chapter_manager-AddChapterToBook";
  338. if (this.formDialog.radio === "2") {
  339. MethodName = "book-courseware_manager-AddCoursewareToBook";
  340. data = {
  341. name: this.formDialog.name,
  342. book_id: this.bookId,
  343. chapter_id: this.parent_id ? this.parent_id : "",
  344. };
  345. }
  346. if (this.isAddFlag) {
  347. this.isAddFlag = false;
  348. this.loading = true;
  349. getContent(MethodName, data)
  350. .then((res) => {
  351. this.loading = false;
  352. this.isAddFlag = true;
  353. this.$message({
  354. message: "添加成功",
  355. type: "success",
  356. });
  357. this.getList();
  358. // let node = JSON.parse(JSON.stringify(this.formDialog));
  359. // this.curNode.children.push(node);
  360. this.dialogFlag = false;
  361. this.formDialog.name = "";
  362. this.formDialog.radio = "1";
  363. this.parent_id = "";
  364. })
  365. .catch((error) => {
  366. this.loading = false;
  367. this.isAddFlag = true;
  368. });
  369. }
  370. } else {
  371. let MethodName = "";
  372. if (this.formDialog.radio === "2") {
  373. // 课件节点
  374. MethodName = "book-courseware_manager-UpdateCourseware";
  375. } else {
  376. MethodName = "book-chapter_manager-UpdateChapter";
  377. }
  378. const datas = {
  379. id: this.oldId,
  380. name: this.formDialog.name,
  381. };
  382. if (this.isAddFlag) {
  383. this.isAddFlag = false;
  384. this.loading = true;
  385. getContent(MethodName, datas)
  386. .then((res) => {
  387. this.loading = false;
  388. this.isAddFlag = true;
  389. this.type = "修改";
  390. this.getList();
  391. this.$message({
  392. type: "success",
  393. message: "修改成功!",
  394. });
  395. // let node = JSON.parse(JSON.stringify(this.formDialog));
  396. // this.curNode.children.push(node);
  397. this.dialogFlag = false;
  398. this.formDialog.name = "";
  399. this.formDialog.radio = "1";
  400. this.parent_id = "";
  401. })
  402. .catch(() => {
  403. this.isAddFlag = true;
  404. this.loading = false;
  405. });
  406. }
  407. }
  408. } else {
  409. this.$message({
  410. message: "节点名称不能为空",
  411. type: "warning",
  412. });
  413. }
  414. },
  415. // 点取消按钮
  416. closeNode() {
  417. this.dialogFlag = false;
  418. this.formDialog.name = "";
  419. this.formDialog.radio = "1";
  420. this.parent_id = "";
  421. },
  422. getList() {
  423. const _this = this;
  424. const MethodName = "book-book_manager-GetBookChapterStruct";
  425. const data = {
  426. book_id: this.bookId,
  427. };
  428. this.loading = true;
  429. getContent(MethodName, data)
  430. .then((res) => {
  431. this.loading = false;
  432. this.handleData(res, 0);
  433. this.treeData = JSON.parse(JSON.stringify(res.nodes));
  434. this.oldLists = JSON.parse(JSON.stringify(res.nodes));
  435. this.changeTreeData(res.nodes);
  436. _this.$nextTick(() => {
  437. _this.foldAll();
  438. if (this.type) {
  439. this.curINdexArr = this.curIndex.split("-");
  440. this.handleFold(res, 0);
  441. this.treeData = JSON.parse(JSON.stringify(res.nodes));
  442. this.changeTreeData(res.nodes);
  443. } else if (this.bookLevel) {
  444. this.curINdexArr = this.bookLevel.split("-");
  445. this.handleFold(res, 0);
  446. this.treeData = JSON.parse(JSON.stringify(res.nodes));
  447. this.changeTreeData(res.nodes);
  448. }
  449. });
  450. })
  451. .catch((e) => {
  452. this.loading = false;
  453. });
  454. },
  455. // 递归
  456. handleData(data, nodeIndex) {
  457. if (data.nodes) {
  458. data.nodes.forEach((item) => {
  459. item.label = item.name;
  460. item.pid = data.id;
  461. item.fatherName = `${data.name} / ${item.name}`;
  462. item.nodexIndex = nodeIndex;
  463. item.$folded = true;
  464. item.is_free_trial_bool =
  465. item.is_free_trial === "true" ? true : false;
  466. if (item.nodes) {
  467. item.children = item.nodes;
  468. this.handleData(item, nodeIndex + 1);
  469. }
  470. });
  471. }
  472. },
  473. // 递归处理展开
  474. handleFold(data, index) {
  475. if (data.nodes) {
  476. data.nodes.forEach((item, indexs) => {
  477. if (
  478. this.curINdexArr[index] &&
  479. index !== this.curINdexArr.length - 1 &&
  480. this.curINdexArr[index] == indexs
  481. ) {
  482. if (item.nodes) {
  483. item.$folded = false;
  484. this.handleFold(item, index + 1);
  485. }
  486. }
  487. });
  488. }
  489. },
  490. handleNodeClick(data) {
  491. console.log(data);
  492. this.activeIndex = data.id;
  493. this.changeId(data.id, data.name, data.fatherName);
  494. },
  495. remove(data, index) {
  496. this.curIndex = data.level_index;
  497. const is_courseware = data.is_courseware;
  498. this.$confirm("确定要删除此节点吗?", "提示", {
  499. confirmButtonText: "确定",
  500. cancelButtonText: "取消",
  501. type: "warning",
  502. })
  503. .then(() => {
  504. let MethodName = "book-chapter_manager-DeleteChapter";
  505. if (is_courseware === "true") {
  506. // 课件节点
  507. MethodName = "book-courseware_manager-DeleteCourseware";
  508. }
  509. const datas = {
  510. id: data.id,
  511. };
  512. this.loading = true;
  513. getContent(MethodName, datas)
  514. .then((res) => {
  515. this.loading = false;
  516. this.type = "删除";
  517. this.getList();
  518. this.$message({
  519. type: "success",
  520. message: "删除成功!",
  521. });
  522. this.activeIndex = "";
  523. this.emptyQustion();
  524. })
  525. .catch(() => {
  526. this.loading = false;
  527. });
  528. })
  529. .catch(() => {});
  530. },
  531. // 重命名
  532. rename(data, index) {
  533. this.type = "修改";
  534. this.curNode = data;
  535. this.curIndex = data.level_index + "-0";
  536. this.parent_id = data.id;
  537. this.dialogTitle = "修改节点";
  538. this.dialogFlag = true;
  539. this.formDialog.name = data.name;
  540. this.dialogDisabled = true;
  541. const is_courseware = data.is_courseware;
  542. this.oldId = data.id;
  543. if (is_courseware === "true") {
  544. // 课件节点
  545. this.formDialog.radio = "2";
  546. } else {
  547. this.formDialog.radio = "1";
  548. }
  549. },
  550. //设置免费试看
  551. tryFree(data, index) {
  552. this.curIndex = data.level_index;
  553. let MethodName = "book-book_manager-SetTreeNodeMark_FreeTrial";
  554. let datas = {
  555. id: data.id,
  556. is_courseware: data.is_courseware,
  557. is_free_trial: JSON.stringify(data.is_free_trial_bool),
  558. };
  559. this.loading = true;
  560. getContent(MethodName, datas)
  561. .then((res) => {
  562. this.loading = false;
  563. this.type = "免费试看";
  564. this.getList();
  565. this.$message({
  566. type: "success",
  567. message: "设置成功!",
  568. });
  569. })
  570. .catch(() => {
  571. this.loading = false;
  572. });
  573. console.log(data);
  574. },
  575. BookenterKey(event) {
  576. const code = event.keyCode
  577. ? event.keyCode
  578. : event.which
  579. ? event.which
  580. : event.charCode;
  581. if (code == 13) {
  582. // this.sureAddNode();
  583. }
  584. },
  585. enterKeyupDestroyed() {
  586. document.removeEventListener("keyup", this.BookenterKey);
  587. },
  588. BookenterKeyup() {
  589. document.addEventListener("keyup", this.BookenterKey);
  590. },
  591. },
  592. };
  593. </script>
  594. <style lang="scss" scoped>
  595. .new-add {
  596. width: 116px;
  597. height: 36px;
  598. line-height: 36px;
  599. padding: 0 15px;
  600. display: flex;
  601. justify-content: center;
  602. align-items: center;
  603. background: #ff9900;
  604. border-radius: 4px;
  605. font-size: 14px;
  606. color: #ffffff;
  607. cursor: pointer;
  608. border: 0;
  609. margin-left: 8px;
  610. &:hover {
  611. opacity: 0.8;
  612. }
  613. img {
  614. width: 20px;
  615. margin-right: 4px;
  616. }
  617. }
  618. .new-add:hover {
  619. background: #ff8800;
  620. }
  621. .tree_box {
  622. display: flex;
  623. justify-content: flex-start;
  624. align-items: center;
  625. height: 40px;
  626. cursor: pointer;
  627. position: relative;
  628. .kuazhan {
  629. font-size: 40px;
  630. padding: 0 10px;
  631. cursor: pointer;
  632. }
  633. .btn_box {
  634. display: flex;
  635. justify-content: center;
  636. align-items: center;
  637. font-size: 30px;
  638. position: absolute;
  639. right: 5px;
  640. > span {
  641. margin-left: 10px;
  642. cursor: pointer;
  643. color: #2c2c2c;
  644. }
  645. }
  646. .tree_box_item {
  647. width: 100%;
  648. padding-left: 4px;
  649. color: #2c2c2c;
  650. height: 100%;
  651. align-items: center;
  652. line-height: 44px;
  653. padding-right: 60px;
  654. overflow: hidden;
  655. white-space: nowrap;
  656. text-overflow: ellipsis;
  657. font-weight: bold;
  658. }
  659. .tree_box_item:hover {
  660. color: #ff9900;
  661. > i {
  662. color: #ff9900;
  663. }
  664. }
  665. .tree_box_item_active,
  666. .tree_box_leaf:hover {
  667. color: #fff;
  668. background: #ff9900;
  669. > i {
  670. color: #fff;
  671. }
  672. }
  673. .tree_box_item_light {
  674. color: #ff9900;
  675. > i {
  676. color: #ff9900;
  677. }
  678. }
  679. .el-icon_box {
  680. width: 24px;
  681. text-align: center;
  682. font-size: 0;
  683. > img {
  684. width: 100%;
  685. margin-top: 4px;
  686. }
  687. }
  688. }
  689. .tree_other_btn {
  690. text-align: center;
  691. > p {
  692. font-size: 14px;
  693. cursor: pointer;
  694. }
  695. }
  696. </style>
  697. <style lang="scss">
  698. .he_tree_view .he-tree .tree-node {
  699. border: 0;
  700. padding: 0;
  701. margin: 0;
  702. }
  703. .he_tree_view {
  704. .tree-node-back {
  705. padding: 0 !important;
  706. }
  707. .tree_box_leaf {
  708. // padding-left: 80px !important;
  709. display: block;
  710. width: 340px !important;
  711. font-weight: normal !important;
  712. }
  713. }
  714. .he_tree_content {
  715. [class*=" el-icon-"],
  716. [class^="el-icon-"] {
  717. font-size: 14px;
  718. }
  719. .el-icon-plus {
  720. font-weight: bold;
  721. }
  722. .el-dialog__body {
  723. padding-right: 20px !important;
  724. }
  725. }
  726. </style>