新增帮助文档管理

This commit is contained in:
六如
2025-06-15 15:27:43 +08:00
parent c18ee0dff9
commit 4d70c5167d
36 changed files with 2649 additions and 5 deletions

View File

@@ -0,0 +1,74 @@
package com.gitee.sop.website.dao.entity;
import com.gitee.fastmybatis.annotation.Column;
import com.gitee.fastmybatis.annotation.Pk;
import com.gitee.fastmybatis.annotation.PkStrategy;
import com.gitee.fastmybatis.annotation.Table;
import lombok.Data;
import java.time.LocalDateTime;
@Table(name = "help_doc", pk = @Pk(name = "id", strategy = PkStrategy.INCREMENT))
@Data
public class HelpDoc {
/**
* 主键ID唯一标识每条帮助文档记录
*/
private Long id;
/**
* 文档名称,显示用的标题或标签
*/
private String label;
/**
* 排序字段,用于控制文档在列表中的显示顺序
*/
private Integer sort;
@Column(logicDelete = true)
private Integer isDeleted;
/**
* 状态字段,表示文档是否启用
* 1启用2禁用
*/
private Byte status;
/**
* 内容字段,存储文档的具体内容
*/
private String content;
/**
* 内容类型,区分内容的格式
* 1Markdown2富文本
*/
private Byte contentType;
/**
* 父级ID用于构建文档的层级结构
*/
private Long parentId;
/**
* 添加时间,记录文档首次创建的时间
*/
private LocalDateTime addTime;
/**
* 更新时间,记录文档最后一次修改的时间
*/
private LocalDateTime updateTime;
/**
* 创建人ID记录创建该文档的用户ID
*/
private Long addBy;
/**
* 修改人ID记录最后一次修改该文档的用户ID
*/
private Long updateBy;
}

View File

@@ -0,0 +1,12 @@
package com.gitee.sop.website.dao.mapper;
import com.gitee.fastmybatis.core.mapper.BaseMapper;
import com.gitee.sop.website.dao.entity.HelpDoc;
import org.apache.ibatis.annotations.Mapper;
/**
* @author 六如
*/
@Mapper
public interface HelpDocMapper extends BaseMapper<HelpDoc> {
}

View File

@@ -0,0 +1,29 @@
package com.gitee.sop.website.service.common;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author 六如
*/
@AllArgsConstructor
@Getter
public enum StatusEnum {
NONE(0),
ENABLE(1),
DISABLE(2);
private final int value;
public static StatusEnum of(Number number) {
if (number == null) {
return NONE;
}
for (StatusEnum value : StatusEnum.values()) {
if (value.value == number.intValue()) {
return value;
}
}
return NONE;
}
}

View File

@@ -0,0 +1,30 @@
package com.gitee.sop.website.service.help;
import com.gitee.fastmybatis.core.support.LambdaService;
import com.gitee.sop.website.common.util.CopyUtil;
import com.gitee.sop.website.dao.entity.HelpDoc;
import com.gitee.sop.website.dao.mapper.HelpDocMapper;
import com.gitee.sop.website.service.common.StatusEnum;
import com.gitee.sop.website.service.help.dto.HelpDocDTO;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author 六如
*/
@Service
public class HelpDocService implements LambdaService<HelpDoc, HelpDocMapper> {
public List<HelpDocDTO> listTree() {
return this.query()
.select(HelpDoc::getId, HelpDoc::getLabel, HelpDoc::getParentId,
HelpDoc::getAddTime,
HelpDoc::getUpdateTime)
.eq(HelpDoc::getStatus, StatusEnum.ENABLE.getValue())
.orderByAsc(HelpDoc::getSort)
.list(data -> CopyUtil.copyBean(data, HelpDocDTO::new));
}
}

View File

