From bddcb4ef710d8bcad722a8e062ee9e0855d7042c Mon Sep 17 00:00:00 2001
From: shinnosuke <tantao@1hoper.com>
Date: 星期五, 21 八月 2026 17:35:55 +0800
Subject: [PATCH] 又又更新

---
 talent-card-admin/src/views/MerchantCreateView.vue                                                     |   12 +++---
 talent-card-server/src/main/java/com/talentcard/server/business/merchant/ManualConsumptionService.java |    8 ++++
 rencaika_merchant/pages/withdrawal-detail/withdrawal-detail.uvue                                       |   34 ++++++++++++++---
 talent-card-server/src/main/java/com/talentcard/server/business/order/OrderService.java                |    5 ++
 talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantWorkService.java      |   43 +++++++++++++++++++++
 talent-card-admin/src/style.css                                                                        |   11 +++++
 talent-card-admin/src/views/ProductAuditDetailView.vue                                                 |    6 ++
 7 files changed, 104 insertions(+), 15 deletions(-)

diff --git a/rencaika_merchant/pages/withdrawal-detail/withdrawal-detail.uvue b/rencaika_merchant/pages/withdrawal-detail/withdrawal-detail.uvue
index 1d7089f..c25bbe3 100644
--- a/rencaika_merchant/pages/withdrawal-detail/withdrawal-detail.uvue
+++ b/rencaika_merchant/pages/withdrawal-detail/withdrawal-detail.uvue
@@ -2,19 +2,18 @@
 	<view class="page">
 		<view class="navigation-bar"><view class="back-button" @tap="goBack"><image class="back-icon" src="/static/icons/lucide/back-left.svg" mode="aspectFit"></image></view><text class="page-title">商户提现</text></view>
 		<scroll-view class="form-scroll" scroll-y="true" :show-scrollbar="false" :enhanced="false">
+			<view class="available-row"><text class="available-label">当前可提</text><text class="available-value">¥{{availableText}}</text></view>
 			<view class="form-card">
-				<view class="available-row"><text class="available-label">当前可提</text><text class="available-value">¥{{availableText}}</text></view>
 				<view class="field-block">
-					<view class="field-row">
+					<view class="field-row amount-field-row">
 						<text class="field-label">提现金额</text>
 						<view class="amount-input-shell" :class="error.length > 0 ? 'field-invalid' : ''">
-							<text class="amount-prefix">¥</text>
-							<input class="amount-input" v-model="amount" type="digit" maxlength="10" placeholder="请输入" placeholder-class="input-placeholder" @input="clearError" />
+							<text class="amount-prefix">¥</text>
+							<input class="amount-input" v-model="amount" type="digit" maxlength="10" placeholder="请输入提现金额" placeholder-class="input-placeholder" @input="clearError" />
 						</view>
 					</view>
 					<text v-if="error.length > 0" class="field-error">{{error}}</text>
 				</view>
-				<view class="field-divider"></view>
 				<view class="field-block bank-field-block">
 					<view class="field-row bank-field-row">
 						<text class="field-label">提现银行卡</text>
@@ -28,8 +27,8 @@
 						<view class="select-bank-button" @tap="chooseBank"><text>{{selected == null ? '选择' : '更换'}}</text></view>
 					</view>
 				</view>
+				<view class="submit-button" :class="submitting ? 'submit-disabled' : ''" @tap="submit"><text>{{submitting ? '提交中' : '发起提现'}}</text></view>
 			</view>
-			<view class="submit-button" :class="submitting ? 'submit-disabled' : ''" @tap="submit"><text>{{submitting ? '提交中' : '发起提现'}}</text></view>
 			<view class="safe-space"></view>
 		</scroll-view>
 	</view>
