前端筆記之Vue(六)分頁排序|酷表單實戰&Vue-cli

1、分頁排序案例

 後端負責提供接口(3000php

 前端負責業務邏輯(8080css

 接口地址:從8080跨域到3000拿數據html

 http://127.0.0.1:3000/shouji前端

 http://127.0.0.1:8080/api/shoujivue

分頁排序接口:
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.jsnode

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.jswebpack

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中引入三要素:git

state.jsaction.jsmutations.js三個文件:github

export default {
    ...
}

 store/index.jsweb

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;
    }
}
示例代碼

2、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服務器均可以。

 


3、酷表單項目

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

相關文章
相關標籤/搜索