1. 程式人生 > 實用技巧 >【轉】 前端筆記之Vue(六)分頁排序|酷表單實戰&Vue-cli

【轉】 前端筆記之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