欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-10-06 13:50:51
一、分页排序案例 后端负责提供接口(3000) 前端负责业务逻辑(8080) 接口地址:从8080跨域到3000拿数据 http://127.0.0.1:3000/shouji http://127.0.0.1:8080/api/shouji 后端app.js var express = requi ......

一、分页排序案例

 后端负责提供接口(3000

 前端负责业务逻辑(8080

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

 

 

分页排序接口:
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的官网:

 在全局安装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

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

 

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

 

 

安装依赖:

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(六)分页排序|酷表单实战&Vue-cli

 

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

第一种:传统方式

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

 

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

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

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

 

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

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

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

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


2.3项目打包上线

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

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

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

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

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


三、酷表单项目

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>

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

 


 

第二步:拆分组件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命令。

官网: