refactor: 重构移动端页面布局与资源路径,优化打包配置

1. 新增layout.uts统一管理页面尺寸与安全区计算
2. 修正图片资源路径到static/mobile目录
3. 调整App.vue与AppIcon组件样式适配多端
4. 为微信小程序配置打包忽略规则减少包体积
5. 修复API请求错误抛出类型,完善类型定义
6. 补充README文档说明项目运行与维护规范
7. 优化多个页面的样式与生命周期逻辑
This commit is contained in:
hujun
2026-09-15 16:16:55 +08:00
parent 626cef9d38
commit b0ee4f9d7e
67 changed files with 80727 additions and 189 deletions
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -18,10 +18,12 @@
</script>
<style>
/* #ifndef APP */
page { background-color: #f5f8fc; color: #13213c; font-family: "PingFang SC", "Microsoft YaHei", sans-serif; }
view, text, image, input, textarea, button, scroll-view { box-sizing: border-box; }
button::after { border: 0; }
.page { min-height: 100vh; background-color: #f5f8fc; }
/* #endif */
.page { background-color: #f5f8fc; }
.page-white { background-color: #ffffff; }
.content { padding: 24rpx 28rpx 190rpx; }
.primary-button, .orange-button { height: 92rpx; border-radius: 16rpx; color: #ffffff; font-size: 30rpx; font-weight: 700; line-height: 92rpx; text-align: center; box-shadow: 0 14rpx 28rpx rgba(0, 100, 255, 0.20); }
+38
View File
@@ -0,0 +1,38 @@
# AI 融资移动端
基于 uni-app x、Vue 3、UTS 的移动端原型,包含 19 个页面。使用 HBuilderX 5.24(VDOM 模式)开发;本项目没有独立的 npm 构建入口。
## 本地运行
在 HBuilderX 中导入当前目录,选择“运行 → 运行到浏览器”。当前接口由 `services/api.uts` 提供本地模拟实现,没有连接真实短信、贷款申请或 AI 服务。
也可使用 HBuilderX CLI。以下 PowerShell 命令中的安装路径应按本机环境调整;在项目根目录执行:
```powershell
$hbuilderCli = 'D:\Develop\HBuilderX\cli.exe'
$projectPath = (Get-Location).Path
& $hbuilderCli open
& $hbuilderCli launch web --project $projectPath --compile false --continue-on-error false
```
## 各平台编译
以下命令仅本地编译,不上传、不发布:
```powershell
& $hbuilderCli launch mp-weixin --project $projectPath --compile true --continue-on-error false
& $hbuilderCli launch app-android --project $projectPath --compile true --continue-on-error false
& $hbuilderCli launch app-ios --project $projectPath --iosTarget simulator --compile true --continue-on-error false
```
产物位于 `unpackage/dist/dev/` 对应平台目录。Android 命令可检查 Kotlin/class 编译;Windows 上 iOS 命令只检查当前编译链生成的源码产物,不代表已完成 Xcode 构建或生成 IPA。
运行微信小程序需配置自己的 AppID,并开启微信开发者工具服务端口。Android 真机运行需启用 USB 调试;iOS 真机运行需相应签名环境。正式发行还需配置应用图标、签名、真实接口及平台要求的资料,并完成真机测试。
## 跨端维护约定
- 页面尺寸和安全区计算集中在 `services/layout.uts`。新增页面应复用这里的逻辑,并检查小屏、横屏、键盘弹出及底部安全区。
- 原生样式遵循 uni-app x 支持范围。仅 Web/小程序支持的样式用条件编译隔离;修改 UTS 类型后应重新验证 Android 编译。
- 线上使用的轻量位图位于 `static/mobile/`,原始图片保留在 `static/images/` 与 `static/icons-generated/`。箭头继续使用 `static/icons/` 下的 SVG。
- 微信打包忽略原始图片目录和未使用的 `static/logo.png`,规则位于 `manifest.json` 的 `mp-weixin.packOptions.ignore`。新增资源要检查引用与排除规则,最终包体以微信开发者工具结果为准。
- API 地址目前为空,表示模拟模式。接入后端时需补齐环境配置、认证、错误契约及真实业务校验,再进行联调。
+4 -2
View File
@@ -1,6 +1,6 @@
<template>
<view class="header-wrap">
<view class="status-bar"></view>
<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="34" class="back-icon" /></view>
<text class="header-title">{{ title }}</text>
@@ -10,6 +10,8 @@
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { statusStyle } = usePageLayout()
import AppIcon from '@/components/AppIcon.uvue'
const props = defineProps({ title: { type: String, default: '' }, back: { type: Boolean, default: true } })
const handleBack = () => {
@@ -22,7 +24,7 @@
<style>
.header-wrap { background-color: #ffffff; }
.status-bar { height: var(--status-bar-height); }
.status-bar { }
.app-header { height: 96rpx; padding: 0 24rpx; flex-direction: row; align-items: center; justify-content: space-between; border-bottom: 1rpx solid #edf1f7; }
.header-side { width: 100rpx; height: 96rpx; align-items: flex-start; justify-content: center; }
.header-right { align-items: flex-end; }
+8 -2
View File
@@ -5,10 +5,16 @@
<script setup lang="uts">
const props = defineProps({ name: { type: String, default: 'sparkles' }, size: { type: Number, default: 42 }, muted: { type: Boolean, default: false } })
const isVector = computed(() => props.name == 'chevron-right' || props.name == 'arrow-right')
const iconSrc = computed(() => isVector.value ? '/static/icons/' + (props.name == 'chevron-right' ? 'chevron-circle' : 'arrow-right') + '.svg' : '/static/icons-generated/' + props.name + '.png')
const iconSrc = computed(() => isVector.value ? '/static/icons/' + (props.name == 'chevron-right' ? 'chevron-circle' : 'arrow-right') + '.svg' : '/static/mobile/icons-generated/' + props.name + '.png')
const iconStyle = computed(() => 'width:' + props.size + 'rpx;height:' + props.size + 'rpx;')
</script>
<style>
.app-icon { display: block; flex-shrink: 0; transform: scale(1.2); }.vector-icon { transform: none; }.muted-icon { opacity: .48; }
.app-icon {
/* #ifndef APP */
display: block;
/* #endif */
flex-shrink: 0; transform: scale(1.2); }
.vector-icon { transform: none; }
.muted-icon { opacity: .48; }
</style>
+7
View File
@@ -12,6 +12,13 @@
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "",
"packOptions" : {
"ignore" : [
{ "type": "folder", "value": "static/images" },
{ "type": "folder", "value": "static/icons-generated" },
{ "type": "file", "value": "static/logo.png" }
]
},
"setting" : {
"urlCheck" : false
},
+38 -9
View File
@@ -1,9 +1,9 @@
<template>
<view class="analyzing-page">
<view class="safe"></view>
<view class="analyzing-page" :style="pageStyle()">
<view class="safe" :style="statusStyle(80)"></view>
<text class="analyzing-title">AI分析中</text>
<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/images/robot-cutout.png" mode="aspectFit"></image></view></view>
<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>{{ progress > index ? '✓' : '·' }}</text></view><text>{{ item }}</text></view></view>
<text class="estimate">预计需要30-60秒,请稍候...</text>
<text class="view-now" @click="finish">查看演示结果</text>
@@ -11,15 +11,44 @@
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { statusStyle, pageStyle } = usePageLayout()
const progress = ref(0)
const items = ['数据解析中', '信用评估中', '匹配方案中', '风险检测中']
let timer = setInterval(() => { if (progress.value < 4) progress.value++; else clearInterval(timer) }, 550)
const finish = () => { clearInterval(timer); uni.redirectTo({ url: '/pages/report/report' }) }
setTimeout(() => finish(), 3200)
let timer = 0
let finishTimer = 0
let finished = false
const finish = () => {
if (finished) return
finished = true
clearInterval(timer)
clearTimeout(finishTimer)
uni.redirectTo({ url: '/pages/report/report' })
}
onShow(() => {
if (finished) return
timer = setInterval(() => { if (progress.value < 4) progress.value++; else clearInterval(timer) }, 550)
finishTimer = setTimeout(() => finish(), 3200)
})
onHide(() => { clearInterval(timer); clearTimeout(finishTimer) })
onUnmounted(() => { clearInterval(timer); clearTimeout(finishTimer) })
</script>
<style>
.analyzing-page { min-height: 100vh; background: linear-gradient(160deg, #061c49, #002c75); align-items: center; color: #fff; }.safe { height: calc(var(--status-bar-height) + 80rpx); }.analyzing-title { font-size: 45rpx; font-weight: 800; }.analyzing-sub { margin-top: 20rpx; color: #c6d9ff; font-size: 25rpx; }
.orbital { width: 400rpx; height: 400rpx; margin-top: 55rpx; align-items: center; justify-content: center; position: relative; }.orbit { position: absolute; border-radius: 50%; border: 2rpx solid rgba(79, 176, 255, .46); }.orbit-one { width: 350rpx; height: 350rpx; }.orbit-two { width: 280rpx; height: 280rpx; border-color: rgba(57, 235, 255, .5); }.ai-core { width: 220rpx; height: 220rpx; border-radius: 50%; background-color: rgba(10, 102, 247, .32); box-shadow: 0 0 70rpx rgba(0, 165, 255, .48); align-items: center; justify-content: center; }.ai-core image { width: 185rpx; height: 185rpx; border-radius: 50%; }
.analysis-list { margin-top: 30rpx; width: 390rpx; }.analysis-item { flex-direction: row; align-items: center; margin-top: 23rpx; font-size: 26rpx; color: #d8e6ff; }.analysis-check { width: 38rpx; height: 38rpx; border-radius: 50%; border: 2rpx solid #4c73a7; align-items: center; justify-content: center; margin-right: 17rpx; }.analysis-check.done { background-color: #158aff; border-color: #41d7ff; color: white; }.estimate { margin-top: 64rpx; color: #8faeda; font-size: 22rpx; }.view-now { margin-top: 28rpx; color: #62c8ff; font-size: 23rpx; }
.analyzing-page { background: linear-gradient(160deg, #061c49, #002c75); align-items: center; color: #fff; }
.safe { }
.analyzing-title { font-size: 45rpx; font-weight: 800; }
.analyzing-sub { margin-top: 20rpx; color: #c6d9ff; font-size: 25rpx; }
.orbital { width: 400rpx; height: 400rpx; margin-top: 55rpx; align-items: center; justify-content: center; position: relative; }
.orbit { position: absolute; border-radius: 50%; border: 2rpx solid rgba(79, 176, 255, .46); }
.orbit-one { width: 350rpx; height: 350rpx; }
.orbit-two { width: 280rpx; height: 280rpx; border-color: rgba(57, 235, 255, .5); }
.ai-core { width: 220rpx; height: 220rpx; border-radius: 50%; background-color: rgba(10, 102, 247, .32); box-shadow: 0 0 70rpx rgba(0, 165, 255, .48); align-items: center; justify-content: center; }
.ai-core-image { width: 185rpx; height: 185rpx; border-radius: 50%; }
.analysis-list { margin-top: 30rpx; width: 390rpx; }
.analysis-item { flex-direction: row; align-items: center; margin-top: 23rpx; font-size: 26rpx; color: #d8e6ff; }
.analysis-check { width: 38rpx; height: 38rpx; border-radius: 50%; border: 2rpx solid #4c73a7; align-items: center; justify-content: center; margin-right: 17rpx; }
.analysis-check.done { background-color: #158aff; border-color: #41d7ff; color: white; }
.estimate { margin-top: 64rpx; color: #8faeda; font-size: 22rpx; }
.view-now { margin-top: 28rpx; color: #62c8ff; font-size: 23rpx; }
</style>
+22 -4
View File
@@ -1,5 +1,5 @@
<template>
<view class="page">
<view class="page" :style="pageStyle()">
<AppHeader title="我的融资档案" />
<view class="archive-content">
<view class="completion"><view class="completion-head"><text class="completion-title">档案完整度 75%</text><text class="completion-score">75%</text></view><text class="completion-desc">完善信息可获得更精准的融资方案</text><view class="progress-track"><view class="progress-bar"></view></view></view>
@@ -10,14 +10,32 @@
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import AppIcon from '@/components/AppIcon.uvue'
const archiveItems = [{ icon: 'file-text', name: '基本信息', done: true }, { icon: 'income', name: '经营 / 收入信息', done: true }, { icon: 'asset', name: '资产 / 负债信息', done: true }, { icon: 'authorization', name: '资料授权', done: false }]
type ArchiveItem = { icon: string, name: string, done: boolean }
const archiveItems: ArchiveItem[] = [{ icon: 'file-text', name: '基本信息', done: true }, { icon: 'income', name: '经营 / 收入信息', done: true }, { icon: 'asset', name: '资产 / 负债信息', done: true }, { icon: 'authorization', name: '资料授权', done: false }]
const edit = (name: string) => { uni.showToast({ title: '编辑' + name, icon: 'none' }) }
const complete = () => { uni.navigateTo({ url: '/pages/assessment/assessment' }) }
</script>
<style>
.archive-content { padding: 26rpx 28rpx; }.completion { padding: 30rpx; border-radius: 18rpx; background: linear-gradient(140deg, #e9f3ff, #f8fbff); border: 1rpx solid #dce9fa; }.completion-head { flex-direction: row; justify-content: space-between; align-items: center; }.completion-title { font-size: 30rpx; font-weight: 800; }.completion-score { color: #0868f7; font-size: 27rpx; font-weight: 800; }.completion-desc { color: #748196; font-size: 22rpx; margin-top: 12rpx; }.progress-track { height: 15rpx; border-radius: 8rpx; background-color: #dce8f6; margin-top: 24rpx; overflow: hidden; }.progress-bar { width: 75%; height: 15rpx; border-radius: 8rpx; background-color: #0868f7; }
.archive-list { margin-top: 24rpx; background-color: #fff; border: 1rpx solid #e5ecf5; border-radius: 18rpx; padding: 0 24rpx; }.archive-row { height: 110rpx; flex-direction: row; align-items: center; border-bottom: 1rpx solid #edf1f6; }.archive-icon { width: 54rpx; height: 54rpx; border-radius: 17rpx; background-color: #e8f2ff; color: #0868f7; align-items: center; justify-content: center; font-size: 21rpx; font-weight: 800; margin-right: 17rpx; }.archive-name { flex: 1; font-size: 25rpx; }.state { font-size: 22rpx; margin-right: 14rpx; }.done { color: #11aa76; }.pending { color: #ff7917; }.chevron { color: #a7b2c1; font-size: 36rpx; }.complete-button { margin-top: 34rpx; }
.archive-content { padding: 26rpx 28rpx; }
.completion { padding: 30rpx; border-radius: 18rpx; background: linear-gradient(140deg, #e9f3ff, #f8fbff); border: 1rpx solid #dce9fa; }
.completion-head { flex-direction: row; justify-content: space-between; align-items: center; }
.completion-title { font-size: 30rpx; font-weight: 800; }
.completion-score { color: #0868f7; font-size: 27rpx; font-weight: 800; }
.completion-desc { color: #748196; font-size: 22rpx; margin-top: 12rpx; }
.progress-track { height: 15rpx; border-radius: 8rpx; background-color: #dce8f6; margin-top: 24rpx; overflow: hidden; }
.progress-bar { width: 75%; height: 15rpx; border-radius: 8rpx; background-color: #0868f7; }
.archive-list { margin-top: 24rpx; background-color: #fff; border: 1rpx solid #e5ecf5; border-radius: 18rpx; padding: 0 24rpx; }
.archive-row { height: 110rpx; flex-direction: row; align-items: center; border-bottom: 1rpx solid #edf1f6; }
.archive-icon { width: 54rpx; height: 54rpx; border-radius: 17rpx; background-color: #e8f2ff; color: #0868f7; align-items: center; justify-content: center; font-size: 21rpx; font-weight: 800; margin-right: 17rpx; }
.archive-name { flex: 1; font-size: 25rpx; }
.state { font-size: 22rpx; margin-right: 14rpx; }
.done { color: #11aa76; }
.pending { color: #ff7917; }
.chevron { color: #a7b2c1; font-size: 36rpx; }
.complete-button { margin-top: 34rpx; }
</style>
+20 -4
View File
@@ -1,16 +1,32 @@
<template>
<view class="page">
<view class="page" :style="pageStyle()">
<AppHeader title="新闻详情" />
<scroll-view scroll-y class="detail-scroll"><view class="detail-content"><text class="detail-title">{{ title }}</text><view class="detail-meta"><text>融资课堂 · 2026-09-08</text><text>6 分钟阅读</text></view><image class="detail-image" src="/static/images/news-city.png" mode="aspectFill"></image><text class="detail-paragraph">融资申请前,先把自己的征信、收入和负债情况梳理清楚,可以显著减少补充资料的时间,也更容易选择适合自己的产品。</text><text class="detail-heading">一、先看信用记录</text><text class="detail-paragraph">保持按时还款,控制信用卡使用率,并提前处理逾期或异常记录。机构会综合判断信用历史的连续性和稳定性。</text><text class="detail-heading">二、准备稳定的收入证明</text><text class="detail-paragraph">工资流水、纳税记录和经营收款记录都可以帮助机构了解还款能力。资料越完整,评估结果越稳定。</text><text class="detail-heading">三、选择匹配的融资方案</text><text class="detail-paragraph">额度、利率、期限和还款方式需要一起比较,优先选择与自身现金流匹配的产品。</text></view></scroll-view>
<scroll-view scroll-y class="detail-scroll" :style="contentStyle(96, false)"><view class="detail-content"><text class="detail-title">{{ title }}</text><view class="detail-meta"><text>融资课堂 · 2026-09-08</text><text>6 分钟阅读</text></view><image class="detail-image" src="/static/mobile/images/news-city.png" mode="aspectFill"></image><text class="detail-paragraph">融资申请前,先把自己的征信、收入和负债情况梳理清楚,可以显著减少补充资料的时间,也更容易选择适合自己的产品。</text><text class="detail-heading">一、先看信用记录</text><text class="detail-paragraph">保持按时还款,控制信用卡使用率,并提前处理逾期或异常记录。机构会综合判断信用历史的连续性和稳定性。</text><text class="detail-heading">二、准备稳定的收入证明</text><text class="detail-paragraph">工资流水、纳税记录和经营收款记录都可以帮助机构了解还款能力。资料越完整,评估结果越稳定。</text><text class="detail-heading">三、选择匹配的融资方案</text><text class="detail-paragraph">额度、利率、期限和还款方式需要一起比较,优先选择与自身现金流匹配的产品。</text></view></scroll-view>
</view>
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { contentStyle, pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
const title = ref('如何提高个人贷款通过率')
onLoad((options) => { if (options != null && options['title'] != null) title.value = options['title'] })
onLoad((options) => { if (options != null && options['title'] != null) title.value = options['title'] as string })
</script>
<style>
.detail-scroll { height: calc(100vh - var(--status-bar-height) - 96rpx); }.detail-content { padding: 30rpx 28rpx 80rpx; background-color: #fff; }.detail-title { color: #172640; font-size: 38rpx; line-height: 54rpx; font-weight: 800; }.detail-meta { flex-direction: row; justify-content: space-between; color: #95a1b1; font-size: 20rpx; margin-top: 16rpx; }.detail-image { width: 100%; height: 300rpx; border-radius: 18rpx; margin-top: 26rpx; }.detail-heading { display: block; color: #22314b; font-size: 28rpx; line-height: 42rpx; font-weight: 800; margin-top: 30rpx; }.detail-paragraph { display: block; color: #59677c; font-size: 25rpx; line-height: 46rpx; margin-top: 20rpx; }
.detail-scroll { }
.detail-content { padding: 30rpx 28rpx 80rpx; background-color: #fff; }
.detail-title { color: #172640; font-size: 38rpx; line-height: 54rpx; font-weight: 800; }
.detail-meta { flex-direction: row; justify-content: space-between; color: #95a1b1; font-size: 20rpx; margin-top: 16rpx; }
.detail-image { width: 100%; height: 300rpx; border-radius: 18rpx; margin-top: 26rpx; }
.detail-heading {
/* #ifndef APP */
display: block;
/* #endif */
color: #22314b; font-size: 28rpx; line-height: 42rpx; font-weight: 800; margin-top: 30rpx; }
.detail-paragraph {
/* #ifndef APP */
display: block;
/* #endif */
color: #59677c; font-size: 25rpx; line-height: 46rpx; margin-top: 20rpx; }
</style>
+42 -15
View File
@@ -1,7 +1,7 @@
<template>
<view class="page page-white">
<view class="page page-white" :style="pageStyle()">
<AppHeader :title="titles[step]" />
<view class="assessment-content">
<view class="assessment-content" :style="contentStyle(96, true)">
<view class="step-line"><view v-for="i in 4" :key="i" :class="i <= step + 1 ? 'step-segment done' : 'step-segment'"></view></view>
<text class="step-count">{{ step + 1 }}/4</text>
<view v-if="step == 0" class="form-list">
@@ -27,17 +27,19 @@
</view>
<view v-else class="upload-area">
<text class="upload-title">上传资料(选填)</text>
<view class="upload-grid"><view class="upload-card" v-for="item in uploads" :key="item" @click="chooseImage(item)"><view class="upload-icon"><AppIcon name="upload" :size="38" /></view><text>{{ item }}</text><text class="upload-state">{{ uploaded[item] == true ? '已选择' : '点击上传' }}</text></view></view>
<view class="upload-grid"><view class="upload-card" v-for="item in uploads" :key="item" @click="chooseImage(item)" :class="item == uploads[uploads.length - 1] ? 'upload-last' : ''"><view class="upload-icon"><AppIcon name="upload" :size="38" /></view><text>{{ item }}</text><text class="upload-state">{{ uploaded[item] == true ? '已选择' : '点击上传' }}</text></view></view>
<text class="upload-title other-title">其他资料</text>
<view class="upload-grid"><view class="upload-card small-card" v-for="item in otherUploads" :key="item" @click="chooseImage(item)"><view class="file-icon"><AppIcon name="file-text" :size="30" /></view><text>{{ item }}</text></view></view>
<view class="upload-grid"><view class="upload-card small-card" v-for="item in otherUploads" :key="item" @click="chooseImage(item)" :class="item == otherUploads[otherUploads.length - 1] ? 'upload-last' : ''"><view class="file-icon"><AppIcon name="file-text" :size="30" /></view><text>{{ item }}</text></view></view>
<view class="consent" @click="consented = !consented"><view :class="consented ? 'checkbox checked' : 'checkbox'"><text v-if="consented">✓</text></view><text>我已阅读并同意《授权协议》</text></view>
</view>
<view class="bottom-action"><button v-if="step > 0" class="back-button" @click="previous">上一步</button><button class="primary-button next-button" @click="next">{{ step == 3 ? '确认信息' : '继续' }}</button></view>
<view class="bottom-action" :style="bottomStyle(20)"><button v-if="step > 0" class="back-button" @click="previous">上一步</button><button class="primary-button next-button" @click="next">{{ step == 3 ? '确认信息' : '继续' }}</button></view>
</view>
</view>
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { bottomStyle, contentStyle, pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import AppIcon from '@/components/AppIcon.uvue'
const step = ref(0)
@@ -46,7 +48,8 @@
const titles = ['基本信息', '经营 / 收入信息', '资产 / 负债信息', '资料上传 / 授权']
const uploads = ['身份证正面', '银行卡', '收入证明']
const otherUploads = ['房产证', '营业执照', '其他文件']
const form = reactive({ name: '', idCard: '', phone: '', city: '', maritalStatus: '', occupation: '', company: '', monthlyIncome: '', socialSecurity: '', housingFund: '', house: '', car: '', savings: '', creditCard: '', otherAssets: '' })
type AssessmentForm = { name: string, idCard: string, phone: string, city: string, maritalStatus: string, occupation: string, company: string, monthlyIncome: string, socialSecurity: string, housingFund: string, house: string, car: string, savings: string, creditCard: string, otherAssets: string }
const form = reactive<AssessmentForm>({ name: '', idCard: '', phone: '', city: '', maritalStatus: '', occupation: '', company: '', monthlyIncome: '', socialSecurity: '', housingFund: '', house: '', car: '', savings: '', creditCard: '', otherAssets: '' })
const uploaded = reactive({ '身份证正面': false, '银行卡': false, '收入证明': false, '房产证': false, '营业执照': false, '其他文件': false })
const saveDraft = () => { uni.setStorageSync('assessmentDraft', { type: assessmentType.value, name: form.name, idCard: form.idCard, phone: form.phone, city: form.city, maritalStatus: form.maritalStatus, occupation: form.occupation, company: form.company, monthlyIncome: form.monthlyIncome, socialSecurity: form.socialSecurity, housingFund: form.housingFund, house: form.house, car: form.car, savings: form.savings, creditCard: form.creditCard, otherAssets: form.otherAssets, consented: consented.value, uploaded: uploaded }) }
const fillDraft = (saved: UTSJSONObject) => {
@@ -56,17 +59,19 @@
}
onLoad((options) => {
const routeType = options != null && options['type'] == 'enterprise' ? 'enterprise' : ''
const saved = uni.getStorageSync('assessmentDraft') as UTSJSONObject
const storedDraft = uni.getStorageSync('assessmentDraft')
if (routeType.length > 0) assessmentType.value = routeType
if (saved != null) {
if (storedDraft != null && typeof storedDraft == 'object') {
const saved = storedDraft as UTSJSONObject
const savedType = saved['type'] == null ? '' : saved['type'] as string
if (savedType.length > 0 && (routeType.length == 0 || savedType == routeType)) fillDraft(saved)
}
})
onShow(() => {
const requestedStep = uni.getStorageSync('assessmentEditStep') as number
if (requestedStep != null && requestedStep >= 0 && requestedStep < 4) {
step.value = requestedStep
const requestedStep = uni.getStorageSync('assessmentEditStep')
if (typeof requestedStep == 'number') {
const targetStep = requestedStep as number
if (targetStep >= 0 && targetStep < 4) step.value = targetStep
uni.removeStorageSync('assessmentEditStep')
}
})
@@ -86,8 +91,30 @@
</script>
<style>
.assessment-content { min-height: calc(100vh - var(--status-bar-height) - 96rpx); padding: 24rpx 30rpx 190rpx; position: relative; }.step-line { height: 8rpx; flex-direction: row; margin-right: 70rpx; }.step-segment { flex: 1; height: 8rpx; border-radius: 8rpx; background-color: #e8edf4; margin-right: 8rpx; }.step-segment.done { background-color: #0868f7; }.step-count { position: absolute; top: 16rpx; right: 30rpx; color: #7f8da2; font-size: 22rpx; }
.form-list { margin-top: 42rpx; }.form-row { min-height: 104rpx; flex-direction: row; align-items: center; border-bottom: 1rpx solid #edf1f6; }.field-label { width: 230rpx; font-size: 27rpx; color: #27354b; }.field-input { flex: 1; text-align: right; font-size: 26rpx; color: #18253d; }.placeholder { color: #b0bac9; }.field-arrow { color: #a5b0c0; margin-left: 10rpx; }
.upload-title { font-size: 29rpx; font-weight: 700; margin-top: 40rpx; }.upload-grid { flex-direction: row; margin-top: 22rpx; }.upload-card { flex: 1; height: 210rpx; margin-right: 14rpx; border-radius: 16rpx; background-color: #f2f7fd; align-items: center; justify-content: center; color: #35445a; font-size: 22rpx; }.upload-card:last-child { margin-right: 0; }.upload-icon { width: 70rpx; height: 70rpx; border-radius: 18rpx; background-color: #dfeeff; align-items: center; justify-content: center; margin-bottom: 14rpx; }.upload-state { color: #9ca8b9; font-size: 19rpx; margin-top: 8rpx; }.other-title { margin-top: 42rpx; }.small-card { height: 150rpx; }.file-icon { width: 54rpx; height: 54rpx; border-radius: 14rpx; background-color: #e6f0ff; align-items: center; justify-content: center; margin-bottom: 10rpx; }.consent { margin-top: 34rpx; flex-direction: row; align-items: center; color: #67758a; font-size: 22rpx; }.checkbox { width: 32rpx; height: 32rpx; border-radius: 50%; border: 2rpx solid #aeb9c8; align-items: center; justify-content: center; margin-right: 12rpx; }.checkbox.checked { background-color: #0868f7; border-color: #0868f7; color: white; }
.bottom-action { position: fixed; left: 0; right: 0; bottom: 0; padding: 20rpx 30rpx calc(20rpx + env(safe-area-inset-bottom)); background-color: #fff; border-top: 1rpx solid #edf1f6; flex-direction: row; }.back-button { width: 180rpx; height: 92rpx; line-height: 92rpx; margin-right: 18rpx; border-radius: 16rpx; background-color: #eef3f9; color: #526075; font-size: 27rpx; }.next-button { flex: 1; margin: 0; }
.assessment-content { padding: 24rpx 30rpx 190rpx; position: relative; }
.step-line { height: 8rpx; flex-direction: row; margin-right: 70rpx; }
.step-segment { flex: 1; height: 8rpx; border-radius: 8rpx; background-color: #e8edf4; margin-right: 8rpx; }
.step-segment.done { background-color: #0868f7; }
.step-count { position: absolute; top: 16rpx; right: 30rpx; color: #7f8da2; font-size: 22rpx; }
.form-list { margin-top: 42rpx; }
.form-row { min-height: 104rpx; flex-direction: row; align-items: center; border-bottom: 1rpx solid #edf1f6; }
.field-label { width: 230rpx; font-size: 27rpx; color: #27354b; }
.field-input { flex: 1; text-align: right; font-size: 26rpx; color: #18253d; }
.placeholder { color: #b0bac9; }
.field-arrow { color: #a5b0c0; margin-left: 10rpx; }
.upload-title { font-size: 29rpx; font-weight: 700; margin-top: 40rpx; }
.upload-grid { flex-direction: row; margin-top: 22rpx; }
.upload-card { flex: 1; height: 210rpx; margin-right: 14rpx; border-radius: 16rpx; background-color: #f2f7fd; align-items: center; justify-content: center; color: #35445a; font-size: 22rpx; }
.upload-last { margin-right: 0; }
.upload-icon { width: 70rpx; height: 70rpx; border-radius: 18rpx; background-color: #dfeeff; align-items: center; justify-content: center; margin-bottom: 14rpx; }
.upload-state { color: #9ca8b9; font-size: 19rpx; margin-top: 8rpx; }
.other-title { margin-top: 42rpx; }
.small-card { height: 150rpx; }
.file-icon { width: 54rpx; height: 54rpx; border-radius: 14rpx; background-color: #e6f0ff; align-items: center; justify-content: center; margin-bottom: 10rpx; }
.consent { margin-top: 34rpx; flex-direction: row; align-items: center; color: #67758a; font-size: 22rpx; }
.checkbox { width: 32rpx; height: 32rpx; border-radius: 50%; border: 2rpx solid #aeb9c8; align-items: center; justify-content: center; margin-right: 12rpx; }
.checkbox.checked { background-color: #0868f7; border-color: #0868f7; color: white; }
.bottom-action { position: fixed; left: 0; right: 0; bottom: 0; padding: 20rpx 30rpx; background-color: #fff; border-top: 1rpx solid #edf1f6; flex-direction: row; }
.back-button { width: 180rpx; height: 92rpx; line-height: 92rpx; margin-right: 18rpx; border-radius: 16rpx; background-color: #eef3f9; color: #526075; font-size: 27rpx; }
.next-button { flex: 1; margin: 0; }
</style>
+26 -11
View File
@@ -1,24 +1,26 @@
<template>
<view class="assistant-page page">
<view class="assistant-page page" :style="pageStyle()">
<AppHeader title="AI融资助手" />
<scroll-view scroll-y class="chat-scroll" :scroll-into-view="lastMessageId">
<scroll-view scroll-y class="chat-scroll" :scroll-into-view="lastMessageId" :style="contentStyle(210, false)">
<view class="chat-content">
<view class="assistant-intro"><image src="/static/images/robot.png" mode="aspectFit"></image><view class="assistant-bubble"><text>您好!我是AI融资助手,可以帮您解答融资相关问题。</text></view></view>
<view class="assistant-intro"><image src="/static/mobile/images/robot.png" mode="aspectFit" class="assistant-intro-image"></image><view class="assistant-bubble"><text>您好!我是AI融资助手,可以帮您解答融资相关问题。</text></view></view>
<view class="quick-questions">
<view class="question" v-for="item in questions" :key="item" @click="ask(item)"><text>{{ item }}</text><AppIcon name="chevron-right" :size="28" muted /></view>
</view>
<view v-for="(item,index) in messages" :key="index" :id="'message-' + index" :class="item.role == 'user' ? 'message-row user-row' : 'message-row'">
<image v-if="item.role == 'assistant'" class="message-avatar" src="/static/images/robot.png" mode="aspectFit"></image>
<image v-if="item.role == 'assistant'" class="message-avatar" src="/static/mobile/images/robot.png" mode="aspectFit"></image>
<view :class="item.role == 'user' ? 'message-bubble user-bubble' : 'message-bubble'"><text>{{ item.text }}</text></view>
</view>
<view v-if="errorMessage.length > 0" class="assistant-error"><text>{{ errorMessage }}</text><text @click="retry">重试</text></view>
<view v-if="errorMessage.length > 0" class="assistant-error"><text>{{ errorMessage }}</text><text @click="retry" class="assistant-error-text-last-child">重试</text></view>
</view>
</scroll-view>
<view class="chat-input"><input v-model="draft" confirm-type="send" @confirm="send" placeholder="请输入您的问题..." /><button @click="send"><AppIcon name="send" :size="34" /></button></view>
<view class="chat-input" :style="bottomStyle(17)"><input v-model="draft" confirm-type="send" @confirm="send" placeholder="请输入您的问题..." class="chat-input-input" /><button @click="send" class="chat-input-button"><AppIcon name="send" :size="34" /></button></view>
</view>
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { bottomStyle, contentStyle, pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import AppIcon from '@/components/AppIcon.uvue'
import { sendAssistantMessage } from '@/services/api.uts'
@@ -29,7 +31,6 @@
const lastQuestion = ref('')
const sending = ref(false)
const errorMessage = ref('')
const ask = (text: string) => { draft.value = text; send() }
const send = async () => {
const text = draft.value.trim()
if (text.length == 0 || sending.value) return
@@ -41,12 +42,26 @@
try { const answer = await sendAssistantMessage(text); messages.value.push({ role: 'assistant', text: answer }); lastMessageId.value = 'message-' + (messages.value.length - 1) } catch (error) { errorMessage.value = '消息发送失败,请稍后重试' }
sending.value = false
}
const ask = (text: string) => { draft.value = text; send() }
const retry = () => { draft.value = lastQuestion.value; errorMessage.value = ''; send() }
</script>
<style>
.assistant-page { background-color: #f4f7fb; }.chat-scroll { height: calc(100vh - var(--status-bar-height) - 210rpx); }.chat-content { padding: 28rpx 26rpx 50rpx; }.assistant-intro { flex-direction: row; align-items: flex-start; }.assistant-intro image, .message-avatar { width: 78rpx; height: 78rpx; border-radius: 24rpx; background-color: #e7f2ff; margin-right: 16rpx; }.assistant-bubble, .message-bubble { max-width: 530rpx; padding: 24rpx; border-radius: 8rpx 22rpx 22rpx 22rpx; background-color: #fff; color: #344259; font-size: 25rpx; line-height: 42rpx; border: 1rpx solid #e4ebf5; }
.quick-questions { margin: 25rpx 0 36rpx 94rpx; }.question { min-height: 72rpx; margin-top: 13rpx; padding: 14rpx 18rpx; border-radius: 16rpx; border: 2rpx solid #bcd9ff; background-color: #eef6ff; color: #0868f7; font-size: 23rpx; flex-direction: row; align-items: center; justify-content: space-between; }.question-arrow { font-size: 34rpx; }.message-row { margin-top: 22rpx; flex-direction: row; align-items: flex-start; }.user-row { justify-content: flex-end; }.user-bubble { color: #fff; background-color: #0868f7; border-color: #0868f7; border-radius: 22rpx 8rpx 22rpx 22rpx; }
.chat-input { position: fixed; left: 0; right: 0; bottom: 0; min-height: 114rpx; padding: 17rpx 24rpx calc(17rpx + env(safe-area-inset-bottom)); background-color: #fff; border-top: 1rpx solid #e3eaf4; flex-direction: row; align-items: center; }.chat-input input { flex: 1; height: 78rpx; border-radius: 39rpx; background-color: #f2f5f9; padding: 0 28rpx; font-size: 25rpx; }.chat-input button { width: 76rpx; height: 76rpx; line-height: 76rpx; border-radius: 50%; margin-left: 15rpx; padding: 0; background-color: #0868f7; color: #fff; font-size: 29rpx; }
.assistant-error { margin: 18rpx 0 0 94rpx; color: #e05a5a; font-size: 21rpx; flex-direction: row; justify-content: space-between; }.assistant-error text:last-child { color: #0868f7; margin-left: 18rpx; }
.assistant-page { background-color: #f4f7fb; }
.chat-scroll { }
.chat-content { padding: 28rpx 26rpx 50rpx; }
.assistant-intro { flex-direction: row; align-items: flex-start; }
.assistant-intro-image, .message-avatar { width: 78rpx; height: 78rpx; border-radius: 24rpx; background-color: #e7f2ff; margin-right: 16rpx; }
.assistant-bubble, .message-bubble { max-width: 530rpx; padding: 24rpx; border-radius: 8rpx 22rpx 22rpx 22rpx; background-color: #fff; color: #344259; font-size: 25rpx; line-height: 42rpx; border: 1rpx solid #e4ebf5; }
.quick-questions { margin: 25rpx 0 36rpx 94rpx; }
.question { min-height: 72rpx; margin-top: 13rpx; padding: 14rpx 18rpx; border-radius: 16rpx; border: 2rpx solid #bcd9ff; background-color: #eef6ff; color: #0868f7; font-size: 23rpx; flex-direction: row; align-items: center; justify-content: space-between; }
.question-arrow { font-size: 34rpx; }
.message-row { margin-top: 22rpx; flex-direction: row; align-items: flex-start; }
.user-row { justify-content: flex-end; }
.user-bubble { color: #fff; background-color: #0868f7; border-color: #0868f7; border-radius: 22rpx 8rpx 22rpx 22rpx; }
.chat-input { position: fixed; left: 0; right: 0; bottom: 0; min-height: 114rpx; padding: 17rpx 24rpx; background-color: #fff; border-top: 1rpx solid #e3eaf4; flex-direction: row; align-items: center; }
.chat-input-input { flex: 1; height: 78rpx; border-radius: 39rpx; background-color: #f2f5f9; padding: 0 28rpx; font-size: 25rpx; }
.chat-input-button { width: 76rpx; height: 76rpx; line-height: 76rpx; border-radius: 50%; margin-left: 15rpx; padding: 0; background-color: #0868f7; color: #fff; font-size: 29rpx; }
.assistant-error { margin: 18rpx 0 0 94rpx; color: #e05a5a; font-size: 21rpx; flex-direction: row; justify-content: space-between; }
.assistant-error-text-last-child { color: #0868f7; margin-left: 18rpx; }
</style>
+39 -11
View File
@@ -1,29 +1,57 @@
<template>
<view class="page">
<view class="page" :style="pageStyle()">
<AppHeader title="融资课堂" />
<scroll-view scroll-y class="classroom-scroll">
<scroll-view scroll-y class="classroom-scroll" :style="contentStyle(96, false)">
<view class="classroom-content">
<view class="classroom-hero"><view class="hero-copy"><text class="hero-title">融资课堂</text><text class="hero-subtitle">把融资知识,变成申请底气</text><text class="hero-desc">从准备材料到优化信用,分阶段掌握实用方法</text></view><image src="/static/images/news-policy.png" mode="aspectFill"></image></view>
<view class="lesson-section"><view class="section-head"><text class="section-title">热门课程</text><text class="lesson-count">12 节课程</text></view><view class="lesson-card" v-for="(item,index) in lessons" :key="item.title" @click="openLesson(index)"><image :src="item.image" mode="aspectFill"></image><view class="lesson-copy"><text class="lesson-title">{{ item.title }}</text><text class="lesson-desc">{{ item.desc }}</text><view class="lesson-meta"><text>{{ item.duration }}</text><text class="lesson-tag">{{ item.tag }}</text></view></view><AppIcon name="chevron-right" :size="28" muted /></view></view>
<view class="topic-section"><text class="section-title">按场景学习</text><view class="topic-grid"><view class="topic-card blue-topic" @click="openTopic('个人信用提升')"><AppIcon name="user" :size="46" /><text>个人信用提升</text><text>3 节课程</text></view><view class="topic-card orange-topic" @click="openTopic('小微经营融资')"><AppIcon name="building" :size="46" /><text>小微经营融资</text><text>4 节课程</text></view><view class="topic-card green-topic" @click="openTopic('材料准备清单')"><AppIcon name="file-text" :size="46" /><text>材料准备清单</text><text>5 节课程</text></view></view></view>
<view class="classroom-hero"><view class="hero-copy"><text class="hero-title">融资课堂</text><text class="hero-subtitle">把融资知识,变成申请底气</text><text class="hero-desc">从准备材料到优化信用,分阶段掌握实用方法</text></view><image src="/static/mobile/images/news-policy.png" mode="aspectFill" class="classroom-hero-image"></image></view>
<view class="lesson-section"><view class="section-head"><text class="section-title">热门课程</text><text class="lesson-count">12 节课程</text></view><view class="lesson-card" v-for="(item,index) in lessons" :key="item.title" @click="openLesson(index)"><image :src="item.image" mode="aspectFill" class="lesson-card-image"></image><view class="lesson-copy"><text class="lesson-title">{{ item.title }}</text><text class="lesson-desc">{{ item.desc }}</text><view class="lesson-meta"><text>{{ item.duration }}</text><text class="lesson-tag">{{ item.tag }}</text></view></view><AppIcon name="chevron-right" :size="28" muted /></view></view>
<view class="topic-section"><text class="section-title">按场景学习</text><view class="topic-grid"><view class="topic-card blue-topic" @click="openTopic('个人信用提升')"><AppIcon name="user" :size="46" /><text class="topic-card-text-1">个人信用提升</text><text class="topic-card-text-2">3 节课程</text></view><view class="topic-card orange-topic" @click="openTopic('小微经营融资')"><AppIcon name="building" :size="46" /><text class="topic-card-text-1">小微经营融资</text><text class="topic-card-text-2">4 节课程</text></view><view class="topic-card green-topic" @click="openTopic('材料准备清单')"><AppIcon name="file-text" :size="46" /><text class="topic-card-text-1">材料准备清单</text><text class="topic-card-text-2">5 节课程</text></view></view></view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { contentStyle, pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import AppIcon from '@/components/AppIcon.uvue'
const lessons = [
{ title: '如何提高个人贷款通过率', desc: '征信、负债和收入的申请准备方法', duration: '6 分钟', tag: '个人融资', image: '/static/images/news-calculator.png' },
{ title: '小微企业融资材料清单', desc: '一次准备齐全,减少补充资料时间', duration: '8 分钟', tag: '企业融资', image: '/static/images/news-business.png' },
{ title: '读懂贷款利率与还款方式', desc: '选对期限,降低长期资金成本', duration: '5 分钟', tag: '基础知识', image: '/static/images/news-city.png' }
type LessonSummary = { title: string, desc: string, duration: string, tag: string, image: string }
const lessons: LessonSummary[] = [
{ title: '如何提高个人贷款通过率', desc: '征信、负债和收入的申请准备方法', duration: '6 分钟', tag: '个人融资', image: '/static/mobile/images/news-calculator.png' },
{ title: '小微企业融资材料清单', desc: '一次准备齐全,减少补充资料时间', duration: '8 分钟', tag: '企业融资', image: '/static/mobile/images/news-business.png' },
{ title: '读懂贷款利率与还款方式', desc: '选对期限,降低长期资金成本', duration: '5 分钟', tag: '基础知识', image: '/static/mobile/images/news-city.png' }
]
const openLesson = (index: number) => { uni.navigateTo({ url: '/pages/lesson/lesson?id=' + index }) }
const openTopic = (title: string) => { uni.navigateTo({ url: '/pages/lesson/lesson?title=' + title }) }
</script>
<style>
.classroom-scroll { height: calc(100vh - var(--status-bar-height) - 96rpx); }.classroom-content { padding: 26rpx 28rpx 70rpx; }.classroom-hero { height: 242rpx; border-radius: 20rpx; background: linear-gradient(135deg, #0b2a66, #087aff); padding: 28rpx; overflow: hidden; position: relative; }.hero-copy { width: 68%; z-index: 2; }.hero-title { color: #fff; font-size: 38rpx; line-height: 50rpx; font-weight: 800; }.hero-subtitle { color: #fff; font-size: 27rpx; line-height: 40rpx; font-weight: 700; margin-top: 10rpx; }.hero-desc { color: #bfd7ff; font-size: 21rpx; line-height: 32rpx; margin-top: 12rpx; }.classroom-hero image { position: absolute; right: -18rpx; top: 0; width: 220rpx; height: 242rpx; opacity: .78; }
.lesson-section { margin-top: 30rpx; }.section-head { flex-direction: row; align-items: center; justify-content: space-between; }.lesson-count { color: #8b98aa; font-size: 21rpx; }.lesson-card { min-height: 150rpx; margin-top: 16rpx; padding: 18rpx; border-radius: 16rpx; background-color: #fff; border: 1rpx solid #e4ebf5; flex-direction: row; align-items: center; }.lesson-card image { width: 142rpx; height: 114rpx; border-radius: 12rpx; margin-right: 18rpx; flex-shrink: 0; }.lesson-copy { flex: 1; }.lesson-title { color: #1a2941; font-size: 26rpx; line-height: 36rpx; font-weight: 700; }.lesson-desc { color: #778499; font-size: 21rpx; line-height: 31rpx; margin-top: 7rpx; }.lesson-meta { flex-direction: row; align-items: center; margin-top: 10rpx; color: #99a5b4; font-size: 18rpx; }.lesson-tag { margin-left: 14rpx; color: #0868f7; }.topic-section { margin-top: 32rpx; }.topic-grid { flex-direction: row; flex-wrap: wrap; justify-content: space-between; margin-top: 16rpx; }.topic-card { width: 48.5%; min-height: 150rpx; margin-bottom: 14rpx; padding: 20rpx; border-radius: 16rpx; border: 1rpx solid #e4ebf5; }.topic-card .app-icon { margin-bottom: 9rpx; }.topic-card text:nth-of-type(1) { color: #23324a; font-size: 23rpx; font-weight: 700; }.topic-card text:nth-of-type(2) { color: #8190a4; font-size: 19rpx; margin-top: 6rpx; }.blue-topic { background-color: #eef6ff; }.orange-topic { background-color: #fff3e9; }.green-topic { background-color: #eaf8f1; }
.classroom-scroll { }
.classroom-content { padding: 26rpx 28rpx 70rpx; }
.classroom-hero { height: 242rpx; border-radius: 20rpx; background: linear-gradient(135deg, #0b2a66, #087aff); padding: 28rpx; overflow: hidden; position: relative; }
.hero-copy { width: 68%; z-index: 2; }
.hero-title { color: #fff; font-size: 38rpx; line-height: 50rpx; font-weight: 800; }
.hero-subtitle { color: #fff; font-size: 27rpx; line-height: 40rpx; font-weight: 700; margin-top: 10rpx; }
.hero-desc { color: #bfd7ff; font-size: 21rpx; line-height: 32rpx; margin-top: 12rpx; }
.classroom-hero-image { position: absolute; right: -18rpx; top: 0; width: 220rpx; height: 242rpx; opacity: .78; }
.lesson-section { margin-top: 30rpx; }
.section-head { flex-direction: row; align-items: center; justify-content: space-between; }
.lesson-count { color: #8b98aa; font-size: 21rpx; }
.lesson-card { min-height: 150rpx; margin-top: 16rpx; padding: 18rpx; border-radius: 16rpx; background-color: #fff; border: 1rpx solid #e4ebf5; flex-direction: row; align-items: center; }
.lesson-card-image { width: 142rpx; height: 114rpx; border-radius: 12rpx; margin-right: 18rpx; flex-shrink: 0; }
.lesson-copy { flex: 1; }
.lesson-title { color: #1a2941; font-size: 26rpx; line-height: 36rpx; font-weight: 700; }
.lesson-desc { color: #778499; font-size: 21rpx; line-height: 31rpx; margin-top: 7rpx; }
.lesson-meta { flex-direction: row; align-items: center; margin-top: 10rpx; color: #99a5b4; font-size: 18rpx; }
.lesson-tag { margin-left: 14rpx; color: #0868f7; }
.topic-section { margin-top: 32rpx; }
.topic-grid { flex-direction: row; flex-wrap: wrap; justify-content: space-between; margin-top: 16rpx; }
.topic-card { width: 48.5%; min-height: 150rpx; margin-bottom: 14rpx; padding: 20rpx; border-radius: 16rpx; border: 1rpx solid #e4ebf5; }
.topic-card .app-icon { margin-bottom: 9rpx; }
.topic-card-text-1 { color: #23324a; font-size: 23rpx; font-weight: 700; }
.topic-card-text-2 { color: #8190a4; font-size: 19rpx; margin-top: 6rpx; }
.blue-topic { background-color: #eef6ff; }
.orange-topic { background-color: #fff3e9; }
.green-topic { background-color: #eaf8f1; }
</style>
+21 -6
View File
@@ -1,5 +1,5 @@
<template>
<view class="page page-white">
<view class="page page-white" :style="pageStyle()">
<AppHeader title="确认信息" />
<view class="confirm-content">
<view class="confirm-tip"><view class="tip-icon"><text>✓</text></view><view><text class="tip-title">信息填写完成</text><text class="tip-desc">请确认以下信息准确无误</text></view></view>
@@ -10,16 +10,21 @@
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import { submitAssessment } from '@/services/api.uts'
const submitting = ref(false)
const sections = ref([{ title: '基本信息', rows: [{ label: '姓名', value: '未填写' }, { label: '身份证号', value: '未填写' }, { label: '手机号', value: '未填写' }, { label: '所在城市', value: '未填写' }, { label: '婚姻状况', value: '未填写' }] }, { title: '经营 / 收入', rows: [{ label: '职业', value: '未填写' }, { label: '工作单位', value: '未填写' }, { label: '月收入', value: '未填写' }, { label: '社保 / 公积金', value: '未填写' }] }, { title: '资产 / 负债', rows: [{ label: '房产情况', value: '未填写' }, { label: '车辆情况', value: '未填写' }, { label: '存款情况', value: '未填写' }, { label: '信用卡', value: '未填写' }, { label: '其他资产', value: '未填写' }] }])
type InfoRow = { label: string, value: string }
type InfoSection = { title: string, rows: InfoRow[] }
const sections = ref<InfoSection[]>([{ title: '基本信息', rows: [{ label: '姓名', value: '未填写' }, { label: '身份证号', value: '未填写' }, { label: '手机号', value: '未填写' }, { label: '所在城市', value: '未填写' }, { label: '婚姻状况', value: '未填写' }] }, { title: '经营 / 收入', rows: [{ label: '职业', value: '未填写' }, { label: '工作单位', value: '未填写' }, { label: '月收入', value: '未填写' }, { label: '社保 / 公积金', value: '未填写' }] }, { title: '资产 / 负债', rows: [{ label: '房产情况', value: '未填写' }, { label: '车辆情况', value: '未填写' }, { label: '存款情况', value: '未填写' }, { label: '信用卡', value: '未填写' }, { label: '其他资产', value: '未填写' }] }])
const payload = ref<UTSJSONObject>({ source: 'mobile', confirmed: false })
const mask = (value: string, left: number, right: number): string => { if (value.length <= left + right) return value; return value.substring(0, left) + '****' + value.substring(value.length - right) }
const read = (saved: UTSJSONObject, key: string): string => { const value = saved[key]; return value == null ? '' : value as string }
const loadDraft = () => {
const saved = uni.getStorageSync('assessmentDraft') as UTSJSONObject
if (saved == null) return
const storedDraft = uni.getStorageSync('assessmentDraft')
if (storedDraft == null || typeof storedDraft != 'object') return
const saved = storedDraft as UTSJSONObject
const name = read(saved, 'name'); const idCard = read(saved, 'idCard'); const phone = read(saved, 'phone'); const city = read(saved, 'city'); const marital = read(saved, 'maritalStatus'); const occupation = read(saved, 'occupation'); const company = read(saved, 'company'); const income = read(saved, 'monthlyIncome'); const social = read(saved, 'socialSecurity'); const fund = read(saved, 'housingFund'); const house = read(saved, 'house'); const car = read(saved, 'car'); const savings = read(saved, 'savings'); const card = read(saved, 'creditCard'); const other = read(saved, 'otherAssets')
sections.value = [{ title: '基本信息', rows: [{ label: '姓名', value: name.length > 0 ? name : '未填写' }, { label: '身份证号', value: idCard.length > 0 ? mask(idCard, 4, 4) : '未填写' }, { label: '手机号', value: phone.length > 0 ? mask(phone, 3, 4) : '未填写' }, { label: '所在城市', value: city.length > 0 ? city : '未填写' }, { label: '婚姻状况', value: marital.length > 0 ? marital : '未填写' }] }, { title: '经营 / 收入', rows: [{ label: '职业', value: occupation.length > 0 ? occupation : '未填写' }, { label: '工作单位', value: company.length > 0 ? company : '未填写' }, { label: '月收入', value: income.length > 0 ? income + '元' : '未填写' }, { label: '社保 / 公积金', value: (social.length > 0 ? social : '未填写') + ' / ' + (fund.length > 0 ? fund : '未填写') }] }, { title: '资产 / 负债', rows: [{ label: '房产情况', value: house.length > 0 ? house : '未填写' }, { label: '车辆情况', value: car.length > 0 ? car : '未填写' }, { label: '存款情况', value: savings.length > 0 ? savings : '未填写' }, { label: '信用卡', value: card.length > 0 ? card : '未填写' }, { label: '其他资产', value: other.length > 0 ? other : '未填写' }] }]
payload.value = { ...saved, source: 'mobile', confirmed: true }
@@ -30,6 +35,16 @@
</script>
<style>
.confirm-content { padding: 26rpx 30rpx 80rpx; }.confirm-tip { padding: 26rpx; border-radius: 16rpx; background-color: #edf6ff; flex-direction: row; align-items: center; }.tip-icon { width: 64rpx; height: 64rpx; border-radius: 50%; background-color: #0868f7; color: white; align-items: center; justify-content: center; font-size: 30rpx; margin-right: 20rpx; }.tip-title { font-size: 28rpx; font-weight: 700; }.tip-desc { color: #718096; font-size: 22rpx; margin-top: 5rpx; }
.info-section { margin-top: 28rpx; border-top: 1rpx solid #edf1f6; }.section-bar { height: 82rpx; flex-direction: row; align-items: center; justify-content: space-between; font-size: 28rpx; font-weight: 700; }.edit { color: #0868f7; font-size: 22rpx; font-weight: 500; }.info-row { min-height: 72rpx; flex-direction: row; align-items: center; justify-content: space-between; }.info-label { color: #778499; font-size: 24rpx; }.info-value { color: #1a273f; font-size: 25rpx; font-weight: 600; }.submit-button { margin-top: 50rpx; }
.confirm-content { padding: 26rpx 30rpx 80rpx; }
.confirm-tip { padding: 26rpx; border-radius: 16rpx; background-color: #edf6ff; flex-direction: row; align-items: center; }
.tip-icon { width: 64rpx; height: 64rpx; border-radius: 50%; background-color: #0868f7; color: white; align-items: center; justify-content: center; font-size: 30rpx; margin-right: 20rpx; }
.tip-title { font-size: 28rpx; font-weight: 700; }
.tip-desc { color: #718096; font-size: 22rpx; margin-top: 5rpx; }
.info-section { margin-top: 28rpx; border-top: 1rpx solid #edf1f6; }
.section-bar { height: 82rpx; flex-direction: row; align-items: center; justify-content: space-between; font-size: 28rpx; font-weight: 700; }
.edit { color: #0868f7; font-size: 22rpx; font-weight: 500; }
.info-row { min-height: 72rpx; flex-direction: row; align-items: center; justify-content: space-between; }
.info-label { color: #778499; font-size: 24rpx; }
.info-value { color: #1a273f; font-size: 25rpx; font-weight: 600; }
.submit-button { margin-top: 50rpx; }
</style>
+29 -7
View File
@@ -1,5 +1,5 @@
<template>
<view :class="type == 'enterprise' ? 'page entry-page enterprise-page' : 'page entry-page'">
<view :class="type == 'enterprise' ? 'page entry-page enterprise-page' : 'page entry-page'" :style="pageStyle()">
<AppHeader :title="type == 'enterprise' ? '企业信用融资' : '个人信用融资'" />
<view class="entry-content">
<view class="intro-panel">
@@ -8,7 +8,7 @@
<text class="intro-sub">{{ type == 'enterprise' ? '适合企业 / 个体工商户' : '适合有稳定收入的个人用户' }}</text>
<view class="benefit" v-for="item in benefits" :key="item"><AppIcon name="check-circle" :size="30" /><text>{{ item }}</text></view>
</view>
<image class="entry-image" :src="type == 'enterprise' ? '/static/images/enterprise-building-cutout.png' : '/static/images/personal-building-cutout.png'" mode="aspectFit"></image>
<image class="entry-image" :src="type == 'enterprise' ? '/static/mobile/images/enterprise-building-cutout.png' : '/static/mobile/images/personal-building-cutout.png'" mode="aspectFit"></image>
</view>
<button :class="type == 'enterprise' ? 'orange-button start-button' : 'primary-button start-button'" @click="start">开始评估</button>
<view class="process-block">
@@ -25,6 +25,8 @@
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import AppIcon from '@/components/AppIcon.uvue'
const type = ref('personal')
@@ -39,10 +41,30 @@
</script>
<style>
.entry-content { padding: 28rpx; }.intro-panel { min-height: 430rpx; background: linear-gradient(145deg, #eef6ff, #ffffff); border-radius: 20rpx; border: 1rpx solid #dce9fb; padding: 38rpx 28rpx; flex-direction: row; position: relative; overflow: hidden; }.enterprise-page .intro-panel { background: linear-gradient(145deg, #fff5ec, #ffffff); border-color: #ffe2cd; }
.intro-copy { width: 63%; z-index: 2; }.intro-title { font-size: 42rpx; line-height: 56rpx; font-weight: 900; color: #0868f7; }.enterprise-page .intro-title { color: #f36b12; }.intro-sub { font-size: 23rpx; color: #5f6d82; margin-top: 8rpx; margin-bottom: 30rpx; }
.benefit { flex-direction: row; align-items: center; margin-top: 18rpx; color: #23324a; font-size: 25rpx; font-weight: 600; }.benefit .app-icon { margin-right: 12rpx; }.enterprise-page .benefit .app-icon { filter: hue-rotate(330deg) saturate(1.7); }
.entry-image { position: absolute; width: 285rpx; height: 300rpx; right: -35rpx; bottom: -22rpx; }.start-button { margin-top: 30rpx; }
.process-block { margin-top: 48rpx; }.process-row { flex-direction: row; align-items: center; justify-content: space-between; margin-top: 34rpx; padding: 0 20rpx; }.process-item { align-items: center; color: #455369; font-size: 22rpx; }.process-icon { width: 88rpx; height: 88rpx; border-radius: 26rpx; background-color: #e9f2ff; align-items: center; justify-content: center; margin-bottom: 14rpx; }.enterprise-page .process-icon { background-color: #fff0e5; }.enterprise-page .process-icon .app-icon { filter: hue-rotate(330deg) saturate(1.7); }.arrow { margin-top: -28rpx; }
.entry-content { padding: 28rpx; }
.intro-panel { min-height: 430rpx; background: linear-gradient(145deg, #eef6ff, #ffffff); border-radius: 20rpx; border: 1rpx solid #dce9fb; padding: 38rpx 28rpx; flex-direction: row; position: relative; overflow: hidden; }
.enterprise-page .intro-panel { background: linear-gradient(145deg, #fff5ec, #ffffff); border-color: #ffe2cd; }
.intro-copy { width: 63%; z-index: 2; }
.intro-title { font-size: 42rpx; line-height: 56rpx; font-weight: 900; color: #0868f7; }
.enterprise-page .intro-title { color: #f36b12; }
.intro-sub { font-size: 23rpx; color: #5f6d82; margin-top: 8rpx; margin-bottom: 30rpx; }
.benefit { flex-direction: row; align-items: center; margin-top: 18rpx; color: #23324a; font-size: 25rpx; font-weight: 600; }
.benefit .app-icon { margin-right: 12rpx; }
/* #ifndef APP */
.enterprise-page .benefit .app-icon { filter: hue-rotate(330deg) saturate(1.7); }
/* #endif */
.entry-image { position: absolute; width: 285rpx; height: 300rpx; right: -35rpx; bottom: -22rpx; }
.start-button { margin-top: 30rpx; }
.process-block { margin-top: 48rpx; }
.process-row { flex-direction: row; align-items: center; justify-content: space-between; margin-top: 34rpx; padding: 0 20rpx; }
.process-item { align-items: center; color: #455369; font-size: 22rpx; }
.process-icon { width: 88rpx; height: 88rpx; border-radius: 26rpx; background-color: #e9f2ff; align-items: center; justify-content: center; margin-bottom: 14rpx; }
.enterprise-page .process-icon { background-color: #fff0e5; }
/* #ifndef APP */
.enterprise-page .process-icon .app-icon { filter: hue-rotate(330deg) saturate(1.7); }
/* #endif */
.arrow { margin-top: -28rpx; }
.privacy-tip { margin-top: 55rpx; align-items: center; color: #97a3b4; font-size: 21rpx; }
</style>
+48 -17
View File
@@ -1,15 +1,15 @@
<template>
<view class="page">
<view class="home-header"><view class="safe"></view><view class="home-bar"><BrandLogo /><view class="header-actions"><view class="round-action" @click="go('/pages/assistant/assistant')"><AppIcon name="sparkles" :size="34" /></view><view class="round-action notify" @click="go('/pages/messages/messages')"><AppIcon name="bell" :size="34" /><view class="red-dot"></view></view></view></view></view>
<scroll-view scroll-y class="home-scroll">
<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('/pages/assistant/assistant')"><AppIcon name="sparkles" :size="34" /></view><view class="round-action notify" @click="go('/pages/messages/messages')"><AppIcon name="bell" :size="34" /><view class="red-dot"></view></view></view></view></view>
<scroll-view scroll-y class="home-scroll" :style="contentStyle(104, false)">
<view class="home-content">
<view class="ai-banner" @click="go('/pages/assistant/assistant')">
<view class="banner-copy"><text class="banner-title">AI智能评估</text><text class="banner-subtitle">精准匹配融资方案</text><text class="banner-desc">让合适的资金主动找上您</text></view>
<image class="robot-img" src="/static/images/robot-cutout.png" mode="aspectFit"></image>
<image class="robot-img" src="/static/mobile/images/robot-cutout.png" mode="aspectFit"></image>
</view>
<view class="entry-row">
<view class="finance-entry personal" @click="openEntry('personal')"><view><text class="entry-title">个人信用融资</text><text class="entry-subtitle">个人/小微商户</text></view><image src="/static/images/personal-building-cutout.png" mode="aspectFit"></image></view>
<view class="finance-entry enterprise" @click="openEntry('enterprise')"><view><text class="entry-title">企业信用融资</text><text class="entry-subtitle">企业/个体工商户</text></view><image src="/static/images/enterprise-building-cutout.png" mode="aspectFit"></image></view>
<view class="finance-entry personal" @click="openEntry('personal')"><view><text class="entry-title">个人信用融资</text><text class="entry-subtitle">个人/小微商户</text></view><image src="/static/mobile/images/personal-building-cutout.png" mode="aspectFit" class="finance-entry-image"></image></view>
<view class="finance-entry enterprise entry-following" @click="openEntry('enterprise')" ><view><text class="entry-title">企业信用融资</text><text class="entry-subtitle">企业/个体工商户</text></view><image src="/static/mobile/images/enterprise-building-cutout.png" mode="aspectFit" class="finance-entry-image"></image></view>
</view>
<view class="shortcut-row">
<view class="shortcut" @click="go('/pages/classroom/classroom')"><view class="shortcut-icon green"><AppIcon name="trend-up" :size="46" /></view><text>融资课堂</text></view>
@@ -20,7 +20,7 @@
<view class="section-head"><text class="section-title">我的融资方案</text><text class="more" @click="go('/pages/matches/matches')">查看全部 ›</text></view>
<view class="plan-card" @click="go('/pages/matches/matches')"><view class="plan-main"><text class="plan-name">信用优享方案</text><text class="plan-meta">3家机构可申请 · 最高50万</text></view><view class="score"><text class="score-number">85</text><text>综合分</text></view></view>
<view class="section-head"><text class="section-title">融资课堂</text><text class="more" @click="go('/pages/classroom/classroom')">进入课堂 ›</text></view>
<view class="news-preview" @click="go('/pages/classroom/classroom')"><view class="news-copy"><text class="news-title">如何提高个人贷款通过率</text><text class="news-meta">信用管理 · 6分钟阅读</text></view><image class="news-art" src="/static/images/news-calculator.png" mode="aspectFill"></image></view>
<view class="news-preview" @click="go('/pages/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>
</scroll-view>
<BottomNav active="home" />
@@ -28,6 +28,8 @@
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { contentStyle, statusStyle, pageStyle } = usePageLayout()
import BrandLogo from '@/components/BrandLogo.uvue'
import BottomNav from '@/components/BottomNav.uvue'
import AppIcon from '@/components/AppIcon.uvue'
@@ -36,18 +38,47 @@
</script>
<style>
.home-header { background-color: #ffffff; }.safe { height: var(--status-bar-height); }
.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; border-radius: 50%; background-color: #eef5ff; align-items: center; justify-content: center; margin-left: 16rpx; position: relative; }
.header-actions { flex-direction: row; }
.round-action { width: 62rpx; height: 62rpx; border-radius: 50%; background-color: #eef5ff; align-items: center; justify-content: center; margin-left: 16rpx; position: relative; }
.red-dot { position: absolute; width: 13rpx; height: 13rpx; right: 2rpx; top: 0; background-color: #ff4d4f; border-radius: 50%; border: 3rpx solid #fff; }
.home-scroll { height: calc(100vh - var(--status-bar-height) - 104rpx); }.home-content { padding: 18rpx 28rpx 180rpx; }
.home-scroll { }
.home-content { padding: 18rpx 28rpx 180rpx; }
.ai-banner { height: 260rpx; border-radius: 20rpx; padding: 32rpx; overflow: hidden; flex-direction: row; align-items: center; background: linear-gradient(125deg, #1263f0 0%, #0788ff 100%); box-shadow: 0 16rpx 36rpx rgba(0, 100, 237, .20); }
.banner-copy { z-index: 2; }.banner-title { color: #ffffff; font-size: 39rpx; font-weight: 800; }.banner-subtitle { color: #ffffff; font-size: 30rpx; font-weight: 700; margin-top: 12rpx; }.banner-desc { color: #dceeff; font-size: 22rpx; margin-top: 14rpx; }
.banner-copy { z-index: 2; }
.banner-title { color: #ffffff; font-size: 39rpx; font-weight: 800; }
.banner-subtitle { color: #ffffff; font-size: 30rpx; font-weight: 700; margin-top: 12rpx; }
.banner-desc { color: #dceeff; font-size: 22rpx; margin-top: 14rpx; }
.robot-img { position: absolute; right: 5rpx; width: 270rpx; height: 235rpx; }
.entry-row { flex-direction: row; margin-top: 22rpx; }.finance-entry { flex: 1; height: 180rpx; border-radius: 18rpx; padding: 26rpx 22rpx; flex-direction: row; overflow: hidden; position: relative; }.finance-entry + .finance-entry { margin-left: 18rpx; }.personal { background-color: #eaf3ff; }.enterprise { background-color: #fff1e7; }
.finance-entry image { position: absolute; width: 150rpx; height: 135rpx; right: -8rpx; bottom: -8rpx; }.entry-title { font-size: 29rpx; font-weight: 800; }.personal .entry-title { color: #0868f7; }.enterprise .entry-title { color: #ff7115; }.entry-subtitle { margin-top: 10rpx; color: #65738a; font-size: 21rpx; }
.shortcut-row { height: 160rpx; margin-top: 24rpx; flex-direction: row; align-items: center; justify-content: space-between; background-color: #fff; border-radius: 18rpx; padding: 0 8rpx; border: 1rpx solid #eaf0f7; }.shortcut { flex: 1; align-items: center; color: #536176; font-size: 18rpx; white-space: nowrap; text-align: center; }.shortcut-icon { width: 76rpx; height: 76rpx; border-radius: 24rpx; align-items: center; justify-content: center; margin-bottom: 10rpx; }.green { background-color: #e7f8f1; }.purple { background-color: #efebff; }.blue { background-color: #e8f2ff; }.orange { background-color: #fff0e8; }
.section-head { margin-top: 34rpx; margin-bottom: 18rpx; flex-direction: row; justify-content: space-between; align-items: center; }.more { color: #1673ed; font-size: 22rpx; }
.plan-card { height: 150rpx; background-color: #fff; border: 1rpx solid #e6edf7; border-radius: 16rpx; padding: 26rpx; flex-direction: row; align-items: center; justify-content: space-between; }.plan-name { font-size: 29rpx; font-weight: 700; }.plan-meta { margin-top: 12rpx; color: #8290a5; font-size: 22rpx; }.score { align-items: center; color: #18aa78; font-size: 19rpx; }.score-number { font-size: 48rpx; line-height: 52rpx; color: #0868f7; font-weight: 800; }
.news-preview { min-height: 130rpx; background-color: #fff; border-radius: 16rpx; padding: 24rpx; flex-direction: row; justify-content: space-between; align-items: center; border: 1rpx solid #e6edf7; }.news-copy { flex: 1; padding-right: 16rpx; }.news-title { font-size: 27rpx; font-weight: 700; }.news-meta { margin-top: 14rpx; font-size: 21rpx; color: #94a0b1; }.news-art { width: 118rpx; height: 82rpx; border-radius: 10rpx; flex-shrink: 0; }
.entry-row { flex-direction: row; margin-top: 22rpx; }
.finance-entry { flex: 1; height: 180rpx; border-radius: 18rpx; padding: 26rpx 22rpx; flex-direction: row; overflow: hidden; position: relative; }
.entry-following { margin-left: 18rpx; }
.personal { background-color: #eaf3ff; }
.enterprise { background-color: #fff1e7; }
.finance-entry-image { position: absolute; width: 150rpx; height: 135rpx; right: -8rpx; bottom: -8rpx; }
.entry-title { font-size: 29rpx; font-weight: 800; }
.personal .entry-title { color: #0868f7; }
.enterprise .entry-title { color: #ff7115; }
.entry-subtitle { margin-top: 10rpx; color: #65738a; font-size: 21rpx; }
.shortcut-row { height: 160rpx; margin-top: 24rpx; flex-direction: row; align-items: center; justify-content: space-between; background-color: #fff; border-radius: 18rpx; padding: 0 8rpx; border: 1rpx solid #eaf0f7; }
.shortcut { flex: 1; align-items: center; color: #536176; font-size: 18rpx; white-space: nowrap; text-align: center; }
.shortcut-icon { width: 76rpx; height: 76rpx; border-radius: 24rpx; align-items: center; justify-content: center; margin-bottom: 10rpx; }
.green { background-color: #e7f8f1; }
.purple { background-color: #efebff; }
.blue { background-color: #e8f2ff; }
.orange { background-color: #fff0e8; }
.section-head { margin-top: 34rpx; margin-bottom: 18rpx; flex-direction: row; justify-content: space-between; align-items: center; }
.more { color: #1673ed; font-size: 22rpx; }
.plan-card { height: 150rpx; background-color: #fff; border: 1rpx solid #e6edf7; border-radius: 16rpx; padding: 26rpx; flex-direction: row; align-items: center; justify-content: space-between; }
.plan-name { font-size: 29rpx; font-weight: 700; }
.plan-meta { margin-top: 12rpx; color: #8290a5; font-size: 22rpx; }
.score { align-items: center; color: #18aa78; font-size: 19rpx; }
.score-number { font-size: 48rpx; line-height: 52rpx; color: #0868f7; font-weight: 800; }
.news-preview { min-height: 130rpx; background-color: #fff; border-radius: 16rpx; padding: 24rpx; flex-direction: row; justify-content: space-between; align-items: center; border: 1rpx solid #e6edf7; }
.news-copy { flex: 1; padding-right: 16rpx; }
.news-title { font-size: 27rpx; font-weight: 700; }
.news-meta { margin-top: 14rpx; font-size: 21rpx; color: #94a0b1; }
.news-art { width: 118rpx; height: 82rpx; border-radius: 10rpx; flex-shrink: 0; }
</style>
+18 -7
View File
@@ -1,26 +1,37 @@
<template>
<view class="splash page-white" @click="enter">
<view class="splash-status"></view>
<view class="splash page-white" @click="enter" :style="pageStyle()">
<view class="splash-status" :style="statusStyle(0)"></view>
<view class="brand-area">
<BrandLogo />
<text class="tagline">智能评估 · 精准匹配</text>
<text class="slogan">让融资更简单</text>
</view>
<image class="splash-art" src="/static/images/shield.png" mode="aspectFit"></image>
<image class="splash-art" src="/static/mobile/images/shield.png" mode="aspectFit"></image>
<view class="launch-line"><view class="launch-dot"></view><text>开启智能融资新体验</text></view>
</view>
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { statusStyle, pageStyle } = usePageLayout()
import BrandLogo from '@/components/BrandLogo.uvue'
import { isLoggedIn } from '@/services/api.uts'
const enter = () => { uni.redirectTo({ url: isLoggedIn() ? '/pages/home/home' : '/pages/login/login' }) }
setTimeout(() => enter(), 1800)
let enterTimer = 0
let entered = false
const enter = () => {
if (entered) return
entered = true
clearTimeout(enterTimer)
uni.redirectTo({ url: isLoggedIn() ? '/pages/home/home' : '/pages/login/login' })
}
onShow(() => { if (!entered) enterTimer = setTimeout(() => enter(), 1800) })
onHide(() => { clearTimeout(enterTimer) })
onUnmounted(() => { clearTimeout(enterTimer) })
</script>
<style>
.splash { min-height: 100vh; align-items: center; position: relative; overflow: hidden; }
.splash-status { height: var(--status-bar-height); }
.splash { align-items: center; position: relative; overflow: hidden; }
.splash-status { }
.brand-area { align-items: center; margin-top: 220rpx; }
.tagline { margin-top: 18rpx; color: #32405a; font-size: 28rpx; font-weight: 600; }
.slogan { margin-top: 42rpx; color: #102557; font-size: 33rpx; font-weight: 800; }
+33 -6
View File
@@ -1,28 +1,55 @@
<template>
<view class="page">
<view class="page" :style="pageStyle()">
<AppHeader title="课程详情" />
<scroll-view scroll-y class="lesson-detail-scroll"><view class="lesson-detail-content"><image class="lesson-cover" :src="cover" mode="aspectFill"></image><text class="lesson-detail-title">{{ title }}</text><view class="lesson-detail-meta"><text>{{ tag }}</text><text>{{ duration }} · 课程</text></view><view class="lesson-progress"><view class="progress-fill"></view></view><text class="lesson-intro">本课程将从实际申请场景出发,帮助你理解准备材料、评估资质和选择产品的关键步骤。</text><view class="chapter" v-for="(item,index) in chapters" :key="item"><view class="chapter-index">{{ index + 1 }}</view><view class="chapter-copy"><text class="chapter-title">{{ item }}</text><text class="chapter-desc">预计 2 分钟 · 可随时回看</text></view><AppIcon name="chevron-right" :size="28" muted /></view><button class="primary-button start-learning" @click="start">开始学习</button></view></scroll-view>
<scroll-view scroll-y class="lesson-detail-scroll" :style="contentStyle(96, false)"><view class="lesson-detail-content"><image class="lesson-cover" :src="cover" mode="aspectFill"></image><text class="lesson-detail-title">{{ title }}</text><view class="lesson-detail-meta"><text>{{ tag }}</text><text>{{ duration }} · 课程</text></view><view class="lesson-progress"><view class="progress-fill"></view></view><text class="lesson-intro">本课程将从实际申请场景出发,帮助你理解准备材料、评估资质和选择产品的关键步骤。</text><view class="chapter" v-for="(item,index) in chapters" :key="item"><view class="chapter-index">{{ index + 1 }}</view><view class="chapter-copy"><text class="chapter-title">{{ item }}</text><text class="chapter-desc">预计 2 分钟 · 可随时回看</text></view><AppIcon name="chevron-right" :size="28" muted /></view><button class="primary-button start-learning" @click="start">开始学习</button></view></scroll-view>
</view>
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { contentStyle, pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import AppIcon from '@/components/AppIcon.uvue'
const title = ref('如何提高个人贷款通过率')
const tag = ref('个人融资')
const duration = ref('6 分钟')
const cover = ref('/static/images/news-calculator.png')
const cover = ref('/static/mobile/images/news-calculator.png')
const chapters = ref(['理解征信评分', '整理收入与负债', '准备申请材料', '选择适合的产品'])
onLoad((options) => {
if (options == null) return
const lessonId = options['id'] == null ? '' : options['id'] as string
if (lessonId == '1') { title.value = '小微企业融资材料清单'; tag.value = '企业融资'; duration.value = '8 分钟'; cover.value = '/static/images/news-business.png'; chapters.value = ['确认主体与经营信息', '整理银行流水与纳税记录', '准备企业资质材料', '核对授权与提交清单'] }
else if (lessonId == '2') { title.value = '读懂贷款利率与还款方式'; tag.value = '基础知识'; duration.value = '5 分钟'; cover.value = '/static/images/news-city.png'; chapters.value = ['年化利率怎么看', '等额本息与先息后本', '期限对总成本的影响', '选择适合现金流的方案'] }
if (lessonId == '1') { title.value = '小微企业融资材料清单'; tag.value = '企业融资'; duration.value = '8 分钟'; cover.value = '/static/mobile/images/news-business.png'; chapters.value = ['确认主体与经营信息', '整理银行流水与纳税记录', '准备企业资质材料', '核对授权与提交清单'] }
else if (lessonId == '2') { title.value = '读懂贷款利率与还款方式'; tag.value = '基础知识'; duration.value = '5 分钟'; cover.value = '/static/mobile/images/news-city.png'; chapters.value = ['年化利率怎么看', '等额本息与先息后本', '期限对总成本的影响', '选择适合现金流的方案'] }
if (options['title'] != null) title.value = options['title'] as string
})
const start = () => { uni.showToast({ title: '课程内容已准备好', icon: 'none' }) }
</script>
<style>
.lesson-detail-scroll { height: calc(100vh - var(--status-bar-height) - 96rpx); }.lesson-detail-content { padding: 26rpx 28rpx 90rpx; }.lesson-cover { width: 100%; height: 290rpx; border-radius: 18rpx; }.lesson-detail-title { display: block; color: #172640; font-size: 36rpx; line-height: 50rpx; font-weight: 800; margin-top: 24rpx; }.lesson-detail-meta { flex-direction: row; justify-content: space-between; color: #0868f7; font-size: 21rpx; margin-top: 12rpx; }.lesson-progress { height: 12rpx; margin-top: 22rpx; border-radius: 8rpx; background-color: #e4edf8; overflow: hidden; }.progress-fill { width: 12%; height: 100%; border-radius: 8rpx; background-color: #0868f7; }.lesson-intro { display: block; color: #5f6d81; font-size: 24rpx; line-height: 42rpx; margin: 26rpx 0; }.chapter { min-height: 100rpx; padding: 16rpx 0; border-bottom: 1rpx solid #e7edf5; flex-direction: row; align-items: center; }.chapter-index { width: 48rpx; height: 48rpx; border-radius: 16rpx; background-color: #e8f2ff; color: #0868f7; align-items: center; justify-content: center; font-size: 22rpx; font-weight: 800; margin-right: 16rpx; }.chapter-copy { flex: 1; }.chapter-title { color: #23324a; font-size: 25rpx; font-weight: 700; }.chapter-desc { display: block; color: #97a3b2; font-size: 19rpx; margin-top: 7rpx; }.start-learning { margin-top: 28rpx; }
.lesson-detail-scroll { }
.lesson-detail-content { padding: 26rpx 28rpx 90rpx; }
.lesson-cover { width: 100%; height: 290rpx; border-radius: 18rpx; }
.lesson-detail-title {
/* #ifndef APP */
display: block;
/* #endif */
color: #172640; font-size: 36rpx; line-height: 50rpx; font-weight: 800; margin-top: 24rpx; }
.lesson-detail-meta { flex-direction: row; justify-content: space-between; color: #0868f7; font-size: 21rpx; margin-top: 12rpx; }
.lesson-progress { height: 12rpx; margin-top: 22rpx; border-radius: 8rpx; background-color: #e4edf8; overflow: hidden; }
.progress-fill { width: 12%; height: 100%; border-radius: 8rpx; background-color: #0868f7; }
.lesson-intro {
/* #ifndef APP */
display: block;
/* #endif */
color: #5f6d81; font-size: 24rpx; line-height: 42rpx; margin: 26rpx 0; }
.chapter { min-height: 100rpx; padding: 16rpx 0; border-bottom: 1rpx solid #e7edf5; flex-direction: row; align-items: center; }
.chapter-index { width: 48rpx; height: 48rpx; border-radius: 16rpx; background-color: #e8f2ff; color: #0868f7; align-items: center; justify-content: center; font-size: 22rpx; font-weight: 800; margin-right: 16rpx; }
.chapter-copy { flex: 1; }
.chapter-title { color: #23324a; font-size: 25rpx; font-weight: 700; }
.chapter-desc {
/* #ifndef APP */
display: block;
/* #endif */
color: #97a3b2; font-size: 19rpx; margin-top: 7rpx; }
.start-learning { margin-top: 28rpx; }
</style>
+31 -23
View File
@@ -1,6 +1,6 @@
<template>
<view class="login-page page-white">
<view class="status-space"></view>
<view class="login-page page-white" :style="pageStyle()">
<view class="status-space" :style="statusStyle(90)"></view>
<view class="login-content">
<BrandLogo />
<text class="welcome">欢迎登录 AI融资</text>
@@ -8,11 +8,11 @@
<view :class="mode == 'phone' ? 'login-tab selected' : 'login-tab'" @click="switchMode('phone')"><text>手机号登录</text></view>
<view :class="mode == 'password' ? 'login-tab selected' : 'login-tab'" @click="switchMode('password')"><text>密码登录</text></view>
</view>
<view class="input-box"><text class="input-prefix">+86</text><input v-model="phone" type="number" maxlength="11" placeholder="请输入手机号" /></view>
<view v-if="mode == 'password'" class="input-box"><text class="input-prefix">密码</text><input v-model="password" password placeholder="请输入登录密码" /></view>
<view v-if="mode == 'phone' && smsSent" class="input-box"><text class="input-prefix">验证码</text><input v-model="smsCode" type="number" maxlength="6" placeholder="请输入 6 位验证码" /><text class="countdown" @click="resendSms">{{ countdown > 0 ? countdown.toString() + 's' : '重新获取' }}</text></view>
<view class="input-box"><text class="input-prefix">+86</text><input v-model="phone" type="number" maxlength="11" placeholder="请输入手机号" class="input-box-input" /></view>
<view v-if="mode == 'password'" class="input-box"><text class="input-prefix">密码</text><input v-model="password" password placeholder="请输入登录密码" class="input-box-input" /></view>
<view v-if="mode == 'phone' && smsSent" class="input-box"><text class="input-prefix">验证码</text><input v-model="smsCode" type="number" maxlength="6" placeholder="请输入 6 位验证码" class="input-box-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>
<button class="login-button" :disabled="loading" @click="login" :class="loading ? 'login-disabled' : ''">{{ loading ? '处理中...' : (mode == 'phone' ? (smsSent ? '登录' : '获取验证码') : '登录') }}</button>
<view class="other-login"><view class="line"></view><text>其他登录方式</text><view class="line"></view></view>
<view class="social-row">
<view class="social wechat"><AppIcon name="message" :size="38" /></view><view class="social apple"><AppIcon name="lock" :size="38" /></view><view class="social phone"><AppIcon name="user" :size="38" /></view>
@@ -23,6 +23,8 @@
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { statusStyle, pageStyle } = usePageLayout()
import BrandLogo from '@/components/BrandLogo.uvue'
import AppIcon from '@/components/AppIcon.uvue'
import { requestSmsCode, loginWithSms, loginWithPassword, saveSession } from '@/services/api.uts'
@@ -35,7 +37,7 @@
const loading = ref(false)
const errorMessage = ref('')
const agreed = ref(true)
let countdownTimer: number | null = null
let countdownTimer = 0
const switchMode = (nextMode: string) => {
mode.value = nextMode
@@ -50,12 +52,12 @@
}
const startCountdown = () => {
countdown.value = 60
if (countdownTimer != null) clearInterval(countdownTimer)
clearInterval(countdownTimer)
countdownTimer = setInterval(() => {
if (countdown.value <= 1) {
countdown.value = 0
if (countdownTimer != null) clearInterval(countdownTimer)
countdownTimer = null
clearInterval(countdownTimer)
countdownTimer = 0
} else {
countdown.value -= 1
}
@@ -70,11 +72,11 @@
loading.value = true
errorMessage.value = ''
try {
if (!(await requestSmsCode(phone.value))) throw '验证码发送失败,请稍后重试'
if (!(await requestSmsCode(phone.value))) throw new Error('验证码发送失败,请稍后重试')
startCountdown()
uni.showToast({ title: '验证码已重新发送', icon: 'none' })
} catch (error) {
errorMessage.value = error == null ? '验证码发送失败,请稍后重试' : error.toString()
errorMessage.value = error instanceof Error ? error.message : '验证码发送失败,请稍后重试'
} finally {
loading.value = false
}
@@ -90,30 +92,30 @@
if (!smsSent.value) {
if (countdown.value > 0) return
const sent = await requestSmsCode(phone.value)
if (!sent) throw '验证码发送失败,请稍后重试'
if (!sent) throw new Error('验证码发送失败,请稍后重试')
smsSent.value = true
startCountdown()
uni.showToast({ title: '验证码已发送', icon: 'none' })
} else {
if (!/^[0-9]{6}$/.test(smsCode.value)) throw '请输入 6 位数字验证码'
if (!/^[0-9]{6}$/.test(smsCode.value)) throw new Error('请输入 6 位数字验证码')
finishLogin(await loginWithSms(phone.value, smsCode.value))
}
} else {
if (password.value.length < 6) throw '密码至少需要 6 位'
if (password.value.length < 6) throw new Error('密码至少需要 6 位')
finishLogin(await loginWithPassword(phone.value, password.value))
}
} catch (error) {
errorMessage.value = error == null ? '登录失败,请稍后重试' : error.toString()
errorMessage.value = error instanceof Error ? error.message : '登录失败,请稍后重试'
} finally {
loading.value = false
}
}
onUnmounted(() => { if (countdownTimer != null) clearInterval(countdownTimer) })
onUnmounted(() => { clearInterval(countdownTimer) })
</script>
<style>
.login-page { min-height: 100vh; }
.status-space { height: calc(var(--status-bar-height) + 90rpx); }
.login-page { }
.status-space { }
.login-content { padding: 0 54rpx; }
.welcome { font-size: 48rpx; line-height: 70rpx; font-weight: 800; margin-top: 60rpx; color: #122347; }
.login-tabs { flex-direction: row; margin-top: 52rpx; border-bottom: 2rpx solid #eaf0f7; }
@@ -121,16 +123,22 @@
.login-tab.selected { color: #0868f7; font-weight: 700; border-bottom: 5rpx solid #0868f7; }
.input-box { height: 96rpx; margin-top: 28rpx; border: 2rpx solid #dfe7f2; border-radius: 14rpx; padding: 0 24rpx; flex-direction: row; align-items: center; background-color: #fbfdff; }
.input-prefix { font-size: 25rpx; color: #3c4a61; margin-right: 24rpx; }
.input-box input { flex: 1; font-size: 28rpx; color: #17243c; }
.input-box-input { flex: 1; font-size: 28rpx; color: #17243c; }
.countdown { min-width: 90rpx; text-align: right; color: #0868f7; font-size: 24rpx; }
.error-message { display: block; margin-top: 16rpx; color: #e55353; font-size: 23rpx; }
.error-message {
/* #ifndef APP */
display: block;
/* #endif */
margin-top: 16rpx; color: #e55353; font-size: 23rpx; }
.login-button { height: 92rpx; line-height: 92rpx; margin-top: 32rpx; border-radius: 14rpx; color: white; font-size: 29rpx; font-weight: 700; background-color: #0868f7; box-shadow: 0 16rpx 30rpx rgba(8, 104, 247, .2); }
.login-button[disabled] { opacity: .6; }
.login-disabled { opacity: .6; }
.other-login { margin-top: 62rpx; flex-direction: row; align-items: center; justify-content: center; color: #a0a9b7; font-size: 22rpx; }
.line { height: 1rpx; background-color: #e5eaf1; width: 120rpx; margin: 0 20rpx; }
.social-row { flex-direction: row; justify-content: center; margin-top: 40rpx; }
.social { width: 84rpx; height: 84rpx; border-radius: 50%; align-items: center; justify-content: center; margin: 0 25rpx; font-size: 28rpx; font-weight: 800; }
.wechat { background-color: #e9f8ef; color: #12ad58; }.apple { background-color: #f2f4f7; color: #111; }.phone { background-color: #fff0ea; color: #ff6530; }
.wechat { background-color: #e9f8ef; color: #12ad58; }
.apple { background-color: #f2f4f7; color: #111; }
.phone { background-color: #fff0ea; color: #ff6530; }
.agreement { margin-top: 62rpx; flex-direction: row; align-items: center; justify-content: center; color: #8793a8; font-size: 21rpx; }
.check { width: 28rpx; height: 28rpx; border: 2rpx solid #aeb8c7; border-radius: 6rpx; align-items: center; justify-content: center; margin-right: 10rpx; }
.checked { color: white; background-color: #0868f7; border-color: #0868f7; }
+30 -9
View File
@@ -1,15 +1,15 @@
<template>
<view class="page">
<view class="page" :style="pageStyle()">
<AppHeader title="为您匹配到 3 个方案" />
<view class="filter-bar"><view :class="filter == 'all' ? 'filter active' : 'filter'" @click="filter='all'"><text>综合排序</text></view><view :class="filter == 'amount' ? 'filter active' : 'filter'" @click="filter='amount'"><text>额度高</text></view><view :class="filter == 'rate' ? 'filter active' : 'filter'" @click="filter='rate'"><text>利率低</text></view></view>
<scroll-view scroll-y class="match-scroll">
<scroll-view scroll-y class="match-scroll" :style="contentStyle(318, false)">
<view class="match-content">
<view v-if="loading" class="state-panel"><text>正在为您匹配方案...</text></view>
<view v-else-if="errorMessage.length > 0" class="state-panel error-panel"><text>{{ errorMessage }}</text><button class="retry-button" @click="loadMatches">重新加载</button></view>
<view class="match-card" v-for="item in matches" :key="item.rank">
<view :class="'rank rank-' + item.rank"><text>{{ item.rank }}</text></view>
<view class="bank-logo"><AppIcon name="building" :size="40" /></view>
<view class="match-main"><text class="bank-name">{{ item.bank }}</text><text class="match-tag">个人信用贷 · 资料简</text><view class="metrics"><view><text class="metric-value">{{ item.amount }}</text><text class="metric-label">最高额度</text></view><view><text class="metric-value rate-value">{{ item.rate }}</text><text class="metric-label">年利率</text></view><view><text class="metric-value">{{ item.term }}</text><text class="metric-label">贷款期限</text></view></view></view>
<view class="match-main"><text class="bank-name">{{ item.bank }}</text><text class="match-tag">个人信用贷 · 资料简</text><view class="metrics"><view class="metrics-view"><text class="metric-value">{{ item.amount }}</text><text class="metric-label">最高额度</text></view><view class="metrics-view"><text class="metric-value rate-value">{{ item.rate }}</text><text class="metric-label">年利率</text></view><view class="metrics-view"><text class="metric-value">{{ item.term }}</text><text class="metric-label">贷款期限</text></view></view></view>
<button class="detail-button" @click="openProduct(item.rank)">查看详情</button>
</view>
<text class="list-tip">匹配结果基于您的评估信息,实际额度以机构审批为准</text>
@@ -20,6 +20,8 @@
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { contentStyle, pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import BottomNav from '@/components/BottomNav.uvue'
import { fetchMatches } from '@/services/api.uts'
@@ -35,13 +37,32 @@
loading.value = false
}
onMounted(() => { loadMatches() })
const openProduct = (rank: number) => { uni.navigateTo({ url: '/pages/product/product?id=' + rank }) }
const openProduct = (rank: any | null) => { if (rank != null) uni.navigateTo({ url: '/pages/product/product?id=' + rank.toString() }) }
</script>
<style>
.filter-bar { height: 92rpx; background-color: #fff; flex-direction: row; align-items: center; padding: 0 30rpx; border-bottom: 1rpx solid #e9eef6; }.filter { flex: 1; height: 92rpx; align-items: center; justify-content: center; color: #758298; font-size: 24rpx; }.filter.active { color: #0868f7; font-weight: 700; border-bottom: 4rpx solid #0868f7; }
.match-scroll { height: calc(100vh - var(--status-bar-height) - 318rpx); }.match-content { padding: 24rpx 28rpx 150rpx; }.match-card { min-height: 310rpx; border-radius: 18rpx; margin-bottom: 22rpx; background-color: #fff; border: 1rpx solid #e4ebf5; padding: 26rpx 22rpx 22rpx; position: relative; flex-direction: row; box-shadow: 0 8rpx 24rpx rgba(24, 54, 95, .05); }.rank { position: absolute; left: -7rpx; top: 20rpx; width: 48rpx; height: 48rpx; color: white; border-radius: 0 24rpx 24rpx 0; align-items: center; justify-content: center; font-weight: 800; }.rank-1 { background-color: #ff8a13; }.rank-2 { background-color: #16b48a; }.rank-3 { background-color: #ee3333; }
.bank-logo { width: 78rpx; height: 78rpx; border-radius: 50%; background-color: #e41e2b; color: white; align-items: center; justify-content: center; font-size: 30rpx; font-weight: 800; margin-left: 22rpx; margin-right: 18rpx; }.match-main { flex: 1; }.bank-name { font-size: 28rpx; font-weight: 800; }.match-tag { color: #8794a7; font-size: 20rpx; margin-top: 8rpx; }.metrics { flex-direction: row; justify-content: space-between; margin-top: 30rpx; }.metrics view { flex: 1; }.metric-value { font-size: 26rpx; font-weight: 800; color: #1c2a41; }.rate-value { color: #0868f7; }.metric-label { margin-top: 7rpx; font-size: 18rpx; color: #95a0b1; }
.detail-button { position: absolute; right: 22rpx; bottom: 18rpx; width: 160rpx; height: 62rpx; line-height: 62rpx; border-radius: 12rpx; margin: 0; padding: 0; background-color: #0868f7; color: white; font-size: 22rpx; font-weight: 700; }.list-tip { text-align: center; color: #9ca8b8; font-size: 20rpx; line-height: 34rpx; margin: 12rpx 30rpx; }
.state-panel { min-height: 260rpx; align-items: center; justify-content: center; color: #8793a6; font-size: 24rpx; }.error-panel { color: #e05a5a; }.retry-button { margin-top: 20rpx; height: 64rpx; line-height: 64rpx; border-radius: 12rpx; color: #0868f7; background-color: #e8f2ff; font-size: 22rpx; }
.filter-bar { height: 92rpx; background-color: #fff; flex-direction: row; align-items: center; padding: 0 30rpx; border-bottom: 1rpx solid #e9eef6; }
.filter { flex: 1; height: 92rpx; align-items: center; justify-content: center; color: #758298; font-size: 24rpx; }
.filter.active { color: #0868f7; font-weight: 700; border-bottom: 4rpx solid #0868f7; }
.match-scroll { }
.match-content { padding: 24rpx 28rpx 150rpx; }
.match-card { min-height: 310rpx; border-radius: 18rpx; margin-bottom: 22rpx; background-color: #fff; border: 1rpx solid #e4ebf5; padding: 26rpx 22rpx 22rpx; position: relative; flex-direction: row; box-shadow: 0 8rpx 24rpx rgba(24, 54, 95, .05); }
.rank { position: absolute; left: -7rpx; top: 20rpx; width: 48rpx; height: 48rpx; color: white; border-radius: 0 24rpx 24rpx 0; align-items: center; justify-content: center; font-weight: 800; }
.rank-1 { background-color: #ff8a13; }
.rank-2 { background-color: #16b48a; }
.rank-3 { background-color: #ee3333; }
.bank-logo { width: 78rpx; height: 78rpx; border-radius: 50%; background-color: #e41e2b; color: white; align-items: center; justify-content: center; font-size: 30rpx; font-weight: 800; margin-left: 22rpx; margin-right: 18rpx; }
.match-main { flex: 1; }
.bank-name { font-size: 28rpx; font-weight: 800; }
.match-tag { color: #8794a7; font-size: 20rpx; margin-top: 8rpx; }
.metrics { flex-direction: row; justify-content: space-between; margin-top: 30rpx; }
.metrics-view { flex: 1; }
.metric-value { font-size: 26rpx; font-weight: 800; color: #1c2a41; }
.rate-value { color: #0868f7; }
.metric-label { margin-top: 7rpx; font-size: 18rpx; color: #95a0b1; }
.detail-button { position: absolute; right: 22rpx; bottom: 18rpx; width: 160rpx; height: 62rpx; line-height: 62rpx; border-radius: 12rpx; margin: 0; padding: 0; background-color: #0868f7; color: white; font-size: 22rpx; font-weight: 700; }
.list-tip { text-align: center; color: #9ca8b8; font-size: 20rpx; line-height: 34rpx; margin: 12rpx 30rpx; }
.state-panel { min-height: 260rpx; align-items: center; justify-content: center; color: #8793a6; font-size: 24rpx; }
.error-panel { color: #e05a5a; }
.retry-button { margin-top: 20rpx; height: 64rpx; line-height: 64rpx; border-radius: 12rpx; color: #0868f7; background-color: #e8f2ff; font-size: 22rpx; }
</style>
+23 -4
View File
@@ -1,8 +1,8 @@
<template>
<view class="page">
<view class="page" :style="pageStyle()">
<AppHeader title="消息中心" :back="false" />
<view class="message-tabs"><view v-for="item in tabs" :key="item" :class="tab == item ? 'message-tab active' : 'message-tab'" @click="tab=item"><text>{{ item }}</text></view></view>
<scroll-view scroll-y class="messages-scroll">
<scroll-view scroll-y class="messages-scroll" :style="contentStyle(314, false)">
<view class="messages-content">
<view class="notice" v-for="item in notices" :key="item.title"><view :class="'notice-icon ' + item.color"><AppIcon :name="item.icon" :size="44" /></view><view class="notice-copy"><view class="notice-head"><text class="notice-title">{{ item.title }}</text><text class="notice-time">{{ item.time }}</text></view><text class="notice-desc">{{ item.desc }}</text></view><view v-if="item.unread" class="unread"></view></view>
</view>
@@ -12,12 +12,15 @@
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { contentStyle, pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import BottomNav from '@/components/BottomNav.uvue'
import AppIcon from '@/components/AppIcon.uvue'
const tabs = ['全部', '系统通知', '审批进度', '活动消息']
const tab = ref('全部')
const notices = [
type Notice = { icon: string, color: string, title: string, desc: string, time: string, unread: boolean }
const notices: Notice[] = [
{ icon: 'approval', color: 'blue', title: '审核进度更新', desc: '您的融资评估已完成,匹配到3个方案。', time: '10:30', unread: true },
{ icon: 'payout', color: 'purple', title: '放款成功', desc: '您的招商银行闪电贷已成功放款。', time: '09:15', unread: false },
{ icon: 'gift', color: 'orange', title: '活动通知', desc: '邀请好友赢积分活动进行中。', time: '08:42', unread: false },
@@ -26,5 +29,21 @@
</script>
<style>
.message-tabs { height: 88rpx; flex-direction: row; background-color: #fff; border-bottom: 1rpx solid #e6edf6; padding: 0 18rpx; }.message-tab { flex: 1; align-items: center; justify-content: center; color: #7b8799; font-size: 22rpx; }.message-tab.active { color: #0868f7; font-weight: 700; border-bottom: 4rpx solid #0868f7; }.messages-scroll { height: calc(100vh - var(--status-bar-height) - 314rpx); }.messages-content { padding: 10rpx 28rpx 150rpx; }.notice { min-height: 150rpx; background-color: #fff; border-bottom: 1rpx solid #e8eef6; flex-direction: row; align-items: center; position: relative; }.notice-icon { width: 78rpx; height: 78rpx; border-radius: 25rpx; align-items: center; justify-content: center; color: #fff; font-size: 24rpx; font-weight: 800; margin-right: 20rpx; }.blue { background-color: #3b7df1; }.purple { background-color: #b74ae8; }.orange { background-color: #ff8431; }.green { background-color: #18ae82; }.notice-copy { flex: 1; }.notice-head { flex-direction: row; justify-content: space-between; align-items: center; }.notice-title { font-size: 27rpx; font-weight: 700; }.notice-time { color: #9ca7b7; font-size: 19rpx; }.notice-desc { color: #778499; font-size: 22rpx; margin-top: 11rpx; }.unread { position: absolute; width: 13rpx; height: 13rpx; border-radius: 50%; background-color: #ff4d4f; right: 2rpx; top: 40rpx; }
.message-tabs { height: 88rpx; flex-direction: row; background-color: #fff; border-bottom: 1rpx solid #e6edf6; padding: 0 18rpx; }
.message-tab { flex: 1; align-items: center; justify-content: center; color: #7b8799; font-size: 22rpx; }
.message-tab.active { color: #0868f7; font-weight: 700; border-bottom: 4rpx solid #0868f7; }
.messages-scroll { }
.messages-content { padding: 10rpx 28rpx 150rpx; }
.notice { min-height: 150rpx; background-color: #fff; border-bottom: 1rpx solid #e8eef6; flex-direction: row; align-items: center; position: relative; }
.notice-icon { width: 78rpx; height: 78rpx; border-radius: 25rpx; align-items: center; justify-content: center; color: #fff; font-size: 24rpx; font-weight: 800; margin-right: 20rpx; }
.blue { background-color: #3b7df1; }
.purple { background-color: #b74ae8; }
.orange { background-color: #ff8431; }
.green { background-color: #18ae82; }
.notice-copy { flex: 1; }
.notice-head { flex-direction: row; justify-content: space-between; align-items: center; }
.notice-title { font-size: 27rpx; font-weight: 700; }
.notice-time { color: #9ca7b7; font-size: 19rpx; }
.notice-desc { color: #778499; font-size: 22rpx; margin-top: 11rpx; }
.unread { position: absolute; width: 13rpx; height: 13rpx; border-radius: 50%; background-color: #ff4d4f; right: 2rpx; top: 40rpx; }
</style>
+31 -13
View File
@@ -1,27 +1,30 @@
<template>
<view class="page">
<view class="page" :style="pageStyle()">
<AppHeader title="新闻" />
<view class="news-tabs"><view v-for="item in tabs" :key="item" :class="tab == item ? 'news-tab active' : 'news-tab'" @click="tab=item"><text>{{ item }}</text></view></view>
<scroll-view scroll-y class="news-scroll">
<scroll-view scroll-y class="news-scroll" :style="contentStyle(184, false)">
<view class="news-content">
<view class="headline" @click="openArticle('headline')"><image class="headline-art" src="/static/images/news-city.png" mode="aspectFill"></image><view class="headline-copy"><text class="headline-tag">政策速递</text><text class="headline-title">普惠金融新政解读:小微企业迎来融资利好</text><text class="headline-date">2026-09-08</text></view></view>
<view class="headline" @click="openArticle('headline')"><image class="headline-art" src="/static/mobile/images/news-city.png" mode="aspectFill"></image><view class="headline-copy"><text class="headline-tag">政策速递</text><text class="headline-title">普惠金融新政解读:小微企业迎来融资利好</text><text class="headline-date">2026-09-08</text></view></view>
<view v-if="filteredArticles.length > 0"><view class="article" v-for="item in filteredArticles" :key="item.title" @click="openArticle(item.title)"><view class="article-copy"><text class="article-title">{{ item.title }}</text><text class="article-summary">{{ item.summary }}</text><text class="article-meta">{{ item.category }} · {{ item.date }}</text></view><image class="article-art" :src="item.image" mode="aspectFill"></image></view></view>
<view v-else class="empty-state"><AppIcon name="search" :size="48" muted /><text>暂无该分类新闻</text></view>
<view v-else class="empty-state"><AppIcon name="search" :size="48" muted /><text class="empty-state-text">暂无该分类新闻</text></view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { contentStyle, pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import AppIcon from '@/components/AppIcon.uvue'
const tabs = ['推荐', '企业融资', '个人融资', '政策解读']
const tab = ref('推荐')
const articles = [
{ title: '如何提高个人贷款通过率', summary: '从征信、负债和收入三个维度做好申请准备。', category: '个人融资', date: '2026-09-05', image: '/static/images/news-calculator.png' },
{ title: '银行最新贷款产品汇总', summary: '本月值得关注的低息信用融资产品。', category: '个人融资', date: '2026-09-03', image: '/static/images/news-city.png' },
{ title: '小微企业融资材料清单', summary: '一次准备齐全,减少补充资料时间。', category: '企业融资', date: '2026-08-30', image: '/static/images/news-business.png' },
{ title: '普惠金融新政解读', summary: '了解小微企业融资支持政策和申请重点。', category: '政策解读', date: '2026-08-28', image: '/static/images/news-policy.png' }
type NewsSummary = { title: string, summary: string, category: string, date: string, image: string }
const articles: NewsSummary[] = [
{ title: '如何提高个人贷款通过率', summary: '从征信、负债和收入三个维度做好申请准备。', category: '个人融资', date: '2026-09-05', image: '/static/mobile/images/news-calculator.png' },
{ title: '银行最新贷款产品汇总', summary: '本月值得关注的低息信用融资产品。', category: '个人融资', date: '2026-09-03', image: '/static/mobile/images/news-city.png' },
{ title: '小微企业融资材料清单', summary: '一次准备齐全,减少补充资料时间。', category: '企业融资', date: '2026-08-30', image: '/static/mobile/images/news-business.png' },
{ title: '普惠金融新政解读', summary: '了解小微企业融资支持政策和申请重点。', category: '政策解读', date: '2026-08-28', image: '/static/mobile/images/news-policy.png' }
]
const filteredArticles = computed(() => {
if (tab.value == '推荐') return articles
@@ -31,8 +34,23 @@
</script>
<style>
.news-tabs { height: 88rpx; flex-direction: row; align-items: center; background-color: #fff; border-bottom: 1rpx solid #e8eef6; padding: 0 22rpx; }.news-tab { flex: 1; height: 88rpx; justify-content: center; align-items: center; color: #78869a; font-size: 23rpx; }.news-tab.active { color: #0868f7; font-weight: 700; border-bottom: 4rpx solid #0868f7; }.news-scroll { height: calc(100vh - var(--status-bar-height) - 184rpx); }.news-content { padding: 24rpx 28rpx 70rpx; }
.headline { height: 260rpx; padding: 28rpx; border-radius: 18rpx; background-color: #0b2a66; flex-direction: row; overflow: hidden; position: relative; }.headline-copy { width: 65%; z-index: 2; }.headline-tag { color: #75caff; font-size: 21rpx; font-weight: 700; }.headline-title { color: #fff; font-size: 31rpx; line-height: 45rpx; font-weight: 800; margin-top: 16rpx; }.headline-date { color: #bfd7ff; font-size: 20rpx; margin-top: 18rpx; }.headline-art { position: absolute; right: 0; top: 0; width: 56%; height: 100%; opacity: .78; }
.article { min-height: 185rpx; padding: 28rpx 0; flex-direction: row; border-bottom: 1rpx solid #e6edf6; }.article-copy { flex: 1; padding-right: 20rpx; }.article-title { color: #1b2941; font-size: 28rpx; line-height: 41rpx; font-weight: 700; }.article-summary { color: #778499; font-size: 22rpx; line-height: 34rpx; margin-top: 10rpx; }.article-meta { color: #a0aaba; font-size: 19rpx; margin-top: 12rpx; }.article-art { width: 150rpx; height: 120rpx; border-radius: 12rpx; flex-shrink: 0; }
.empty-state { min-height: 250rpx; align-items: center; justify-content: center; color: #94a0b1; font-size: 23rpx; }.empty-state text { margin-top: 15rpx; }
.news-tabs { height: 88rpx; flex-direction: row; align-items: center; background-color: #fff; border-bottom: 1rpx solid #e8eef6; padding: 0 22rpx; }
.news-tab { flex: 1; height: 88rpx; justify-content: center; align-items: center; color: #78869a; font-size: 23rpx; }
.news-tab.active { color: #0868f7; font-weight: 700; border-bottom: 4rpx solid #0868f7; }
.news-scroll { }
.news-content { padding: 24rpx 28rpx 70rpx; }
.headline { height: 260rpx; padding: 28rpx; border-radius: 18rpx; background-color: #0b2a66; flex-direction: row; overflow: hidden; position: relative; }
.headline-copy { width: 65%; z-index: 2; }
.headline-tag { color: #75caff; font-size: 21rpx; font-weight: 700; }
.headline-title { color: #fff; font-size: 31rpx; line-height: 45rpx; font-weight: 800; margin-top: 16rpx; }
.headline-date { color: #bfd7ff; font-size: 20rpx; margin-top: 18rpx; }
.headline-art { position: absolute; right: 0; top: 0; width: 56%; height: 100%; opacity: .78; }
.article { min-height: 185rpx; padding: 28rpx 0; flex-direction: row; border-bottom: 1rpx solid #e6edf6; }
.article-copy { flex: 1; padding-right: 20rpx; }
.article-title { color: #1b2941; font-size: 28rpx; line-height: 41rpx; font-weight: 700; }
.article-summary { color: #778499; font-size: 22rpx; line-height: 34rpx; margin-top: 10rpx; }
.article-meta { color: #a0aaba; font-size: 19rpx; margin-top: 12rpx; }
.article-art { width: 150rpx; height: 120rpx; border-radius: 12rpx; flex-shrink: 0; }
.empty-state { min-height: 250rpx; align-items: center; justify-content: center; color: #94a0b1; font-size: 23rpx; }
.empty-state-text { margin-top: 15rpx; }
</style>
+33 -9
View File
@@ -1,9 +1,9 @@
<template>
<view class="page">
<view class="page" :style="pageStyle()">
<AppHeader title="积分中心" />
<scroll-view scroll-y class="points-scroll">
<scroll-view scroll-y class="points-scroll" :style="contentStyle(96, false)">
<view class="points-content">
<view class="points-hero"><text class="points-label">我的积分</text><text class="points-number">2680</text><button @click="showRules">积分规则</button><view class="points-shine"></view></view>
<view class="points-hero"><text class="points-label">我的积分</text><text class="points-number">2680</text><button @click="showRules" class="points-hero-button">积分规则</button><view class="points-shine"></view></view>
<view class="task-grid"><view class="task" v-for="item in tasks" :key="item.name" @click="doTask(item.name)"><view class="task-icon"><AppIcon :name="item.icon" :size="42" /></view><text class="task-name">{{ item.name }}</text><text class="task-score">+{{ item.score }}</text></view></view>
<view class="invite-panel"><text class="section-title">邀请有礼</text><text class="invite-copy">每邀请1位好友,双方都可获得积分</text><view class="invite-code"><text>我的邀请码</text><text class="code">AI2680</text><text class="copy" @click="copyCode">复制</text></view><button class="primary-button invite-button" @click="invite">立即邀请</button></view>
<view class="records"><view class="record-head"><text class="section-title">积分明细</text><text class="more">全部 ›</text></view><view class="record" v-for="item in records" :key="item.name"><view><text class="record-name">{{ item.name }}</text><text class="record-date">{{ item.date }}</text></view><text class="record-score">+{{ item.score }}</text></view></view>
@@ -13,10 +13,14 @@
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { contentStyle, pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import AppIcon from '@/components/AppIcon.uvue'
const tasks = [{ icon: 'calendar-check', name: '签到', score: 10 }, { icon: 'share', name: '分享', score: 20 }, { icon: 'edit-file', name: '完善任务', score: 50 }, { icon: 'gift', name: '邀请好友', score: 100 }]
const records = [{ name: '每日签到', date: '今天 09:20', score: 10 }, { name: '完成融资评估', date: '昨天 18:36', score: 50 }, { name: '邀请好友', date: '09-02 14:20', score: 100 }]
type PointsTask = { icon: string, name: string, score: number }
const tasks: PointsTask[] = [{ icon: 'calendar-check', name: '签到', score: 10 }, { icon: 'share', name: '分享', score: 20 }, { icon: 'edit-file', name: '完善任务', score: 50 }, { icon: 'gift', name: '邀请好友', score: 100 }]
type PointsRecord = { name: string, date: string, score: number }
const records: PointsRecord[] = [{ name: '每日签到', date: '今天 09:20', score: 10 }, { name: '完成融资评估', date: '昨天 18:36', score: 50 }, { name: '邀请好友', date: '09-02 14:20', score: 100 }]
const showRules = () => { uni.showModal({ title: '积分规则', content: '签到、评估、分享和邀请好友均可获得积分。', showCancel: false }) }
const doTask = (name: string) => { uni.showToast({ title: name + '任务入口', icon: 'none' }) }
const copyCode = () => { uni.setClipboardData({ data: 'AI2680' }) }
@@ -24,8 +28,28 @@
</script>
<style>
.points-scroll { height: calc(100vh - var(--status-bar-height) - 96rpx); }.points-content { padding: 26rpx 28rpx 70rpx; }.points-hero { height: 260rpx; padding: 36rpx; border-radius: 20rpx; color: white; background: linear-gradient(125deg, #5140e8, #9c56f6); overflow: hidden; position: relative; }.points-label { font-size: 24rpx; color: #e7e1ff; }.points-number { font-size: 72rpx; line-height: 90rpx; font-weight: 900; }.points-hero button { position: absolute; right: 28rpx; top: 32rpx; width: 150rpx; height: 58rpx; line-height: 58rpx; padding: 0; margin: 0; border-radius: 29rpx; color: #6348de; background-color: #fff; font-size: 21rpx; }.points-shine { position: absolute; width: 260rpx; height: 260rpx; border-radius: 50%; background-color: rgba(255,255,255,.1); right: -60rpx; bottom: -110rpx; }
.task-grid { height: 180rpx; flex-direction: row; background-color: #fff; border: 1rpx solid #e5ecf5; border-radius: 18rpx; margin-top: 22rpx; }.task { flex: 1; align-items: center; justify-content: center; }.task-icon { width: 56rpx; height: 56rpx; border-radius: 18rpx; color: #0868f7; background-color: #e9f2ff; align-items: center; justify-content: center; font-size: 22rpx; font-weight: 800; }.task-name { margin-top: 10rpx; font-size: 21rpx; color: #3d4b61; }.task-score { margin-top: 4rpx; font-size: 18rpx; color: #8e9aab; }
.invite-panel, .records { margin-top: 24rpx; padding: 28rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e5ecf5; }.invite-copy { margin-top: 12rpx; color: #7a8799; font-size: 22rpx; }.invite-code { height: 92rpx; padding: 0 22rpx; margin-top: 24rpx; background-color: #f3f6fa; border-radius: 13rpx; flex-direction: row; align-items: center; color: #788599; font-size: 22rpx; }.code { flex: 1; margin-left: 22rpx; color: #1b2940; font-size: 30rpx; font-weight: 800; }.copy { color: #0868f7; }.invite-button { margin-top: 22rpx; }
.record-head { flex-direction: row; justify-content: space-between; align-items: center; }.more { color: #0868f7; font-size: 22rpx; }.record { min-height: 100rpx; flex-direction: row; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #edf1f6; }.record-name { font-size: 24rpx; color: #29374d; }.record-date { color: #9ba6b6; font-size: 19rpx; margin-top: 8rpx; }.record-score { color: #0daa74; font-size: 27rpx; font-weight: 700; }
.points-scroll { }
.points-content { padding: 26rpx 28rpx 70rpx; }
.points-hero { height: 260rpx; padding: 36rpx; border-radius: 20rpx; color: white; background: linear-gradient(125deg, #5140e8, #9c56f6); overflow: hidden; position: relative; }
.points-label { font-size: 24rpx; color: #e7e1ff; }
.points-number { font-size: 72rpx; line-height: 90rpx; font-weight: 900; }
.points-hero-button { position: absolute; right: 28rpx; top: 32rpx; width: 150rpx; height: 58rpx; line-height: 58rpx; padding: 0; margin: 0; border-radius: 29rpx; color: #6348de; background-color: #fff; font-size: 21rpx; }
.points-shine { position: absolute; width: 260rpx; height: 260rpx; border-radius: 50%; background-color: rgba(255,255,255,.1); right: -60rpx; bottom: -110rpx; }
.task-grid { height: 180rpx; flex-direction: row; background-color: #fff; border: 1rpx solid #e5ecf5; border-radius: 18rpx; margin-top: 22rpx; }
.task { flex: 1; align-items: center; justify-content: center; }
.task-icon { width: 56rpx; height: 56rpx; border-radius: 18rpx; color: #0868f7; background-color: #e9f2ff; align-items: center; justify-content: center; font-size: 22rpx; font-weight: 800; }
.task-name { margin-top: 10rpx; font-size: 21rpx; color: #3d4b61; }
.task-score { margin-top: 4rpx; font-size: 18rpx; color: #8e9aab; }
.invite-panel, .records { margin-top: 24rpx; padding: 28rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e5ecf5; }
.invite-copy { margin-top: 12rpx; color: #7a8799; font-size: 22rpx; }
.invite-code { height: 92rpx; padding: 0 22rpx; margin-top: 24rpx; background-color: #f3f6fa; border-radius: 13rpx; flex-direction: row; align-items: center; color: #788599; font-size: 22rpx; }
.code { flex: 1; margin-left: 22rpx; color: #1b2940; font-size: 30rpx; font-weight: 800; }
.copy { color: #0868f7; }
.invite-button { margin-top: 22rpx; }
.record-head { flex-direction: row; justify-content: space-between; align-items: center; }
.more { color: #0868f7; font-size: 22rpx; }
.record { min-height: 100rpx; flex-direction: row; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #edf1f6; }
.record-name { font-size: 24rpx; color: #29374d; }
.record-date { color: #9ba6b6; font-size: 19rpx; margin-top: 8rpx; }
.record-score { color: #0daa74; font-size: 27rpx; font-weight: 700; }
</style>
+33 -9
View File
@@ -1,20 +1,22 @@
<template>
<view class="page">
<view class="page" :style="pageStyle()">
<AppHeader title="产品详情"><text class="share">分享</text></AppHeader>
<scroll-view scroll-y class="product-scroll">
<scroll-view scroll-y class="product-scroll" :style="contentStyle(220, false)">
<view class="product-content">
<view class="bank-head"><view class="bank-mark"><AppIcon name="building" :size="42" /></view><view><text class="product-name">招商银行·闪电贷</text><text class="product-tags">个人信用贷 线上申请 快速放款</text></view></view>
<view class="product-metrics"><view><text class="metric-label">最高额度</text><text class="amount">50万</text></view><view><text class="metric-label">年利率</text><text class="rate">3.85%</text></view><view><text class="metric-label">贷款期限</text><text class="term">12-36期</text></view></view>
<view class="product-metrics"><view class="product-metrics-view"><text class="metric-label">最高额度</text><text class="amount">50万</text></view><view class="product-metrics-view"><text class="metric-label">年利率</text><text class="rate">3.85%</text></view><view class="product-metrics-view product-metrics-view-last-child"><text class="metric-label">贷款期限</text><text class="term">12-36期</text></view></view>
<view class="detail-section"><text class="section-title">产品优势</text><view class="advantage-grid"><view class="advantage" v-for="item in advantages" :key="item"><AppIcon name="check-circle" :size="30" /><text>{{ item }}</text></view></view></view>
<view class="detail-section"><text class="section-title">申请条件</text><view class="condition"><text>年龄要求</text><text>22-55周岁</text></view><view class="condition"><text>工作要求</text><text>现单位满6个月</text></view><view class="condition"><text>信用要求</text><text>近两年无严重逾期</text></view></view>
<view class="detail-section"><text class="section-title">申请条件</text><view class="condition"><text>年龄要求</text><text class="condition-text-last-child">22-55周岁</text></view><view class="condition"><text>工作要求</text><text class="condition-text-last-child">现单位满6个月</text></view><view class="condition"><text>信用要求</text><text class="condition-text-last-child">近两年无严重逾期</text></view></view>
<view class="detail-section"><text class="section-title">所需材料</text><text class="material">身份证 · 银行卡 · 收入证明</text></view>
</view>
</scroll-view>
<view class="apply-bar"><button class="primary-button apply-button" @click="apply">立即申请</button></view>
<view 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'
const { bottomStyle, contentStyle, pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import AppIcon from '@/components/AppIcon.uvue'
const advantages = ['线上申请', '额度高', '审批快速', '利率低', '无抵押', '无担保']
@@ -22,8 +24,30 @@
</script>
<style>
.share { color: #0868f7; font-size: 23rpx; }.product-scroll { height: calc(100vh - var(--status-bar-height) - 220rpx); }.product-content { padding: 26rpx 28rpx 130rpx; }.bank-head { min-height: 150rpx; padding: 26rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e6edf6; flex-direction: row; align-items: center; }.bank-mark { width: 84rpx; height: 84rpx; border-radius: 50%; background-color: #e8192c; align-items: center; justify-content: center; margin-right: 22rpx; }.bank-mark .app-icon { filter: brightness(0) invert(1); }.product-name { font-size: 30rpx; font-weight: 800; }.product-tags { color: #8794a8; font-size: 20rpx; margin-top: 11rpx; }
.product-metrics { min-height: 180rpx; margin-top: 20rpx; padding: 34rpx 15rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e6edf6; flex-direction: row; }.product-metrics view { flex: 1; align-items: center; border-right: 1rpx solid #edf1f6; }.product-metrics view:last-child { border-right: 0; }.metric-label { color: #8491a5; font-size: 21rpx; }.amount, .rate, .term { margin-top: 16rpx; font-size: 34rpx; font-weight: 800; }.rate { color: #0868f7; }
.detail-section { margin-top: 22rpx; padding: 28rpx; background-color: #fff; border-radius: 18rpx; border: 1rpx solid #e6edf6; }.advantage-grid { margin-top: 25rpx; flex-direction: row; flex-wrap: wrap; }.advantage { width: 50%; flex-direction: row; align-items: center; margin-bottom: 22rpx; font-size: 24rpx; color: #435168; }.advantage .app-icon { margin-right: 12rpx; }.condition { height: 80rpx; flex-direction: row; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #edf1f6; color: #667489; font-size: 24rpx; }.condition text:last-child { color: #1e2c43; font-weight: 600; }.material { margin-top: 22rpx; font-size: 24rpx; color: #657389; }
.apply-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 18rpx 28rpx calc(18rpx + env(safe-area-inset-bottom)); background-color: #fff; border-top: 1rpx solid #e7edf6; }.apply-button { margin: 0; }
.share { color: #0868f7; font-size: 23rpx; }
.product-scroll { }
.product-content { padding: 26rpx 28rpx 130rpx; }
.bank-head { min-height: 150rpx; padding: 26rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e6edf6; flex-direction: row; align-items: center; }
.bank-mark { width: 84rpx; height: 84rpx; border-radius: 50%; background-color: #e8192c; align-items: center; justify-content: center; margin-right: 22rpx; }
/* #ifndef APP */
.bank-mark .app-icon { filter: brightness(0) invert(1); }
/* #endif */
.product-name { font-size: 30rpx; font-weight: 800; }
.product-tags { color: #8794a8; font-size: 20rpx; margin-top: 11rpx; }
.product-metrics { min-height: 180rpx; margin-top: 20rpx; padding: 34rpx 15rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e6edf6; flex-direction: row; }
.product-metrics-view { flex: 1; align-items: center; border-right: 1rpx solid #edf1f6; }
.product-metrics-view-last-child { border-right: 0; }
.metric-label { color: #8491a5; font-size: 21rpx; }
.amount, .rate, .term { margin-top: 16rpx; font-size: 34rpx; font-weight: 800; }
.rate { color: #0868f7; }
.detail-section { margin-top: 22rpx; padding: 28rpx; background-color: #fff; border-radius: 18rpx; border: 1rpx solid #e6edf6; }
.advantage-grid { margin-top: 25rpx; flex-direction: row; flex-wrap: wrap; }
.advantage { width: 50%; flex-direction: row; align-items: center; margin-bottom: 22rpx; font-size: 24rpx; color: #435168; }
.advantage .app-icon { margin-right: 12rpx; }
.condition { height: 80rpx; flex-direction: row; justify-content: space-between; align-items: center; border-bottom: 1rpx solid #edf1f6; color: #667489; font-size: 24rpx; }
.condition-text-last-child { color: #1e2c43; font-weight: 600; }
.material { margin-top: 22rpx; font-size: 24rpx; color: #657389; }
.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; }
</style>
+33 -7
View File
@@ -1,10 +1,10 @@
<template>
<view class="page">
<view class="page" :style="pageStyle()">
<AppHeader title="个人中心" :back="false" />
<scroll-view scroll-y class="profile-scroll">
<scroll-view scroll-y class="profile-scroll" :style="contentStyle(226, false)">
<view class="profile-content">
<view class="profile-card"><view class="avatar"><text>张</text></view><view class="profile-main"><text class="profile-name">张三</text><text class="profile-phone">138****8888</text></view><view class="verified"><text>已认证</text></view></view>
<view class="stat-row"><view><text class="stat-number">1</text><text class="stat-label">申请中</text></view><view><text class="stat-number">3</text><text class="stat-label">已匹配</text></view><view><text class="stat-number">2680</text><text class="stat-label">积分</text></view></view>
<view class="stat-row"><view class="stat-row-view"><text class="stat-number">1</text><text class="stat-label">申请中</text></view><view class="stat-row-view"><text class="stat-number">3</text><text class="stat-label">已匹配</text></view><view class="stat-row-view stat-row-view-last-child"><text class="stat-number">2680</text><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.url, item.name)"><view :class="'menu-icon ' + item.color"><AppIcon :name="item.icon" :size="40" /></view><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" muted /></view>
</view>
@@ -16,11 +16,14 @@
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { contentStyle, pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
import BottomNav from '@/components/BottomNav.uvue'
import AppIcon from '@/components/AppIcon.uvue'
import { clearSession } from '@/services/api.uts'
const menus = [
type ProfileMenu = { icon: string, name: string, note: string, color: string, url: string }
const menus: ProfileMenu[] = [
{ icon: 'archive', name: '我的融资档案', note: '75%', color: 'blue', url: '/pages/archive/archive' },
{ icon: 'points', name: '积分中心', note: '2680', color: 'purple', url: '/pages/points/points' },
{ icon: 'shield', name: '账户安全', note: '', color: 'green', url: '' },
@@ -33,7 +36,30 @@
</script>
<style>
.profile-scroll { height: calc(100vh - var(--status-bar-height) - 226rpx); }.profile-content { padding: 26rpx 28rpx 160rpx; }.profile-card { min-height: 170rpx; padding: 30rpx; border-radius: 18rpx 18rpx 0 0; background-color: #fff; border: 1rpx solid #e5ecf5; flex-direction: row; align-items: center; }.avatar { width: 92rpx; height: 92rpx; border-radius: 50%; background: linear-gradient(145deg, #2d8cff, #0c4fc6); align-items: center; justify-content: center; color: white; font-size: 36rpx; font-weight: 800; margin-right: 22rpx; }.profile-main { flex: 1; }.profile-name { font-size: 32rpx; font-weight: 800; }.profile-phone { color: #8390a3; font-size: 22rpx; margin-top: 10rpx; }.verified { padding: 8rpx 14rpx; border-radius: 9rpx; background-color: #e7f8f0; color: #0ba66e; font-size: 19rpx; }
.stat-row { height: 140rpx; background-color: #fff; border: 1rpx solid #e5ecf5; border-top: 0; border-radius: 0 0 18rpx 18rpx; flex-direction: row; }.stat-row view { flex: 1; align-items: center; justify-content: center; border-right: 1rpx solid #edf1f6; }.stat-row view:last-child { border-right: 0; }.stat-number { font-size: 31rpx; font-weight: 800; color: #102557; }.stat-label { color: #8491a4; font-size: 20rpx; margin-top: 8rpx; }
.menu-list { margin-top: 24rpx; padding: 0 24rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e5ecf5; }.menu-row { height: 105rpx; flex-direction: row; align-items: center; border-bottom: 1rpx solid #edf1f6; }.menu-icon { width: 52rpx; height: 52rpx; border-radius: 16rpx; align-items: center; justify-content: center; font-size: 20rpx; font-weight: 800; margin-right: 18rpx; }.blue { color: #0868f7; background-color: #e8f2ff; }.purple { color: #7757ea; background-color: #efebff; }.green { color: #0ba979; background-color: #e6f8f1; }.orange { color: #f2751b; background-color: #fff0e5; }.cyan { color: #0a9eb2; background-color: #e7f8fb; }.gray { color: #667489; background-color: #eef2f6; }.menu-name { flex: 1; font-size: 25rpx; }.menu-note { color: #8793a6; font-size: 21rpx; margin-right: 14rpx; }.chevron { color: #abb5c4; font-size: 36rpx; }.logout { margin-top: 25rpx; height: 88rpx; line-height: 88rpx; border-radius: 16rpx; color: #ef4444; background-color: #fff; border: 1rpx solid #e5ecf5; font-size: 26rpx; }
.profile-scroll { }
.profile-content { padding: 26rpx 28rpx 160rpx; }
.profile-card { min-height: 170rpx; padding: 30rpx; border-radius: 18rpx 18rpx 0 0; background-color: #fff; border: 1rpx solid #e5ecf5; flex-direction: row; align-items: center; }
.avatar { width: 92rpx; height: 92rpx; border-radius: 50%; background: linear-gradient(145deg, #2d8cff, #0c4fc6); align-items: center; justify-content: center; color: white; font-size: 36rpx; font-weight: 800; margin-right: 22rpx; }
.profile-main { flex: 1; }
.profile-name { font-size: 32rpx; font-weight: 800; }
.profile-phone { color: #8390a3; font-size: 22rpx; margin-top: 10rpx; }
.verified { padding: 8rpx 14rpx; border-radius: 9rpx; background-color: #e7f8f0; color: #0ba66e; font-size: 19rpx; }
.stat-row { height: 140rpx; background-color: #fff; border: 1rpx solid #e5ecf5; border-top: 0; border-radius: 0 0 18rpx 18rpx; flex-direction: row; }
.stat-row-view { flex: 1; align-items: center; justify-content: center; border-right: 1rpx solid #edf1f6; }
.stat-row-view-last-child { border-right: 0; }
.stat-number { font-size: 31rpx; font-weight: 800; color: #102557; }
.stat-label { color: #8491a4; font-size: 20rpx; margin-top: 8rpx; }
.menu-list { margin-top: 24rpx; padding: 0 24rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e5ecf5; }
.menu-row { height: 105rpx; flex-direction: row; align-items: center; border-bottom: 1rpx solid #edf1f6; }
.menu-icon { width: 52rpx; height: 52rpx; border-radius: 16rpx; align-items: center; justify-content: center; font-size: 20rpx; font-weight: 800; margin-right: 18rpx; }
.blue { color: #0868f7; background-color: #e8f2ff; }
.purple { color: #7757ea; background-color: #efebff; }
.green { color: #0ba979; background-color: #e6f8f1; }
.orange { color: #f2751b; background-color: #fff0e5; }
.cyan { color: #0a9eb2; background-color: #e7f8fb; }
.gray { color: #667489; background-color: #eef2f6; }
.menu-name { flex: 1; font-size: 25rpx; }
.menu-note { color: #8793a6; font-size: 21rpx; margin-right: 14rpx; }
.chevron { color: #abb5c4; font-size: 36rpx; }
.logout { margin-top: 25rpx; height: 88rpx; line-height: 88rpx; border-radius: 16rpx; color: #ef4444; background-color: #fff; border: 1rpx solid #e5ecf5; font-size: 26rpx; }
</style>
+27 -6
View File
@@ -1,10 +1,10 @@
<template>
<view class="page">
<view class="page" :style="pageStyle()">
<AppHeader title="融资需求报告" />
<scroll-view scroll-y class="report-scroll">
<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">您的融资资质超过 82% 的用户</text></view>
<view class="radar-panel"><image class="radar-image" src="/static/images/finance-radar.png" mode="aspectFit"></image><view class="radar-core"><text>85</text></view><view class="radar-label top"><text>还款能力</text></view><view class="radar-label left"><text>收入稳定</text></view><view class="radar-label right"><text>信用状况</text></view><view class="radar-label bottom-left"><text>资产条件</text></view><view class="radar-label bottom-right"><text>负债情况</text></view></view>
<view class="radar-panel"><image class="radar-image" src="/static/mobile/images/finance-radar.png" mode="aspectFit"></image><view class="radar-core"><text>85</text></view><view class="radar-label top"><text>还款能力</text></view><view class="radar-label left"><text>收入稳定</text></view><view class="radar-label right"><text>信用状况</text></view><view class="radar-label bottom-left"><text>资产条件</text></view><view class="radar-label bottom-right"><text>负债情况</text></view></view>
<view class="conclusion"><text class="section-title">评估结论</text><text class="conclusion-text">您的资质整体良好,符合多家银行产品要求。</text><view class="suggest-row"><text>建议额度</text><text class="suggest-value">6-36个月</text></view></view>
<button class="primary-button" @click="viewMatches">查看匹配方案</button>
</view>
@@ -13,12 +13,33 @@
</template>
<script setup lang="uts">
import { usePageLayout } from '@/services/layout.uts'
const { contentStyle, pageStyle } = usePageLayout()
import AppHeader from '@/components/AppHeader.uvue'
const viewMatches = () => { uni.navigateTo({ url: '/pages/matches/matches' }) }
</script>
<style>
.report-scroll { height: calc(100vh - var(--status-bar-height) - 96rpx); }.report-content { padding: 28rpx 30rpx 80rpx; }.score-panel { background-color: #fff; border-radius: 18rpx; padding: 30rpx; border: 1rpx solid #e7edf6; }.score-label { font-size: 24rpx; color: #6f7d91; }.score-row { flex-direction: row; align-items: flex-end; margin-top: 8rpx; }.big-score { color: #102557; font-size: 82rpx; line-height: 90rpx; font-weight: 900; }.score-unit { font-size: 28rpx; margin: 0 16rpx 14rpx 5rpx; }.score-badge { padding: 8rpx 17rpx; border-radius: 10rpx; background-color: #e5f8ee; color: #0ba868; font-size: 22rpx; margin-bottom: 15rpx; }.score-caption { color: #8793a6; font-size: 22rpx; margin-top: 6rpx; }
.radar-panel { height: 500rpx; margin-top: 24rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e7edf6; align-items: center; justify-content: center; position: relative; overflow: hidden; }.radar-image { width: 430rpx; height: 430rpx; }.radar-core { position: absolute; width: 92rpx; height: 92rpx; left: 50%; top: 50%; margin-left: -46rpx; margin-top: -46rpx; border-radius: 50%; background-color: #0868f7; align-items: center; justify-content: center; color: white; font-size: 34rpx; font-weight: 800; box-shadow: 0 0 32rpx rgba(8,104,247,.28); }.radar-label { position: absolute; color: #58667b; font-size: 21rpx; }.top { top: 28rpx; }.left { left: 27rpx; top: 224rpx; }.right { right: 27rpx; top: 224rpx; }.bottom-left { left: 95rpx; bottom: 32rpx; }.bottom-right { right: 86rpx; bottom: 32rpx; }
.conclusion { margin: 24rpx 0 32rpx; background-color: #fff; border-radius: 18rpx; padding: 28rpx; border: 1rpx solid #e7edf6; }.conclusion-text { margin-top: 18rpx; font-size: 24rpx; color: #59677b; line-height: 42rpx; }.suggest-row { margin-top: 24rpx; padding-top: 22rpx; border-top: 1rpx solid #edf1f6; flex-direction: row; justify-content: space-between; font-size: 25rpx; }.suggest-value { color: #0868f7; font-weight: 700; }
.report-scroll { }
.report-content { padding: 28rpx 30rpx 80rpx; }
.score-panel { background-color: #fff; border-radius: 18rpx; padding: 30rpx; border: 1rpx solid #e7edf6; }
.score-label { font-size: 24rpx; color: #6f7d91; }
.score-row { flex-direction: row; align-items: flex-end; margin-top: 8rpx; }
.big-score { color: #102557; font-size: 82rpx; line-height: 90rpx; font-weight: 900; }
.score-unit { font-size: 28rpx; margin: 0 16rpx 14rpx 5rpx; }
.score-badge { padding: 8rpx 17rpx; border-radius: 10rpx; background-color: #e5f8ee; color: #0ba868; font-size: 22rpx; margin-bottom: 15rpx; }
.score-caption { color: #8793a6; font-size: 22rpx; margin-top: 6rpx; }
.radar-panel { height: 500rpx; margin-top: 24rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e7edf6; align-items: center; justify-content: center; position: relative; overflow: hidden; }
.radar-image { width: 430rpx; height: 430rpx; }
.radar-core { position: absolute; width: 92rpx; height: 92rpx; left: 50%; top: 50%; margin-left: -46rpx; margin-top: -46rpx; border-radius: 50%; background-color: #0868f7; align-items: center; justify-content: center; color: white; font-size: 34rpx; font-weight: 800; box-shadow: 0 0 32rpx rgba(8,104,247,.28); }
.radar-label { position: absolute; color: #58667b; font-size: 21rpx; }
.top { top: 28rpx; }
.left { left: 27rpx; top: 224rpx; }
.right { right: 27rpx; top: 224rpx; }
.bottom-left { left: 95rpx; bottom: 32rpx; }
.bottom-right { right: 86rpx; bottom: 32rpx; }
.conclusion { margin: 24rpx 0 32rpx; background-color: #fff; border-radius: 18rpx; padding: 28rpx; border: 1rpx solid #e7edf6; }
.conclusion-text { margin-top: 18rpx; font-size: 24rpx; color: #59677b; line-height: 42rpx; }
.suggest-row { margin-top: 24rpx; padding-top: 22rpx; border-top: 1rpx solid #edf1f6; flex-direction: row; justify-content: space-between; font-size: 25rpx; }
.suggest-value { color: #0868f7; font-weight: 700; }
</style>
+7 -7
View File
@@ -13,7 +13,7 @@ export function saveSession(token: string, phone: string): void {
export function getSession(): UTSJSONObject | null {
const value = uni.getStorageSync(SESSION_KEY)
if (value == null) return null
if (value == null || typeof value != 'object') return null
return value as UTSJSONObject
}
@@ -25,7 +25,7 @@ export function clearSession(): void {
uni.removeStorageSync(SESSION_KEY)
}
const apiMessage = (error: unknown, fallback: string): string => {
const apiMessage = (error: any | null, fallback: string): string => {
if (error == null) return fallback
const text = error.toString()
return text.length > 0 ? text : fallback
@@ -65,12 +65,12 @@ export async function requestSmsCode(phone: string): Promise<boolean> {
export async function loginWithSms(phone: string, code: string): Promise<string> {
if (API_BASE_URL.length == 0) {
await wait(300)
if (code.length != 6) throw '验证码应为 6 位数字'
if (code.length != 6) throw new Error('验证码应为 6 位数字')
return 'demo-sms-token'
}
const result = await requestJson('/auth/sms-login', 'POST', { phone, code })
const token = result['token'] as string | null
if (token == null || token.length == 0) throw '登录响应缺少 token'
if (token == null || token.length == 0) throw new Error('登录响应缺少 token')
return token
}
@@ -81,7 +81,7 @@ export async function loginWithPassword(phone: string, password: string): Promis
}
const result = await requestJson('/auth/password-login', 'POST', { phone, password })
const token = result['token'] as string | null
if (token == null || token.length == 0) throw '登录响应缺少 token'
if (token == null || token.length == 0) throw new Error('登录响应缺少 token')
return token
}
@@ -93,7 +93,7 @@ export async function submitAssessment(payload: UTSJSONObject): Promise<string>
}
const result = await requestJson('/assessments', 'POST', payload)
const id = result['id'] as string | null
if (id == null || id.length == 0) throw '评估提交成功但未返回编号'
if (id == null || id.length == 0) throw new Error('评估提交成功但未返回编号')
return id
}
@@ -118,7 +118,7 @@ export async function sendAssistantMessage(message: string): Promise<string> {
}
const result = await requestJson('/assistant/messages', 'POST', { message })
const reply = result['reply'] as string | null
if (reply == null || reply.length == 0) throw '助手暂时没有返回内容'
if (reply == null || reply.length == 0) throw new Error('助手暂时没有返回内容')
return reply
}
+39
View File
@@ -0,0 +1,39 @@
import { ref, onMounted, onUnmounted } from 'vue'
import { onResize } from '@dcloudio/uni-app'
export type PageLayout = {
pageStyle: () => string,
contentStyle: (offset: number, minimum: boolean) => string,
statusStyle: (offset: number) => string,
bottomStyle: (padding: number) => string
}
// 原生渲染不支持 vh/calc,统一以窗口尺寸计算,并随旋转更新。
export function usePageLayout(): PageLayout {
const windowInfo = ref(uni.getWindowInfo())
const refresh = () => { windowInfo.value = uni.getWindowInfo() }
onMounted(() => refresh())
// #ifdef APP
onResize((_options) => { refresh() })
// #endif
// #ifndef APP
const handleResize = (_options: OnWindowResizeCallbackResult) => { refresh() }
uni.onWindowResize(handleResize)
onUnmounted(() => { uni.offWindowResize(handleResize) })
// #endif
const rpx = (value: number): number => value * windowInfo.value.windowWidth / 750
const status = (): number => windowInfo.value.statusBarHeight ?? 0
const pageStyle = (): string => 'min-height:' + windowInfo.value.windowHeight + 'px;'
const contentStyle = (offset: number, minimum: boolean): string => {
// #ifdef WEB
return (minimum ? 'min-height:' : 'height:') + 'calc(100vh - ' + status() + 'px - ' + offset + 'rpx);'
// #endif
// #ifndef WEB
const height = Math.max(0, windowInfo.value.windowHeight - status() - rpx(offset))
return (minimum ? 'min-height:' : 'height:') + height + 'px;'
// #endif
}
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 }
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB