qrCode.vue 9.25 KB
<!--
 * @Date: 2024-01-16 10:06:47
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2024-12-26 11:15:45
 * @FilePath: /xyxBooking-weapp/src/components/qrCode.vue
 * @Description: 预约码卡组件
-->
<template>
  <view class="qr-code-page">
    <view v-if="userList.length" class="show-qrcode">
      <view class="qrcode-content">
        <view class="user-info">{{ userinfo.name }}&nbsp;{{ userinfo.id }}</view>
        <view class="user-qrcode">
          <view class="left" @tap="prevCode">
            <image src="https://cdn.ipadbiz.cn/xys/booking/%E5%B7%A6@2x.png" />
          </view>
          <view class="center">
            <image :src="currentQrCodeUrl" mode="aspectFit" />
            <view v-if="useStatus === STATUS_CODE.CANCELED || useStatus === STATUS_CODE.USED" class="qrcode-used">
              <view class="overlay"></view>
              <text class="status-text">二维码{{ qr_code_status[useStatus] }}</text>
            </view>
          </view>
          <view class="right" @tap="nextCode">
            <image src="https://cdn.ipadbiz.cn/xys/booking/%E5%8F%B3@2x.png" />
          </view>
        </view>
        <view style="color: red; margin-top: 1rem;">{{ userinfo.datetime }}</view>
      </view>
      <view class="user-list">
        <view
          @tap="selectUser(index)"
          v-for="(item, index) in userList"
          :key="index"
          :class="[
            'user-item',
            select_index === index ? 'checked' : '',
            userList.length > 1 && item.sort ? 'border' : '',
          ]">
          {{ item.name }}
        </view>
      </view>
    </view>
    <view v-else class="no-qrcode">
      <image src="https://cdn.ipadbiz.cn/xys/booking/%E6%9A%82%E6%97%A0@2x.png" style="width: 10rem; height: 10rem;" />
      <view class="no-qrcode-title">今天没有预约记录</view>
      <view style="text-align: center; color: #A67939; margin-top: 0.5rem;">查看我的“<text @tap="toRecord" style="text-decoration: underline; color: #ED9820;">预约记录</text>”</view>
    </view>
  </view>
</template>

<script setup>
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import Taro, { useDidShow } from '@tarojs/taro'
import { formatDatetime } from '@/utils/tools';
import { qrcodeListAPI, qrcodeStatusAPI, billPersonAPI } from '@/api/index'
import { useGo } from '@/hooks/useGo'
import BASE_URL from '@/utils/config';

const go = useGo();

const props = defineProps({
  status: {
    type: String,
    default: ''
  },
  type: {
    type: String,
    default: ''
  },
  payId: { // 接收 payId
    type: String,
    default: ''
  }
});

const select_index = ref(0);
const userList = ref([]);

const prevCode = () => {
  select_index.value = select_index.value - 1;
  if (select_index.value < 0) {
    select_index.value = userList.value.length - 1;
  }
};
const nextCode = () => {
  select_index.value = select_index.value + 1;
  if (select_index.value > userList.value.length - 1) {
    select_index.value = 0;
  }
};

watch(
  () => select_index.value,
  (index) => {
    refreshBtn();
  }
)

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);
  return inputString.substring(0, start) + replacement + inputString.substring(end);
}

const formatId = (id) => replaceMiddleCharacters(id);

const userinfo = computed(() => {
  return {
    name: userList.value[select_index.value]?.name,
    id: formatId(userList.value[select_index.value]?.id_number),
    datetime: userList.value[select_index.value]?.datetime,
  };
});

const currentQrCodeUrl = computed(() => {
    const url = userList.value[select_index.value]?.qr_code_url;
    if (url && url.startsWith('/')) {
        return BASE_URL + url;
    }
    return url;
})

const useStatus = ref('0');

const qr_code_status = {
  '1': '未激活',
  '3': '待使用',
  '5': '被取消',
  '7': '已使用',
};

const STATUS_CODE = {
  APPLY: '1',
  SUCCESS: '3',
  CANCELED: '5',
  USED: '7',
};

const refreshBtn = async () => {
  if (!userList.value[select_index.value]) return;
  const { code, data } = await qrcodeStatusAPI({ qr_code: userList.value[select_index.value].qr_code });
  if (code) {
    useStatus.value = data.status;
  }
}

const selectUser = (index) => {
  select_index.value = index;
}

const formatGroup = (data) => {
  let lastPayId = null;
  for (let i = 0; i < data.length; i++) {
    if (data[i].pay_id !== lastPayId) {
      data[i].sort = 1;
      lastPayId = data[i].pay_id;
    } else {
      data[i].sort = 0;
    }
  }
  return data;
}

const init = async () => {
  if (!props.type) {
    const { code, data } = await qrcodeListAPI();
    if (code) {
      data.forEach(item => {
        item.qr_code_url = '/admin?m=srv&a=get_qrcode&key=' + item.qr_code;
        item.datetime = formatDatetime({ begin_time: item.begin_time, end_time: item.end_time })
        item.sort = 0;
      });
      // 剔除qr_code为空的二维码
      const validData = data.filter(item => item.qr_code !== '');
      
      if (validData.length > 0) {
          userList.value = formatGroup(validData);
          refreshBtn();
      } else {
          userList.value = [];
      }
    }
  } else {
    if (props.payId) {
        const { code, data } = await billPersonAPI({ pay_id: props.payId });
        if (code) {
          data.forEach(item => {
            item.qr_code_url = '/admin?m=srv&a=get_qrcode&key=' + item.qr_code;
            item.sort = 0;
            // billPersonAPI 返回的数据可能没有 datetime 字段,需要检查
            // 如果没有,可能需要从外部传入或者假设是当天的?
            // H5 代码没有处理 datetime,但在 template 里用了。
            // 这里暂且不做处理,如果没有 datetime 就不显示
          });
          const validData = data.filter(item => item.qr_code !== '');
           if (validData.length > 0) {
              userList.value = validData;
              refreshBtn();
          } else {
              userList.value = [];
          }
        }
    }
  }
};

onMounted(() => {
    init();
});

const poll = async () => {
  if (userList.value.length && useStatus.value === STATUS_CODE.SUCCESS) {
     if (userList.value[select_index.value]) {
        const { code, data } = await qrcodeStatusAPI({ qr_code: userList.value[select_index.value].qr_code });
        if (code) {
          useStatus.value = data.status;
        }
    }
  }
};

const intervalId = setInterval(poll, 3000); // 3秒轮询一次,避免过于频繁

onUnmounted(() => {
  clearInterval(intervalId);
});

const toRecord = () => {
  go('/bookingList');
}
</script>

<style lang="less">
.qr-code-page {
  .qrcode-content {
    padding: 1rem 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background-color: #FFF;
    border-radius: 8px;
    box-shadow: 0rem 0rem 0.92rem 0rem rgba(106,106,106,0.27);
    
    .user-info {
      color: #A6A6A6;
      font-size: 1.15rem;
      margin-top: 0.5rem;
      margin-bottom: 0.5rem;
    }
    .user-qrcode {
      display: flex;
      align-items: center;
      .left {
        image {
          width: 1.75rem; height: 1.75rem; margin-right: 0.5rem;
        }
      }
      .center {
        border: 1px solid #D1D1D1;
        border-radius: 20px;
        padding: 0.5rem;
        position: relative;
        image {
          width: 15rem; height: 15rem;
        }
        .qrcode-used {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            border-radius: 20px;
            overflow: hidden;
            
            .overlay {
                width: 100%;
                height: 100%;
                background-image: url('https://cdn.ipadbiz.cn/xys/booking/southeast.jpeg');
                background-size: contain;
                opacity: 0.9;
            }
            
            .status-text {
                color: #A67939;
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                font-size: 1.2rem;
                white-space: nowrap;
                font-weight: bold;
                z-index: 10;
            }
        }
      }
      .right {
        image {
          width: 1.75rem; height: 1.75rem;
          margin-left: 0.5rem;
        }
      }
    }
  }
  .user-list {
    display: flex;
    padding: 1rem;
    align-items: center;
    flex-wrap: wrap;
    .user-item {
      position: relative;
      padding: 0.25rem 0.5rem;
      border: 1px solid #A67939;
      margin: 0.25rem;
      border-radius: 5px;
      color: #A67939;
      &.checked {
        color: #FFF;
        background-color: #A67939;
      }
      &.border {
        margin-right: 0.5rem;
        &::after {
          position: absolute;
          right: -0.5rem;
          top: calc(50% - 0.5rem);
          content: '';
          height: 1rem;
          border-right: 1px solid #A67939;
        }
      }
    }
  }

  .no-qrcode {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    margin-bottom: 1rem;
    
    .no-qrcode-title {
      color: #A67939;
      font-size: 1.05rem;
    }
  }
}
</style>