feat: 文档维护功能

This commit is contained in:
liulujian 2026-04-29 15:29:41 +08:00
parent e54bd8af41
commit daeebcd1d1
41 changed files with 20018 additions and 63514 deletions

View File

@ -0,0 +1,40 @@
| 序号 | 解决方案 | 合作伙伴 | 服务内容 |
|------|----------|----------|----------|
| 1 | 绿色企业认定 | 联合征信 | 提供企业绿色属性评估认证服务,核查企业碳排放表现,颁发绿色企业资质证书 |
| 2 | 绿色项目认定 | 联合征信 | 对项目碳减排效果进行评估认证,核算项目产生的碳资产价值,出具绿色项目认定报告 |
| 3 | 中小企业环境信息披露 | 联合征信 | 协助企业完成环境信息合规披露编制ESG报告满足监管要求的碳排放数据上报 |
| 4 | 碳资产管理平台解决方案 | 零数科技 | 提供碳资产数字化管理服务,支持碳资产注册登记、交易撮合、结算清缴等全流程 |
| 5 | 碳交易最优策略模拟平台解决方案 | 零数科技 | 基于AI算法模拟碳交易策略辅助企业优化碳资产配置降低履约成本 |
| 6 | CCER 碳资产开发平台解决方案 | 零数科技 | 辅助开发核证自愿减排量(CCER)项目,追踪减排量签发流程,提供项目全周期管理 |
| 7 | CBAM 辅助核算系统 | 欧冶云商 | 依据欧盟CBAM法规要求辅助计算进口商品的隐含碳排放量生成合规填报数据 |
| 8 | 钢铁全产业链 EPD 平台 | 欧冶云商 | 提供钢铁产品环境产品声明(EPD)全流程服务,涵盖碳足迹计算、第三方审核及发布 |
| 9 | OYLCA 工具 | 欧冶云商 | 提供生命周期碳足迹(LCA)评估工具,支持企业计算产品全生命周期的碳排放数据 |
| 10 | 建筑节能降碳服务解决方案 | 时链科技 | 提供建筑能耗诊断、节能改造实施、碳减排量核算等一站式建筑降碳服务 |
| 11 | 基于医院建筑运行特征智能化碳评价 | 上海建工四建 | 针对医院能耗特性进行数据分析,建立碳排放模型,输出智能化碳评价报告 |
| 12 | 能碳智慧管家解决方案 | 上海电气 | 提供综合能源管理与碳排放监测平台,实时追踪企业碳足迹,智能优化用能策略 |
| 13 | ISO 14064-1 温室气体核查认证解决方案 | TUV 北德 | 提供组织层面温室气体排放核查认证服务出具国际认可的ISO 14064-1核查报告 |
| 14 | ISO 14067 产品碳足迹核查认证解决方案 | TUV 北德 | 核查认证产品碳足迹数据确保符合ISO 14067标准要求出具产品碳标签认证 |
| 15 | ISO 14068-1 碳中和核查认证解决方案 | TUV 北德 | 核查碳中和路径规划和抵消方案,认证组织或产品碳中和声明,出具权威核查证书 |
| 16 | ISO 14064 盘查 & 核查服务流程 | 必维认证 | 提供温室气体排放量盘查及第三方核查服务,指导建立碳排放清单,完成核查认证 |
| 17 | ISO 14067 产品碳足迹服务流程 | 必维认证 | 按照ISO 14067标准提供产品碳足迹核算服务覆盖全生命周期碳排放量化与验证 |
| 18 | ISO 14068 碳中和服务流程 | 必维认证 | 提供碳中和认证全流程服务,包括碳足迹核算、抵消方案评估及碳中和声明验证 |
| 19 | PAS 2080 碳管理体系服务流程 | 必维认证 | 提供碳管理体系建设咨询服务帮助企业建立符合PAS 2080标准的碳管理架构 |
| 20 | CBAM 填报 & 核验服务流程 | 必维认证 | 提供欧盟碳边境调节机制合规服务辅助完成CBAM数据填报、第三方核验及证书申请 |
| 21 | HiQLCD 数据库 + HiQEditor | 易碳数科 | 提供碳排放因子数据库及编辑工具,支持企业建立产品碳足迹模型,自动生成报告 |
| 22 | 积木 LCA 云 | 易碳数科 | 基于SaaS模式的生命周期评估云平台提供模块化碳核算工具支持多场景LCA分析 |
| 23 | 积木碳云 | 易碳数科 | 提供企业碳管理云服务,涵盖碳排放监测、减排项目管理、碳资产对接等功能 |
| 24 | 碳边境调节机制计算工具 CBAM TOOL | 易碳数科 | 依据欧盟CBAM法规提供在线碳排放计算工具自动换算隐含碳排放量生成合规报表 |
| 25 | 零碳园区解决方案 | AMT 企源 | 提供零碳园区规划咨询、智慧能源系统建设、碳中和路径设计等全流程解决方案 |
| 26 | 基于区块链技术的组织碳管理平台 | AMT 企源 | 利用区块链技术建立可信碳管理账本,实现组织碳排放数据的存证、溯源与共享 |
| 27 | 可持续供应链溯源解决方案 | AMT 企源 | 基于区块链追踪供应链碳排放数据,确保上游原材料碳足迹透明可视 |
| 28 | 基于区块链技术的能碳双控解决方案 | AMT 企源 | 结合区块链与能源管理系统,实现能耗与碳排放的协同管控,数据可信可追溯 |
| 29 | 区块链驱动的全生命周期碳足迹溯源 | AMT 企源 | 通过区块链技术记录产品全生命周期碳数据,实现碳足迹的可信溯源与验证 |
| 30 | 绿色产品生态设计平台 | AMT 企源 | 提供绿色产品生态设计工具,辅助企业进行低碳产品规划与生态化设计 |
| 31 | ESG 综合服务平台 | AMT 企源 | 提供ESG评级提升、报告编制、信息披露等一站式ESG管理服务 |
| 32 | CBAM 申报平台 | AMT 企源 | 提供CBAM数据采集、报表生成、在线申报等智能化申报服务 |
| 33 | 可再生能源溯源解决方案(源侧) | 中国电气装备 | 提供可再生能源发电碳减排量核算与溯源服务,追踪绿色电力来源与消纳 |
| 34 | 智能微电网自洽系统解决方案(网侧) | 中国电气装备 | 提供智能微电网能量管理解决方案,实现分布式能源的协调优化与碳排放控制 |
| 35 | 零碳园区解决方案(荷侧) | 中国电气装备 | 提供园区负荷侧节能改造、智慧能源管理、碳排放监测等荷侧综合解决方案 |
| 36 | 电化学储能解决方案(储侧) | 中国电气装备 | 提供储能系统建设与运维服务,通过移峰填谷优化碳排放结构 |
| 37 | 碳数据库解决方案(装备侧) | 中国电气装备 | 提供能源装备碳排放数据库建设服务,支撑企业建立设备级碳排放计量体系 |
| 38 | 虚拟电厂场景解决方案(调控侧) | 中国电气装备 | 提供虚拟电厂运营管理平台,聚合分布式能源参与碳减排交易,优化调控策略 |

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,253 @@
# 平台文档管理功能设计方案
## 1. 需求概述
### 1.1 功能目标
- 运营管理人员通过 `txw-yygl-web` 管理平台文档(增删改查)
- 门户网站 `txw-mhzc-web` 通过 API 获取文档列表和内容,前端渲染 Markdown
- 支持 Markdown 富文本编辑
- 文档按分类管理,支持通过 URL 直接跳转至对应文档
### 1.2 关键需求点
- 运营管理端:文档 CRUD + 分类管理
- 门户前端:文档列表 + 文档详情页(支持 URL 直接访问)
- 数据存储:`txw-mhzc` MySQL 数据库
---
## 2. 系统架构
### 2.1 整体架构
```
┌─────────────────────────────────────────────────────────────┐
│ 运营管理端 │
│ txw-yygl-web ←→ txw-yygl-service-biz ←→ txw-mhzc MySQL │
│ (内部调用) (新建 document 库) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 门户前端 │
│ txw-mhzc-web ←→ txw-gateway聚合层 ←→ 同一 API │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 技术选型
- 后端框架Spring Boot复用现有 `txw-yygl-service-biz`
- 前端框架Vue 2 + TDesign复用现有技术栈
- Markdown 编辑器:`mavon-editor`轻量、Vue 2 兼容)
- Markdown 渲染:`marked` + `highlight.js`
- 数据库MySQL`txw-mhzc` 库)
---
## 3. 数据库设计
### 3.1 文档分类表document_category
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | bigint | PK, AUTO_INCREMENT | 主键 |
| name | varchar(100) | NOT NULL | 分类名称 |
| sort | int | DEFAULT 0 | 排序号,越小越靠前 |
| status | tinyint | DEFAULT 1 | 状态0-禁用1-启用 |
| create_time | datetime | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| update_time | datetime | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
### 3.2 文档表document
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | bigint | PK, AUTO_INCREMENT | 主键 |
| category_id | bigint | NOT NULL, FK | 所属分类ID |
| title | varchar(200) | NOT NULL | 文档标题 |
| content | text | NOT NULL | Markdown 正文内容 |
| status | tinyint | DEFAULT 0 | 状态0-草稿1-已发布 |
| sort | int | DEFAULT 0 | 排序号,越小越靠前 |
| create_time | datetime | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| update_time | datetime | ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
---
## 4. API 设计
### 4.1 文档分类接口
| 接口 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 分类列表 | GET | /document/category/list | 获取所有启用的分类 |
| 新增分类 | POST | /document/category | 新增文档分类 |
| 编辑分类 | PUT | /document/category/{id} | 编辑分类信息 |
| 删除分类 | DELETE | /document/category/{id} | 删除分类(需检查无关联文档)|
### 4.2 文档接口
| 接口 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 文档列表 | GET | /document/list | 获取文档列表(支持分类筛选、状态筛选)|
| 文档详情 | GET | /document/{id} | 获取文档详情Markdown 内容)|
| 新增文档 | POST | /document | 新增文档 |
| 编辑文档 | PUT | /document/{id} | 编辑文档 |
| 删除文档 | DELETE | /document/{id} | 删除文档 |
### 4.3 响应数据结构
**文档列表项(不含正文)**
```json
{
"id": 1,
"categoryId": 1,
"categoryName": "使用指南",
"title": "如何注册账号",
"status": 1,
"sort": 1,
"createTime": "2026-04-28 10:00:00",
"updateTime": "2026-04-28 10:00:00"
}
```
**文档详情(含正文)**
```json
{
"id": 1,
"categoryId": 1,
"categoryName": "使用指南",
"title": "如何注册账号",
"content": "# 注册流程\n\n1. 点击注册按钮\n2. 填写企业信息\n...",
"status": 1,
"sort": 1,
"createTime": "2026-04-28 10:00:00",
"updateTime": "2026-04-28 10:00:00"
}
```
---
## 5. 前端页面设计
### 5.1 运营管理端txw-yygl-web
**路由:`/document/*`**
| 页面 | 路由 | 功能 |
|------|------|------|
| 文档列表 | /document/list | 分类筛选、状态筛选、搜索、新建按钮 |
| 新增/编辑文档 | /document/edit/:id? | Markdown 编辑器、表单提交 |
| 分类管理 | /document/category | 分类列表、增删改 |
**组件说明**
- `DocumentList.vue` - 文档列表页
- `DocumentEdit.vue` - 文档编辑页(含 mavon-editor
- `CategoryManage.vue` - 分类管理页
### 5.2 门户前端txw-mhzc-web
**路由:`/help/*` 或 `/document/*`**
| 页面 | 路由 | 功能 |
|------|------|------|
| 文档中心首页 | /help | 分类展示 + 文档列表 |
| 文档详情页 | /help/:id | Markdown 渲染展示 |
**组件说明**
- `DocumentCenter.vue` - 文档中心首页
- `DocumentDetail.vue` - 文档详情页(含 marked 渲染)
---
## 6. 核心功能流程
### 6.1 文档创建/编辑流程
```
运营用户 → 文档列表页 → 新建/编辑 → Markdown编辑器填写内容 → 保存 → 更新数据库
```
### 6.2 文档查看流程
```
普通用户 → 文档中心首页 → 选择分类 → 查看文档列表 → 点击文档 → URL变为 /help/:id → 渲染Markdown正文
```
### 6.3 URL 直接访问流程
```
用户访问 /help/123 → 前端路由解析id → 调用API获取文档详情 → 渲染Markdown正文
```
---
## 7. 技术实现要点
### 7.1 后端实现
- 在 `txw-yygl-service-biz` 中新建 `DocumentController`、`DocumentService`、`DocumentMapper`
- 新建 `DocumentCategoryController`、`DocumentCategoryService`、`DocumentCategoryMapper`
- 使用 MyBatis-Plus 简化 CRUD 操作
- 事务管理:新建/编辑文档在同一事务中执行
### 7.2 前端 Markdown 编辑器
- 编辑端:使用 `mavon-editor`(支持预览切换、语法高亮)
- 渲染端:使用 `marked` + `highlight.js`
### 7.3 路由配置
- yygl-web新增文档管理路由
- mhzc-web新增文档中心路由支持 `/help/:id` 格式
---
## 8. 目录结构
### 8.1 后端txw-yygl-service-biz
```
src/main/java/com/css/txw/yygl/
├── controller/
│ ├── DocumentController.java
│ └── DocumentCategoryController.java
├── service/
│ ├── DocumentService.java
│ ├── DocumentServiceImpl.java
│ ├── DocumentCategoryService.java
│ └── DocumentCategoryServiceImpl.java
├── mapper/
│ ├── DocumentMapper.java
│ └── DocumentCategoryMapper.java
├── entity/
│ ├── Document.java
│ └── DocumentCategory.java
└── vo/
├── DocumentListVO.java
└── DocumentDetailVO.java
```
### 8.2 前端txw-yygl-web
```
src/pages/document/
├── list/
│ └── index.vue # 文档列表页
├── edit/
│ └── index.vue # 新增/编辑页
└── category/
└── index.vue # 分类管理页
```
### 8.3 前端txw-mhzc-web
```
src/pages/help/
├── index.vue # 文档中心首页
└── detail/
└── index.vue # 文档详情页
```
---
## 9. 测试要点
### 9.1 后端测试
- 分类 CRUD增删改查、状态切换
- 文档 CRUD增删改查、分类关联、状态管理
- 异常场景:删除有关联文档的分类、查询不存在的文档
### 9.2 前端测试
- Markdown 编辑器:编辑、预览切换
- 文档列表:分类筛选、状态筛选、分页
- 文档详情Markdown 渲染、URL 直接访问

View File

@ -9,8 +9,15 @@ const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
const ROOT = __dirname;
const WEBS = ['txw-gxzx-web', 'txw-kxtfwzx-web', 'txw-mhzc-web', 'txw-tzzx-web', 'txw-ytzx-web', 'txw-yygl-web'];
const ROOT = path.join(__dirname, '..');
const WEBS = [
// 'txw-gxzx-web',
'txw-kxtfwzx-web',
'txw-mhzc-web',
'txw-tzzx-web',
'txw-ytzx-web',
'txw-yygl-web'
];
const LOCAL_PKG = 'local-nodemodules/@cssyq/ggzc-web';
function run(cmd, opts = {}) {

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,9 @@
"css-split-webpack-plugin": "0.2.6",
"dayjs": "^1.10.4",
"echarts": "^5.5.0",
"highlight.js": "^11.9.0",
"lodash-es": "^4.17.21",
"marked": "^4.3.0",
"qrcodejs2": "0.0.3",
"regenerator-runtime": "^0.13.9",
"tdesign-icons-vue": "0.0.8",

View File

@ -0,0 +1,25 @@
import { fetchSso } from '@/core/request';
const basurl = '/mhzc';
export default {
getCategoryList() {
return fetchSso({
url: `${basurl}/document/category/list`,
method: 'get',
});
},
getDocumentList(params) {
return fetchSso({
url: `${basurl}/document/list`,
method: 'get',
params,
});
},
getDocumentDetail(id) {
return fetchSso({
url: `${basurl}/document/${id}`,
method: 'get',
});
},
};

View File

@ -106,6 +106,11 @@ function zhanghugl() {
return import(/* webpackChunkName: "zhanghugl" */ '@/pages/index/views/yhzx/zhanghugl/index.vue');
}
// 帮助中心
function help() {
return import(/* webpackChunkName: "help" */ '@/pages/index/views/help/index.vue');
}
@ -306,4 +311,29 @@ export default [
disableBack: true,
},
},
{
name: 'help',
path: '/help',
component: help,
meta: {
title: '帮助中心',
isShowSideBar: false,
hasHome: true,
breadCrumbs: [{ title: '帮助中心', to: '/help' }],
},
children: [
{
name: 'helpIndex',
path: '',
component: () => import(/* webpackChunkName: "help" */ '@/pages/index/views/help/index.vue'),
meta: { title: '帮助中心' }
},
{
name: 'helpDetail',
path: ':id',
component: () => import(/* webpackChunkName: "help" */ '@/pages/index/views/help/detail/index.vue'),
meta: { title: '帮助详情' }
}
]
},
];

