uniapp、小程序、预约时段选择插件-会议预约

效果预览

功能:实现小程序或者h5的一个时间预约功能,支持不同日期的在不同的开放时段进行选择,支持禁用及共享。还支持最大预约时段的约束

1、支持参数

/**
 * @description 预约时间选择组件
 * @props {boolean} isShare - 是否可共享,默认值为 false
 * @props {Array} checkTime - 已选择时间,默认值为 []
 * @props {Array} openTime - 开放时段,默认值为 []
 * @props {string} meetingDate - 会议日期,默认值为 ""
 * @props {number} start - 绘制的开始时间,默认值为 8,需大于 0 且为整数
 * @props {number} end - 绘制的结束时间,默认值为 23,需小于 24 且为整数
 * @props {number} maxTimeLength - 最大预约时长
 */

2、具体代码-代码中注释

<template>
  <view>
    <view class="timeSelect-box">
      <view
        class="item"
        v-bind:class="{active:item.active,disabled:item.disabled,select:item.isSelect,close:item.isClose,'border-top-red':timeList.start==item.start,'border-bottom-red':timeList.end==item.end}"
        v-for="(item, index) in list"
        :key="index"
        v-on:click="clickItem(item,index)"
      >
        <view
          class="time-text"
          :class="{'active bg-color':timeList.start==item.start}"
          v-if="timeList.start==item.start"
        >{{item.start}}</view>
        <view
          class="time-text last"
          :class="{'active bg-color':timeList.end==item.end}"
          v-if="timeList.end==item.end"
        >{{item.end}}</view>

        <view class="time-text" v-if="index%2==0">{{item.start}}</view>
        <view class="time-text last" v-else-if="index==list.length-1">{{item.end}}</view>

        <view v-if="item.title" class="title-box d-center" :style="{height:item.height+'px'}">
          <view class="text" @click.stop="clickViewDetail(item)">{{item.title}}</view>
        </view>
        <view
          v-if="item.closeCount"
          class="close-box d-center color-white"
          :style="{height:item.closeCount*itemHeight+'px'}"
        >该时段暂未开放</view>
      </view>
    </view>
  </view>
</template>

<script>
/**
 * @description 时间选择组件
 * @props {boolean} isShare - 是否可共享,默认值为 false
 * @props {Array} checkTime - 已选择时间,默认值为 []
 * @props {Array} openTime - 开放时段,默认值为 []
 * @props {string} meetingDate - 会议日期,默认值为 ""
 * @props {number} start - 绘制的开始时间,默认值为 8,需大于 0 且为整数
 * @props {number} end - 绘制的结束时间,默认值为 23,需小于 24 且为整数
 * @props {number} maxTimeLength - 最大预约时长
 */
