Vuejs实战基础篇和进阶篇(前12章)学完之后,做的小练习(表格curd和tab页切换)

一、项目结构

在这里插入图片描述

说明:用vue-cli搭建项目,components目录放组件,router目录放路由页面,
Vuex目录里面放各个模块的vuex信息(共享的数据和操作数据的方法)

二、最终效果:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

说明:最终效果是多个tab页切换,表格tab也可以操作表格数据(增删改查和排序,筛选),
考卷页面可以上下翻页和提交答案还有答案验证。

三、各个页面说明:

(1)、Vuex目录:

<1>、index.js

const state={
    shopList:[
      {shopName:'香蕉',shopPrice:7,shopNum:11,state:0},
      {shopName:'苹果',shopPrice:3,shopNum:18,state:0},
      {shopName:'橙子',shopPrice:12,shopNum:35,state:0}
    ],
    titleList:[
      {text:'序号',type:'rowNum'},
      {text:'商品名称',type:'shopName'},
      {text:'商品价格',type:'shopPrice',isSort:true},
      {text:'商品数量',type:'shopNum',isSort:true},
      {text:'商品总价',type:'shopTotal'},
      {text:'操作',type:'caozuo'}
    ],
    quesnaireList:[
        {id:1,testTitle:"您的性别是?",preserve:"sex",quesnaireType:"radio",quesnaireValue:['男','女','保留']},
        {id:2,testTitle:"您的爱好是?",preserve:"hobby",quesnaireType:"checkbox",quesnaireValue:['篮球','足球','游泳']},
        {id:3,testTitle:"您的简介?",preserve:"introduc",quesnaireType:"textarea",quesnaireValue:''}
      ],
    currentPage:1,
    sex:"",
    hobby:[],
    introduc:"",
    selected:'',
    radioValue:1
};

const mutations={
  setRadioValue(state,args){
    state.radioValue=args;
  },
  setSelectedVal(state,args){
    state.selected=args;
  },
  setSex(state,args){
    state.sex=args;
  },
  setHobby(state,args){
    state.hobby=args;
  },
  setIntroduc(state,args){
    state.introduc=args;
  },
  setCurrentPage(state,args){
    state.currentPage+=args;
  },
  setShopList(state,args){
    if (args=='seach'){
      state.shopList.map(item=> {
        item.shopName == state.selected ?
          item.state=0 : item.state=1
      });
    };
    if (args=='rest'){
      state.shopList.map(item=>item.state=0);
    };
  },
  sortShopList(state,args){
    state.shopList.sort((a,b)=>{
      return args.sortOrder=='asc'?a[args.type]-b[args.type]:b[args.type]-a[args.type];
    });
  }
};

export default {
  state,
  mutations
};

说明:主要声明了需要用到的共享数据和数据操作方法。

(2)、modules.js

import Vue from 'vue';
import Vuex from 'Vuex';
Vue.use(Vuex);

import mytableVuex from './Vuex/myTable/index.js';

export default new Vuex.Store({
  modules:{
    mytableVuex:mytableVuex
  }
});

说明:整合各个模块的vuex仓库(vuex总配置)

(3)、main.js

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue';
import App from './App.vue';
import VueRouter  from 'vue-router';
import $ from 'jquery';
import '../static/bootstrap-3.3.7-dist/css/bootstrap.css';
import '../static/bootstrap-3.3.7-dist/js/bootstrap.min';
import store from  './modules.js';
Vue.use(VueRouter);
Vue.config.productionTip = false

const Routers=[
  {
    path:'/index',
    component:(resolve)=>require(['./router/view/pageMyTable.vue'],resolve)
  },{
    path:'/about',
    name:'about',
    component:(resolve)=>require(['./router/view/about.vue'],resolve)
  },{
    path:'*',
    redirect:'/index'
  }
]

const RouterConfig={
  mode:'history',
  base:__dirname,
  routes:Routers
}

