refactor: 重构项目分包结构与资源管理

1.  调整页面路由,将业务页面迁移到对应分包,主包仅保留首页
2.  拆分公共服务模块,将账户、融资相关逻辑移入对应分包
3.  统一资源路径,将专属静态资源放入各分包static目录,更新导航栏图标资源
4.  修复启动引导逻辑,实现仅首次冷启动显示引导页
5.  更新README文档,补充分包配置与开发规范说明
This commit is contained in:
hujun
2026-09-20 10:52:10 +08:00
parent 94a4558290
commit 69130d11e7
43 changed files with 278 additions and 180 deletions
+95
View File
@@ -0,0 +1,95 @@
<template>
<view class="page" :style="pageStyle()">
<AppHeader title="产品详情"><text v-if="available" class="share" @click="shareProduct">分享</text></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>
<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>
</view>
</view>
</scroll-view>
<view v-if="available" class="apply-bar" :style="bottomStyle(18)"><button class="primary-button apply-button" @click="apply">准备申请</button></view>
</view>
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
import { FinanceProduct, demoProducts } from '@/packages/finance/services/finance.uts'
import { fetchProduct, API_BASE_URL } from '@/packages/finance/services/api.uts'
import AppHeader from '@/components/AppHeader.uvue'
import AppIcon from '@/components/AppIcon.uvue'
const { bottomStyle, contentStyle, pageStyle } = usePageLayout()
const product = ref<FinanceProduct>(demoProducts[0])
const available = ref(false)
const productId = ref('')
const loading = ref(true)
const loadFailed = ref(false)
const isDemo = API_BASE_URL.length == 0
const loadProduct = async () => {
loading.value = true
loadFailed.value = false
available.value = false
try {
const selected = await fetchProduct(productId.value)
if (selected != null) { product.value = selected; available.value = true }
} catch (error) { loadFailed.value = true }
loading.value = false
}
onLoad((options) => {
productId.value = options?.['id'] ?? ''
loadProduct()
})
const shareProduct = () => {
const item = product.value
if (!available.value) return
uni.setClipboardData({ data: item.bank + '|最高' + item.amount + '万元|年化利率' + item.rate.toFixed(2) + '%起|' + item.term + '。展示信息仅供参考,以机构审批为准。' })
}
const apply = () => {
uni.showModal({
title: '准备申请资料',
content: '先完善融资档案,核对申请所需材料。当前尚未开通机构申请,资料不会提交至银行。',
confirmText: '完善档案',
success: (result) => { if (result.confirm) uni.navigateTo({ url: '/packages/finance/archive/archive' }) }
})
}
</script>
<style>
.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; }
.bank-mark { width: 80rpx; height: 80rpx; border-radius: 24rpx; align-items: center; justify-content: center; margin-right: 20rpx; flex-shrink: 0; }
.bank-initial { color: #fff; font-size: 36rpx; font-weight: 700; }
.bank-copy { flex: 1; }
.product-name { font-size: 30rpx; font-weight: 700; color: #20324d; }
.product-tags { color: #75859b; font-size: 22rpx; margin-top: 10rpx; }
.product-metrics { margin-top: 20rpx; padding: 30rpx 12rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e6edf6; flex-direction: row; }
.metric { flex: 1; align-items: center; border-right: 1rpx solid #edf1f6; }
.last-metric { border-right: 0; }
.metric-label { color: #75859b; font-size: 21rpx; }
.amount, .rate, .term { margin-top: 14rpx; font-size: 36rpx; line-height: 46rpx; font-weight: 800; color: #20324d; }
.rate { color: #0868f7; }
.term { font-size: 27rpx; }
.detail-section { margin-top: 20rpx; padding: 26rpx; background-color: #fff; border-radius: 18rpx; border: 1rpx solid #e6edf6; }
.advantage-grid { margin-top: 24rpx; flex-direction: row; flex-wrap: wrap; }
.advantage { width: 50%; flex-direction: row; align-items: center; margin-bottom: 16rpx; }
.advantage-label { margin-left: 12rpx; font-size: 25rpx; color: #435168; }
.condition { min-height: 78rpx; padding: 14rpx 0; flex-direction: row; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #edf1f6; }
.condition-label { color: #748399; font-size: 24rpx; flex-shrink: 0; margin-right: 20rpx; }
.condition-value { flex: 1; text-align: right; color: #273b54; font-size: 24rpx; line-height: 36rpx; }
.material { margin-top: 18rpx; font-size: 24rpx; line-height: 38rpx; color: #53667f; }
.product-note { margin: 22rpx 6rpx 0; color: #7a899e; font-size: 21rpx; line-height: 34rpx; }
.apply-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 18rpx 28rpx; background-color: #fff; border-top: 1rpx solid #e7edf6; }
.apply-button { margin: 0; }
.state { padding: 80rpx 24rpx; align-items: center; }
.state-title { font-size: 30rpx; color: #20324d; font-weight: 600; margin-bottom: 14rpx; }
.state-text { font-size: 25rpx; line-height: 40rpx; text-align: center; color: #75859b; }
.retry-button { margin-top: 24rpx; color: #0868f7; background-color: #e8f2ff; font-size: 25rpx; }
</style>