前端笔记之Vue(六)分页排序|酷表单实战&Vue-cli

Tony 2019-05-21 01:4692 阅读

一、分页排序案例

 后端负责提供接口(3000

 前端负责业务逻辑(8080

 接口地址:从8080跨域到3000拿数据

 http://127.0.0.1:3000/shouji

 http://127.0.0.1:8080/api/shouji

分页排序接口:
http://127.0.0.1:3000/shouji?page=1&pagesize=5&sortby=price&sortdirection=dao

代理跨域回来的数据接口地址: http://127.0.0.1:8080/api/shouji?page=1
&pagesize=5&sortby=price&sortdirection=dao

 

后端app.js

var express = require("express");
var url = require("url");
var app = express();

var arr = [
    {"id" : 1 , "title" : "苹果A" , "price" : 1699},
    {"id" : 2 , "title" : "苹果B" , "price" : 1999},
    ...
    {"id" : 14 , "title" : "苹果N" , "price" : 8888}
];

app.get("/shouji" , function(req,res){
    var obj = url.parse(req.url, true).query;
    var page = obj.page;   //页码
    var pagesize = obj.pagesize; //每页显示的数量
    var sortby = obj.sortby;     //排序条件
    var sortdirection = obj.sortdirection; //排序条件(正序或倒序)

    //按照id或价格排序
    arr = arr.sort(function(a,b){
        if(sortdirection == "zheng"){
            return a[sortby] - b[sortby];
        }else if(sortdirection == "dao"){
            return b[sortby] - a[sortby];
        }
})
    //提供数据给前端
    res.json({
        "number" : arr.length , //商品总数量
        "results": arr.slice((page - 1) * pagesize, page * pagesize) //显示多少条数据
    })
});
app.listen(3000);
示例代码

 

前端main.js

import Vue from "vue";
import Vuex from "vuex";
import App from "./App.vue";
import store from "./store";
Vue.use(Vuex);

new Vue({
    el : "#app",
    store,
    render : (h) => h(App)
})
示例代码

 

新建taobao文件夹存放三要素,然后在文件夹的index.js中引入三要素:

state.jsaction.jsmutations.js三个文件:

export default {
    ...
}

 store/index.js

import Vue from "vue";
import Vuex from "vuex";
import createLogger from "vuex/dist/logger";

import counterState from "./counter/state.js"
import counterMutations from "./counter/mutations.js"
import counterActions from "./counter/actions.js"

import taobaoState from "./taobao/state.js"
import taobaoMutations from "./taobao/mutations.js"
import taobaoActions from "./taobao/actions.js"

Vue.use(Vuex);
//全局数据
const store = new Vuex.Store({
    state : {
        counterState,
        taobaoState
    },
    //同步的(commit)
    mutations : {
        ...counterMutations,
        ...taobaoMutations
    },
    //异步的(dispatch)
    actions : {
        ...counterActions,
        ...taobaoActions
    },
    plugins : [createLogger()]
});

export default store;
示例代码

 

state.js存储默认数据:

export default {
    page : 1,
    pagesize: 5,
    sortby : "id",
    sortdirection:"zheng",
    number : 0,
    results :[]
}
示例代码

 

App.vue

<template>
    <div>
        <table>
            <tr>
                <th>编号</th>
                <th>商品</th>
                <th>价格</th>
            </tr>
        </table>
    </div>
</template>
<script>
    export default {
        created(){
            //生命周期,当组件被创建时触发,发出一个异步请求接口数据
            this.$store.dispatch("init");
        }
    }
</script>
示例代码

 

actions.js

export default {
async init({commit,state}){
    var page = state.taobaoState.page;
    var pagesize = state.taobaoState.pagesize;
    var sortby = state.taobaoState.sortby;
    var sortdirection = state.taobaoState.sortdirection;
    //发出异步的get请求拿数据
    var {results,number} = await fetch(`/api/shouji?page=${page}&pagesize=${pagesize}
&sortby=${sortby}&sortdirection=${sortdirection}`).then(data => data.json());

    //数据从后端拿回来后,改变results和number
    //改变state只能通过mutations
    commit("changeResults", {results})
    commit("changeNumber", {number})
}
}
示例代码

 

mutations.js,此时可以从控制台logger中查看请求数据成功,然后回到App.vue的结构显示数据。

export default {
    changeResults(state , payload){ 
        state.taobaoState.results = payload.results;
    },
    changeNumber(state, payload) {
        state.taobaoState.number = payload.number;
    }
}
示例代码

 

回到App.vue显示数据和换页:

<template>
    <div>
        <table>
            <tr v-for="item in $store.state.taobaoState.results">
                <td>{{item.id}}</td>
                <td>{{item.title}}</td>
                <td>{{item.price}}</td>
            </tr>
        </table>
        <button v-for="i in allPage()" @click="changePage(i)">{{i}}</button>
    </div>
</template>
<script>
    export default {
        created(){
            //生命周期,当组件被创建的时候触发
            this.$store.dispatch("init");
        },
        computed:{
            allPage(){
                //计算总页码数:总数量 / 每页数量,向上取整
                var _state = this.$store.state.taobaoState;
                return Math.ceil(_state.number / _state.pagesize)
            }
        },
        methods : {
        //分页页码跳转
            changePage(page){
                this.$store.dispatch("changePage", {page});
            }
        }
    }
</script>
示例代码

 

actions.js

export default {
async init({commit,state}){
    ...
},
//其他都和init一样,只改名字即可
async changePage({commit,state},{page}){
    //改变page
    commit("changePage", {page})
    //凑齐4个数据
    var page = state.taobaoState.page;
    var pagesize = state.taobaoState.pagesize;
    var sortby = state.taobaoState.sortby;
    var sortdirection = state.taobaoState.sortdirection;

    //发出请求和init方法的一样
    var {results, number} = await fetch(`/api/shouji?page=${page}&pagesize=${pagesize}
&sortby=${sortby}&sortdirection=${sortdirection}`).then(data => data.json());

    //改变state只能通过mutations
    commit("changeResults", {results})
    commit("changeNumber", {number})
}
}
示例代码

 

mutations.js

export default {
    changeResult(state , payload){
        state.taobaoState.results = payload.results;
    },
    changeNumber(state, payload) {
        state.taobaoState.number = payload.number;
},
//页码跳转
    changePage(state , payload){
        state.taobaoState.page = payload.page;
    }
}
示例代码

 

 App.vue下面实现每页显示多少条:

<template>
    <div>
        <table>
            <tr v-for="item in $store.state.taobaoState.results">
                <td>{{item.id}}</td>
                <td>{{item.title}}</td>
                <td>{{item.price}}</td>
            </tr>
        </table>
        <button v-for="i in allPage()" @click="changePage(i)">{{i}}</button>

        <select v-model="pagesize"> 
            <option value="3">每页3条</option>
            <option value="5">每页5条</option>
            <option value="10">每页10条</option>
        </select>
    </div>
</template>
<script>
    export default {
        data(){
            return {
                pagesize : this.$store.state.taobaoState.pagesize
            }
        },
        created(){
            //生命周期,当组件被创建的时候触发
            this.$store.dispatch("init");
        },
        methods : {
            changePage(page){
                this.$store.dispatch("changePage" , {page});
            }
        },
    //v-mode的值不能加圆括号,所以不能直接计算,先通过data(){}中计算,再用watch监听变化
        //Vue提供一种更通用的方式来观察和响应Vue实例上的数据变动:侦听属性。
        //以V-model绑定数据时使用的数据变化监测
        //使用watch允许我们执行异步操作
        watch : {
            pagesize(v){ //当pagesize改变,发出一个请求,去改变pagesize
                this.$store.dispatch("changePageSize" , {pagesize : v});
            }
        }
    }
</script>
示例代码

 

actions.js

export default {
async init({commit,state}){
    ...
},
async changePageSize({commit,state},{pagesize}){
    //改变page
    commit("changePageSize", {pagesize:pagesize})
    //凑齐4个数据
    var page = state.taobaoState.page;
    var pagesize = state.taobaoState.pagesize;
    var sortby = state.taobaoState.sortby;
    var sortdirection = state.taobaoState.sortdirection;

    //发出请求
    var {results,number} = await fetch(`/api/shouji?page=${page}&pagesize=${pagesize}
&sortby=${sortby}&sortdirection=${sortdirection}`).then(data => data.json());

    //改变state只能通过mutations
    commit("changeResults", {results})
    commit("changeNumber", {number})
}
}
示例代码

 

mutations.js

export default {
    //...省略
    changePage(state , payload){
        state.taobaoState.page = payload.page;
    },
    changePageSize(state, payload) {
        state.taobaoState.pagesize = payload.pagesize;
    }
}
示例代码

 

下面实现id和价格的排序

App.vue

<template>
    <div>
        <table>
            <tr>
                <th>
                    id:
                    <button @click="changeSort('id','dao')"></button>
                    <button @click="changeSort('id','zheng')"></button>
                </th>
                <th>商品:</th>
                <th>
                    价格:
                    <button @click="changeSort('price','dao')"></button>
                    <button @click="changeSort('price','zheng')"></button>
                </th>
            </tr>
            <tr v-for="item in $store.state.taobaoState.results">
                <td>{{item.id}}</td>
                <td>{{item.title}}</td>
                <td>{{item.price}}</td>
            </tr>
        </table>
        <button v-for="i in allPage()" @click="changePage(i)" 
:class="{'cur':$store.state.taobaoState.page == i}">
{{i}}
</button>
    </div>
</template>
<script>
    export default {
        methods : {
            changePage(page){
                this.$store.dispatch("changePage", {page});
            },
            changeSort(sortby , sortdirection){
                this.$store.dispatch("changeSort", {sortby , sortdirection});
            }
        }
    }
</script>
<style>
    .cur{ background : orange;}
</style>
示例代码

 

actions.js封装成函数:

async function load(commit, state){
    //凑齐4个
    var page = state.taobaoState.page;
    var pagesize = state.taobaoState.pagesize;
    var sortby = state.taobaoState.sortby;
    var sortdirection = state.taobaoState.sortdirection;

    //发出请求
    var {results,number} = await fetch(`/api/shouji?page=${page}&pagesize=${pagesize}&sortby=${sortby}&sortdirection=${sortdirection}`).then(data => data.json());

    //数据从后端拿回来后,改变results和number
    //改变state只能通过mutations
    commit("changeResults", { results})
    commit("changeNumber", { number })
}

export default {
    async init({commit , state}){
        await load(commit , state);
    },
    async changePage({ commit, state } , {page}) {
        //改变page
        commit("changePage", { page: page})
        await load(commit, state);
    },
    async changePageSize({ commit, state }, { pagesize }) {
        //改变pagesize
        commit("changePageSize", { pagesize: pagesize })
        //页码归1
        commit("changePage", { page: 1 })
        await load(commit, state);
},
//排序
    async changeSort({commit, state}, {sortby, sortdirection}){
        //改变pagesize
        commit("changeSort", { sortby, sortdirection})
        //页码归1
        commit("changePage", { page: 1 })
        await load(commit, state);
    },
}
示例代码

 

mutations.js

export default {
     //...省略
    changePageSize(state, payload) {
        state.taobaoState.pagesize = payload.pagesize;
    },
    changeSort(state , payload){
        state.taobaoState.sortby = payload.sortby;
        state.taobaoState.sortdirection = payload.sortdirection;
    }
}
示例代码

二、Vue-cli

2.1 Vue-cli的安装

Vue 提供一个官方命令行工具(CLI),可用于快速搭建大型单页应用。该工具为现代化的前端开发工作流提供了开箱即用的构建配置。只需几分钟即可创建并启动一个带热重载、保存时静态检查以及可用于生产环境的构建配置的项目

Vue-cliVue的快速起步工具(脚手架工具),再也不用手动配webpack

Vue-loader的官网:

https://cn.vuejs.org/v2/guide/installation.html

https://vue-loader.vuejs.org/zh-cn/start/setup.html

 在全局安装vue-cli

npm install -g vue-cli

 

创建一个基于webpack模板的新项目文件夹,并初始化配置:

vue init webpack hello-vue
vue init webpack-simple hello-vue

vue init webpack-simple项目默认打包后只有一个htmljs文件(适合小项目)

vue init webpack项目默认打包完之后,会有很标准的目录(适合中大型项目)

 

两种方式初始化Vue-cli项目的目录差别很大,你会发现vue init webpack的方式初始化项目,默认提供了很多webpack的配置,也更加方便你对代理(跨域)、最终打包资源放到服务器什么目录、以及jscssimg和项目在打包过程等优化的配置等。

vue init webpack

vue init webpack-simple

 

 

 

 

 

安装依赖:

npm install

启动项目:

npm run dev

2.2 Vue-cli的配置讲解

"scripts": {
  "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
  "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
},

cross-env NODE_ENV=development 将环境变量设置成开发模式

cross-env NODE_ENV=production  将环境变量设置成生产模式

--open 自动开启浏览器

--hot 开启热更新, 热更新就是保存后进行局部刷新

打开项目以后 vue-cli给我们配置了很多东西。

 

.editorconfig对编辑器的统一配置,是让大家的代码有一个规范、代码缩进形式的统一,当大家提交代码后使用不同的编辑器打开时,显示的代码格式是一样的

root = true
[*]
charset = utf-8
indent_style = space //空格缩进
indent_size = 4      //统一缩进为4个
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

 

关于生产模式的配置

开发过程不需要优化配置,只有在生产模式下,才需要优化、css压缩打包到一个文件,js合并压缩,关于性能优化,小图片会转成base64 减少http请求。

if (process.env.NODE_ENV === 'production') {
  module.exports.devtool = '#source-map'
  // http://vue-loader.vuejs.org/en/workflow/production.html
  module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      compress: {
        warnings: false
      }
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  ])
}

