longz
2026-07-13 5882ba8863d77fdcc168b176c3ffb30e8a20556a
缩短二维码原始参数
14个文件已修改
249 ■■■■■ 已修改文件
.gitignore 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/sinata/appointment/common/GlobalExceptionHandler.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/sinata/appointment/controller/AppointmentController.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/sinata/appointment/controller/VisitorUserManageController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/sinata/appointment/service/AppointmentApplicationService.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/sinata/appointment/service/IntegrationHikvisionService.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/sinata/appointment/service/LocalImageUploadService.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/sinata/appointment/service/PostApprovalOrchestrationService.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/sinata/appointment/service/StaffManageApplicationService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
backend/src/main/java/com/sinata/appointment/service/VisitorUserManageApplicationService.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yuyueMiniProgram/src/pages/appointment/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yuyueMiniProgram/src/pages/index/index.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yuyueMiniProgram/src/pages/visitorCode/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yuyueMiniProgram/src/pages/visitorCodeShare/index.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -8,3 +8,5 @@
*.log
.env
.env.local
.codegraph/
**/.codegraph/
backend/src/main/java/com/sinata/appointment/common/GlobalExceptionHandler.java
@@ -35,16 +35,27 @@
    
    @ExceptionHandler(SaTokenException.class)
    public Result<Void> handleSaToken(SaTokenException e) {
        log.error("satoken error", e);
        if (e instanceof NotPermissionException){
            log.warn("satoken forbidden: {}", safeMessage(e));
            return ResultUtil.error(HttpBizCode.FORBIDDEN, "无权限访问");
        }
        if (e instanceof NotLoginException notLogin
                && NotLoginException.KICK_OUT.equals(notLogin.getType())) {
            log.warn("satoken unauthorized: type={} message={}", notLogin.getType(), safeMessage(e));
            return ResultUtil.error(HttpBizCode.UNAUTHORIZED, "账号已被禁用,请重新登录");
        }
        if (e instanceof NotLoginException notLogin) {
            log.warn("satoken unauthorized: type={} message={}", notLogin.getType(), safeMessage(e));
        return ResultUtil.error(HttpBizCode.UNAUTHORIZED, "账号信息已过期,请重新登录");
    }
        log.warn("satoken error: {} message={}", e.getClass().getSimpleName(), safeMessage(e));
        return ResultUtil.error(HttpBizCode.UNAUTHORIZED, "账号信息已过期,请重新登录");
    }
    private String safeMessage(Exception e) {
        String message = e.getMessage();
        return message == null || message.isBlank() ? "-" : message;
    }
    @ExceptionHandler(Exception.class)
    public Result<Void> handleOther(Exception e) {
backend/src/main/java/com/sinata/appointment/controller/AppointmentController.java
@@ -9,13 +9,12 @@
import com.sinata.appointment.service.AppointmentApplicationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotBlank;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -67,7 +66,8 @@
    @Operation(summary = "导出 Excel")
    @GetMapping("/export")
    @SaCheckPermission("appointments:export")
    public ResponseEntity<byte[]> export(
    public void export(
            HttpServletResponse response,
            @RequestParam(required = false) String visitedUserName,
            @RequestParam(required = false) Long visitedDeptId,
            @RequestParam(required = false) Long accessAreaId,
@@ -78,9 +78,10 @@
            @RequestParam(required = false) String displayStatus,
            @RequestParam(required = false) String approvalStatus,
            @RequestParam(required = false) String visitorKeyword,
            @RequestParam(required = false) String visitReasonCode) {
        byte[] body =
            @RequestParam(required = false) String visitReasonCode)
            throws IOException {
                appointmentApplicationService.exportExcel(
                response,
                        visitedUserName,
                        visitedDeptId,
                        accessAreaId,
@@ -92,12 +93,6 @@
                        approvalStatus,
                        visitorKeyword,
                        visitReasonCode);
        return ResponseEntity.ok()
                .contentType(
                        MediaType.parseMediaType(
                                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"appointments.xlsx\"")
                .body(body);
    }
    @Operation(summary = "详情含访客")
backend/src/main/java/com/sinata/appointment/controller/VisitorUserManageController.java
@@ -10,13 +10,12 @@
import com.sinata.appointment.service.VisitorUserManageApplicationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import java.io.IOException;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -58,24 +57,17 @@
    @Operation(summary = "导出 Excel")
    @GetMapping("/export")
    @SaCheckPermission("sys:visitor-users:export")
    public ResponseEntity<byte[]> export(
    public void export(
            HttpServletResponse response,
            @RequestParam(required = false) String userName,
            @RequestParam(required = false) String mobile,
            @RequestParam(required = false) String companyName,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate registerStartDate,
            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate registerEndDate,
            @RequestParam(required = false) Integer status) {
        byte[] body =
            @RequestParam(required = false) Integer status)
            throws IOException {
                visitorUserManageApplicationService.exportExcel(
                        userName, mobile, companyName, registerStartDate, registerEndDate, status);
        return ResponseEntity.ok()
                .contentType(
                        MediaType.parseMediaType(
                                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
                .header(
                        HttpHeaders.CONTENT_DISPOSITION,
                        "attachment; filename=\"visitor-users.xlsx\"")
                .body(body);
                response, userName, mobile, companyName, registerStartDate, registerEndDate, status);
    }
    @Operation(summary = "详情")
backend/src/main/java/com/sinata/appointment/service/AppointmentApplicationService.java
@@ -38,7 +38,10 @@
import com.sinata.appointment.util.MobileMaskUtil;
import com.sinata.appointment.util.SensitiveDataUtil;
import com.sinata.appointment.util.UuidAssert;
import java.io.ByteArrayOutputStream;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -110,7 +113,8 @@
        return buildAdminPage(pagedOrders);
    }
    public byte[] exportExcel(
    public void exportExcel(
            HttpServletResponse response,
            String visitedUserName,
            Long visitedDeptId,
            Long accessAreaId,
@@ -121,11 +125,16 @@
            String displayStatus,
            String approvalStatus,
            String visitorKeyword,
            String visitReasonCode) {
            String visitReasonCode)
            throws IOException {
        prepareExcelResponse(response, "appointments.xlsx");
        List<Long> accessAreaIds = resolveAccessAreaFilterIds(accessAreaId);
        long pageNo = 1L;
        long pageSize = 200L;
        List<AppointmentAdminExportRow> rows = new ArrayList<>();
        boolean wroteAnyPage = false;
        try (ExcelWriter writer =
                EasyExcel.write(response.getOutputStream(), AppointmentAdminExportRow.class).build()) {
            WriteSheet sheet = EasyExcel.writerSheet("预约记录").build();
        while (true) {
            Page<AppointmentOrder> orderPage = new Page<>(pageNo, pageSize);
            IPage<AppointmentOrder> pagedOrders =
@@ -147,24 +156,30 @@
                break;
            }
            AdminPageData pageData = loadAdminPageData(orders);
            rows.addAll(
                writer.write(
                    buildExportRows(
                            orders,
                            pageData.visitorRowsMap(),
                            pageData.approvalInstanceMap(),
                            pageData.reasonNameMap()));
                                pageData.reasonNameMap()),
                        sheet);
                wroteAnyPage = true;
            if (pageNo * pageSize >= pagedOrders.getTotal()) {
                break;
            }
            pageNo++;
        }
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            EasyExcel.write(bos, AppointmentAdminExportRow.class).sheet("预约记录").doWrite(rows);
            return bos.toByteArray();
        } catch (Exception e) {
            return new byte[0];
            if (!wroteAnyPage) {
                writer.write(List.of(), sheet);
        }
    }
    }
    private void prepareExcelResponse(HttpServletResponse response, String filename) {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
    }
    public AppointmentOrder getById(Long id, String uuid) {
        AppointmentOrder o = appointmentOrderMapper.selectById(id);
backend/src/main/java/com/sinata/appointment/service/IntegrationHikvisionService.java
@@ -95,7 +95,6 @@
        payload.put("workUnit", firstNonBlank(valueOf(visitorProfile, MpVisitor::getVisitorCompany), order.getVisitorOrg()));
        payload.put("carNumber", trimToEmpty(visitor.getPlateNo()));
        payload.put("visitPurpose", trimToEmpty(visitPurpose));
        payload.put("jobNo", valueOf(visitedUser, MpUser::getStaffNo));
        payload.put("beVisitorPhone", valueOf(visitedUser, MpUser::getMobile));
        payload.put("startTime", formatDateTime(startTime, QR_TIME_FORMATTER));
        payload.put("endTime", formatDateTime(endTime, QR_TIME_FORMATTER));
@@ -104,9 +103,6 @@
        payload.put("date", formatDateTime(qrGenerateTime, QR_DATE_FORMATTER));
        payload.put("validTime", VALID_TIME_PREFIX + Math.max(validMinutes, 1));
        payload.put("codeId", trimToEmpty(codeId));
        payload.put("customa", EMPTY);
        payload.put("customb", EMPTY);
        payload.put("customc", EMPTY);
        try {
            String json = objectMapper.writeValueAsString(payload);
            log.info(
backend/src/main/java/com/sinata/appointment/service/LocalImageUploadService.java
@@ -4,6 +4,7 @@
import com.sinata.appointment.constants.BizErrorCode;
import com.sinata.appointment.exception.BizException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
@@ -41,9 +42,9 @@
        if (!target.getParent().equals(uploadDir)) {
            throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "图片上传路径非法");
        }
        try {
        try (InputStream in = file.getInputStream()) {
            Files.createDirectories(uploadDir);
            Files.copy(file.getInputStream(), target, StandardCopyOption.REPLACE_EXISTING);
            Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException ex) {
            throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "图片上传失败");
        }
backend/src/main/java/com/sinata/appointment/service/PostApprovalOrchestrationService.java
@@ -120,6 +120,15 @@
            visitor.setPlateNo(driverPlateMap.get(visitor.getMpVisitorId()));
            String ticketUuid = UUID.randomUUID().toString().replace("-", "");
            String visitorIdCardPlain = resolveVisitorIdCardPlain(visitor, visitorProfile);
            QrcodeTicket ticket = new QrcodeTicket();
            ticket.setUuid(ticketUuid);
            ticket.setOrderId(order.getId());
            ticket.setMpVisitorId(visitor.getMpVisitorId());
            ticket.setPayload("{}");
            ticket.setTicketStatus(QrcodeTicketStatus.UNUSED);
            ticket.setGenerateTime(generateTime);
            ticket.setExpireTime(expireTime);
            qrcodeTicketMapper.insert(ticket);
            String payload =
                    integrationHikvisionService.requestQrcodePayload(
                            order,
@@ -131,24 +140,18 @@
                            generateTime,
                            expireTime,
                            validMinutes,
                            ticketUuid,
                            String.valueOf(ticket.getId()),
                            visitorIdCardPlain);
            QrcodeTicket ticket = new QrcodeTicket();
            ticket.setUuid(ticketUuid);
            ticket.setOrderId(order.getId());
            ticket.setMpVisitorId(visitor.getMpVisitorId());
            ticket.setPayload(payload);
            ticket.setTicketStatus(QrcodeTicketStatus.UNUSED);
            ticket.setGenerateTime(generateTime);
            ticket.setExpireTime(expireTime);
            qrcodeTicketMapper.insert(ticket);
            qrcodeTicketMapper.updateById(ticket);
            existingTicketMap.put(visitor.getMpVisitorId(), ticket);
            createdCount++;
            log.info(
                    "{} raw-payload orderId={} mpVisitorId={} ticketUuid={} payloadLength={}",
                    "{} raw-payload orderId={} mpVisitorId={} ticketId={} ticketUuid={} payloadLength={}",
                    IntegrationConstant.HIKVISION_QRCODE_EVENT,
                    order.getId(),
                    visitor.getMpVisitorId(),
                    ticket.getId(),
                    ticketUuid,
                    payload == null ? 0 : payload.length());
        }
backend/src/main/java/com/sinata/appointment/service/StaffManageApplicationService.java
@@ -99,9 +99,9 @@
        Path dir = resolveAvatarUploadDir();
        String fileName = UUID.randomUUID().toString().replace("-", "") + ext;
        Path target = dir.resolve(fileName);
        try {
        try (InputStream in = file.getInputStream()) {
            Files.createDirectories(dir);
            Files.copy(file.getInputStream(), target, StandardCopyOption.REPLACE_EXISTING);
            Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException ex) {
            throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "头像上传失败");
        }
@@ -156,7 +156,7 @@
                q.notIn(MpUser::getId, inGroup);
            }
        }
        q.orderByDesc(MpUser::getCreateTime);
        q.orderByAsc(MpUser::getNickName).orderByAsc(MpUser::getId);
        Page<MpUser> p = mpUserMapper.selectPage(new Page<>(page, size), q);
        Set<Long> deptIds = new HashSet<>();
        Set<Long> userIds = new HashSet<>();
backend/src/main/java/com/sinata/appointment/service/VisitorUserManageApplicationService.java
@@ -1,6 +1,8 @@
package com.sinata.appointment.service;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -20,7 +22,8 @@
import com.sinata.appointment.util.MobileMaskUtil;
import com.sinata.appointment.util.PhoneNormalize;
import com.sinata.appointment.util.UuidAssert;
import java.io.ByteArrayOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -62,27 +65,58 @@
        return result.convert(user -> toListVo(user, bookingCountMap));
    }
    public byte[] exportExcel(
    public void exportExcel(
            HttpServletResponse response,
            String userName,
            String mobile,
            String companyName,
            LocalDate registerStartDate,
            LocalDate registerEndDate,
            Integer status) {
        List<MpUser> users =
                mpUserMapper.selectList(
            Integer status)
            throws IOException {
        prepareExcelResponse(response, "visitor-users.xlsx");
        long pageNo = 1L;
        long pageSize = 500L;
        boolean wroteAnyPage = false;
        try (ExcelWriter writer =
                EasyExcel.write(response.getOutputStream(), VisitorUserExportRow.class).build()) {
            WriteSheet sheet = EasyExcel.writerSheet("访客信息").build();
            while (true) {
                IPage<MpUser> page =
                        mpUserMapper.selectPage(
                                new Page<>(pageNo, pageSize),
                        buildVisitorQuery(
                                userName, mobile, companyName, registerStartDate, registerEndDate, status));
                                        userName,
                                        mobile,
                                        companyName,
                                        registerStartDate,
                                        registerEndDate,
                                        status));
                List<MpUser> users = page.getRecords();
                if (users == null || users.isEmpty()) {
                    break;
                }
        List<Long> userIds = users.stream().map(MpUser::getId).filter(Objects::nonNull).toList();
        Map<Long, Long> bookingCountMap = loadBookingCountMap(userIds);
        List<VisitorUserExportRow> rows =
                users.stream().map(user -> toExportRow(user, bookingCountMap)).toList();
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            EasyExcel.write(bos, VisitorUserExportRow.class).sheet("访客信息").doWrite(rows);
            return bos.toByteArray();
        } catch (Exception e) {
            return new byte[0];
                writer.write(rows, sheet);
                wroteAnyPage = true;
                if (pageNo * pageSize >= page.getTotal()) {
                    break;
        }
                pageNo++;
            }
            if (!wroteAnyPage) {
                writer.write(List.of(), sheet);
            }
        }
    }
    private void prepareExcelResponse(HttpServletResponse response, String filename) {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
    }
    public VisitorUserDetailVo detail(Long id, String uuid) {
yuyueMiniProgram/src/pages/appointment/index.vue
@@ -1563,7 +1563,7 @@
                  <view v-if="formData.vehicleEntry" class="w-[16rpx] h-[16rpx] rounded-full bg-[#1677FF]">
                  </view>
                </view>
                <text class="ml-[12rpx] text-[28rpx]" :class="formData.vehicleEntry ? 'text-[#333333]' : 'text-[#999999]'">车辆进场</text>
                <text class="ml-[12rpx] text-[28rpx]" :class="formData.vehicleEntry ? 'text-[#333333]' : 'text-[#999999]'">接送货</text>
              </view>
            </view>
          </view>
yuyueMiniProgram/src/pages/index/index.vue
@@ -14,6 +14,10 @@
  qrCodes: VisitorCodeDisplayTicket[]
}
function wait(ms: number) {
  return new Promise(resolve => setTimeout(resolve, ms))
}
const systemInfo = uni.getSystemInfoSync()
const { safeAreaInsets } = systemInfo
const parkFlowBottomPx = Math.max(24, Math.round(systemInfo.windowHeight / 8))
@@ -72,9 +76,9 @@
)
const visitorCodeCardMinHeightClass = computed(() => {
  if (hasLongAccessAreaText.value) {
    return currentOrderHasMultipleTickets.value ? 'min-h-[900rpx]' : 'min-h-[860rpx]'
    return currentOrderHasMultipleTickets.value ? 'min-h-[940rpx]' : 'min-h-[900rpx]'
  }
  return currentOrderHasMultipleTickets.value ? 'min-h-[804rpx]' : 'min-h-[760rpx]'
  return currentOrderHasMultipleTickets.value ? 'min-h-[844rpx]' : 'min-h-[800rpx]'
})
const currentTicket = computed(() => {
  if (!currentOrder.value) {
@@ -259,7 +263,7 @@
    errorCorrectionLevel: 'M',
    margin: 1,
  })
  const size = 300
  const size = 400
  const moduleCount = qr.modules.size
  const tileSize = size / moduleCount
  const canvasId = 'visitorQrCanvas'
@@ -285,6 +289,7 @@
    }
    context.draw(false, () => {
      void wait(500).then(() => {
      uni.canvasToTempFilePath({
        canvasId,
        width: size,
@@ -293,6 +298,7 @@
        destHeight: size * 2,
        success: res => resolve(res.tempFilePath),
        fail: reject,
        })
      })
    })
  })
