refactor: 优化跨端导航适配与页面文案体验

- 新增微信小程序胶囊区域避让布局逻辑
- 统一首页导航与标题对齐规则
- 更新多处演示提示文案为正式表述
- 调整个人中心账户状态显示
- 优化产品页分享按钮条件渲染
This commit is contained in:
hujun
2026-09-20 14:18:55 +08:00
parent 69130d11e7
commit 046a03e5e9
12 changed files with 110 additions and 43 deletions
+2
View File
@@ -32,6 +32,8 @@ $projectPath = (Get-Location).Path
## 跨端维护约定
- 页面尺寸和安全区计算集中在 `services/layout.uts`。新增页面应复用这里的逻辑,并检查小屏、横屏、键盘弹出及底部安全区。
- 微信小程序首页在右侧预留胶囊区域与 8px 间距;公共页面标题采用左右对称的安全留白,始终按屏幕中心定位。获取胶囊失败时按 104px 避让。产品分享入口在小程序端放在内容区,App 和 Web 仍保留在标题栏右侧。导航不新增顶部空行,顶部按状态栏和安全区计算。
- 首页右上角只保留消息通知;微信端通知按钮与胶囊等高、顶部对齐,左侧 Logo 对齐胶囊的垂直中心;其他端仍在导航栏内居中。AI 助手保留首页横幅及快捷入口。
- 原生样式遵循 uni-app x 支持范围。仅 Web/小程序支持的样式用条件编译隔离;修改 UTS 类型后应重新验证 Android 编译。
- 首页及跨包共用的轻量位图位于 `static/mobile/`,专属大图位于各分包的 `static/`,原始图片保留在 `static/images/` 与 `static/icons-generated/`。箭头继续使用 `static/icons/` 下的 SVG。
- 底栏使用 `static/mobile/navigation/` 的专用位图,由现有高清图标按可见边界裁切、等高居中得到。更换素材时保持一致的图案高度和画布留白,避免仅统一图片元素尺寸却出现视觉错位。
+5 -4
View File
@@ -3,7 +3,7 @@
<view class="status-bar" :style="statusStyle(0)"></view>
<view class="app-header">
<view class="header-side" @click="handleBack"><AppIcon v-if="back" name="chevron-right" :size="48" class="back-icon" /></view>
<text class="header-title">{{ title }}</text>
<view class="header-title-area" :style="navigationTitleStyle()"><text class="header-title">{{ title }}</text></view>
<view class="header-side header-right"><slot></slot></view>
</view>
</view>
@@ -11,7 +11,7 @@
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { statusStyle } = usePageLayout()
const { statusStyle, navigationTitleStyle } = usePageLayout()
import AppIcon from '@/components/AppIcon.uvue'
const props = defineProps({ title: { type: String, default: '' }, back: { type: Boolean, default: true }, customBack: { type: Boolean, default: false } })
const emit = defineEmits(['back'])
@@ -27,9 +27,10 @@
<style>
.header-wrap { background-color: #ffffff; }
.status-bar { }
.app-header { height: 96rpx; padding: 0 24rpx; flex-direction: row; align-items: center; justify-content: space-between; border-bottom: 1rpx solid #edf1f7; }
.app-header { height: 96rpx; padding: 0 24rpx; flex-direction: row; align-items: center; justify-content: space-between; border-bottom: 1rpx solid #edf1f7; position: relative; }
.header-side { width: 88rpx; height: 96rpx; align-items: flex-start; justify-content: center; flex-shrink: 0; }
.header-right { align-items: flex-end; }
.back-icon { transform: rotate(180deg); margin-top: 0; }
.header-title { font-size: 34rpx; font-weight: 700; color: #111d35; }
.header-title-area { position: absolute; left: 112rpx; right: 112rpx; top: 0; bottom: 0; align-items: center; justify-content: center; }
.header-title { text-align: center; font-size: 34rpx; font-weight: 700; color: #111d35; }
</style>
+3 -6
View File
@@ -12,8 +12,7 @@
<view v-if="mode == 'phone' && smsSent" class="input-box"><input v-model="smsCode" type="number" maxlength="6" placeholder="请输入 6 位验证码" class="field-input" /><text class="countdown" @click="resendSms">{{ countdown > 0 ? countdown.toString() + 's 后重试' : '重新获取' }}</text></view>
<text v-if="errorMessage.length > 0" class="error-message">{{ errorMessage }}</text>
<button class="login-button" :disabled="loading" @click="login">{{ loading ? '处理中…' : (mode == 'phone' ? (smsSent ? '登录' : '获取验证码') : '登录') }}</button>
<view class="mode-row"><text class="mode-link" @click="switchMode">{{ mode == 'phone' ? '密码登录' : '使用验证码登录' }}</text><text v-if="isDemo" class="demo-label">演示模式</text></view>
<text v-if="isDemo && smsSent" class="demo-hint">未发送真实短信,可输入任意 6 位数字体验。</text>
<view class="mode-row"><text class="mode-link" @click="switchMode">{{ mode == 'phone' ? '密码登录' : '使用验证码登录' }}</text></view>
<view class="other-login"><view class="line"></view><text class="other-text">其他登录方式</text><view class="line"></view></view>
<view class="social-row"><view class="social-button" @click="socialLogin('微信')"><image class="social-icon" src="/packages/account/static/wechat.svg" mode="aspectFit" /><text class="social-label">微信</text></view><view class="social-button" @click="socialLogin('一键登录')"><image class="social-icon" src="/packages/account/static/quick-login.svg" mode="aspectFit" /><text class="social-label">一键登录</text></view></view>
<view class="agreement"><view class="check-hit" @click="agreed = !agreed"><view :class="agreed ? 'check checked' : 'check'"><text v-if="agreed" class="check-mark">✓</text></view></view><text class="agreement-copy">我已阅读并同意<text class="agreement-link" @click="showAgreement('用户协议')">《用户协议》</text>和<text class="agreement-link" @click="showAgreement('隐私政策')">《隐私政策》</text></text></view>
@@ -67,7 +66,7 @@
try {
if (!(await requestSmsCode(phone.value))) throw new Error('验证码发送失败,请稍后重试')
startCountdown()
uni.showToast({ title: isDemo ? '演示验证码已就绪' : '验证码已重新发送', icon: 'none' })
uni.showToast({ title: isDemo ? '请输入 6 位验证码' : '验证码已重新发送', icon: 'none' })
} catch (error) { errorMessage.value = error instanceof Error ? error.message : '验证码发送失败,请稍后重试' } finally { loading.value = false }
}
const login = async () => {
@@ -80,7 +79,7 @@
if (!smsSent.value) {
if (!(await requestSmsCode(phone.value))) throw new Error('验证码发送失败,请稍后重试')
smsSent.value = true; startCountdown()
uni.showToast({ title: isDemo ? '演示验证码已就绪' : '验证码已发送', icon: 'none' })
uni.showToast({ title: isDemo ? '请输入 6 位验证码' : '验证码已发送', icon: 'none' })
} else {
if (!/^[0-9]{6}$/.test(smsCode.value)) throw new Error('请输入 6 位数字验证码')
finishLogin(await loginWithSms(phone.value, smsCode.value))
@@ -107,8 +106,6 @@
.login-button { height: 92rpx; line-height: 92rpx; margin-top: 28rpx; border-radius: 16rpx; color: #fff; font-size: 30rpx; font-weight: 600; background-color: #0868f7; }
.mode-row { flex-direction: row; justify-content: space-between; align-items: center; margin-top: 12rpx; }
.mode-link { padding: 16rpx 0; color: #31415a; font-size: 26rpx; }
.demo-label { color: #8591a2; font-size: 22rpx; }
.demo-hint { color: #65748a; font-size: 24rpx; line-height: 36rpx; margin-top: 8rpx; }
.other-login { margin-top: 66rpx; flex-direction: row; align-items: center; justify-content: center; }
.other-text { font-size: 24rpx; color: #8490a3; flex-shrink: 0; }
.line { height: 1rpx; background-color: #e5eaf1; flex: 1; margin: 0 20rpx; }
+3 -3
View File
@@ -3,7 +3,7 @@
<AppHeader title="个人中心" :back="false" />
<scroll-view scroll-y :style="contentStyle(226, false)">
<view class="profile-content">
<view class="profile-card" @click="openAccount"><view class="avatar"><AppIcon name="user" :size="80" /></view><view class="profile-main"><text class="profile-name">{{ displayName }}</text><text class="profile-phone">{{ loggedIn ? maskedPhone : '登录后管理您的融资信息' }}</text></view><text class="account-state">{{ loggedIn ? '演示账户' : '未登录' }}</text></view>
<view class="profile-card" @click="openAccount"><view class="avatar"><AppIcon name="user" :size="80" /></view><view class="profile-main"><text class="profile-name">{{ displayName }}</text><text class="profile-phone">{{ loggedIn ? maskedPhone : '登录后管理您的融资信息' }}</text></view><text class="account-state">{{ loggedIn ? '已登录' : '未登录' }}</text></view>
<view class="stat-row"><view class="stat-item" @click="go('/packages/finance/archive/archive')"><text class="stat-value">{{ completion }}%</text><text class="stat-label">档案完整度</text></view><view class="stat-item" @click="openReport"><text class="stat-value">{{ hasReport ? '已生成' : '暂无' }}</text><text class="stat-label">评估报告</text></view><view class="stat-item" @click="go('/packages/account/points/points')"><AppIcon name="points" :size="42" /><text class="stat-label">积分中心</text></view></view>
<view class="menu-list"><view class="menu-row" v-for="item in menus" :key="item.name" @click="open(item.key)"><AppIcon :name="item.icon" :size="46" /><text class="menu-name">{{ item.name }}</text><text v-if="item.note.length > 0" class="menu-note">{{ item.note }}</text><AppIcon name="chevron-right" :size="28" /></view></view>
<button class="logout" @click="logout">{{ loggedIn ? '退出登录' : '登录 / 注册' }}</button>
@@ -46,9 +46,9 @@
const open = (key: string) => {
if (key == 'archive') go('/packages/finance/archive/archive')
else if (key == 'help') go('/packages/finance/assistant/assistant')
else if (key == 'security') uni.showModal({ title: '账户安全', content: loggedIn.value ? '当前账户:' + maskedPhone.value + '\n当前为本地演示登录,尚未进行真实身份认证。请勿使用真实密码体验。' : '当前尚未登录,可使用手机号进入演示账户。', showCancel: false, confirmText: '我知道了' })
else if (key == 'security') uni.showModal({ title: '账户安全', content: loggedIn.value ? '当前账户:' + maskedPhone.value + '\n请妥善保管账户信息,不要向他人透露密码或验证码。' : '当前尚未登录,请先使用手机号登录。', showCancel: false, confirmText: '我知道了' })
else if (key == 'privacy') uni.showActionSheet({ itemList: ['查看隐私说明', '管理资料与授权'], success: (result) => { if (result.tapIndex == 0) showAgreementNotice('隐私说明'); else if (result.tapIndex == 1) { uni.setStorageSync('assessmentEditStep', 3); go('/packages/finance/assessment/assessment?type=' + activeAssessmentType()) } } })
else if (key == 'about') uni.showModal({ title: '关于 AI融资', content: '版本 V1.0.0\n帮助您整理融资信息、了解融资知识并体验智能方案匹配。当前为功能演示,评估结果不构成授信承诺。', showCancel: false, confirmText: '我知道了' })
else if (key == 'about') uni.showModal({ title: '关于 AI融资', content: '版本 V1.0.0\n帮助您整理融资信息、了解融资知识与融资方案。评估结果仅供参考,不构成授信承诺。', showCancel: false, confirmText: '我知道了' })
}
const openAccount = () => { if (!loggedIn.value) go('/packages/account/login/login'); else open('security') }
const logout = () => {
+2 -2
View File
@@ -5,8 +5,8 @@
<text class="analyzing-sub">正在整理融资评估信息…</text>
<view class="orbital"><view class="orbit orbit-one"></view><view class="orbit orbit-two"></view><view class="ai-core"><image src="/static/mobile/images/robot-cutout.png" mode="aspectFit" class="ai-core-image"></image></view></view>
<view class="analysis-list"><view class="analysis-item" v-for="(item,index) in items" :key="item"><view :class="progress > index ? 'analysis-check done' : 'analysis-check'"><text class="analysis-mark">{{ progress > index ? '✓' : '·' }}</text></view><text class="analysis-label">{{ item }}</text></view></view>
<text class="estimate">正在生成示例报告,请稍候…</text>
<text class="view-now" @click="finish">查看演示结果</text>
<text class="estimate">正在生成评估报告,请稍候…</text>
<text class="view-now" @click="finish">查看评估结果</text>
</view>
</template>
+3 -4
View File
@@ -7,7 +7,7 @@
<view class="confirm-tip"><view class="tip-icon"><text class="tip-mark">✓</text></view><view class="tip-copy"><text class="tip-title">{{ assessmentType == 'enterprise' ? '企业融资信息' : '个人融资信息' }}</text><text class="tip-desc">请确认后再开始评估,可随时返回修改。</text></view></view>
<text v-if="!hasDraft" class="empty-hint">还没有评估信息,请先完成信息填写。</text>
<view class="info-section" v-for="section in sections" :key="section.title"><view class="section-bar"><text class="section-name">{{ section.title }}</text><text class="edit" @click="edit(section.step)">编辑</text></view><template v-for="(row, index) in section.rows" :key="row.label"><view v-if="index < 3 || section.expanded" class="info-row"><text class="info-label">{{ row.label }}</text><text class="info-value">{{ row.value }}</text></view></template><text v-if="section.rows.length > 3" class="expand-link" @click="section.expanded = !section.expanded">{{ section.expanded ? '收起详情' : '查看其余信息' }}</text></view>
<text v-if="isDemo" class="demo-note">本次为演示评估,不会发起真实融资申请。</text>
<text class="assessment-note">提交信息用于融资评估,不代表已向金融机构提交贷款申请。</text>
<view class="scroll-spacer" :style="bottomStyle(24)"></view>
</view>
</scroll-view>
@@ -18,14 +18,13 @@
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
import AppHeader from '@/components/AppHeader.uvue'
import { API_BASE_URL, submitAssessment } from '@/packages/finance/services/api.uts'
import { submitAssessment } from '@/packages/finance/services/api.uts'
import { activeAssessmentType, loadAssessmentDraft, draftText, documentsFromDraft } from '@/services/assessment.uts'
const { pageStyle, contentStyle, bottomStyle } = usePageLayout()
type InfoRow = { label: string, value: string }
type InfoSection = { title: string, step: number, rows: InfoRow[], expanded: boolean }
const submitting = ref(false)
const hasDraft = ref(false)
const isDemo = API_BASE_URL.length == 0
const assessmentType = ref('personal')
const sections = ref<InfoSection[]>([])
const payload = ref<UTSJSONObject>({ source: 'mobile', confirmed: false })
@@ -114,7 +113,7 @@
.info-label { color: #758297; font-size: 25rpx; line-height: 38rpx; width: 176rpx; flex-shrink: 0; margin-right: 16rpx; }
.info-value { flex: 1; min-width: 0; text-align: right; color: #26374f; font-size: 26rpx; line-height: 38rpx; }
.expand-link { color: #7387a3; font-size: 24rpx; padding: 15rpx 0 10rpx; }
.demo-note { font-size: 23rpx; line-height: 36rpx; color: #8490a3; margin-top: 24rpx; }
.assessment-note { font-size: 23rpx; line-height: 36rpx; color: #8490a3; margin-top: 24rpx; }
.scroll-spacer { min-height: 152rpx; }
.submit-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 20rpx 30rpx; border-top: 1rpx solid #edf1f6; background-color: #fff; }
.submit-button { margin: 0; }
+2 -3
View File
@@ -8,7 +8,7 @@
<view v-else-if="errorMessage.length > 0" class="state-panel"><text class="state-text">{{ errorMessage }}</text><button class="retry-button" @click="loadMatches">重新加载</button></view>
<view v-else-if="matches.length == 0" class="state-panel"><text class="state-text">暂无可展示方案,请完善评估资料后重试</text></view>
<view v-else>
<text class="result-summary">共 {{ matches.length }} 个方案 · {{ isDemo ? '示例数据' : '按您的评估信息匹配' }}</text>
<text class="result-summary">共 {{ matches.length }} 个方案 · 了解额度与申请条件</text>
<view class="match-card" v-for="item in sortedMatches" :key="item.id" @click="openProduct(item.id)">
<view class="bank-row"><view class="bank-logo" :style="'background-color:' + item.color"><text class="bank-initial">{{ item.initials }}</text></view><view class="bank-copy"><text class="bank-name">{{ item.bank }}</text><text class="match-tag">个人信用融资</text></view><text class="match-score">{{ item.score }}%匹配</text></view>
<view class="metrics"><view class="metric"><text class="metric-value">{{ item.amount }}<text class="metric-unit"> 万</text></text><text class="metric-label">最高额度</text></view><view class="metric"><text class="metric-value rate-value">{{ item.rate.toFixed(2) }}%</text><text class="metric-label">年化利率(起)</text></view><view class="metric term-metric"><text class="term-value">{{ item.term }}</text><text class="metric-label">贷款期限</text></view></view>
@@ -25,7 +25,7 @@
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
import { FinanceProduct } from '@/packages/finance/services/finance.uts'
import { fetchMatches, API_BASE_URL } from '@/packages/finance/services/api.uts'
import { fetchMatches } from '@/packages/finance/services/api.uts'
import AppHeader from '@/components/AppHeader.uvue'
import BottomNav from '@/components/BottomNav.uvue'
import AppIcon from '@/components/AppIcon.uvue'
@@ -36,7 +36,6 @@
const matches = ref<FinanceProduct[]>([])
const loading = ref(true)
const errorMessage = ref('')
const isDemo = API_BASE_URL.length == 0
const sortedMatches = computed((): FinanceProduct[] => {
const items = matches.value.slice()
items.sort((a: FinanceProduct, b: FinanceProduct): number => {
+11 -3
View File
@@ -1,17 +1,24 @@
<template>
<view class="page" :style="pageStyle()">
<AppHeader title="产品详情"><text v-if="available" class="share" @click="shareProduct">分享</text></AppHeader>
<AppHeader title="产品详情">
<!-- #ifndef MP-WEIXIN -->
<text v-if="available" class="share" @click="shareProduct">分享</text>
<!-- #endif -->
</AppHeader>
<scroll-view scroll-y :style="contentStyle(224, false)">
<view class="product-content">
<view v-if="loading" class="state"><text class="state-text">正在加载产品…</text></view>
<view v-else-if="!available" class="state"><text class="state-title">{{ loadFailed ? '产品加载失败' : '暂未找到该产品' }}</text><text class="state-text">请重试或返回方案列表选择其他产品</text><button class="retry-button" @click="loadProduct">重新加载</button></view>
<view v-else>
<view class="bank-head"><view class="bank-mark" :style="'background-color:' + product.color"><text class="bank-initial">{{ product.initials }}</text></view><view class="bank-copy"><text class="product-name">{{ product.bank }}</text><text class="product-tags">个人信用融资 · {{ isDemo ? '示例产品' : '产品信息' }}</text></view></view>
<!-- #ifdef MP-WEIXIN -->
<view class="product-actions"><text class="share" @click="shareProduct">分享产品</text></view>
<!-- #endif -->
<view class="bank-head"><view class="bank-mark" :style="'background-color:' + product.color"><text class="bank-initial">{{ product.initials }}</text></view><view class="bank-copy"><text class="product-name">{{ product.bank }}</text><text class="product-tags">个人信用融资 · 产品信息</text></view></view>
<view class="product-metrics"><view class="metric"><text class="metric-label">最高额度</text><text class="amount">{{ product.amount }}万</text></view><view class="metric"><text class="metric-label">年化利率(起)</text><text class="rate">{{ product.rate.toFixed(2) }}%</text></view><view class="metric last-metric"><text class="metric-label">贷款期限</text><text class="term">{{ product.term }}</text></view></view>
<view class="detail-section"><text class="section-title">产品优势</text><view class="advantage-grid"><view class="advantage" v-for="item in product.advantages" :key="item"><AppIcon name="check-circle" :size="30" /><text class="advantage-label">{{ item }}</text></view></view></view>
<view class="detail-section"><text class="section-title">申请条件</text><view class="condition"><text class="condition-label">年龄要求</text><text class="condition-value">{{ product.age }}</text></view><view class="condition"><text class="condition-label">工作要求</text><text class="condition-value">{{ product.work }}</text></view><view class="condition"><text class="condition-label">信用要求</text><text class="condition-value">{{ product.credit }}</text></view></view>
<view class="detail-section"><text class="section-title">所需材料</text><text class="material">{{ product.materials }}</text></view>
<text class="product-note">额度、利率及申请条件仅供参考,以机构实际审批为准。{{ isDemo ? '当前可体验申请准备流程,尚未向机构提交资料。' : '' }}</text>
<text class="product-note">额度、利率及申请条件仅供参考,以机构实际审批为准。{{ isDemo ? '当前可准备申请资料,尚未向机构提交。' : '' }}</text>
</view>
</view>
</scroll-view>
@@ -62,6 +69,7 @@
</script>
<style>
.product-actions { flex-direction: row; justify-content: flex-end; margin-bottom: 12rpx; }
.share { color: #0868f7; font-size: 25rpx; padding: 16rpx 0 16rpx 16rpx; }
.product-content { padding: 24rpx 28rpx 60rpx; }
.bank-head { padding: 26rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e6edf6; flex-direction: row; align-items: center; }
+2 -2
View File
@@ -3,9 +3,9 @@
<AppHeader title="融资需求报告" />
<scroll-view scroll-y class="report-scroll" :style="contentStyle(96, false)">
<view class="report-content">
<view class="score-panel"><text class="score-label">综合评分 · 示例报告</text><view class="score-row"><text class="big-score">85</text><text class="score-unit">分</text><text class="score-badge">良好</text></view><text class="score-caption">从五个维度了解融资资质</text></view>
<view class="score-panel"><text class="score-label">综合评分</text><view class="score-row"><text class="big-score">85</text><text class="score-unit">分</text><text class="score-badge">良好</text></view><text class="score-caption">从五个维度了解融资资质</text></view>
<view class="radar-panel"><image class="radar-image" src="/packages/finance/static/finance-radar.png" mode="aspectFit"></image><text class="radar-label top">还款能力</text><text class="radar-label left">收入稳定</text><text class="radar-label right">信用状况</text><text class="radar-label bottom-left">资产条件</text><text class="radar-label bottom-right">负债情况</text></view>
<view class="conclusion"><text class="section-title">评估结论</text><text class="conclusion-text">示例资质整体良好,可进一步比较信用融资产品的额度、期限与还款安排。</text><view class="suggest-row"><text class="suggest-label">建议期限</text><text class="suggest-value">6–36个月</text></view><text class="report-note">本报告为体验示例,正式评估以授权资料和机构审核为准。</text></view>
<view class="conclusion"><text class="section-title">评估结论</text><text class="conclusion-text">可进一步比较信用融资产品的额度、期限与还款安排,结合自身需求选择合适方案。</text><view class="suggest-row"><text class="suggest-label">建议期限</text><text class="suggest-value">6–36个月</text></view><text class="report-note">报告内容仅供参考,实际融资结果以授权资料和机构审核为准。</text></view>
<button class="primary-button" @click="viewMatches">查看匹配方案</button>
</view>
</scroll-view>
+10 -12
View File
@@ -2,12 +2,9 @@
<view class="page" :style="pageStyle()">
<view class="home-header">
<view class="safe" :style="statusStyle(0)"></view>
<view class="home-bar">
<BrandLogo />
<view class="header-actions">
<view class="round-action" @click="go('/packages/finance/assistant/assistant')">
<view class="action-surface"><image class="header-icon" src="/static/icons/sparkles.svg" mode="aspectFit" /></view>
</view>
<view class="home-bar" :style="navigationStyle()">
<view class="home-brand" :style="navigationBrandStyle(104)"><BrandLogo /></view>
<view class="header-actions" :style="navigationActionStyle(104)">
<view class="round-action notify" @click="go('/packages/account/messages/messages')">
<view class="action-surface"><image class="header-icon" src="/static/icons/bell.svg" mode="aspectFit" /></view>
<view v-if="hasUnread" class="red-dot"></view>
@@ -33,7 +30,7 @@
</view>
<view class="archive-entry" @click="go('/packages/finance/archive/archive')"><AppIcon name="archive" :size="42" /><text class="archive-label">我的融资档案</text><text class="archive-action">去完善</text><AppIcon name="chevron-right" :size="28" /></view>
<view class="section-head"><text class="section-title">我的融资方案</text><view class="more-link" @click="go('/packages/finance/matches/matches')"><text class="more">查看全部</text><AppIcon name="chevron-right" :size="24" /></view></view>
<view class="plan-card" @click="go('/packages/finance/matches/matches')"><view class="plan-main"><text class="plan-name">信用融资方案</text><text class="plan-meta">了解额度、利率与申请条件</text></view><view class="score"><text class="score-number">3</text><text class="score-label">示例方案</text></view></view>
<view class="plan-card" @click="go('/packages/finance/matches/matches')"><view class="plan-main"><text class="plan-name">信用融资方案</text><text class="plan-meta">了解额度、利率与申请条件</text></view><view class="score"><text class="score-number">3</text><text class="score-label">可查看方案</text></view></view>
<view class="section-head"><text class="section-title">融资课堂</text><view class="more-link" @click="go('/packages/content/classroom/classroom')"><text class="more">进入课堂</text><AppIcon name="chevron-right" :size="24" /></view></view>
<view class="news-preview" @click="go('/packages/content/classroom/classroom')"><view class="news-copy"><text class="news-title">如何提高个人贷款通过率</text><text class="news-meta">信用管理 · 6分钟阅读</text></view><image class="news-art" src="/static/mobile/images/news-calculator.png" mode="aspectFill"></image></view>
</view>
@@ -44,7 +41,7 @@
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { contentStyle, statusStyle, pageStyle } = usePageLayout()
const { contentStyle, statusStyle, pageStyle, navigationStyle, navigationActionStyle, navigationBrandStyle } = usePageLayout()
import BrandLogo from '@/components/BrandLogo.uvue'
import BottomNav from '@/components/BottomNav.uvue'
import AppIcon from '@/components/AppIcon.uvue'
@@ -61,10 +58,11 @@
<style>
.home-header { background-color: #ffffff; }
.safe { }
.home-bar { height: 104rpx; padding: 0 28rpx; flex-direction: row; align-items: center; justify-content: space-between; }
.header-actions { flex-direction: row; }
.round-action { width: 62rpx; height: 62rpx; margin-left: 16rpx; position: relative; flex-shrink: 0; }
.action-surface { width: 62rpx; height: 62rpx; border-radius: 50%; background-color: #eef5ff; align-items: center; justify-content: center; }
.home-bar { height: 104rpx; padding: 0 28rpx; flex-direction: row; align-items: center; justify-content: space-between; position: relative; }
.home-brand { flex-shrink: 0; justify-content: center; }
.header-actions { width: 62rpx; height: 62rpx; margin-left: 16rpx; flex-shrink: 0; }
.round-action { width: 100%; height: 100%; position: relative; flex-shrink: 0; }
.action-surface { width: 100%; height: 100%; border-radius: 50%; background-color: #eef5ff; align-items: center; justify-content: center; }
.header-icon { width: 36rpx; height: 36rpx; flex-shrink: 0; }
/* 红点与圆形背景同级,避免被跨端 view 的圆角裁切。 */
.red-dot { position: absolute; width: 16rpx; height: 16rpx; right: 2rpx; top: 2rpx; z-index: 1; background-color: #ff4d4f; border-radius: 50%; border: 2rpx solid #fff; }
+1 -1
View File
@@ -65,7 +65,7 @@ export function saveAssessmentDraft(type: string, step: number, form: Assessment
uni.setStorageSync('assessmentDraft', payload)
}
export function showAgreementNotice(title: string): void {
uni.showModal({ title, content: '正式协议尚未配置。当前为功能演示,勾选仅记录本地选择,不会发起实名认证、查询外部数据或上传资料。正式服务开放前,将展示完整协议供您阅读与确认。', showCancel: false, confirmText: '我知道了' })
uni.showModal({ title, content: '完整协议将在相关服务开放时提供。当前勾选仅保存您的选择,不会发起实名认证、查询外部数据或上传资料。', showCancel: false, confirmText: '我知道了' })
}
export type AssessmentProgress = { percent: number, basicDone: boolean, incomeDone: boolean, assetsDone: boolean, authorizationSelected: boolean }
+66 -3
View File
@@ -5,13 +5,32 @@ export type PageLayout = {
pageStyle: () => string,
contentStyle: (offset: number, minimum: boolean) => string,
statusStyle: (offset: number) => string,
navigationStyle: () => string,
navigationTitleStyle: () => string,
navigationActionStyle: (headerHeight: number) => string,
navigationBrandStyle: (headerHeight: number) => string,
bottomStyle: (padding: number) => string
}
// 原生渲染不支持 vh/calc,统一以窗口尺寸计算,并随旋转更新。
export function usePageLayout(): PageLayout {
const windowInfo = ref(uni.getWindowInfo())
const refresh = () => { windowInfo.value = uni.getWindowInfo() }
const capsuleLeft = ref(-1)
const capsuleTop = ref(0)
const capsuleHeight = ref(0)
const readCapsule = () => {
// #ifdef MP-WEIXIN
try {
const capsule = uni.getMenuButtonBoundingClientRect()
const valid = capsule.width > 0 && capsule.height > 0 && capsule.left > 0 && capsule.left < windowInfo.value.windowWidth
capsuleLeft.value = valid ? capsule.left : -1
capsuleTop.value = valid ? capsule.top : 0
capsuleHeight.value = valid ? capsule.height : 0
} catch (_error) { capsuleLeft.value = -1; capsuleTop.value = 0; capsuleHeight.value = 0 }
// #endif
}
readCapsule()
const refresh = () => { windowInfo.value = uni.getWindowInfo(); readCapsule() }
onMounted(() => refresh())
// #ifdef APP
onResize((_options) => { refresh() })
@@ -22,7 +41,51 @@ export function usePageLayout(): PageLayout {
onUnmounted(() => { uni.offWindowResize(handleResize) })
// #endif
const rpx = (value: number): number => value * windowInfo.value.windowWidth / 750
const status = (): number => windowInfo.value.statusBarHeight ?? 0
const status = (): number => Math.max(windowInfo.value.statusBarHeight ?? 0, windowInfo.value.safeAreaInsets?.top ?? 0)
const navigationStyle = (): string => {
// 只在微信端横向避让胶囊,保持业务导航原有高度。
// #ifdef MP-WEIXIN
const reserved = capsuleLeft.value > 0 ? windowInfo.value.windowWidth - capsuleLeft.value + 8 : 104
return 'padding-right:' + reserved + 'px;'
// #endif
// #ifndef MP-WEIXIN
return ''
// #endif
}
const navigationTitleStyle = (): string => {
// 标题左右使用相同留白,以屏幕中心对齐,同时避开微信胶囊。
// #ifdef MP-WEIXIN
const reserved = capsuleLeft.value > 0 ? windowInfo.value.windowWidth - capsuleLeft.value + 8 : 104
const inset = Math.max(rpx(112), reserved)
return 'left:' + inset + 'px;right:' + inset + 'px;'
// #endif
// #ifndef MP-WEIXIN
return ''
// #endif
}
const navigationActionStyle = (headerHeight: number): string => {
// 微信通知按钮与系统胶囊等高、顶部对齐;App 保持导航行内居中。
// #ifdef MP-WEIXIN
const size = capsuleHeight.value > 0 ? capsuleHeight.value : 32
const top = capsuleHeight.value > 0 ? Math.max(0, capsuleTop.value - status()) : Math.max(0, (rpx(headerHeight) - size) / 2)
const right = capsuleLeft.value > 0 ? windowInfo.value.windowWidth - capsuleLeft.value + 8 : 104
return 'position:absolute;top:' + top + 'px;right:' + right + 'px;width:' + size + 'px;height:' + size + 'px;'
// #endif
// #ifndef MP-WEIXIN
return ''
// #endif
}
const navigationBrandStyle = (headerHeight: number): string => {
// Logo 保留自身比例,与胶囊共享垂直中心,不改变登录页和启动页的 Logo。
// #ifdef MP-WEIXIN
const height = rpx(68)
const center = capsuleHeight.value > 0 ? capsuleTop.value - status() + capsuleHeight.value / 2 : rpx(headerHeight) / 2
return 'align-self:flex-start;margin-top:' + Math.max(0, center - height / 2) + 'px;height:' + height + 'px;'
// #endif
// #ifndef MP-WEIXIN
return ''
// #endif
}
const pageStyle = (): string => 'min-height:' + windowInfo.value.windowHeight + 'px;'
const contentStyle = (offset: number, minimum: boolean): string => {
// #ifdef WEB
@@ -35,5 +98,5 @@ export function usePageLayout(): PageLayout {
}
const statusStyle = (offset: number): string => 'height:' + (status() + rpx(offset)) + 'px;'
const bottomStyle = (padding: number): string => 'padding-bottom:' + (rpx(padding) + (windowInfo.value.safeAreaInsets?.bottom ?? 0)) + 'px;'
return { pageStyle, contentStyle, statusStyle, bottomStyle }
return { pageStyle, contentStyle, statusStyle, navigationStyle, navigationTitleStyle, navigationActionStyle, navigationBrandStyle, bottomStyle }
}