後端負責提供接口(3000)php
前端負責業務邏輯(8080)css
接口地址:從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.js、action.js、mutations.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; } }
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
"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請求。
若是把本身的項目放到服務器運行,就須要使用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) })
<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>
<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中添加。
<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命令。