export default {
  props: {
    // 是否可共享
    isShare: {
      type: Boolean,
      default: false
    },
    // 已选择时间
    checkTime: {
      type: Array,
      default: () => []
    },
    // 开放时段
    openTime: {
      type: Array,
      default: () => []
    },
    // 会议日期
    meetingDate: {
      type: String,
      default: ""
    },
    // 绘制的开始时间
    start: {
      type: Number,
      default: 8,
      validator: function(value) {
        // 验证开始时间需大于 0 且为整数
        return value > 0 && Number.isInteger(value);
      }
    },
    // 绘制的结束时间
    end: {
      type: Number,
      default: 23,
      validator: function(value) {
        // 验证结束时间需小于 24 且为整数
        return value < 24 && Number.isInteger(value);
      }
    },
    // 最大预约时长
    maxTimeLength: Number
  },
  data() {
    return {
      list: [], // 时间列表
      clickNumber: 0, // 点击次数
      disabledList: [], // 禁用的时间列表
      indexArr: [], // 选中的索引数组
      itemHeight: 0 // 每个时间项的高度
    };
  },
  created() {
    // 组件创建时初始化时间列表
    // this.initTime();
  },
  mounted() {
    // 组件挂载时可执行的操作,当前为空
  },
  computed: {
    timeList() {
      let list = [];
      // 筛选出激活的时间项
      this.list.forEach(item => {
        if (item.active) {
          list.push(item);
        }
      });
      let obj = {
        start: "",
        end: ""
      };
      if (list.length) {
        // 获取选中时间段的开始和结束时间
        obj.start = list[0].time.split("-")[0];
        obj.end = list[list.length - 1].time.split("-")[1];
      }
      return obj;
    }
  },
  methods: {
    /**
     * @description 根据当前日期和已选则时间列表处理选择禁用
     */
    setDisabled() {
      if (this.isShare) {
        // 合并激活的时间项
        this.mergeActive();
      }
      this.checkTime.forEach(item => {
        if (this.isShare) {
          // 添加可共享情况下的禁用时间列表
          this.disabledList.push(
            this.getTimeArr(item.start, item.lastTime || item.end)
          );
        } else {
          // 添加不可共享情况下的禁用时间列表
          this.disabledList.push(this.getTimeArr(item.start, item.end));
        }
      });
      let currentTime = new Date();

      // 未开放时段的开始、结束下标
      var closeStartIndex = -1;
      var closeEndIndex = -1;

      this.list.forEach((item, index2) => {
        this.disabledList.forEach((vitem, index) => {
          // 查找当前时间项是否在禁用列表中
          const findIndex = vitem.findIndex(d => d == item.time);
          const checkItem = this.checkTime[index];

          if (findIndex == 0 && !this.isShare) {
            // 设置不可共享情况下的标题和信息
            item.title = checkItem.title;
            item.info = [checkItem];
            item.height = this.itemHeight * vitem.length + 1;
          }
          if (findIndex == 0 && this.isShare && !checkItem.isRecord) {
            // 设置可共享情况下的标题和信息
            item.title = `${checkItem.count || 1}个活动`;
            item.info = checkItem.info || [checkItem];
            item.height = this.itemHeight * vitem.length + 1;
          }
          if (findIndex > -1) {
            if (this.isShare) {
              // 可共享情况下设置为已选择
              item.isSelect = true;
            } else {
              // 不可共享情况下设置为禁用
              item.disabled = true;
            }
          }
        });
		 //  24小时内禁止预约(可自行开启关闭)
		// const isBeforeTime = this.getTime(item.start) < currentTime;
		// if (isBeforeTime) {
		// 	          // 当前时间之前的时段设置为关闭
		// 	          item.isClose = true;
		// 	          closeStartIndex = closeStartIndex > -1 ? closeStartIndex : index2;
		// 	          var closeStartItem = this.list[closeStartIndex];
		// 	          closeStartItem.closeCount = closeStartItem.closeCount
		// 	            ? closeStartItem.closeCount
		// 	            : 0;
		// 	          closeStartItem.closeCount++;
		// 	       }

		if (this.openTime?.length == 2) {
								if (this.getTime(item.start) < this.getTime(this.openTime[0])) {
									// 开放时段之前的时段设置为关闭
									item.isClose = true;
									closeStartIndex = closeStartIndex > -1 ? closeStartIndex : index2;
									var closeStartItem = this.list[closeStartIndex];
									closeStartItem.closeCount = closeStartItem.closeCount ?
										closeStartItem.closeCount :
										0;
									closeStartItem.closeCount++;
								}

								this.$set(this.list, index2, item)
							}); 
        this.$set(this.list,index2,item)
      });
    },
    /**
     * @description 合并激活的时间项
     */
    mergeActive() {
      this.checkTime.map((item,index) => {
					if (item.isRecord) return;
					for (var i = 1; i < this.checkTime.length; i++) {
						var nextTime = this.checkTime[i];
						if(index==i)continue;
						if (item.lastTime) {
							if (
								this.getTime(nextTime.start) >= this.getTime(item.start) &&
								this.getTime(nextTime.start) <= this.getTime(item.lastTime)
							) {
								item.lastTime = nextTime.end;
								nextTime.isRecord = true;
								item.count++;
								item.info.push(JSON.parse(JSON.stringify(nextTime)))
							}
						} else {
							
							if (
								this.getTime(nextTime.start) >= this.getTime(item.start) &&
								this.getTime(nextTime.start) <= this.getTime(item.end)
							) {
								
								item.lastTime = nextTime.end;
								nextTime.isRecord = true;
								item.count = item.count ? item.count : 1;
								item.count++;
								item.info = item.info ? item.info : [JSON.parse(JSON.stringify(item))];
								item.info.push(nextTime)
							}
						}
						console.log(nextTime);
					}
				});
    },
    /**
     * @description 根据时间字符串获取日期对象
     * @param {string} time - 时间字符串,格式为 "HH:mm"
     * @returns {Date} 日期对象
     */
    getTime(time) {
      return new Date(`${this.meetingDate} ${time}:00`);
    },
    /**
     * @description 根据开始结束时间获取数组中的项
     * @param {string} start - 开始时间,格式为 "HH:mm"
     * @param {string} end - 结束时间,格式为 "HH:mm"
     * @returns {Array} 时间数组
     */
    getTimeArr(start, end) {
      const list = [];
      for (let home = this.start; home < this.end; home++) {
        const h = `0${home}`.slice(-2);
        // 生成半小时的时间间隔
        list.push(`${h}:00-${h}:30`, `${h}:30-${`0${home + 1}`.slice(-2)}:00`);
      }
      return list.slice(
        list.findIndex(v => v.startsWith(start)),
        list.findIndex(v => v.endsWith(end)) + 1
      );
    },
    /**
     * @description 构建时间数组
     */
    initTime() {
      this.disabledList = [];
      const list = [];
      for (let home = this.start; home < this.end; home++) {
        const h = `0${home}`.slice(-2);
        // 生成半小时的时间项
        list.push(
          {
            start: `${h}:00`,
            end: `${h}:30`,
            time: `${h}:00-${h}:30`,
            disabled: false,
            active: false
          },
          {
            start: `${h}:30`,
            end: `${`0${home + 1}`.slice(-2)}:00`,
            time: `${h}:30-${`0${home + 1}`.slice(-2)}:00`,
            disabled: false,
            active: false
          }
        );
      }
      this.list = list;
      this.$nextTick(() => {
        uni
          .createSelectorQuery()
          .in(this)
          .select(".item")
          .boundingClientRect(data => {
            // 获取每个时间项的高度
            this.itemHeight = data.height;
            // 处理选择禁用
            this.setDisabled();
          })
          .exec();
      });
    },
    /**
     * @description 点击时间项的处理函数
     * @param {Object} item - 当前点击的时间项
     * @param {number} index - 当前点击的时间项的索引
     */
    clickItem(item, index) {
      if (item.disabled) {
        // 禁用的时间项不处理点击事件
        return;
      }
      this.clickNumber++;
      if (this.clickNumber == 1) {
        // 第一次点击,记录索引
        this.indexArr.push(index);
      }
      if (this.clickNumber == 2) {
        // 第二次点击,记录索引
        this.indexArr.push(index);
        if (
          this.maxTimeLength &&
          (Math.abs(index - this.indexArr[0]) + 1) / 2 > this.maxTimeLength
        ) {
          // 检查是否超过最大预约时长
          this.indexArr = this.indexArr.sort((a, b) => a - b);
          var isFlag = this.getCheckList(item, true);
          if (isFlag) {
            // 显示提示信息
            uni.showToast({
              title: `预约时长不可超过${this.maxTimeLength}小时`,
              icon: "none",
              duration: 1500
            });
          }
          return;
        }
      }
      if (this.clickNumber == 3) {
        // 第三次点击,重置索引
        this.indexArr = [index];
      }
      this.indexArr = this.indexArr.sort((a, b) => a - b);
      if (this.clickNumber % 3 == 0) {
        // 每三次点击重置激活状态
        this.clickNumber = 1;
        this.list.forEach(v => (v.active = false));
        item.active = true;
      }
      // 获取选中的时间列表
      this.getCheckList(item);
    },
    /**
     * @description 获取选中的时间列表
     * @param {Object} vitem - 当前时间项
     * @param {boolean} isError - 是否为错误检查
     * @returns {boolean} 是否成功获取选中的时间列表
     */
    getCheckList(vitem, isError) {
      var isFlag = true;
      try {
        this.list.forEach((item, idx) => {
          const [start, end] = this.indexArr;
          if (idx >= start && idx <= end) {
            if (item.disabled) {
              // 遇到禁用的时间项,抛出错误
              throw new Error("end");
            }
            if (isError) return;
            // 设置选中的时间项为激活状态
            item.active = true;
          }
        });
      } catch (err) {
        // 处理错误,重置激活状态
        this.list.forEach(v => (v.active = false));
        vitem.active = true;
        isFlag = false;
      }
      if (!isError) {
        // 设置当前时间项为激活状态
        vitem.active = true;
      }
      // 触发自定义事件,传递选中的时间列表
      this.$emit("getTime", this.timeList);
      return isFlag;
    },
    /**
     * @description 点击标题的处理函数
     * @param {Array} item - 标题对应的信息列表
     */
    clickViewDetail(item) {
      // 触发自定义事件,传递标题对应的信息列表
      this.$emit("clickTitle", item.info);
    }
  }
};
</script>

