1122 lines
32 KiB
Markdown
1122 lines
32 KiB
Markdown
# 平台文档管理功能实现计划
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** 实现平台文档管理功能,支持运营端文档 CRUD + 分类管理,门户端文档展示
|
||
|
||
**Architecture:** 在 `txw-yygl-service-biz` 中新增文档和分类的 DO/Mapper/Service/Controller,前端使用 Vue 2 + mavon-editor,门户端使用 marked 渲染 Markdown
|
||
|
||
**Tech Stack:** Spring Boot + MyBatis-Plus + Vue 2 + TDesign + mavon-editor + marked
|
||
|
||
---
|
||
|
||
## 文件结构总览
|
||
|
||
### 后端(txw-yygl-service-biz)
|
||
|
||
```
|
||
src/main/java/com/css/txw/yygl/
|
||
├── controller/
|
||
│ └── DocumentController.java # 文档 + 分类 API
|
||
├── service/
|
||
│ ├── DocumentService.java # 接口
|
||
│ └── impl/
|
||
│ └── DocumentServiceImpl.java # 实现
|
||
├── mapper/
|
||
│ └── DocumentMapper.java # MyBatis Mapper
|
||
├── pojo/
|
||
│ ├── domain/
|
||
│ │ ├── DocumentCategoryDO.java # 分类实体
|
||
│ │ └── DocumentDO.java # 文档实体
|
||
│ └── vo/
|
||
│ ├── DocumentListVO.java # 列表响应
|
||
│ └── DocumentDetailVO.java # 详情响应
|
||
└── resources/mapper/
|
||
└── DocumentMapper.xml # Mapper XML
|
||
```
|
||
|
||
### 前端(txw-yygl-web)
|
||
|
||
```
|
||
src/pages/document/
|
||
├── list/index.vue # 文档列表页
|
||
├── edit/index.vue # 新增/编辑页
|
||
└── category/index.vue # 分类管理页
|
||
```
|
||
|
||
### 前端(txw-mhzc-web)
|
||
|
||
```
|
||
src/pages/help/
|
||
├── index.vue # 文档中心首页
|
||
└── detail/index.vue # 文档详情页
|
||
```
|
||
|
||
---
|
||
|
||
## 第一阶段:数据库表创建
|
||
|
||
### Task 1: 创建数据库表
|
||
|
||
**SQL文件:** 直接在 `txw-mhzc` 数据库执行
|
||
|
||
```sql
|
||
-- 文档分类表
|
||
CREATE TABLE document_category (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||
name VARCHAR(100) NOT NULL COMMENT '分类名称',
|
||
sort INT DEFAULT 0 COMMENT '排序号',
|
||
status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用',
|
||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
|
||
) COMMENT '文档分类表';
|
||
|
||
-- 文档表
|
||
CREATE TABLE document (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||
category_id BIGINT NOT NULL COMMENT '分类ID',
|
||
title VARCHAR(200) NOT NULL COMMENT '文档标题',
|
||
content TEXT NOT NULL COMMENT 'Markdown正文内容',
|
||
status TINYINT DEFAULT 0 COMMENT '状态:0-草稿,1-已发布',
|
||
sort INT DEFAULT 0 COMMENT '排序号',
|
||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||
FOREIGN KEY (category_id) REFERENCES document_category(id)
|
||
) COMMENT '文档表';
|
||
```
|
||
|
||
- [ ] **Step 1: 在 txw-mhzc 数据库执行上述 SQL**
|
||
|
||
---
|
||
|
||
## 第二阶段:后端实现
|
||
|
||
### Task 2: 创建 DO 实体类
|
||
|
||
**文件:** `txw-yygl-service-biz/src/main/java/com/css/txw/yygl/pojo/domain/`
|
||
|
||
- [ ] **Step 1: 创建 DocumentCategoryDO.java**
|
||
|
||
```java
|
||
package com.css.txw.yygl.pojo.domain;
|
||
|
||
import com.baomidou.mybatisplus.annotation.IdType;
|
||
import com.baomidou.mybatisplus.annotation.TableField;
|
||
import com.baomidou.mybatisplus.annotation.TableId;
|
||
import com.baomidou.mybatisplus.annotation.TableName;
|
||
import java.io.Serializable;
|
||
import java.util.Date;
|
||
import lombok.Data;
|
||
|
||
@TableName(value = "document_category")
|
||
@Data
|
||
public class DocumentCategoryDO implements Serializable {
|
||
@TableId(value = "id", type = IdType.AUTO)
|
||
private Long id;
|
||
|
||
@TableField(value = "name")
|
||
private String name;
|
||
|
||
@TableField(value = "sort")
|
||
private Integer sort;
|
||
|
||
@TableField(value = "status")
|
||
private Integer status;
|
||
|
||
@TableField(value = "create_time")
|
||
private Date createTime;
|
||
|
||
@TableField(value = "update_time")
|
||
private Date updateTime;
|
||
|
||
@TableField(exist = false)
|
||
private static final long serialVersionUID = 1L;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 创建 DocumentDO.java**
|
||
|
||
```java
|
||
package com.css.txw.yygl.pojo.domain;
|
||
|
||
import com.baomidou.mybatisplus.annotation.IdType;
|
||
import com.baomidou.mybatisplus.annotation.TableField;
|
||
import com.baomidou.mybatisplus.annotation.TableId;
|
||
import com.baomidou.mybatisplus.annotation.TableName;
|
||
import java.io.Serializable;
|
||
import java.util.Date;
|
||
import lombok.Data;
|
||
|
||
@TableName(value = "document")
|
||
@Data
|
||
public class DocumentDO implements Serializable {
|
||
@TableId(value = "id", type = IdType.AUTO)
|
||
private Long id;
|
||
|
||
@TableField(value = "category_id")
|
||
private Long categoryId;
|
||
|
||
@TableField(value = "title")
|
||
private String title;
|
||
|
||
@TableField(value = "content")
|
||
private String content;
|
||
|
||
@TableField(value = "status")
|
||
private Integer status;
|
||
|
||
@TableField(value = "sort")
|
||
private Integer sort;
|
||
|
||
@TableField(value = "create_time")
|
||
private Date createTime;
|
||
|
||
@TableField(value = "update_time")
|
||
private Date updateTime;
|
||
|
||
@TableField(exist = false)
|
||
private static final long serialVersionUID = 1L;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: 创建 VO 类
|
||
|
||
**文件:** `txw-yygl-service-biz/src/main/java/com/css/txw/yygl/pojo/vo/`
|
||
|
||
- [ ] **Step 1: 创建 DocumentListVO.java**
|
||
|
||
```java
|
||
package com.css.txw.yygl.pojo.vo;
|
||
|
||
import lombok.Data;
|
||
import java.util.Date;
|
||
|
||
@Data
|
||
public class DocumentListVO {
|
||
private Long id;
|
||
private Long categoryId;
|
||
private String categoryName;
|
||
private String title;
|
||
private Integer status;
|
||
private Integer sort;
|
||
private Date createTime;
|
||
private Date updateTime;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 创建 DocumentDetailVO.java**
|
||
|
||
```java
|
||
package com.css.txw.yygl.pojo.vo;
|
||
|
||
import lombok.Data;
|
||
import java.util.Date;
|
||
|
||
@Data
|
||
public class DocumentDetailVO {
|
||
private Long id;
|
||
private Long categoryId;
|
||
private String categoryName;
|
||
private String title;
|
||
private String content;
|
||
private Integer status;
|
||
private Integer sort;
|
||
private Date createTime;
|
||
private Date updateTime;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: 创建 CategoryVO.java**
|
||
|
||
```java
|
||
package com.css.txw.yygl.pojo.vo;
|
||
|
||
import lombok.Data;
|
||
import java.util.Date;
|
||
|
||
@Data
|
||
public class CategoryVO {
|
||
private Long id;
|
||
private String name;
|
||
private Integer sort;
|
||
private Integer status;
|
||
private Date createTime;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4: 创建 Mapper 接口和 XML
|
||
|
||
**文件:** `txw-yygl-service-biz/src/main/java/com/css/txw/yygl/mapper/`
|
||
|
||
- [ ] **Step 1: 创建 DocumentCategoryMapper.java**
|
||
|
||
```java
|
||
package com.css.txw.yygl.mapper;
|
||
|
||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||
import com.css.txw.yygl.pojo.domain.DocumentCategoryDO;
|
||
import org.apache.ibatis.annotations.Mapper;
|
||
|
||
@Mapper
|
||
public interface DocumentCategoryMapper extends BaseMapper<DocumentCategoryDO> {
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 创建 DocumentMapper.java**
|
||
|
||
```java
|
||
package com.css.txw.yygl.mapper;
|
||
|
||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||
import com.css.txw.yygl.pojo.domain.DocumentDO;
|
||
import org.apache.ibatis.annotations.Mapper;
|
||
|
||
@Mapper
|
||
public interface DocumentMapper extends BaseMapper<DocumentDO> {
|
||
}
|
||
```
|
||
|
||
**文件:** `txw-yygl-service-biz/src/main/resources/mapper/`
|
||
|
||
- [ ] **Step 3: 创建 DocumentCategoryMapper.xml**
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||
<mapper namespace="com.css.txw.yygl.mapper.DocumentCategoryMapper">
|
||
<resultMap id="BaseResultMap" type="com.css.txw.yygl.pojo.domain.DocumentCategoryDO">
|
||
<id property="id" column="id" jdbcType="BIGINT"/>
|
||
<result property="name" column="name" jdbcType="VARCHAR"/>
|
||
<result property="sort" column="sort" jdbcType="INTEGER"/>
|
||
<result property="status" column="status" jdbcType="TINYINT"/>
|
||
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
|
||
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
|
||
</resultMap>
|
||
<sql id="Base_Column_List">id,name,sort,status,create_time,update_time</sql>
|
||
</mapper>
|
||
```
|
||
|
||
- [ ] **Step 4: 创建 DocumentMapper.xml**
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||
<mapper namespace="com.css.txw.yygl.mapper.DocumentMapper">
|
||
<resultMap id="BaseResultMap" type="com.css.txw.yygl.pojo.domain.DocumentDO">
|
||
<id property="id" column="id" jdbcType="BIGINT"/>
|
||
<result property="categoryId" column="category_id" jdbcType="BIGINT"/>
|
||
<result property="title" column="title" jdbcType="VARCHAR"/>
|
||
<result property="content" column="content" jdbcType="VARCHAR"/>
|
||
<result property="status" column="status" jdbcType="TINYINT"/>
|
||
<result property="sort" column="sort" jdbcType="INTEGER"/>
|
||
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
|
||
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
|
||
</resultMap>
|
||
<sql id="Base_Column_List">id,category_id,title,content,status,sort,create_time,update_time</sql>
|
||
</mapper>
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5: 创建 Service 层
|
||
|
||
**文件:** `txw-yygl-service-biz/src/main/java/com/css/txw/yygl/service/`
|
||
|
||
- [ ] **Step 1: 创建 DocumentService.java 接口**
|
||
|
||
```java
|
||
package com.css.txw.yygl.service;
|
||
|
||
import com.baomidou.mybatisplus.extension.service.IService;
|
||
import com.css.txw.yygl.pojo.domain.DocumentDO;
|
||
import com.css.txw.yygl.pojo.vo.DocumentDetailVO;
|
||
import com.css.txw.yygl.pojo.vo.DocumentListVO;
|
||
import java.util.List;
|
||
|
||
public interface DocumentService extends IService<DocumentDO> {
|
||
List<DocumentListVO> getDocumentList(Long categoryId, Integer status);
|
||
DocumentDetailVO getDocumentDetail(Long id);
|
||
void saveDocument(DocumentDO document);
|
||
void updateDocument(DocumentDO document);
|
||
void deleteDocument(Long id);
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 创建 DocumentServiceImpl.java 实现**
|
||
|
||
```java
|
||
package com.css.txw.yygl.service.impl;
|
||
|
||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||
import com.css.txw.yygl.mapper.DocumentCategoryMapper;
|
||
import com.css.txw.yygl.mapper.DocumentMapper;
|
||
import com.css.txw.yygl.pojo.domain.DocumentDO;
|
||
import com.css.txw.yygl.pojo.vo.DocumentDetailVO;
|
||
import com.css.txw.yygl.pojo.vo.DocumentListVO;
|
||
import com.css.txw.yygl.service.DocumentService;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.stereotype.Service;
|
||
import org.springframework.transaction.annotation.Transactional;
|
||
import java.util.List;
|
||
import java.util.stream.Collectors;
|
||
|
||
@Service
|
||
public class DocumentServiceImpl extends ServiceImpl<DocumentMapper, DocumentDO> implements DocumentService {
|
||
|
||
@Autowired
|
||
private DocumentCategoryMapper categoryMapper;
|
||
|
||
@Override
|
||
public List<DocumentListVO> getDocumentList(Long categoryId, Integer status) {
|
||
var wrapper = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<DocumentDO>();
|
||
if (categoryId != null) wrapper.eq(DocumentDO::getCategoryId, categoryId);
|
||
if (status != null) wrapper.eq(DocumentDO::getStatus, status);
|
||
wrapper.orderByAsc(DocumentDO::getSort).orderByDesc(DocumentDO::getCreateTime);
|
||
List<DocumentDO> list = this.list(wrapper);
|
||
return list.stream().map(doc -> {
|
||
DocumentListVO vo = new DocumentListVO();
|
||
vo.setId(doc.getId());
|
||
vo.setCategoryId(doc.getCategoryId());
|
||
vo.setTitle(doc.getTitle());
|
||
vo.setStatus(doc.getStatus());
|
||
vo.setSort(doc.getSort());
|
||
vo.setCreateTime(doc.getCreateTime());
|
||
vo.setUpdateTime(doc.getUpdateTime());
|
||
return vo;
|
||
}).collect(Collectors.toList());
|
||
}
|
||
|
||
@Override
|
||
public DocumentDetailVO getDocumentDetail(Long id) {
|
||
DocumentDO doc = this.getById(id);
|
||
if (doc == null) return null;
|
||
DocumentDetailVO vo = new DocumentDetailVO();
|
||
vo.setId(doc.getId());
|
||
vo.setCategoryId(doc.getCategoryId());
|
||
vo.setTitle(doc.getTitle());
|
||
vo.setContent(doc.getContent());
|
||
vo.setStatus(doc.getStatus());
|
||
vo.setSort(doc.getSort());
|
||
vo.setCreateTime(doc.getCreateTime());
|
||
vo.setUpdateTime(doc.getUpdateTime());
|
||
return vo;
|
||
}
|
||
|
||
@Transactional
|
||
@Override
|
||
public void saveDocument(DocumentDO document) {
|
||
this.save(document);
|
||
}
|
||
|
||
@Transactional
|
||
@Override
|
||
public void updateDocument(DocumentDO document) {
|
||
this.updateById(document);
|
||
}
|
||
|
||
@Transactional
|
||
@Override
|
||
public void deleteDocument(Long id) {
|
||
this.removeById(id);
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: 创建 DocumentCategoryService.java 接口**
|
||
|
||
```java
|
||
package com.css.txw.yygl.service;
|
||
|
||
import com.baomidou.mybatisplus.extension.service.IService;
|
||
import com.css.txw.yygl.pojo.domain.DocumentCategoryDO;
|
||
import com.css.txw.yygl.pojo.vo.CategoryVO;
|
||
import java.util.List;
|
||
|
||
public interface DocumentCategoryService extends IService<DocumentCategoryDO> {
|
||
List<CategoryVO> getCategoryList();
|
||
void saveCategory(DocumentCategoryDO category);
|
||
void updateCategory(DocumentCategoryDO category);
|
||
void deleteCategory(Long id);
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: 创建 DocumentCategoryServiceImpl.java 实现**
|
||
|
||
```java
|
||
package com.css.txw.yygl.service.impl;
|
||
|
||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||
import com.css.txw.yygl.mapper.DocumentCategoryMapper;
|
||
import com.css.txw.yygl.pojo.domain.DocumentCategoryDO;
|
||
import com.css.txw.yygl.pojo.vo.CategoryVO;
|
||
import com.css.txw.yygl.service.DocumentCategoryService;
|
||
import org.springframework.stereotype.Service;
|
||
import org.springframework.transaction.annotation.Transactional;
|
||
import java.util.List;
|
||
import java.util.stream.Collectors;
|
||
|
||
@Service
|
||
public class DocumentCategoryServiceImpl extends ServiceImpl<DocumentCategoryMapper, DocumentCategoryDO>
|
||
implements DocumentCategoryService {
|
||
|
||
@Override
|
||
public List<CategoryVO> getCategoryList() {
|
||
var wrapper = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<DocumentCategoryDO>();
|
||
wrapper.eq(DocumentCategoryDO::getStatus, 1);
|
||
wrapper.orderByAsc(DocumentCategoryDO::getSort);
|
||
List<DocumentCategoryDO> list = this.list(wrapper);
|
||
return list.stream().map(c -> {
|
||
CategoryVO vo = new CategoryVO();
|
||
vo.setId(c.getId());
|
||
vo.setName(c.getName());
|
||
vo.setSort(c.getSort());
|
||
vo.setStatus(c.getStatus());
|
||
vo.setCreateTime(c.getCreateTime());
|
||
return vo;
|
||
}).collect(Collectors.toList());
|
||
}
|
||
|
||
@Transactional
|
||
@Override
|
||
public void saveCategory(DocumentCategoryDO category) {
|
||
this.save(category);
|
||
}
|
||
|
||
@Transactional
|
||
@Override
|
||
public void updateCategory(DocumentCategoryDO category) {
|
||
this.updateById(category);
|
||
}
|
||
|
||
@Transactional
|
||
@Override
|
||
public void deleteCategory(Long id) {
|
||
this.removeById(id);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6: 创建 Controller
|
||
|
||
**文件:** `txw-yygl-service-biz/src/main/java/com/css/txw/yygl/controller/`
|
||
|
||
- [ ] **Step 1: 创建 DocumentController.java**
|
||
|
||
```java
|
||
package com.css.txw.yygl.controller;
|
||
|
||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||
import com.css.ggzc.framework.common.pojo.CommonResult;
|
||
import com.css.txw.yygl.pojo.domain.DocumentDO;
|
||
import com.css.txw.yygl.pojo.vo.DocumentDetailVO;
|
||
import com.css.txw.yygl.pojo.vo.DocumentListVO;
|
||
import com.css.txw.yygl.service.DocumentService;
|
||
import io.swagger.v3.oas.annotations.Operation;
|
||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||
import org.springframework.validation.annotation.Validated;
|
||
import org.springframework.web.bind.annotation.*;
|
||
import javax.annotation.Resource;
|
||
import java.util.List;
|
||
|
||
@RestController
|
||
@RequestMapping("/document")
|
||
@Tag(name = "文档管理")
|
||
@Validated
|
||
public class DocumentController {
|
||
|
||
@Resource
|
||
private DocumentService documentService;
|
||
|
||
@GetMapping("/list")
|
||
@Operation(summary = "文档列表")
|
||
public CommonResult<List<DocumentListVO>> getDocumentList(
|
||
@RequestParam(required = false) Long categoryId,
|
||
@RequestParam(required = false) Integer status) {
|
||
return CommonResult.success(documentService.getDocumentList(categoryId, status));
|
||
}
|
||
|
||
@GetMapping("/{id}")
|
||
@Operation(summary = "文档详情")
|
||
public CommonResult<DocumentDetailVO> getDocumentDetail(@PathVariable Long id) {
|
||
return CommonResult.success(documentService.getDocumentDetail(id));
|
||
}
|
||
|
||
@PostMapping
|
||
@Operation(summary = "新增文档")
|
||
public CommonResult<String> saveDocument(@RequestBody DocumentDO document) {
|
||
documentService.saveDocument(document);
|
||
return CommonResult.success("success");
|
||
}
|
||
|
||
@PutMapping("/{id}")
|
||
@Operation(summary = "编辑文档")
|
||
public CommonResult<String> updateDocument(@PathVariable Long id, @RequestBody DocumentDO document) {
|
||
document.setId(id);
|
||
documentService.updateDocument(document);
|
||
return CommonResult.success("success");
|
||
}
|
||
|
||
@DeleteMapping("/{id}")
|
||
@Operation(summary = "删除文档")
|
||
public CommonResult<String> deleteDocument(@PathVariable Long id) {
|
||
documentService.deleteDocument(id);
|
||
return CommonResult.success("success");
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: 创建 DocumentCategoryController.java**
|
||
|
||
```java
|
||
package com.css.txw.yygl.controller;
|
||
|
||
import com.css.ggzc.framework.common.pojo.CommonResult;
|
||
import com.css.txw.yygl.pojo.domain.DocumentCategoryDO;
|
||
import com.css.txw.yygl.pojo.vo.CategoryVO;
|
||
import com.css.txw.yygl.service.DocumentCategoryService;
|
||
import io.swagger.v3.oas.annotations.Operation;
|
||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||
import org.springframework.validation.annotation.Validated;
|
||
import org.springframework.web.bind.annotation.*;
|
||
import javax.annotation.Resource;
|
||
import java.util.List;
|
||
|
||
@RestController
|
||
@RequestMapping("/document/category")
|
||
@Tag(name = "文档分类管理")
|
||
@Validated
|
||
public class DocumentCategoryController {
|
||
|
||
@Resource
|
||
private DocumentCategoryService categoryService;
|
||
|
||
@GetMapping("/list")
|
||
@Operation(summary = "分类列表")
|
||
public CommonResult<List<CategoryVO>> getCategoryList() {
|
||
return CommonResult.success(categoryService.getCategoryList());
|
||
}
|
||
|
||
@PostMapping
|
||
@Operation(summary = "新增分类")
|
||
public CommonResult<String> saveCategory(@RequestBody DocumentCategoryDO category) {
|
||
categoryService.saveCategory(category);
|
||
return CommonResult.success("success");
|
||
}
|
||
|
||
@PutMapping("/{id}")
|
||
@Operation(summary = "编辑分类")
|
||
public CommonResult<String> updateCategory(@PathVariable Long id, @RequestBody DocumentCategoryDO category) {
|
||
category.setId(id);
|
||
categoryService.updateCategory(category);
|
||
return CommonResult.success("success");
|
||
}
|
||
|
||
@DeleteMapping("/{id}")
|
||
@Operation(summary = "删除分类")
|
||
public CommonResult<String> deleteCategory(@PathVariable Long id) {
|
||
categoryService.deleteCategory(id);
|
||
return CommonResult.success("success");
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 第三阶段:前端实现(txw-yygl-web)
|
||
|
||
### Task 7: 安装 Markdown 编辑器依赖
|
||
|
||
- [ ] **Step 1: 在 txw-yygl-web 安装 mavon-editor**
|
||
|
||
```bash
|
||
cd txw-yygl-web && npm install mavon-editor --save
|
||
```
|
||
|
||
---
|
||
|
||
### Task 8: 创建文档管理页面
|
||
|
||
**文件:** `txw-yygl-web/src/pages/document/list/index.vue`
|
||
|
||
- [ ] **Step 1: 创建文档列表页**
|
||
|
||
```vue
|
||
<template>
|
||
<div class="document-list-container">
|
||
<div class="filter-bar">
|
||
<t-select v-model="filters.categoryId" :options="categoryOptions" placeholder="选择分类" clearable />
|
||
<t-select v-model="filters.status" :options="statusOptions" placeholder="选择状态" clearable />
|
||
<t-button @click="loadDocumentList">查询</t-button>
|
||
<t-button theme="primary" @click="goToEdit">新建文档</t-button>
|
||
</div>
|
||
<t-table :data="documentList" :columns="columns" row-key="id">
|
||
<template #operations="{ row }">
|
||
<t-button size="small" @click="goToEdit(row.id)">编辑</t-button>
|
||
<t-button size="small" theme="danger" @click="deleteDocument(row.id)">删除</t-button>
|
||
</template>
|
||
</t-table>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'DocumentList',
|
||
data() {
|
||
return {
|
||
filters: { categoryId: null, status: null },
|
||
categoryOptions: [],
|
||
statusOptions: [
|
||
{ label: '草稿', value: 0 },
|
||
{ label: '已发布', value: 1 }
|
||
],
|
||
documentList: [],
|
||
columns: [
|
||
{ colKey: 'id', title: 'ID' },
|
||
{ colKey: 'title', title: '标题' },
|
||
{ colKey: 'categoryName', title: '分类' },
|
||
{ colKey: 'status', title: '状态', formatter: (v) => v === 1 ? '已发布' : '草稿' },
|
||
{ colKey: 'createTime', title: '创建时间' },
|
||
{ slotName: 'operations', title: '操作' }
|
||
]
|
||
}
|
||
},
|
||
mounted() {
|
||
this.loadCategoryList()
|
||
this.loadDocumentList()
|
||
},
|
||
methods: {
|
||
loadCategoryList() {
|
||
this.$request.get('/document/category/list').then(res => {
|
||
if (res.data.code === 0) {
|
||
this.categoryOptions = res.data.data.map(c => ({ label: c.name, value: c.id }))
|
||
}
|
||
})
|
||
},
|
||
loadDocumentList() {
|
||
this.$request.get('/document/list', { params: this.filters }).then(res => {
|
||
if (res.data.code === 0) {
|
||
this.documentList = res.data.data
|
||
}
|
||
})
|
||
},
|
||
goToEdit(id) {
|
||
this.$router.push(id ? `/document/edit/${id}` : '/document/edit')
|
||
},
|
||
deleteDocument(id) {
|
||
this.$request.delete(`/document/${id}`).then(res => {
|
||
if (res.data.code === 0) {
|
||
this.$message.success('删除成功')
|
||
this.loadDocumentList()
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
**文件:** `txw-yygl-web/src/pages/document/edit/index.vue`
|
||
|
||
- [ ] **Step 2: 创建文档编辑页(含 mavon-editor)**
|
||
|
||
```vue
|
||
<template>
|
||
<div class="document-edit-container">
|
||
<t-form :model="document" label-width="100px">
|
||
<t-form-item label="所属分类">
|
||
<t-select v-model="document.categoryId" :options="categoryOptions" placeholder="请选择分类" />
|
||
</t-form-item>
|
||
<t-form-item label="文档标题">
|
||
<t-input v-model="document.title" placeholder="请输入文档标题" />
|
||
</t-form-item>
|
||
<t-form-item label="排序号">
|
||
<t-input-number v-model="document.sort" :min="0" />
|
||
</t-form-item>
|
||
<t-form-item label="状态">
|
||
<t-radio-group v-model="document.status">
|
||
<t-radio :value="0">草稿</t-radio>
|
||
<t-radio :value="1">已发布</t-radio>
|
||
</t-radio-group>
|
||
</t-form-item>
|
||
<t-form-item label="文档内容">
|
||
<mavon-editor v-model="document.content" />
|
||
</t-form-item>
|
||
<t-form-item>
|
||
<t-button theme="primary" @click="saveDocument">保存</t-button>
|
||
<t-button @click="goBack">取消</t-button>
|
||
</t-form-item>
|
||
</t-form>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { mavonEditor } from 'mavon-editor'
|
||
import 'mavon-editor/dist/css/index.css'
|
||
|
||
export default {
|
||
name: 'DocumentEdit',
|
||
components: { mavonEditor },
|
||
data() {
|
||
return {
|
||
document: {
|
||
id: null,
|
||
categoryId: null,
|
||
title: '',
|
||
content: '',
|
||
status: 0,
|
||
sort: 0
|
||
},
|
||
categoryOptions: []
|
||
}
|
||
},
|
||
mounted() {
|
||
this.loadCategoryList()
|
||
const id = this.$route.params.id
|
||
if (id) {
|
||
this.loadDocument(id)
|
||
}
|
||
},
|
||
methods: {
|
||
loadCategoryList() {
|
||
this.$request.get('/document/category/list').then(res => {
|
||
if (res.data.code === 0) {
|
||
this.categoryOptions = res.data.data.map(c => ({ label: c.name, value: c.id }))
|
||
}
|
||
})
|
||
},
|
||
loadDocument(id) {
|
||
this.$request.get(`/document/${id}`).then(res => {
|
||
if (res.data.code === 0) {
|
||
this.document = res.data.data
|
||
}
|
||
})
|
||
},
|
||
saveDocument() {
|
||
const id = this.document.id
|
||
const promise = id
|
||
? this.$request.put(`/document/${id}`, this.document)
|
||
: this.$request.post('/document', this.document)
|
||
promise.then(res => {
|
||
if (res.data.code === 0) {
|
||
this.$message.success('保存成功')
|
||
this.goBack()
|
||
}
|
||
})
|
||
},
|
||
goBack() {
|
||
this.$router.back()
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
**文件:** `txw-yygl-web/src/pages/document/category/index.vue`
|
||
|
||
- [ ] **Step 3: 创建分类管理页**
|
||
|
||
```vue
|
||
<template>
|
||
<div class="category-manage-container">
|
||
<div class="toolbar">
|
||
<t-button theme="primary" @click="showAddDialog">新建分类</t-button>
|
||
</div>
|
||
<t-table :data="categoryList" :columns="columns" row-key="id">
|
||
<template #operations="{ row }">
|
||
<t-button size="small" @click="showEditDialog(row)">编辑</t-button>
|
||
<t-button size="small" theme="danger" @click="deleteCategory(row.id)">删除</t-button>
|
||
</template>
|
||
</t-table>
|
||
|
||
<t-dialog :visible="dialogVisible" @close="dialogVisible = false" :header="isEdit ? '编辑分类' : '新建分类'">
|
||
<t-form :model="category" label-width="80px">
|
||
<t-form-item label="分类名称">
|
||
<t-input v-model="category.name" />
|
||
</t-form-item>
|
||
<t-form-item label="排序号">
|
||
<t-input-number v-model="category.sort" :min="0" />
|
||
</t-form-item>
|
||
<t-form-item label="状态">
|
||
<t-radio-group v-model="category.status">
|
||
<t-radio :value="0">禁用</t-radio>
|
||
<t-radio :value="1">启用</t-radio>
|
||
</t-radio-group>
|
||
</t-form-item>
|
||
</t-form>
|
||
<template #footer>
|
||
<t-button @click="dialogVisible = false">取消</t-button>
|
||
<t-button theme="primary" @click="saveCategory">确定</t-button>
|
||
</template>
|
||
</t-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'CategoryManage',
|
||
data() {
|
||
return {
|
||
categoryList: [],
|
||
dialogVisible: false,
|
||
isEdit: false,
|
||
category: { id: null, name: '', sort: 0, status: 1 },
|
||
columns: [
|
||
{ colKey: 'id', title: 'ID' },
|
||
{ colKey: 'name', title: '分类名称' },
|
||
{ colKey: 'sort', title: '排序号' },
|
||
{ colKey: 'status', title: '状态', formatter: (v) => v === 1 ? '启用' : '禁用' },
|
||
{ slotName: 'operations', title: '操作' }
|
||
]
|
||
}
|
||
},
|
||
mounted() {
|
||
this.loadCategoryList()
|
||
},
|
||
methods: {
|
||
loadCategoryList() {
|
||
this.$request.get('/document/category/list').then(res => {
|
||
if (res.data.code === 0) {
|
||
this.categoryList = res.data.data
|
||
}
|
||
})
|
||
},
|
||
showAddDialog() {
|
||
this.isEdit = false
|
||
this.category = { id: null, name: '', sort: 0, status: 1 }
|
||
this.dialogVisible = true
|
||
},
|
||
showEditDialog(row) {
|
||
this.isEdit = true
|
||
this.category = { ...row }
|
||
this.dialogVisible = true
|
||
},
|
||
saveCategory() {
|
||
const promise = this.isEdit
|
||
? this.$request.put(`/document/category/${this.category.id}`, this.category)
|
||
: this.$request.post('/document/category', this.category)
|
||
promise.then(res => {
|
||
if (res.data.code === 0) {
|
||
this.$message.success('保存成功')
|
||
this.dialogVisible = false
|
||
this.loadCategoryList()
|
||
}
|
||
})
|
||
},
|
||
deleteCategory(id) {
|
||
this.$request.delete(`/document/category/${id}`).then(res => {
|
||
if (res.data.code === 0) {
|
||
this.$message.success('删除成功')
|
||
this.loadCategoryList()
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
---
|
||
|
||
## 第四阶段:前端实现(txw-mhzc-web)
|
||
|
||
### Task 9: 安装 Markdown 渲染依赖
|
||
|
||
- [ ] **Step 1: 在 txw-mhzc-web 安装 marked 和 highlight.js**
|
||
|
||
```bash
|
||
cd txw-mhzc-web && npm install marked highlight.js --save
|
||
```
|
||
|
||
---
|
||
|
||
### Task 10: 创建门户文档中心页面
|
||
|
||
**文件:** `txw-mhzc-web/src/pages/help/index.vue`
|
||
|
||
- [ ] **Step 1: 创建文档中心首页**
|
||
|
||
```vue
|
||
<template>
|
||
<div class="help-center">
|
||
<div class="category-list">
|
||
<div
|
||
v-for="cat in categoryList"
|
||
:key="cat.id"
|
||
:class="['category-item', { active: selectedCategoryId === cat.id }]"
|
||
@click="selectCategory(cat.id)"
|
||
>
|
||
{{ cat.name }}
|
||
</div>
|
||
</div>
|
||
<div class="document-list">
|
||
<div
|
||
v-for="doc in documentList"
|
||
:key="doc.id"
|
||
class="document-item"
|
||
@click="goToDetail(doc.id)"
|
||
>
|
||
<span class="doc-title">{{ doc.title }}</span>
|
||
<span class="doc-date">{{ doc.createTime }}</span>
|
||
</div>
|
||
<div v-if="documentList.length === 0" class="empty">暂无文档</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'HelpCenter',
|
||
data() {
|
||
return {
|
||
categoryList: [],
|
||
selectedCategoryId: null,
|
||
documentList: []
|
||
}
|
||
},
|
||
mounted() {
|
||
this.loadCategoryList()
|
||
this.loadDocumentList()
|
||
},
|
||
methods: {
|
||
loadCategoryList() {
|
||
this.$request.get('/document/category/list').then(res => {
|
||
if (res.data.code === 0) {
|
||
this.categoryList = res.data.data
|
||
}
|
||
})
|
||
},
|
||
loadDocumentList() {
|
||
this.$request.get('/document/list', { params: { status: 1 } }).then(res => {
|
||
if (res.data.code === 0) {
|
||
this.documentList = res.data.data
|
||
}
|
||
})
|
||
},
|
||
selectCategory(categoryId) {
|
||
this.selectedCategoryId = categoryId
|
||
this.$request.get('/document/list', { params: { categoryId, status: 1 } }).then(res => {
|
||
if (res.data.code === 0) {
|
||
this.documentList = res.data.data
|
||
}
|
||
})
|
||
},
|
||
goToDetail(id) {
|
||
this.$router.push(`/help/${id}`)
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
**文件:** `txw-mhzc-web/src/pages/help/detail/index.vue`
|
||
|
||
- [ ] **Step 2: 创建文档详情页(Markdown 渲染)**
|
||
|
||
```vue
|
||
<template>
|
||
<div class="document-detail">
|
||
<div class="back-btn" @click="goBack">返回</div>
|
||
<h1 class="doc-title">{{ document.title }}</h1>
|
||
<div class="doc-content" v-html="renderedContent"></div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import marked from 'marked'
|
||
import hljs from 'highlight.js'
|
||
import 'highlight.js/styles/github.css'
|
||
|
||
export default {
|
||
name: 'DocumentDetail',
|
||
data() {
|
||
return {
|
||
document: { id: null, title: '', content: '' }
|
||
}
|
||
},
|
||
computed: {
|
||
renderedContent() {
|
||
marked.setOptions({ highlight: (code, lang) => hljs.highlight(code, { language: lang }).value })
|
||
return marked(this.document.content || '')
|
||
}
|
||
},
|
||
mounted() {
|
||
const id = this.$route.params.id
|
||
if (id) {
|
||
this.loadDocument(id)
|
||
}
|
||
},
|
||
methods: {
|
||
loadDocument(id) {
|
||
this.$request.get(`/document/${id}`).then(res => {
|
||
if (res.data.code === 0) {
|
||
this.document = res.data.data
|
||
}
|
||
})
|
||
},
|
||
goBack() {
|
||
this.$router.back()
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
```
|
||
|
||
---
|
||
|
||
## 第五阶段:路由配置
|
||
|
||
### Task 11: 配置 yygl-web 路由
|
||
|
||
**文件:** `txw-yygl-web/src/core/router/`
|
||
|
||
- [ ] **Step 1: 在路由配置中添加文档管理路由**
|
||
|
||
在现有路由配置中新增:
|
||
```javascript
|
||
{
|
||
path: '/document',
|
||
component: () => import('@/pages/document'),
|
||
children: [
|
||
{ path: 'list', component: () => import('@/pages/document/list') },
|
||
{ path: 'edit/:id?', component: () => import('@/pages/document/edit') },
|
||
{ path: 'category', component: () => import('@/pages/document/category') }
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Task 12: 配置 mhzc-web 路由
|
||
|
||
**文件:** `txw-mhzc-web/src/core/router/`
|
||
|
||
- [ ] **Step 1: 在路由配置中添加文档中心路由**
|
||
|
||
```javascript
|
||
{
|
||
path: '/help',
|
||
component: () => import('@/pages/help'),
|
||
children: [
|
||
{ path: '', component: () => import('@/pages/help/index') },
|
||
{ path: ':id', component: () => import('@/pages/help/detail') }
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 执行方式
|
||
|
||
**Plan 完成。两种执行方式:**
|
||
|
||
1. **Subagent-Driven(推荐)** - 每 task 由独立子 agent 执行,任务间有审核
|
||
2. **Inline Execution** - 在当前会话中批量执行,带检查点审核
|
||
|
||
**选择哪种方式?**
|