@@ -671,7 +677,7 @@
                />
                <image
                  :src="currentOrder?.orderId === order.orderId ? currentTicket?.qrCode : order.qrCodes[0]?.qrCode"
                  class="h-[360rpx] w-[360rpx]"
                  class="h-[400rpx] w-[400rpx]"
                  mode="aspectFit"
                />
                <image
@@ -795,8 +801,8 @@
  position: fixed;
  left: -9999px;
  top: -9999px;
  width: 300px;
  height: 300px;
  width: 400px;
  height: 400px;
  pointer-events: none;
}
</style>
yuyueMiniProgram/src/pages/visitorCode/index.vue
@@ -7,6 +7,10 @@
  qrCode: string
}
function wait(ms: number) {
  return new Promise(resolve => setTimeout(resolve, ms))
}
definePage({
  name: 'visitorCode',
  layout: 'default',
@@ -56,7 +60,7 @@
    errorCorrectionLevel: 'M',
    margin: 1,
  })
  const size = 400
  const size = 448
  const moduleCount = qr.modules.size
  const tileSize = size / moduleCount
  const canvasId = 'sharedVisitorQrCanvas'
@@ -82,6 +86,7 @@
    }
    context.draw(false, () => {
      void wait(500).then(() => {
      uni.canvasToTempFilePath({
        canvasId,
        width: size,
@@ -90,6 +95,7 @@
        destHeight: size * 2,
        success: res => resolve(res.tempFilePath),
        fail: reject,
        })
      })
    })
  })