vue-cli提供的src文件夹中的assets图片文件夹,移动到根目录外面去,就是为了不让图片webpack编译打包。

 

 

vue中使用图片的两种方式:

第一种:传统方式

<img src="../assets/logo.png">

 

第二种:使用vuev-bind指令来使用data数据中的图片:

<img :src="imgSrc">
<script>
export default {
  data () {
    return {
       imgSrc : "../assets/logo.png"
    }
  }
}
</script>

 

webpackpng的图片变成base64,使用url-loader

{
    test: /\.(png|jpg|gif|svg)$/,
    loader: 'url-loader',
    options: {
        limit: 8192
    }
}

limit是设置一个图片大小的临界点,值小于8192字节的图片就转成base64图片源码,好处就是能减少一个HTTP请求。

 


2.3项目打包上线

如果把自己的项目放到服务器运行,就需要使用npm run build将自己的项目打包出来。

 

然后在dist文件夹下面,就有打包、优化好的项目文件、打包好的项目文件可以放到服务器中运行。

注意:项目启动一定要在服务器环境下运行,在webpack服务器、nodephpnow服务器都可以。

 


三、酷表单项目

main.js

import Vue from "vue";
import Vuex from "vuex";
import App from "./App.vue";

Vue.use(Vuex);
// 创建一个全局仓库
const store = new Vuex.Store({
    state : {

    }
})

