longz
2026-06-29 9d4cbff7464ebfd7803c2ce53ded49d0f73fe011
yuyueMiniProgram/src/pages/appointment/index.vue
@@ -28,11 +28,9 @@
const router = useRouter()
const appointmentAssetBase = '/static/appointment'
const defaultPlateTag = '新能源'
const VISIT_REASON_CODE_OTHER = 'OTHER'
const VISIT_REASON_NAME_OTHER = '其他'
const VISIT_TIME_MODE_SINGLE = 'SINGLE'
const VISIT_TIME_MODE_MULTI = 'MULTI'
const APPROVAL_RESULT_SUBSCRIBE_TEMPLATE_ID = '4FQovCBNFM7rth42AWHTusHajO788eT9VpplSN4ZG2s'
const APPROVAL_RESULT_SUBSCRIBE_TEMPLATE_ID = 'rIJpfN51_q818RTpJAqTe5ssTwWCHnxDM5rpq64uuKM'
const TIME_SLOT_LABEL_TO_CODE = {
  '全天00:00-23:59': 'ALL_DAY',
  '上午00:00-11:59': 'AM',
@@ -95,13 +93,7 @@
const isVisitReasonRequired = computed(() => isFieldRequired(APPOINTMENT_FIELD_CODES.VISIT_REASON, true))
const isRespondentRequired = computed(() => isFieldRequired(APPOINTMENT_FIELD_CODES.RESPONDENT, true))
const isVisitTimeRequired = computed(() => isFieldRequired(APPOINTMENT_FIELD_CODES.VISIT_START_TIME, true))
const showCustomReasonInput = computed(() => {
  if (!selectedVisitReason.value) {
    return false
  }
  return selectedVisitReason.value.reasonCode === VISIT_REASON_CODE_OTHER
    || selectedVisitReason.value.reasonName === VISIT_REASON_NAME_OTHER
})
const showCustomReasonInput = computed(() => Boolean(selectedVisitReason.value))
const showReasonPicker = ref(false)
const appointmentIcons = {
  male: `${appointmentAssetBase}/man.png`,
@@ -921,9 +913,26 @@
  }
}
function sortVisitReasons(reasons: MiniVisitReasonItem[]) {
  return [...reasons].sort((left, right) => {
    const leftIsOther = left.reasonCode === 'OTHER' || left.reasonName === '其他'
    const rightIsOther = right.reasonCode === 'OTHER' || right.reasonName === '其他'
    if (leftIsOther !== rightIsOther) {
      return leftIsOther ? 1 : -1
    }
    const leftOrder = left.sortOrder ?? Number.MAX_SAFE_INTEGER
    const rightOrder = right.sortOrder ?? Number.MAX_SAFE_INTEGER
    if (leftOrder !== rightOrder) {
      return leftOrder - rightOrder
    }
    return left.id - right.id
  })
}
async function loadVisitReasons() {
  try {
    visitReasons.value = await fetchVisitReasons()
    visitReasons.value = sortVisitReasons(await fetchVisitReasons())
    if (selectedVisitReason.value) {
      const nextReason = visitReasons.value.find(item => item.reasonCode === selectedVisitReason.value?.reasonCode) || null
      selectedVisitReason.value = nextReason
@@ -975,9 +984,6 @@
  const target = visitReasons.value.find(item => item.reasonName === reasonName) || null
  selectedVisitReason.value = target
  formData.reason = target?.reasonName || ''
  if (!showCustomReasonInput.value) {
    customVisitReason.value = ''
  }
}
function resolveVisitTimeMode() {
@@ -1026,6 +1032,31 @@
  }
}
async function prefillDefaultVisitorForCreate() {
  if (rebookOrderId.value || rebookOrderUuid.value || formData.visitors.length > 0) {
    return
  }
  try {
    const visitors = await fetchMyVisitors()
    const defaultVisitor = visitors.find(item => item.isDefault)
    if (!defaultVisitor) {
      return
    }
    if (defaultVisitor.missingFieldCodes?.length) {
      uni.showToast({
        title: '默认来访人信息不完善,请先完善后再预约',
        icon: 'none',
        duration: 3000,
      })
      return
    }
    formData.visitors = [mapVisitorFromCurrentList(defaultVisitor)]
  }
  catch (error) {
    console.warn('prefill default visitor failed', error)
  }
}
async function applyRebookPrefill(detailData: MiniMyAppointmentDetailItem, currentVisitors: MiniVisitorListItem[]) {
  const warnings: string[] = []
  resetForm()
@@ -1051,9 +1082,7 @@
      warnings.push('原拜访事由不可用,请重新选择')
    }
  }
  customVisitReason.value = detailData.visitReasonCode === VISIT_REASON_CODE_OTHER
    ? detailData.visitReasonOther || ''
    : ''
  customVisitReason.value = detailData.visitReasonOther || ''
  const currentVisitorMap = new Map(currentVisitors.map(item => [item.id, item]))
  const restoredVisitors: AppointmentVisitor[] = []
