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