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

32 KiB
Raw Blame History

平台文档管理功能实现计划

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 数据库执行

-- 文档分类表
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
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
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
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
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
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
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
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 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 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 接口
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 实现
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 接口
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 实现
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
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
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
cd txw-yygl-web && npm install mavon-editor --save

Task 8: 创建文档管理页面

文件: txw-yygl-web/src/pages/document/list/index.vue

  • Step 1: 创建文档列表页
<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
<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: 创建分类管理页
<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
cd txw-mhzc-web && npm install marked highlight.js --save

Task 10: 创建门户文档中心页面

文件: txw-mhzc-web/src/pages/help/index.vue

  • Step 1: 创建文档中心首页
<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 渲染)
<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: 在路由配置中添加文档管理路由

在现有路由配置中新增:

{
  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: 在路由配置中添加文档中心路由
{
  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 - 在当前会话中批量执行,带检查点审核

选择哪种方式?