Vue脚手架实现试卷页面---更新试题库页面及excel导出

1,687 阅读3分钟

在之前的代码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')