@@ -1124,11 +1153,12 @@
  if (!visitReasonCode) {
    throw new Error('请选择拜访事由')
  }
  if (visitReasonCode === VISIT_REASON_CODE_OTHER && !customVisitReason.value.trim()) {
    throw new Error('请输入具体来访事由')
  const visitReasonOther = customVisitReason.value.trim()
  if (!visitReasonOther) {
    throw new Error('请输入事由说明')
  }
  if (visitReasonCode === VISIT_REASON_CODE_OTHER && customVisitReason.value.trim().length > 15) {
    throw new Error('具体来访事由不能超过15个字')
  if (visitReasonOther.length > 15) {
    throw new Error('事由说明不能超过15个字')
  }
  if (!formData.visitors.length) {
    throw new Error('请选择来访人员')
@@ -1138,15 +1168,12 @@
  const payload: MiniAppointmentCreateRequest = {
    visitedUserId: visitedUser.id,
    visitReasonCode,
    visitReasonOther,
    visitTimeMode,
    personEntry: true,
    visitorIds: formData.visitors.map(item => item.id),
    vehicleEntry: formData.vehicleEntry,
  }
  if (visitReasonCode === VISIT_REASON_CODE_OTHER) {
    payload.visitReasonOther = customVisitReason.value.trim()
  }
  if (visitTimeMode === VISIT_TIME_MODE_SINGLE) {
    if (!formData.visitTimeSingle) {
      throw new Error('请选择拜访时间')
@@ -1288,7 +1315,13 @@
    loadFieldConfigs(),
    loadBookingSettings(),
    loadVisitReasons(),
  ]).then(() => prefillFromRebookDetail())
  ]).then(async () => {
    if (rebookOrderId.value && rebookOrderUuid.value) {
      await prefillFromRebookDetail()
      return
    }
    await prefillDefaultVisitorForCreate()
  })
})
onUnload(() => {
@@ -1335,7 +1368,7 @@
        class="mb-[48rpx] rounded-[16rpx] bg-white pl-[32rpx] pr-[12rpx] py-[8rpx] shadow-[0px_4px_8px_0px_rgba(6,47,108,0.1608)]">
        <wd-form :model="formData">
          <!-- 被访人 -->
          <wd-input v-model="formData.interviewee" placeholder="请输入要接见谁" align="right" custom-class="custom-cell"
          <wd-input v-model="formData.interviewee" placeholder="请输入要拜访谁" align="right" custom-class="custom-cell"
            placeholder-class="custom-placeholder">
            <template #label>
              <view class="inline-flex items-start">
@@ -1380,7 +1413,7 @@
              v-model.trim="customVisitReason"
              class="min-h-[160rpx] w-full rounded-[16rpx] bg-[#F8FAFD] px-[24rpx] py-[20rpx] text-[26rpx] text-[#333333] box-border"
              maxlength="15"
              placeholder="请输入具体来访事由"
              placeholder="请输入事由说明"
              placeholder-class="text-[#B3B3B3]"
            />
          </view>
@@ -1600,10 +1633,10 @@
              class="visitor-row flex items-center justify-between px-[8rpx] py-[20rpx]"
            >
              <view class="flex items-center min-w-0 flex-1">
                <text class="w-[72rpx] shrink-0 text-[28rpx] text-[#333333]">
                <text class="w-[96rpx] shrink-0 truncate text-[28rpx] text-[#333333]">
                  {{ visitor.name }}
                </text>
                <view class="ml-[20rpx] min-w-0 flex-1">
                <view class="ml-[12rpx] min-w-0 flex-1">
                  <view class="flex items-center min-w-0">
                    <image :src="getVisitorGenderIcon(visitor.gender)" class="h-[32rpx] w-[32rpx] shrink-0" />
                    <text class="ml-[10rpx] truncate text-[24rpx] text-[#999999]">
@@ -1618,10 +1651,10 @@
                  </view>
                </view>
              </view>
              <view class="ml-[16rpx] flex items-center shrink-0">
              <view class="ml-[8rpx] flex items-center shrink-0">
                <view
                  v-if="visitor.badge"
                  class="rounded-full border border-[#1677FF] border-solid px-[20rpx] py-[8rpx] text-[24rpx] text-[#1677FF]"
                  class="rounded-full border border-[#1677FF] border-solid px-[16rpx] py-[8rpx] text-[24rpx] text-[#1677FF]"
                >
                  {{ visitor.badge }}
                </view>