View File

@ -0,0 +1,55 @@
<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'
import api from '@/pages/index/api/help'
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: {
async loadDocument(id) {
const res = await api.getDocumentDetail(id)
this.document = res.data
},
goBack() {
this.$router.back()
}
}
}
</script>
<style scoped>
.document-detail { padding: 20px; max-width: 800px; margin: 0 auto; }
.back-btn { cursor: pointer; color: #0052d9; margin-bottom: 20px; }
.doc-title { font-size: 24px; margin-bottom: 20px; }
.doc-content { line-height: 1.8; }
.doc-content :deep(h1) { font-size: 20px; margin: 20px 0 10px; }
.doc-content :deep(h2) { font-size: 18px; margin: 16px 0 8px; }
.doc-content :deep(pre) { background: #f5f5f5; padding: 12px; border-radius: 4px; overflow-x: auto; }
.doc-content :deep(code) { font-family: monospace; }
</style>

View File

@ -0,0 +1,76 @@
<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>
import api from '@/pages/index/api/help';
export default {
name: 'HelpCenter',
data() {
return {
categoryList: [],
selectedCategoryId: null,
documentList: []
}
},
mounted() {
this.loadCategoryList()
this.loadDocumentList()
},
methods: {
async loadCategoryList() {
const res = await api.getCategoryList()
this.categoryList = res.data
},
async loadDocumentList() {
const res = await api.getDocumentList({ status: 1 })
this.documentList = res.data
},
async selectCategory(categoryId) {
this.selectedCategoryId = categoryId
const res = await api.getDocumentList({ categoryId, status: 1 })
this.documentList = res.data
},
goToDetail(id) {
this.$router.push(`/help/${id}`)
}
}
}
</script>
<style scoped>
.help-center { padding: 20px; }
.category-list { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
.category-item { padding: 8px 16px; cursor: pointer; border-radius: 4px; background: #f5f5f5; }
.category-item.active { background: #0052d9; color: #fff; }
.document-list { display: flex; flex-direction: column; gap: 10px; }
.document-item { padding: 12px; border: 1px solid #e5e5e5; border-radius: 4px; cursor: pointer; display: flex; justify-content: space-between; }
.document-item:hover { background: #f5f5f5; }
.doc-title { font-size: 14px; }
.doc-date { font-size: 12px; color: #999; }
.empty { text-align: center; padding: 40px; color: #999; }
</style>

View File

@ -286,26 +286,26 @@ module.exports = {
// 会误伤 SPA 路由 /view/mhzc/...,刷新时整页请求被转发到后端导致 Proxy error。必须用 ^ 限定为路径前缀。
proxy: {
'^/sso': {
// target: 'http://localhost:9301',
target: 'http://carbon.liantu.tech',
target: 'http://localhost:9301',
// target: 'http://carbon.liantu.tech',
// target: 'http://10.23.20.13:94/',
changeOrigin: true,
},
'^/mhzc': {
// target: 'http://localhost:9302',
target: 'http://carbon.liantu.tech',
target: 'http://localhost:9302',
// target: 'http://carbon.liantu.tech',
// target: 'http://10.23.20.13:94/',
changeOrigin: true,
},
'^/gxzx': {
// target: 'http://localhost:9303',
target: 'http://carbon.liantu.tech',
target: 'http://localhost:9303',
// target: 'http://carbon.liantu.tech',
// target: 'http://10.23.20.13:94/',
changeOrigin: true,
},
'^/yygl': {
// target: 'http://localhost:20010',
target: 'http://carbon.liantu.tech',
target: 'http://localhost:20010',
// target: 'http://carbon.liantu.tech',
// target: 'http://10.23.20.13:94/',
changeOrigin: true,
},

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -31,6 +31,7 @@
"dayjs": "^1.10.4",
"echarts": "^5.5.0",
"lodash-es": "^4.17.21",
"mavon-editor": "^2.10.0",
"qrcodejs2": "0.0.3",
"regenerator-runtime": "^0.13.9",
"tdesign-icons-vue": "0.0.8",

View File

@ -43,17 +43,14 @@ request.interceptors.request.use(
if (newConf.loading) {
SingleLoading.startLoading();
}
// 设置随机数, 定位后端日志和解决浏览器缓存
const { url } = newConf;
if (url.indexOf('?') !== -1) {
newConf.url = `${url}&t=${new Date().getTime()}`; // 请求添加时间戳
} else {
newConf.url = `${url}?t=${new Date().getTime()}`;
}
// get请求映射params参数
if (newConf.method === 'get' && newConf.params) {
let url = `${newConf.url}?`;
// 先加时间戳(如果 URL 还没参数)
if (url.indexOf('?') === -1) {
newConf.url = `${newConf.url}?t=${new Date().getTime()}&`;
}
// 再追加 params
let paramsUrl = `${newConf.url}`;
for (const propName of Object.keys(newConf.params)) {
const value = newConf.params[propName];
const part = `${encodeURIComponent(propName)}=`;
@ -62,16 +59,23 @@ request.interceptors.request.use(
for (const key of Object.keys(value)) {
const params = `${propName}[${key}]`;
const subPart = `${encodeURIComponent(params)}=`;
url += `${subPart + encodeURIComponent(value[key])}&`;
paramsUrl += `${subPart + encodeURIComponent(value[key])}&`;
}
} else {
url += `${part + encodeURIComponent(value)}&`;
paramsUrl += `${part + encodeURIComponent(value)}&`;
}
}
}
url = url.slice(0, -1);
paramsUrl = paramsUrl.slice(0, -1);
newConf.params = {};
newConf.url = url;
newConf.url = paramsUrl;
} else {
// 无 params 时,直接加时间戳
if (url.indexOf('?') !== -1) {
newConf.url = `${url}&t=${new Date().getTime()}`;
} else {
newConf.url = `${url}?t=${new Date().getTime()}`;
}
}
return newConf;
},

View File

@ -0,0 +1,84 @@
import { fetchSso } from '@/core/request';
const headers = { 'token-Type': 'ADMIN' };
const servicePrefix = 'yygl';
export default {
// 分类列表
getCategoryList() {
return fetchSso({
url: `${servicePrefix}/document/category/list`,
method: 'get',
headers,
});
},
// 新增分类
addCategory(data) {
return fetchSso({
url: `${servicePrefix}/document/category`,
data: JSON.stringify(data),
method: 'post',
headers,
});
},
// 编辑分类
updateCategory(id, data) {
return fetchSso({
url: `${servicePrefix}/document/category/${id}`,
data: JSON.stringify(data),
method: 'put',
headers,
});
},
// 删除分类
deleteCategory(id) {
return fetchSso({
url: `${servicePrefix}/document/category/${id}`,
method: 'delete',
headers,
});
},
// 文档列表
getDocumentList(params) {
return fetchSso({
url: `${servicePrefix}/document/list`,
method: 'get',
params,
headers,
});
},
// 文档详情
getDocumentDetail(id) {
return fetchSso({
url: `${servicePrefix}/document/${id}`,
method: 'get',
headers,
});
},
// 新增文档
addDocument(data) {
return fetchSso({
url: `${servicePrefix}/document`,
data: JSON.stringify(data),
method: 'post',
headers,
});
},
// 编辑文档
updateDocument(id, data) {
return fetchSso({
url: `${servicePrefix}/document/${id}`,
data: JSON.stringify(data),
method: 'put',
headers,
});
},
// 删除文档
deleteDocument(id) {
return fetchSso({
url: `${servicePrefix}/document/${id}`,
method: 'delete',
headers,
});
},
};

View File

@ -7,6 +7,9 @@ function login() {
function yhzx() {
return import(/* webpackChunkName: "sbfdemo" */ '@/pages/index/views/glxtSy/glxtSy.vue');
}
function document() {
return import(/* webpackChunkName: "document" */ '@/pages/index/views/document/index.vue');
}
export default [
{
name: 'login',
@ -44,4 +47,41 @@ export default [
disableBack: true,
},
},
{
name: 'document',
path: '/document',
component: document,
meta: {
title: '文档管理',
isShowSideBar: true,
hasHome: true,
breadCrumbs: [{ title: '文档管理', to: '/document' }],
},
children: [
{
name: 'documentOverview',
path: '',
component: () => import(/* webpackChunkName: "document" */ '@/pages/index/views/document/overview/index.vue'),
meta: { title: '文档概览' }
},
{
name: 'documentList',
path: 'list',
component: () => import(/* webpackChunkName: "document" */ '@/pages/index/views/document/list/index.vue'),
meta: { title: '文档列表' }
},
{
name: 'documentEdit',
path: 'edit/:id?',
component: () => import(/* webpackChunkName: "document" */ '@/pages/index/views/document/edit/index.vue'),
meta: { title: '文档编辑' }
},
{
name: 'categoryManage',
path: 'category',
component: () => import(/* webpackChunkName: "document" */ '@/pages/index/views/document/category/index.vue'),
meta: { title: '分类管理' }
}
]
},
];

View File

@ -0,0 +1,96 @@
<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>
import api from '@/pages/index/api/document';
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 ? '启用' : '禁用' },
{ colKey: 'operation', title: '操作', cell: 'operations' }
]
}
},
mounted() {
this.loadCategoryList()
},
methods: {
async loadCategoryList() {
const res = await api.getCategoryList()
this.categoryList = res.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
},
async saveCategory() {
if (this.isEdit) {
await api.updateCategory(this.category.id, this.category)
} else {
await api.addCategory(this.category)
}
this.$message.success('保存成功')
this.dialogVisible = false
this.loadCategoryList()
},
async deleteCategory(id) {
await api.deleteCategory(id)
this.$message.success('删除成功')
this.loadCategoryList()
}
}
}
</script>
<style scoped>
.category-manage-container { padding: 20px; }
.toolbar { margin-bottom: 20px; }
</style>

View File

@ -0,0 +1,86 @@
<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'
import api from '@/pages/index/api/document'
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: {
async loadCategoryList() {
const res = await api.getCategoryList()
this.categoryOptions = res.data.map(c => ({ label: c.name, value: c.id }))
},
async loadDocument(id) {
const res = await api.getDocumentDetail(id)
this.document = res.data
},
async saveDocument() {
const id = this.document.id
if (id) {
await api.updateDocument(id, this.document)
} else {
await api.addDocument(this.document)
}
this.$message.success('保存成功')
this.goBack()
},
goBack() {
this.$router.back()
}
}
}
</script>
<style scoped>
.document-edit-container { padding: 20px; }
</style>

View File

@ -0,0 +1,17 @@
<template>
<div class="document-layout">
<router-view />
</div>
</template>
<script>
export default {
name: 'DocumentLayout'
}
</script>
<style scoped>
.document-layout {
height: 100%;
}
</style>

View File

@ -0,0 +1,70 @@
<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>
import api from '@/pages/index/api/document';
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: '创建时间' },
{ colKey: 'operation', title: '操作', cell: 'operations' }
]
}
},
mounted() {
this.loadCategoryList()
this.loadDocumentList()
},
methods: {
async loadCategoryList() {
const res = await api.getCategoryList()
this.categoryOptions = res.data.map(c => ({ label: c.name, value: c.id }))
},
async loadDocumentList() {
const res = await api.getDocumentList(this.filters)
this.documentList = res.data
},
goToEdit(id) {
this.$router.push(id ? `/document/edit/${id}` : '/document/edit')
},
async deleteDocument(id) {
await api.deleteDocument(id)
this.$message.success('删除成功')
this.loadDocumentList()
}
}
}
</script>
<style scoped>
.document-list-container { padding: 20px; }
.filter-bar { display: flex; gap: 10px; margin-bottom: 20px; }
</style>

