题库列表
01-题库搜索ui渲染
- 搜索form表单部分
-
学科
1、去发请求获取所有的学科数据,存到subjectList:【】数组中
2、获取到数据之后,遍历数组 v-for="item in subjectList"得到每一个将其中的几个值设置给el-selectel-option的label、value、key -
企业
1、去发请求获取所有企业的数据,存到 enterpriseList:【】数组中
2、获取到数据之后,遍历数组 v-for="item in enterpriseList"得到每一个将其中的几个值设置给el-selectel-option的label、value、key -
阶段,题型,难度
1、设置数据:stepObj: { 1: “初级”, 2: “中级”, 3: “高级” }, //题目阶段
typeObj: { 1: “单选”, 2: “多选”, 3: “简答” }, //题目类型
difficultyObj: { 1: “简单”, 2: “一般”, 3: “困难” } // 题目难度
2、获取到数据之后,遍历对象v-for="(value,name) in xxxObj"得到每一个将其中的几个值设置给el-selectel-option的label、value、key -
作者、标题
: 普通的el-input框 -
状态
:就两个要么禁用,要么启用,在el-select下的el-option中可以写死
-
02-题库列表ui渲染
- 发送请求得到列表数据一个items就是一行
- el-table有些数据要格式化用formatter
要根据这个对象 typeObj: { 1: “单选”, 2: “多选”, 3: “简答” },在页面上显示汉字而不是数字,可以用el-table中的格式化,以下下是具体代码:
- 要格式化学科.阶段到页面,以下是具体代码:
02-分页ui渲染
03-点击搜索,清除,分页点击,更新状态,删除功能
跟之前做的都一样啦!没啥好说的,
其中做清除功能的时候 如果要调用 form 表单的 resetFields 这个方法,需要给 el-form-item 设置 prop
04-详细代码:
question文件夹下的index.vue组件:
<template>
<div class="question">
<!-- 搜索内容区域 -->
<el-card>
<el-form inline :model="searchForm" ref="searchFormRef" label-width="80px">
<!-- 第一行 -->
<el-row>
<el-col :span="6">
<el-form-item label="学科" prop="subject">
<el-select v-model="searchForm.subject" placeholder="请选择学科">
<el-option
v-for="item in subjectList"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="阶段" prop="step">
<el-select v-model="searchForm.step" placeholder="请选择阶段">
<el-option v-for="(value,name) in stepObj" :key="name" :label="value" :value="name"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="企业" prop="enterprise">
<el-select v-model="searchForm.enterprise" placeholder="请选择企业">
<el-option
v-for="item in enterpriseList"
:key="item.id"
:label="item.name"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="题型" prop="type">
<el-select v-model="searchForm.type" placeholder="请选择题型">
<el-option v-for="(value,name) in typeObj" :key="name" :label="value" :value="name"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 第二行 -->
<el-row>
<el-col :span="6">
<el-form-item label="难度" prop="difficulty">
<el-select v-model="searchForm.difficulty" placeholder="请选择难度">
<el-option
v-for="(value,name) in difficultyObj"
:key="name"
:label="value"
:value="name"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="作者" prop="username">
<el-input style="width:217px" v-model="searchForm.username"></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="状态" prop="status">
<el-select v-model="searchForm.status" placeholder="请选择状态">
<el-option label="启用" value="1"></el-option>
<el-option label="禁用" value="0"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="日期" prop="create_date">
<el-date-picker v-model="searchForm.create_date" type="date" placeholder="请选择日期"></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<!-- 第三行 -->
<el-row>
<el-col :span="12">
<el-form-item label="标题" prop="title">
<el-input style="width:620px" v-model="searchForm.title"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<el-button type="primary" @click="search">搜索</el-button>
<el-button @click="clear">清除</el-button>
<el-button type="primary" @click="add">+新增试题</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 列表区域 -->
<el-card style="margin-top:15px">
<el-table :data="questionList" border stripe>
<el-table-column label="序号" type="index"></el-table-column>
<el-table-column label="题目" width="300">
<template slot-scope="scope">
<span v-html="scope.row.title"></span>
</template>
</el-table-column>
<!-- 方法一 -->
<el-table-column label="学科.阶段" :formatter="formatterSubject"></el-table-column>
<!-- 方法二 -->
<!-- <el-table-column label="学科.阶段">
<template slot-scope="scope">
<span>{{scope.row.subject_name}}.{{stepObj[scope.row.step]}}</span>
</template>
</el-table-column>-->
<el-table-column label="题型" :formatter="formatterType">
<!-- <template slot-scope="scope"> -->
<!-- 可以使,简单直接 -->
<!-- <span>{{typeObj[scope.row.type]}}</span> -->
<!-- 过滤器不好使;过滤器中的this是undefined -->
<!-- <span>{{scope.row.type | formatType}}</span> -->
<!-- 计算属性不好使;无法传递以及接收参数 -->
<!-- <span>{{formatType}}</span> -->
<!-- 可以使,使用调用方法的形式,既可以接收参数,this也可以获取到 -->
<!-- <span>{{formatType(scope.row.type)}}</span> -->
<!-- </template> -->
</el-table-column>
<el-table-column label="企业" prop="enterprise_name"></el-table-column>
<el-table-column label="状态">
<template slot-scope="scope">
<span
:style="{color:scope.row.status===1?'#85ce61':'red'}"
>{{scope.row.status===1?'启用':'禁用'}}</span>
</template>
</el-table-column>
<el-table-column label="访问量" prop="reads"></el-table-column>
<el-table-column label="操作" width="280">
<template slot-scope="scope">
<el-button type="primary" @click="editQuestion(scope.row)">编辑</el-button>
<!-- <el-button
@click="changeStatus(scope.row.id)"
:type="scope.row.status===1?'info':'success'"
>{{scope.row.status===1?'禁用':'启用'}}</el-button>-->
<!-- 3.调用混入对象中的方法 -->
<el-button
@click="changeStatus('/question/status',scope.row.id)"
:type="scope.row.status===1?'info':'success'"
>{{scope.row.status===1?'禁用':'启用'}}</el-button>
<!-- <el-button type="danger" @click="del(scope.row.id)">删除</el-button> -->
<el-button type="danger" @click="del('/question/remove',scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div style="margin-top:15px;text-align:center">
<el-pagination
@size-change="sizeChange"
@current-change="currentChange"
:current-page="page"
:page-sizes="[2, 4, 6, 8]"
:page-size="limit"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
background
></el-pagination>
</div>
</el-card>
<!-- 传给子组件的值 -->
<qusetion-edit
ref="questionEditRef"
:subjectList="subjectList"
:enterpriseList="enterpriseList"
:stepObj="stepObj"
:typeObj="typeObj"
:difficultyObj="difficultyObj"
></qusetion-edit>
</div>
</template>
<script>
import QusetionEdit from "./question-add-or-update";
//1.导入混入对象
import common from "@/mixins/common";
export default {
//2. 在自身组件中进入混入
mixins: [common],
components: {
QusetionEdit
},
name: "Qusetion",
data() {
return {
page: 1, //页码 默认为1
limit: 2, //页容量
questionList: [], //题库列表
total: 0, //总条数
//模型提交给后台的
searchForm: {
title: "", //标题名称
subject: "", //学科id
enterprise: "", // 企业id
step: "", // 题目阶段:1(初级),2(中级),3(高级)
type: "", // 题目类型:1(单选),2(多选),3(简答)
difficulty: "", // 题目难度: 1(简单),2(一般),3(困难)
username: "", // 作者
status: "", //状态:0(禁用),1(启用)
create_date: "" // 创建日期
},
subjectList: [], //学科列表
enterpriseList: [], //企业列表
stepObj: { 1: "初级", 2: "中级", 3: "高级" }, //题目阶段
typeObj: { 1: "单选", 2: "多选", 3: "简答" }, //题目类型
difficultyObj: { 1: "简单", 2: "一般", 3: "困难" } // 题目难度
};
},
created() {
this.getSubjectData();
this.getEnterpriseData();
this.getListData();
},
methods: {
//获取所有的学科列表
async getSubjectData() {
const res = await this.$axios.get("/subject/list");
if (res.data.code == 200) {
this.subjectList = res.data.data.items;
}
},
//获取所有的企业列表
async getEnterpriseData() {
const res = await this.$axios.get("/enterprise/list");
if (res.data.code == 200) {
this.enterpriseList = res.data.data.items;
}
},
//获取的题库列表
async getListData() {
const res = await this.$axios.get("/question/list", {
params: {
...this.searchForm,
page: this.page,
limit: this.limit
}
});
if (res.data.code == 200) {
this.questionList = res.data.data.items;
this.total = res.data.data.pagination.total;
}
},
//搜索
search() {
this.page = 1;
this.getListData();
},
//清除
clear() {
// 如果要调用 form 表单的 resetFields 这个方法,需要给 el-form-item 设置 prop
this.$refs.searchFormRef.resetFields();
this.search();
},
// 格式化学科.阶段
formatterSubject(row) {
return `${row.subject_name}.${this.stepObj[row.step]}`;
},
// 格式化题型
formatterType(row) {
return this.typeObj[row.type];
},
//页容量发生变化
sizeChange(val) {
this.limit = val;
this.search();
},
// 当前页发生变化
currentChange(val) {
this.page = val;
this.getListData();
},
// 改变状态
// async changeStatus(id) {
// const res = await this.$axios.post("/question/status", { id });
// if (res.data.code == 200) {
// this.$message({
// type: "success",
// message: "更新状态成功"
// });
// //重新刷新当前页面
// this.geListData();
// } else {
// this.$message.error(res.data.message);
// }
// },
//删除
// del(id) {
// this.$confirm("确定删除该题目吗?", "提示", {
// confirmButtonText: "确定",
// cancelButtonText: "取消",
// type: "warning"
// })
// .then(async () => {
// const res = await this.$axios.post("/question/remove", { id });
// if (res.data.code == 200) {
// this.$message({
// type: "success",
// message: "删除成功!"
// });
// //重新刷新第一页数据
// this.search();
// }
// })
// .catch(() => {});
// },
//新增
add() {
this.$refs.questionEditRef.modal = "add";
this.$refs.questionEditRef.dialogVisible = true;
// 点击新增的时候新增&编辑组件就要清空questionForm表单
this.$refs.questionEditRef.questionForm = {
title: "", // 标题
subject: "", // 学科id标识
step: "", //阶段1、初级 2、中级 3、高级
enterprise: "", // 企业id标识
city: "", // [省、市、区县]
type: 1, // 题型 1单选 、2多选 、3简答
difficulty: 1, // 题目难度 1简单 、2一般 、3困难
single_select_answer: "", // 单选题答案
multiple_select_answer: [], //多选题答案
short_answer: "", // 简答题答案
video: "", // 解析视频地址
answer_analyze: "", // 答案解析
remark: "", // 答案备注
select_options: [
{
label: "A",
text: "splice",
image: ""
},
{
label: "B",
text: "slice",
image: ""
},
{
label: "C",
text: "pop",
image: ""
},
{
label: "D",
text: "shift",
image: ""
}
] // 选项,介绍,图片介绍
};
},
//编辑
editQuestion(row) {
this.$refs.questionEditRef.modal = "edit";
this.$refs.questionEditRef.dialogVisible = true;
//深拷贝,把点编辑的那行的数据拷贝给新增&编辑组件的questionForm,那这样一点编辑这个questionForm就有值
// 但是有几个显示不出来比如城市(要变成数组才行),题型,难度,阶段(因为编辑的那行返回的数据这是三个都是数字,所以把他们的value变成数字就好)
this.$refs.questionEditRef.questionForm = JSON.parse(JSON.stringify(row));
if (row.city) {
// 要把返回数据的变成数组
this.$refs.questionEditRef.questionForm.city = row.city.split(",");
}else{
this.$refs.questionEditRef.questionForm.city = []
}
if (row.multiple_select_answer) {
this.$refs.questionEditRef.questionForm.multiple_select_answer=row.multiple_select_answer.split(',')
}else{
this.$refs.questionEditRef.questionForm.multiple_select_answer=[]
}
}
// 好使
// formatType(val){
// return this.typeObj[val]
// }
}
// 不好使,里面的this不是指向vue实例
// filters:{
// formatType(val){
// return this.typeObj[val]
// }
// }
//不好使,不能接收参数
// computed:{
// formatType(val){
// console.log('这是',val);//这里面的val不是传来的参数是vue实例而且上面也不能写()传递参数不然会以为是方法
// return 'test'
// }
// }
};
</script>
05-效果演示
06-新增&编辑题目功能
要新建一个组件写在question文件夹下的question-add-or-update.vue组件中写在el-dialog中
- 几个下拉框的ui渲染,其中学科,阶段,企业,城市(第三方包做),题型,难度,这些数据父组件里面本来也发送请求早就有了所以呢从父组件传过来就好,子组件就不用再发请求获取,像下面用props方法传来之后呢,再渲染就用el-dialog,el-form,el-select,el-radio-group这些element的组件做,具体参考element文档
-
其中城市这个下拉框中要用到element中的el-cascader级联选择器
城市数据也要用到第三方可以在git hub上搜element-china-area-data,点赞最多的就是我们要用的包有教你怎么使用
直通车: link.
1、安装:npm install element-china-area-data -S
2、导入:import { regionData } from “element-china-area-data”;(其中那个()里面看你需要哪种效果的就导入哪种,我导的是regionData是省市区三级联动数据(不带“全部”选项))
3、使用el-cascader显示内容 -
两个富文本编辑器跟一个input的ui渲染,
富文本编辑器:插件 vue-quill-editor 直通车:link. 可以直接在github上搜这个 vue-quill-editor使用步骤
1、.安装 npm i vue-quill-editor
2、导入相应的css
import ‘quill/dist/quill.core.css’
import ‘quill/dist/quill.snow.css’
import ‘quill/dist/quill.bubble.css’
3、导入组件,并且注册
4、就可以在 template 中使用了
-
题型子组件中
新增试题本身就是题库列表的一个子组件,然后这个地方展示题型组件又是新增试题的子组件,右边的上传到的文件图片以及下面要做的解析视频又是题型组件的子组件
其中题型子组件就是根据你上面radio题型选中的是单选、多选、简答,来决定你这里显示的是什么内容,下面三张图就是三种不同的显示情况下,(单选情况下与多选差不多的就是一个单选radio变成了checkbox),我这里说的是题型组件,孙子组件(上传组件)后面说
1、传值
:父子组件传值,有 props & emit ,还有$parent $refs
这里我们巧妙的应用引用类型来达到父子组件传值的目的
将父组件的questionForm传递给题型子组件,然后在题型子组件里面接受传来的questionForm,因为他们的引用类型地址还是同一个,那么在题型子组件中questionForm里面的数据有什么变动,在父组件question-add-or-update.vue中都也会变
2,步骤
a、新建一个名为 question-type 的子组件,写好里面的内容
b、在 新增/修改 的组件中集成它就是导入注册,并且使用它
c、在父组件中,我会把整个 questionForm 对象传递给子组件,目的是为了在子组件中操作方便
d、传值的时候,父组件中定义好属性名,把questionForm对象传递过去,然后子组件这边通过 props 来接收
e、此时question-type题型子组件中也有questionForm了,那就可以渲染题型组件并且双向绑定questionForm中的值
<template>
<div>
<!-- 选中单选的时候 -->
<div v-if="questionForm.type==1">
<div v-for="(item, index) in questionForm.select_options" :key="index" class="item">
<!-- 单选radio -->
<el-radio v-model="questionForm.single_select_answer" :label="item.label">{{item.label}}</el-radio>
<!-- input -->
<el-input v-model="item.text"></el-input>
</div>
</div>
<!-- 选中多选的时候 -->
<div v-if="questionForm.type==2">
<div v-for="(item, index) in questionForm.select_options" :key="index" class="item">
<!-- 多选checkbox -->
<el-checkbox v-model="questionForm.multiple_select_answer" :label="item.label"></el-checkbox>
<!-- input -->
<el-input v-model="item.text" style="margin-left:15px"></el-input>
</div>
</div>
<!-- 选中简答的时候 -->
<div v-if="questionForm.type==3">
<el-input type="textarea" :rows="5" placeholder="请输入内容" v-model="questionForm.short_answer"></el-input>
</div>
</div>
</template>
选中单选的时候:
选中多选的时候:
选中简答的时候:
07 新增&编辑组件中的上传文件(图片\视频)功能
- 上传子组件的封装:一种是只能上传图片,一种是只能上传视频
- 父子组件传值: v-model
1
、先定义好一个子组件 upload-file ,并且里面写好内容
upload-file组件中:
2
、在 question-add-or-update 中,集成 upload-file 子组件的时候,我们给它传递了 type = video,代表只能上传视频。在 question-type 中集成它,这里没有给它传值 type,那么子组件中 type 的默认值就是image
这里是question-add-or-update.vue新增&编辑组件中的,传值给子组件上传组件:
这里是question-type.vue题型组件中的,传值给子组件上传组件:
3
、子组件上传成功之后,该如何处理?
这是上传组件upload.vue子组件:
根据父组件接受过来的值(分情况有还是没有)来决定显示在页面上是文件(视频,图片)还是+号
上传之前检测上传的东西是否符合规范。上传成功之后,触发input事件,然后传值给父组件,然后给父组件中 v-model 绑定的值赋值,同时也把我们的内容(图片\视频)展示出来。
- 注意点:
如果我们多个地方使用到了同一个组件,并且使用的时候,又略有不同,这个时候,我们一般使用 props 通过传递不同的值来解决
08 新增题目之前,做一次校验
question-type 题型子组件校验
:
对题型组件中一改变就校验
:
打个比方比如我们点新增确定按钮时,提交的时候发现这些校验未通过还没选择,那我立刻去选择,当时改变就应该当时校验消失
题型组件中:
新增&编辑组件中:
监听子组件传来的事件
对富文本编辑器中一改变就校验
:
09新增
10 修改
不能直接就给模型赋值,有些地方需要处理一下
- 比如城市
服务器获取回来的是字符串:city: “山西省,太原市,小店区”
但是展示需要的是一个数组:this.$refs.questionEditRef.questionForm.city = row.city.split(’,’)
修改提交的时候,又必须把它变成字符串:这应该是后台没有处理好 - 阶段、题型、难度
因为我们stepObj、typeObj、difficultyObj它的属性名是字符串,而服务器返回的 step、type、difficulty 又是数字,所以,我们需要在遍历的时候,给相应的value变成数字 - 比如多选的选项
服务器返回的是字符串
但是我们需要的是一个数组
question文件夹下的index.vue组件中:点击编辑按钮的时候
11 点击新增跟修改组件的时候要把前面的内容跟校验清空
question文件夹的index.vue组件中:
点击新增的时候清空内容
question-add-or-update.vue组件中:
点击新增跟编辑时清空校验,编辑时内容不用清空
12-详细代码
13-效果演示
后面一篇说吧