<style lang="scss">

// 通用样式
.timeSelect-box{
  padding: 20rpx 0 20rpx 80rpx;
}


.item {
  min-height: 48rpx;
  line-height: 48rpx;
  // background: rgba(65, 163, 254, .2);
  background: white;
  border-bottom: 1px solid #d2d2d2;
  position: relative;

  .time-text {
    width: 80rpx;
    position: absolute;
    left: -80rpx;
    color: black;
    font-size: 24rpx;
    transform: translateY(-48%);

    &.last {
      transform: translateY(48%);
    }

    &.active {
      color: red;
      z-index: 2;
    }
  }
}

.item.select {
  background: rgb(239, 248, 223);
  // color: red;
  border-bottom: none;
}

.item.active {
  // background: #5076B8;
  // color: #fff;
  background: rgb(255, 230, 219);
  border-bottom: none;

  &::before {
    content: "";
    position: absolute;
    width: 100%;
    height: 100%;
    background: rgb(255, 230, 219);
    z-index: 11;
  }
}

.item.active.border-top-red::before {
  content: "";
  position: absolute;
  width: 100%;
  top: -1px;
  z-index: 11;
  border-top: 1px solid red !important;
}

.item.active.border-bottom-red::before {
  content: "";
  position: absolute;
  width: 100%;
  bottom: 0px;
  z-index: 11;
  border-bottom: 1px solid red !important;
}

