txw/docs/superpowers/plans/2026-04-28-平台文档管理功能实现计划.md

1122 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 平台文档管理功能实现计划
> **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** - 在当前会话中批量执行,带检查点审核
**选择哪种方式?**