毕设篇:点击预订按钮实现简单酒店+景点预订购买功能

效果展示

景点详情模块
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

技术实现

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 != '' ">&nbsp;¥{{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 != ''">&nbsp;¥{{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>&nbsp;x&nbsp;{{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 != '') ">&nbsp;
                        <strong style="text-decoration: none;color: red;font-size: 14px;">{{totalPrice}}</strong></span>
                    <span v-else></span>
                    <!-- <span v-if="totalPrice != 0">¥&nbsp;{{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、简书、百度搜索而得到解决,所以感谢那些帮我完成功能模块的优质文章创作者们!所以附上我认为在此案例中有价值的文章链接。

ElementUI中获取select 的label值

vue+elementui 对话框取消 表单验证重置示例

elementUi Dialog 对话框使用中数据获取问题

Dialog 对话框

计算属性

Logo

前往低代码交流专区

更多推荐