| rencaika/pages/merchant/merchant.uvue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| rencaika/pages/product-detail/product-detail.uvue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| talent-card-admin/src/style.css | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| talent-card-admin/src/views/MerchantCreateView.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| talent-card-server/src/main/java/com/talentcard/server/business/portal/PortalService.java | ●●●●● 补丁 | 查看 | 原始文档 | 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(/ /gi, ' ').replace(/&/gi, '&').replace(/</gi, '<').replace(/>/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");