一、实现效果
1、新增页面
展示默认选中信息,缩略图上传裁剪,标签新增,富文本编辑器
2、编辑页面
同新增,在其基础上增加了默认值的展示
二、表单初始搭建
1、计算获取表单数据
使用计算属性获取表单选项-由于 statusArray 和 categoryData 是响应式引用(ref),直接使用 .value 会导致初始渲染时数据为空,而后续数据更新不会自动反映到表单中。
2、写入表单基本信息
(1)必填数据
(2)选填数据
(3)给新增的窗口一个默认值
发现在给表单默认值的时候,新增窗口的默认值赋予的是{},所以到值新增时状态不会自动勾选(这里就以需要的状态默认为例)
在主页面表单数据中,加入一个字段defaultData,专门用于存储每项的默认值
在Table组件中,对含有默认值的进行循环处理,并赋值给表单数据
三、封面图完善
1、增加缩略图
(1)设置封面图的类型为type
(2)设置缩略图类型
表单中引入缩略图组件,并且加入新类型cropper
(3)修改原样式
由于之前定义的样式会影响现在的样式,所以需要去掉,由父决定
设置样式为父页面传递的参数(这增加了新参数borderRadius决定圆角)
在父页面表单组件中增加了圆角定义(这里可以不加,因为默认就是5px)
2、增加上传成功后的图片显示
之前图片上传成功之后有,是会将图片地址暴露给父组件的,这里直接用
在父页面直接使用该方法
调用方法设置图片
四、表单中标签的设置
1、官网参考
2、tag组件初始搭建
(1)新建标签组件
组件下新建Tag.vue
(2)视图层
直接复制官网提供的代码,在原来基础上增加每个标签的右边距
(3)逻辑层基本功能实现
根据官网提供的方法完成基本逻辑层代码编写
3、标签使用+标签数据传递
(1)表单中使用标签组件
引入标签组件
在表单中定义标签类型,并将标签的值传递给标签组件ruleForm[item.field]
(2)主页面中修改标签的类型
将标签的类型修改为tag
(3)标签组件获取传递的标签数组
在Tag组件中,获取传递的tagArray的值
使用watch监听,将获取的值放入标签数组dynamicTags
- 监听参数中的tagArray,如果有变化,就将标签数组dynamicTags的值设置为解析过的tagArray的最新值
- 这里使用数组解析[…newVal]是为了不影响别的地方设置的标签
4、解决提交表单标签的数据问题
发现以上代码完成后,表单中提交后,标签的值未进行变化,这里需要每次标签值变化时进行监听,并将值返回给父页面,然后再在父页面获取值,并将该项的值设置为获取到的值
标签页在每个需要数据变化的时候执行emit将数据返回给父页面
父页面处理数据
父页面中调用方法,获取到变化后的数据,并将值直接赋值给该标签
五 、富文本编辑器
1、vue-markdown的安装
官网:v-md-editor
这里使用npm安装
npm i @kangc/v-md-editor@next -S
安装成功
2、markdown组件封装
新建组件Markdown.vue
根据官方提供的引入与使用语法进行编写
3、主页面引入
(1)修改内容类型为markdown
(2)表单中增加markdown类型
引用markdown组件
使用富文本
4、默认值写入
在编辑窗口中是有默认值的,一打开表单是希望有默认值的展示
5、监听markdown中数据变化
当markdown中数据变化,需要实时更新父组件中的对应的值,以便提交的时候能准确获取到markdown的最新数据
markdown实时监听其中的数据变化,并将值暴露给父页面
父页面中执行方法,并将更新的数据赋值给当前项
6、图片上传
(1)建立多文件上传的api
新建接口:传递必要的数组参数files
新建期望:成功后返回多张图片信息
(2)参考官网
图片上传的基本用法
图片上传的基本参数
(3)加入图片上传功能
增加文件上传的必要参数
- 上传图片菜单默认为禁用状态 设置 disabled-menus 为空数组可以开启。
- @upload-image :图片上传的事件
- upload-image-config:图片上传的基本属性
- accept:允许上传图片类型
- multiple:是否允许上传多张图片
方法实现
根据官方给的方法进行编写
- 执行上传
- 接口请求
- 返回的数据写入到markdown
7、增加代码行号显示
参考官网可知,直接引入插件使用即可
六 、解决单条数据查询数据量大的问题
由于内容这个字段一般比较多,所以全部请求可能或导致数据返回不完整,所以需要用到单条数据请求返回数据进行查询
之前权限管理使用过类似功能,只需要传递apiUrl的信息即可
1、参考
传递参数与url的值
Table组件中会接收信息,并根据路径进行请求,最后将值传递给表单并赋值,进行显示
2、Apifox接口信息
在之前文章中已经完成了接口书写
可参考:vue3:十八、内容管理-实现内容的数据展示,开关switch设行,tag标签展示
3、写入apiUrl
4、效果展示
可看出已经实现了单项数据请求的功能
七、完整代码
1、内容管理
src/views/ContentView.vue
<template>
<Table :columns="columns" :apiUrl='apiUrl' :showPage="true" :showSearch="true" @update:tableData="updateTableData"
:formItems="formItems">
<template #tag="{ row }">
<el-tag v-for="item, index in row.tag" :key="index">
{
{ item }}
</el-tag>
</template>
<template #audit="{ row }">
<el-tag :type="row.audit == 1 ? 'success' : row.audit == 2 ? 'danger' : 'primary'">
{
{ auditObj[row.audit] }}
</el-tag>
<!-- 如果是待签核,可以执行签核 -->
<div v-if="row.audit == 0" class="mt-10">
<el-button size="small" type="success" @click="UpdateAuditStatus(row, 1)">同意</el-button>
<el-button size="small" class="ml-0" type="danger" @click="UpdateAuditStatus(row, 2)">拒绝</el-button>
</div>
</template>
<template #status="{ row }">
<el-switch v-model="row.status" :loading="row.loading || false"
:before-change="beforeChange.bind(this, row)" :active-value="1" :inactive-value="0"
:active-text="statusObj[1]" :inactive-text="statusObj[0]" inline-prompt />
</template>
<template #coverimage="{ row, index }">
<el-image style="width: 100px; height: 100px" :src="row.coverimage" :zoom-rate="1.2" :max-scale="7"
:min-scale="0.2" :preview-src-list="srcList" show-progress :initial-index="initialindex"
:preview-teleported="true" :hide-on-click-modal="true" fit="contain" @show="showImg(index)" />
</template>
</Table>
</template>
<script setup>
import {
ref, reactive, computed } from 'vue'
import Table from '@/components/Table.vue'
import {
showStatus, auditStatus } from '@/api/status'
import {
ElMessage, ElMessageBox } from 'element-plus'
import {
updateStatus, updateAudit } from '@/api/content'
import {
getCategoryList } from '@/api/category'
//表格列
const columns = [
{
field: 'id', label: 'ID' },
{
field: 'title', label: '标题' },
{
field: 'subtitle', label: '副标题' },
{
field: 'coverimage', label: '封面图' },
{
field: 'categroy', label: '分类', searchFormType: 'select', searchList: () => {
return categoryData.value } },
{
field: 'tag', label: '标签' },
{
field: 'author', label: '作者' },
{
field: 'clicknum', label: '点击量', searchFormType: 'numrange' },
{
field: 'desc', label: '描述' },
{
field: 'status', label: '状态', searchFormType: 'radio', searchList: () => {
return statusArray.value } },
{
field: 'audit', label: '审核状态', searchFormType: 'select', searchList: () => {
return auditArray.value } },
{
field: 'createtime', label: '创建时间', searchFormType: 'daterange' },
]
//路径
const apiUrl = {
list: '/content/list',
del: '/content/del',
add: '/content/add',
edit: '/content/edit',
defaultselect: '/content/getcontentbyid'
}
//状态切换
const beforeChange = (row) => {
row.loading = true //开启loading
return new Promise((resolve) => {
//修改后端状态
updateStatus({
id: row.id }).then(res => {
row.loading = false //停止loading
if (res.code == 1) {
ElMessage.success(res.msg || '修改成功')
return resolve(true)
}
else {
ElMessage.error(res.msg || '修改失败')
return resolve(false)
}
}).catch(() => {
row.loading = false //停止loading
ElMessage.error('修改失败')
return resolve(false)
})
})
}
//预览图片列表
const srcList = ref([])
const updateTableData = (data) => {
srcList.value = data.map(item => item.coverimage)
}
//预览图片索引
const initialindex = ref(0)
const showImg = (index) => {
initialindex.value = index
}
//审核状态更改
const UpdateAuditStatus = (row, status) => {
//同意
if (status == 1) {
ElMessageBox.confirm('确定审核通过吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then(() => {
updateAudit({
id: row.id, audit: status }).then(res => {
if (res.code == 1) {
row.audit = 1; //更改当前行审核状态
ElMessage.success(res.msg || '审核成功') //提示信息
}
else {
ElMessage.error(res.msg || '审核失败')
}
})
})
}
//拒绝
else {
ElMessageBox.prompt('请输入拒绝理由', '拒绝原因', {
comfirmButtonText: '提交',
cancelButton: '取消',
inputType: 'textarea',
inputValidator: (val) => {
if (!val) return '请输入拒绝理由'
}
}).then(({
value }) => {
updateAudit({
id: row.id, audit: status, reason: value }).then(res => {
if (res.code == 1) {
row.audit = 2; //更改当前行审核状态
ElMessage.success(res.msg || '审核成功') //提示信息
}
else {
ElMessage.error(res.msg || '审核失败')
}
})
})
}
}
//定义分类下拉数据
const categoryData = ref({
})
//获取分类
getCategoryList().then(res => {
if (res.code == 1) {
categoryData.value = res.data.map(item => ({
value: item.id,
label: item.name
}));
}
})
// 显示状态
const statusObj = ref({
})
const statusArray = ref([])
showStatus().then(res => {
if (res.code == 1) {
res.data.forEach((item) => {
statusObj.value[item.value] = item.label
})
statusArray.value = res.data
}
})
// 审核状态
const auditObj = ref({
})
const auditArray = ref([])
auditStatus().then