shinnosuke
2026-09-27 35c0810b268661c79f776fe922f7e3834cba4186
rencaika_merchant/pages/product-detail/product-detail.uvue
@@ -51,14 +51,15 @@
            <view class="change-cover" @tap="addCheckItem"><text class="change-plus">+</text><text>新增检查项目</text></view>
         </view>
         <view v-if="!isHealthCheckMerchant" class="form-card detail-card">
         <!-- 图文详情:所有商家都能传(体检医院的商品详情是下面的检查项目表,这里只放图片,不放富文本)。 -->
         <view class="form-card detail-card">
            <text class="section-title">图文详情</text>
            <view class="gallery-row">
               <image v-for="(imagePath, index) in detailImages" :key="index" class="detail-thumb" :src="imagePath" mode="aspectFill"></image>
               <view v-if="detailImages.length < productDetailImageMaxCount" class="add-image" @tap="addDetailImage"><text>+</text></view>
            </view>
            <text class="image-rule">已选择 {{detailImages.length}}/{{productDetailImageMaxCount}} 张</text>
            <textarea class="detail-textarea" maxlength="1000" v-model="detailText" placeholder="请输入" placeholder-class="input-placeholder" :auto-height="false"></textarea>
            <textarea v-if="!isHealthCheckMerchant" class="detail-textarea" maxlength="1000" v-model="detailText" placeholder="请输入" placeholder-class="input-placeholder" :auto-height="false"></textarea>
         </view>
         <view v-if="!isHealthCheckMerchant" class="form-card notice-card">
@@ -155,6 +156,20 @@
      itemError.value = ''
   }
   /** 判断 detailText 是不是体检检查项目([{name,meaning,gender}] 的 JSON 数组)。 */
   const looksLikeHealthCheckItems = (text: string): boolean => {
      const source = text == null ? '' : ('' + text).trim()
      if (source.length == 0 || source.indexOf('[') != 0) return false
      try {
         const parsed = JSON.parse(source)
         if (!(parsed instanceof Array) || parsed.length == 0) return false
         const first = parsed[0] as any
         return first != null && first.name != null && first.meaning != null && first.gender != null
      } catch (error) {
         return false
      }
   }
   /** 检查项目在后台团单里存成 detailText(JSON 数组);坏数据或空值按一行空项目处理。 */
   const parseCheckItems = (text: string): any[] => {
      const items: any[] = []
@@ -215,7 +230,9 @@
      otherRules.value = detail.purchaseNotice != null && detail.purchaseNotice.otherRules != null ? '' + detail.purchaseNotice.otherRules : ''
      coverImage.value = detail.coverUrl.length > 0 ? detail.coverUrl : '/static/product-sauerkraut-fish.jpg'
      detailImages.value = detail.productImages.length > 0 ? detail.productImages.slice() : []
      isHealthCheckMerchant.value = detail.merchantTypeCode == 'medical'
      // 体检医院优先看商家类型;后端还没带上 merchantTypeCode 时,用「商品详情就是检查项目 JSON」兜底识别,
      // 否则页面会退回通用表单,把 detail_text 的原始 JSON 直接摊在文本框里给商户看。
      isHealthCheckMerchant.value = detail.merchantTypeCode == 'medical' || looksLikeHealthCheckItems(detail.detailText)
      noticeValidPeriod.value = detail.purchaseNotice != null && detail.purchaseNotice.validPeriod != null ? '' + detail.purchaseNotice.validPeriod : ''
      checkItems.value = parseCheckItems(detail.detailText)
      const terms = parseReserveTerms(noticeValidPeriod.value)
@@ -307,6 +324,18 @@
   const clearNameError = () => { nameError.value = '' }
   const clearAmountError = () => { amountError.value = '' }
   // 体检医院「可预约时间」的两个日期选择器:模板上挂的 @change 一直没有对应处理函数,
   // 选完日期不写回表单,表现就是「选了没生效」。
   const onReserveStartChange = (event: any) => {
      reserveStartTerm.value = event.detail.value as string
      reserveError.value = ''
   }
   const onReserveEndChange = (event: any) => {
      reserveEndTerm.value = event.detail.value as string
      reserveError.value = ''
   }
   const changeCover = () => {
      coverError.value = ''
      uni.chooseImage({