const router=new VueRouter(RouterConfig);


new Vue({
  el:"#app",
  components:{App},
  template: '<App/>',
  router,
  store
});

说明:配置路由,vuex,实例化Vue对象,挂载到id为app的元素上。
(main.js是打包的入口文件,需要打包的js全部在此引入).

(4)、App.vue

<template>
  <div id="app" class="container">
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>

</style>

说明:整个程序的主组件,在这里挂载路由视图

(5)、components目录:

<1>、table组件:myTable.vue

<template>
  <div class="tableClass">
    <table class="table table-hover table-condensed">
      <thead>
      <th v-for="(title,index) in titleList">
        {{title.text}}
        <span v-if="title.isSort">
          <a @click="sortClick('asc',title.type)">↑</a>
          <a @click="sortClick('desc',title.type)">↓</a>
        </span>
      </th>
      </thead>
      <tbody>
      <tr v-for="(shop,index) in shopList" v-if="shop.state==0" @dblclick="handlerDblclick(shop.shopName)">
        <td>{{index}}</td>
        <td>{{shop.shopName}}</td>
        <td>{{shop.shopPrice}}</td>
        <td><input type="number" v-model.lazy.number="shop.shopNum"></td>
        <td>{{shop.shopPrice*shop.shopNum}}</td>
        <td>
          <button @click="handlerClick('add',shop)">+</button>&nsub;<button @click="handlerClick('del',shop)">-</button>
        </td>
      </tr>
      </tbody>
    </table>
    <span class="countClass">商品总价为:{{count}}</span>
  </div>
</template>

<script>
    export default {
        name: "myTable",
        props:{
            maxNum:{
              type:Number,
              default:20
            },
            minNum:{
                type: Number,
                default: 0
            }
        },
      methods:{
        handlerClick:function (type,shop) {
          var radioNum=this.$store.state.mytableVuex.radioValue;
          var num=type=='add'?radioNum*1:radioNum*-1;
          shop.shopNum=shop.shopNum+num;
        },
        sortClick:function (sortOrder,type) {
          this.$store.commit('sortShopList',{
            sortOrder:sortOrder,
            type:type
          });
        },
        handlerDblclick:function (shopName) {
          this.$router.push({ name: 'about', params: { shopName: shopName }});
          // this.$router.push({path:'/about',query:{shopName:shopName}});
        }
      },
      computed:{
          count(){
            var num=0;
            this.shopList.map(item=>{
              if (item.state==0)
               num+=item.shopPrice*item.shopNum;
            });
            return num;
          },
          titleList(){
              return this.$store.state.mytableVuex.titleList;
          },
          shopList:{
              get(){
                return this.$store.state.mytableVuex.shopList;
              }
          }
      },
      watch:{
        shopList:{
          handler(val){
              var _this=this;
              val.forEach(function (value,index) {
                if (value.shopNum>_this.maxNum)
                    value.shopNum=_this.maxNum;
                if (value.shopNum<_this.minNum)
                    value.shopNum=_this.minNum;
              });
            },deep:true
        }
      }
    }
</script>

<style scoped>
  .tableClass table tbody,thead th,td{
    text-align: center;
    vertical-align: middle;
  }

  .countClass{
    text-align: right;
    float: right;
  }

  td input{
    width: 60px;
  }

  a{
    cursor:pointer;
    color: #2aabd2;
  }

</style>

<2> 、筛选按钮组件:myScreen.vue

<template>
  <div class="form-inline">
    <select class="form-control" v-model="selectedVal">
      <option v-for="(item) in selectedList" :value="item.value">{{item.text}}</option>
    </select>
    <button class="btn btn-primary" @click="seachClick">筛选</button>
    <button class="btn btn-primary" @click="restClick">重置</button>
    <label for="one">1</label>&nsup;
    <input id="one" v-model="radioValue" :value="1" type="radio">
    <label for="two">2</label>&nsup;
    <input id="two" v-model="radioValue" :value="2"  type="radio">
  </div>