@@ -0,0 +1,66 @@
package com.gitee.sop.website.service.help.dto;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class HelpDocDTO {
/**
* 主键ID唯一标识每条帮助文档记录
*/
private Long id;
/**
* 文档名称,显示用的标题或标签
*/
private String label;
/**
* 排序字段,用于控制文档在列表中的显示顺序
*/
private Integer sort;
/**
* 状态字段,表示文档是否启用
* 1启用2禁用
*/
private Byte status;
/**
* 内容字段,存储文档的具体内容
*/
private String content;
/**
* 内容类型,区分内容的格式
* 1Markdown2富文本
*/
private Byte contentType;
/**
* 父级ID用于构建文档的层级结构
*/
private Long parentId;
/**
* 添加时间,记录文档首次创建的时间
*/
private LocalDateTime addTime;
/**
* 更新时间,记录文档最后一次修改的时间
*/
private LocalDateTime updateTime;
/**
* 创建人ID记录创建该文档的用户ID
*/
private Long addBy;
/**
* 修改人ID记录最后一次修改该文档的用户ID
*/
private Long updateBy;
}

View File

@@ -0,0 +1,55 @@
package com.gitee.sop.website.controller.website;
import com.gitee.sop.website.common.resp.Result;
import com.gitee.sop.website.common.util.CopyUtil;
import com.gitee.sop.website.controller.website.vo.HelpDocVO;
import com.gitee.sop.website.dao.entity.HelpDoc;
import com.gitee.sop.website.service.common.StatusEnum;
import com.gitee.sop.website.service.help.HelpDocService;
import com.gitee.sop.website.service.help.dto.HelpDocDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Objects;
/**
* @author 六如
*/
@RestController
@RequestMapping("website/help/doc")
public class WebsiteHelpController {
@Autowired
private HelpDocService helpDocService;
/**
* 查询帮助文档树状结构
*
* @return
*/
@GetMapping("tree")
public Result<List<HelpDocVO>> listTree() {
List<HelpDocDTO> list = helpDocService.listTree();
List<HelpDocVO> retList = CopyUtil.copyList(list, HelpDocVO::new);
return Result.ok(retList);
}
/**
* 查询帮助文档
*
* @return
*/
@GetMapping("detail")
public Result<HelpDocVO> doc(Long id) {
HelpDoc helpDoc = helpDocService.getById(id);
if (Objects.equals(helpDoc.getStatus(), StatusEnum.DISABLE.getValue())) {
throw new RuntimeException("文档不存在");
}
HelpDocVO helpDocVO = CopyUtil.copyBean(helpDoc, HelpDocVO::new);
return Result.ok(helpDocVO);
}
}

View File

@@ -0,0 +1,66 @@
package com.gitee.sop.website.controller.website.vo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class HelpDocVO {
/**
* 主键ID唯一标识每条帮助文档记录
*/
private Long id;
/**
* 文档名称,显示用的标题或标签
*/
private String label;
/**
* 排序字段,用于控制文档在列表中的显示顺序
*/
private Integer sort;
/**
* 状态字段,表示文档是否启用
* 1启用2禁用
*/
private Byte status;
/**
* 内容字段,存储文档的具体内容
*/
private String content;
/**
* 内容类型,区分内容的格式
* 1Markdown2富文本
*/
private Byte contentType;
/**
* 父级ID用于构建文档的层级结构
*/
private Long parentId;
/**
* 添加时间,记录文档首次创建的时间
*/
private LocalDateTime addTime;
/**
* 更新时间,记录文档最后一次修改的时间
*/
private LocalDateTime updateTime;
/**
* 创建人ID记录创建该文档的用户ID
*/
private Long addBy;
/**
* 修改人ID记录最后一次修改该文档的用户ID
*/
private Long updateBy;
}

View File

