shinnosuke
2026-08-24 01070a7081891d508255d0447ee118afa4b1340e
又又更新
5个文件已修改
120 ■■■■■ 已修改文件
rencaika/pages/merchant/merchant.uvue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika/pages/product-detail/product-detail.uvue 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/style.css 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/views/MerchantCreateView.vue 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/portal/PortalService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika/pages/merchant/merchant.uvue
@@ -35,7 +35,7 @@
                        <text class="merchant-title">{{merchantName}}</text>
                        <view class="merchant-tags"><text v-if="merchantHasCoupon" class="coupon-tag">券</text><text v-if="merchantDiscount != ''" class="discount-tag">{{merchantDiscount}}</text><text v-if="isHotelMerchant && merchantConsumeDiscount != ''" class="discount-tag">{{merchantConsumeDiscount}}</text></view>
                    </view>
                    <view class="service-tags"><text v-if="!isParkingMerchant && !isMedicalMerchant">{{merchantServiceLabel}}</text><text>{{applicableCards}}</text></view>
                    <view v-if="merchantServiceLabel != '' || applicableCards != ''" class="service-tags"><text v-if="!isParkingMerchant && !isMedicalMerchant && merchantServiceLabel != ''">{{merchantServiceLabel}}</text><text v-if="applicableCards != ''">{{applicableCards}}</text></view>
                    <view v-if="!isParkingMerchant && merchantType != 'apartment'" class="merchant-address-row">
                        <image class="address-icon" src="/static/benefits-assets/map-pin.svg"></image>
                        <text>{{merchantAddress}}</text>
@@ -301,7 +301,7 @@
            if (disabledMatched == null) disabledMatched = item
        }
        if (matched != null) {
            const tag = matched.tag != null && matched.tag != '' ? matched.tag as string : cardLevel
            const tag = matched.tag != null && matched.tag != '' ? matched.tag as string : ''
            if (tag != '') applicableCards.value = tag
            const configuredDetail = rightDetail(matched)
            merchantRightsText.value = configuredDetail != null && configuredDetail != ''
@@ -310,22 +310,20 @@
            return
        }
        if (rights.length > 0) {
            const tag = disabledMatched != null && disabledMatched.tag != null && disabledMatched.tag != '' ? disabledMatched.tag as string : cardLevel
            applicableCards.value = tag != '' ? tag : '暂未开通'
            const tag = disabledMatched != null && disabledMatched.tag != null && disabledMatched.tag != '' ? disabledMatched.tag as string : ''
            applicableCards.value = tag
            merchantRightsText.value = disabledMatched != null ? '当前人才卡暂未开通该权益' : '当前人才卡暂未配置该权益'
            return
        }
        if (disabledMatched != null) {
            const tag = disabledMatched.tag != null && disabledMatched.tag != '' ? disabledMatched.tag as string : cardLevel
            applicableCards.value = tag != '' ? tag : '暂未开通'
            const tag = disabledMatched.tag != null && disabledMatched.tag != '' ? disabledMatched.tag as string : ''
            applicableCards.value = tag
            merchantRightsText.value = '当前人才卡暂未开通该权益'
            return
        }
        merchantRightsText.value = data != null && data.rightsText != null && data.rightsText != '' ? data.rightsText as string : '当前人才卡暂未配置该权益'
        if (data != null && data.applicableCards != null && data.applicableCards != '') {
            applicableCards.value = data.applicableCards as string
        } else if (cardLevel != '') {
            applicableCards.value = cardLevel
        }
    }