new Vue({
    el : "#app",
    store,
    render : (h) => h(App)
})
示例代码

第一步:写静态页面组件,App.vue

<template>
    <div>
        <div class="warp">
            <div class="leftPart">左侧部分</div>
            <div class="centerPart">
                <div class="outerBox onedit">
                    <div class="cbox">
                        <div class="qtitle"><em>*</em> 我是新的多选题目,请编辑</div>
                        <div class="qoption">
                            <label><input type="checkbox" />新的项目A</label>
                            <label><input type="checkbox" />新的项目B</label>
                        </div>
                    </div>
                    <span class="edit"></span>
                    <span class="up"></span>
                    <span class="down"></span>
                </div>
                <div class="outerBox">
                    <div class="cbox">
                        <div class="qtitle"><em>*</em> 我是新的单选题目,请编辑</div>
                        <div class="qoption">
                            <label><input type="radio" />新的项目A</label>
                            <label><input type="radio" />新的项目B</label>
                        </div>
                    </div>
                    <span class="edit"></span>
                    <span class="up"></span>
                    <span class="down"></span>
                </div>
                <div class="outerBox">
                    <div class="cbox">
                        <div class="qtitle"><em>*</em> 我是新的下拉选项题目,请编辑</div>
                        <div class="qoption">
                            <select>
                                <option>新的项目A</option>
                                <option>新的项目B</option>
                            </select>
                        </div>
                    </div>
                    <span class="edit"></span>
                    <span class="up"></span>
                    <span class="down"></span>
                </div>
            </div>
            <div class="rightPart">右侧部分</div>
        </div>
    </div>
