【轉】 前端筆記之Vue(六)分頁排序|酷表單實戰&Vue-cli
【轉】 前端筆記之Vue(六)分頁排序|酷表單實戰&Vue-cli
一、分頁排序案例
後端負責提供介面(3000)
前端負責業務邏輯(8080)
介面地址:從8080跨域到3000拿資料
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.js、action.js、mutations.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-cli是Vue的快速起步工具(腳手架工具),再也不用手動配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專案預設打包後只有一個html和js檔案(適合小專案)
vue init webpack專案預設打包完之後,會有很標準的目錄(適合中大型專案)
兩種方式初始化Vue-cli專案的目錄差別很大,你會發現vue init webpack的方式初始化專案,預設提供了很多webpack的配置,也更加方便你對代理(跨域)、最終打包資源放到伺服器什麼目錄、以及js、css、img和專案在打包過程等優化的配置等。
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">
第二種:使用vue的v-bind指令來使用data資料中的圖片:
<img :src="imgSrc"> <script> export default { data () { return { imgSrc : "../assets/logo.png" } } } </script>
webpack將png的圖片變成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伺服器、node、phpnow伺服器都可以。
三、酷表單專案
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.vue、multipleOption.vue、menuOption.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()來更改!
2)vue中檢視更新的原理和React完全不同,vue使用資料劫持,只要資料變化,檢視一定變化。
v-model可以直接和全域性的store中的資料進行雙向繫結!但是綁定了,就違背了commit()更改資料的原則,管他呢!!
我們可以在computed中寫set()和get()方法,讓get從store中取值,set發出commit命令。
官網:https://vuex.vuejs.org/zh-cn/forms.html