.item.disabled {
  background: rgb(239, 248, 223);
  color: #999999;
  border-bottom: none;
}

.title-box,
.close-box {
  position: absolute;
  width: 100%;
  top: -1px;
  z-index: 10;
  line-height: initial;
  padding: 0 10%;
  pointer-events: none;
  text-align: center;
  font-size: 28rpx;

  .text {
    pointer-events: all;
  }
}

.item.close {
  // background: rgb(204, 204, 204);
  pointer-events: none;
  border-bottom: none;

  .close-box {
    background: rgba(0, 0, 0, 0.3);
  }
}

.item.disabled .title-box {
  border-bottom: 1px solid #d2d2d2;
}

.item.select .title-box {
  border-top: 1px solid rgb(152, 207, 59);
  border-bottom: 1px solid rgb(152, 207, 59);
}


</style>

3、引入

<TimeSelection
		ref="timeSelectRef"
        :isShare="siteInfo.isShare"
        :openTime="siteInfo.openTime"
        :maxTimeLength="siteInfo.maxTimeLength"
        :checkTime="checkTime"
        :meetingDate="currentDate"
        @getTime="getTime"
        @clickTitle="clickShowTitle"
      />

4、具体使用及案例

<template>
  <view class="bg-color" :class="timeList.start?'page-fixed-bottom':'page-bottom'">
    <view class="p-10">
      <u-scroll-list :indicator="false">
        <view
          class="date-box d-center flex-shrink-0"
          :class="{'active':currentDate==item.date}"
          v-for="(item, index) in dateList"
          :key="index"
          @click="clickSelectDate(item)"
        >
          <view class="text-center">
            <view class="date-text font-28">{{item.showDate}}</view>
            <view class="week-text font-24">{{item.week}}</view>
          </view>
        </view>
      </u-scroll-list>
      <TimeSelection
      	ref="timeSelectRef"
        :isShare="siteInfo.isShare"
        :openTime="siteInfo.openTime"
        :maxTimeLength="siteInfo.maxTimeLength"
        :checkTime="checkTime"
        :meetingDate="currentDate"
        @getTime="getTime"
        @clickTitle="clickShowTitle"
      />
    </view>
    <view class="fixed bg-white p-lr-20 border-top-1" v-if="timeList.start">
      <view class="d-align-center mt-10">
        <view
          class="btn-brand flex-grow-1"
          @click="clickReservation"
        >预约 {{timeList.start}}-{{timeList.end}}</view>
      </view>
    </view>
  </view>
