调查问卷WebApp
1. 效果演示
2. 主要知识点
- 使用slot分发内容
- 动态组件
- 组件通信
- 实例的生命周期
- 表单
3. 遇到的问题
-
bus 通信 第一次 $on 监听不到
// 解决bus 第一次通信 $on 监听不到 this.$nextTick(function () { if (_this.totalSize - 1 == _this.order) { bus.$emit('submiteDisabled', disabledStatus) } else { bus.$emit('nextStepDisabled', disabledStatus) } })
-
bus 通信 $on 多次触发
// bus传值之后要进行销毁, 尤其是跳转页面进行使用的时候 beforeDestroy() { if (this.totalSize - 1 == this.order) { bus.$off('submiteDisabled') } else { bus.$off('nextStepDisabled') } },
4. 代码整理
-
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>答题卡</title> <link rel="stylesheet" href="./css/style.css"> </head> <body> <div id="app"> <main-component v-for="(question, index) in questions" :key="index" :title="question.title" :order="index" v-if="index === currentOrder" :content="question.chooses"> <template slot="title" slot-scope="props"> <span>{{props.order}}. {{props.title}}</span> </template> <template slot="content" slot-scope="props"> <component :is="props.type" :datas="question.chooses.values" :selected = "question.chooses.selected" :order = "index" :total-size = "questions.length" v-if="props.type != ''"></component> <textarea-component :value="question.chooses.value" :order="index" :total-size = "questions.length" v-else></textarea-component> </template> <template slot="functionalDomain"> <button-component :now-order = "currentOrder" :total-elements = "questions.length" :next-step-disabled = "true" ></button-component> </template> </main-component> </div> <script src="./js/vue.js"></script> <script src="./js/index.js"></script> </body> </html>
-
JS
var bus = new Vue(); Vue.component('main-component', { props: { order: Number, title: String, content: { type: Object } }, template: '<div class="main">\ <div class="title">\ <slot name="title" :title="mytitle" :order="myorder + 1"></slot>\ </div>\ <div class="content">\ <slot name="content" :type="typeSelect"></slot>\ </div>\ <div class="footer">\ <slot name="functionalDomain" ></slot>\ </div>\ </div>', data: function () { return { mytitle: this.title, myorder: this.order } }, computed: { typeSelect: function () { let type_co = this.content.type; if ('radio' == type_co || 'checkbox' == type_co) { return type_co + '-component'; }else{ return ''; } } }, }); Vue.component('radio-component', { props:{ selected: String, datas:Array, order: Number, totalSize: Number }, template: '<div>\ <label v-for="(item, index) in mydatas" :key="index"><input type="radio" :value="item.value" v-model="results" />{{item.name}}</label>\ </div>', data:function (){ return { results: this.selected, mydatas: this.datas } }, methods: { updatNextStepStatus: function (val) { let disabledStatus = true; let _this = this; if (val != '') { disabledStatus = false; } // 解决bus 第一次通信 $on 监听不到 this.$nextTick(function () { if (_this.totalSize - 1 == _this.order) { bus.$emit('submiteDisabled', disabledStatus) } else { bus.$emit('nextStepDisabled', disabledStatus) } }) } }, watch: { results: function (val) { this.updatNextStepStatus(val); bus.$emit('valueChange', this.order, val) }, selected: function (val) { this.results = val } }, mounted() { this.updatNextStepStatus(this.results); }, beforeDestroy() { if (this.totalSize - 1 == this.order) { bus.$off('submiteDisabled') } else { bus.$off('nextStepDisabled') } }, }); Vue.component('checkbox-component', { props: { selected: Array, datas: Array, order: Number, totalSize: Number }, template: '<div>\ <label v-for="(item, index) in mydatas" :key="index"><input type="checkbox" :value="item.value" v-model="results" />{{item.name}}</label>\ </div>', data: function () { return { results: this.selected, mydatas: this.datas } }, methods: { updatNextStepStatus: function (newValue, oldValue) { let disabledStatus = true; let _this = this; if (oldValue.length < 2 && newValue.length < 2) { return; } else if (oldValue.length >= 2 && newValue.length < 2) { disabledStatus = true; } else if (newValue.length >= 3){ alert("多选,最多选择3个选项") }else{ disabledStatus = false; } // 解决bus 第一次通信 $on 监听不到 this.$nextTick(function () { if (_this.totalSize - 1 == _this.order) { bus.$emit('submiteDisabled', disabledStatus) } else { bus.$emit('nextStepDisabled', disabledStatus) } }) } }, watch: { results: function (newValue, oldValue) { this.updatNextStepStatus(newValue, oldValue); bus.$emit('valueChange', this.order, newValue) }, selected: function (val) { this.results = val } }, mounted() { this.updatNextStepStatus(this.results,[]); }, beforeDestroy() { if (this.totalSize - 1 == this.order) { bus.$off('submiteDisabled') } else { bus.$off('nextStepDisabled') } }, }); Vue.component('textarea-component', { props: { value: String, order: Number, totalSize: Number }, template: '<textarea v-model="results" placeholder="不少于100字"></textarea>', data: function () { return { results: this.value, } }, methods: { updatNextStepStatus: function (newValue, oldValue) { let disabledStatus = true; let _this = this; if (newValue.length > 0 && newValue.length <= 5) { disabledStatus = false; }else{ disabledStatus = true; } // 解决bus 第一次通信 $on 监听不到 this.$nextTick(function () { if (_this.totalSize - 1 == _this.order) { bus.$emit('submiteDisabled', disabledStatus) }else{ bus.$emit('nextStepDisabled', disabledStatus) } }) } }, watch: { results: function (newValue, oldValue) { this.updatNextStepStatus(newValue, ''); bus.$emit('valueChange', this.order, newValue) }, value: function (val) { this.results = val } }, mounted() { this.updatNextStepStatus(this.results, ''); }, beforeDestroy() { if (this.totalSize - 1 == this.order) { bus.$off('submiteDisabled') }else{ bus.$off('nextStepDisabled') } }, }); Vue.component('button-component', { props: { basicClasses: { type: Object, default: function () { return { 'button-white': true } } }, nextStepClasses: { type: Object, default: function () { return { 'button-primary': true } } }, nextStepDisabled:{ type: Boolean, default: false }, nowOrder: Number, totalElements: Number }, template: '<div>\ <button class="btn" :disabled="nextStepStatus" :class="primary" \ v-if="order != total - 1" @click="handleNextStep">下一步</button>\ <button class="btn" :disabled="submiteStatus" :class="primary" \ v-if="order == total - 1" @click="handleSubmit">提交</button>\ <button class="btn" :disabled="false" :class="basic"\ v-if= "order != 0" @click="handleBackStep">上一步</button>\ <button class="btn" :disabled="false" @click="handleReset" :class="basic">重置</button>\ </div>', data() { return { basic: this.basicClasses, primary: this.nextStepClasses, nextStepStatus: this.nextStepDisabled, order: this.nowOrder, total: this.totalElements, submiteStatus: true } }, methods: { handleNextStep: function () { bus.$emit('nextStep') }, handleBackStep: function () { bus.$emit('backStep') }, handleSubmit: function () { bus.$emit('submit') }, handleReset: function () { let _this = this; bus.$emit('reset', _this.order) } }, mounted() { let _this = this; bus.$on('nextStepDisabled', function (status) { _this.nextStepStatus = status }); bus.$on('submiteDisabled', function (status) { _this.submiteStatus = status }) }, }); var app = new Vue({ el: "#app", data: { currentOrder: 0, questions: [ { title: '请问您的性别是?', chooses: { type: 'radio', values: [{ 'name': '男', 'value': '1' }, { 'name': '女', 'value': '2' }, { 'name': '保密', 'value': '3' }], selected: '' } }, { title: '请选择您的兴趣爱好?', chooses: { type: 'checkbox', values: [{ 'name': '看书', 'value': 'read book' }, { 'name': '游泳', 'value': 'swim' }, { 'name': '跑步', 'value': 'run' }, { 'name': '看电影', 'value': 'see movie' }], selected: [] } }, { title: '请介绍一下你自己', chooses: { type: 'text', value: '' } }, ] }, methods: { handleNext: function () { let current = this.currentOrder; if (++current >= this.questions.length) { return; }else{ this.currentOrder += 1; } }, handleBack: function () { let currentOrder = this.currentOrder; if (-- currentOrder < 0) { return; }else{ this.currentOrder -= 1; } }, handleReset: function (order, val) { let currentQuestion = this.questions[order].chooses; if (currentQuestion.type == 'radio') { currentQuestion.selected = val === undefined ? '': val; } else if (currentQuestion.type == 'checkbox') { currentQuestion.selected = val === undefined ? [] : val; } else { currentQuestion.value = val === undefined ? '' : val } } }, mounted: function () { let _this = this; bus.$on('nextStep', function (msg) { let current = _this.currentOrder; if (++current >= _this.questions.length) { return; } else { _this.currentOrder += 1; } }); bus.$on('backStep', function (msg) { let currentOrder = _this.currentOrder; if (--currentOrder < 0) { return; } else { _this.currentOrder -= 1; } }); bus.$on('submit', function (msg) { console.log('提交') }); bus.$on('reset', function (order) { _this.handleReset(order) }); bus.$on('valueChange', function (order, val) { _this.handleReset(order, val) }); } })
-
CSS
[v-cloak]{ display: none; } .btn { border: none; outline:none; color: white; padding: 7px 25px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; } .button-white { background-color: rgb(217, 219, 223); color: #000000; } .button-white:active { background-color: rgb(134, 149, 179); color: #000000; } .button-primary { background-color: rgb(39, 126, 228); border: none; color: white; padding: 7px 25px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; margin: 4px 2px; cursor: pointer; } .button-primary:active { background-color: rgb(185, 171, 31); } .button-primary:disabled { background-color: rgb(115, 122, 130); }
5. 升级
- 组件重复内容很多,可合并