yeg
2026-09-16 cd06355d42a90ff88df61bd5d2a7947cb007e65a
fix(merchant): 进件进度页对齐小程序视觉,并修掉四处呈现问题

这一页原来和商户端其它页面不是一个画风:说明文字被两侧裁掉、底部内容被视口切掉,
完成后还在让人「刷新」和「扫码」。除了视觉对齐,四处都是实际会误导或挡住信息的缺陷。

真实缺陷(不只是好不好看):
- 阶段说明被横向裁掉。所有说明都套在 .result-reason 上,而它写了 width:100% + text-align:center
——<text> 在小程序里是行内元素,于是排成一整行居中溢出、左右都被切。改成 display:block
正常换行(与进件表单的 .field-label 同一写法)。
- 底部内容被视口切掉。滚动区没有底部留白,最后一行贴着屏幕边缘。

误导性呈现:
- 授权已完成(实名阶段 confirmed)之后二维码还在显示,文案却是「请本人用微信扫码」——
已经授权完还让人去扫一次。通道查询在授权后仍会回传该码,所以由前端按状态收起
(qrVisible:有码且状态不是 confirmed 才显示)。
- 整单完成后仍显示「刷新进度」与「请再次刷新」。全部确认后已无可办理步骤,再摆一个橙色主按钮
等于暗示还有事没做完;已完成的商户本就不需要手动再核(通道能力由服务端定时刷新兜底)。
两者统一由 canRefresh(refreshable 且不是 approved)控制。

视觉对齐(沿用小程序既有设计语言,不新造色):
- 页面结构:状态头 → 一张白卡(34rpx 圆角 + 0 14rpx 34rpx rgba(49,55,66,0.06) 弥散阴影,
与首页 .setting-card 同一套)→ 操作区。
- 阶段改行式列表配 1rpx 分隔线,不再是六张各自浮起的白卡;卡头一行「开通进度 / 最后同步时间」。
- 阶段加状态色标:原来六个阶段长得一样,看状态得读文字。用页面已有的语义色——
已确认 #FF7625/#FFF0E4(与进件表单的步骤完成态同一 token)、待办 #B4520F/#FFF5EA、
失败 #F06A66/#FEF3F2、等待 #9AA4B3/#EDF0F4。
- 二维码居中放进浅色托板,补一行「请本人用微信扫码完成实名授权」(原来是一张图直接怼在卡片左侧、
没有任何说明)。
- 丢掉默认 <button>:微信默认按钮是灰底直角,是全页最跳的地方。换成与进件表单完全一致的胶囊样式
——主操作 90deg #FF8A24→#FF4B36 渐变、次要 #EDF0F4,处理中显示「处理中...」而不是转圈。
- 导航栏返回按钮对齐进件表单页(80rpx / left 18rpx / top 79rpx),原来两页间距不一致。

验证:用同一套 CSS 按 750 宽折算成 1:1 静态稿渲染核对了各状态(含全部未确认、待办理带二维码、
失败、以及 approved 全绿),确认长文案正常换行不再被裁、二维码按状态收起、完成态只留
「返回商户中心」。仅前端一个文件,接口与后端逻辑未动,后端全量测试未重跑。

未验证:微信开发者工具真机编译(本仓无 CLI 构建链)。样式用的都是页面/表单页已有的写法与色值,
没有引入新的样式能力。
1个文件已修改
116 ■■■■■ 已修改文件
rencaika_merchant/pages/merchant-onboarding-pending/merchant-onboarding-pending.uvue 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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>