当前位置: 移动技术网 > IT编程>开发语言>JavaScript > 前端笔记之Vue(六)分页排序|酷表单实战&Vue-cli

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

2019年05月21日  | 移动技术网IT编程  | 我要评论

一、分页排序案例

 后端负责提供接口(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

 

 

 

 

 

安装依赖:

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命令。

官网:

 


 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网