</template>
<style lang='stylus'>
    .warp{
        width:1300px;min-height:500px; margin:50px auto;overflow:hidden;
        .leftPart,.rightPart{
            float:left; width:350px;min-height:500px;background-color:#ccc;
        }
        .centerPart{
            float:left; width:600px;min-height:500px;padding:20px;
            overflow:hidden;box-sizing:border-box;background: #fff;
            .outerBox{
                width: 500px;position: relative;
                .cbox{
                    width:500px; padding:10px 0px;
                    border-bottom: 1px solid #eee;position: relative;
                    .qtitle{
                        font-size:18px;font-weight:bold;margin-bottom:10px;
                    }
                    label{margin-right: 10px;cursor: pointer;}
                    input[type=checkbox], input[type=radio]{margin-right: 5px;}
                    select{
                        width:300px;height:30px;
                        border: 1px solid #bdbdbd;border-radius:6px;
                    }
                }
                .edit{
                    position:absolute;right:20px;top:16px;
                    width:20px;height:20px;
                    background:url(/images/bianji.svg);
                    background-size:cover; display:none;
                }
                .down{
                    position:absolute;right:50px;top:18px;
                    width:16px;height:16px;
                    background:url(/images/down.svg);
                    background-size:cover;display:none;
                }
                .up{
                    position:absolute;right:80px;top:18px;
                    width:16px;height:16px;
                    background:url(/images/up.svg);
                    background-size:cover;display:none;
                }
                &:hover .edit, &:hover .up, &:hover .down{display:block;}
                &.onedit{animation:donghua .5s linear infinite alternate;}
                @-webkit-keyframes donghua{
                    0%{box-shadow:0px 0px 0px red;}
                    100%{box-shadow:0px 0px 20px red;}
                }
            }
        }
    }