</template>

<script>
// 导入时间选择组件
import timeSelection from "./component/timeSelection.vue";
// 从工具模块导入时间格式化函数
// import { formatTime } from "../../utils/util";

/**
 * @description 场地预约页面组件
 */
export default {
  // 注册组件
  components: {
    timeSelection
  },
  // 定义数据
  data() {
    return {
      // 场地信息
      siteInfo: {
        // 场地名称
        name: "多功能研讨室",
        // 可提前预约的天数
        advanceDay: 10,
        // 是否可共享
        isShare: false,
        // 最大预约时长
        maxTimeLength: 2,
        // 开放时间
        openTime: ["9:00", "20:00"]
      },
      // 当前选中的日期
      currentDate: "",
      // 日期列表
      dateList: [],
      // 选中的时间范围
      timeList: {
        start: "",
        end: ""
      },
      // 已选时间段列表
      checkTime: []
    };
  },

  // 初始化,页面加载时执行
  async onLoad(options) {
    // if (options?.siteId) {
    // 加载数据
    this.loadData();
    // }
  },
  // 页面显示时执行
  onShow() {
    // this.getAllActiveListFun()
  },
  /**
   * 页面上拉触底事件的处理函数
   */
  onPullDownRefresh() {
    // 下拉刷新时重新加载数据
    this.loadData();
  },
  // 页面触底时执行
  onReachBottom() {},
  methods: {
    /**
     * @description 加载数据
     */
    loadData() {
      // 定义星期数组
      var weeks = ["周日", "周一", "周二", "周三", "周四", "周伍", "周六"];
      // 循环生成可预约的日期列表
      for (var i = 0; i < this.siteInfo.advanceDay; i++) {
        // 获取当前时间
        var currentTime = new Date();
        // 计算当前日期加上 i + 1 天后的日期
        var time = new Date(currentTime.setDate(currentTime.getDate() + i + 1));
        // 构建日期对象
        var date = {
          // 显示的日期(月-日)
          showDate: this.formatTime(time, "md"),
          // 完整日期(年-月-日)
          date: this.formatTime(time, "ymd"),
          // 星期几
          week: weeks[time.getDay()]
        };
        // 将日期对象添加到日期列表中
        this.dateList.push(date);
      }
      // 如果日期列表不为空
      if (this.dateList.length) {
        // 点击第一个日期
        this.clickSelectDate(this.dateList[0]);
      }

      // 获取当前时间
      var currentTime = new Date();
      // 模拟已预约的时间段数据
      var data = [
        {
          id: 1,
          // 开始时间
          beginTime: this.formatTime(currentTime, "ymd") + " 10:00",
          // 结束时间
          endTime: this.formatTime(currentTime, "ymd") + " 11:30",
          // 活动名称
          activityName: "我为同学做实事:电影宣传(二)1"
        },
        {
          id: 2,
          beginTime: this.formatTime(currentTime, "ymd") + " 15:00",
          endTime: this.formatTime(currentTime, "ymd") + " 16:30",
          activityName: "我为同学做实事:电影宣传(二)2"
        }
        // {
        // 	id: 3,
        // 	beginTime: this.formatTime(currentTime, "ymd") + " 16:00",
        // 	endTime: this.formatTime(currentTime, "ymd") + " 17:30",
        // 	activityName: "我为同学做实事:电影宣传(二)3"
        // }
      ];
      // 转换已预约时间段数据格式
      this.checkTime = data.map(item => {
        return {
          ...item,
          // 开始时间(时:分)
          start: this.formatTime(item.beginTime, "hm"),
          // 标题
          title: item.activityName,
          // 结束时间(时:分)
          end: this.formatTime(item.endTime, "hm")
        };
      });
      //获取数据后初始化组件数据
      this.$refs.timeSelectRef?.initTime()
    },
    /**
     * @description 点击选择日期
     * @param {Object} item - 点击的日期对象
     */
    clickSelectDate(item) {
      // 更新当前选中的日期
      this.currentDate = item.date;
    },
    /**
     * @description 获取选中的时间范围
     * @param {Object} val - 选中的时间范围对象
     */
    getTime(val) {
      // 更新选中的时间范围
      this.timeList = val;
    },
    /**
     * @description 点击显示处理事件
     */
    clickShowTitle() {},
    /**
     * @description 格式化日期
     * @param {string|number|Date} currentDate - 要格式化的日期
     * @param {string} format - 日期格式,默认为 "ymdhm"
     * @returns {string} 格式化后的日期字符串
     */
    formatTime(currentDate, format = "ymdhm") {
      if (!currentDate) return "";
      let date;
      if (typeof currentDate === "string") {
        currentDate = currentDate.replace(/-/g, "/");
        date = new Date(currentDate);
      } else if (typeof currentDate === "number") {
        date = new Date(currentDate);
      } else {
        date = currentDate;
      }

      const year = date.getFullYear();
      const month = date.getMonth() + 1;
      const day = date.getDate();
      const hour = date.getHours();
      const minute = date.getMinutes();
      const second = date.getSeconds();

      const formatNumber = n => {
        n = n.toString();
        return n[1] ? n : "0" + n;
      };

      const formatMap = {
        ymd: `${[year, month, day].map(formatNumber).join("/")}`,
        md: `${[month, day].map(formatNumber).join("/")}`,
        ymdhm: `${[year, month, day].map(formatNumber).join("/")} ${[
          hour,
          minute
        ]
          .map(formatNumber)
          .join(":")}`,
        ymdhms: `${[year, month, day].map(formatNumber).join("/")} ${[
          hour,
          minute,
          second
        ]
          .map(formatNumber)
          .join(":")}`,
        hms: `${[hour, minute, second].map(formatNumber).join(":")}`,
        hm: `${[hour, minute].map(formatNumber).join(":")}`
      };

      return formatMap[format] || "";
    }
  }
};
</script>