@@ -332,8 +338,8 @@
  position: fixed;
  left: -9999px;
  top: -9999px;
  width: 400px;
  height: 400px;
  width: 448px;
  height: 448px;
  opacity: 0;
  pointer-events: none;
}
yuyueMiniProgram/src/pages/visitorCodeShare/index.vue
@@ -7,6 +7,10 @@
  qrCode: string
}
function wait(ms: number) {
  return new Promise(resolve => setTimeout(resolve, ms))
}
interface VisitorCodeDisplayOrder extends Omit<MiniMyVisitorCodeOrderItem, 'qrCodes'> {
  qrCodes: VisitorCodeDisplayTicket[]
}
@@ -19,7 +23,6 @@
  },
})
const router = useRouter()
const sysInfo = uni.getSystemInfoSync()
const statusBarHeight = sysInfo.statusBarHeight || 0
const navBarHeight = 44
@@ -30,7 +33,22 @@
const currentTicket = computed(() => order.value?.qrCodes?.[0] || null)
function handleBack() {
  router.back()
  const pages = getCurrentPages()
  if (pages.length > 1) {
    uni.navigateBack({
      delta: 1,
      fail: () => {
        uni.switchTab({ url: '/pages/index/index' })
      },
    })
    return
  }
  uni.switchTab({
    url: '/pages/index/index',
    fail: () => {
      uni.reLaunch({ url: '/pages/index/index' })
    },
  })
}
function ticketTimeText() {
@@ -49,7 +67,7 @@
    errorCorrectionLevel: 'M',
    margin: 1,
  })
  const size = 400
  const size = 448
  const moduleCount = qr.modules.size
  const tileSize = size / moduleCount
  const canvasId = 'sharedSingleVisitorQrCanvas'
@@ -75,6 +93,7 @@
    }
    context.draw(false, () => {
      void wait(500).then(() => {
      uni.canvasToTempFilePath({
        canvasId,
        width: size,
@@ -83,6 +102,7 @@
        destHeight: size * 2,
        success: res => resolve(res.tempFilePath),
        fail: reject,
        })
      })
    })
  })
@@ -286,8 +306,8 @@
  position: fixed;
  left: -9999px;
  top: -9999px;
  width: 400px;
  height: 400px;
  width: 448px;
  height: 448px;
  opacity: 0;
  pointer-events: none;
}