</style>
示例代码

 


 

第二步:拆分组件App.vue

<template>
    <div>
        <div class="warp">
            <div class="leftPart">
                <typeTestArea></typeTestArea>
            </div>
            <div class="centerPart">
                <div class="outerBox">
                    <singleOption></singleOption>
                    <span class="edit"></span>
                    <span class="up"></span>
                    <span class="down"></span>
                </div>
<div class="outerBox">
                    ...
                </div>
            </div>
            <div class="rightPart">
                <setArea></setArea>
            </div>
        </div>
    </div>
</template>
<script>
    import singleOption from "./components/singleOption.vue";
    import multipleOption from "./components/multipleOption.vue";
    import menuOption from "./components/menuOption.vue";
    import setArea from "./components/setArea.vue";
    import typeTestArea from "./components/typeTestArea.vue";

    export default{
        components:{
            singleOption,
            multipleOption,
            menuOption,
            setArea,
            typeTestArea 
        }
    }
</script>
示例代码

把单选、多选、下拉分别拆分到singleOption.vuemultipleOption.vuemenuOption.vue中。

组件中的.cbox类名可以不用写了,后面会在App.vue中添加。


第三步:设置题目默认数据和显示到视图(App.vue

<template>
    <div>
        <div class="warp">
            <div class="leftPart">
                <typeTestArea></typeTestArea>
            </div>
            <div class="centerPart" >
                <div class="outerBox" v-for="(item,index) in q">
                    <!-- <singleOption></singleOption> -->
                    <!-- :is="item.type" 表示要显示的选项类型 -->
                    <div :is="item.type" :item="item" :index="index" class="cbox"></div>
                    <span class="edit" :data-index="index"></span>
                    <span class="up"   :data-index="index"></span>
                    <span class="down" :data-index="index"></span>
                </div>
            </div>
            <div class="rightPart">
                <setArea></setArea>
            </div>
        </div>
    </div>
</template>
<script>
    export default{
        data(){
            return {
                q:[
                    {
                        "title":"你觉得下面哪个学历最牛叉?",
                        "type":"singleOption",
                        "option":[
                            {"v":"家里蹲大学"},
                            {"v":"英国贱桥大学"},
                            {"v":"美国麻绳礼工"},
                            {"v":"蓝翔技工学校"}
                        ],
                        "required":false //是否为必填
                    },
                    {
                        "title":"你喜欢吃的食物? ",
                        "type":"multipleOption",
                        "option":[
                            {"v":"榴莲"},
                            {"v":"香蕉"},
                            {"v":"葡萄"},
                            {"v":"梨子"}
                        ],
                        "required":false
                    },
                    {
                        "title":"治疗失眠最有效的方法是?",
                        "type":"menuOption",
                        "option":[
                            {"v":"吃安眠药"},
                            {"v":"看国产电视剧"},
                            {"v":"催眠术"},
                            {"v":"用大锤打晕"}
                        ],
                        "required":false
                    }
                ]
            }
        }
    }
</script>
示例代码

下面把数据显示在视图

单选组件singleoption.vue

<template>
    <div>
        <div class="qtitle">
            <em v-show="item.required"> * </em> {{index+1}}、{{item.title}}
        </div>
        <div class="qoption">
            <label v-for="option in item.option">
                <input type="radio" :name="'q'+(index+1)" /> {{option.v}}
            </label>
        </div>
    </div>
</template>
<script>
    export default{
        props:["item","index"]
    }
</script>
示例代码

 

多选组件multipleoption.vue

<template>
    <div>
        <div class="qtitle">
            <em v-show="item.required"> * </em> {{index+1}}、{{item.title}}
        </div>
        <div class="qoption">
            <label v-for="option in item.option">
                <input type="checkbox" :name="'q'+(index+1)" /> {{option.v}}
            </label>
        </div>
    </div>
</template>
<script>
    export default{
        props:["item","index"]
    }
</script>
示例代码

 

下拉组件menuoption.vue

<template>
    <div>
        <div class="qtitle">
            <em v-show="item.required">*</em> {{index+1}}、{{item.title}}
        </div>
        <div class="qoption">
            <select>
                <option v-for="option in item.option" :value="option.v">
                    {{option.v}}
                </option>
            </select>
        </div>
    </div>
</template>
<script>
    export default{
        props:["item","index"]
    }
</script>
示例代码

第四步:拖拽

App.vue

<script>
    export default{
        data(){
            return {
                ...
        },
        components:{
            ...
        },
    //组件上树之后的生命周期
        mounted:function(){
            var self = this;
            //draggable(拖拽)和sortable(拖拽排序)结合使用
            //拖拽
            $('.typeTestBox li').draggable({
                connectToSortable:".centerPart", //可拖拽到什么位置
                helper:"clone",   //克隆拖拽
                revert: "invalid",//拖拽停止时,归位的动画
            });
            //拖拽排序
            $('.centerPart').sortable({
                 cancel:".cbox,span", //禁止从匹配的元素上拖拽排序。
                 //当排序停止时触发该事件。
                 stop:function(event,ui){
                    //获取拖拽后的排序编号和data-titletype属性值
                    var index = $(ui.item[0]).index();
                    var titleType = $(ui.item[0]).data("titletype");
                    //拖拽后题目名称消失
                    $(ui.item[0]).remove();
                    //然后从index开始,不删除,添加新项
                    self.q.splice(index,0,{
                        "title":"一个新的题目,请编辑",
                        "type":titleType,
                        "option":[
                            {"v":"新选项A"},
                            ..
                            {"v":"新选项D"}
                        ],
                        "required":false
                    });
                 }
            })
//事件委托,上箭头、下箭头
            //向上排序交互位置
            $(".centerPart").on('click','.up', function(event){
                var index = $(this).data("index"); //获取题目编号
                if(index > 0){//如果大于0即可交换位置
        //尾删头插
        //temp是要添加的新项,即删除的那项(即当前点击的项)
                    var temp = self.q.splice(index,1)[0];
        //从当前的上一题开始,删除0项,从后面添加新项
                    self.q.splice(index-1,0,temp);
                };
            });
            $(".centerPart").on('click','.down', function(event){
                var index = $(this).data("index");
                var temp = self.q.splice(index,1)[0];
                self.q.splice(index+1,0,temp)
            });
        }
    }
</script>
示例代码

第五步:题目编辑

main.js

import Vue from "vue";
import Vuex from "vuex";
import App from "./App.vue";

Vue.use(Vuex);
// 创建一个全局仓库
const store = new Vuex.Store({
    state : {
        nowedit : 1 //当前编辑的题号
    },
    mutations: {
        // 修改全局的nowedit
        changeNowEdit(state,{nowedit}){
            state.nowedit = nowedit
        }
    }
})
示例代码

 

App.vue

<template>
    <div>
        <div class="wrap">
            <div class="leftPart">
                <typeTestArea></typeTestArea>
            </div>
            <div class="centerPart">
                <div class="outerBox" v-for="(item,index) in q">
                    <div :is="item.type" :item="item" :index="index" class="cbox"></div>
                    <span class="edit" :data-index="index"></span>
                    <span class="up" :data-index="index"></span>
                    <span class="down" :data-index="index"></span>
                </div>
            </div>
            <div class="rightPart">
                <setArea v-if="$store.state.nowedit != 0" :item="q[$store.state.nowedit - 1]">
                </setArea>
            </div>
        </div>
    </div>
</template>
示例代码

 

setarea.vue右侧组件布局:

<template>
    <div class="typeTestArea">
        <h3>设置题目</h3>
        <div class="con">
            标题:<input type="text" v-model="item.title" />
        </div>
        <div class="con">
            是否必填:<input type="checkbox" v-model="item.required">
        </div>
        <div class="con">
            题型:
            <input type="radio" value="singleoption"   v-model="item.type" />单选
            <input type="radio" value="multipleoption" v-model="item.type" />多选
            <input type="radio" value="menuoption"     v-model="item.type" />下拉
        </div>
        <div class="con">
            <!-- 题目选项们(更改之后,鼠标离开后双向修改) -->
            <div class="options">
                <p v-for="(option,index) in item.option" :key="option.v">
                    <input type="text" v-model="option.v">
                    <span class="del"></span>
                    <span class="changeOrder"></span>
                </p>
            </div>
            <p class="addoption" >添加新的选项</p>
        </div>
    </div>
</template>
<script>
    export default{
        props:["item"],
    }
</script>
<style scoped lang='stylus'>
    .typeTestArea{
        padding:20px;
        .con{
            line-height:150%;padding:10px 0;
        }
        input[type="text"]{
            width:230px;height:30px;color: #495060;
            border-radius:4px; border: 1px solid #dddee1;padding-left:5px;
        }
        .addoption{
            width:230px;height:35px;background: #2db7f5;border-radius:5px;
        }
        .addoption:hover{background:#18b566;}
        .options input{ margin-bottom:10px; }
        .del,.changeOrder{
            display:inline-block;width: 16px;height:16px;padding:2px;
            background:url(/images/del.svg);background-size:cover;
            position:relative;top:6px;left:5px;border-radius:5px;
        }
        .changeOrder{
            background:url(/images/order.svg);cursor:move;
        }
        .del:hover,.changeOrder:hover{animation:donghua 0.3s linear 0s  alternate;}
        @-webkit-keyframes donghua{
            0%{transform:rotate(0deg) scale(1);}
            50%{transform:rotate(180deg) scale(1.3);}
            100%{transform:rotate(360deg) scale(1);}
        }
    }
</style>
示例代码

 

setarea.vue右侧组件功能实现:

<template>
    <div class="typeTestArea">
        <h3>设置题目</h3>
        <div class="con">
            标题:<input type="text" v-model="item.title" />
        </div>
        <div class="con">
            是否必填:<input type="checkbox" v-model="item.required">
        </div>
        <div class="con">
            题型:
            <input type="radio" value="singleoption"   v-model="item.type" />单选
            <input type="radio" value="multipleoption" v-model="item.type" />多选
            <input type="radio" value="menuoption"     v-model="item.type" />下拉
        </div>
        <div class="con">
            <!-- 题目选项们(更改之后,鼠标离开后双向修改) -->
            <div class="options" ref="option">
                <p v-for="(option,index) in item.option" :key="option.v">
                    <input type="text" v-model.lazy="option.v">
                    <span class="del" @click="delBtn(index)"></span>
                    <span class="changeOrder"></span>
                </p>
            </div>
            <p class="addoption" @click="addoption">添加新的选项</p>
        </div>
    </div>
</template>
<script>
    export default{
        props:["item"],
        methods:{
            addoption(){
                this.item.option.push({"v":""});
            },
            delBtn(index){
                this.item.option.splice(index,1);
            }
        },
        mounted:function(){
            var startIndex = 0; //全局变量
            var self =this;
            $(this.$refs.option).sortable({
                handle:".changeOrder", //限制拖拽的对象
                //拖拽开始
                start:function(e,ui){
                    //获取当前拖拽的编号
                    startIndex = $(ui.item).index();
                    console.log(startIndex)
                },
                //拖拽结束后
                stop:function(e,ui){
                    //拖拽结束后的编号
                    var endIndex = $(ui.item).index();
                    //视图中题目的选项也要跟着变化(前删后插)
                    //从startIndex删除1项
                    var delOption = self.item.option.splice(startIndex,1)[0];
                    //从endIndex的位置添加之前删除的项
                    self.item.option.splice(endIndex,0,delOption);
                }
            })
        }
    }
</script>
示例代码

 

App.vue 编辑题目按钮:

<template>
    <div>
        <div class="warp">
            <div class="leftPart">
                <typetestarea></typetestarea>
            </div>
            <div class="centerPart" >
                <div :class="{'outerBox':true,'onedit':$store.state.nowedit==index+1}" v-for="(item,index) in q">
                    <div :is="item.type" :item="item" :index="index" class="cbox"></div>
                    <span class="edit" :data-index="index"
                          @click="$store.commit('changeNowEdit',{'nowedit':index+1})">
                    </span>
                    <span class="up"   :data-index="index"
                          @click="$store.commit('changeNowEdit',{'nowedit':index+1})">
                    </span>
                    <span class="down" :data-index="index" 
                          @click="$store.commit('changeNowEdit',{'nowedit':index+1})">
                    </span>
                </div>
            </div>
            <div class="rightPart">
                <setarea v-if="$store.state.nowedit != 0" 
:item="q[$store.state.nowedit-1]">
</setarea>
            </div>
        </div>
    </div>
</template>
<script>
    export default{
        data(){
            return {
                ...
            }
        },
        mounted:function(){
            var self = this;
            //拖拽排序
            $('.centerPart').sortable({
                 cancel:".cbox,span", 
                 // 当排序停止时触发该事件。
                 stop:function(event,ui){
                   ...
                    self.q.splice(index,0,{
                        ...
                    });
                    //拖拽添加题目完成后,让新的题目变成当前编辑状态
                    self.$store.commit('changeNowEdit',{'nowedit':index+1});
                 }
            });
        }
    }
</script>
示例代码

v-model的问题:

1)我们的数据放在全局,全局的数据理论上讲只能有commit()来更改!

2vue中视图更新的原理和React完全不同,vue使用数据劫持,只要数据变化,视图一定变化。

 

v-model可以直接和全局的store中的数据进行双向绑定!但是绑定了,就违背了commit()更改数据的原则,管他呢!!

我们可以在computed中写set()get()方法,让getstore中取值,set发出commit命令。

官网:https://vuex.vuejs.org/zh-cn/forms.html

 


 

回复数量: 0
暂无评论~~
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!