前端笔记之Vue(六)分页排序|酷表单实战&Vue-cli
一、分页排序案例
后端负责提供接口(3000)
前端负责业务逻辑(8080)
接口地址:从8080跨域到3000拿数据
分页排序接口: 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的官网:
在全局安装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命令。
官网: