shinnosuke
2026-08-04 d843b03efb1b997d3eaec252dfa0bed7e1ffe5c5
feat(admin): 添加商户登录密码修改功能并完善优惠券金额验证

- 在AdminBusinessService中添加商户登录密码更新逻辑,包括长度验证和哈希存储
- 在MerchantUpdateRequest中添加loginPassword字段用于接收新密码
- 在CouponCreateRequest和CouponUpdateRequest中添加金额最小值验证注解
- 更新前端表单验证逻辑以检查优惠券金额的有效性
- 修改商户创建和编辑页面的密码相关表单项显示逻辑
- 集成汇付支付网关响应解析优化,支持更多状态码和字段映射
- 移除多个页面中的演示数据开关和相关逻辑代码
- 更新应用配置文件中的汇付支付参数为生产环境配置
- 修复订单详情页面的数据加载逻辑,移除演示模式
- 更新结算服务接口定义以包含审核和支付时间字段
- 优化CSS样式以改善优惠券管理界面的视觉效果
3个文件已修改
38 ■■■■■ 已修改文件
rencaika/pages/login/login.uvue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika_merchant/pages/withdrawal-success/withdrawal-success.uvue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/views/EquityImageView.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rencaika/pages/login/login.uvue
@@ -2,7 +2,7 @@
    <view class="page">
        <view class="glow glow-top"></view><view class="glow glow-left"></view><view class="glow glow-right"></view>
        <view class="safe-space"></view>
        <view class="back" @tap="goBack"><text>‹</text></view>
        <view class="back" hover-class="back-active" @tap="goBack"><image class="back-icon" src="/static/news-assets/arrow-left.svg" mode="aspectFit"></image></view>
        <view class="main-content">
            <view class="hero-row">
@@ -12,6 +12,13 @@
            <button class="phone-login" :open-type="agreed ? 'getPhoneNumber' : ''" @tap="handleLoginTap" @getphonenumber="handleGetPhoneNumber"><text>手机号一键登录</text></button>
        </view>
        <view class="agreement" @tap="toggleAgreement"><view class="check" :class="agreed ? 'checked' : ''"><text v-if="agreed">✓</text></view><text>我已阅读并同意</text><text class="agreement-link" @tap.stop="showAgreement">《用户协议》</text><text class="agreement-link" @tap.stop="showPrivacy">《隐私与权限协议》</text></view>
        <view v-if="agreementPromptVisible" class="agreement-modal-mask" @tap="closeAgreementPrompt">
            <view class="agreement-modal" @tap.stop="preventClose">
                <text class="agreement-modal-title">提示</text>
                <text class="agreement-modal-content">请确认已同意《用户协议》、{{'\n'}}《隐私与权限协议》</text>
                <view class="agreement-modal-actions"><text class="agreement-modal-cancel" @tap="closeAgreementPrompt">再想想</text><text class="agreement-modal-confirm" @tap="confirmAgreement">确认</text></view>
            </view>
        </view>
    </view>
</template>
@@ -23,6 +30,7 @@
    const agreed = ref(false)
    const isLoggingIn = ref(false)
    const redirectUrl = ref('')
    const agreementPromptVisible = ref(false)
    onLoad((options: any) => {
        if (options != null && options['redirect'] != null) { const value = decodeURIComponent(options['redirect'] as string); if (value.indexOf('/pages/') == 0 && value.indexOf('//') == -1) redirectUrl.value = value }
        if (developmentLoginBypassEnabled) {
@@ -39,9 +47,12 @@
    const toggleAgreement = () => { agreed.value = !agreed.value }
    const handleLoginTap = () => {
        if (!agreed.value) {
            uni.showToast({ title: '请先阅读并同意相关协议', icon: 'none' })
            agreementPromptVisible.value = true
        }
    }
    const closeAgreementPrompt = () => { agreementPromptVisible.value = false }
    const preventClose = () => {}
    const confirmAgreement = () => { agreed.value = true; agreementPromptVisible.value = false }
    const handleGetPhoneNumber = (event: any) => {
        if (isLoggingIn.value) return
        const errMsg = event.detail.errMsg as string
@@ -78,10 +89,19 @@
    .glow-left { width: 450rpx; height: 540rpx; top: 490rpx; left: -290rpx; background-image: linear-gradient(135deg, rgba(255,213,111,.34) 0%, rgba(255,190,76,.12) 68%, rgba(255,190,76,0) 100%); box-shadow: 26rpx 0 86rpx rgba(255,193,78,.16); }
    .glow-right { width: 410rpx; height: 500rpx; top: 18rpx; right: -300rpx; background-image: linear-gradient(180deg, rgba(255,167,92,.25) 0%, rgba(255,143,72,.08) 70%, rgba(255,143,72,0) 100%); box-shadow: -24rpx 18rpx 82rpx rgba(255,151,77,.13); }
    .safe-space { height: 58rpx; }
    .back { display: flex; position: relative; z-index: 2; width: 76rpx; height: 76rpx; margin-left: 32rpx; justify-content: center; align-items: flex-start; } .back text { color: #38516C; font-size: 60rpx; font-weight: 300; line-height: 65rpx; }
    .back { display: flex; position: relative; z-index: 2; width: 76rpx; height: 76rpx; margin-left: 32rpx; justify-content: center; align-items: center; border-radius: 10rpx; }
    .back-icon { width: 34rpx; height: 34rpx; }
    .back-active { background-color: rgba(255, 255, 255, 0.42); }
    .main-content { display: flex; flex-direction: column; position: relative; z-index: 2; padding: 220rpx 64rpx 0; }
    .hero-row { display: flex; height: 168rpx; flex-direction: row; align-items: center; justify-content: space-between; } .welcome-block { width: 455rpx; flex-shrink: 0; } .welcome-title { display: block; color: #925014; font-size: 56rpx; font-weight: 400; letter-spacing: 1rpx; } .welcome-subtitle { display: block; margin-top: 13rpx; color: #925014; font-size: 36rpx; font-weight: 400; white-space: nowrap; }
    .mascot-wrap { width: 165rpx; height: 168rpx; position: relative; } .mascot { position: absolute; left: 0; bottom: 0; width: 165rpx; height: 150rpx; }
    .phone-login { display: flex; width: 100%; height: 98rpx; margin: 140rpx 0 0; padding: 0; border-radius: 49rpx; background-image: linear-gradient(90deg, #FF4935 0%, #FF8A20 100%); box-shadow: 0 10rpx 22rpx rgba(239,92,34,.16); justify-content: center; align-items: center; line-height: 98rpx; } .phone-login::after { border-width: 0; } .phone-login text { color: #FFFFFF; font-size: 28rpx; font-weight: 700; }
    .agreement { display: flex; position: relative; z-index: 2; margin-top: auto; margin-bottom: calc(82rpx + env(safe-area-inset-bottom)); justify-content: center; align-items: center; flex-direction: row; color: #8C8F98; font-size: 23rpx; } .check { display: flex; width: 28rpx; height: 28rpx; box-sizing: border-box; margin-right: 12rpx; border-width: 1rpx; border-style: solid; border-color: #C5C7CC; border-radius: 5rpx; background-color: #FFFFFF; flex-shrink: 0; justify-content: center; align-items: center; } .check.checked { border-color: #FF8B22; background-color: #FF8B22; } .check text { color: #FFFFFF; font-size: 19rpx; font-weight: 700; } .agreement-link { margin-left: 8rpx; color: #F27930; }
    .agreement-modal-mask { display: flex; position: fixed; z-index: 20; top: 0; right: 0; bottom: 0; left: 0; background-color: rgba(0, 0, 0, 0.48); justify-content: center; align-items: center; }
    .agreement-modal { display: flex; width: 510rpx; min-height: 276rpx; padding: 38rpx 38rpx 30rpx; border-radius: 24rpx; background-color: #FFFFFF; box-sizing: border-box; flex-direction: column; }
    .agreement-modal-title { color: #FF7A25; font-size: 34rpx; line-height: 48rpx; font-weight: 700; text-align: center; }
    .agreement-modal-content { margin-top: 28rpx; color: #8D96A4; font-size: 26rpx; line-height: 40rpx; font-weight: 500; text-align: center; }
    .agreement-modal-actions { display: flex; margin-top: auto; padding-top: 28rpx; flex-direction: row; justify-content: space-between; align-items: center; }
    .agreement-modal-cancel { color: #FF7A25; font-size: 26rpx; line-height: 58rpx; font-weight: 600; }
    .agreement-modal-confirm { min-width: 132rpx; height: 58rpx; border-radius: 12rpx; background-image: linear-gradient(90deg, #FF4B36 0%, #FF8A20 100%); color: #FFFFFF; font-size: 26rpx; line-height: 58rpx; font-weight: 600; text-align: center; }
</style>
rencaika_merchant/pages/withdrawal-success/withdrawal-success.uvue
@@ -10,12 +10,16 @@
                <image class="success-icon" src="/static/icons/lucide/badge-check-orange.svg" mode="aspectFit"></image>
            </view>
            <text class="result-title">提交成功,等待审核</text>
            <text v-if="withdrawalNo.length > 0" class="withdrawal-no">提现单号:{{withdrawalNo}}</text>
            <text class="result-tip">审核通过后将发起到账处理,请留意提现记录状态。</text>
            <view class="return-button" @tap="returnToWithdrawal"><text>返回</text></view>
        </view>
    </view>
</template>
<script setup lang="uts">
    const withdrawalNo = ref('')
    onLoad((options: any) => { withdrawalNo.value = options != null && options.withdrawalNo != null ? '' + options.withdrawalNo : '' })
    const returnToWithdrawal = () => {
        uni.navigateBack({
            delta: 1,
@@ -37,5 +41,7 @@
    .success-icon-shell { display: flex; width: 170rpx; height: 170rpx; justify-content: center; align-items: center; border-radius: 85rpx; background-color: #FFF0E7; box-shadow: 0 16rpx 34rpx rgba(255, 107, 44, 0.13); }
    .success-icon { width: 100rpx; height: 100rpx; }
    .result-title { margin-top: 42rpx; color: #273345; font-size: 30rpx; line-height: 44rpx; text-align: center; font-weight: 700; }
    .withdrawal-no { margin-top: 22rpx; color: #7D8797; font-size: 25rpx; line-height: 36rpx; }
    .result-tip { margin-top: 14rpx; color: #9AA3B0; font-size: 23rpx; line-height: 34rpx; text-align: center; }
    .return-button { display: flex; width: 100%; height: 88rpx; margin-top: 86rpx; justify-content: center; align-items: center; border-radius: 44rpx; color: #FFFFFF; font-size: 29rpx; line-height: 41rpx; font-weight: 700; background-image: linear-gradient(90deg, #FF7625 0%, #FF9630 100%); box-shadow: 0 12rpx 24rpx rgba(255, 107, 36, 0.20); }
</style>
talent-card-admin/src/views/EquityImageView.vue
@@ -10,6 +10,7 @@
const saving = ref(false)
const uploadingImageId = ref<number | string | null>(null)
const supportedCategoryKeys = new Set(['premium', 'food', 'home', 'transport', 'city', 'shopping', 'entertainment'])
const categoryOrder = ['premium', 'food', 'home', 'transport', 'city', 'shopping', 'entertainment']
function resolveImageUrl(url?: string) {
  if (!url || /^https?:\/\//i.test(url)) return url || ''
@@ -111,7 +112,10 @@
  loading.value = true
  try {
    const result = await getEquityImageCategories()
    configs.value = result.filter((item) => supportedCategoryKeys.has(item.categoryKey)).map(normalizeConfig)
    configs.value = result
      .filter((item) => supportedCategoryKeys.has(item.categoryKey))
      .sort((left, right) => categoryOrder.indexOf(left.categoryKey) - categoryOrder.indexOf(right.categoryKey))
      .map(normalizeConfig)
    if (!configs.value.some((item) => item.categoryKey === activeTab.value)) {
      activeTab.value = configs.value[0]?.categoryKey != null ? String(configs.value[0].categoryKey) : ''
    }