</template>

<script>
    export default {
        name: "myScreen",
        data(){
          return{
            selectedList:[
              {text:'苹果',value:'苹果'},
              {text:'香蕉',value:'香蕉'},
              {text:'橙子',value:'橙子'}
            ]
          }
        },
      computed:{
        radioValue:{
          get(){
            return this.$store.state.mytableVuex.radioValue;
          },
          set(newValue){
            return this.$store.commit('setRadioValue',newValue);
          }
        },
        selectedVal:{
          get(){
            return this.$store.state.mytableVuex.selected;
          },
          set(newValue){
            return this.$store.commit('setSelectedVal',newValue);
          }
        }
      },
      methods:{
        seachClick(){
          this.$store.commit('setShopList','seach');
        },
        restClick(){
          this.$store.commit('setShopList','rest');
        }
      }
    }
</script>

<style scoped>

</style>

<3>、myTab.vue:选项卡组件

<template>
    <div class="myTab">
        <div class="myTab-header" >
          <div :class="tabClass(nav)" v-for="(nav,index) in navList" @click="handlerClick(nav)">
              {{nav.lable}}
          </div>
        </div>
        <div class="myTab-center">
            <slot></slot>
        </div>
    </div>
</template>

<script>
    export default {
        name: "myTab",
        data() {
          return {
            navList:[],
            currentVal:{},
            intNum:0
          }
        },
        methods:{
          getTabsPane(){
            return  this.$children.filter(function (item,index) {
              return item.$options.name==="pane";
            })
          },
          updateNav(){
            var _this=this;
            _this.navList=[];
            this.getTabsPane().forEach(function (item,index) {
              _this.navList.push({
                lable:item.lable,
                name:item.name
              });
              if (index==_this.intNum){
                _this.currentVal=item.name;
              }
            });
            this.updateState();
          },
          updateState(){
            var _this=this;
            this.getTabsPane().map(item=>{
              item.show=item.name===_this.currentVal;
            });
          },
          handlerClick:function(nav){
            this.currentVal=nav.name;
            this.updateState();
          },
          tabClass:function(item){
            return[
              'tabs-tab',
              {
                'tabs-tab-active':item.name===this.currentVal
              }
            ]
          }
        }
    }
</script>

<style scoped>
  .tabs-tab{
    display: inline-block;
    padding: 4px 16px;
    border: 1px solid #2aabd2;
    cursor: pointer;
    position: relative;
    margin-left: 2px;
    margin-bottom:2px;
  }

  .tabs-tab-active{
    background: #ffff00;
  }

</style>

<4>、myPane.vue:选项组件

<template>
    <div v-if="show">
      <slot></slot>
    </div>
</template>

<script>
    export default {
        name: "pane",
        props:{
          lable:{
            type:String,
            default:""
          },
          name:{
            type:String,
            default: ""
          }
        },
      data(){
        return{
          show:false
        }
      },
      methods:{
          updateNav(){
            this.$parent.updateNav();
          }
      },
      mounted(){
         this.updateNav();
      }
    }
</script>

<style scoped>

</style>

<5>、myTest.vue:考试页面组件

<template>
      <div class="my-page-testpaper">
          <slot name="my-page-testpaper" v-for="quesnaire in quesnaireList"
          :id="quesnaire.id" :testTitle="quesnaire.testTitle" :quesnaireType="quesnaire.quesnaireType"
          :preserve="quesnaire.preserve" :quesnaireValue="quesnaire.quesnaireValue">

          </slot>
      </div>
</template>

<script>
    export default {
        name: "myTest",
        props:{
          quesnaireList:{}
        }
    }
</script>

<style scoped>

</style>

<6>、myBth.vue:考试页面按钮组件

