|
@@ -3,7 +3,7 @@
|
|
|
<div class="breadcrumb">
|
|
|
<ul>
|
|
|
<li>
|
|
|
- <span>教材管理</span>
|
|
|
+ <span @click="$router.push('/')">教材管理</span>
|
|
|
</li>
|
|
|
<li>
|
|
|
<span class="separator">></span>
|
|
@@ -18,16 +18,17 @@
|
|
|
<div class="title">基本信息</div>
|
|
|
<el-button @click="edit"><SvgIcon icon-class="edit" /> 编辑</el-button>
|
|
|
</div>
|
|
|
- <el-image :src="data.cover_image_url" class="cover-image">
|
|
|
+ <el-image :src="data.picture_url" class="cover-image">
|
|
|
<div slot="error" class="image-slot">
|
|
|
<i class="el-icon-picture-outline"></i>
|
|
|
</div>
|
|
|
</el-image>
|
|
|
<div class="name">{{ data.name }}</div>
|
|
|
- <div class="brief-introduction">{{ data.brief_introduction }}</div>
|
|
|
+ <div class="brief-introduction">{{ data.description }}</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="catalogue-wrapper">
|
|
|
+ <div class="catalogue-wrapper" :style="{ backgroundColor: isEdit ? 'inherit' : '#fff' }">
|
|
|
+ <!-- 显示区域 -->
|
|
|
<template v-if="!isEdit">
|
|
|
<div class="catalogue-top">
|
|
|
<div class="title">目录</div>
|
|
@@ -38,88 +39,323 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div v-for="{ id, name, children } in catalogueList" :key="id" class="catalogue">
|
|
|
+ <div v-for="{ id, name, nodes: children } in nodes" :key="id" class="catalogue">
|
|
|
<div class="catalogue-title">{{ name }}</div>
|
|
|
<template v-for="item in children">
|
|
|
- <div :key="item.id" :class="['catalogue-item', item.type === 'content' ? 'content' : 'subdirectory']">
|
|
|
+ <div
|
|
|
+ :key="item.id"
|
|
|
+ :class="['catalogue-item', item.is_leaf_chapter === 'true' ? 'content' : 'subdirectory']"
|
|
|
+ >
|
|
|
<span class="name">{{ item.name }}</span>
|
|
|
<span class="time">{{ item.time }} {{ item.editor }}</span>
|
|
|
- <span class="edit" @click="editBookContent(item.id)">编辑</span>
|
|
|
+ <span
|
|
|
+ v-if="item.is_leaf === 'true' && item.is_leaf_chapter === 'true'"
|
|
|
+ class="edit"
|
|
|
+ @click="editBookContent(item.id)"
|
|
|
+ >编辑</span
|
|
|
+ >
|
|
|
</div>
|
|
|
- <div v-for="li in item.children" :key="li.id">
|
|
|
+ <div v-for="li in item.nodes" :key="li.id">
|
|
|
<div :class="['catalogue-item', 'children']">
|
|
|
- <span class="name">{{ li.name }}</span>
|
|
|
- <span class="time">{{ li.time }} {{ li.editor }}</span>
|
|
|
- <span class="edit" @click="editBookContent(li.id)">编辑</span>
|
|
|
+ <span class="name">{{ li?.name }}</span>
|
|
|
+ <span class="time">{{ li?.time }} {{ li.editor }}</span>
|
|
|
+ <span v-if="li.is_leaf_chapter === 'true'" class="edit" @click="editBookContent(li.id)">编辑</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
</div>
|
|
|
</template>
|
|
|
- <template v-else></template>
|
|
|
+ <!-- 编辑区域 -->
|
|
|
+ <div v-else class="catalogue-edit">
|
|
|
+ <div class="catalogue-edit-top">
|
|
|
+ <span class="title">编辑目录</span>
|
|
|
+ <div class="operation">
|
|
|
+ <el-button class="cancel" @click="isEdit = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="isEdit = false">完成</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="nodes">
|
|
|
+ <template v-for="item in nodes">
|
|
|
+ <!-- 一级目录 -->
|
|
|
+ <div v-if="item.id === curEditNodeId" :key="item.id" class="nodes-edit">
|
|
|
+ <el-input v-model="item.name" placeholder="请输入标题" />
|
|
|
+ <el-button type="primary" @click="confirmEditNode(item.id, '', 'false')">确定</el-button>
|
|
|
+ <el-button @click="cancelEditNode(item.id)">取消</el-button>
|
|
|
+ </div>
|
|
|
+ <div v-else :key="item.id" class="nodes-item">
|
|
|
+ <span class="title">{{ item.name }}</span>
|
|
|
+ <span class="operation">
|
|
|
+ <el-dropdown trigger="click">
|
|
|
+ <span class="el-dropdown-link">
|
|
|
+ <SvgIcon icon-class="add-rectangle" />
|
|
|
+ </span>
|
|
|
+ <el-dropdown-menu slot="dropdown">
|
|
|
+ <el-dropdown-item @click.native="createChildCatalogue(item.id, 'false')">子目录</el-dropdown-item>
|
|
|
+ <el-dropdown-item @click.native="createChildCatalogue(item.id, 'true')">内容</el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </el-dropdown>
|
|
|
+ <SvgIcon icon-class="edit" @click="setCurEditNodeId(item.id)" />
|
|
|
+ <SvgIcon icon-class="delete-2" @click="deleteChapter(item.id)" />
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 子目录及内容 -->
|
|
|
+ <template v-if="item.is_leaf_chapter === 'false' && item.nodes?.length > 0">
|
|
|
+ <template v-for="li in item.nodes">
|
|
|
+ <div v-if="li.id === curEditNodeId" :key="li.id" class="nodes-edit">
|
|
|
+ <el-input v-model="li.name" placeholder="请输入标题" />
|
|
|
+ <el-button type="primary" @click="confirmEditNode(li.id, item.id, li.is_leaf_chapter)">
|
|
|
+ 确定
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="cancelEditNode(li.id)">取消</el-button>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-else
|
|
|
+ :key="li.id"
|
|
|
+ :class="['nodes-item', 'children', li.is_leaf_chapter === 'true' ? '' : 'subdirectory']"
|
|
|
+ >
|
|
|
+ <span class="name">{{ li.name }}</span>
|
|
|
+ <span class="operation">
|
|
|
+ <el-dropdown trigger="click">
|
|
|
+ <span class="el-dropdown-link">
|
|
|
+ <SvgIcon icon-class="add-rectangle" />
|
|
|
+ </span>
|
|
|
+ <el-dropdown-menu slot="dropdown">
|
|
|
+ <el-dropdown-item
|
|
|
+ v-if="li.is_leaf_chapter === 'true'"
|
|
|
+ @click.native="createChildCatalogue(item.id, 'false')"
|
|
|
+ >子目录</el-dropdown-item
|
|
|
+ >
|
|
|
+ <el-dropdown-item
|
|
|
+ @click.native="
|
|
|
+ createChildCatalogue(li.is_leaf_chapter === 'true' ? item.id : li.id, 'true')
|
|
|
+ "
|
|
|
+ >内容</el-dropdown-item
|
|
|
+ >
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </el-dropdown>
|
|
|
+ <SvgIcon icon-class="edit" @click="setCurEditNodeId(li.id)" />
|
|
|
+ <SvgIcon icon-class="delete-2" @click="deleteChapter(li.id)" />
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template v-if="li.is_leaf_chapter === 'false' && li.nodes?.length > 0">
|
|
|
+ <template v-for="child in li.nodes">
|
|
|
+ <div v-if="child.id === curEditNodeId" :key="child.id" class="nodes-edit">
|
|
|
+ <el-input v-model="child.name" placeholder="请输入标题" />
|
|
|
+ <el-button type="primary" @click="confirmEditNode(child.id, li.id, child.is_leaf_chapter)">
|
|
|
+ 确定
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="cancelEditNode(child.id)">取消</el-button>
|
|
|
+ </div>
|
|
|
+ <div v-else :key="child.id" :class="['nodes-item', 'children']" :style="{ paddingLeft: '32px' }">
|
|
|
+ <span class="name">{{ child.name }}</span>
|
|
|
+ <span class="operation">
|
|
|
+ <el-dropdown trigger="click">
|
|
|
+ <span class="el-dropdown-link">
|
|
|
+ <SvgIcon icon-class="add-rectangle" />
|
|
|
+ </span>
|
|
|
+ <el-dropdown-menu slot="dropdown">
|
|
|
+ <el-dropdown-item
|
|
|
+ @click.native="
|
|
|
+ createChildCatalogue(child.is_leaf_chapter === 'true' ? li.id : child.id, 'true')
|
|
|
+ "
|
|
|
+ >内容</el-dropdown-item
|
|
|
+ >
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </el-dropdown>
|
|
|
+ <SvgIcon icon-class="edit" @click="setCurEditNodeId(child.id)" />
|
|
|
+ <SvgIcon icon-class="delete-2" @click="deleteChapter(child.id)" />
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-button type="primary" @click="createCatalogue"><SvgIcon icon-class="add-rectangle" /> 新建目录</el-button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</main>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
+import { GetBook, GetBookChapterStruct, AddChapterToBook, UpdateChapter, DeleteChapter } from '@/api/book';
|
|
|
+
|
|
|
export default {
|
|
|
name: 'SettingPage',
|
|
|
data() {
|
|
|
return {
|
|
|
- book_id: this.$route.params.id,
|
|
|
+ book_id: this.$route.params.book_id,
|
|
|
isEdit: false, // 是否编辑状态
|
|
|
+ curEditNodeId: '', // 当前编辑的节点id
|
|
|
data: {
|
|
|
- cover_image_url: '',
|
|
|
- image_id: '',
|
|
|
- name: '新实用汉语',
|
|
|
+ picture_url: '',
|
|
|
+ picture_id: '',
|
|
|
+ name: '',
|
|
|
author: '',
|
|
|
current_price: '',
|
|
|
original_price: '',
|
|
|
- tags: '',
|
|
|
- classify: '',
|
|
|
- brief_introduction: '123',
|
|
|
+ label_name_list: '',
|
|
|
+ type_id: '',
|
|
|
+ description: '',
|
|
|
},
|
|
|
- catalogueList: [
|
|
|
- {
|
|
|
- id: 1,
|
|
|
- name: '第一章',
|
|
|
- children: [
|
|
|
- {
|
|
|
- id: 2,
|
|
|
- type: 'subdirectory',
|
|
|
- name: '学习拼音',
|
|
|
- time: '2022/12/27 11:25',
|
|
|
- editor: '张三',
|
|
|
- children: [
|
|
|
- {
|
|
|
- id: 4,
|
|
|
- type: 'content',
|
|
|
- name: '第一节',
|
|
|
- time: '2022/12/27 11:25',
|
|
|
- editor: '张三',
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- id: 3,
|
|
|
- type: 'content',
|
|
|
- name: '认识笔画',
|
|
|
- time: '2022/12/27 11:25',
|
|
|
- editor: '张三',
|
|
|
- children: [],
|
|
|
- },
|
|
|
- ],
|
|
|
- },
|
|
|
- ],
|
|
|
+ nodes: [],
|
|
|
};
|
|
|
},
|
|
|
+ created() {
|
|
|
+ GetBook({ id: this.book_id }).then(({ name, picture_id, picture_url, author, type_id, price, description }) => {
|
|
|
+ this.data = {
|
|
|
+ name,
|
|
|
+ picture_id,
|
|
|
+ picture_url,
|
|
|
+ author,
|
|
|
+ type_id,
|
|
|
+ current_price: price,
|
|
|
+ original_price: '0.00',
|
|
|
+ label_name_list: [],
|
|
|
+ description,
|
|
|
+ };
|
|
|
+ });
|
|
|
+ this.getBookChapterStruct();
|
|
|
+ },
|
|
|
methods: {
|
|
|
edit() {
|
|
|
this.$router.push({ path: '/book/create', query: { id: this.book_id } });
|
|
|
},
|
|
|
- editBookContent(id) {
|
|
|
- this.$router.push({ path: '/chapter', query: { id } });
|
|
|
+ editBookContent(chapter_id) {
|
|
|
+ this.$router.push({ path: '/chapter', query: { chapter_id, book_id: this.book_id } });
|
|
|
+ },
|
|
|
+ getBookChapterStruct() {
|
|
|
+ GetBookChapterStruct({ book_id: this.book_id, node_deep_mode: 0 }).then(({ nodes }) => {
|
|
|
+ this.nodes = nodes ?? [];
|
|
|
+ });
|
|
|
+ },
|
|
|
+ confirmEditNode(id, parent_id, is_leaf_chapter) {
|
|
|
+ const position = this.findNodeIndexById(this.nodes, id);
|
|
|
+ let nodes = this.nodes;
|
|
|
+ for (let i = 0; i < position.length - 1; i++) {
|
|
|
+ nodes = nodes[position[i]].nodes;
|
|
|
+ }
|
|
|
+ const name = nodes[position[position.length - 1]].name;
|
|
|
+ if (name.length <= 0) {
|
|
|
+ this.$message.error('请输入名称');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (nodes[position[position.length - 1]].temporary) {
|
|
|
+ this.addChapterToBook(name, parent_id, is_leaf_chapter);
|
|
|
+ } else {
|
|
|
+ this.updateChapter(id, name);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ addChapterToBook(name, parent_id, is_leaf) {
|
|
|
+ AddChapterToBook({ name, book_id: this.book_id, parent_id, is_leaf }).then(() => {
|
|
|
+ this.getBookChapterStruct();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ updateChapter(id, name) {
|
|
|
+ UpdateChapter({ id, name }).then(() => {
|
|
|
+ this.getBookChapterStruct();
|
|
|
+ this.curEditNodeId = '';
|
|
|
+ });
|
|
|
+ },
|
|
|
+ deleteChapter(id) {
|
|
|
+ this.$confirm('确定删除该目录吗?', '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ DeleteChapter({ id, is_force_delete: 'true' }).then(() => {
|
|
|
+ this.$message.success('删除成功');
|
|
|
+ this.getBookChapterStruct();
|
|
|
+ });
|
|
|
+ })
|
|
|
+ .catch(() => {});
|
|
|
+ },
|
|
|
+ createCatalogue() {
|
|
|
+ let id = new Date().getTime();
|
|
|
+ const level_index = this.nodes.length;
|
|
|
+ this.nodes.push({
|
|
|
+ id,
|
|
|
+ name: '',
|
|
|
+ is_leaf_chapter: 'false',
|
|
|
+ level_index,
|
|
|
+ temporary: true, // 临时节点
|
|
|
+ nodes: [],
|
|
|
+ });
|
|
|
+ this.curEditNodeId = id;
|
|
|
+ },
|
|
|
+ createChildCatalogue(parent_id, is_leaf_chapter) {
|
|
|
+ let id = new Date().getTime();
|
|
|
+ let position = this.findNodeIndexById(this.nodes, parent_id);
|
|
|
+
|
|
|
+ let parent = this.nodes;
|
|
|
+ for (let i = 0; i < position.length; i++) {
|
|
|
+ if (i === position.length - 1) {
|
|
|
+ parent = parent[position[i]];
|
|
|
+ } else {
|
|
|
+ parent = parent[position[i]].nodes;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const level_index = position.join('-');
|
|
|
+ if (!Object.hasOwn(parent, 'nodes')) {
|
|
|
+ this.$set(parent, 'nodes', []);
|
|
|
+ }
|
|
|
+ parent.nodes.push({
|
|
|
+ id,
|
|
|
+ name: '',
|
|
|
+ is_leaf_chapter,
|
|
|
+ level_index,
|
|
|
+ temporary: true, // 临时节点
|
|
|
+ nodes: [],
|
|
|
+ });
|
|
|
+ this.curEditNodeId = id;
|
|
|
+ },
|
|
|
+ setCurEditNodeId(id) {
|
|
|
+ this.curEditNodeId = id;
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 根据节点id查找节点在nodes中的位置
|
|
|
+ * @param {array} nodes 节点数组
|
|
|
+ * @param {string} id 节点id
|
|
|
+ * @param {array} position 节点位置
|
|
|
+ */
|
|
|
+ findNodeIndexById(nodes, id, position = []) {
|
|
|
+ for (let i = 0; i < nodes.length; i++) {
|
|
|
+ const node = nodes[i];
|
|
|
+ if (node.id === id) {
|
|
|
+ position.push(i);
|
|
|
+ return position;
|
|
|
+ }
|
|
|
+ if (node.nodes && node.nodes.length > 0) {
|
|
|
+ const childPosition = this.findNodeIndexById(node.nodes, id, [...position, i]);
|
|
|
+ if (childPosition.length > 0) {
|
|
|
+ return childPosition;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return [];
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 取消编辑节点
|
|
|
+ * @param {string} id 节点id
|
|
|
+ */
|
|
|
+ cancelEditNode(id) {
|
|
|
+ const indexArr = this.findNodeIndexById(this.nodes, id);
|
|
|
+ let nodes = this.nodes;
|
|
|
+ for (let i = 0; i < indexArr.length - 1; i++) {
|
|
|
+ nodes = nodes[indexArr[i]].nodes;
|
|
|
+ }
|
|
|
+ // 删除临时节点
|
|
|
+ if (nodes[indexArr[indexArr.length - 1]].temporary) {
|
|
|
+ nodes.splice(indexArr[indexArr.length - 1], 1);
|
|
|
+ }
|
|
|
+ this.curEditNodeId = '';
|
|
|
},
|
|
|
},
|
|
|
};
|
|
@@ -233,7 +469,6 @@ export default {
|
|
|
row-gap: 16px;
|
|
|
width: 916px;
|
|
|
padding: 24px;
|
|
|
- background-color: #fff;
|
|
|
border-radius: 4px;
|
|
|
|
|
|
.catalogue-top {
|
|
@@ -271,10 +506,16 @@ export default {
|
|
|
font-size: 14px;
|
|
|
border-bottom: 1px solid #ebebeb;
|
|
|
|
|
|
- &.subdirectory {
|
|
|
+ &:hover {
|
|
|
background-color: #f3f3f3;
|
|
|
}
|
|
|
|
|
|
+ &.subdirectory {
|
|
|
+ .name {
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
&.children {
|
|
|
padding-left: 32px;
|
|
|
}
|
|
@@ -295,6 +536,95 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ .catalogue-edit {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ row-gap: 16px;
|
|
|
+
|
|
|
+ &-top {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+
|
|
|
+ .title {
|
|
|
+ font-weight: bold;
|
|
|
+ color: #000;
|
|
|
+ }
|
|
|
+
|
|
|
+ .operation {
|
|
|
+ .cancel {
|
|
|
+ background-color: #e7e7e7;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-button + .el-button {
|
|
|
+ margin-left: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .nodes {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ row-gap: 8px;
|
|
|
+
|
|
|
+ &-edit {
|
|
|
+ display: flex;
|
|
|
+ column-gap: 8px;
|
|
|
+
|
|
|
+ .el-button + .el-button {
|
|
|
+ margin-left: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-input {
|
|
|
+ :deep .el-input__inner {
|
|
|
+ background-color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding-right: 16px;
|
|
|
+
|
|
|
+ .title {
|
|
|
+ flex: 1;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #000;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.children {
|
|
|
+ padding: 8px 16px;
|
|
|
+ background-color: #fff;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background-color: #f3f3f3;
|
|
|
+ }
|
|
|
+
|
|
|
+ .name {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.subdirectory {
|
|
|
+ .name {
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .operation {
|
|
|
+ display: flex;
|
|
|
+ column-gap: 8px;
|
|
|
+
|
|
|
+ .svg-icon {
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|