一、项目结构

在这里插入图片描述

二、基础配置(引入js框架\样式\vuex\router\axios等):

和上一个博客中的一样,不同的是需要多一个element-ui配置:
(1)、安装npm install element-ui -S
(2)、main.js里面引入:
	import ElementUI from 'element-ui';
	import 'element-ui/lib/theme-chalk/index.css';
	Vue.use(ElementUI);

三、各个文件:

(1)、src\vuex

<1>:modules.js

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

import shopStore from './shopStore/index.js';
import questionStore from './questionStore/index.js';
import createPersistedState from "vuex-persistedstate"
// vuex-persistedstate --save

export default new Vuex.Store({
  modules:{
    shopStore:shopStore,
    questionStore:questionStore
  },
  plugins: [createPersistedState({
    storage: window.sessionStorage
  })]
})

<2>、src\vuex\questionStore\index.js:

//声明数据
const state={
  questionList:[
    {id:1,question:'你的性别是?',modelVal:'sex',type:'radio',answer:['男','女','保密']},
    {id:2,question:'你的爱好是?',modelVal:'hobby',type:'checkbox',answer:['游泳','篮球','足球']},
    {id:3,question:'请填写自我介绍!',modelVal:'remarks',type:'textarea',answer:''}
  ],
  currentPage:1
};

const mutations={
  setCurrentPage(state,data){
    state.currentPage+=data;
  }
};

export default {
  state,
  mutations
}

<3>、src\vuex\shopStore\index.js:

import productData from '../../../static/product.js';

//声明取出重复方法
function getFilterArray(array) {
  const res= [];
  const json = {};
  array.forEach((item,index)=>{
    const _self=item;
    if (!json[_self]) {
      res.push(_self);
      json[_self]=1
    }
  })
  return res;
};


//声明数据
const state={
  productList:[],
  carList:[]
};

//声明数据赋值方法
const mutations={
  setAllProduct(state,data){
    state.productList=data;
  },
  addCar(state,data){
    let val=state.carList.find((item)=>item.id==data);
    if (val!=undefined){
      val.count++;
    }else {
      state.carList.push({
        id:data,
        count:1
      })
    }
  },
  restCar(state,data){
    data.forEach((item)=>{
      let index=state.carList.findIndex((val)=>item.id==val.id);
      state.carList.splice(index,1);
    });
  }
};

//声明数据逻辑方法
const actions={
  getAllProduct(concent){
    setTimeout(()=>{
      concent.commit('setAllProduct',productData);
    },50);
  }
};

//声明视图
const getters={
    colorAll:(state)=>{
      let color=state.productList.map((item)=>item.color);
      return  getFilterArray(color);
    },
    brandAll:(state)=>{
      let brand=state.productList.map((item)=>item.brand);
      return getFilterArray(brand);
    }
};

export default {
  state,
  mutations,
  actions,
  getters
}

(2)、src\router

<1>:src\router\index.js:

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

Vue.use(Router)

//配置路由路径
const RoutersPath=[
  {
    path: '*',
    meta:{
      title:'总列表'
    },
    redirect: '/productList'
  },
  {
    path: '/productList',
    name:'productList',
    meta:{
      title:'商品列表',
      show:true
    },
    component:(resolve)=>require(['./view/productList.vue'],resolve)
  },{
    path: '/carList',
    name:'carList',
    meta:{
      title:'购物车',
      show:true
    },
    component:(resolve)=>require(['./view/carList.vue'],resolve)
  },{
    path: '/productInfo',
    name:'productInfo',
    meta:{
      title:'商品详情'
    },
    component:(resolve)=>require(['./view/productInfo.vue'],resolve)
  },{
    path: '/question',
    name:'question',
    meta:{
      title:'问卷调查',
      show:true
    },
    component:(resolve)=>require(['./view/question.vue'],resolve)
  }

]

//配置路由属性
const routerConfig={
  routes:RoutersPath,
  mode:'history',
  base:__dirname
}

//实例化一个router对象
const router=new Router(routerConfig);

//声明钩子函数
router.beforeEach((to,from,next)=>{
  window.document.title=to.meta.title;
  next();
});

router.afterEach((to,from,next)=>{
  window.scroll(0,0);
});

export default router;

<2>:src\router\view\carList.vue:

<template>
  <div class="carList-main">
    <el-row type="flex" justify="start">
      <el-col :span="24">
        <div class="carList-main-containerCls">
          <el-select clearable filterable size="small" v-model="nowSelect" placeholder="请选择">
            <el-option v-for="item in dictList" :key="item.id" :label="item.name" :value="item.name"></el-option>
          </el-select>
          <el-button type="primary" @click="search"  size="small" icon="el-icon-search">搜索</el-button>
        </div>
      </el-col>
    </el-row>

    <el-row type="flex" justify="center">
      <el-col :span="24">
        <template v-if="carlist.length!=0">
          <el-table :data="dictList" style="width: 100%" stripe border @selection-change="handleSelectionChange" show-summary sum-text="合计">
            <el-table-column type="selection" width="55"></el-table-column>
            <el-table-column type="index"></el-table-column>
            <el-table-column align="center" label="商品" prop="name">
              <template slot-scope="scope">
                <el-popover trigger="hover" placement="top">
                  <p>名称: {{ scope.row.name }}</p>
                  <p>价格: {{ scope.row.sales }}</p>
                  <div slot="reference" class="name-wrapper">
                    <el-tag size="medium">{{ scope.row.name }}</el-tag>
                  </div>
                </el-popover>
              </template>
            </el-table-column>
            <el-table-column align="center" label="品牌" prop="brand"></el-table-column>
            <el-table-column align="center" label="价格" prop="sales" sortable></el-table-column>
            <el-table-column align="center" label="数量" prop="count" sortable>
              <template slot-scope="scope">
                <div slot="reference" class="name-wrapper">
                  <el-input type="number" clearable max=100 min=0 v-model.lazy.number="scope.row.count" placeholder="请输入数量"></el-input>
                </div>
              </template>
            </el-table-column>
            <el-table-column align="center" label="总价" prop="total" sortable></el-table-column>
          </el-table>
        </template>
        <template v-else>
          <el-alert title="购物车暂时没有记录!"type="warning"></el-alert>
        </template>
      </el-col>
    </el-row>

    <el-row  type="flex"  :gutter="20" justify="start" style="margin: 15px">
      <el-col :span="6">
          <el-input v-model="input"  size="small" placeholder="请输入优惠码"></el-input>
      </el-col>
      <el-col :span="4">
           <el-button type="primary"  size="small" @click="handleYouhui">使用优惠码</el-button>
      </el-col>
      <el-col :span="3">
        <el-button type="primary"  size="small" @click="shop">结算</el-button>

        <el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
          <span>您购买的商品是:</span>
          <div v-for="multipitem in nowMultipleSelection">{{multipitem.name}}</div>
          <span slot="footer" class="dialog-footer">
          <el-button @click="dialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
        </span>
        </el-dialog>

      </el-col>
      <el-col :span="9"></el-col>
      <el-col :span="6" >
          <div class="carList-total">
            <div style="color: #e38d13">当前商品原价:{{total}}</div>
            <div style="color: #e38d13">当前优惠价:{{youhui}}</div>
            <div style="color: #e38d13">优惠后价格:{{total-youhui}}</div>
          </div>
      </el-col>
    </el-row>
  </div>
</template>

<script>
  export default {
    name: "carList",
    data(){
      return{
        carlist:this.$store.state.shopStore.carList,
        productList:this.$store.state.shopStore.productList,
        multipleSelection: [],
        nowMultipleSelection: [],
        input:'',
        youhui:0,
        dialogVisible:false,
        maxNum:5,
        minNum:0,
        nowSelect:'',
        fiflerSelect:''
      }
    },
    computed:{
      dictList(){
        var _this=this;
        var dict={};
        this.productList.forEach((item)=>{
          dict[item.id]=item;
        });
        var dictCar=this.carlist.map((item)=>{
          item.name=dict[item.id].name;
          item.brand=dict[item.id].brand;
          item.image=dict[item.id].image;
          item.sales=dict[item.id].sales;
          item.color=dict[item.id].color;
          item.total=dict[item.id].sales*item.count;
          return item;
        });
        if (_this.fiflerSelect!=''){
          dictCar=dictCar.filter((item)=>item.name==_this.fiflerSelect);
        }
        return dictCar;
      },
      total(){
        var total=0;
        this.multipleSelection.filter((item)=>{
          total+=item.sales*item.count
        });
        return total;
      }
    },
    methods:{
      handleSelectionChange(val){
        this.multipleSelection = val;
      },
      shop(){
        var _this=this;
        this.$confirm('此操作将购买选中的商品, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          _this.$store.commit('restCar',this.multipleSelection);
          _this.dialogVisible=true;
          _this.nowMultipleSelection=_this.multipleSelection;
          _this.multipleSelection=[];
        }).catch(() => {
          this.$message({
            type: 'info',
            message: '已取消购买'
          });
        });
      },
      handleYouhui(){
        if (this.total<=0){
          this.$message({
            message: '请先购买商品!',
            type: 'warning',
            duration:1000
          });
          return false;
        }

        if (this.input!='vue.js'){
          this.$message({
            message: '优惠码错误',
            type: 'warning',
            duration:500
          });
          this.input='';
        }else{

          if (this.youhui>0){
            this.$message({
              message: '已经优惠过了,不能再使用!',
              type: 'warning',
              duration:500
            });
            return false;
          }
          this.youhui=500;
        }
      },
      search(){
          this.fiflerSelect=this.nowSelect;
      }
    },
    watch:{
      dictList:{
        handler(val){
          var _this=this;;
          val.forEach((item)=>{
            if (item.count>_this.maxNum){
              item.count=_this.maxNum
                this.$message({
                  message: '超出范围,最大值为5!',
                  type: 'warning',
                  duration:1000
                });
            };
            if (item.count<_this.minNum){
              item.count=_this.minNum
              this.$message({
                message: '超出范围,最小值为0!',
                type: 'warning',
                duration:1000
              });
            };
          });
        },deep:true
      }
    }
  }
