笔记来源:拉勾教育 - 大前端就业集训营
文章内容:学习过程中的笔记、感悟、和经验
课程管理
布局与准备工作
基础布局与内容展示和之前一样
-
单独封装组件
-
使用分页查询课程信息接口
-
实现查询、重置,筛选功能
// src/services/coures.js 新建文件,封装课程相关接口
// 引入接口模块
import request from '@/utils/request'
// 分页查询课程信息
export const getQueryCourses = data => {
return request({
method: 'post',
url: '/boss/course/getQueryCourses',
data
})
}
src/views/course/index.vue 课程管理组件,直接引入子组件
<template>
<!-- 子组件实例 -->
<course-content></course-content>
</template>
<script>
// 引入子组件
import courseContent from './son/content'
export default {
name: 'course',
// 注册子组件
components: {
courseContent
}
}
</script>
src/views/course/son/content.vue 课程管理直接子组件
<template>
<!-- 子组件容器 -->
<div class="course-content">
<!-- 顶部筛选功能区 -->
<el-card class="box-card">
<!-- 标题 -->
<div slot="header" class="clearfix">
<span>数据筛选</span>
</div>
<!-- 数据筛选表单 -->
<el-form :inline="true" :model="form" class="demo-form-inline" ref="form">
<el-form-item label="课程名称" prop="courseName">
<el-input v-model="form.courseName" placeholder="课程名称"></el-input>
</el-form-item>
<!-- 选择器:选择课程当前状态 -->
<el-form-item label="当前状态" prop="status">
<el-select v-model="form.status" placeholder="请选择当前状态">
<el-option
v-for="item in selectOptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<!-- 重置和查询功能按钮 - 设置点击事件 -->
<el-button @click="reset()">重置</el-button>
<el-button @click="queryCoures()" type="primary">查询</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 底部展示区卡片 -->
<el-card class="box-card">
<!-- 顶部标题和添加课程按钮 -->
<div slot="header" class="clearfix">
<span>课程列表</span>
<el-button style="float: right; padding: 3px 0" type="text">添加课程</el-button>
</div>
<!-- 用于展示课程的列表 -->
<el-table
:data="tableData"
style="width: 100%">
<el-table-column
prop="id"
label="ID"/>
<el-table-column
prop="courseName"
label="课程名称"/>
<el-table-column
prop="price"
label="价格"/>
<el-table-column
prop="sortNum"
label="排序"/>
<el-table-column
prop="status"
label="上架状态">
</el-table-column>
<el-table-column
label="操作">
<template slot-scope="scope">
<!-- 操作按钮 -->
<el-button @click="editCoures(scope.row)">编辑</el-button>
<el-button @click="managementCoures(scope.row)">内容管理</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page.sync="form.currentPage"
:page-sizes="[10, 15, 20]"
:page-size.sync="form.pageSize"
layout="total, sizes, prev, pager, next"
:total="total">
</el-pagination>
</el-card>
</div>
</template>
<script>
// 引入封装的接口:分页查询课程信息
import { getQueryCourses } from '@/services/coures'
export default {
name: 'course-content',
data () {
return {
// 顶部筛选数据
form: {
currentPage: 1,
pageSize: 10,
courseName: '',
status: ''
},
// 课程当前状态选择器选项
selectOptions: [
{ value: '', label: '全部' },
{ value: '1', label: '上架' },
{ value: '0', label: '下架' }
],
// 课程展示列表数据
tableData: [],
// 课程总数,绑定分页组件
total: 0
}
},
// 钩子函数
created () {
// 初始化获取课程
this.getCourses()
},
methods: {
// 页码变化,直接获取课程
handleCurrentChange () {
this.getCourses()
},
// 每页条数变化,先重置页码,之后获取课程
handleSizeChange () {
this.form.currentPage = 1
console.log('更改了每页条数')
this.getCourses()
},
// 重置
reset () {
// 使用表单组件的重置功能
this.$refs.form.resetFields()
},
// 查询课程,先重置页码,再查询课程
queryCoures () {
this.form.currentPage = 1
this.getCourses()
},
// 编辑课程
editCoures () {
console.log('编辑课程')
},
// 内容管理
managementCoures () {
console.log('内容管理')
},
// 获取课程函数
async getCourses () {
// 获取数据
const { data } = await getQueryCourses(this.form)
if (data.code === '000000') {
// 获取成功,把数据交给列表和分页组件
this.tableData = data.data.records
this.total = data.data.total
}
}
}
}
</script>
上下架状态显示
使用开关组件,注意需要绑定一下数据,因为我们返回的是1和0,而默认需要布尔值
状态需要使用请求回来的数据中
src/views/course/son/content.vue 修改展示列表中上下架状态显示
<el-table-column
label="是否上架">
<template slot-scope="scope">
<!-- 上架状态展示及操作开关,重设开关时候的值为0和1 -->
<el-switch
v-model="scope.row.status"
active-color="#409eff"
:active-value="1"
:inactive-value="0">
</el-switch>
</template>
</el-table-column>
上下架操作
-
使用上下架接口-params
-
开关组件提供了事件可以进行控制
-
设置成功后显示提示信息
// src/services/coures.js 封装课程上下架接口
// 课程上下架
export const changeState = data => {
return request({
method: 'get',
url: '/boss/course/changeState',
params: {
courseId: data.courseId,
status: data.status
}
})
}
src/views/course/son/content.vue 给开关组件添加状态修改事件
<el-table-column
label="是否上架">
<template slot-scope="scope">
<!-- 上架状态展示及操作开关,重设开关时候的值为0和1,监听状态修改 -->
<el-switch
@change="switchChange(scope.row)"
v-model="scope.row.status"
active-color="#409eff"
:active-value="1"
:inactive-value="0">
</el-switch>
</template>
</el-table-column>
<script>
// 引入封装的接口:分页查询课程信息、课程上下架
import { getQueryCourses, changeState } from '@/services/coures'
.............
async switchChange (row) {
// 调用上下架接口
const { data } = await changeState({
// 设置传入参数
courseId: row.id,
status: row.status
})
if (data.code === '000000') {
// 如果修改成功弹出提示、更新数据
this.$message.success('状态修改成功')
this.getCourses()
}
},
.............
</script>
上下架优化
为了避免用户频繁操作,导致可能会出现问题
-
设置在调用上下架接口之前禁用掉开关,调用后再开启-disabled
-
在每个数据中都添加一个信号值
src/views/course/son/content.vue 给每个开关添加禁用属性,在初次获取数据的时候就给每条数据添加一个标记值,当修改开关的时候修改信号值
<el-table-column
label="是否上架">
<template slot-scope="scope">
<!-- 上架状态展示及操作开关,重设开关时候的值为0和1,监听状态修改,添加是否禁用属性disabled,使用row中的数据 -->
<el-switch
:disabled="scope.row.disabled"
@change="switchChange(scope.row)"
v-model="scope.row.status"
active-color="#409eff"
:active-value="1"
:inactive-value="0">
</el-switch>
</template>
</el-table-column>
............
<script>
...............
// 状态修改事件函数
async switchChange (row) {
// 调用接口前将开关组件禁用
row.disabled = true
// 调用上下架接口
const { data } = await changeState({
// 设置传入参数
courseId: row.id,
status: row.status
})
if (data.code === '000000') {
// 如果修改成功弹出提示、更新数据
this.$message.success('状态修改成功')
this.getCourses()
}
// 最后启用开关
row.disabled = false
}
................
新建课程添加菜单布局
-
新建组件
-
设置路由
-
点击添加课程按钮跳转到页面
-
使用进度条组件,使用v-for
-
步骤对应操作区域可以使用v-show进行切换
-
添加下一步
-
还有保存按钮
src/router/index.js 添加新路由
{// 添加课程
path: '/addCourse',
name: 'addCourse',
component: () => import(/* webpackChunkName:'addCourse' */ '@/views/course/add')
}
src/views/course/son/content.vue 给添加课程按钮添加点击事件跳转到添加课程组件
<!-- 添加课程按钮添加事件跳转添加页面 -->
<el-button style="float: right; padding: 3px 0" type="text" @click="$router.push('/addCourse')">添加课程</el-button>
src/views/course/add.vue 添加课程页面组件
<template>
<!-- 容器 -->
<el-card>
<!-- 顶部导航 -->
<div slot="header" class="clearfix">
<!-- 步骤条组件 -->
<el-steps :active="nowSteps" simple>
<!-- 循环遍历数据添加结构 -->
<el-step v-for="item in steps" :key="item.id" :title="item.title" :icon="item.icon"></el-step>
</el-steps>
</div>
<!-- 底部表单 -->
<el-form ref="form" :model="form" label-width="80px">
<!-- 五个步骤,绑定数据控制显示和隐藏 -->
<div v-show="nowSteps === 0">基本信息</div>
<div v-show="nowSteps === 1">课程封面</div>
<div v-show="nowSteps === 2">销售信息</div>
<div v-show="nowSteps === 3">秒杀信息</div>
<div v-show="nowSteps === 4">课程详情</div>
<!-- 最下方按钮 -->
<el-form-item>
<!-- 下一步按钮前四步都显示可跳转到下一步 -->
<el-button v-show="nowSteps < 4" @click="nowSteps += 1" type="primary">下一步</el-button>
<!-- 保存按钮只有最后一步才显示,用于保存数据 -->
<el-button v-show="nowSteps === 4" type="primary">保存</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
export default {
name: 'addCourse',
data () {
return {
// 当前步骤
nowSteps: 0,
// 步骤条组件数据
steps: [
{ id: 0, title: '基本信息', icon: 'el-icon-edit' },
{ id: 1, title: '课程封面', icon: 'el-icon-picture-outline' },
{ id: 2, title: '销售信息', icon: 'el-icon-shopping-cart-full' },
{ id: 3, title: '秒杀信息', icon: 'el-icon-present' },
{ id: 4, title: '课程详情', icon: 'el-icon-document' }
],
// form表单
form: {}
}
}
}
</script>
步骤条操作
-
给步骤条绑定点击事件,可以跳转回对应步骤
-
修改鼠标样式
src/views/course/add.vue 给步骤条添加点击事件修改当前步骤,并且CSS设置手型鼠标
<!-- 步骤条组件 -->
<el-steps :active="nowSteps" simple>
<!-- 循环遍历数据添加结构,添加点击事件(更改当前步骤) -->
<el-step v-for="item in steps" :key="item.id" :title="item.title" :icon="item.icon" @click.native="nowSteps = item.id"></el-step>
</el-steps>
<style lang="scss" scoped>
// 步骤条
.el-step {
// 手型鼠标
cursor: pointer
}
</style>
基本信息和课程封面、销售、秒杀、课程详情布局
-
使用计数器组件
-
使用上传组件
-
样式可能需要深度作用选择器建议使用
::v-deep -
输入框组件可以设置单位展示
-
秒杀活动使用开关组件控制显示和隐藏
-
课程详情可以先使用文本域代替,后面要使用富文本编辑器
src/views/course/add.vue 书写全部课程信息结构(但是还没绑定数据)
<template>
<!-- 容器 -->
<el-card>
<!-- 顶部导航 -->
<div slot="header" class="clearfix">
<!-- 步骤条组件 -->
<el-steps :active="nowSteps" simple>
<!-- 循环遍历数据添加结构,添加点击事件(更改当前步骤) -->
<el-step v-for="item in steps" :key="item.id" :title="item.title" :icon="item.icon" @click.native="nowSteps = item.id"></el-step>
</el-steps>
</div>
<!-- 底部表单 -->
<el-form ref="form" :model="form" label-width="80px">
<!-- 基本信息 -->
<div v-show="nowSteps === 0">
<el-form-item label="课程名称">
<el-input v-model="form.name" placeholder="请输入课程名称"></el-input>
</el-form-item>
<el-form-item label="课程简介">
<el-input v-model="form.name" placeholder="请输入课程简介"></el-input>
</el-form-item>
<el-form-item label="课程概述">
<!-- 设置样式让两个输入框在一行显示 -->
<el-input v-model="form.name" style="width: 49%; minwidth: 300px; margin-right: 10px" placeholder="请输入第一条课程概述">
<!-- 使用slot="append"在后天添加单位或者描述 -->
<template slot="append">0 / 20</template>
</el-input>
<el-input v-model="form.name" style="width: 49%; minwidth: 300px;" placeholder="请输入第二条课程概述"></el-input>
</el-form-item>
<el-form-item label="讲师姓名">
<el-input v-model="form.name" placeholder="请输入讲师姓名"></el-input>
</el-form-item>
<el-form-item label="讲师简介">
<el-input v-model="form.name" placeholder="请输入讲师简介"></el-input>
</el-form-item>
<el-form-item label="课程排序">
<el-input-number v-model="num" controls-position="right" :min="1"></el-input-number>
</el-form-item>
</div>
<!-- 课程封面 -->
<div v-show="nowSteps === 1">
<el-form-item label="课程封面">
<!-- 上传组件 -->
<el-upload
class="avatar-uploader"
action="https://jsonplaceholder.typicode.com/posts/"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item label="解锁封面">
<el-upload
class="avatar-uploader"
action="https://jsonplaceholder.typicode.com/posts/"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="imageUrl" :src="imageUrl2" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
</div>
<!-- 销售信息 -->
<div v-show="nowSteps === 2">
<el-form-item label="售卖价格">
<el-input v-model="form.name" placeholder="请输入售卖价格">
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="商品原价">
<el-input v-model="form.name" placeholder="请输入商品原价">
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="销量">
<el-input v-model="form.name" placeholder="请输入销量">
<template slot="append">单</template>
</el-input>
</el-form-item>
<el-form-item label="活动标签">
<el-input v-model="form.name" placeholder="请输入活动标签"></el-input>
</el-form-item>
</div>
<!-- 限时秒杀 -->
<div v-show="nowSteps === 3">
<!-- 开关组件,绑定数据 -->
<el-form-item label="限时秒杀开关" label-width="120px">
<el-switch
v-model="miaosha"
active-color="#409eff"
>
</el-switch>
</el-form-item>
<!-- 限时秒杀信息,根据秒杀开关是否打开决定是否显示 -->
<div v-if="miaosha">
<el-form-item label="开始时间">
<el-date-picker
v-model="value1"
type="datetime"
placeholder="选择开始日期时间">
</el-date-picker>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="value1"
type="datetime"
placeholder="选择结束日期时间">
</el-date-picker>
</el-form-item>
<el-form-item label="秒杀价">
<el-input v-model="form.name" placeholder="请输入秒杀价">
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="库存">
<el-input v-model="form.name" placeholder="请输入库存">
<template slot="append">个</template>
</el-input>
</el-form-item>
</div>
</div>
<!-- 课程详情 -->
<div v-show="nowSteps === 4">
<!-- 这里使用文本域,后期换成富文本编辑器 -->
<el-form-item label="课程详情" label-width="120px">
<el-input
type="textarea"
:rows="12"
placeholder="请输入课程详细描述信息"
v-model="textarea">
</el-input>
</el-form-item>
</div>
<!-- 最下方按钮 -->
<el-form-item>
<!-- 下一步按钮前四步都显示可跳转到下一步 -->
<el-button v-show="nowSteps < 4" @click="nowSteps += 1" type="primary">下一步</el-button>
<!-- 保存按钮只有最后一步才显示,用于保存数据 -->
<el-button v-show="nowSteps === 4" type="primary">保存</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
export default {
name: 'addCourse',
data () {
return {
// 当前步骤
nowSteps: 4,
// 步骤条组件数据
steps: [
{ id: 0, title: '基本信息', icon: 'el-icon-edit' },
{ id: 1, title: '课程封面', icon: 'el-icon-picture-outline' },
{ id: 2, title: '销售信息', icon: 'el-icon-shopping-cart-full' },
{ id: 3, title: '秒杀信息', icon: 'el-icon-present' },
{ id: 4, title: '课程详情', icon: 'el-icon-document' }
],
// form表单
form: {},
// 基本信息计数器数据,后期会全部写在form内
num: 1,
// 两个上传的封面地址
imageUrl: '',
imageUrl2: '',
// 秒杀开关,后期会全部写在form内
miaosha: false
}
},
methods: {
// 上传成功钩子函数
handleAvatarSuccess (res, file) {
// 设置展示图片地址
this.imageUrl = URL.createObjectURL(file.raw)
},
// 上传之前钩子函数,检查是否符合要求
beforeAvatarUpload (file) {
const isJPG = file.type === 'image/jpeg'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!')
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!')
}
return isJPG && isLt2M
}
}
}
</script>
<style lang="scss" scoped>
// 步骤条
.el-step {
// 手型鼠标
cursor: pointer
}
// 上传组件样式,使用::v-deep进行深度设置样式
::v-deep .avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
::v-deep .avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
数据绑定
使用保存或更新课程信息接口,根据里面的接口信息设置名称
src/views/course/add.vue 将全部form组件和数据绑定,这里先暂时注释掉一些暂时用不到的数据,后期可能会用到
<template>
<!-- 容器 -->
<el-card>
<!-- 顶部导航 -->
<div slot="header" class="clearfix">
<!-- 步骤条组件 -->
<el-steps :active="nowSteps" simple>
<!-- 循环遍历数据添加结构,添加点击事件(更改当前步骤) -->
<el-step v-for="item in steps" :key="item.id" :title="item.title" :icon="item.icon" @click.native="nowSteps = item.id"></el-step>
</el-steps>
</div>
<!-- 底部表单 -->
<el-form ref="form" :model="form" label-width="80px">
<!-- 基本信息 -->
<div v-show="nowSteps === 0">
<el-form-item label="课程名称">
<el-input v-model="form.courseName" placeholder="请输入课程名称"></el-input>
</el-form-item>
<el-form-item label="课程简介">
<el-input v-model="form.brief" placeholder="请输入课程简介"></el-input>
</el-form-item>
<el-form-item label="课程概述">
<!-- 设置样式让两个输入框在一行显示 -->
<el-input v-model="form.previewFirstField" style="width: 49%; minwidth: 300px; margin-right: 10px" placeholder="请输入第一条课程概述">
<!-- 使用slot="append"在后天添加单位或者描述 -->
<template slot="append">0 / 20</template>
</el-input>
<el-input v-model="form.previewSecondField" style="width: 49%; minwidth: 300px;" placeholder="请输入第二条课程概述"></el-input>
</el-form-item>
<el-form-item label="讲师姓名">
<el-input v-model="form.teacherDTO.teacherName" placeholder="请输入讲师姓名"></el-input>
</el-form-item>
<el-form-item label="讲师简介">
<el-input v-model="form.teacherDTO.description" placeholder="请输入讲师简介"></el-input>
</el-form-item>
<el-form-item label="课程排序">
<el-input-number v-model="form.sortNum" controls-position="right" :min="1"></el-input-number>
</el-form-item>
</div>
<!-- 课程封面 -->
<div v-show="nowSteps === 1">
<el-form-item label="课程封面">
<!-- 上传组件 -->
<el-upload
class="avatar-uploader"
action="https://jsonplaceholder.typicode.com/posts/"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="form.courseListImg" :src="form.courseListImg" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item label="解锁封面">
<el-upload
class="avatar-uploader"
action="https://jsonplaceholder.typicode.com/posts/"
:show-file-list="false"
:on-success="handleAvatarSuccess2"
:before-upload="beforeAvatarUpload">
<img v-if="form.courseImgUrl" :src="form.courseImgUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
</div>
<!-- 销售信息 -->
<div v-show="nowSteps === 2">
<el-form-item label="售卖价格">
<el-input v-model="form.discounts" placeholder="请输入售卖价格">
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="商品原价">
<el-input v-model="form.price" placeholder="请输入商品原价">
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="销量">
<el-input v-model="form.sales" placeholder="请输入销量">
<template slot="append">单</template>
</el-input>
</el-form-item>
<el-form-item label="活动标签">
<el-input v-model="form.discountsTag" placeholder="请输入活动标签"></el-input>
</el-form-item>
</div>
<!-- 限时秒杀 -->
<div v-show="nowSteps === 3">
<!-- 开关组件,绑定数据 -->
<el-form-item label="限时秒杀开关" label-width="120px">
<el-switch
v-model="form.activityCourse"
active-color="#409eff"
>
</el-switch>
</el-form-item>
<!-- 限时秒杀信息,根据秒杀开关是否打开决定是否显示 -->
<div v-if="form.activityCourse">
<el-form-item label="开始时间">
<el-date-picker
v-model="form.activityCourseDTO.beginTime"
type="datetime"
placeholder="选择开始日期时间">
</el-date-picker>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="form.activityCourseDTO.endTime"
type="datetime"
placeholder="选择结束日期时间">
</el-date-picker>
</el-form-item>
<el-form-item label="秒杀价">
<el-input v-model="form.activityCourseDTO.amount" placeholder="请输入秒杀价">
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="库存">
<el-input v-model="form.activityCourseDTO.stock" placeholder="请输入库存">
<template slot="append">个</template>
</el-input>
</el-form-item>
</div>
</div>
<!-- 课程详情 -->
<div v-show="nowSteps === 4">
<!-- 这里使用文本域,后期换成富文本编辑器 -->
<el-form-item label="课程详情" label-width="120px">
<el-input
type="textarea"
:rows="12"
placeholder="请输入课程详细描述信息"
v-model="form.courseDescriptionMarkDown">
</el-input>
</el-form-item>
</div>
<!-- 最下方按钮 -->
<el-form-item>
<!-- 下一步按钮前四步都显示可跳转到下一步 -->
<el-button v-show="nowSteps < 4" @click="nowSteps += 1" type="primary">下一步</el-button>
<!-- 保存按钮只有最后一步才显示,用于保存数据 -->
<el-button v-show="nowSteps === 4" type="primary">保存</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
export default {
name: 'addCourse',
data () {
return {
// 当前步骤
nowSteps: 0,
// 步骤条组件数据
steps: [
{ id: 0, title: '基本信息', icon: 'el-icon-edit' },
{ id: 1, title: '课程封面', icon: 'el-icon-picture-outline' },
{ id: 2, title: '销售信息', icon: 'el-icon-shopping-cart-full' },
{ id: 3, title: '秒杀信息', icon: 'el-icon-present' },
{ id: 4, title: '课程详情', icon: 'el-icon-document' }
],
// form表单数据
form: {
// id: 0,
// 课程名称
courseName: '',
// 课程简介
brief: '',
teacherDTO: {
// id: 0,
// courseId: 0,
// 讲师姓名
teacherName: '',
// teacherHeadPicUrl: '',
// position: '',
// 讲师简介
description: ''
},
// 课程详情/描述信息
courseDescriptionMarkDown: '',
// 原价
price: null,
// 优惠价格
discounts: null,
// priceTag: '',
// 活动标签
discountsTag: '',
// isNew: true,
// isNewDes: '',
// 课程列表展示图片
courseListImg: '',
// 课程解锁封面
courseImgUrl: '',
// 课程排序
sortNum: 0,
// 课程概述/预览第一段
previewFirstField: '',
// 课程概述/预览第二段
previewSecondField: '',
// 当课程前状态
status: 0,
// 销量
sales: null,
// 是否开启秒杀
activityCourse: true,
activityCourseDTO: {
// id: 0,
// courseId: 0,
// 开始时间
beginTime: '',
// 结束时间
endTime: '',
// 秒杀价格
amount: null,
// 库存
stock: null
}
// autoOnlineTime: ''
}
}
},
methods: {
// 上传课程封面成功钩子函数
handleAvatarSuccess (res, file) {
this.form.courseListImg = URL.createObjectURL(file.raw)
},
// 上传解锁封面成功钩子函数
handleAvatarSuccess2 (res, file) {
this.form.courseImgUrl = URL.createObjectURL(file.raw)
},
// 上传之前钩子函数,检查是否符合要求
beforeAvatarUpload (file) {
const isJPG = file.type === 'image/jpeg'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!')
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!')
}
return isJPG && isLt2M
}
}
}
</script>
<style lang="scss" scoped>
// 步骤条
.el-step {
// 手型鼠标
cursor: pointer
}
// 上传组件样式,使用::v-deep进行深度设置样式
::v-deep .avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
::v-deep .avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
基本信息
设置输入框计数(上一步已经包括了)
封面上传
使用上传图片接口,获取上传的图片地址添加到上传的数据中
注意上传图片的data应该是formdata对象(new FormDate.append('file',data))
使用自定义上传,自带的上传太复杂,函数提供了上传文件的相关信息,直接使用即可
封装上传功能组件,使用数据的双向绑定
// src/services/coures.js 封装上传封面接口
..................
// 上传图片
export const upLoad = data => {
return request({
method: 'post',
url: '/boss/course/upload',
data
})
}
src/views/course/add.vue 将上传组件单独封装,然后add单独引入
<div v-show="nowSteps === 1">
<!-- 创建子组件实例,使用v-model双向绑定 -->
<!-- 课程封面 -->
<upload-img v-model="form.courseListImg" title="课程封面"></upload-img>
<!-- 解锁封面 -->
<upload-img v-model="form.courseImgUrl" title="解锁封面"></upload-img>
</div>
// 引入子组件
import uploadImg from './son/uploadImg'
// 注册组件
components: {
uploadImg
}
src/views/course/son/uploadImg.vue 新建自定义组件封装上传图片组件
<template>
<el-form-item :label="title">
<!-- 上传组件
http-request:自定义上传
on-success:上传成功触发
before-upload:上传之前触发
-->
<el-upload
:http-request="httpRequest"
class="avatar-uploader"
action=""
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
</template>
<script>
// 引入封装的图片上传接口
import { upLoad } from '@/services/coures'
export default {
// 接收参数
props: {
title: {
type: String
},
// value是v-model绑定的数据
value: {
type: String
}
},
data () {
return {
// 上传后预览地址
imageUrl: ''
}
},
methods: {
// 自定义上传
// 参数:组件自动把上传的信息打包作为参数
async httpRequest (obj) {
// 创建FormData实例
const fd = new FormData()
// 向FormData添加文件信息
fd.append('file', obj.file)
// 调用接口上传图片
const { data } = await upLoad(fd)
if (data.code === '000000') {
// 如果上传成功,利用v-model的自定义事件将地址传递给父组件
this.$emit('input', data.data.name)
}
},
// 上传课程封面成功钩子函数
handleAvatarSuccess (res, file) {
this.imageUrl = URL.createObjectURL(file.raw)
},
// 上传之前钩子函数,检查是否符合要求
beforeAvatarUpload (file) {
const isJPG = file.type === 'image/jpeg'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!')
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!')
}
return isJPG && isLt2M
}
}
}
</script>
<style scoped lang="scss">
// 上传组件样式,使用::v-deep进行深度设置样式
::v-deep .avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
::v-deep .avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
上传进度、进度百分比显示
使用进度条组件
上传的时候隐藏图片组件,上传完毕隐藏进度条显示图片
上传组件提供进度显示,但是我们自定义上传,导致这个功能会失效
h5的ajax中提供了onUploadProgress用于检测上传进度
上传完毕设置进度归0,上传为100后显示绿色成功
// src/services/coures.js 修改上传图片接口,添加一个用于获取进度的方法
// 上传图片接口,接受第二个参数一个函数
export const upLoad = (data, onUploadProgress) => {
return request({
method: 'post',
url: '/boss/course/upload',
data,
// onUploadProgress是H5提供的用于检测上传进度的方法
onUploadProgress
})
}
src/views/course/son/uploadImg.vue 添加进度条组件,并且获取上传进度在进度条上实时显示
<template>
<el-form-item :label="title">
<!-- 进度条组件,使用v-if控制显示和隐藏,设置进度条和上传组件不可同时出现 -->
<el-progress v-if="upLoading" type="circle" :percentage="percentage" :width="178"></el-progress>
<!-- 上传组件
http-request:自定义上传
on-success:上传成功触发
before-upload:上传之前触发
-->
<el-upload
v-else
:http-request="httpRequest"
class="avatar-uploader"
action=""
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
</template>
<script>
// 引入封装的图片上传接口
import { upLoad } from '@/services/coures'
export default {
name: 'uploadImg',
// 接收参数
props: {
title: {
type: String
},
// value是v-model绑定的数据
value: {
type: String
}
},
data () {
return {
// 上传后预览地址
imageUrl: '',
// 进度条进度
percentage: 0,
// 信号值 - 表示当前是否正在上传
upLoading: false
}
},
methods: {
// 用于获取上传中的进度数据
onUploadProgress (opt) {
// 修改本地进度条进度数据
this.percentage = Math.floor(opt.loaded / opt.total * 100)
},
// 自定义上传
// 参数:组件自动把上传的信息打包作为参数
async httpRequest (obj) {
// 创建FormData实例
const fd = new FormData()
// 向FormData添加文件信息
fd.append('file', obj.file)
// 调用接口上传图片,并且带上获取进度的函数
const { data } = await upLoad(fd, this.onUploadProgress)
if (data.code === '000000') {
// 如果上传成功,利用v-model的自定义事件将地址传递给父组件
this.$emit('input', data.data.name)
}
},
// 上传课程封面成功钩子函数
handleAvatarSuccess (res, file) {
this.imageUrl = URL.createObjectURL(file.raw)
this.upLoading = false
this.percentage = 0
},
// 上传之前钩子函数,检查是否符合要求
beforeAvatarUpload (file) {
this.upLoading = true
const isJPG = file.type === 'image/jpeg'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!')
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!')
}
return isJPG && isLt2M
}
}
}
</script>
<style scoped lang="scss">
// 上传组件样式,使用::v-deep进行深度设置样式
::v-deep .avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
::v-deep .avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
销售、秒杀数据绑定
绑定相关数据(上面步骤已经进行了绑定)
保存功能
最后把不需要的数据注释掉即可,因为新增课程不需要,这是编辑的时候才需要额
可以添加一个开关处理是否默认上架
保存后跳转回列表页
src/services/coures.js 封装保存更新课程接口
// 保存或者更新课程信息
export const saveOrUpdateCourse = data => {
return request({
method: 'post',
url: '/boss/course/saveOrUpdateCourse',
data
})
}
<template>
<!-- 容器 -->
<el-card>
<!-- 顶部导航 -->
<div slot="header" class="clearfix">
<!-- 步骤条组件 -->
<el-steps :active="nowSteps" simple>
<!-- 循环遍历数据添加结构,添加点击事件(更改当前步骤) -->
<el-step v-for="item in steps" :key="item.id" :title="item.title" :icon="item.icon" @click.native="nowSteps = item.id"></el-step>
</el-steps>
</div>
<!-- 底部表单 -->
<el-form ref="form" :model="form" label-width="80px">
<!-- 基本信息 -->
<div v-show="nowSteps === 0">
<el-form-item label="课程名称">
<el-input v-model="form.courseName" placeholder="请输入课程名称"></el-input>
</el-form-item>
<el-form-item label="课程简介">
<el-input v-model="form.brief" placeholder="请输入课程简介"></el-input>
</el-form-item>
<el-form-item label="课程概述">
<!-- 设置样式让两个输入框在一行显示 -->
<el-input v-model="form.previewFirstField" style="width: 49%; minwidth: 300px; margin-right: 10px" placeholder="请输入第一条课程概述">
<!-- 使用slot="append"在后天添加单位或者描述 -->
<template slot="append">0 / 20</template>
</el-input>
<el-input v-model="form.previewSecondField" style="width: 49%; minwidth: 300px;" placeholder="请输入第二条课程概述"></el-input>
</el-form-item>
<el-form-item label="讲师姓名">
<el-input v-model="form.teacherDTO.teacherName" placeholder="请输入讲师姓名"></el-input>
</el-form-item>
<el-form-item label="讲师简介">
<el-input v-model="form.teacherDTO.description" placeholder="请输入讲师简介"></el-input>
</el-form-item>
<el-form-item label="课程排序">
<el-input-number v-model="form.sortNum" controls-position="right" :min="1"></el-input-number>
</el-form-item>
</div>
<!-- 课程封面 -->
<div v-show="nowSteps === 1">
<!-- 创建子组件实例,使用v-model双向绑定 -->
<!-- 课程封面 -->
<upload-img v-model="form.courseListImg" title="课程封面"></upload-img>
<!-- 解锁封面 -->
<upload-img v-model="form.courseImgUrl" title="解锁封面"></upload-img>
</div>
<!-- 销售信息 -->
<div v-show="nowSteps === 2">
<el-form-item label="售卖价格">
<el-input v-model="form.discounts" placeholder="请输入售卖价格">
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="商品原价">
<el-input v-model="form.price" placeholder="请输入商品原价">
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="销量">
<el-input v-model="form.sales" placeholder="请输入销量">
<template slot="append">单</template>
</el-input>
</el-form-item>
<el-form-item label="活动标签">
<el-input v-model="form.discountsTag" placeholder="请输入活动标签"></el-input>
</el-form-item>
</div>
<!-- 限时秒杀 -->
<div v-show="nowSteps === 3">
<!-- 开关组件,绑定数据 -->
<el-form-item label="限时秒杀开关" label-width="120px">
<el-switch
v-model="form.activityCourse"
active-color="#409eff"
>
</el-switch>
</el-form-item>
<!-- 限时秒杀信息,根据秒杀开关是否打开决定是否显示 -->
<div v-if="form.activityCourse">
<el-form-item label="开始时间">
<el-date-picker
v-model="form.activityCourseDTO.beginTime"
type="datetime"
placeholder="选择开始日期时间">
</el-date-picker>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="form.activityCourseDTO.endTime"
type="datetime"
placeholder="选择结束日期时间">
</el-date-picker>
</el-form-item>
<el-form-item label="秒杀价">
<el-input v-model="form.activityCourseDTO.amount" placeholder="请输入秒杀价">
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="库存">
<el-input v-model="form.activityCourseDTO.stock" placeholder="请输入库存">
<template slot="append">个</template>
</el-input>
</el-form-item>
</div>
</div>
<!-- 课程详情 -->
<div v-show="nowSteps === 4">
<!-- 这里使用文本域,后期换成富文本编辑器 -->
<el-form-item label="课程详情" label-width="120px">
<el-input
type="textarea"
:rows="12"
placeholder="请输入课程详细描述信息"
v-model="form.courseDescriptionMarkDown">
</el-input>
</el-form-item>
<!-- 开关组件,绑定数据 -->
<el-form-item label="立即上架" label-width="120px">
<el-switch
v-model="form.status"
active-color="#409eff"
:active-value="1"
:inactive-value="0"
>
</el-switch>
</el-form-item>
</div>
<!-- 最下方按钮 -->
<el-form-item>
<!-- 下一步按钮前四步都显示可跳转到下一步 -->
<el-button v-show="nowSteps < 4" @click="nowSteps += 1" type="primary">下一步</el-button>
<!-- 保存按钮只有最后一步才显示,用于保存数据,添加点击事件 -->
<el-button v-show="nowSteps === 4" type="primary" @click="preservationCoures">保存</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
// 引入子组件
import uploadImg from './son/uploadImg'
// 引入封装的保存课程功能
import { saveOrUpdateCourse } from '@/services/coures'
export default {
name: 'addCourse',
data () {
return {
// 当前步骤
nowSteps: 0,
// 步骤条组件数据
steps: [
{ id: 0, title: '基本信息', icon: 'el-icon-edit' },
{ id: 1, title: '课程封面', icon: 'el-icon-picture-outline' },
{ id: 2, title: '销售信息', icon: 'el-icon-shopping-cart-full' },
{ id: 3, title: '秒杀信息', icon: 'el-icon-present' },
{ id: 4, title: '课程详情', icon: 'el-icon-document' }
],
// form表单
form: {
// id: 0,
// 课程名称
courseName: '',
// 课程简介
brief: '',
teacherDTO: {
// id: 0,
// courseId: 0,
// 讲师姓名
teacherName: '',
// teacherHeadPicUrl: '',
// position: '',
// 讲师简介
description: ''
},
// 课程详情/描述信息
courseDescriptionMarkDown: '',
// 原价
price: null,
// 优惠价格
discounts: null,
// priceTag: '',
// 活动标签
discountsTag: '',
// isNew: true,
// isNewDes: '',
// 课程列表展示图片
courseListImg: '',
// 课程解锁封面
courseImgUrl: '',
// 课程排序
sortNum: 0,
// 课程概述/预览第一段
previewFirstField: '',
// 课程概述/预览第二段
previewSecondField: '',
// 当课程前状态
status: 0,
// 销量
sales: null,
// 是否开启秒杀
activityCourse: false,
activityCourseDTO: {
// id: 0,
// courseId: 0,
// 开始时间
beginTime: '',
// 结束时间
endTime: '',
// 秒杀价格
amount: null,
// 库存
stock: null
}
// autoOnlineTime: ''
}
}
},
// 注册组件
components: {
uploadImg
},
methods: {
// 保存按钮点击事件函数
async preservationCoures () {
// 调用接口
const { data } = await saveOrUpdateCourse(this.form)
if (data.code === '000000') {
// 如果保存成功,跳转回课程管理页面
this.$router.push('/course')
}
}
}
}
</script>
<style lang="scss" scoped>
// 步骤条
.el-step {
// 手型鼠标
cursor: pointer
}
</style>
课程详情富文本编辑器
github中的流行富文本编辑器
- ckeditor
- quill
- ueditor(百度研发)
- tinymce
- wangeditor(本项目使用,比较新,维护度比较高,中文)
封装富文本编辑器
安装为开发依赖:npm i wangeditor --save
在src - components文件夹中创建公共的基础组件
引入组件并且绑定数据,输入参数,并且触发input事件输出值
输入和输出富文本编辑器都提供了方法
src/components/editor.vue 单独创建文件夹存放公共组件,创建editor.vue单独封装富文本编辑器
<template>
<!-- editor根标签 -->
<div ref="div"></div>
</template>
<script>
// 引入富文本编辑器
import E from 'wangeditor'
export default {
name: 'editor',
// 钩子函数
mounted () {
// 调用函数
this.initEditor()
},
methods: {
// editor初始化函数
initEditor () {
// 创建实例,利用ref获得元素
const editor = new E(this.$refs.div)
editor.create()
// 设置内容,暂时设置为空
editor.txt.html('')
// 设置change事件
editor.config.onchange = newHtml => {
// 事件触发后利用v-model把新数据传递回父元素
this.$emit('input', newHtml)
}
}
}
}
</script>
src/views/course/add.vue 引入富文本编辑器组件并且绑定数据
<div v-show="nowSteps === 4">
<el-form-item label="课程详情">
<!-- 创建富文本编辑器组件实例 -->
<editor v-model="form.courseDescriptionMarkDown"></editor>
</el-form-item>
// 引入富文本编辑器
import editor from '@/components/editor'
// 注册组件
components: {
uploadImg,
editor
}
富文本编辑器图片上传
默认富文本编辑器只能插入图片地址,我们可以使用富文本编辑器的图片上传功能上传本地图片
使用自己上传图片功能,因为默认的上传功能对接口有一定要求
// src/components/editor.vue 引入上传图片接口,实现上传功能
// 引入封装的图片上传接口
import { upLoad } from '@/services/coures'
// editor初始化函数
initEditor () {
// 创建实例,利用ref获得元素
const editor = new E(this.$refs.div)
// 上传图片配置要写在editor.create()之前!!!!!!!!!!!!!!!!!!
editor.config.customUploadImg = async (resultFiles, insertImgFn) => {
// 创建一个空的图片地址
let imgUrl = ''
// 创建FormData实例
const fd = new FormData()
// 向FormData添加文件信息,resultFiles[0]是上传图片信息列表(如果想多个上传,可以遍历resultFiles)
fd.append('file', resultFiles[0])
// 调用接口上传图片,并且带上获取进度的函数
const { data } = await upLoad(fd, this.onUploadProgress)
if (data.code === '000000') {
// 如果上传成功修改url地址
imgUrl = data.data.name
}
// 调用第二个参数实现插入
insertImgFn(imgUrl)
}
editor.create()
// 设置内容,暂时设置为空
editor.txt.html('')
// 设置change事件
editor.config.onchange = newHtml => {
// 事件触发后利用v-model把新数据传递回父元素
this.$emit('input', newHtml)
}
}
编辑组件
编辑组件和添加基本相同,可以把相同部分抽离出来单独封装
设置组件 书写路由 设置路由路径参数
使用通过课程ID获取课程信息接口
// src/services/coures.js 添加查询课程信息接口
// 根据课程id查询课程信息
export const getCourseById = courseId => {
return request({
method: 'get',
url: `/boss/course/getCourseById?courseId=${courseId}`
})
}
src/views/course/edit.vue 新建编辑课程页面,与添加课程共用子组件
<template>
<div class="addCourse">
<!-- 标题 -->
<div style="margin: 0 0 10px 0">编辑课程</div>
<!-- 创建子组件实例 -->
<addOrEdit :courseId="courseId"></addOrEdit>
</div>
</template>
<script>
// 引入子组件
import addOrEdit from './son/addOrEdit'
export default {
name: 'edit',
// 引入参数
props: ['courseId'],
// 注册组件
components: {
addOrEdit
}
}
</script>
下面两个就不贴出来了:
把编辑课程组件添加进路由
在管理主页面的编辑上添加点击事件,带着课程id参数跳转到编辑页面
src/views/course/son/addOrEdit.vue
<template>
<!-- 容器 -->
<el-card>
<!-- 顶部导航 -->
<div slot="header" class="clearfix">
<!-- 步骤条组件 -->
<el-steps :active="nowSteps" simple>
<!-- 循环遍历数据添加结构,添加点击事件(更改当前步骤) -->
<el-step v-for="item in steps" :key="item.id" :title="item.title" :icon="item.icon" @click.native="nowSteps = item.id"></el-step>
</el-steps>
</div>
<!-- 底部表单 -->
<el-form ref="form" :model="form" label-width="80px">
<!-- 基本信息 -->
<div v-show="nowSteps === 0">
<el-form-item label="课程名称">
<el-input v-model="form.courseName" placeholder="请输入课程名称"></el-input>
</el-form-item>
<el-form-item label="课程简介">
<el-input v-model="form.brief" placeholder="请输入课程简介"></el-input>
</el-form-item>
<el-form-item label="课程概述">
<!-- 设置样式让两个输入框在一行显示 -->
<el-input v-model="form.previewFirstField" style="width: 49%; minwidth: 300px; margin-right: 10px" placeholder="请输入第一条课程概述">
<!-- 使用slot="append"在后天添加单位或者描述 -->
<template slot="append">0 / 20</template>
</el-input>
<el-input v-model="form.previewSecondField" style="width: 49%; minwidth: 300px;" placeholder="请输入第二条课程概述"></el-input>
</el-form-item>
<el-form-item label="讲师姓名">
<el-input v-model="form.teacherDTO.teacherName" placeholder="请输入讲师姓名"></el-input>
</el-form-item>
<el-form-item label="讲师简介">
<el-input v-model="form.teacherDTO.description" placeholder="请输入讲师简介"></el-input>
</el-form-item>
<el-form-item label="课程排序">
<el-input-number v-model="form.sortNum" controls-position="right" :min="1"></el-input-number>
</el-form-item>
</div>
<!-- 课程封面 -->
<div v-show="nowSteps === 1">
<!-- 创建子组件实例,使用v-model双向绑定 -->
<!-- 课程封面 -->
<upload-img v-model="form.courseListImg" title="课程封面"></upload-img>
<!-- 解锁封面 -->
<upload-img v-model="form.courseImgUrl" title="解锁封面"></upload-img>
</div>
<!-- 销售信息 -->
<div v-show="nowSteps === 2">
<el-form-item label="售卖价格">
<el-input v-model="form.discounts" placeholder="请输入售卖价格">
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="商品原价">
<el-input v-model="form.price" placeholder="请输入商品原价">
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="销量">
<el-input v-model="form.sales" placeholder="请输入销量">
<template slot="append">单</template>
</el-input>
</el-form-item>
<el-form-item label="活动标签">
<el-input v-model="form.discountsTag" placeholder="请输入活动标签"></el-input>
</el-form-item>
</div>
<!-- 限时秒杀 -->
<div v-show="nowSteps === 3">
<!-- 开关组件,绑定数据 -->
<el-form-item label="限时秒杀开关" label-width="120px">
<el-switch
v-model="form.activityCourse"
active-color="#409eff"
>
</el-switch>
</el-form-item>
<!-- 限时秒杀信息,根据秒杀开关是否打开决定是否显示 -->
<div v-if="form.activityCourse">
<el-form-item label="开始时间">
<el-date-picker
v-model="form.activityCourseDTO.beginTime"
type="datetime"
placeholder="选择开始日期时间">
</el-date-picker>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="form.activityCourseDTO.endTime"
type="datetime"
placeholder="选择结束日期时间">
</el-date-picker>
</el-form-item>
<el-form-item label="秒杀价">
<el-input v-model="form.activityCourseDTO.amount" placeholder="请输入秒杀价">
<template slot="append">元</template>
</el-input>
</el-form-item>
<el-form-item label="库存">
<el-input v-model="form.activityCourseDTO.stock" placeholder="请输入库存">
<template slot="append">个</template>
</el-input>
</el-form-item>
</div>
</div>
<!-- 课程详情 -->
<div v-show="nowSteps === 4">
<el-form-item label="课程详情">
<!-- 创建富文本编辑器组件实例 -->
<editor v-model="form.courseDescriptionMarkDown"></editor>
</el-form-item>
<!-- 开关组件,绑定数据 -->
<el-form-item label="立即上架">
<el-switch
v-model="form.status"
active-color="#409eff"
:active-value="1"
:inactive-value="0"
>
</el-switch>
</el-form-item>
</div>
<!-- 最下方按钮 -->
<el-form-item>
<!-- 下一步按钮前四步都显示可跳转到下一步 -->
<el-button v-show="nowSteps < 4" @click="nowSteps += 1" type="primary">下一步</el-button>
<!-- 保存按钮只有最后一步才显示,用于保存数据,添加点击事件 -->
<el-button v-show="nowSteps === 4" type="primary" @click="preservationCoures">保存</el-button>
</el-form-item>
</el-form>
</el-card>
</template>
<script>
// 引入富文本编辑器
import editor from '@/components/editor'
// 引入子组件
import uploadImg from './uploadImg'
// 引入封装的保存课程功能
import { saveOrUpdateCourse, getCourseById } from '@/services/coures'
export default {
name: 'addOrEdit',
props: ['courseId'],
data () {
return {
// 当前步骤
nowSteps: 0,
// 步骤条组件数据
steps: [
{ id: 0, title: '基本信息', icon: 'el-icon-edit' },
{ id: 1, title: '课程封面', icon: 'el-icon-picture-outline' },
{ id: 2, title: '销售信息', icon: 'el-icon-shopping-cart-full' },
{ id: 3, title: '秒杀信息', icon: 'el-icon-present' },
{ id: 4, title: '课程详情', icon: 'el-icon-document' }
],
// form表单
form: {
// id: 0,
// 课程名称
courseName: '',
// 课程简介
brief: '',
teacherDTO: {
// id: 0,
// courseId: 0,
// 讲师姓名
teacherName: '',
// teacherHeadPicUrl: '',
// position: '',
// 讲师简介
description: ''
},
// 课程详情/描述信息
courseDescriptionMarkDown: '',
// 原价
price: null,
// 优惠价格
discounts: null,
// priceTag: '',
// 活动标签
discountsTag: '',
// isNew: true,
// isNewDes: '',
// 课程列表展示图片
courseListImg: '',
// 课程解锁封面
courseImgUrl: '',
// 课程排序
sortNum: 0,
// 课程概述/预览第一段
previewFirstField: '',
// 课程概述/预览第二段
previewSecondField: '',
// 当课程前状态
status: 0,
// 销量
sales: null,
// 是否开启秒杀
activityCourse: false,
activityCourseDTO: {
// id: 0,
// courseId: 0,
// 开始时间
beginTime: '',
// 结束时间
endTime: '',
// 秒杀价格
amount: 0,
// 库存
stock: 0
}
// autoOnlineTime: ''
}
}
},
// 钩子函数
created () {
// 判断是否带着课程id,如果带着id调用方法获取id信息
if (this.courseId) {
this.getCourse(this.courseId)
}
},
// 注册组件
components: {
uploadImg,
editor
},
methods: {
// 根据id获取课程信息
async getCourse (courseId) {
// 调用接口或获取信息
const { data } = await getCourseById(courseId)
if (data.code === '000000') {
// 如果获取成功,判断是否开启了秒杀
if (!data.data.activityCourse) {
// 如果没有开启秒杀,补全秒杀需要的数据,防止报错
data.data.activityCourseDTO = {
beginTime: '',
endTime: '',
amount: 0,
stock: 0
}
}
// 把数据交给表单
this.form = data.data
}
},
// 保存按钮点击事件函数
async preservationCoures () {
// 调用接口
const { data } = await saveOrUpdateCourse(this.form)
if (data.code === '000000') {
// 如果保存成功,弹出提示信息,跳转回课程管理页面
this.$router.push('/course')
}
}
}
}
</script>
<style lang="scss" scoped>
// 步骤条
.el-step {
// 手型鼠标
cursor: pointer
}
</style>
编辑组件改进
(上一步已经设置)提示文字需要更改
(上一步已经设置)如果原本没有开启秒杀,我们编辑想开启秒杀就会报错,因为返回的数据不包含秒杀信息,读取不到 - 我们需要在获取数据的时候判断,如果没有开启秒杀需要手动添加结构
富文本编辑器无法展示数据,因为读文本编辑器获取数据是同步的(展示的是默认值),而获取后台数据是异步的 所以导致无法获取,设置监听数据变化(只有第一次加载的时候有问题,所以只需要在第一次加载的时候显示正确数据)
src/components/editor.vue 设置首次加载编辑器的时候自动获取数据
<template>
<!-- editor根标签 -->
<div ref="div"></div>
</template>
<script>
// 引入封装的图片上传接口
import { upLoad } from '@/services/coures'
// 引入富文本编辑器
import E from 'wangeditor'
export default {
name: 'editor',
// 参数
props: ['value'],
data () {
return {
// 信号值,是否已经加载完毕
loading: false
}
},
// 钩子函数
mounted () {
// 调用函数
this.initEditor()
},
methods: {
// editor初始化函数
initEditor () {
// 创建实例,利用ref获得元素
const editor = new E(this.$refs.div)
// 将editor设置给this,否则后面监听器使用不了
this.editor = editor
// 上传图片配置要写在editor.create()之前
editor.config.customUploadImg = async (resultFiles, insertImgFn) => {
// 创建一个空的图片地址
let imgUrl = ''
// 创建FormData实例
const fd = new FormData()
// 向FormData添加文件信息,resultFiles[0]是上传图片信息列表(如果想多个上传,可以遍历resultFiles)
fd.append('file', resultFiles[0])
// 调用接口上传图片,并且带上获取进度的函数
const { data } = await upLoad(fd, this.onUploadProgress)
if (data.code === '000000') {
// 如果上传成功修改url地址
imgUrl = data.data.name
}
// 调用第二个参数实现插入
insertImgFn(imgUrl)
}
editor.create()
// 设置内容,暂时设置为空
editor.txt.html('')
// 设置change事件
editor.config.onchange = newHtml => {
// 事件触发后利用v-model把新数据传递回父元素
this.$emit('input', newHtml)
}
}
},
watch: {
// 监听value变化,只有第一次加载的时候触发
value () {
if (!this.loading) {
// 如果没有加载,那么设置默认展示信息
this.editor.txt.html(this.value)
// 修改信号值为true,这样以后在更改数据就不会触发
this.loading = true
}
}
}
}
</script>