@@ -40,6 +40,25 @@ const permissionRouter = {
]
};
const helpRouter = {
path: "/help",
name: "Help",
meta: {
title: "帮助",
icon: "ep:question",
rank: 10
},
children: [
{
path: "/help",
name: "Help",
meta: {
title: "帮助"
}
}
]
};
export default defineFakeRoute([
{
url: "/get-async-routes",
@@ -47,7 +66,7 @@ export default defineFakeRoute([
response: () => {
return {
success: true,
data: [permissionRouter]
data: [permissionRouter, helpRouter]
};
}
}

View File

@@ -0,0 +1,41 @@
import { createUrl, http } from "@/utils/http";
import type { Result } from "@/model";
// 后端请求接口
const apiUrl: any = createUrl({
listHelpTree: "/website/help/doc/tree",
getDoc: "/website/help/doc/detail"
});
export interface HelpDoc {
id: number;
label: string;
parentId: number;
content: string;
}
/**
* 接口管理
*/
export const api: any = {
/**
* 查询帮助树
* @param data
*/
listHelpTree(params: object) {
return http.get<Result<Array<HelpDoc>>, any>(apiUrl.listHelpTree, {
params
});
},
/**
* 文档详情
* @param data
*/
getDoc(id: any) {
return http.get<Result<any>, any>(apiUrl.getDoc, {
params: {
id
}
});
}
};

View File

@@ -0,0 +1,69 @@
<template>
<template v-for="item in rootItems" :key="item.id">
<el-sub-menu v-if="hasChildren(item.id)" :index="item.id.toString()">
<template #title>
<el-icon><Document /></el-icon>
<span>{{ item.label }}</span>
</template>
<template v-for="child in getChildren(item.id)" :key="child.id">
<el-sub-menu v-if="hasChildren(child.id)" :index="child.id.toString()">
<template #title>
<el-icon><Document /></el-icon>
<span>{{ child.label }}</span>
</template>
<el-menu-item
v-for="grandChild in getChildren(child.id)"
:key="grandChild.id"
:index="grandChild.id.toString()"
>
<el-icon><Document /></el-icon>
<span>{{ grandChild.label }}</span>
</el-menu-item>
</el-sub-menu>
<el-menu-item v-else :index="child.id.toString()">
<el-icon><Document /></el-icon>
<span>{{ child.label }}</span>
</el-menu-item>
</template>
</el-sub-menu>
<el-menu-item v-else :index="item.id.toString()">
<el-icon><Document /></el-icon>
<span>{{ item.label }}</span>
</el-menu-item>
</template>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from "vue";
import type { HelpDoc } from "@/api/help";
import { Document } from "@element-plus/icons-vue";
export default defineComponent({
name: "RecursiveMenu",
components: {
Document
},
props: {
items: {
type: Array as PropType<HelpDoc[]>,
required: true
}
},
setup(props) {
const rootItems = computed(() => {
return props.items.filter(item => item.parentId === 0);
});
const getChildren = (parentId: number) => {
return props.items.filter(item => item.parentId === parentId);
};
const hasChildren = (parentId: number) => {
return props.items.some(item => item.parentId === parentId);
};
return {
rootItems,
getChildren,
hasChildren
};
}
});
</script>

View File

@@ -0,0 +1,27 @@
import { api, type HelpDoc } from "@/api/help";
import { onMounted, ref } from "vue";
export function useHelp() {
const menuItems = ref<Array<HelpDoc>>([]);
const getMenuItems = async () => {
const res = await api.listHelpTree({});
// console.log(res);
menuItems.value = res.data;
};
const getDoc = async (id: any) => {
const res = await api.getDoc(id);
// console.log(res);
return res.data;
};
onMounted(async () => {
await getMenuItems();
});
return {
menuItems,
getDoc
};
}

View File

@@ -0,0 +1,51 @@
<template>
<el-container>
<el-aside width="200px">
<el-menu default-active="1" class="help-menu" @select="handleSelect">
<recursive-menu :items="menuItems" />
</el-menu>
</el-aside>
<el-main>
<div class="help-content">
<h2>{{ docInfo.label }}</h2>
<MarkdownEditor :value="docInfo.content" readonly />
</div>
</el-main>
</el-container>
</template>
<script setup lang="ts">
import { QuestionFilled } from "@element-plus/icons-vue";
import { useHelp } from "@/views/help/index";
import RecursiveMenu from "@/components/RecursiveMenu.vue";
import { ref } from "vue";
import MarkdownEditor from "@/components/MarkdownEditor";
const { menuItems, getDoc } = useHelp();
const docInfo = ref({
label: "",
content: ""
});
const handleSelect = async (key: string) => {
const doc = await getDoc(key);
docInfo.value.label = doc.label;
docInfo.value.content = doc.content;
console.log(docInfo.value);
};
</script>
<style scoped>
.help-menu {
height: 100%;
border-right: solid 1px var(--el-menu-border-color);
}
.el-aside {
background-color: var(--el-menu-bg-color);
}
.el-main {
padding: 20px;
}
</style>