在之前的代码Vue脚手架实现试卷页面基础上新增了如下细节
- 当题目加入试题库的同时,在state的getters中放四个对象,分别代表单选题,多选题,判断题,简答题将购物车数组进行分类,对象的tilte为单选题,多选题,判断题,简答题
- 在试题库中删除单个题目时,通过filter更新试题库,于此同时分类的题库也会同时变化
- 在computed中定义一个数组list,通过判断分类对象中的数组是否不为空,依次push到空数组中,页面根据list循环组件
- 点击返回页面继续添加
- 使用element-admin的Export2Excel.js导出excel
- 定义一个组件,存放每种题型的题库
- 实现页面

试题库页面Cart.vue
- 这里要注意的是虽然标题都可以通过各个分类的对象得到,但是它的序号不是跟着数组下标来的,所以在computed中定义了list,根据list的下标来更新
- 导出excel是通过Export2Excel.js,需注意data时嵌套的数组,data.length代表了多少行数据,里面嵌套的数组length需与表头一致,另外使用Export2Excel.js需安装xlsx和file-saver
<template>
<div class="main">
<button @click="toExcelDom">转为excel文档</button>
<div>
<div v-if="!subjectCartList.length">请添加题目</div>
<div v-else>
<Fenlei v-for="(item, index) in list" :key="index" :list="item">
<h1>{{index|fmtNum2En}}、{{item.title}}</h1>
</Fenlei>
</div>
<router-link class="room" tag="div" to="/">返回添加({{subjectCartNum}})</router-link>
</div>
</div>
</template>
<script>
import Fenlei from "@/components/Fenlei";
import "@/mock";
import { createNamespacedHelpers } from "vuex";
import { export_json_to_excel } from "@/utils/Export2Excel";
let {
mapState,
mapMutations,
mapActions,
mapGetters
} = createNamespacedHelpers("moduleA");
export default {
components: {
Fenlei
},
computed: {
list() {
let data = [];
[
this.cartDanxuanList,
this.cartDuoxuanList,
this.cartPanduanList,
this.cartJiandaList
].forEach(item => {
if (item.subjectList.length) data.push(item);
});
return data;
},
...mapState(["subjectList", "subjectCartList"]),
...mapGetters([
"subjectCartNum",
"cartDanxuanList",
"cartDuoxuanList",
"cartPanduanList",
"cartJiandaList"
])
},
methods: {
remove(item) {
item.payload = "";
this.removeCart2Subject(item);
if (!item.exist) this.$Message.success("移除成功");
},
...mapMutations(["getSubjectList", "removeCart2Subject"]),
fmtOptions(item) {
return item.map(r => r.choice).join("|");
},
toExcelDom() {
let data = [];
this.list.forEach(r => {
r.subjectList.forEach(item => {
data.push([
r.title,
item.title,
this.fmtOptions(item.choice),
item.answer,
item.desc
]);
});
});
export_json_to_excel({
header: ["题型", "标题", "选项", "答案", "解析"], //表头 必填
data, //具体数据 必填
filename: "题库" + new Date().toLocaleDateString() //非必填
});
}
}
};
</script>
<style scoped lang='scss'>
.main {
border: 1px solid red;
width: 800px;
margin: 100px auto;
.room {
width: 30px;
position: fixed;
padding: 10px;
top: 50%;
right: 20px;
color: #fff;
background: rgb(5, 136, 138);
transform: translateY(-50%);
}
}
</style>
组件Fenlei.vue
这里要注意的是我用了slot是为了控制标题的序列号是连续的
<template>
<div class="mine">
<slot v-if="list.subjectList.length"></slot>
<ul>
<li class="item" v-for="(item, i) in list.subjectList" :key="i">
<button class="btn" @click="remove(item)">从题库移除</button>
<h4>{{i+1}}.[{{item.type|fmtSubjectType}}] {{item.title}}</h4>
<div style="color:#888;font-size:14px">{{item.author}}{{item.createDate}}</div>
<fieldset style="padding:0 10px;" v-if="checkSubjectType(item.type)">
<legend>选项</legend>
<div v-for="(choice, j) in item.choice" :key="j">
{{j|fmtOrder2ABC}} {{choice.answer}}
</div>
</fieldset>
<div v-if="checkSubjectType(item.type)">答案:{{item.answer}}</div>
<div>解析:{{item.desc}}</div>
</li>
</ul>
</div>
</template>
<script>
import { createNamespacedHelpers } from "vuex";
let {
mapState,
mapMutations,
mapActions,
mapGetters
} = createNamespacedHelpers("moduleA");
export default {
props:['list'],
created() {
},
methods: {
remove(item) {
item.payload = "";
this.removeCart2Subject(item);
if (!item.exist) this.$Message.success("移除成功");
},
...mapMutations([ "removeCart2Subject"])
},
};
</script>
<style scoped lang='scss'>
.main {
.item {
padding: 10px;
border-bottom: 1px solid #ccc;
position: relative;
.btn {
position: absolute;
right: 10px;
top: 10px;
}
}
.room {
width: 30px;
position: fixed;
padding: 10px;
top: 50%;
right: 20px;
color: #fff;
background: rgb(5, 136, 138);
transform: translateY(-50%);
}
}
</style>
主页面Home.vue
<template>
<div class="main">
<ul>
<li class="item" v-for="(item, i) in subjectList" :key="i">
<button class="btn" @click="add(item)">添加到试题库</button>
<h4>{{i+1}}.[{{item.type|fmtSubjectType}}] {{item.title}}</h4>
<div style="color:#888;font-size:14px">{{item.author}}{{item.createDate}}</div>
<fieldset style="padding:0 10px;" v-if="checkSubjectType(item.type)">
<legend>选项</legend>
<div v-for="(choice, j) in item.choice" :key="j">
{{j|fmtOrder2ABC}} {{choice.answer}}
</div>
</fieldset>
<div v-if="checkSubjectType(item.type)">答案:{{item.answer}}</div>
<div>解析:{{item.desc}}</div>
</li>
</ul>
<router-link class="room" tag="div" to="/cart">试题库({{subjectCartNum}})</router-link>
</div>
</template>
<script>
import "@/mock";
import { createNamespacedHelpers } from "vuex";
let {
mapState,
mapMutations,
mapActions,
mapGetters
} = createNamespacedHelpers("moduleA");
export default {
components: {},
created() {
setTimeout(async () => {
let { subjectList } = await this.$get("/subjectList");
this.getSubjectList(subjectList);
}, 2000);
},
computed: {
...mapState(["subjectList", "subjectCartList"]),
...mapGetters(["subjectCartNum"])
},
methods: {
add(item) {
item.payload = "";
this.addSubject2Cart(item);
if(item.exist) this.$Message.success('添加成功');
else this.$Message.warning('请勿重复添加');
},
...mapMutations(["getSubjectList", "addSubject2Cart"])
}
};
</script>
<style scoped lang='scss'>
.main {
border: 1px solid red;
width: 800px;
margin: 100px auto;
.item {
padding: 10px;
border-bottom: 1px solid #ccc;
position: relative;
.btn {
position: absolute;
right: 10px;
top: 10px;
}
}
.room {
width: 30px;
position: fixed;
padding: 10px;
top: 50%;
right: 20px;
color: #fff;
background: rgb(5, 136, 138);
transform: translateY(-50%);
}
}
</style>
axios.js
在utils文件夹新建了axios.js自定义了一个axios函数,且运用View UI组件创造请求数据请求的progress效果
import axios from 'axios'
import Vue from 'vue'
import { BASE_URL } from '@/config'
import ViewUI from 'view-design';
Vue.use(ViewUI);
const instance = axios.create({
baseURL: BASE_URL
});
instance.interceptors.request.use(function (config) {
ViewUI.LoadingBar.start();
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
instance.interceptors.response.use(function (response) {
ViewUI.LoadingBar.finish();
return response;
}, function (error) {
ViewUI.LoadingBar.error();
return Promise.reject(error);
});
export default instance
router/index.js
运用View UI组件创造切换页面的progress效果
import VueRouter from 'vue-router'
import Vue from 'vue'
import ViewUI from 'view-design';
Vue.use(ViewUI);
Vue.use(VueRouter)
let router = new VueRouter({
routes: [{
path: '/',
meta: { name: 'home' },
component: () => import(/*WebpackChunkName:'zzz'*/ '@/page/Home')
},{
path: '/cart',
meta: { name: 'cart' },
component: () => import(/*WebpackChunkName:'zzz'*/ '@/page/Cart')
},{
path:'*',
component:()=>import(/*webpackChunkName:'zzz'*/ '@/page/Detail')
}
]
})
router.beforeEach((to, from, next) => {
ViewUI.LoadingBar.start();
next();
});
router.afterEach(() => {
ViewUI.LoadingBar.finish();
});
export default router
mixin/index.js
使用定义的axios
import axios from '@/utils/axios'
import Vue from 'vue'
import { BASE_URL } from '@/config'
import {SUBJECT_TYPE} from '@/config/enum'
Vue.mixin({
methods: {
async $get(url,params){
let {data} = await axios.get(BASE_URL+url,{params})
return data
}
getTimeBet(time){
let timeStamp = +new Date - +new Date(time)
console.log(timeStamp)
},
checkSubjectType(type){
return type===SUBJECT_TYPE.DANXUAN||type===SUBJECT_TYPE.DUOXUAN
}
},
filters:{
fmtSubjectType(val){
return ['单选题', '多选题', '判断题', '简答题'][val]
},
fmtOrder2ABC(val) {
return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[val]
},
fmtNum2En(val){
return '一二三四五六七八九十'[val]
}
},
data() {
return {
SUBJECT_TYPE
}
},
})
main.js
最后别忘了view ui需要安装呀,再引入view ui的css样式
import Vue from 'vue'
import App from './App.vue'
import router from '@/router'
import store from '@/store'
import ViewUI from 'view-design';
import 'view-design/dist/styles/iview.css';
Vue.use(ViewUI);
Vue.config.productionTip = false
import './mixin'
new Vue({
render: h => h(App),
store,
router,
}).$mount('#app')