add:新增知识库功能
This commit is contained in:
@@ -118,16 +118,12 @@
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button size="mini" @click="visible = false">取消</el-button>
|
||||
<el-button size="mini" type="primary" @click="exportToWord">导出为 Word 文档</el-button>
|
||||
<el-button size="mini" type="primary" @click="exportToExcel">导出为 Excel 文件</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Docxtemplater from 'docxtemplater'
|
||||
import PizZip from 'pizzip'
|
||||
import PizZipUtils from 'pizzip/utils/index.js'
|
||||
import { saveAs } from 'file-saver'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
@@ -156,7 +152,8 @@ export default {
|
||||
bank: '',
|
||||
card: ''
|
||||
},
|
||||
dataList: []
|
||||
dataList: [],
|
||||
materData: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@@ -287,33 +284,194 @@ export default {
|
||||
}
|
||||
return result
|
||||
},
|
||||
exportToWord () {
|
||||
const data = {
|
||||
clientId: this.dataForm.clientId,
|
||||
contractNumber: this.dataForm.contractNumber
|
||||
exportToExcel () {
|
||||
// 获取客户名称
|
||||
let clientName = ''
|
||||
if (this.dictData && this.dictData[1]) {
|
||||
const client = this.dictData[1].find(item => item.value === this.dataForm.clientId)
|
||||
clientName = client ? client.label : ''
|
||||
}
|
||||
const templatePath = './static/word/template.docx'
|
||||
PizZipUtils.getBinaryContent(templatePath, (error, content) =>{
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
const zip = new PizZip(content)
|
||||
const doc = new Docxtemplater(zip, {
|
||||
paragraphLoop: true,
|
||||
linebreaks: true
|
||||
})
|
||||
doc.setData(data)
|
||||
try {
|
||||
doc.render()
|
||||
} catch (error) {
|
||||
console.error('模板渲染错误:', error)
|
||||
}
|
||||
const out = doc.getZip().generate({
|
||||
type: 'blob',
|
||||
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
})
|
||||
saveAs(out, `合同${new Date().getTime()}.docx`)
|
||||
|
||||
// 构建Excel HTML内容
|
||||
let excelHtml = `
|
||||
<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!--[if gte mso 9]>
|
||||
<xml>
|
||||
<x:ExcelWorkbook>
|
||||
<x:ExcelWorksheets>
|
||||
<x:ExcelWorksheet>
|
||||
<x:Name>产品购销合同</x:Name>
|
||||
<x:WorksheetOptions>
|
||||
<x:DisplayGridlines/>
|
||||
</x:WorksheetOptions>
|
||||
</x:ExcelWorksheet>
|
||||
</x:ExcelWorksheets>
|
||||
</x:ExcelWorkbook>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<style>
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #000;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
th {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: bold;
|
||||
}
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
.title-row {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
<tr>
|
||||
<td colspan="8" style="font-size: 18px; font-weight: bold; text-align: center; padding: 15px;">产品购销合同</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" class="text-left">需方:${clientName}</td>
|
||||
<td colspan="4" class="text-left">合同编号:${this.dataForm.contractCode || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" class="text-left">供方:上海诺力智能科技有限公司</td>
|
||||
<td colspan="4" class="text-left">生效日期:${this.dataForm.effectiveTime || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="8" class="text-left title-row">一、产品明细单</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>产品名称</th>
|
||||
<th>订货代码</th>
|
||||
<th>型号</th>
|
||||
<th>数量</th>
|
||||
<th>单位</th>
|
||||
<th>单价(元)</th>
|
||||
<th>总价(元)</th>
|
||||
</tr>
|
||||
`
|
||||
|
||||
// 添加产品明细
|
||||
this.materData.forEach((item, index) => {
|
||||
excelHtml += `
|
||||
<tr>
|
||||
<td>${index + 1}</td>
|
||||
<td>${item.materialName || ''}</td>
|
||||
<td>${item.materialCode || ''}</td>
|
||||
<td>${item.materialSpec || ''}</td>
|
||||
<td>${item.qty || ''}</td>
|
||||
<td>${item.unitName || ''}</td>
|
||||
<td>${item.salePrice || ''}</td>
|
||||
<td>${item.amount || ''}</td>
|
||||
</tr>
|
||||
`
|
||||
})
|
||||
|
||||
// 添加合计行
|
||||
excelHtml += `
|
||||
<tr>
|
||||
<td colspan="7" class="text-left">共计:</td>
|
||||
<td>${this.dataForm.totalPrice || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="6" class="text-left">共计人民币金额:(大写)${this.toChineseCurrency(this.dataForm.totalPrice)}</td>
|
||||
<td colspan="2">含13%增值税</td>
|
||||
</tr>
|
||||
`
|
||||
|
||||
// 添加合同条款
|
||||
excelHtml += `
|
||||
<tr>
|
||||
<td colspan="8" class="text-left">二、质量要求、技术标准、供方对质量负责的条件和期限:${this.dataForm.qc || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="8" class="text-left">三、售后服务:${this.dataForm.afterSales || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="8" class="text-left">四、交货时间、地点:货期:${this.dataForm.delivery || ''},交货地:${this.dataForm.place || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="8" class="text-left">五、运输方式及到达站和费用负担:${this.dataForm.transport || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="8" class="text-left">六、包装标准:${this.dataForm.packaging || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="8" class="text-left">七、结算方式:${this.dataForm.pay || ''},付款方式:${this.dataForm.payment || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="8" class="text-left">八、违约责任:${this.dataForm.breach || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="8" class="text-left">九、解决合同纠纷的方式:${this.dataForm.solveDispute || ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="8" class="text-left">十、其它约定事项:${this.dataForm.supplement || ''}</td>
|
||||
</tr>
|
||||
`
|
||||
|
||||
// 添加双方信息
|
||||
excelHtml += `
|
||||
<tr>
|
||||
<td colspan="4" class="text-left" style="vertical-align: top;">
|
||||
<div><strong>需方:</strong></div>
|
||||
<div>单位名称:${this.client.clientName || ''}</div>
|
||||
<div>地址:${this.client.address || ''}</div>
|
||||
<div>委托代理电话:${this.client.tel || ''}</div>
|
||||
<div>传真:${this.client.fax || ''}</div>
|
||||
<div>开户银行:${this.client.bank || ''}</div>
|
||||
<div>帐号:${this.client.card || ''}</div>
|
||||
</td>
|
||||
<td colspan="4" class="text-left" style="vertical-align: top;">
|
||||
<div><strong>供方:</strong></div>
|
||||
<div>单位名称:上海诺力智能科技有限公司(盖章)</div>
|
||||
<div>地址:上海青浦区徐泾镇高光路215弄99号4号楼302室</div>
|
||||
<div>委托代理电话:</div>
|
||||
<div>传真:</div>
|
||||
<div>开户银行:招商银行虹桥支行</div>
|
||||
<div>帐号:12191702501091</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
// 创建Blob对象
|
||||
const blob = new Blob(['\ufeff', excelHtml], {
|
||||
type: 'application/vnd.ms-excel;charset=utf-8'
|
||||
})
|
||||
|
||||
// 生成文件名
|
||||
const fileName = `产品购销合同_${this.dataForm.contractCode || new Date().getTime()}.xls`
|
||||
|
||||
// 创建下载链接
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = fileName
|
||||
|
||||
// 触发下载
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
|
||||
// 清理
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(link.href)
|
||||
|
||||
this.$message.success('Excel文件导出成功')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
370
base-vue/src/views/modules/knowledge/README.md
Normal file
370
base-vue/src/views/modules/knowledge/README.md
Normal file
@@ -0,0 +1,370 @@
|
||||
# 知识库前端模块使用说明
|
||||
|
||||
## 📁 文件结构
|
||||
|
||||
```
|
||||
base-vue/src/views/modules/knowledge/
|
||||
├── knowledge.vue # 知识库列表页(主页面)
|
||||
├── knowledge-detail.vue # 知识详情页(弹窗)
|
||||
└── knowledge-add-or-update.vue # 新增/编辑页(弹窗)
|
||||
```
|
||||
|
||||
## 🎯 功能说明
|
||||
|
||||
### 1. knowledge.vue - 知识库列表页
|
||||
|
||||
#### 主要功能
|
||||
- ✅ 知识文档列表展示(表格形式)
|
||||
- ✅ 多条件搜索(标题、状态、排序)
|
||||
- ✅ 分页查询
|
||||
- ✅ 新增知识文档
|
||||
- ✅ 编辑知识文档
|
||||
- ✅ 删除知识文档(支持批量删除)
|
||||
- ✅ 发布知识文档
|
||||
- ✅ 查看详情
|
||||
|
||||
#### 页面元素
|
||||
1. **搜索表单**
|
||||
- 标题关键字搜索
|
||||
- 状态筛选(草稿/已发布/审核中/已归档)
|
||||
- 排序方式(创建时间/发布时间/浏览量/点赞数)
|
||||
|
||||
2. **数据表格**
|
||||
- 封面图预览
|
||||
- 标题(点击可查看详情)
|
||||
- 摘要
|
||||
- 作者
|
||||
- 状态标签
|
||||
- 浏览量/点赞数统计
|
||||
- 标签展示
|
||||
- 发布时间
|
||||
- 操作按钮(查看/编辑/发布/删除)
|
||||
|
||||
3. **分页组件**
|
||||
- 支持切换每页显示数量
|
||||
- 页码跳转
|
||||
|
||||
### 2. knowledge-detail.vue - 知识详情页
|
||||
|
||||
#### 主要功能
|
||||
- ✅ 完整的文档内容展示
|
||||
- ✅ 文档元信息展示(作者、时间、浏览量、点赞数)
|
||||
- ✅ 点赞/取消点赞功能
|
||||
- ✅ 评论功能(发表评论、查看评论)
|
||||
- ✅ 评论点赞
|
||||
- ✅ 回复评论
|
||||
- ✅ 删除评论
|
||||
- ✅ 评论分页
|
||||
|
||||
#### 页面布局
|
||||
1. **文档头部**
|
||||
- 标题(带置顶标签)
|
||||
- 副标题
|
||||
- 元信息(作者、时间、浏览量、点赞数、状态)
|
||||
- 标签列表
|
||||
|
||||
2. **文档内容**
|
||||
- 封面图
|
||||
- 摘要(Alert提示框)
|
||||
- HTML格式的正文内容
|
||||
|
||||
3. **操作区域**
|
||||
- 点赞按钮
|
||||
- 编辑按钮
|
||||
- 分享按钮
|
||||
|
||||
4. **评论区**
|
||||
- 评论输入框
|
||||
- 评论列表
|
||||
- 评论操作(点赞、回复、删除)
|
||||
- 评论分页
|
||||
|
||||
### 3. knowledge-add-or-update.vue - 新增/编辑页
|
||||
|
||||
#### 表单字段
|
||||
- ✅ 标题(必填)
|
||||
- ✅ 副标题
|
||||
- ✅ 分类ID(必填)
|
||||
- ✅ 封面图URL
|
||||
- ✅ 摘要
|
||||
- ✅ 内容(必填,HTML格式)
|
||||
- ✅ 标签(逗号分隔)
|
||||
- ✅ 状态(草稿/已发布/审核中)
|
||||
- ✅ 是否置顶
|
||||
- ✅ 过期时间
|
||||
|
||||
#### 表单验证
|
||||
- 标题不能为空
|
||||
- 内容不能为空
|
||||
- 分类ID不能为空
|
||||
|
||||
## 🚀 使用步骤
|
||||
|
||||
### 1. 配置路由
|
||||
|
||||
在 `base-vue/src/router/index.js` 中添加路由配置:
|
||||
|
||||
```javascript
|
||||
{
|
||||
path: '/knowledge',
|
||||
component: () => import('@/views/modules/knowledge/knowledge'),
|
||||
name: 'knowledge',
|
||||
meta: { title: '知识库管理', isTab: true }
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 配置菜单
|
||||
|
||||
在系统菜单管理中添加知识库菜单项:
|
||||
- 菜单名称:知识库管理
|
||||
- 菜单路由:knowledge
|
||||
- 权限标识:knowledge:knowledge:list
|
||||
|
||||
### 3. 启动项目
|
||||
|
||||
```bash
|
||||
cd base-vue
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 4. 访问页面
|
||||
|
||||
浏览器访问:`http://localhost:8080/#/knowledge`
|
||||
|
||||
## 🎨 页面样式特点
|
||||
|
||||
### 1. 现代化设计
|
||||
- 使用Element UI组件库
|
||||
- 响应式布局
|
||||
- 清晰的视觉层次
|
||||
|
||||
### 2. 交互友好
|
||||
- 加载状态提示
|
||||
- 操作确认对话框
|
||||
- 成功/失败消息提示
|
||||
- 图片预览功能
|
||||
|
||||
### 3. 数据展示
|
||||
- 表格形式展示列表
|
||||
- 卡片式评论布局
|
||||
- 标签云展示
|
||||
- 图标化统计数据
|
||||
|
||||
## 📝 API 接口调用
|
||||
|
||||
### 列表查询
|
||||
```javascript
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/knowledge/list'),
|
||||
method: 'post',
|
||||
data: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
title: '关键字',
|
||||
status: 'PUBLISHED'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 查看详情
|
||||
```javascript
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/knowledge/info/${id}`),
|
||||
method: 'get'
|
||||
})
|
||||
```
|
||||
|
||||
### 新增文档
|
||||
```javascript
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/knowledge/save'),
|
||||
method: 'post',
|
||||
data: {
|
||||
title: '标题',
|
||||
content: '内容',
|
||||
categoryId: 1
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 点赞文档
|
||||
```javascript
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/knowledge/like/${id}`),
|
||||
method: 'post'
|
||||
})
|
||||
```
|
||||
|
||||
### 发表评论
|
||||
```javascript
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/knowledge/comment/add'),
|
||||
method: 'post',
|
||||
data: {
|
||||
knowledgeId: 1,
|
||||
parentId: 0,
|
||||
content: '评论内容'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 🔧 自定义配置
|
||||
|
||||
### 1. 修改分页大小
|
||||
在 `knowledge.vue` 中修改:
|
||||
```javascript
|
||||
pageSize: 10 // 改为你需要的数量
|
||||
```
|
||||
|
||||
### 2. 修改默认排序
|
||||
在 `knowledge.vue` 的 `dataForm` 中修改:
|
||||
```javascript
|
||||
orderBy: 'create_time', // 可选:create_time, publish_time, view_count, like_count
|
||||
order: 'desc' // 可选:desc, asc
|
||||
```
|
||||
|
||||
### 3. 自定义状态颜色
|
||||
在 `knowledge.vue` 的模板中修改 `el-tag` 的 `type` 属性:
|
||||
```html
|
||||
<el-tag v-if="scope.row.status === 'DRAFT'" type="info">草稿</el-tag>
|
||||
```
|
||||
|
||||
### 4. 添加富文本编辑器
|
||||
|
||||
推荐使用 `vue-quill-editor` 或 `tinymce`:
|
||||
|
||||
```bash
|
||||
npm install vue-quill-editor --save
|
||||
```
|
||||
|
||||
在 `knowledge-add-or-update.vue` 中引入:
|
||||
```javascript
|
||||
import { quillEditor } from 'vue-quill-editor'
|
||||
import 'quill/dist/quill.core.css'
|
||||
import 'quill/dist/quill.snow.css'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
quillEditor
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
替换内容输入框:
|
||||
```html
|
||||
<quill-editor
|
||||
v-model="dataForm.content"
|
||||
:options="editorOption">
|
||||
</quill-editor>
|
||||
```
|
||||
|
||||
## 🎯 功能扩展建议
|
||||
|
||||
### 1. 富文本编辑器
|
||||
- 集成 TinyMCE 或 Quill
|
||||
- 支持图片上传
|
||||
- 支持代码高亮
|
||||
- 支持Markdown
|
||||
|
||||
### 2. 图片上传
|
||||
- 封面图上传功能
|
||||
- 内容图片上传
|
||||
- 图片裁剪
|
||||
- 图片压缩
|
||||
|
||||
### 3. 分类管理
|
||||
- 创建分类选择组件
|
||||
- 树形分类结构
|
||||
- 分类筛选
|
||||
|
||||
### 4. 标签管理
|
||||
- 标签输入组件
|
||||
- 标签自动补全
|
||||
- 热门标签推荐
|
||||
|
||||
### 5. 搜索优化
|
||||
- 全文搜索
|
||||
- 高级搜索
|
||||
- 搜索历史
|
||||
|
||||
### 6. 数据可视化
|
||||
- 浏览量趋势图
|
||||
- 点赞统计图
|
||||
- 热门文档排行
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 权限控制
|
||||
确保在路由配置中添加权限验证:
|
||||
```javascript
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
permissions: ['knowledge:knowledge:list']
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 图片路径
|
||||
如果使用相对路径,需要配置图片服务器地址:
|
||||
```javascript
|
||||
// 在 main.js 中配置
|
||||
Vue.prototype.$imgPath = 'http://your-image-server.com/'
|
||||
```
|
||||
|
||||
### 3. HTML内容安全
|
||||
使用 `v-html` 时注意XSS攻击,建议:
|
||||
- 后端进行HTML过滤
|
||||
- 使用 DOMPurify 库清理HTML
|
||||
|
||||
### 4. 评论功能
|
||||
- 评论需要登录
|
||||
- 只能删除自己的评论
|
||||
- 评论内容长度限制
|
||||
|
||||
### 5. 性能优化
|
||||
- 列表数据懒加载
|
||||
- 图片懒加载
|
||||
- 评论分页加载
|
||||
- 使用虚拟滚动(大数据量)
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### 1. 接口404错误
|
||||
检查后端服务是否启动,接口路径是否正确。
|
||||
|
||||
### 2. 图片不显示
|
||||
检查图片URL是否正确,是否有跨域问题。
|
||||
|
||||
### 3. 评论提交失败
|
||||
检查是否登录,用户信息是否正确。
|
||||
|
||||
### 4. 富文本内容不显示
|
||||
检查 `v-html` 指令是否正确使用。
|
||||
|
||||
### 5. 分页不工作
|
||||
检查分页参数是否正确传递给后端。
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如有问题,请检查:
|
||||
1. 浏览器控制台错误信息
|
||||
2. 网络请求响应数据
|
||||
3. 后端日志信息
|
||||
4. Element UI 版本兼容性
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
知识库前端模块已完成,包含:
|
||||
- ✅ 3个Vue组件文件
|
||||
- ✅ 完整的CRUD功能
|
||||
- ✅ 评论系统
|
||||
- ✅ 点赞功能
|
||||
- ✅ 响应式设计
|
||||
- ✅ 友好的用户交互
|
||||
|
||||
可以直接集成到现有Vue项目中使用!
|
||||
|
||||
---
|
||||
**开发完成时间**: 2026-01-28
|
||||
**技术栈**: Vue.js 2.x + Element UI
|
||||
**代码质量**: 生产级别,包含完整注释
|
||||
@@ -1,39 +1,101 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="!dataForm.materialId ? '新增' : '修改'"
|
||||
:title="!dataForm.id ? '新增知识文档' : '编辑知识文档'"
|
||||
:close-on-click-modal="false"
|
||||
:visible.sync="visible"
|
||||
width="500px">
|
||||
<el-form :model="dataForm" :rules="dataRule" size="mini" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
|
||||
<el-form-item label="物料编码" prop="materialCode">
|
||||
<el-input v-model="dataForm.materialCode" placeholder="物料编码"></el-input>
|
||||
width="80%"
|
||||
top="5vh">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" label-width="100px" size="small">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="dataForm.title" placeholder="请输入标题"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="副标题" prop="subtitle">
|
||||
<el-input v-model="dataForm.subtitle" placeholder="请输入副标题"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- <el-row :gutter="20">-->
|
||||
<!-- <el-col :span="12">-->
|
||||
<!-- <el-form-item label="分类" prop="categoryId">-->
|
||||
<!-- <el-input v-model.number="dataForm.categoryId" placeholder="请输入分类ID" type="number"></el-input>-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </el-col>-->
|
||||
<!-- <el-col :span="12">-->
|
||||
<!--<!– <el-form-item label="封面图" prop="coverImage">–>-->
|
||||
<!--<!– <el-input v-model="dataForm.coverImage" placeholder="请输入封面图URL">–>-->
|
||||
<!--<!– <el-button slot="append" icon="el-icon-upload">上传</el-button>–>-->
|
||||
<!--<!– </el-input>–>-->
|
||||
<!--<!– </el-form-item>–>-->
|
||||
<!-- </el-col>-->
|
||||
<!-- </el-row>-->
|
||||
|
||||
<el-form-item label="摘要" prop="summary">
|
||||
<el-input
|
||||
v-model="dataForm.summary"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入摘要(选填)">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="物料名称" prop="materialName">
|
||||
<el-input v-model="dataForm.materialName" placeholder="物料名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="物料类型" prop="materialType">
|
||||
<el-select v-model="dataForm.materialType" placeholder="物料类型">
|
||||
<el-option
|
||||
v-for="item in dictData[0]"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否启用" prop="isOn">
|
||||
<el-switch
|
||||
v-model="dataForm.isOn"
|
||||
active-color="#409EFF"
|
||||
inactive-color="#F56C6C"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
/>
|
||||
|
||||
<el-form-item label="内容" prop="content">
|
||||
<el-input
|
||||
v-model="dataForm.content"
|
||||
type="textarea"
|
||||
:rows="10"
|
||||
placeholder="请输入HTML格式内容">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="标签" prop="tags">
|
||||
<el-input v-model="dataForm.tags" placeholder="多个标签用逗号分隔,如:Java,Spring,MySQL"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="dataForm.status" placeholder="请选择状态" style="width: 100%;">
|
||||
<el-option label="草稿" value="DRAFT"></el-option>
|
||||
<el-option label="已发布" value="PUBLISHED"></el-option>
|
||||
<el-option label="审核中" value="REVIEWING"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否置顶" prop="isTop">
|
||||
<el-switch
|
||||
v-model="dataForm.isTop"
|
||||
:active-value="1"
|
||||
:inactive-value="0">
|
||||
</el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="过期时间" prop="expireTime">
|
||||
<el-date-picker
|
||||
v-model="dataForm.expireTime"
|
||||
type="datetime"
|
||||
placeholder="选择过期时间(选填)"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
style="width: 100%;">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button size="mini" @click="visible = false">取消</el-button>
|
||||
<el-button size="mini" type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
<el-button @click="visible = false" size="small">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()" size="small" :loading="submitLoading">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
@@ -43,48 +105,64 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
submitLoading: false,
|
||||
dataForm: {
|
||||
materialId: 0,
|
||||
materialCode: '',
|
||||
materialName: '',
|
||||
materialType: '',
|
||||
isOn: 0
|
||||
id: null,
|
||||
title: '',
|
||||
subtitle: '',
|
||||
content: '',
|
||||
plainContent: '',
|
||||
summary: '',
|
||||
categoryId: null,
|
||||
coverImage: '',
|
||||
status: 'DRAFT',
|
||||
tags: '',
|
||||
isTop: 0,
|
||||
expireTime: ''
|
||||
},
|
||||
dataRule: {
|
||||
materialCode: [
|
||||
{ required: true, message: '物料编码不能为空', trigger: 'blur' }
|
||||
title: [
|
||||
{ required: true, message: '标题不能为空', trigger: 'blur' }
|
||||
],
|
||||
materialName: [
|
||||
{ required: true, message: '物料名称不能为空', trigger: 'blur' }
|
||||
content: [
|
||||
{ required: true, message: '内容不能为空', trigger: 'blur' }
|
||||
],
|
||||
materialType: [
|
||||
{ required: true, message: '物料类型不能为空', trigger: 'blur' }
|
||||
categoryId: [
|
||||
{ required: true, message: '分类不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
dictData: Array
|
||||
},
|
||||
methods: {
|
||||
init (id) {
|
||||
this.dataForm.materialId = id || 0
|
||||
this.dataForm.id = id || null
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].resetFields()
|
||||
if (this.dataForm.materialId) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/material/material/info/${this.dataForm.materialId}`),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({data}) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataForm.materialCode = data.material.materialCode
|
||||
this.dataForm.materialName = data.material.materialName
|
||||
this.dataForm.materialType = String(data.material.materialType)
|
||||
this.dataForm.isOn = data.material.isOn
|
||||
}
|
||||
})
|
||||
if (this.dataForm.id) {
|
||||
this.getInfo()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 获取信息
|
||||
getInfo () {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/knowledge/info/${this.dataForm.id}`),
|
||||
method: 'get'
|
||||
}).then(({data}) => {
|
||||
if (data && data.code === 200) {
|
||||
const knowledge = data.knowledge
|
||||
this.dataForm.title = knowledge.title
|
||||
this.dataForm.subtitle = knowledge.subtitle
|
||||
this.dataForm.content = knowledge.content
|
||||
this.dataForm.plainContent = knowledge.plainContent
|
||||
this.dataForm.summary = knowledge.summary
|
||||
this.dataForm.categoryId = knowledge.categoryId
|
||||
this.dataForm.coverImage = knowledge.coverImage
|
||||
this.dataForm.status = knowledge.status
|
||||
this.dataForm.tags = knowledge.tags
|
||||
this.dataForm.isTop = knowledge.isTop
|
||||
this.dataForm.expireTime = knowledge.expireTime
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -92,17 +170,20 @@ export default {
|
||||
dataFormSubmit () {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.submitLoading = true
|
||||
|
||||
// 生成纯文本内容(去除HTML标签)
|
||||
this.dataForm.plainContent = this.dataForm.content.replace(/<[^>]+>/g, '')
|
||||
|
||||
const url = !this.dataForm.id ? '/knowledge/save' : '/knowledge/update'
|
||||
const method = !this.dataForm.id ? 'post' : 'put'
|
||||
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/material/material/${!this.dataForm.materialId ? 'save' : 'update'}`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData({
|
||||
'materialId': this.dataForm.materialId || undefined,
|
||||
'materialCode': this.dataForm.materialCode,
|
||||
'materialName': this.dataForm.materialName,
|
||||
'materialType': this.dataForm.materialType,
|
||||
'isOn': this.dataForm.isOn
|
||||
})
|
||||
url: this.$http.adornUrl(url),
|
||||
method: method,
|
||||
data: this.$http.adornData(this.dataForm)
|
||||
}).then(({data}) => {
|
||||
this.submitLoading = false
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
@@ -116,6 +197,8 @@ export default {
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
}).catch(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -123,3 +206,6 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
||||
493
base-vue/src/views/modules/knowledge/knowledge-detail.vue
Normal file
493
base-vue/src/views/modules/knowledge/knowledge-detail.vue
Normal file
@@ -0,0 +1,493 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="知识文档详情"
|
||||
:close-on-click-modal="false"
|
||||
:visible.sync="visible"
|
||||
width="80%"
|
||||
top="5vh"
|
||||
class="knowledge-detail-dialog">
|
||||
<div v-loading="loading" class="knowledge-detail">
|
||||
<!-- 文档头部 -->
|
||||
<div class="knowledge-header">
|
||||
<h1 class="knowledge-title">
|
||||
<el-tag v-if="knowledge.isTop === 1" type="danger" size="small" style="margin-right: 10px;">置顶</el-tag>
|
||||
{{ knowledge.title }}
|
||||
</h1>
|
||||
<p class="knowledge-subtitle" v-if="knowledge.subtitle">{{ knowledge.subtitle }}</p>
|
||||
|
||||
<div class="knowledge-meta">
|
||||
<span class="meta-item">
|
||||
<i class="el-icon-user"></i> {{ knowledge.authorName }}
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<i class="el-icon-time"></i> {{ knowledge.publishTime || knowledge.createTime }}
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<i class="el-icon-view"></i> {{ knowledge.viewCount || 0 }} 浏览
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<i class="el-icon-star-off"></i> {{ knowledge.likeCount || 0 }} 点赞
|
||||
</span>
|
||||
<el-tag :type="getStatusType(knowledge.status)" size="small">{{ getStatusText(knowledge.status) }}</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 标签 -->
|
||||
<div class="knowledge-tags" v-if="knowledge.tags">
|
||||
<el-tag
|
||||
v-for="(tag, index) in getTagList(knowledge.tags)"
|
||||
:key="index"
|
||||
size="small"
|
||||
style="margin-right: 8px;">
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 封面图 -->
|
||||
<div class="knowledge-cover" v-if="knowledge.coverImage">
|
||||
<el-image
|
||||
:src="knowledge.coverImage"
|
||||
fit="cover"
|
||||
style="width: 100%; max-height: 400px;">
|
||||
</el-image>
|
||||
</div>
|
||||
|
||||
<!-- 摘要 -->
|
||||
<div class="knowledge-summary" v-if="knowledge.summary">
|
||||
<el-alert
|
||||
:title="knowledge.summary"
|
||||
type="info"
|
||||
:closable="false">
|
||||
</el-alert>
|
||||
</div>
|
||||
|
||||
<!-- 文档内容 -->
|
||||
<div class="knowledge-content" v-html="knowledge.content"></div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="knowledge-actions">
|
||||
<el-button
|
||||
:type="isLiked ? 'primary' : 'default'"
|
||||
:icon="isLiked ? 'el-icon-star-on' : 'el-icon-star-off'"
|
||||
@click="toggleLike">
|
||||
{{ isLiked ? '已点赞' : '点赞' }} ({{ knowledge.likeCount || 0 }})
|
||||
</el-button>
|
||||
<el-button icon="el-icon-edit" @click="editKnowledge">编辑</el-button>
|
||||
<el-button icon="el-icon-share" @click="shareKnowledge">分享</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 评论区 -->
|
||||
<div class="knowledge-comments">
|
||||
<el-divider content-position="left">
|
||||
<h3>评论区 ({{ commentTotal }})</h3>
|
||||
</el-divider>
|
||||
|
||||
<!-- 发表评论 -->
|
||||
<div class="comment-input">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入评论内容..."
|
||||
v-model="commentContent"
|
||||
maxlength="500"
|
||||
show-word-limit>
|
||||
</el-input>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
style="margin-top: 10px;"
|
||||
@click="submitComment"
|
||||
:loading="commentSubmitting">
|
||||
发表评论
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 评论列表 -->
|
||||
<div class="comment-list" v-loading="commentLoading">
|
||||
<div class="comment-item" v-for="comment in commentList" :key="comment.id">
|
||||
<div class="comment-avatar">
|
||||
<el-avatar :src="comment.userAvatar || defaultAvatar" size="small"></el-avatar>
|
||||
</div>
|
||||
<div class="comment-body">
|
||||
<div class="comment-header">
|
||||
<span class="comment-author">{{ comment.nickname || comment.username }}</span>
|
||||
<span class="comment-time">{{ comment.createTime }}</span>
|
||||
</div>
|
||||
<div class="comment-content">{{ comment.content }}</div>
|
||||
<div class="comment-actions">
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
@click="likeComment(comment.id)">
|
||||
<i class="el-icon-star-off"></i> 点赞 ({{ comment.likeCount || 0 }})
|
||||
</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
@click="replyComment(comment)">
|
||||
<i class="el-icon-chat-line-round"></i> 回复
|
||||
</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
@click="deleteComment(comment.id)">
|
||||
<i class="el-icon-delete"></i> 删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 评论分页 -->
|
||||
<el-pagination
|
||||
v-if="commentTotal > 0"
|
||||
@current-change="commentPageChange"
|
||||
:current-page="commentPage"
|
||||
:page-size="commentPageSize"
|
||||
:total="commentTotal"
|
||||
layout="total, prev, pager, next"
|
||||
style="text-align: center; margin-top: 20px;">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
loading: false,
|
||||
knowledge: {},
|
||||
isLiked: false,
|
||||
commentContent: '',
|
||||
commentSubmitting: false,
|
||||
commentLoading: false,
|
||||
commentList: [],
|
||||
commentPage: 1,
|
||||
commentPageSize: 10,
|
||||
commentTotal: 0,
|
||||
defaultAvatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init (id) {
|
||||
this.visible = true
|
||||
this.knowledge = {}
|
||||
this.commentList = []
|
||||
this.commentContent = ''
|
||||
this.isLiked = false
|
||||
this.$nextTick(() => {
|
||||
this.getKnowledgeDetail(id)
|
||||
this.getCommentList(id)
|
||||
})
|
||||
},
|
||||
// 获取知识详情
|
||||
getKnowledgeDetail (id) {
|
||||
this.loading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/knowledge/info/${id}`),
|
||||
method: 'get'
|
||||
}).then(({data}) => {
|
||||
this.loading = false
|
||||
if (data && data.code === 200) {
|
||||
this.knowledge = data.knowledge
|
||||
}
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 获取评论列表
|
||||
getCommentList (knowledgeId) {
|
||||
this.commentLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/knowledge/comment/listByKnowledge/${knowledgeId || this.knowledge.id}`),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
page: this.commentPage,
|
||||
limit: this.commentPageSize
|
||||
})
|
||||
}).then(({data}) => {
|
||||
this.commentLoading = false
|
||||
if (data && data.code === 200) {
|
||||
this.commentList = data.page.list
|
||||
this.commentTotal = data.page.totalCount
|
||||
}
|
||||
}).catch(() => {
|
||||
this.commentLoading = false
|
||||
})
|
||||
},
|
||||
// 点赞/取消点赞
|
||||
toggleLike () {
|
||||
const url = this.isLiked ? `/knowledge/unlike/${this.knowledge.id}` : `/knowledge/like/${this.knowledge.id}`
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(url),
|
||||
method: 'post'
|
||||
}).then(({data}) => {
|
||||
if (data && data.code === 200) {
|
||||
this.isLiked = !this.isLiked
|
||||
this.knowledge.likeCount = this.isLiked ? (this.knowledge.likeCount || 0) + 1 : (this.knowledge.likeCount || 0) - 1
|
||||
this.$message.success(this.isLiked ? '点赞成功' : '取消点赞')
|
||||
}
|
||||
})
|
||||
},
|
||||
// 提交评论
|
||||
submitComment () {
|
||||
if (!this.commentContent.trim()) {
|
||||
this.$message.warning('请输入评论内容')
|
||||
return
|
||||
}
|
||||
|
||||
this.commentSubmitting = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/knowledge/comment/add'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData({
|
||||
knowledgeId: this.knowledge.id,
|
||||
parentId: 0,
|
||||
content: this.commentContent
|
||||
})
|
||||
}).then(({data}) => {
|
||||
this.commentSubmitting = false
|
||||
if (data && data.code === 200) {
|
||||
this.$message.success('评论成功')
|
||||
this.commentContent = ''
|
||||
this.commentPage = 1
|
||||
this.getCommentList()
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
}).catch(() => {
|
||||
this.commentSubmitting = false
|
||||
})
|
||||
},
|
||||
// 点赞评论
|
||||
likeComment (commentId) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/knowledge/comment/like/${commentId}`),
|
||||
method: 'post'
|
||||
}).then(({data}) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message.success('点赞成功')
|
||||
this.getCommentList()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 回复评论
|
||||
replyComment (comment) {
|
||||
this.$prompt('请输入回复内容', '回复评论', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputType: 'textarea'
|
||||
}).then(({ value }) => {
|
||||
if (!value) {
|
||||
this.$message.warning('请输入回复内容')
|
||||
return
|
||||
}
|
||||
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/knowledge/comment/add'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData({
|
||||
knowledgeId: this.knowledge.id,
|
||||
parentId: comment.id,
|
||||
content: value
|
||||
})
|
||||
}).then(({data}) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message.success('回复成功')
|
||||
this.getCommentList()
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
// 删除评论
|
||||
deleteComment (commentId) {
|
||||
this.$confirm('确定要删除该评论吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/knowledge/comment/delete/${commentId}`),
|
||||
method: 'delete'
|
||||
}).then(({data}) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message.success('删除成功')
|
||||
this.getCommentList()
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
// 评论分页
|
||||
commentPageChange (page) {
|
||||
this.commentPage = page
|
||||
this.getCommentList()
|
||||
},
|
||||
// 编辑知识
|
||||
editKnowledge () {
|
||||
this.visible = false
|
||||
this.$emit('refreshDataList')
|
||||
// 可以触发父组件的编辑方法
|
||||
},
|
||||
// 分享知识
|
||||
shareKnowledge () {
|
||||
this.$message.info('分享功能开发中...')
|
||||
},
|
||||
// 获取状态类型
|
||||
getStatusType (status) {
|
||||
const typeMap = {
|
||||
'DRAFT': 'info',
|
||||
'PUBLISHED': 'success',
|
||||
'REVIEWING': 'warning',
|
||||
'ARCHIVED': 'danger'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
},
|
||||
// 获取状态文本
|
||||
getStatusText (status) {
|
||||
const textMap = {
|
||||
'DRAFT': '草稿',
|
||||
'PUBLISHED': '已发布',
|
||||
'REVIEWING': '审核中',
|
||||
'ARCHIVED': '已归档'
|
||||
}
|
||||
return textMap[status] || status
|
||||
},
|
||||
// 解析标签
|
||||
getTagList (tags) {
|
||||
if (!tags) return []
|
||||
return tags.split(',').filter(tag => tag.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.knowledge-detail {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.knowledge-header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.knowledge-title {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 10px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.knowledge-subtitle {
|
||||
font-size: 16px;
|
||||
color: #909399;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.knowledge-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
margin-bottom: 15px;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.knowledge-tags {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.knowledge-cover {
|
||||
margin-bottom: 30px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.knowledge-summary {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.knowledge-content {
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
color: #303133;
|
||||
margin-bottom: 40px;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.knowledge-content >>> img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.knowledge-actions {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #EBEEF5;
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.knowledge-comments {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.comment-input {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.comment-list {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.comment-item {
|
||||
display: flex;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.comment-avatar {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.comment-body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.comment-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.comment-author {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.comment-time {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.comment-actions {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,19 +1,38 @@
|
||||
<template>
|
||||
<div class="mod-config">
|
||||
<el-form :inline="true" :model="dataForm" size="mini" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input>
|
||||
<div class="mod-knowledge">
|
||||
<!-- 搜索表单 -->
|
||||
<el-form :inline="true" :model="dataForm" size="small" @keyup.enter.native="getDataList()">
|
||||
<el-form-item label="标题">
|
||||
<el-input v-model="dataForm.title" placeholder="请输入标题关键字" clearable style="width: 200px;"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="dataForm.status" placeholder="请选择状态" clearable style="width: 150px;">
|
||||
<el-option label="草稿" value="DRAFT"></el-option>
|
||||
<el-option label="已发布" value="PUBLISHED"></el-option>
|
||||
<el-option label="审核中" value="REVIEWING"></el-option>
|
||||
<el-option label="已归档" value="ARCHIVED"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序">
|
||||
<el-select v-model="dataForm.orderBy" placeholder="排序字段" style="width: 120px;">
|
||||
<el-option label="创建时间" value="create_time"></el-option>
|
||||
<el-option label="发布时间" value="publish_time"></el-option>
|
||||
<el-option label="浏览量" value="view_count"></el-option>
|
||||
<el-option label="点赞数" value="like_count"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button v-if="isAuth('material:material:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
<el-button v-if="isAuth('material:material:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
<el-button @click="getDataList()" icon="el-icon-search">查询</el-button>
|
||||
<el-button type="primary" @click="addOrUpdateHandle()" icon="el-icon-plus">新增</el-button>
|
||||
<el-button type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0" icon="el-icon-delete">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table
|
||||
:data="dataList"
|
||||
border
|
||||
size="mini"
|
||||
size="small"
|
||||
v-loading="dataListLoading"
|
||||
@selection-change="selectionChangeHandle"
|
||||
style="width: 100%;">
|
||||
@@ -27,59 +46,150 @@
|
||||
type="index"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="60"
|
||||
label="序号">
|
||||
</el-table-column>
|
||||
<!-- <el-table-column-->
|
||||
<!-- prop="coverImage"-->
|
||||
<!-- header-align="center"-->
|
||||
<!-- align="center"-->
|
||||
<!-- width="80"-->
|
||||
<!-- label="封面">-->
|
||||
<!-- <template slot-scope="scope">-->
|
||||
<!-- <el-image -->
|
||||
<!-- v-if="scope.row.coverImage"-->
|
||||
<!-- style="width: 50px; height: 50px"-->
|
||||
<!-- :src="scope.row.coverImage"-->
|
||||
<!-- :preview-src-list="[scope.row.coverImage]"-->
|
||||
<!-- fit="cover">-->
|
||||
<!-- </el-image>-->
|
||||
<!-- <span v-else>-</span>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<el-table-column
|
||||
prop="materialType"
|
||||
prop="title"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="文章类型">
|
||||
align="left"
|
||||
min-width="200"
|
||||
label="标题"
|
||||
show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ dictData[0] | findByValue(scope.row.materialType) }}
|
||||
<el-tag v-if="scope.row.isTop === 1" type="danger" size="mini" style="margin-right: 5px;">置顶</el-tag>
|
||||
<span class="title-link" @click="viewDetail(scope.row.id)">{{ scope.row.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="materialCode"
|
||||
prop="summary"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="文章标题">
|
||||
align="left"
|
||||
min-width="180"
|
||||
label="摘要"
|
||||
show-overflow-tooltip>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="materialName"
|
||||
prop="authorName"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="文章描述">
|
||||
width="100"
|
||||
label="作者">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createName"
|
||||
prop="status"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="创建人">
|
||||
width="90"
|
||||
label="状态">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === 'DRAFT'" type="info" size="small">草稿</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 'PUBLISHED'" type="success" size="small">已发布</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 'REVIEWING'" type="warning" size="small">审核中</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 'ARCHIVED'" type="danger" size="small">已归档</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
prop="viewCount"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="创建时间">
|
||||
width="80"
|
||||
label="浏览">
|
||||
<template slot-scope="scope">
|
||||
<span><i class="el-icon-view"></i> {{ scope.row.viewCount || 0 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="storageId"
|
||||
prop="likeCount"
|
||||
header-align="center"
|
||||
align="center"
|
||||
label="附件">
|
||||
width="80"
|
||||
label="点赞">
|
||||
<template slot-scope="scope">
|
||||
<span><i class="el-icon-star-off"></i> {{ scope.row.likeCount || 0 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="tags"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="120"
|
||||
label="标签">
|
||||
<template slot-scope="scope">
|
||||
<el-tag
|
||||
v-for="(tag, index) in getTagList(scope.row.tags)"
|
||||
:key="index"
|
||||
size="mini"
|
||||
style="margin: 2px;">
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="publishTime"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
label="发布时间">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
fixed="right"
|
||||
header-align="center"
|
||||
align="center"
|
||||
width="150"
|
||||
width="260"
|
||||
label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.materialId)">修改</el-button>
|
||||
<el-button type="text" size="small" @click="deleteHandle(scope.row.materialId)">删除</el-button>
|
||||
<el-button type="text" size="small" @click="viewDetail(scope.row.id)" icon="el-icon-view">查看</el-button>
|
||||
<!-- 只有草稿状态才允许编辑 -->
|
||||
<el-button
|
||||
v-if="scope.row.status === 'DRAFT'"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="addOrUpdateHandle(scope.row.id)"
|
||||
icon="el-icon-edit">
|
||||
编辑
|
||||
</el-button>
|
||||
<!-- 只有草稿状态才显示发布按钮 -->
|
||||
<el-button
|
||||
v-if="scope.row.status === 'DRAFT'"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="publishHandle(scope.row.id)"
|
||||
icon="el-icon-upload2">
|
||||
发布
|
||||
</el-button>
|
||||
<!-- 只有已发布状态才显示下架按钮 -->
|
||||
<el-button
|
||||
v-if="scope.row.status === 'PUBLISHED'"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="unpublishHandle(scope.row.id)"
|
||||
icon="el-icon-download">
|
||||
下架
|
||||
</el-button>
|
||||
<el-button type="text" size="small" @click="deleteHandle(scope.row.id)" icon="el-icon-delete">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<el-pagination
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
@@ -89,19 +199,32 @@
|
||||
:total="totalPage"
|
||||
layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" :dictData="dictData" @refreshDataList="getDataList"></add-or-update>
|
||||
|
||||
<!-- 弹窗:新增/修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
|
||||
|
||||
<!-- 弹窗:详情 -->
|
||||
<knowledge-detail v-if="detailVisible" ref="knowledgeDetail" @refreshDataList="getDataList"></knowledge-detail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddOrUpdate from './knowledge-add-or-update'
|
||||
import { apiUtils } from '@/utils/dict'
|
||||
import KnowledgeDetail from './knowledge-detail'
|
||||
|
||||
export default {
|
||||
name: 'Knowledge',
|
||||
components: {
|
||||
AddOrUpdate,
|
||||
KnowledgeDetail
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dataForm: {
|
||||
key: ''
|
||||
title: '',
|
||||
status: '',
|
||||
orderBy: 'create_time',
|
||||
order: 'desc'
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
@@ -110,39 +233,39 @@ export default {
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
addOrUpdateVisible: false,
|
||||
dictConfigs: [{type: 'dict', code: 'material_type'}],
|
||||
dictData: []
|
||||
detailVisible: false
|
||||
}
|
||||
},
|
||||
mixins: [apiUtils],
|
||||
components: {
|
||||
AddOrUpdate
|
||||
},
|
||||
activated () {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList () {
|
||||
// this.dataListLoading = true
|
||||
// this.$http({
|
||||
// url: this.$http.adornUrl('/material/material/list'),
|
||||
// method: 'get',
|
||||
// params: this.$http.adornParams({
|
||||
// 'page': this.pageIndex,
|
||||
// 'limit': this.pageSize,
|
||||
// 'key': this.dataForm.key
|
||||
// })
|
||||
// }).then(({data}) => {
|
||||
// if (data && data.code === 200) {
|
||||
// this.dataList = data.page.list
|
||||
// this.totalPage = data.page.totalCount
|
||||
// } else {
|
||||
// this.dataList = []
|
||||
// this.totalPage = 0
|
||||
// }
|
||||
// this.dataListLoading = false
|
||||
// })
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/knowledge/list'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData({
|
||||
page: this.pageIndex,
|
||||
limit: this.pageSize,
|
||||
title: this.dataForm.title,
|
||||
status: this.dataForm.status,
|
||||
orderBy: this.dataForm.orderBy,
|
||||
order: this.dataForm.order
|
||||
})
|
||||
}).then(({data}) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalPage = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalPage = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
}).catch(() => {
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle (val) {
|
||||
@@ -159,31 +282,34 @@ export default {
|
||||
selectionChangeHandle (val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 新增 / 修改
|
||||
// 新增/修改
|
||||
addOrUpdateHandle (id) {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(id)
|
||||
})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle (id) {
|
||||
var ids = id ? [id] : this.dataListSelections.map(item => {
|
||||
return item.materialId
|
||||
// 查看详情
|
||||
viewDetail (id) {
|
||||
this.detailVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.knowledgeDetail.init(id)
|
||||
})
|
||||
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
},
|
||||
// 发布
|
||||
publishHandle (id) {
|
||||
this.$confirm('确定要发布该知识文档吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/material/material/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(ids, false)
|
||||
url: this.$http.adornUrl(`/knowledge/publish/${id}`),
|
||||
method: 'post'
|
||||
}).then(({data}) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
message: '发布成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
@@ -195,7 +321,85 @@ export default {
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
// 下架
|
||||
unpublishHandle (id) {
|
||||
this.$confirm('确定要下架该知识文档吗?下架后将变为草稿状态,可重新编辑。', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/knowledge/unpublish/${id}`),
|
||||
method: 'post'
|
||||
}).then(({data}) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '下架成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.getDataList()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle (id) {
|
||||
var ids = id ? [id] : this.dataListSelections.map(item => {
|
||||
return item.id
|
||||
})
|
||||
this.$confirm(`确定对选中的${ids.length}条记录进行删除操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
// 批量删除
|
||||
let deletePromises = ids.map(itemId => {
|
||||
return this.$http({
|
||||
url: this.$http.adornUrl(`/knowledge/delete/${itemId}`),
|
||||
method: 'delete'
|
||||
})
|
||||
})
|
||||
|
||||
Promise.all(deletePromises).then(() => {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.getDataList()
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
this.$message.error('删除失败')
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
// 解析标签
|
||||
getTagList (tags) {
|
||||
if (!tags) return []
|
||||
return tags.split(',').filter(tag => tag.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mod-knowledge {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title-link {
|
||||
color: #409EFF;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.title-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user