</script>

<style scoped>
  .carList-total{
    margin: 10px auto;
    text-align: right;
    vertical-align: middle;
  }
  .carList-span{
    margin: 10px;
    text-align: left;
  }

  .carList-main-containerCls{
    margin: 10px auto;
  }

</style>

<3>:src\router\view\productInfo.vue:

<template>
    <div class="productInfo-main">
      <el-row  type="flex" justify="center" align="middle">
        <el-col :span="3"></el-col>
        <el-col :span="18">
            <el-container>
              <el-header>
                <el-alert title="商品详情" center  type="success" :closable="false"></el-alert>
              </el-header>
              <el-main>
                  <el-container>
                    <el-aside>
                        <img :src="require('@/assets/img/'+productInfoData.image)"></img>
                    </el-aside>
                    <el-main>
                        <div class="productInfo-main-item"><span>名称:{{productInfoData.name}}</span></div>
                        <div class="productInfo-main-item"><span>品牌{{productInfoData.brand}}</span></div>
                        <div class="productInfo-main-item"><span>价格:{{productInfoData.sales}}</span></div>
                        <div class="productInfo-main-item"><el-button @click="addCar(productInfoData.id)" type="warning" round>go</el-button></div>
                    </el-main>
                  </el-container>
              </el-main>
            </el-container>
        </el-col>
        <el-col :span="3"></el-col>
      </el-row>
    </div>
</template>

<script>
    export default {
        name: "productInfo",
        data(){
          return{
            productData:this.$store.state.shopStore.productList,
            id:this.$route.params.id
          }
        },
        computed:{
          productInfoData(){
            var _this=this;
            return  this.productData.find((item)=>item.id==_this.id);
          }
        },
        methods:{
          addCar(id){
            this.$store.commit('addCar',id);
            this.$message({
              message: '购买成功!',
              type: 'success',
              duration:500
            });
          }
        }

    }
</script>

<style scoped>
.productInfo-main-item{
  text-align: center;
  margin: 5px auto;
}
</style>

<4>:src\router\view\productList.vue:

<template>
   <div class="productList-main">
     <div class="shopList-conterll-item">
       <div class="shopList-item">
         <el-tag>排序:</el-tag>
         <el-tag type="info" :class="[{itemClsAct:nowSort.indexOf('sales')>-1},'itemCls']" @click="handleSort('sales')">价格<span v-if="nowSort.indexOf('ASC')>-1">↓</span><span v-else>↑</span></el-tag>
         <el-tag type="info" :class="[{itemClsAct:nowSort.indexOf('cost')>-1},'itemCls']" @click="handleSort('cost')">销量<span v-if="nowSort.indexOf('ASC')>-1">↓</span><span v-else>↑</span></el-tag>
       </div>
       <div class="shopList-item">
         <el-tag>颜色:</el-tag>
         <el-tag :class="[{itemClsAct:nowColor.indexOf(item)>-1},'itemCls']" type="info" @click="handleClick(item)" name="item" :key="item" v-for="item in colorAll">{{item}}</el-tag>
       </div>
       <div class="shopList-item">
         <el-tag>品牌:</el-tag>
         <el-tag :class="[{itemClsAct:nowBrand==item},'itemCls']" type="info" @click="handleBrandClick(item)" name="item" :key="item" v-for="item in brandAll">{{item}}</el-tag>
       </div>
     </div>
     <div class="shopList-product">
       <product v-for="item in fiferList" :key="item.id" :item="item"></product>
     </div>
   </div>
</template>

