yeg
2026-09-16 cd06355d42a90ff88df61bd5d2a7947cb007e65a
rencaika_merchant/pages/merchant-onboarding-pending/merchant-onboarding-pending.uvue
@@ -6,24 +6,40 @@
      </view>
      <scroll-view scroll-y="true" class="result-content">
         <view class="status-icon-wrap"><image class="status-icon" :src="statusIcon" mode="aspectFit"></image></view>
         <text class="result-title">{{statusTitle}}</text>
         <text v-if="statusReason.length > 0" class="result-reason">{{statusReason}}</text>
         <view v-if="refreshable" class="progress-panel">
            <text class="result-reason">最后成功同步:{{lastSyncedAt.length > 0 ? lastSyncedAt : '尚未同步'}}</text>
            <text v-if="syncError.length > 0" class="result-reason">{{syncError}}</text>
            <view v-for="stage in stages" :key="stage.key" class="progress-stage">
               <text>{{stage.label}} · {{stageLabel(stage.status)}}</text>
               <text class="result-reason">{{stage.reason}}</text>
               <image v-if="stage.qrCode.length > 0" :src="stage.qrCode" mode="aspectFit" style="width: 360rpx; height: 360rpx;"></image>
            </view>
            <text class="result-reason">刷新会查询进度并继续办理已满足条件的缺失能力。本人实名或授权办理后,请再次刷新。</text>
            <button :disabled="syncing" :loading="syncing" @tap="refreshProgress(false)">刷新进度</button>
            <button v-if="retryable" :disabled="syncing" @tap="refreshProgress(true)">重试失败步骤</button>
            <button v-if="realnameActionable" :disabled="syncing" @tap="startRealname">{{realnameButtonLabel}}</button>
         <view class="status-hero">
            <view class="status-icon-wrap"><image class="status-icon" :src="statusIcon" mode="aspectFit"></image></view>
            <text class="status-title">{{statusTitle}}</text>
            <text v-if="statusReason.length > 0" class="status-reason">{{statusReason}}</text>
         </view>
         <view v-if="onboardingStatus == 'rejected'" class="return-button" @tap="resubmit"><text>重新提交</text></view>
         <view v-else class="return-button" @tap="goBack"><text>返回商户中心</text></view>
         <view v-if="refreshable" class="progress-panel">
            <view class="panel-head">
               <text class="panel-head-label">开通进度</text>
               <text class="panel-head-time">{{lastSyncedAt.length > 0 ? lastSyncedAt : '尚未同步'}}</text>
            </view>
            <text v-if="syncError.length > 0" class="panel-sync-error">{{syncError}}</text>
            <view v-for="(stage, index) in stages" :key="stage.key" class="progress-stage" :class="index == stages.length - 1 ? 'progress-stage-last' : ''">
               <view class="stage-head">
                  <text class="stage-label">{{stage.label}}</text>
                  <text class="stage-pill" :class="pillClass(stage.status)">{{stageLabel(stage.status)}}</text>
               </view>
               <text v-if="stage.reason.length > 0" class="stage-reason">{{stage.reason}}</text>
               <view v-if="qrVisible(stage)" class="qr-block">
                  <view class="qr-frame"><image class="qr-image" :src="stage.qrCode" mode="aspectFit"></image></view>
                  <text class="qr-hint">请本人用微信扫码完成实名授权</text>
               </view>
            </view>
            <text v-if="canRefresh" class="panel-foot">刷新会查询进度并继续办理已满足条件的缺失能力。本人实名或授权办理后,请再次刷新。</text>
         </view>
         <view class="action-area">
            <view v-if="canRefresh" class="action-primary" :class="syncing ? 'button-disabled' : ''" @tap="refreshProgress(false)"><text>{{syncing ? '处理中...' : '刷新进度'}}</text></view>
            <view v-if="refreshable && retryable" class="action-secondary" :class="syncing ? 'button-disabled' : ''" @tap="refreshProgress(true)"><text>重试失败步骤</text></view>
            <view v-if="refreshable && realnameActionable" class="action-secondary" :class="syncing ? 'button-disabled' : ''" @tap="startRealname"><text>{{realnameButtonLabel}}</text></view>
            <view v-if="onboardingStatus == 'rejected'" class="action-primary" @tap="resubmit"><text>重新提交</text></view>
            <view v-else class="action-secondary" @tap="goBack"><text>返回商户中心</text></view>
         </view>
         <view class="safe-space"></view>
      </scroll-view>
   </view>
