一、项目结构
说明:用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>⊄<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>⊅
<input id="one" v-model="radioValue" :value="1" type="radio">
<label for="two">2</label>⊅
<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,基本上书上涵盖的知识点都用上了。