<template>
    <div class="my-page-mybtn">
        <slot name="my-page-mybtn" v-for="btn in bthList" :btnId="btn.id" :btnText="btn.text">
        </slot>
    </div>
</template>

<script>
    export default {
        name: "myBth",
        data(){
            return{
              bthList:[
                {id:"up",text:"上一页"},
                {id:"next",text:"下一页"},
                {id:"rest",text:"重置"},
                {id:"submit",text:"提交"}
              ]
            }
        }
    }
</script>

<style scoped>
  .my-page-mybtn{
    text-align: center;
  }
</style>

(6)、router目录:

<1>、 pageMyTable.vue:整合所有组件(访问后路由视图就加载这个组件)

<template>
  <div>
    <myTab>
        <myPane lable="表格" name="table">
            <myScreen></myScreen>
            <myTable></myTable>
        </myPane>
        <myPane lable="考卷" name="test">
          <div class="row">
            <div class="col-md-4"></div>
            <div class="col-md-4">
              <div class="my-page">
                <div class="testPage-title">
                  <span>考试问卷</span>
                </div>
                <div class="testPage-center">
                    <myTest :quesnaireList="quesnaireList">
                        <template slot="my-page-testpaper"  slot-scope="props">
                          <div class="my-page-testpaper-quesnaire" v-show="props.id==currentPage">
                            <div class="my-page-testpaper-quesnaire-problem" >
                              <span>{{props.id}}.{{props.testTitle}}</span>
                            </div>
                            <div class="my-page-testpaper-elm-center">
                              <div class="my-page-testpaper-elm-answer" v-if="props.quesnaireType=='radio'" v-for="(answer,index) in props.quesnaireValue">
                                <label for="index">{{answer}}</label>
                                <input id="index" type="radio" :value="answer" v-model="sex">
                              </div>
                              <div class="my-page-testpaper-elm-answer" v-if="props.quesnaireType=='checkbox'" v-for="(answer,index2) in props.quesnaireValue">
                                <label for="index2">{{answer}}</label>
                                <input id="index2" type="checkbox" :value="answer" v-model="hobby">
                              </div>
                              <div class="my-page-testpaper-elm-answer" v-if="props.quesnaireType=='textarea'">
                                <textarea id="index3" placeholder="请输入简介,不少于5个字" v-model="introduc" style="height: 70px"></textarea>
                              </div>
                            </div>
                          </div>
                        </template>
                    </myTest>
                </div>
                <div class="testPage-foot">
                  <myBth >
                      <template slot="my-page-mybtn"  slot-scope="props">
                        <button id="up"  @click="handlerClick" v-show="props.btnId=='up'" :class="{computCls:currentPage==1}" class="btn btn-primary my-page-mybtnelm">{{props.btnText}}</button>
                        <button  id="next"  @click="handlerClick" v-show="props.btnId=='next' && currentPage!=quesnaireList.length"  class="btn btn-primary my-page-mybtnelm">{{props.btnText}}</button>
                        <button  id="rest"  @click="handlerClick" v-show="props.btnId=='rest'"  class="btn btn-primary my-page-mybtnelm">{{props.btnText}}</button>
                        <button  id="submit"  @click="handlerClick" v-show="props.btnId=='submit' && currentPage==quesnaireList.length"  class="btn btn-primary my-page-mybtnelm">{{props.btnText}}</button>
                      </template>
                  </myBth>
                </div>
              </div>
            </div>
            <div class="col-md-4"></div>
          </div>
        </myPane>
    </myTab>
  </div>
</template>