</template>
@@ -48,6 +64,21 @@
      if (status == 'failed') return '失败'
      return '未确认'
   }
   /** 阶段状态对应的标记底色。只用小程序既有的语义色:橙=完成、灰=等待、暖橙=待办、红=失败。 */
   const pillClass = (status: string): string => {
      if (status == 'confirmed') return 'stage-pill-done'
      if (status == 'failed') return 'stage-pill-failed'
      if (status == 'action_required' || status == 'missing') return 'stage-pill-action'
      return 'stage-pill-waiting'
   }
   // 全部确认之后就没有可办理的步骤了:不再显示「刷新进度」,也不再说「请再次刷新」——
   // 那时候让商户去点刷新只会让人以为还有事没做完。通道能力由服务端定时刷新兜底,
   // 页面留「返回商户中心」即可。
   const canRefresh = computed((): boolean => refreshable.value && onboardingStatus.value != 'approved')
   // 二维码是给本人扫码授权用的:授权已完成(confirmed)之后它就没有用途了,
   // 再挂着「请扫码」只会让商户以为还要去扫一次。通道查询在授权完成后仍会回传这个码,
   // 所以由前端按状态收起。
   const qrVisible = (stage: OnboardingStage): boolean => stage.qrCode.length > 0 && stage.status != 'confirmed'
   const applyProgress = (item: SettlementOnboarding) => {
      onboardingStatus.value = item.status
      statusReason.value = item.reason
@@ -142,18 +173,53 @@
</script>
<style>
   /* 与商户端其它页面同一套视觉:底 #F4F6F9、白卡大圆角 + 极淡弥散阴影、橙色渐变做主操作。
      这里刻意不用 <button>:微信默认按钮样式(灰底直角)跟小程序整体美术风格对不上,
      改成与进件表单同一套 view + 渐变胶囊。 */
   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; background-color: #F4F6F9; 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; }
   .back-button { display: flex; width: 80rpx; height: 80rpx; justify-content: center; align-items: center; position: absolute; left: 18rpx; top: 79rpx; z-index: 2; }
   .back-icon { width: 44rpx; height: 44rpx; }
   .page-title { width: 100%; color: #202B3E; font-size: 34rpx; line-height: 48rpx; text-align: center; font-weight: 700; }
   .result-content { display: flex; padding: 70rpx 44rpx 0; flex: 1; flex-direction: column; align-items: center; box-sizing: border-box; }
   .progress-panel { width: 100%; padding-top: 24rpx; padding-bottom: 32rpx; }
   .progress-stage { padding: 20rpx; margin-bottom: 16rpx; background-color: #FFFFFF; border-radius: 16rpx; }
   .result-content { display: flex; flex: 1; height: 0; padding: 24rpx 38rpx 0; flex-direction: column; box-sizing: border-box; }
   .status-hero { display: flex; flex-direction: column; align-items: center; padding: 30rpx 0 8rpx; }
   .status-icon-wrap { display: flex; width: 176rpx; height: 176rpx; justify-content: center; align-items: center; border-radius: 88rpx; background-image: linear-gradient(145deg, #FFF0E6 0%, #FFF8F3 100%); box-shadow: 0 18rpx 38rpx rgba(255, 107, 44, 0.12); }
   .status-icon { width: 82rpx; height: 82rpx; }
   .result-title { margin-top: 40rpx; color: #293547; font-size: 34rpx; line-height: 48rpx; font-weight: 700; }
   .result-reason { width: 100%; margin-top: 24rpx; color: #7D8796; font-size: 25rpx; line-height: 38rpx; text-align: center; }
   .return-button { display: flex; width: 100%; height: 88rpx; margin-top: 70rpx; 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); }
   .status-title { margin-top: 32rpx; color: #202B3E; font-size: 36rpx; line-height: 50rpx; font-weight: 700; text-align: center; }
   /* display: block 才能正常换行:<text> 默认是行内元素,之前用 width:100% + 居中会排成
      一整行溢出并被裁掉两侧。 */
   .status-reason { display: block; margin-top: 16rpx; padding: 0 20rpx; color: #7D8796; font-size: 25rpx; line-height: 38rpx; text-align: center; }
   .progress-panel { margin-top: 34rpx; padding: 8rpx 30rpx 26rpx; border-radius: 34rpx; background-color: #FFFFFF; box-shadow: 0 14rpx 34rpx rgba(49, 55, 66, 0.06); box-sizing: border-box; }
   .panel-head { display: flex; height: 88rpx; flex-direction: row; align-items: center; justify-content: space-between; }
   .panel-head-label { color: #2E394A; font-size: 29rpx; line-height: 40rpx; font-weight: 700; }
   .panel-head-time { color: #9AA4B3; font-size: 22rpx; line-height: 32rpx; }
   .panel-sync-error { display: block; margin-bottom: 16rpx; padding: 16rpx 20rpx; color: #F06A66; font-size: 23rpx; line-height: 34rpx; background-color: #FEF3F2; border-radius: 20rpx; }
   .progress-stage { padding: 24rpx 0; border-bottom: 1rpx solid #F0F3F7; }
   .progress-stage-last { border-bottom-width: 0; }
   .stage-head { display: flex; flex-direction: row; align-items: center; justify-content: space-between; }
   .stage-label { flex: 1; color: #313744; font-size: 29rpx; line-height: 40rpx; font-weight: 700; }
   .stage-pill { flex: none; margin-left: 16rpx; padding: 6rpx 18rpx; border-radius: 20rpx; font-size: 22rpx; line-height: 30rpx; font-weight: 600; }
   .stage-pill-done { color: #FF7625; background-color: #FFF0E4; }
   .stage-pill-waiting { color: #9AA4B3; background-color: #EDF0F4; }
   .stage-pill-action { color: #B4520F; background-color: #FFF5EA; }
   .stage-pill-failed { color: #F06A66; background-color: #FEF3F2; }
   .stage-reason { display: block; margin-top: 10rpx; color: #9AA4B3; font-size: 24rpx; line-height: 36rpx; }
   .qr-block { display: flex; flex-direction: column; align-items: center; margin-top: 24rpx; }
   .qr-frame { display: flex; padding: 22rpx; justify-content: center; align-items: center; border-radius: 24rpx; background-color: #F7F9FC; }
   .qr-image { width: 340rpx; height: 340rpx; }
   .qr-hint { margin-top: 14rpx; color: #9AA4B3; font-size: 23rpx; line-height: 34rpx; }
   .panel-foot { display: block; margin-top: 22rpx; color: #9AA4B3; font-size: 22rpx; line-height: 34rpx; }
   .action-area { display: flex; margin-top: 34rpx; flex-direction: column; }
   .action-primary { display: flex; height: 100rpx; margin-bottom: 22rpx; justify-content: center; align-items: center; border-radius: 50rpx; color: #FFFFFF; font-size: 30rpx; line-height: 42rpx; font-weight: 700; background-image: linear-gradient(90deg, #FF8A24 0%, #FF4B36 100%); box-shadow: 0 14rpx 26rpx rgba(255, 101, 48, 0.22); }
   .action-secondary { display: flex; height: 100rpx; margin-bottom: 22rpx; justify-content: center; align-items: center; border-radius: 50rpx; color: #313744; font-size: 28rpx; line-height: 38rpx; font-weight: 700; background-color: #EDF0F4; }
   .button-disabled { opacity: 0.58; }
   .safe-space { height: 60rpx; }
</style>