index.vue 9.46 KB
<!--
 * @Date: 2024-01-15 16:25:51
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2026-01-16 20:38:48
 * @FilePath: /xyxBooking-weapp/src/pages/submit/index.vue
 * @Description: 预约人员信息
-->
<template>
  <view class="submit-page">
    <view @tap="goToBooking" class="visit-time">
      <view>参访时间</view>
      <view class="flex items-center">
        <text style="font-size: 30rpx;">{{ date }} {{ time }}</text>
        <IconFont name="rect-right" class="ml-1" />
      </view>
    </view>
    <view @tap="goToVisitor" class="add-visitors">
      <view class="flex items-center justify-center">
        <IconFont name="plus" class="mr-1" /> 添加参观者
      </view>
    </view>
    <view v-if="visitorList.length" class="visitors-list">
      <view v-for="(item, index) in visitorList" :key="index" @tap="addVisitor(item)" class="visitor-item">
        <view style="margin-right: 32rpx;">
          <image v-if="!checked_visitors.includes(item.id)" :src="icon_check1" style="width: 38rpx; height: 38rpx;" />
          <image v-else :src="icon_check2" style="width: 38rpx; height: 38rpx;" />
        </view>
        <view>
          <view style="color: #A67939;">{{ item.name }}</view>
          <view>证件号:{{ formatId(item.id_number) }}</view>
          <view v-if="item.is_reserve === RESERVE_STATUS.ENABLE" style="color: #9C9A9A; font-size: 26rpx;">*已预约过{{ date
            }}参观,请不要重复预约</view>
        </view>
      </view>
    </view>
    <view v-else class="no-visitors-list">
      <image src="https://cdn.ipadbiz.cn/xys/booking/%E6%9A%82%E6%97%A0@2x.png"
        style="width: 320rpx; height: 320rpx;" />
      <view class="no-visitors-list-title">您还没有添加过参观者</view>
    </view>
    <view style="height: 160rpx;"></view>
    <view class="submit-wrapper">
      <view class="control-wrapper">
        <view class="left">
          <view style="margin-left: 32rpx; display: flex;align-items: center;">
            订单金额&nbsp;&nbsp;<view style="color: #FF1919;display: inline-block;">¥<view
                style="font-size: 48rpx;display: inline-block;">&nbsp;{{ total }}</view>
            </view>
          </view>
        </view>
        <view @tap="submitBtn" class="right">提交订单</view>
      </view>
      <view style="font-size: 27rpx;margin-left: 32rpx;color: #FF1919; margin-bottom: 32rpx;">提交后请在10分钟内完成支付</view>
    </view>
  </view>
</template>

<script setup>
import { ref, computed } from 'vue'
import Taro, { useDidShow, useRouter as useTaroRouter } from '@tarojs/taro'
import { IconFont } from '@nutui/icons-vue-taro'
import { useGo, useReplace } from '@/hooks/useGo'
import icon_check1 from '@/assets/images/多选01@2x.png'
import icon_check2 from '@/assets/images/多选02@2x.png'
import { personListAPI, addReserveAPI } from '@/api/index'
import { wechat_pay } from '@/utils/wechatPay'

const router = useTaroRouter();
const go = useGo();
const replace = useReplace();

const visitorList = ref([]);
const date = ref('');
const time = ref('');
const price = ref(0);
const period_type = ref('');

/**
 * 生成15位身份证号中间8位替换为*号
 * @param {*} inputString
 */
function replaceMiddleCharacters(inputString) {
  if (!inputString || inputString.length < 15) {
    return inputString; // 字符串长度不足,不进行替换
  }

  const start = Math.floor((inputString.length - 8) / 2); // 开始替换的索引位置
  const end = start + 8; // 结束替换的索引位置

  const replacement = '*'.repeat(8); // 生成包含8个*号的字符串

  const replacedString = inputString.substring(0, start) + replacement + inputString.substring(end);
  return replacedString;
}

const formatId = (id) => {
  return replaceMiddleCharacters(id);
};

const RESERVE_STATUS = {
  ENABLE: '1'
}

const checked_visitors = ref([]);
const is_submitting = ref(false); // 是否正在提交订单
const addVisitor = (item) => {
  if (item.is_reserve === RESERVE_STATUS.ENABLE) { // 今天已经预约
    Taro.showToast({ title: '已预约过参观,请不要重复预约', icon: 'none' })
    return;
  }
  if (checked_visitors.value.includes(item.id)) {
    checked_visitors.value = checked_visitors.value.filter((v) => v !== item.id);
  } else {
    checked_visitors.value.push(item.id);
  }
}

const total = computed(() => {
  return price.value * checked_visitors.value.length;
})

const goToBooking = () => {
  go('/booking');
}
const goToVisitor = () => {
  go('/addVisitor');
}

// 待支付订单ID
const pending_pay_id = ref(null);
// 待支付订单是否需要支付
const pending_need_pay = ref(null);