View File

@ -0,0 +1,109 @@
<template>
<div class="document-overview">
<div class="overview-header">
<h2>文档管理</h2>
<p class="subtitle">管理文档内容和分类</p>
</div>
<div class="overview-cards">
<div class="overview-card" @click="$router.push('/document/list')">
<div class="card-icon">
<t-icon name="document" />
</div>
<div class="card-content">
<h3>文档列表</h3>
<p>查看和管理所有文档</p>
</div>
<div class="card-arrow">
<t-icon name="arrow-right" />
</div>
</div>
<div class="overview-card" @click="$router.push('/document/category')">
<div class="card-icon">
<t-icon name="folder" />
</div>
<div class="card-content">
<h3>分类管理</h3>
<p>管理文档分类</p>
</div>
<div class="card-arrow">
<t-icon name="arrow-right" />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DocumentOverview'
}
</script>
<style scoped>
.document-overview {
padding: 24px;
}
.overview-header {
margin-bottom: 32px;
}
.overview-header h2 {
margin: 0 0 8px;
font-size: 24px;
font-weight: 500;
color: var(--td-text-color-primary);
}
.subtitle {
margin: 0;
color: var(--td-text-color-secondary);
}
.overview-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.overview-card {
display: flex;
padding: 24px;
cursor: pointer;
background: var(--td-bg-color-container);
border: 1px solid var(--td-border-level-1-color);
border-radius: 8px;
transition: all 0.2s ease;
align-items: center;
}
.overview-card:hover {
border-color: var(--td-brand-color);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.card-icon {
display: flex;
width: 48px;
height: 48px;
margin-right: 16px;
font-size: 24px;
color: var(--td-brand-color);
background: var(--td-brand-color-light);
border-radius: 8px;
align-items: center;
justify-content: center;
}
.card-content {
flex: 1;
}
.card-content h3 {
margin: 0 0 4px;
font-size: 16px;
font-weight: 500;
color: var(--td-text-color-primary);
}
.card-content p {
margin: 0;
font-size: 14px;
color: var(--td-text-color-secondary);
}
.card-arrow {
font-size: 16px;
color: var(--td-text-color-secondary);
}
</style>

View File

@ -280,6 +280,18 @@ module.exports = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
},
proxy: {
'^/sso': {
target: 'http://localhost:9301',
// target: 'http://carbon.liantu.tech',
// target: 'http://10.23.20.13:94/',
changeOrigin: true,
},
'^/yygl': {
target: 'http://localhost:20010',
changeOrigin: true,
}
},
// proxy: {
// // '/api': {
// // // target: 'http://dzswj.mhnsrd.jcsj.tax.cn/sbzx/api/cxssb', // 云测试环境

