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


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

    一、分页排序案例

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


  • 相关阅读:
    (转)if __name__ == '__main__' 如何正确理解?
    (转)Django配置mysql数据库
    (转)Python虚拟环境pyenv、venv(pyvenv)、virtualenv之间的区别,终于搞清楚了!
    找出Framework 4.0 新增的方法和新增的类(下)
    C# MBG 扩展方法类库 分享
    是技术还是态度,网易的视频Title
    不要返回null之EmptyFactory
    你知道这段代码的输出吗?
    C# 4.0 大数的运算,BigInteger
    CleanCode: 面向过程 PK 面向对象
  • 原文地址:https://www.cnblogs.com/Javastudy-note/p/13813690.html
Copyright © 2020-2023  润新知