<script>
  import product from '../../components/product.vue';
   export default {
       name: "productList",
       components:{
         product
       },
       data(){
           return{
             nowColor:[],
             nowBrand:'',
             nowSort:''
           }
       },
     computed:{
       productList(){
         return this.$store.state.shopStore.productList
       },
       colorAll(){
         return this.$store.getters.colorAll;
       },
       brandAll(){
         return this.$store.getters.brandAll;
       },
       fiferList(){
         let list=[...this.productList];
         //按颜色筛选
         if(this.nowColor.length!=0){
           list=list.filter((item)=>{
             var obj=this.nowColor.find((value)=>value==item.color);
             if (obj!=undefined) return  item;
           })
         }
         //按品牌筛选
         if (this.nowBrand!=''){
           list=list.filter((item)=>item.brand==this.nowBrand);
         }

         //排序
         if (this.nowSort!=''){
             //按价格排序
             if (this.nowSort.indexOf('sales')>-1){
               list=list.sort((a,b)=>{
                 return this.nowSort.indexOf("ASC")>-1?a.sales-b.sales:b.sales-a.sales;
               });
             }else {
               //按销量排序
               list=list.sort((a,b)=>{
                 return this.nowSort.indexOf("ASC")>-1?a.cost-b.cost:b.cost-a.cost;
               });
             }
         }
         return list;
       }
     },
     methods:{
         getAllProduct(){
           this.$store.dispatch('getAllProduct');
         },
         handleClick(itemVal){
           var findIndex=this.nowColor.findIndex((item)=>item==itemVal);
           if (findIndex>-1){
             this.nowColor.splice(findIndex,1);
           }else{
             this.nowColor.push(itemVal);
           }
         },
         handleBrandClick(itemVal){
           this.nowBrand=this.nowBrand==itemVal?'':itemVal;
         },
         handleSort(type){
             //如果是按价格排序
             if (type=='sales') {
               this.nowSort=this.nowSort==''?'salesASC':(this.nowSort=='salesASC'?'salesDESC':'salesASC');
             }
             //如果是按销量排序
             if (type=='cost') {
               this.nowSort=this.nowSort==''?'costASC':(this.nowSort=='costASC'?'costDESC':'costASC');
             }
         }
     },
     created(){
       this.getAllProduct();
     }
   }
</script>

<style scoped>
 .itemCls{
   cursor: pointer;
   margin: 0px 2px;
 }
 .itemClsAct{
   background-color: #ffbd6c;
 }

 .shopList-item{
   margin: 5px auto;
   display: flex;
   flex-flow:row  wrap;
   justify-content: start;
   align-items:center;
 }


 .shopList-product{
   display: flex;
   flex-flow:row  wrap;
   justify-content: start;
   align-items:center;
 }
</style>

<5>:src\router\view\question.vue:

<template>
  <div class="question-main">
    <el-container>
      <el-header>
        <span>调查问卷</span>
      </el-header>
      <el-main>
        <div class="question-main-slot">
            <questionComp>
              <template slot="questionSlot" slot-scope="props">
                <div class="question-main-slot-quesnaire" v-show="props.questionItem.id==currentPage">
                    <div class="question-main-slot-quesnaire-title">
                      <span>{{props.questionItem.id}}.{{props.questionItem.question}}</span>
                    </div>
                    <div class="question-main-slot-quesnaire-answer"  v-if="props.questionItem.modelVal=='sex'">
                      <el-radio-group v-model="sex">
                        <el-radio v-for="answer in props.questionItem.answer" :key="answer" :label="answer" >{{answer}}</el-radio>
                      </el-radio-group>
                    </div>
                  <div class="question-main-slot-quesnaire-answer"  v-if="props.questionItem.modelVal=='hobby'">
                    <el-checkbox-group v-model="hobby">
                      <el-checkbox v-for="answer in props.questionItem.answer" :key="answer" :label="answer" >{{answer}}</el-checkbox>
                    </el-checkbox-group>
                  </div>
                  <div class="question-main-slot-quesnaire-answer" v-if="props.questionItem.modelVal=='remarks'">
                    <el-input type="textarea" autosize  placeholder="请输入内容" v-model="remarks"></el-input>
                  </div>
                </div>
              </template>
            </questionComp>
        </div>
      </el-main>
      <el-footer></el-footer>
    </el-container>
  </div>
</template>

<script>
  import questionComp from '../../components/questionComp.vue';
    export default {
        name: "question",
        components:{
          questionComp
        },
      data(){
          return{
            sex:'',
            hobby:[],
            remarks:''
          }
      },
      computed:{
        currentPage(){
          return this.$store.state.questionStore.currentPage
        }
      }
    }
</script>