@@ -79,4 +78,27 @@
 	page { background-color: #F4F6F9; }.page { display: flex; flex: 1; width: 100%; height: 100vh; flex-direction: column; overflow: hidden; background-color: #F4F6F9; }.navigation-bar { display: flex; height: 174rpx; padding: 68rpx 38rpx 0; flex: none; flex-direction: row; align-items: center; position: relative; box-sizing: border-box; }.back-button { display: flex; width: 64rpx; height: 64rpx; justify-content: center; align-items: center; position: absolute; left: 38rpx; top: 89rpx; z-index: 2; }.back-icon { width: 38rpx; height: 38rpx; transform: rotate(180deg); }.page-title { width: 100%; color: #202B3E; font-size: 34rpx; line-height: 48rpx; text-align: center; font-weight: 700; }.form-scroll { flex: 1; height: 0; padding: 0 34rpx; box-sizing: border-box; }.available-row { display: flex; height: 72rpx; padding: 0 12rpx; align-items: center; }.available-label { color: #7D8797; font-size: 25rpx; }.available-value { margin-left: 12rpx; color: #2C3748; font-size: 27rpx; font-weight: 700; }.form-card { padding: 6rpx 28rpx; border-radius: 16rpx; background-color: #FFFFFF; box-shadow: 0 8rpx 24rpx rgba(49,55,66,.05); }.field-block { display: flex; flex-direction: column; }.bank-field-block { margin-top: 0; }.field-row { display: flex; min-height: 104rpx; align-items: center; }.bank-field-row { align-items: center; }.field-label { width: 178rpx; padding-top: 0; flex: none; color: #5F6978; font-size: 27rpx; font-weight: 400; }.amount-input-shell { display: flex; height: 70rpx; padding: 0 18rpx; flex: 1; align-items: center; border: 1rpx solid #E3E7ED; border-radius: 8rpx; background-color: #FFFFFF; box-sizing: border-box; }.amount-prefix { color: #8B94A2; font-size: 27rpx; font-weight: 400; }.amount-input { height: 68rpx; margin-left: 8rpx; flex: 1; color: #2C3748; font-size: 27rpx; }.input-placeholder { color: #B5BDC9; font-size: 25rpx; }.field-invalid { border-color: #F0746D; background-color: #FFF9F8; }.field-error { margin: 0 0 14rpx 178rpx; color: #E95B55; font-size: 22rpx; }.field-divider { height: 1rpx; margin-left: 178rpx; background-color: #EEF0F3; }.bank-value { display: flex; min-width: 0; flex: 1; }.bank-placeholder { color: #B5BDC9; font-size: 25rpx; }.selected-bank-copy { display: flex; min-width: 0; flex: 1; flex-direction: column; }.selected-bank-name { color: #2C3748; font-size: 25rpx; }.selected-card-number { margin-top: 4rpx; color: #8A93A1; font-size: 22rpx; }.select-bank-button { display: flex; width: 84rpx; height: 52rpx; padding: 0; margin: 0 0 0 14rpx; flex: none; align-items: center; justify-content: center; border: 1rpx solid #58A5F8; border-radius: 8rpx; color: #318CF0; background-color: #FFFFFF; font-size: 23rpx; font-weight: 400; }.submit-button { display: flex; width: 360rpx; height: 72rpx; margin: 34rpx auto 0; align-items: center; justify-content: center; border-radius: 8rpx; color: #FFFFFF; background-color: #409EFF; font-size: 27rpx; font-weight: 500; box-shadow: none; }.submit-disabled { opacity: .58; }.safe-space { height: 70rpx; }
 	.back-icon { transform: none; }
 	.submit-button { width: 100%; margin-left: 0; margin-right: 0; }
+
+	/* Match the withdrawal prototype: balance above the card and vertically stacked fields. */
+	.form-scroll { padding-top: 0; }
+	.available-row { height: 128rpx; padding: 20rpx 0 24rpx; flex-direction: column; justify-content: center; align-items: center; box-sizing: border-box; }
+	.available-label { font-size: 25rpx; line-height: 36rpx; }
+	.available-value { margin: 2rpx 0 0; color: #E87535; font-size: 30rpx; line-height: 42rpx; }
+	.form-card { padding: 54rpx 28rpx 34rpx; border-radius: 28rpx; }
+	.amount-field-row { min-height: 0; flex-direction: column; align-items: stretch; }
+	.amount-field-row .field-label { width: auto; margin-bottom: 10rpx; text-align: center; color: #273345; font-size: 30rpx; line-height: 42rpx; font-weight: 700; }
+	.amount-input-shell { width: 100%; height: 160rpx; padding: 10rpx 18rpx 0; flex: none; flex-direction: column; align-items: center; box-sizing: border-box; }
+	.amount-prefix { line-height: 34rpx; }
+	.amount-input { width: 100%; height: 100rpx; margin: 0; flex: none; text-align: center; }
+	.input-placeholder { text-align: center; }
+	.field-divider { display: none; }
+	.bank-field-block { margin-top: 56rpx; }
+	.bank-field-row { min-height: 0; flex-direction: column; align-items: flex-start; }
+	.bank-field-row .field-label { width: auto; margin-bottom: 18rpx; color: #273345; font-size: 30rpx; line-height: 42rpx; font-weight: 700; }
+	.bank-value { width: 100%; flex: none; }
+	.bank-placeholder { font-size: 25rpx; }
+	.bank-placeholder { display: none; }
+	.select-bank-button { width: 240rpx; height: 76rpx; margin: 18rpx 0 0; border-color: #F0D1B9; border-radius: 38rpx; color: #D97643; background-color: #FFF9F4; font-size: 28rpx; }
+	.submit-button { width: 100%; height: 88rpx; margin: 62rpx 0 0; border-radius: 44rpx; color: #FFFFFF; background-image: linear-gradient(90deg, #FF7625 0%, #FF9630 100%); font-size: 29rpx; line-height: 41rpx; font-weight: 700; box-shadow: 0 12rpx 24rpx rgba(255, 107, 36, 0.20); }
+	.safe-space { height: 70rpx; }
 </style>
diff --git a/talent-card-admin/src/style.css b/talent-card-admin/src/style.css
index 5ef8e80..d410a2c 100644
--- a/talent-card-admin/src/style.css
+++ b/talent-card-admin/src/style.css
@@ -738,6 +738,17 @@
   padding-left: 12px;
 }
 
+.merchant-settings-form .created-time-row > label {
+  box-sizing: border-box;
+  padding-left: 12px;
+}
+
+.merchant-settings-form .merchant-form-row > label,
+.merchant-settings-form .merchant-hours-block > label {
+  box-sizing: border-box;
+  padding-left: 12px;
+}
+
 .merchant-form-row.recommend-row .el-switch {
   justify-self: start;
 }
diff --git a/talent-card-admin/src/views/MerchantCreateView.vue b/talent-card-admin/src/views/MerchantCreateView.vue
index 7f684f1..3a9484f 100644
--- a/talent-card-admin/src/views/MerchantCreateView.vue
+++ b/talent-card-admin/src/views/MerchantCreateView.vue
@@ -2248,8 +2248,8 @@
         <h3>商家信息</h3>
 
         <el-form ref="merchantFormRef" :model="form" :rules="merchantRules" class="merchant-settings-form" label-width="58px" label-position="left">
-        <div class="merchant-create-content" :class="{ 'service-merchant-content': isServiceMerchant || isApartmentMerchant || isHotelMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant }">
-          <div class="merchant-create-left" :style="isApartmentMerchant || isFoodMerchant || isHotelMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant ? 'flex: 1' : ''">
+        <div class="merchant-create-content" :class="{ 'service-merchant-content': isServiceMerchant || isApartmentMerchant || isHotelMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant || usesSimplifiedMerchantSettings }">
+          <div class="merchant-create-left" :style="isApartmentMerchant || isFoodMerchant || isHotelMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant || usesSimplifiedMerchantSettings ? 'flex: 1' : ''">
             <el-form-item class="merchant-form-row three-columns" label="商家名称" prop="merchantName" required>
               <el-input v-model="form.merchantName" />
             </el-form-item>
@@ -2273,7 +2273,7 @@
             </el-form-item>
           </div>
 
-          <div v-if="!isServiceMerchant && !isApartmentMerchant && !isHotelMerchant && !isCarWashMerchant && !isHealthCheckMerchant && !isParkingMerchant && !isChildcareMerchant && !isFinancialMerchant && !isMovieMerchant && !isSpouseEmploymentMerchant && !isApprovalMerchant" class="merchant-create-middle">
+          <div v-if="!isServiceMerchant && !isApartmentMerchant && !isHotelMerchant && !isCarWashMerchant && !isHealthCheckMerchant && !isParkingMerchant && !isChildcareMerchant && !isFinancialMerchant && !isMovieMerchant && !isSpouseEmploymentMerchant && !isApprovalMerchant && !usesSimplifiedMerchantSettings" class="merchant-create-middle">
             <el-form-item class="merchant-form-row" label="联系电话" prop="contactMobile" required>
               <el-input v-model="form.contactMobile" />
             </el-form-item>
@@ -2297,12 +2297,12 @@
             </template>
           </div>
 
-          <div class="merchant-create-right" :style="isApartmentMerchant || isFoodMerchant || isHotelMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant ? 'flex: 1' : ''">
-            <el-form-item v-if="isServiceMerchant || isApartmentMerchant || isHotelMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant" class="merchant-form-row service-contact-row" label="联系电话" prop="contactMobile" required>
+          <div class="merchant-create-right" :style="isApartmentMerchant || isFoodMerchant || isHotelMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant || usesSimplifiedMerchantSettings ? 'flex: 1' : ''">
+            <el-form-item v-if="isServiceMerchant || isApartmentMerchant || isHotelMerchant || isCarWashMerchant || isHealthCheckMerchant || isParkingMerchant || isChildcareMerchant || isFinancialMerchant || isMovieMerchant || isSpouseEmploymentMerchant || isApprovalMerchant || usesSimplifiedMerchantSettings" class="merchant-form-row service-contact-row" label="联系电话" prop="contactMobile" required>
               <el-input v-model="form.contactMobile" placeholder="请输入联系电话" />
             </el-form-item>
 
-            <div v-if="hasExistingMerchant" class="merchant-form-row">
+            <div v-if="hasExistingMerchant" class="merchant-form-row created-time-row">
               <label>创建时间</label>
               <el-input v-model="form.createdAt" readonly />
             </div>
diff --git a/talent-card-admin/src/views/ProductAuditDetailView.vue b/talent-card-admin/src/views/ProductAuditDetailView.vue
index 137ebbf..73c0295 100644
--- a/talent-card-admin/src/views/ProductAuditDetailView.vue
+++ b/talent-card-admin/src/views/ProductAuditDetailView.vue
@@ -53,6 +53,10 @@
   const merchantType = product.value?.merchantType || ''
   return merchantType === '酒店服务' || merchantType === '酒店'
 })
+const hideTalentBenefitPanel = computed(() => {
+  const merchantType = String(product.value?.merchantType || '').trim().toLowerCase()
+  return ['酒店服务', '酒店', '医院服务', '医院', '医疗服务', '医疗', 'hotel', 'hospital', 'medical', 'health_check'].includes(merchantType)
+})
 const productAmountText = computed(() => {
   const salePrice = product.value?.salePrice
   const originalPrice = product.value?.originalPrice
@@ -283,7 +287,7 @@
       <img v-if="previewUrl" :src="previewUrl" alt="商品图片预览" class="product-audit-preview-image" />
     </el-dialog>
 
-    <section class="audit-detail-panel talent-benefit-panel">
+    <section v-if="!hideTalentBenefitPanel" class="audit-detail-panel talent-benefit-panel">
       <h3>人才权益</h3>
       <div class="benefit-config-grid">
         <div v-for="row in benefitRows" :key="row.level" class="benefit-config-row">
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/ManualConsumptionService.java b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/ManualConsumptionService.java
index d83c8d9..7295ab1 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/ManualConsumptionService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/ManualConsumptionService.java
@@ -181,6 +181,7 @@
         result.put("name", result.get("ruleName"));
         result.put("discountRate", configuredDiscountRate(right.get("discount")));
         result.put("subsidyEnabled", booleanValue(right.get("governmentSubsidy")) ? 1 : 0);
+        result.put("merchantDetail", right.get("detail"));
         result.put("consumeDesc", right.get("consumeDesc"));
         result.put("benefitText", configuredBenefitText(right));
         return result;
@@ -332,6 +333,13 @@
                 .orElse("");
         if (configuredDescription.isBlank()) {
             configuredDescription = configuredRules(merchantId, cardLevel).stream()
+                    .map(rule -> string(rule.get("merchantDetail")))
+                    .filter(value -> !value.isBlank())
+                    .findFirst()
+                    .orElse("");
+        }
+        if (configuredDescription.isBlank()) {
+            configuredDescription = configuredRules(merchantId, cardLevel).stream()
                     .map(rule -> string(rule.get("benefitText")))
                     .filter(value -> !value.isBlank())
                     .findFirst()
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantWorkService.java b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantWorkService.java
index 0c3a0a0..0eb6113 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantWorkService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantWorkService.java
@@ -20,10 +20,14 @@
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import tools.jackson.core.type.TypeReference;
+import tools.jackson.databind.ObjectMapper;
 
 @Service
 public class MerchantWorkService {
 	private static final long REVOCATION_HOURS = 3;
+	private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+	private static final TypeReference<List<Map<String, Object>>> RIGHTS_TYPE = new TypeReference<>() {};
 
 	private final JdbcTemplate jdbcTemplate;
 	private final ManualConsumptionService manualConsumptionService;
@@ -73,7 +77,8 @@
 				select o.id, o.order_no, o.talent_id, tu.name as talent_name, tu.mobile as talent_mobile, tu.gender,
 				       o.card_id, tc.card_no, coalesce(nullif(o.card_level_snapshot, ''), tc.card_level) as card_level,
 				       o.benefit_id, b.benefit_name,
-				       o.merchant_id, m.merchant_name, o.product_id, o.product_name_snapshot, p.rights_config,
+				       o.merchant_id, m.merchant_name, m.merchant_rights_config,
+				       o.product_id, o.product_name_snapshot, p.rights_config,
 				       o.order_type, o.order_amount, o.base_pay_amount, o.coupon_amount,
 				       o.subsidy_amount, o.pay_amount, o.order_status, o.verify_status, o.created_at,
 				       vr.verify_time, vr.status as verify_record_status
@@ -314,6 +319,8 @@
 
 	private String currentBenefitText(Map<String, Object> order, String category) {
 		String cardLevel = text(order.get("cardLevel"));
+		String merchantDetail = merchantBenefitDetail(order.get("merchantRightsConfig"), cardLevel);
+		if (!merchantDetail.isBlank()) return merchantDetail;
 		if ("store".equals(category)) {
 			Long merchantId = number(order.get("merchantId"));
 			return merchantId == null ? "以当前商户到店消费规则为准" : manualConsumptionService.benefitTextForMerchant(merchantId, cardLevel);
@@ -326,6 +333,40 @@
 		return configured.isBlank() ? "以当前订单权益规则为准" : configured;
 	}
 
+	private String merchantBenefitDetail(Object rawConfig, String cardLevel) {
+		if (rawConfig == null || cardLevel.isBlank()) return "";
+		try {
+			List<Map<String, Object>> configs = rawConfig instanceof List<?>
+					? ((List<?>) rawConfig).stream().filter(Map.class::isInstance)
+							.map(item -> (Map<String, Object>) item).toList()
+					: OBJECT_MAPPER.readValue(String.valueOf(rawConfig), RIGHTS_TYPE);
+			for (Map<String, Object> config : configs) {
+				if (!booleanValue(config.get("enabled")) || !sameCardLevel(config.get("level"), cardLevel)) continue;
+				String detail = text(config.get("detail"));
+				if (!detail.isBlank()) return detail;
+			}
+		} catch (Exception ignored) {
+			// Invalid legacy configuration should fall back to the structured rule text.
+		}
+		return "";
+	}
+
+	private boolean sameCardLevel(Object configured, String actual) {
+		return normalizeCardLevel(configured).equals(normalizeCardLevel(actual));
+	}
+
+	private String normalizeCardLevel(Object value) {
+		String raw = text(value).toUpperCase();
+		if (raw.contains("卓越") || "EXCELLENT".equals(raw)) return "EXCELLENT";
+		if (raw.contains("杰出") || "OUTSTANDING".equals(raw)) return "OUTSTANDING";
+		if (raw.contains("菁英") || "ELITE".equals(raw)) return "ELITE";
+		return raw;
+	}
+
+	private boolean booleanValue(Object value) {
+		return value instanceof Boolean bool ? bool : "1".equals(text(value)) || "true".equalsIgnoreCase(text(value));
+	}
+
 	private String productBenefitText(ProductPriceCalculator.ProductRight right) {
 		String discountText = discountText(right.discount());
 		if (!right.free()) return discountText.isBlank() ? "以商品配置为准" : "按市价" + discountText;
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/order/OrderService.java b/talent-card-server/src/main/java/com/talentcard/server/business/order/OrderService.java
index 285a92f..10470e9 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/order/OrderService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/order/OrderService.java
@@ -416,13 +416,16 @@
 				       %s as talent_level,
 				       o.card_id, tc.card_no, tc.card_type,
 				       o.merchant_id, m.merchant_name, m.merchant_type,
-				       o.benefit_id, b.benefit_name, o.order_type,
+				       o.benefit_id, b.benefit_name,
+				       coalesce(nullif(o.product_name_snapshot, ''), p.product_name, b.benefit_name) as product_name,
+				       o.order_type,
 				       o.order_amount, o.subsidy_amount, o.pay_amount,
 				       o.order_status, o.verify_status, o.created_at, o.updated_at as pay_time
 				from benefit_order o
 				left join talent_user tu on tu.id = o.talent_id
 				left join talent_card tc on tc.id = o.card_id
 				left join benefit b on b.id = o.benefit_id
+				left join merchant_product p on p.id = o.product_id and p.deleted = 0
 				left join merchant m on m.id = o.merchant_id
 				""".formatted(ORDER_CARD_LEVEL_SQL) + where + " order by o.id desc limit ? offset ?", pageArgs.toArray());
 		Long total = jdbcTemplate.queryForObject("""

--
Gitblit v1.7.1