<style lang="scss" scoped>
.date-box {
  border-radius: 8rpx;
  width: 104rpx;
  height: 112rpx;
  background-color: white;
  margin-right: 16rpx;

  &.active {
    border-radius: 20rpx;
    // box-shadow: 0px 5px 30px 0px rgba(209, 66, 1, 0.2),inset 0px 0px 20px 0px rgb(255, 255, 255);
    background: linear-gradient(
      -45deg,
      rgb(247, 106, 52),
      rgb(248, 142, 60) 98.473%
    );

    .date-text,
    .week-text {
      color: white;
    }
  }
}

.page-fixed-bottom,
.fixed {
  padding-bottom: calc(0rpx + env(safe-area-inset-bottom));
  .btn-brand {
    padding: 22rpx;
  }
}

//通用样式-可修改
.bg-color {
  background-color: #f7f8fa;
}

.page-fixed-bottom {
  padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
}

.p-10 {
  padding: 20rpx;
}

.border-top-1 {
  border-top: 2rpx solid #eeeeee;
}

.fixed {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 10;
  padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
}

.bg-white {
  background-color: white;
}

.p-lr-20 {
  padding-left: 4%;
  padding-right: 40rpx;
}

.mt-10 {
  margin-top: 10rpx;
}

.font-28 {
  font-size: 28rpx;
}

.font-24 {
  font-size: 24rpx; 
}

.d-center{
  display: flex;
  justify-content: center;
  align-items: center;
}

.flex-shrink-0 {
  flex-shrink: 0;
}

.d-align-center {
  display: flex;
  align-items: center;
}

.text-center {
  text-align: center;
}

.btn-brand {
  padding: 28rpx;
  text-align: center;
  color: white;
  font-size: 32rpx;
  border-radius: 100rpx;
  box-shadow: 0px 10rpx 40rpx 0px rgba(247, 114, 54, 0.2);
  background: linear-gradient(-45deg, #f76a34, #f88e3c 98.473%);
  transition: -webkit-transform 0.3s;
  transition: transform 0.3s;
  transition: transform 0.3s, -webkit-transform 0.3s;
}

.flex-grow-1 {
  flex-grow: 1;
}


</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值