<style scoped>
  .el-header, .el-footer {
    background-color: #B3C0D1;
    color: #333;
    text-align: center;
    line-height: 60px;
  }

  .question-main-slot-quesnaire-answer{
    margin: 5px auto;
  }

</style>

(3)、src\components

<1>:product.vue:

<template>
<div class="product-main" @click.stop="routerInfo(item.id)">
  <div class="product-main-item" :style="{backgroundColor:colors[item.color]}">
    <el-row  type="flex" justify="center" align="middle">
      <el-col :span="24">
        <div class="product-main-img">
          <img :src="require('@/assets/img/'+item.image)">
        </div>
        <div class="product-main-name">
          {{item.name}}
        </div>
        <div class="product-main-sales">
          ¥{{item.sales}}
        </div>
        <div class="product-main-cost">
          销量:{{item.cost}}
        </div>
      </el-col>
    </el-row>
    <div class="product-add" @click.stop="addProduct(item.id)">加入购物车</div>
  </div>
</div>

<2>:questionComp.vue:

<template>
    <div class="questionComp-main">
      <div class="questionComp-main-problem">
        <el-row type="flex" justify="center" align="center">
            <slot name="questionSlot" v-for="item in question" :questionItem="item"></slot>
        </el-row>
      </div>
      <div class="questionComp-main-btn">
        <el-row type="flex" justify="center" :gutter="20">
          <el-button @click="handlerClick('up')" size="small" type="primary">上一题</el-button>
          <el-button @click="handlerClick('next')" size="small" type="primary">下一题</el-button>
          <el-button @click="handlerClick('rest')" size="small" type="primary">重置</el-button>
          <el-button @click="handlerClick('submit')" size="small" type="primary">提交</el-button>
        </el-row>

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

<script>
    export default {
        name: "questionComp",
        data(){
          return{
            question:this.$store.state.questionStore.questionList
          }
        },
      methods:{
        handlerClick(type){
          if (type=='up'){
            this.$store.commit('setCurrentPage',-1)
          }
          if (type=='next') {
            this.$store.commit('setCurrentPage',1)
          }
        }
      }
    }
</script>

<style scoped>

</style>

(4)、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'
import router from './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 '../static/css/main.css';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
// 引入axiosUtil.js
import axiosUtil from './util/axiosUtil.js';
//引入Vuex总仓库
import store from './vuex/modules.js';

// 设置全局变量$axios
Vue.prototype.$axios = axiosUtil;
Vue.use(ElementUI);
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

(5)、App.vue

<template>
  <div id="app">
    <el-row type="flex" justify="center">
        <el-col :span="6"></el-col>
        <el-col :span="12">
            <el-menu :default-active="this.$route.path" mode="horizontal"
                 background-color="#6F6D6D" text-color="#F3F2F2" active-text-color="#EFEB7F" @select="handleSelect">
              <el-menu-item  v-for="(item,i) in navList" :key="i" :index="item.name">
                <template v-if="item.meta.show">
                    <i class="el-icon-location"></i>
                    {{item.meta.title}}
                    <template v-if="carTotal!=0 && item.meta.title=='购物车'">
                      <span>({{carTotal}})</span>
                    </template>
                </template>
              </el-menu-item>
            </el-menu>
          <router-view/>
        </el-col>
        <el-col :span="6"></el-col>
    </el-row>
  </div>
</template>

<script>
export default {
  name: 'App',
  computed:{
    navList(){
      return this.$router.options.routes.filter((item)=>{
        if (item.meta.show!= undefined){
          return item;
        }
      });
    },
    carTotal(){
      var carList=this.$store.state.shopStore.carList;
      var num=0;
      carList.forEach((item)=>{
        num+=item.count
      });
      return num;
    }
  },
  methods:{
    handleSelect(key,keyPath){
      this.$router.push({name:key});
    }
  }
}
</script>

<style>
</style>

四、总结:

这个项目和上一篇博客页面上基本一样,只是为了练习一下element-ui就重做了一遍。
新的心得是:
1、更加理解了slot:slot说白了,就是为了父组件向子组件发送dom,比如要显示3个子组件,
子组件分为顶部\中部\底部分,其中顶部和底部样式都一样,中间部分不一样。
这个时候就要用到slot,在父组件任何位置发送dom,插入到子组件里面声明了slot的地方,从而起到复用的作用。

2、加深了计算属性和data的区别,
动态的数据要用计算属性,data数据初始化后,如果不手动改变,数据不会改变

3、熟悉了element-ui

4、加深了es6语法中this关键字的理解,=>里面的this是定义时的对象。
Logo

前往低代码交流专区

更多推荐