@@ -607,8 +605,9 @@
            merchantHasCoupon.value = data != null && (data.couponAvailable === true || Number(data.couponAvailable) == 1)
            const discountText = data != null && data.discountText != null ? data.discountText as string : ''
            merchantDiscount.value = discountText == '人才专享' ? '' : discountText
            merchantServiceLabel.value = data != null && data.serviceLabel != null && data.serviceLabel != '' ? data.serviceLabel as string : merchantServiceLabel.value
            applicableCards.value = data != null && data.applicableCards != null && data.applicableCards != '' ? data.applicableCards as string : applicableCards.value
            const configuredTag = data != null && data.rightsTag != null ? '' + data.rightsTag : ''
            merchantServiceLabel.value = configuredTag != '' && data != null && data.serviceLabel != null ? data.serviceLabel as string : ''
            applicableCards.value = data != null && data.applicableCards != null ? data.applicableCards as string : ''
            merchantRightsText.value = data != null && data.rightsText != null && data.rightsText != '' ? data.rightsText as string : merchantRightsText.value
            usageInstructions.value = data != null && data.usageInstructions != null && data.usageInstructions != '' ? data.usageInstructions as string : usageInstructions.value
            if (data != null && data.cardLevel != null && data.cardLevel != '') currentCardLevel.value = data.cardLevel as string
rencaika/pages/product-detail/product-detail.uvue
@@ -15,10 +15,6 @@
                <image class="hero-image" :src="productImage" mode="aspectFill" @error="replaceProductImage"></image>
                <view class="hero-shade"></view>
            </view>
            <view v-if="detailImages.length > 0" class="product-image-gallery">
                <image v-for="(imageUrl, index) in detailImages" :key="index" :src="imageUrl" mode="widthFix" @error="replaceDetailImage(index)"></image>
            </view>
            <view class="product-summary">
                <view class="price-line"><text class="discount-label">折扣价</text><text class="price-symbol">¥</text><text class="price">{{productPrice}}</text><text class="old-price">¥{{productOriginalPrice}}</text><view class="summary-tags"><text v-if="productHasCoupon" class="coupon-tag">券</text><text v-if="productDiscount != ''" class="discount-tag">{{productDiscount}}</text></view></view>
                <text class="product-name">{{productName}}</text>
@@ -75,7 +71,6 @@
    const productSoldOut = ref(false)
    // 后台分别返回图片数组与文字结构,不使用富文本 HTML。
    const detailImages = ref<any[]>([])
    const detailText = ref('')
    // 购买须知固定只有四项,后台只返回四项各自的内容文本。
@@ -134,8 +129,8 @@
            else if (original > 0 && sale < original) productDiscount.value = '' + Math.round(sale / original * 100) / 10 + '折'
            productAvailable.value = data == null || data.available !== false
            productSoldOut.value = data != null && (data.soldOut === true || Number(data.stock) == 0)
            detailText.value = data != null && data.detailText != null ? data.detailText as string : ''
            detailImages.value = getProductImages(data != null ? data.productImages : null)
            const rawDetailText = data != null && data.detailText != null ? data.detailText as string : ''
            detailText.value = stripDetailMarkup(rawDetailText)
            purchaseNotices.value = buildPurchaseNotices(data != null ? data.purchaseNotice : null)
        }, () => {})
    }