View File

@ -1196,6 +1196,11 @@
"@types/conventional-commits-parser" "^5.0.0"
chalk "^5.3.0"
"@fortawesome/fontawesome-free@^7.0.1":
version "7.2.0"
resolved "https://registry.npmmirror.com/@fortawesome/fontawesome-free/-/fontawesome-free-7.2.0.tgz#188c1053ce422ad1f934d7df242a973fcb89636d"
integrity sha512-3DguDv/oUE+7vjMeTSOjCSG+KeawgVQOHrKRnvUuqYh1mfArrh7s+s8hXW3e4RerBA1+Wh+hBqf8sJNpqNrBWg==
"@gar/promisify@^1.0.1":
version "1.1.3"
resolved "http://10.23.10.90:4873/@gar%2fpromisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
@ -3768,7 +3773,7 @@ commander@2.17.x:
resolved "http://10.23.10.90:4873/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
commander@^2.18.0, commander@^2.20.0:
commander@^2.18.0, commander@^2.20.0, commander@^2.20.3:
version "2.20.3"
resolved "http://10.23.10.90:4873/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@ -4387,6 +4392,11 @@ cssesc@^3.0.0:
resolved "http://10.23.10.90:4873/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
cssfilter@0.0.10:
version "0.0.10"
resolved "https://registry.npmmirror.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae"
integrity sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==
cssnano-preset-default@^4.0.0, cssnano-preset-default@^4.0.8:
version "4.0.8"
resolved "http://10.23.10.90:4873/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz#920622b1fc1e95a34e8838203f1397a504f2d3ff"
@ -8294,6 +8304,13 @@ mathml-tag-names@^2.0.1, mathml-tag-names@^2.1.3:
resolved "http://10.23.10.90:4873/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
mavon-editor@^2.10.0:
version "2.10.4"
resolved "https://registry.npmmirror.com/mavon-editor/-/mavon-editor-2.10.4.tgz#58d6c4dc208933f0ac4595c10c60655899ba8ba8"
integrity sha512-CFsBLkgt/KZBDg+SJYe2fyYv4zClY149PiwpH0rDAiiP4ae1XNs0GC8nBsoTeipsHcebDLN1QMkt3bUsnMDjQw==
dependencies:
xss "^1.0.6"
md5.js@^1.3.4:
version "1.3.5"
resolved "http://10.23.10.90:4873/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@ -13703,6 +13720,14 @@ x-is-string@^0.1.0:
resolved "http://10.23.10.90:4873/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82"
integrity sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w==
xss@^1.0.6:
version "1.0.15"
resolved "https://registry.npmmirror.com/xss/-/xss-1.0.15.tgz#96a0e13886f0661063028b410ed1b18670f4e59a"
integrity sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==
dependencies:
commander "^2.20.3"
cssfilter "0.0.10"
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
version "4.0.2"
resolved "http://10.23.10.90:4873/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"

