使用vue.js+element实现简单dialog对话框功能
1.对门票价格进行只读属性,对门票个数进行增减操作(最少减至1,为1时减少按钮失效)2.添加radio单选按钮,实现预订模式的转换,选择否,对应景点预订;选择是,对应酒店+景点预订3.添加酒店、酒店房间类型、入住时间选择、入住天数四个select选择下拉框,关键在于对其value和label的数据获取和传递4.关联酒店和房间类型的数据、根据具体公式计算最终预订价格(并且在所有表单事件获取有效数据才
毕设篇:点击预订按钮实现简单酒店+景点预订购买功能
效果展示
技术实现
HTML、Vue.js、Element.js 感兴趣的小伙伴们可以下载测试一波
案例实现源码 提取码:love
功能需求
点击预订按钮,弹出一个dialog对话框,并将景点详情模块的数据传递到对话框中,具体包括景点名称,景区门票价格然后在dialog对话框中镶嵌一个form表单。
form表单功能模块简述:
1.对门票价格进行只读属性,对门票个数进行增减操作(最少减至1,为1时减少按钮失效)
2.添加radio单选按钮,实现预订模式的转换,选择否,对应景点预订;选择是,对应酒店+景点预订
3.添加酒店、酒店房间类型、入住时间选择、入住天数四个select选择下拉框,关键在于对其value和label的数据获取和传递
4.关联酒店和房间类型的数据、根据具体公式计算最终预订价格(并且在所有表单事件获取有效数据才显示最终价格),利用element自带的模板代码进行ref控制
5.按钮的确认、取消、重置以及气泡提示框的组件使用,增强交互效果
代码实现
boxforshop.html
<div id="app">
<!-- 遍历vue里面data里面infos数组里面的对象,这里不需要将 el-dialog也遍历,因为:model="form"中的form不是一个函数(仅代表个人理解)-->
<div v-for="(info,index) in infos" :key="info.name+index">
<!-- 景点模块 -->
<div class="content">
<figure>
<!-- 遍历图片 -->
<img :src="info.img" alt="" :key="info.img">
</figure>
<div class="cr">
<!-- 遍历景点名称,简述内容和门票价格 -->
<h3 :key="info.name">{{info.name}}</h3>
<span class="stn1" :key="info.cont">{{info.cont}}</span>
<span class="stn2" :key="info.price">¥{{info.price}}</span>
<!-- 利用element ui 制作 弹出对话框 给予点击事件传递循环中每个按钮对应的data数据,这一步很important-->
<el-button @click="changeValue(info.name,info.price,info.count)" class="btn">预订
</el-button>
</div>
</div>
</div>
<!-- 基于element对话框dialog修改 :before-close="handleClose" 表示 右上角 X号的事件函数,很奇怪在这里不是@click-->
<el-dialog :title="fname" :visible.sync="dialogFormVisible" :before-close="handleClose">
<!-- :inline="true" 表示 将每个<el-form-item>以行内-块级元素并存的形式展示,等价于 display: "inline-block" 这里的作用是在足够宽度下不独占一行-->
<el-form :model="form" label-width="120px" :rules="rules" ref="form" :inline="true"
class="demo-form-inline">
<el-form-item label="门票价格" :label-width="formLabelWidth">
<!-- readonly="readonly" 使input的value作为只读属性,不能删除修改,不用disaled的原因是v-model="info.price"时,这个无法传递参数,会报错 -->
<el-input type="text" v-model="fprice" readonly="readonly" class="e-input" style="width: 201.5px;">
</el-input>
</el-form-item>
<el-form-item label="选择门票" :label-width="formLabelWidth">
<el-input type="text" v-model="fcount" placeholder="请选择购买门票个数" class="e-input"
style="width: 201.5px;"></el-input>
<!-- 设置监听事件,默认门票数至少为1,最多为5(这个max没有限制,但是门票数关联功能暂没实现,为保障逻辑,就限制了怎么了) -->
<el-button @click="dec()" :disabled="fcount <= 1">-</el-button>
<!-- {{info.count}} -->
<!-- 使用el-tooltip 实现文字提示 -->
<el-tooltip class="item" effect="dark" content="最多购买5张" placement="top-start">
<el-button @click='add()' :disabled="fcount >= 5">+</el-button>
</el-tooltip>
</el-form-item>
<!-- 设置radio作为单选判断,让用户选择 单独景点 和 景点+酒店 两种模式 -->
<el-form-item label="酒店+景点" :label-width="formLabelWidth" prop="resource" style="display: block;">
<el-radio-group v-model="form.resource">
<el-radio label="是" value="1"></el-radio>
<el-radio label="否" value="0"></el-radio>
</el-radio-group>
</el-form-item>
<!-- 使用vi-if做判断,form.resource === '是',显示酒店+景点模式,否则直接计算 -->
<div v-if="form.resource === '是' ">
<el-form-item label="选择酒店" :label-width="formLabelWidth" prop="name">
<el-select v-model="form.name" placeholder="请选择酒店" @change="hotelValue">
<el-option v-for="(item,index) in hotelList" :label="item.label" :value="item.value"
:key="item.value">
</el-option>
</el-select>
<!-- <span v-if="form.name != '' "> ¥{{item.value}}</span>
<span v-else></span> -->
</el-form-item>
<!-- 原理同上 为保障逻辑,限制房间数量-->
<el-form-item label="房间类型" :label-width="formLabelWidth" prop="namec">
<el-tooltip class="item" effect="dark" content="仅限购买一间" placement="top-start">
<el-select v-model="form.namec" placeholder="请选择酒店房间类型" @change="hotelcValue"
style="margin-left: 0;">
<el-option v-for="(itemc,index) in hotelcList" :label="itemc.label" :value="index+1"
:key="'hc-'+index">
</el-option>
</el-select>
</el-tooltip>
<!-- 显示选择不同酒店对应的不同房间价格,关于酒店选择和酒店房间的关联做的比较简单,有兴趣者可以自行丰富 -->
<span v-if="form.namec != '' && form.name != ''"> ¥{{itemc.value}}</span>
<span v-else></span>
</el-form-item>
<el-form-item label="居住时间" :label-width="formLabelWidth" prop="date1">
<el-col :span="11">
<el-date-picker type="date" placeholder="选择日期" v-model="form.date1" style="width: 204%;">
</el-date-picker>
</el-col>
<!-- <el-col class="line" :span="2">-</el-col> -->
<!-- <el-col :span="11">
<el-time-picker placeholder="选择时间" v-model="form.date2" style="width: 74%;"></el-time-picker>
</el-col> -->
</el-form-item>
<el-form-item label="居住天数" :label-width="formLabelWidth" prop="region" style="margin-left: -15px;">
<el-select v-model="form.region" placeholder="请选择居住天数" @change="dayValue">
<el-option v-for="(item1,index) in dayList" :label="item1.label" :value="item1.value"
:key="'d-'+index">
</el-option>
</el-select>
<!-- <span> x {{form.region}}</span> -->
</el-form-item>
</div>
<div v-else>
<!-- <span>{{form.resource}}</span> -->
</div>
<!-- 装饰功能 -->
<el-form-item label="电子发票" prop="delivery">
<el-switch v-model="form.delivery"></el-switch>
</el-form-item>
<!-- 设置计算结果的显示与隐藏 对于数值计算,优先使用计算属性功能-->
<el-form-item label="总计支付" :label-width="formLabelWidth" style="display: block;">
<!-- <el-input type="text" v-model="form.desc"></el-input> -->
<span v-if="form.resource == '否'|| (form.resource == '是' && form.region != '' && form.name != '' && form.namec != '') ">¥
<strong style="text-decoration: none;color: red;font-size: 14px;">{{totalPrice}}</strong></span>
<span v-else></span>
<!-- <span v-if="totalPrice != 0">¥ {{totalPrice}}</span>
<span v-else></span> -->
<!-- <el-button @click="getPrice()" style="margin-left: 200px;">结算</el-button> -->
</el-form-item>
</el-form>
<!-- 本质上dialog由header、center、footer三部分组成,footer层负责取消,确定(数据传递) ,表单重置功能-->
<div slot="footer" class="dialog-footer">
<el-button @click="closeFrom('form')">取 消</el-button>
<el-button type="primary" @click="submitForm('form')">确 定</el-button>
<el-button @click="resetForm('form')">重置</el-button>
</div>
</el-dialog>
</div>
<!-- 将vue.js代码存放到js文件,实现js和html分离,提升代码可读性 -->
<script src="js/boxforshop.js"></script>
css样式
<style>
.content {
width: 1000px;
height: 200px;
/* text-align: center; */
/* background: orange; */
}
figure {
width: 240px;
height: 180px;
padding-top: 10px;
float: left;
}
img {
width: 240px;
height: 180px;
}
.cr {
width: 600px;
height: 200px;
float: left;
position: relative;
}
h3 {
margin: 30px;
}
.stn1 {
padding-left: 30px;
}
.btn {
position: absolute;
right: 10px;
bottom: 10px;
/* border: 1px solid skyblue; */
}
.stn2 {
display: block;
width: 40px;
height: 20px;
margin-top: 60px;
margin-left: 450px;
}
/* element样式 可在检查元素中进行测试*/
.e-input {
width: 34%;
}
/* 控制弹出框右侧的滚动条不显示,差不多就这个意思,原始为22px */
.el-form-item {
margin-bottom: 15px;
}
.el-dialog__footer {
padding: 0px 0 15px;
text-align: center;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.el-dialog__header {
padding: 20px 320px 10px;
}
</style>
js导入,这里我使用的是公共js
common.js
//调用公共css
document.write('<link rel="stylesheet" href="lib/theme-chalk/index.css">');
document.write('<link rel="stylesheet" href="css/common.css">');
//调用公共js
document.write('<script type="text/javascript" src="js/vue.js"></script>');
document.write('<script type="text/javascript" src="lib/index.js"></script>');
boxforshop.js
/*
* @Author: lzm
* @Date: 2021-11-24 17:21:59
* @Notes: 使用built命令快速得到一些常用的snippets,右击py文件可以preview代码
* @LastEditTime: 2021-11-24 23:21:46
*/
//保存当前的this
// var _this = this;
new Vue({
//
el: '#app',
data() {
return {
//数据源
infos: [
{
img: 'img/img1.png',
name: '张家界天门山',
cont: '这就是我,不一样的烟火',
price: 299,
count: 1
},
{
img: 'img/img2.png',
name: '张家界武陵源',
cont: '不到武陵源,枉到张家界',
price: 279,
count: 1
}
],
//
form: {
name: '',
namec: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: ''
},
//控制dialog显示与隐藏
dialogFormVisible: false,
//label宽度
formLabelWidth: '120px',
//酒店列表
hotelList: [
{ label: '张家界华天大酒店', value: 129 },
{ label: '张家界韦斯特大酒店', value: 159 },
],
//酒店房间类型列表
hotelcList: [
{ label: '普通单人房', value: 1 },
{ label: '普通双人房', value: 2 },
{ label: '豪华单间', value: 3 },
{ label: '豪华双人房', value: 4 },
{ label: '经济大床房(3-5)', value: 5 },
{ label: '豪华大套房(3-5)', value: 6 },
],
//居住天数
dayList: [
{ label: '1', value: 1 },
{ label: '2', value: 2 },
{ label: '3', value: 3 },
{ label: '5', value: 5 },
{ label: '7', value: 7 },
],
//初始化变量
value: '',
//循环接收对应变量 对应酒店价格、房间价格、居住天数
fname: "",
fprice: "",
fcount: "",
//关联酒店与房间的中间变量
iname: 0,
//展示酒店名称的变量
janem: '',
// 列表value提取
item: {},
item1: {},
itemc: {},
//表单规则
rules: {
name: [
{ required: true, message: '请选择酒店', trigger: 'blur' },
// { min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }
],
namec: [
{ required: true, message: '请选择酒店类型', trigger: 'blur' },
],
region: [
{ required: true, message: '请选择居住天数', trigger: 'change' }
],
date1: [
{ type: 'date', required: true, message: '请选择日期', trigger: 'change' }
],
resource: [
{ required: true, message: '请选择购买形式', trigger: 'change' }
],
// date2: [
// { type: 'date', required: true, message: '请选择时间', trigger: 'change' }
// ],
// type: [
// { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' }
// ],
// desc: [
// { required: true, message: '请填写活动形式', trigger: 'blur' }
// ]
}
}
},
methods: {
//未调用
onSubmit() {
//ajax发送成功的是响应的时候也需要要关闭弹出层
this.dialogFormVisible = false
},
//监听酒店的select选择事件
hotelValue(value) {
this.item = this.hotelList.find((item) => {
console.log(this.form.name);
console.log(this.itemc.value);
console.log(this.iname);
if (this.iname != 0) {
this.hotelcValue(this.iname)
}
//这里必须全等于,数据才会动态绑定在一起,原理还未百度
return item.value === value
});
// console.log(this.item);
this.jame = this.item.label;
//由于没有双向绑定jame,并且由于函数作用域问题导致无法在全局使用,暂不影响
console.log(this.jame);
},
//监听酒店房间的select选择事件
hotelcValue(value) {
console.log(this.form.name);
console.log(this.item.value);
this.itemc = this.hotelcList.find((itemc) => {
// console.log(value);
itemc.value = value
this.iname = itemc.value
if (itemc.value === 1) {
// console.log(itemc.value);
return itemc.value = this.form.name - 20;
}
if (itemc.value === 2) {
return itemc.value = this.form.name + 30;
}
if (itemc.value === 3) {
return itemc.value = this.form.name;
}
if (itemc.value === 4) {
return itemc.value = this.form.name + 60;
}
if (itemc.value === 5) {
return itemc.value = this.form.name + 100;
}
if (itemc.value === 6) {
return itemc.value = this.form.name + 160;
}
return itemc.value = value
})
},
//监听居住天数的select选择事件
dayValue(value) {
this.item1 = this.dayList.find((item1) => {
return item1.value === value
})
},
//使用methods方法实现点击事件获取价格计算
getPrice() {
console.log(this.form.name);
console.log(this.form.region);
this.totalPrice = this.itemc.value * this.form.region + this.info.price * this.info.count
if (this.totalPrice > 1000) {
return this.totalPrice = this.totalPrice - 100
}
if (this.totalPrice > 2000) {
return this.totalPrice = this.totalPrice - 200
}
if (this.totalPrice > 3000) {
return this.totalPrice = this.totalPrice - 500
}
return this.totalPrice
},
//门票数增减button
dec() {
// console.log('dec',index);
this.fcount--
},
add() {
// console.log('add',index);
this.fcount++
},
//确定按钮点击事件
submitForm(formName) {
console.log(formName);
this.$refs[formName].validate((valid) => {
if (valid) {
// console.log(this.item.label);
if (this.form.resource == "否") {
alert('购买成功!, 你选择的套餐是:' + this.fname + '景点');
}
else {
alert('购买成功!, 你选择的套餐是:' + this.fname + '景点和' + this.item.label + '酒店');
}
// return this.dialogFormVisible = false
this.dialogFormVisible = false;
// 点击取消 数据重置
this.$refs[formName].resetFields();
} else {
console.log('error submit!!');
return false;
}
});
},
//重置按钮
resetForm(formName) {
this.$refs[formName].resetFields();
},
//接收循环对应数据,值传递, 这一步很重要
changeValue(name, price, count) {
this.dialogFormVisible = true
this.fname = name
this.fprice = price
this.fcount = count
},
//X号点击事件
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done();
})
.catch(_ => { });
},
// 对话框取消事件
closeFrom(formName) {
this.dialogFormVisible = false;
// 点击取消 数据重置
this.$refs[formName].resetFields();
}
},
//计算属性
computed: {
//计算价格
totalPrice() {
let result = 0;
// console.log(this.form.name);
// console.log(this.form.region);
// result = this.info.price * this.info.count
if (this.form.resource === "是") {
result = this.itemc.value * this.form.region + this.fprice * this.fcount
}
else {
result = this.fprice * this.fcount
}
if (result > 1200) {
return result = result - 100
}
if (result > 2000) {
return result = result - 300
}
if (result > 3000) {
return result = result - 500
}
else {
return result
}
// return result
}
}
});
//log代码暂不删除,方便后期查看测试,学习过程很乏味,但是一旦有了自己想要的成果,这一切就变得有意义了
问题总结
-
1.首先界面UI比较容易实现,找到对应的class进行样式设置,就是需要反复调试费时间
-
2.element中的dialog对话框组件不需要跟随页面数据循环,这样不仅会造成代码冗余,还会被提示form不是一个函数的错误警告,原因是form具有唯一性(我猜的)
-
3.对于vue相关指令的了解与熟练使用,可以说在这个案例中显得淋漓尽致,包含v-model、v-for、v-if、v-else、v-bind、v-on都有涉及
-
4.对于报错,需要常常用到console.log()进行测试
-
5.难点在于选择酒店和酒店房间类型之间的关联和数据绑定与传递,我理解的酒店房间的选择才是最终的实际价格,以及select选择的非顺序输入对交互和数据显示的影响,好在都已调试解决
案例不足
没有对票数和人数与酒店元素相关联,功能并不完善;
酒店和酒店房间的价格关联并不合理,缺乏逻辑性,但是应付需求还是可行的
问题讨论
//监听居住天数的select选择事件
dayValue(value) {
this.item1 = this.dayList.find((item1) => {
return item1.value === value
})
},
这里的find()方法有什么作用?以及 return item1.value === value 中的全等于 与 赋值 = 有什么区别(在我的案例中,这里写赋值=,会获取不到改变的select的value值)
参考文献
在实现这些功能的过程中,会因为实现了某个需求而喜悦,也会因为某个代码报错而感到烦恼,但是好在我的问题都可以通过在CSDN、简书、百度搜索而得到解决,所以感谢那些帮我完成功能模块的优质文章创作者们!所以附上我认为在此案例中有价值的文章链接。
更多推荐
所有评论(0)