/**
 * 刷新参观者列表
 */

const refreshVisitorList = async (options) => {
  if (!date.value || !time.value) return;
  const res = await personListAPI({
    reserve_date: date.value,
    begin_time: time.value.split('-')[0],
    end_time: time.value.split('-')[1],
    period_type: period_type.value
  });
  if (res && res.code) {
    visitorList.value = res.data || [];
    if (options?.reset_checked) {
      checked_visitors.value = [];
    }
  }
}

let is_showing_pay_modal = false;
const showPayErrorModal = async (content) => {
  if (is_showing_pay_modal) return;
  is_showing_pay_modal = true;
  try {
    const res = await Taro.showModal({
      title: '提示',
      content: content || '支付失败,请稍后再试',
      showCancel: true,
      cancelText: '离开',
      confirmText: '继续支付',
    });
    return !!res?.confirm;
  } finally {
    is_showing_pay_modal = false;
  }
}

const submitBtn = async () => {
  if (is_submitting.value) return;
  if (!checked_visitors.value.length) {
    Taro.showToast({ title: '请先添加参观者', icon: 'none' })
    return;
  }

  is_submitting.value = true;
  try {
    let pay_id = pending_pay_id.value;
    let need_pay = pending_need_pay.value;

    if (!pay_id) { // TAG: 提交订单, 如果没有待支付订单ID, 则创建一个新的订单
      Taro.showLoading({ title: '提交中...' });
      let reserve_res = null;
      try {
        reserve_res = await addReserveAPI({
          reserve_date: date.value,
          begin_time: time.value.split('-')[0],
          end_time: time.value.split('-')[1],
          person_id_list: JSON.stringify(checked_visitors.value),
          period_type: period_type.value
        });
      } finally {
        Taro.hideLoading();
      }

      if (!reserve_res || reserve_res.code != 1) {
        return;
      }
      pay_id = reserve_res.data.pay_id;
      pending_pay_id.value = pay_id;
      need_pay = reserve_res.data?.need_pay;
      pending_need_pay.value = need_pay;
      await refreshVisitorList({ reset_checked: true });
    }

    // 以接口返回的 need_pay 为准:1=需要支付,0=不需要支付
    if (Number(need_pay) === 1 || need_pay === true) {
      // 初始化循环
      let should_continue = true;
      // 循环支付直到支付成功或用户取消支付
      while (should_continue) {
        const pay_res = await wechat_pay({ pay_id })
        if (pay_res && pay_res.code == 1) {
          pending_pay_id.value = null;
          pending_need_pay.value = null;
          go('/success', { pay_id });
          return
        }
        // 刷新参观者列表, 清除已预约标记
        refreshVisitorList({ reset_checked: true }).catch(() => {});
        should_continue = await showPayErrorModal(pay_res?.msg || '支付未完成,可再次点击提交订单继续支付')
      }

      replace('/bookingList')
      return
    } else {
      pending_pay_id.value = null;
      pending_need_pay.value = null;
      go('/success', { pay_id });
    }
  } finally {
    is_submitting.value = false;
  }
}

useDidShow(async () => {
  const params = router.params;
  date.value = params.date || '';
  time.value = params.time || '';
  price.value = params.price || 0;
  period_type.value = params.period_type || '';

  await refreshVisitorList();
});
</script>

<style lang="less">
.submit-page {
  margin: 32rpx;
  position: relative;

  .visit-time {
    background-color: #FFF;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 24rpx;
    border-radius: 16rpx;
  }

  .add-visitors {
    border: 2rpx dashed #A67939;
    color: #A67939;
    border-radius: 10rpx;
    text-align: center;
    padding: 21rpx 0;
    margin: 32rpx 0;
    font-size: 37rpx;
  }

  .visitors-list {
    .visitor-item {
      background-color: #FFF;
      border-radius: 16rpx;
      padding: 32rpx;
      margin-bottom: 32rpx;
      display: flex;
      align-items: center;
    }
  }

  .no-visitors-list {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;

    img {
      margin-top: 32rpx;
      margin-bottom: 32rpx;
      width: 320rpx;
    }

    .no-visitors-list-title {
      color: #A67939;
      font-size: 34rpx;
    }
  }

  .submit-wrapper {
    position: fixed;
    bottom: 0;
    left: 0;
    width: 750rpx;
    display: flex;
    background-color: #FFF;
    // padding: 32rpx;
    justify-content: space-between;
    flex-direction: column;
    box-shadow: 0 -10rpx 8rpx 0 rgba(0, 0, 0, 0.12);

    .control-wrapper {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .left {
      display: flex;
      justify-content: center;
      align-items: center;
      flex-wrap: nowrap;
    }

    .right {
      background-color: #A67939;
      color: #FFF;
      margin: 32rpx;
      padding: 26rpx 96rpx;
      border-radius: 5px;
      font-size: 35rpx;
      margin-bottom: 0;
    }
  }
}
</style>