@@ -166,27 +161,12 @@
        return next
    }
    const getProductImages = (value: any): string[] => {
        let images: any = value
        if (typeof images == 'string') {
            try {
                images = JSON.parse(images)
            } catch (_) {
                return []
            }
        }
        if (!(images instanceof Array)) return []
        const result: string[] = []
        for (let index = 0; index < images.length; index++) {
            const image = images[index]
            if (typeof image == 'string' && image.length > 0) result.push(resolveApiUrl(image))
        }
        return result
    const stripDetailMarkup = (value: string): string => {
        if (value == '') return ''
        let text = value.replace(/<img[^>]*>/gi, '')
        text = text.replace(/<br\s*\/?>/gi, '\n').replace(/<\/p>/gi, '\n').replace(/<[^>]+>/g, '')
        return text.replace(/&nbsp;/gi, ' ').replace(/&amp;/gi, '&').replace(/&lt;/gi, '<').replace(/&gt;/gi, '>').trim()
    }
    const replaceDetailImage = (index: number) => {
        if (index >= 0 && index < detailImages.value.length) detailImages.value[index] = fallbackImage
    }
    const goBack = () => safeBack('/pages/benefits/benefits')
    const handleScroll = (event: any) => {
        const shouldShow = event.detail.scrollTop > 64
@@ -246,8 +226,6 @@
    .product-status { display: block; margin-top: 11rpx; color: #E45D3C; font-size: 24rpx; }
    .detail-card, .notice-card { margin: 16rpx 25rpx 0; padding: 27rpx 27rpx 30rpx; border-radius: 15rpx; background-color: #FFFFFF; }
    .card-title { display: block; margin-bottom: 23rpx; color: #55575B; font-size: 30rpx; font-weight: 700; }
    .product-image-gallery { display: flex; padding: 16rpx 25rpx 0; box-sizing: border-box; background-color: #F7F7F7; flex-direction: column; }
    .product-image-gallery image { display: block; width: 100%; margin-bottom: 16rpx; border-radius: 12rpx; }
    .detail-description { display: block; color: #626870; font-size: 25rpx; line-height: 43rpx; white-space: pre-wrap; }
    .notice-item { display: flex; min-height: 105rpx; padding: 19rpx 0; border-bottom-width: 1rpx; border-bottom-style: solid; border-bottom-color: #EEEEEE; flex-direction: row; align-items: flex-start; }
    .notice-item:last-child { border-bottom-width: 0; }
talent-card-admin/src/style.css
@@ -1076,6 +1076,12 @@
  width: 180px;
}
.merchant-rights-required {
  margin-right: 3px;
  color: #f56c6c;
  font-style: normal;
}
.merchant-rights-level-row > .el-switch {
  justify-self: start;
  margin-left: -17px;
@@ -2199,14 +2205,14 @@
}
.coupon-amount-control .el-input {
  width: 184px;
  width: 200px;
}
.merchant-coupon-edit-form .coupon-amount-control .el-input__wrapper {
  min-height: 30px;
  min-height: 28px;
  padding: 1px 11px;
  background: #f5f6f8;
  box-shadow: none;
  background: #ffffff;
  box-shadow: 0 0 0 1px #d9dee8 inset;
}
.coupon-amount-control > span {
talent-card-admin/src/views/MerchantCreateView.vue
@@ -991,6 +991,7 @@
let locationMap: any
let locationMarker: any
let amapApi: any
let browserLocationRequest: Promise<{ longitude: number; latitude: number } | undefined> | undefined
async function loadAMap() {
  if (amapApi) return amapApi
@@ -1076,6 +1077,7 @@
  activeFuelStore.value = row
  pendingLongitude.value = String(row.longitude || '')
  pendingLatitude.value = String(row.latitude || '')
  browserLocationRequest = selectedCoordinates() ? undefined : requestBrowserLocation()
  locationDialogVisible.value = true
  locationSearch.value = row.address || ''
  await initializeLocationMap()
@@ -1087,6 +1089,7 @@
  activeParkingAddress.value = row
  pendingLongitude.value = String(row.longitude || '')
  pendingLatitude.value = String(row.latitude || '')
  browserLocationRequest = selectedCoordinates() ? undefined : requestBrowserLocation()
  locationDialogVisible.value = true
  locationSearch.value = row.address || ''
  await initializeLocationMap()
@@ -1125,6 +1128,7 @@
  activeSchoolAddress.value = editingSchool
  pendingLongitude.value = String(editingSchool.longitude || '')
  pendingLatitude.value = String(editingSchool.latitude || '')
  browserLocationRequest = selectedCoordinates() ? undefined : requestBrowserLocation()
  locationDialogVisible.value = true
  locationSearch.value = editingSchool.address || ''
  await initializeLocationMap()
@@ -1199,7 +1203,39 @@
  if (!longitudeText || !latitudeText) return undefined
  const longitude = Number(longitudeText)
  const latitude = Number(latitudeText)
  // The API uses zero for an unset coordinate. Treat it as empty instead of
  // opening the map at 0,0.
  if (longitude === 0 || latitude === 0) return undefined
  return Number.isFinite(longitude) && Number.isFinite(latitude) ? { longitude, latitude } : undefined
}
function requestBrowserLocation(): Promise<{ longitude: number; latitude: number } | undefined> {
  return new Promise((resolve) => {
    const geolocation = window.navigator?.geolocation
    if (!geolocation) {
      ElMessage.info('当前浏览器不支持定位,请直接搜索或点击地图标记')
      resolve(undefined)
      return
    }
    const hostname = window.location.hostname
    if (!window.isSecureContext && hostname !== 'localhost' && hostname !== '127.0.0.1') {
      ElMessage.warning('浏览器定位需要使用 localhost 或 HTTPS 地址访问')
      resolve(undefined)
      return
    }
    geolocation.getCurrentPosition(
      (position) => {
        const longitude = position.coords.longitude
        const latitude = position.coords.latitude
        resolve(Number.isFinite(longitude) && Number.isFinite(latitude) ? { longitude, latitude } : undefined)
      },
      (error) => {
        if (error.code === error.PERMISSION_DENIED) ElMessage.info('未获得浏览器定位权限,请直接搜索或点击地图标记')
        resolve(undefined)
      },
      { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 },
    )
  })
}
function setMapMarker(latitude: number, longitude: number, center = true) {
@@ -1238,6 +1274,13 @@
      reverseGeocodeLocation(latitude, longitude)
    })
    if (selected) setMapMarker(initial.latitude, initial.longitude, false)
    if (!selected) {
      const browserPosition = await (browserLocationRequest || requestBrowserLocation())
      browserLocationRequest = undefined
      if (browserPosition && locationMap && !locationMarker && !selectedCoordinates()) {
        setMapMarker(browserPosition.latitude, browserPosition.longitude)
      }
    }
    window.setTimeout(() => locationMap?.resize(), 120)
  } catch (error) {
    console.error('Failed to initialize AMap', error)
@@ -1311,6 +1354,7 @@
  locationMap?.destroy()
  locationMap = undefined
  locationMarker = undefined
  browserLocationRequest = undefined
  locationSearchResults.value = []
}
@@ -1320,6 +1364,7 @@
  activeFuelStore.value = undefined
  pendingLongitude.value = String(form.longitude || '')
  pendingLatitude.value = String(form.latitude || '')
  browserLocationRequest = selectedCoordinates() ? undefined : requestBrowserLocation()
  locationDialogVisible.value = true
  locationSearch.value = form.address || ''
  await initializeLocationMap()
talent-card-server/src/main/java/com/talentcard/server/business/portal/PortalService.java
@@ -672,7 +672,7 @@
        if (configuredRight != null) {
            merchant.put("rightsTag", textOrDefault(configuredRight.get("tag"), ""));
            merchant.put("rightsText", textOrDefault(configuredRight.get("detail"), textOrDefault(merchant.get("rightsText"), "")));
            merchant.put("applicableCards", textOrDefault(configuredRight.get("tag"), cardLevelText(cardLevel) + "可用"));
            merchant.put("applicableCards", textOrDefault(configuredRight.get("tag"), ""));
            merchant.put("rightsStatus", "AVAILABLE");
            freeVoucherEnabled = booleanValue(configuredRight.get("freeVoucherEnabled"));
            freeVoucherTimes = textOrDefault(configuredRight.get("freeVoucherTimes"), "");
@@ -685,12 +685,12 @@
        } else if (levelRight != null) {
            merchant.put("rightsTag", textOrDefault(levelRight.get("tag"), ""));
            merchant.put("rightsText", "当前人才卡暂未开通该权益");
            merchant.put("applicableCards", textOrDefault(levelRight.get("tag"), cardLevelText(cardLevel) + "可用"));
            merchant.put("applicableCards", textOrDefault(levelRight.get("tag"), ""));
            merchant.put("rightsStatus", "DISABLED");
        } else if (!rightsConfigs.isEmpty()) {
            merchant.put("rightsTag", "");
            merchant.put("rightsText", "当前人才卡暂未配置该权益");
            merchant.put("applicableCards", cardLevelText(cardLevel) + "可用");
            merchant.put("applicableCards", "");
            merchant.put("rightsStatus", "MISSING");
        } else {
            merchant.put("rightsStatus", "UNCONFIGURED");