View File

@ -0,0 +1,50 @@
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");
}
}

View File

@ -0,0 +1,59 @@
package com.css.txw.yygl.controller;
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");
}
}

View File

@ -0,0 +1,9 @@
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> {
}

View File

@ -0,0 +1,9 @@
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> {
}

View File

@ -0,0 +1,34 @@
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 = "txw_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;
}

View File

@ -0,0 +1,40 @@
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 = "txw_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;
}

View File

@ -0,0 +1,17 @@
package com.css.txw.yygl.pojo.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
public class CategoryVO {
private Long id;
private String name;
private Integer sort;
private Integer status;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
}

View File

@ -0,0 +1,23 @@
package com.css.txw.yygl.pojo.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
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;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}

View File

@ -0,0 +1,22 @@
package com.css.txw.yygl.pojo.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
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;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date updateTime;
}

View File

@ -0,0 +1,13 @@
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);
}

View File

@ -0,0 +1,15 @@
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);
}

View File

@ -0,0 +1,52 @@
package com.css.txw.yygl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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() {
LambdaQueryWrapper<DocumentCategoryDO> wrapper = new 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);
}
}

View File

@ -0,0 +1,76 @@
package com.css.txw.yygl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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) {
LambdaQueryWrapper<DocumentDO> wrapper = new 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);
}
}