<script>
  import myTable from '../../components/myTable.vue';
  import myScreen from '../../components/myScreen.vue';
  import myPane from '../../components/myPane.vue';
  import myTab from '../../components/myTab.vue';
  import myTest from '../../components/myTest.vue';
  import myBth from  '../../components/myBth.vue';
    export default {
        name: "pageMyTable",
        methods:{
          handlerClick:function (e) {
            if (e.target.id=='rest') {
              if (this.currentPage==1){this.sex=''};
              if (this.currentPage==2){this.hobby=[]};
              if (this.currentPage==3){this.introduc=''};
              return false;
            };

            if (e.target.id=='next') {
              var obj=this.isClick(this.currentPage);
              if (!obj.isTrue){
                alert(obj.isString);
                return false;
              };
              this.$store.commit('setCurrentPage',1);
            };

            if (e.target.id=='submit') {
              var obj=this.isClick( this.currentPage);
              if (!obj.isTrue){
                alert(obj.isString);
                return false;
              };
              alert("提交成功!,您的性别为:"+this.sex+"<br>"+"您的爱好是:"+this.hobby+"<br>"+"您的简介是:"+this.introduc)
            };
            if (e.target.id=='up') { this.$store.commit('setCurrentPage',-1);};
          },
          isClick:function (page) {
            var dataArr=this.quesnaireList.filter(function (item) {
              return item.id==page
            });
            var data=dataArr[0];
            var obj={};
            var isString='';
            var isTrue=true;
            if (data.preserve=='sex') {
              isTrue=this.sex.length==0?false:true;
              isString='性别没有选择,不能提交!';
            }else if (data.preserve=='hobby') {
              isTrue=this.hobby.length<2?false:true;
              isString='爱好少于2个,不能提交!';
            }else {
              isTrue=this.introduc.length<5?false:true;
              isString='简介少于5个字,不能提交!';
            };
            obj.isTrue=isTrue;
            obj.isString=isString;
            return obj;
          }
        },
        computed:{
          sex:{
            get(){
              return this.$store.state.mytableVuex.sex;
            },
            set(newValue) {
              return this.$store.commit('setSex',newValue);
            }
          },
          hobby:{
            get(){
              return this.$store.state.mytableVuex.hobby;
            },
            set(newValue){
              return this.$store.commit('setHobby',newValue);
            }
          },
          introduc:{
            get(){
              return this.$store.state.mytableVuex.introduc;
            },
            set(newValue){
              return this.$store.commit('setIntroduc',newValue);
            }
          },
          quesnaireList(){
            return this.$store.state.mytableVuex.quesnaireList;
          },
          currentPage:{
            get(){
              return this.$store.state.mytableVuex.currentPage;
            }
          }
        },
        components:{
          myTable,
          myScreen,
          myTab,
          myPane,
          myTest,
          myBth
        }
    }
</script>

<style scoped>
  .my-page{
    margin-top: 20px;
    border: 1px solid #1E9FFF;
    height: 420px;
  }

  .testPage-title{
    background-color: #1E9FFF;
    text-align: center;
    font-size: 15px;
    font-family: "Angsana New";
    height: 30px;
  }

  .testPage-title span{
    line-height: 30px;
  }


  .my-page-testpaper{
    margin: 25px auto;
    height: 300px;
    display: flex;
    flex-flow:column  wrap;
    justify-content: center;
    align-items:center;
  }

  .my-page-testpaper-quesnaire-problem{
    text-align: center;
  }

  .my-page-testpaper-elm-center{
    display: flex;
    flex-flow:row  nowrap;
    justify-content: center;
    align-items:center;
  }

  .my-page-mybtnelm{
    margin-left: 10px;
  }

  .computCls{
    opacity:0.5;
    pointer-events:none;
  }

</style>

<2>、 about.vue:表格组件双击就进入这个路由视图(主要是练习一下路由转发传参,没什么用场)

<template>
  <div>
    {{name}}
    {{this.$route.params.shopName}}
  </div>
</template>
<script>
    export default {
        name: "about",
        data(){
          return{
            name:"about"
          }
        }
    }
</script>

<style scoped>

</style>

以上就是根据vue.js实在前12章的知识点做出的demo,基本上书上涵盖的知识点都用上了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值