View File

@ -3,7 +3,7 @@ spring:
name: txw-yygl
profiles:
active: env
active: local
server:
port: 20010

View File

@ -0,0 +1,13 @@
<?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>

View File

@ -0,0 +1,15 @@
<?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>

View File

@ -0,0 +1,45 @@
-- 文档分类表
CREATE TABLE txw_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 txw_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 '文档表';
-- ----------------------------
-- 测试数据
-- ----------------------------
-- 插入文档分类数据
INSERT INTO txw_document_category (id, name, sort, status, create_time, update_time) VALUES
(1, '平台介绍', 1, 1, NOW(), NOW()),
(2, '使用指南', 2, 1, NOW(), NOW()),
(3, '常见问题', 3, 1, NOW(), NOW()),
(4, '行业动态', 4, 1, NOW(), NOW()),
(5, '规章制度', 5, 1, NOW(), NOW());
-- 插入文档数据
INSERT INTO txw_document (id, category_id, title, content, status, sort, create_time, update_time) VALUES
(1, 1, '关于我们', '# 关于我们\n\n碳信网是国内领先的碳排放管理平台...\n\n## 联系方式\n\n- 电话400-xxx-xxxx\n- 邮箱contact@tanxin.com', 1, 1, NOW(), NOW()),
(2, 1, '平台公告', '# 平台公告\n\n欢迎使用碳信网平台最新公告内容将在这里发布。', 1, 2, NOW(), NOW()),
(3, 2, '快速入门', '# 快速入门\n\n## 第一步:注册账号\n\n点击注册按钮填写企业信息...\n\n## 第二步:认证企业\n\n完成企业实名认证后即可使用平台功能。', 1, 1, NOW(), NOW()),
(4, 2, '企业认证指南', '# 企业认证指南\n\n企业认证需要提供以下材料\n\n1. 营业执照\n2. 法人身份证\n3. 企业授权书', 1, 2, NOW(), NOW()),
(5, 3, '如何注册账号?', '# 如何注册账号?\n\n## 注册步骤\n\n1. 点击右上角"注册"按钮\n2. 选择"企业用户"类型\n3. 填写基本信息\n4. 完成验证', 1, 1, NOW(), NOW()),
(6, 3, '忘记密码怎么办?', '# 忘记密码怎么办?\n\n点击登录页的"忘记密码"链接,通过绑定的手机号或邮箱找回密码。', 1, 2, NOW(), NOW()),
(7, 4, '2026年碳市场趋势分析', '# 2026年碳市场趋势分析\n\n## 市场概况\n\n2026年碳市场整体呈现稳中有升的趋势...\n\n## 政策解读\n\n最新政策对碳排放企业提出了更高的要求。', 1, 1, NOW(), NOW()),
(8, 5, '用户协议', '# 用户协议\n\n## 第一条:总则\n\n本协议适用于碳信网所有用户...', 1, 1, NOW(), NOW());