feat: 完成全流程融资助手应用开发
- 新增文章、课程详情页面,完善融资课堂模块 - 新增登录/注册、评估填报、结果匹配全流程业务 - 新增会话管理、错误重试与状态加载逻辑 - 重构首页路由逻辑,适配已登录用户跳转 - 完善个人中心登出与数据持久化功能
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,8 @@
|
|||||||
{ "path": "pages/assistant/assistant", "style": { "navigationStyle": "custom" } },
|
{ "path": "pages/assistant/assistant", "style": { "navigationStyle": "custom" } },
|
||||||
{ "path": "pages/news/news", "style": { "navigationStyle": "custom" } },
|
{ "path": "pages/news/news", "style": { "navigationStyle": "custom" } },
|
||||||
{ "path": "pages/classroom/classroom", "style": { "navigationStyle": "custom" } },
|
{ "path": "pages/classroom/classroom", "style": { "navigationStyle": "custom" } },
|
||||||
|
{ "path": "pages/article/article", "style": { "navigationStyle": "custom" } },
|
||||||
|
{ "path": "pages/lesson/lesson", "style": { "navigationStyle": "custom" } },
|
||||||
{ "path": "pages/points/points", "style": { "navigationStyle": "custom" } },
|
{ "path": "pages/points/points", "style": { "navigationStyle": "custom" } },
|
||||||
{ "path": "pages/messages/messages", "style": { "navigationStyle": "custom" } },
|
{ "path": "pages/messages/messages", "style": { "navigationStyle": "custom" } },
|
||||||
{ "path": "pages/archive/archive", "style": { "navigationStyle": "custom" } },
|
{ "path": "pages/archive/archive", "style": { "navigationStyle": "custom" } },
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<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>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="uts">
|
||||||
|
import AppHeader from '@/components/AppHeader.uvue'
|
||||||
|
const title = ref('如何提高个人贷款通过率')
|
||||||
|
onLoad((options) => { if (options != null && options['title'] != null) title.value = options['title'] })
|
||||||
|
</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; }
|
||||||
|
</style>
|
||||||
@@ -4,28 +4,35 @@
|
|||||||
<view class="assessment-content">
|
<view class="assessment-content">
|
||||||
<view class="step-line"><view v-for="i in 4" :key="i" :class="i <= step + 1 ? 'step-segment done' : 'step-segment'"></view></view>
|
<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>
|
<text class="step-count">{{ step + 1 }}/4</text>
|
||||||
<view v-if="step < 3" class="form-list">
|
<view v-if="step == 0" class="form-list">
|
||||||
<view class="form-row" v-for="item in fieldGroups[step]" :key="item.label">
|
<view class="form-row"><text class="field-label">姓名</text><input v-model="form.name" class="field-input" placeholder="请输入姓名" placeholder-class="placeholder" /></view>
|
||||||
<text class="field-label">{{ item.label }}</text>
|
<view class="form-row"><text class="field-label">身份证号</text><input v-model="form.idCard" class="field-input" placeholder="请输入身份证号" placeholder-class="placeholder" /></view>
|
||||||
<input class="field-input" :placeholder="item.placeholder" placeholder-class="placeholder" />
|
<view class="form-row"><text class="field-label">手机号</text><input v-model="form.phone" class="field-input" type="number" placeholder="请输入手机号" placeholder-class="placeholder" /></view>
|
||||||
<text v-if="item.select" class="field-arrow">⌄</text>
|
<view class="form-row"><text class="field-label">所在城市</text><input v-model="form.city" class="field-input" placeholder="请选择所在城市" placeholder-class="placeholder" /><text class="field-arrow">⌄</text></view>
|
||||||
</view>
|
<view class="form-row"><text class="field-label">婚姻状况</text><input v-model="form.maritalStatus" class="field-input" placeholder="请选择" placeholder-class="placeholder" /><text class="field-arrow">⌄</text></view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="step == 1" class="form-list">
|
||||||
|
<view class="form-row"><text class="field-label">职业类型</text><input v-model="form.occupation" class="field-input" placeholder="请选择" placeholder-class="placeholder" /><text class="field-arrow">⌄</text></view>
|
||||||
|
<view class="form-row"><text class="field-label">工作单位</text><input v-model="form.company" class="field-input" placeholder="请输入工作单位" placeholder-class="placeholder" /></view>
|
||||||
|
<view class="form-row"><text class="field-label">月收入(元)</text><input v-model="form.monthlyIncome" class="field-input" type="number" placeholder="请输入月收入" placeholder-class="placeholder" /></view>
|
||||||
|
<view class="form-row"><text class="field-label">社保缴纳</text><input v-model="form.socialSecurity" class="field-input" placeholder="请选择" placeholder-class="placeholder" /><text class="field-arrow">⌄</text></view>
|
||||||
|
<view class="form-row"><text class="field-label">公积金缴纳</text><input v-model="form.housingFund" class="field-input" placeholder="请选择" placeholder-class="placeholder" /><text class="field-arrow">⌄</text></view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="step == 2" class="form-list">
|
||||||
|
<view class="form-row"><text class="field-label">房产情况</text><input v-model="form.house" class="field-input" placeholder="请选择" placeholder-class="placeholder" /><text class="field-arrow">⌄</text></view>
|
||||||
|
<view class="form-row"><text class="field-label">车辆情况</text><input v-model="form.car" class="field-input" placeholder="请选择" placeholder-class="placeholder" /><text class="field-arrow">⌄</text></view>
|
||||||
|
<view class="form-row"><text class="field-label">存款情况</text><input v-model="form.savings" class="field-input" placeholder="请选择" placeholder-class="placeholder" /><text class="field-arrow">⌄</text></view>
|
||||||
|
<view class="form-row"><text class="field-label">信用卡使用情况</text><input v-model="form.creditCard" class="field-input" placeholder="请选择" placeholder-class="placeholder" /><text class="field-arrow">⌄</text></view>
|
||||||
|
<view class="form-row"><text class="field-label">其他资产</text><input v-model="form.otherAssets" class="field-input" placeholder="选填" placeholder-class="placeholder" /><text class="field-arrow">⌄</text></view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="upload-area">
|
<view v-else class="upload-area">
|
||||||
<text class="upload-title">上传资料(选填)</text>
|
<text class="upload-title">上传资料(选填)</text>
|
||||||
<view class="upload-grid">
|
<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-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">点击上传</text></view>
|
|
||||||
</view>
|
|
||||||
<text class="upload-title other-title">其他资料</text>
|
<text class="upload-title other-title">其他资料</text>
|
||||||
<view class="upload-grid">
|
<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-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="consent" @click="consented = !consented"><view :class="consented ? 'checkbox checked' : 'checkbox'"><text v-if="consented">✓</text></view><text>我已阅读并同意《授权协议》</text></view>
|
<view class="consent" @click="consented = !consented"><view :class="consented ? 'checkbox checked' : 'checkbox'"><text v-if="consented">✓</text></view><text>我已阅读并同意《授权协议》</text></view>
|
||||||
</view>
|
</view>
|
||||||
<view class="bottom-action">
|
<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>
|
||||||
<button v-if="step > 0" class="back-button" @click="step--">上一步</button>
|
|
||||||
<button class="primary-button next-button" @click="next">{{ step == 3 ? '下一步' : '继续' }}</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -34,21 +41,48 @@
|
|||||||
import AppHeader from '@/components/AppHeader.uvue'
|
import AppHeader from '@/components/AppHeader.uvue'
|
||||||
import AppIcon from '@/components/AppIcon.uvue'
|
import AppIcon from '@/components/AppIcon.uvue'
|
||||||
const step = ref(0)
|
const step = ref(0)
|
||||||
|
const assessmentType = ref('personal')
|
||||||
const consented = ref(true)
|
const consented = ref(true)
|
||||||
const titles = ['基本信息', '经营 / 收入信息', '资产 / 负债信息', '资料上传 / 授权']
|
const titles = ['基本信息', '经营 / 收入信息', '资产 / 负债信息', '资料上传 / 授权']
|
||||||
const fieldGroups = [
|
|
||||||
[{ label: '姓名', placeholder: '请输入姓名', select: false }, { label: '身份证号', placeholder: '请输入身份证号', select: false }, { label: '手机号', placeholder: '请输入手机号', select: false }, { label: '所在城市', placeholder: '请选择所在城市', select: true }, { label: '婚姻状况', placeholder: '请选择', select: true }],
|
|
||||||
[{ label: '职业类型', placeholder: '请选择', select: true }, { label: '工作单位', placeholder: '请输入工作单位', select: false }, { label: '月收入(元)', placeholder: '请输入月收入', select: false }, { label: '社保缴纳', placeholder: '请选择', select: true }, { label: '公积金缴纳', placeholder: '请选择', select: true }],
|
|
||||||
[{ label: '房产情况', placeholder: '请选择', select: true }, { label: '车辆情况', placeholder: '请选择', select: true }, { label: '存款情况', placeholder: '请选择', select: true }, { label: '信用卡使用情况', placeholder: '请选择', select: true }, { label: '其他资产', placeholder: '选填', select: true }]
|
|
||||||
]
|
|
||||||
const uploads = ['身份证正面', '银行卡', '收入证明']
|
const uploads = ['身份证正面', '银行卡', '收入证明']
|
||||||
const otherUploads = ['房产证', '营业执照', '其他文件']
|
const otherUploads = ['房产证', '营业执照', '其他文件']
|
||||||
const chooseImage = (name: string) => { uni.showToast({ title: name + '上传入口已预留', icon: 'none' }) }
|
const form = reactive({ name: '', idCard: '', phone: '', city: '', maritalStatus: '', occupation: '', company: '', monthlyIncome: '', socialSecurity: '', housingFund: '', house: '', car: '', savings: '', creditCard: '', otherAssets: '' })
|
||||||
const next = () => {
|
const uploaded = reactive({ '身份证正面': false, '银行卡': false, '收入证明': false, '房产证': false, '营业执照': false, '其他文件': false })
|
||||||
if (step.value < 3) step.value++
|
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 }) }
|
||||||
else if (!consented.value) uni.showToast({ title: '请同意授权协议', icon: 'none' })
|
const fillDraft = (saved: UTSJSONObject) => {
|
||||||
else uni.navigateTo({ url: '/pages/confirm/confirm' })
|
const read = (key: string): string => { const value = saved[key]; return value == null ? '' : value as string }
|
||||||
|
form.name = read('name'); form.idCard = read('idCard'); form.phone = read('phone'); form.city = read('city'); form.maritalStatus = read('maritalStatus'); form.occupation = read('occupation'); form.company = read('company'); form.monthlyIncome = read('monthlyIncome'); form.socialSecurity = read('socialSecurity'); form.housingFund = read('housingFund'); form.house = read('house'); form.car = read('car'); form.savings = read('savings'); form.creditCard = read('creditCard'); form.otherAssets = read('otherAssets')
|
||||||
|
const savedConsent = saved['consented']; if (savedConsent != null) consented.value = savedConsent as boolean
|
||||||
}
|
}
|
||||||
|
onLoad((options) => {
|
||||||
|
const routeType = options != null && options['type'] == 'enterprise' ? 'enterprise' : ''
|
||||||
|
const saved = uni.getStorageSync('assessmentDraft') as UTSJSONObject
|
||||||
|
if (routeType.length > 0) assessmentType.value = routeType
|
||||||
|
if (saved != null) {
|
||||||
|
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
|
||||||
|
uni.removeStorageSync('assessmentEditStep')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const chooseImage = (name: string) => { uploaded[name] = true; saveDraft(); uni.showToast({ title: name + '上传入口已预留', icon: 'none' }) }
|
||||||
|
const previous = () => { saveDraft(); if (step.value > 0) step.value-- }
|
||||||
|
const validateStep = (): boolean => {
|
||||||
|
if (step.value == 0) {
|
||||||
|
if (form.name.trim().length < 2) { uni.showToast({ title: '请输入正确姓名', icon: 'none' }); return false }
|
||||||
|
if (form.idCard.trim().length < 15) { uni.showToast({ title: '请输入正确身份证号', icon: 'none' }); return false }
|
||||||
|
if (form.phone.trim().length != 11 || !form.phone.startsWith('1')) { uni.showToast({ title: '请输入正确手机号', icon: 'none' }); return false }
|
||||||
|
if (form.city.trim().length == 0) { uni.showToast({ title: '请选择所在城市', icon: 'none' }); return false }
|
||||||
|
}
|
||||||
|
if (step.value == 1 && form.monthlyIncome.trim().length == 0) { uni.showToast({ title: '请输入月收入', icon: 'none' }); return false }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const next = () => { if (!validateStep()) return; saveDraft(); if (step.value < 3) step.value++; else if (!consented.value) uni.showToast({ title: '请同意授权协议', icon: 'none' }); else uni.navigateTo({ url: '/pages/confirm/confirm' }) }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<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/images/robot.png" mode="aspectFit"></image>
|
||||||
<view :class="item.role == 'user' ? 'message-bubble user-bubble' : 'message-bubble'"><text>{{ item.text }}</text></view>
|
<view :class="item.role == 'user' ? 'message-bubble user-bubble' : 'message-bubble'"><text>{{ item.text }}</text></view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="errorMessage.length > 0" class="assistant-error"><text>{{ errorMessage }}</text><text @click="retry">重试</text></view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-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"><input v-model="draft" confirm-type="send" @confirm="send" placeholder="请输入您的问题..." /><button @click="send"><AppIcon name="send" :size="34" /></button></view>
|
||||||
@@ -25,20 +26,27 @@
|
|||||||
const messages = ref<UTSJSONObject[]>([])
|
const messages = ref<UTSJSONObject[]>([])
|
||||||
const draft = ref('')
|
const draft = ref('')
|
||||||
const lastMessageId = ref('')
|
const lastMessageId = ref('')
|
||||||
|
const lastQuestion = ref('')
|
||||||
|
const sending = ref(false)
|
||||||
|
const errorMessage = ref('')
|
||||||
const ask = (text: string) => { draft.value = text; send() }
|
const ask = (text: string) => { draft.value = text; send() }
|
||||||
const send = async () => {
|
const send = async () => {
|
||||||
const text = draft.value.trim()
|
const text = draft.value.trim()
|
||||||
if (text.length == 0) return
|
if (text.length == 0 || sending.value) return
|
||||||
|
lastQuestion.value = text
|
||||||
messages.value.push({ role: 'user', text: text })
|
messages.value.push({ role: 'user', text: text })
|
||||||
draft.value = ''
|
draft.value = ''
|
||||||
const answer = await sendAssistantMessage(text)
|
sending.value = true
|
||||||
messages.value.push({ role: 'assistant', text: answer })
|
errorMessage.value = ''
|
||||||
lastMessageId.value = 'message-' + (messages.value.length - 1)
|
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 retry = () => { draft.value = lastQuestion.value; errorMessage.value = ''; send() }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<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; }
|
.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; }
|
.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; }
|
.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; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
<scroll-view scroll-y class="classroom-scroll">
|
<scroll-view scroll-y class="classroom-scroll">
|
||||||
<view class="classroom-content">
|
<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="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 in lessons" :key="item.title" @click="openLesson(item.title)"><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="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="openLesson('个人信用提升')"><AppIcon name="user" :size="46" /><text>个人信用提升</text><text>3 节课程</text></view><view class="topic-card orange-topic" @click="openLesson('小微经营融资')"><AppIcon name="building" :size="46" /><text>小微经营融资</text><text>4 节课程</text></view><view class="topic-card green-topic" @click="openLesson('材料准备清单')"><AppIcon name="file-text" :size="46" /><text>材料准备清单</text><text>5 节课程</text></view></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>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
@@ -19,7 +19,8 @@
|
|||||||
{ title: '小微企业融资材料清单', desc: '一次准备齐全,减少补充资料时间', duration: '8 分钟', tag: '企业融资', image: '/static/images/news-business.png' },
|
{ title: '小微企业融资材料清单', desc: '一次准备齐全,减少补充资料时间', duration: '8 分钟', tag: '企业融资', image: '/static/images/news-business.png' },
|
||||||
{ title: '读懂贷款利率与还款方式', desc: '选对期限,降低长期资金成本', duration: '5 分钟', tag: '基础知识', image: '/static/images/news-city.png' }
|
{ title: '读懂贷款利率与还款方式', desc: '选对期限,降低长期资金成本', duration: '5 分钟', tag: '基础知识', image: '/static/images/news-city.png' }
|
||||||
]
|
]
|
||||||
const openLesson = (title: string) => { uni.showToast({ title: title + '课程已打开', icon: 'none' }) }
|
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>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
+16
-14
@@ -3,11 +3,8 @@
|
|||||||
<AppHeader title="确认信息" />
|
<AppHeader title="确认信息" />
|
||||||
<view class="confirm-content">
|
<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>
|
<view class="confirm-tip"><view class="tip-icon"><text>✓</text></view><view><text class="tip-title">信息填写完成</text><text class="tip-desc">请确认以下信息准确无误</text></view></view>
|
||||||
<view class="info-section" v-for="section in sections" :key="section.title">
|
<view class="info-section" v-for="(section, sectionIndex) in sections" :key="section.title"><view class="section-bar"><text>{{ section.title }}</text><text class="edit" @click="edit(sectionIndex)">编辑</text></view><view class="info-row" v-for="row in section.rows" :key="row.label"><text class="info-label">{{ row.label }}</text><text class="info-value">{{ row.value }}</text></view></view>
|
||||||
<view class="section-bar"><text>{{ section.title }}</text><text class="edit" @click="edit">编辑</text></view>
|
<button class="primary-button submit-button" :disabled="submitting" @click="submit">{{ submitting ? '提交中...' : '确认提交' }}</button>
|
||||||
<view class="info-row" v-for="row in section.rows" :key="row.label"><text class="info-label">{{ row.label }}</text><text class="info-value">{{ row.value }}</text></view>
|
|
||||||
</view>
|
|
||||||
<button class="primary-button submit-button" @click="submit">确认提交</button>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -15,16 +12,21 @@
|
|||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import AppHeader from '@/components/AppHeader.uvue'
|
import AppHeader from '@/components/AppHeader.uvue'
|
||||||
import { submitAssessment } from '@/services/api.uts'
|
import { submitAssessment } from '@/services/api.uts'
|
||||||
const sections = [
|
const submitting = ref(false)
|
||||||
{ title: '基本信息', rows: [{ label: '姓名', value: '张三' }, { label: '身份证号', value: '4401****1234' }, { label: '手机号', value: '138****8888' }, { label: '所在城市', value: '新加坡' }] },
|
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: '未填写' }] }])
|
||||||
{ title: '经营 / 收入', rows: [{ label: '职业', value: '互联网 / 技术' }, { label: '月收入', value: '20,000元' }] },
|
const payload = ref<UTSJSONObject>({ source: 'mobile', confirmed: false })
|
||||||
{ title: '资产 / 负债', rows: [{ label: '房产情况', value: '无负债' }, { label: '信用卡', value: '使用良好' }] }
|
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 edit = () => { uni.navigateBack() }
|
const loadDraft = () => {
|
||||||
const submit = async () => {
|
const saved = uni.getStorageSync('assessmentDraft') as UTSJSONObject
|
||||||
await submitAssessment({ source: 'mobile', confirmed: true })
|
if (saved == null) return
|
||||||
uni.redirectTo({ url: '/pages/analyzing/analyzing' })
|
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 }
|
||||||
}
|
}
|
||||||
|
onLoad(() => loadDraft())
|
||||||
|
const edit = (sectionIndex: number) => { uni.setStorageSync('assessmentEditStep', sectionIndex); if (getCurrentPages().length > 1) uni.navigateBack({ delta: 1 }); else uni.navigateTo({ url: '/pages/assessment/assessment' }) }
|
||||||
|
const submit = async () => { if (submitting.value) return; submitting.value = true; try { await submitAssessment(payload.value); uni.redirectTo({ url: '/pages/analyzing/analyzing' }) } catch (e) { submitting.value = false; uni.showToast({ title: '提交失败,请重试', icon: 'none' }) } }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
|
|
||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import BrandLogo from '@/components/BrandLogo.uvue'
|
import BrandLogo from '@/components/BrandLogo.uvue'
|
||||||
const enter = () => { uni.redirectTo({ url: '/pages/login/login' }) }
|
import { isLoggedIn } from '@/services/api.uts'
|
||||||
|
const enter = () => { uni.redirectTo({ url: isLoggedIn() ? '/pages/home/home' : '/pages/login/login' }) }
|
||||||
setTimeout(() => enter(), 1800)
|
setTimeout(() => enter(), 1800)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<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>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="uts">
|
||||||
|
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 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 (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; }
|
||||||
|
</style>
|
||||||
+86
-7
@@ -5,12 +5,14 @@
|
|||||||
<BrandLogo />
|
<BrandLogo />
|
||||||
<text class="welcome">欢迎登录 AI融资</text>
|
<text class="welcome">欢迎登录 AI融资</text>
|
||||||
<view class="login-tabs">
|
<view class="login-tabs">
|
||||||
<view :class="mode == 'phone' ? 'login-tab selected' : 'login-tab'" @click="mode = 'phone'"><text>手机号登录</text></view>
|
<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="mode = 'password'"><text>密码登录</text></view>
|
<view :class="mode == 'password' ? 'login-tab selected' : 'login-tab'" @click="switchMode('password')"><text>密码登录</text></view>
|
||||||
</view>
|
</view>
|
||||||
<view class="input-box"><text class="input-prefix">+86</text><input v-model="phone" type="number" maxlength="11" placeholder="请输入手机号" /></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 == 'password'" class="input-box"><text class="input-prefix">密码</text><input v-model="password" password placeholder="请输入登录密码" /></view>
|
||||||
<button class="login-button" @click="login">{{ mode == 'phone' ? '获取验证码' : '登录' }}</button>
|
<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>
|
||||||
|
<text v-if="errorMessage.length > 0" class="error-message">{{ errorMessage }}</text>
|
||||||
|
<button class="login-button" :disabled="loading" @click="login">{{ loading ? '处理中...' : (mode == 'phone' ? (smsSent ? '登录' : '获取验证码') : '登录') }}</button>
|
||||||
<view class="other-login"><view class="line"></view><text>其他登录方式</text><view class="line"></view></view>
|
<view class="other-login"><view class="line"></view><text>其他登录方式</text><view class="line"></view></view>
|
||||||
<view class="social-row">
|
<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>
|
<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,16 +25,90 @@
|
|||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import BrandLogo from '@/components/BrandLogo.uvue'
|
import BrandLogo from '@/components/BrandLogo.uvue'
|
||||||
import AppIcon from '@/components/AppIcon.uvue'
|
import AppIcon from '@/components/AppIcon.uvue'
|
||||||
import { requestSmsCode } from '@/services/api.uts'
|
import { requestSmsCode, loginWithSms, loginWithPassword, saveSession } from '@/services/api.uts'
|
||||||
const mode = ref('phone')
|
const mode = ref('phone')
|
||||||
const phone = ref('13800138000')
|
const phone = ref('13800138000')
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
|
const smsCode = ref('')
|
||||||
|
const smsSent = ref(false)
|
||||||
|
const countdown = ref(0)
|
||||||
|
const loading = ref(false)
|
||||||
|
const errorMessage = ref('')
|
||||||
const agreed = ref(true)
|
const agreed = ref(true)
|
||||||
const login = async () => {
|
let countdownTimer: number | null = null
|
||||||
if (!agreed.value) { uni.showToast({ title: '请先同意用户协议', icon: 'none' }); return }
|
|
||||||
if (mode.value == 'phone') await requestSmsCode(phone.value)
|
const switchMode = (nextMode: string) => {
|
||||||
|
mode.value = nextMode
|
||||||
|
errorMessage.value = ''
|
||||||
|
}
|
||||||
|
const validPhone = (): boolean => {
|
||||||
|
if (!/^1[3-9][0-9]{9}$/.test(phone.value)) {
|
||||||
|
errorMessage.value = '请输入正确的 11 位手机号'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const startCountdown = () => {
|
||||||
|
countdown.value = 60
|
||||||
|
if (countdownTimer != null) clearInterval(countdownTimer)
|
||||||
|
countdownTimer = setInterval(() => {
|
||||||
|
if (countdown.value <= 1) {
|
||||||
|
countdown.value = 0
|
||||||
|
if (countdownTimer != null) clearInterval(countdownTimer)
|
||||||
|
countdownTimer = null
|
||||||
|
} else {
|
||||||
|
countdown.value -= 1
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
const finishLogin = (token: string) => {
|
||||||
|
saveSession(token, phone.value)
|
||||||
uni.redirectTo({ url: '/pages/home/home' })
|
uni.redirectTo({ url: '/pages/home/home' })
|
||||||
}
|
}
|
||||||
|
const resendSms = async () => {
|
||||||
|
if (loading.value || countdown.value > 0 || !validPhone()) return
|
||||||
|
loading.value = true
|
||||||
|
errorMessage.value = ''
|
||||||
|
try {
|
||||||
|
if (!(await requestSmsCode(phone.value))) throw '验证码发送失败,请稍后重试'
|
||||||
|
startCountdown()
|
||||||
|
uni.showToast({ title: '验证码已重新发送', icon: 'none' })
|
||||||
|
} catch (error) {
|
||||||
|
errorMessage.value = error == null ? '验证码发送失败,请稍后重试' : error.toString()
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const login = async () => {
|
||||||
|
errorMessage.value = ''
|
||||||
|
if (!agreed.value) { errorMessage.value = '请先同意用户协议'; return }
|
||||||
|
if (!validPhone()) return
|
||||||
|
if (loading.value) return
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
if (mode.value == 'phone') {
|
||||||
|
if (!smsSent.value) {
|
||||||
|
if (countdown.value > 0) return
|
||||||
|
const sent = await requestSmsCode(phone.value)
|
||||||
|
if (!sent) throw '验证码发送失败,请稍后重试'
|
||||||
|
smsSent.value = true
|
||||||
|
startCountdown()
|
||||||
|
uni.showToast({ title: '验证码已发送', icon: 'none' })
|
||||||
|
} else {
|
||||||
|
if (!/^[0-9]{6}$/.test(smsCode.value)) throw '请输入 6 位数字验证码'
|
||||||
|
finishLogin(await loginWithSms(phone.value, smsCode.value))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (password.value.length < 6) throw '密码至少需要 6 位'
|
||||||
|
finishLogin(await loginWithPassword(phone.value, password.value))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
errorMessage.value = error == null ? '登录失败,请稍后重试' : error.toString()
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onUnmounted(() => { if (countdownTimer != null) clearInterval(countdownTimer) })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -46,7 +122,10 @@
|
|||||||
.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-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-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; }
|
||||||
.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 { 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; }
|
||||||
.other-login { margin-top: 62rpx; flex-direction: row; align-items: center; justify-content: center; color: #a0a9b7; font-size: 22rpx; }
|
.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; }
|
.line { height: 1rpx; background-color: #e5eaf1; width: 120rpx; margin: 0 20rpx; }
|
||||||
.social-row { flex-direction: row; justify-content: center; margin-top: 40rpx; }
|
.social-row { flex-direction: row; justify-content: center; margin-top: 40rpx; }
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
<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>
|
<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">
|
||||||
<view class="match-content">
|
<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="match-card" v-for="item in matches" :key="item.rank">
|
||||||
<view :class="'rank rank-' + item.rank"><text>{{ item.rank }}</text></view>
|
<view :class="'rank rank-' + item.rank"><text>{{ item.rank }}</text></view>
|
||||||
<view class="bank-logo"><AppIcon name="building" :size="40" /></view>
|
<view class="bank-logo"><AppIcon name="building" :size="40" /></view>
|
||||||
@@ -24,7 +26,15 @@
|
|||||||
import AppIcon from '@/components/AppIcon.uvue'
|
import AppIcon from '@/components/AppIcon.uvue'
|
||||||
const filter = ref('all')
|
const filter = ref('all')
|
||||||
const matches = ref<UTSJSONObject[]>([])
|
const matches = ref<UTSJSONObject[]>([])
|
||||||
onMounted(async () => { matches.value = await fetchMatches() })
|
const loading = ref(true)
|
||||||
|
const errorMessage = ref('')
|
||||||
|
const loadMatches = async () => {
|
||||||
|
loading.value = true
|
||||||
|
errorMessage.value = ''
|
||||||
|
try { matches.value = await fetchMatches() } catch (error) { errorMessage.value = '匹配方案加载失败,请重试' }
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
onMounted(() => { loadMatches() })
|
||||||
const openProduct = (rank: number) => { uni.navigateTo({ url: '/pages/product/product?id=' + rank }) }
|
const openProduct = (rank: number) => { uni.navigateTo({ url: '/pages/product/product?id=' + rank }) }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -33,4 +43,5 @@
|
|||||||
.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; }
|
.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; }
|
.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; }
|
.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>
|
</style>
|
||||||
|
|||||||
+13
-5
@@ -4,8 +4,9 @@
|
|||||||
<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>
|
<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">
|
||||||
<view class="news-content">
|
<view class="news-content">
|
||||||
<view class="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/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="article" v-for="item in articles" :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 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>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
@@ -13,18 +14,25 @@
|
|||||||
|
|
||||||
<script setup lang="uts">
|
<script setup lang="uts">
|
||||||
import AppHeader from '@/components/AppHeader.uvue'
|
import AppHeader from '@/components/AppHeader.uvue'
|
||||||
|
import AppIcon from '@/components/AppIcon.uvue'
|
||||||
const tabs = ['推荐', '企业融资', '个人融资', '政策解读']
|
const tabs = ['推荐', '企业融资', '个人融资', '政策解读']
|
||||||
const tab = ref('推荐')
|
const tab = ref('推荐')
|
||||||
const articles = [
|
const articles = [
|
||||||
{ title: '如何提高个人贷款通过率', summary: '从征信、负债和收入三个维度做好申请准备。', category: '个人融资', date: '2026-09-05', image: '/static/images/news-calculator.png' },
|
{ 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-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-30', image: '/static/images/news-business.png' },
|
||||||
|
{ title: '普惠金融新政解读', summary: '了解小微企业融资支持政策和申请重点。', category: '政策解读', date: '2026-08-28', image: '/static/images/news-policy.png' }
|
||||||
]
|
]
|
||||||
const openArticle = (title: string) => { uni.showToast({ title: title, icon: 'none' }) }
|
const filteredArticles = computed(() => {
|
||||||
|
if (tab.value == '推荐') return articles
|
||||||
|
return articles.filter((item) => item.category == tab.value)
|
||||||
|
})
|
||||||
|
const openArticle = (title: string) => { uni.navigateTo({ url: '/pages/article/article?title=' + title }) }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<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; }
|
.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; }
|
.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; }
|
.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>
|
</style>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
import AppHeader from '@/components/AppHeader.uvue'
|
import AppHeader from '@/components/AppHeader.uvue'
|
||||||
import BottomNav from '@/components/BottomNav.uvue'
|
import BottomNav from '@/components/BottomNav.uvue'
|
||||||
import AppIcon from '@/components/AppIcon.uvue'
|
import AppIcon from '@/components/AppIcon.uvue'
|
||||||
|
import { clearSession } from '@/services/api.uts'
|
||||||
const menus = [
|
const menus = [
|
||||||
{ icon: 'archive', name: '我的融资档案', note: '75%', color: 'blue', url: '/pages/archive/archive' },
|
{ icon: 'archive', name: '我的融资档案', note: '75%', color: 'blue', url: '/pages/archive/archive' },
|
||||||
{ icon: 'points', name: '积分中心', note: '2680', color: 'purple', url: '/pages/points/points' },
|
{ icon: 'points', name: '积分中心', note: '2680', color: 'purple', url: '/pages/points/points' },
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
{ icon: 'info', name: '关于我们', note: 'V1.0.0', color: 'gray', url: '' }
|
{ icon: 'info', name: '关于我们', note: 'V1.0.0', color: 'gray', url: '' }
|
||||||
]
|
]
|
||||||
const open = (url: string, name: string) => { if (url.length > 0) uni.navigateTo({ url: url }); else uni.showToast({ title: name + '功能已预留', icon: 'none' }) }
|
const open = (url: string, name: string) => { if (url.length > 0) uni.navigateTo({ url: url }); else uni.showToast({ title: name + '功能已预留', icon: 'none' }) }
|
||||||
const logout = () => { uni.reLaunch({ url: '/pages/login/login' }) }
|
const logout = () => { clearSession(); uni.reLaunch({ url: '/pages/login/login' }) }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
+105
-14
@@ -1,34 +1,125 @@
|
|||||||
const API_BASE_URL = ''
|
/** Mobile API adapter with deterministic local mocks. */
|
||||||
|
export const API_BASE_URL = ''
|
||||||
|
const REQUEST_TIMEOUT = 10000
|
||||||
|
const SESSION_KEY = 'aiFinanceSession'
|
||||||
|
|
||||||
const wait = (duration: number) => new Promise<void>((resolve) => { setTimeout(() => resolve(), duration) })
|
const wait = (duration: number) => new Promise<void>((resolve) => {
|
||||||
|
setTimeout(() => resolve(), duration)
|
||||||
|
})
|
||||||
|
|
||||||
|
export function saveSession(token: string, phone: string): void {
|
||||||
|
uni.setStorageSync(SESSION_KEY, { token, phone, loggedInAt: Date.now() })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSession(): UTSJSONObject | null {
|
||||||
|
const value = uni.getStorageSync(SESSION_KEY)
|
||||||
|
if (value == null) return null
|
||||||
|
return value as UTSJSONObject
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLoggedIn(): boolean {
|
||||||
|
return getSession() != null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearSession(): void {
|
||||||
|
uni.removeStorageSync(SESSION_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiMessage = (error: unknown, fallback: string): string => {
|
||||||
|
if (error == null) return fallback
|
||||||
|
const text = error.toString()
|
||||||
|
return text.length > 0 ? text : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestJson = (path: string, method: RequestMethod, payload: UTSJSONObject): Promise<UTSJSONObject> => {
|
||||||
|
return new Promise<UTSJSONObject>((resolve, reject) => {
|
||||||
|
uni.request({
|
||||||
|
url: API_BASE_URL + path,
|
||||||
|
method,
|
||||||
|
data: payload,
|
||||||
|
timeout: REQUEST_TIMEOUT,
|
||||||
|
success: (response) => {
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||||
|
if (response.data == null) resolve(new UTSJSONObject())
|
||||||
|
else resolve(response.data as UTSJSONObject)
|
||||||
|
} else {
|
||||||
|
reject('请求失败(' + response.statusCode.toString() + ')')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
reject(apiMessage(error, '网络请求失败,请稍后重试'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// API_BASE_URL 为空时使用本地演示数据;接入后端后仅需替换各方法内部实现。
|
|
||||||
export async function requestSmsCode(phone: string): Promise<boolean> {
|
export async function requestSmsCode(phone: string): Promise<boolean> {
|
||||||
if (API_BASE_URL.length == 0) { await wait(350); return phone.length >= 11 }
|
if (API_BASE_URL.length == 0) {
|
||||||
|
await wait(350)
|
||||||
|
return phone.length == 11
|
||||||
|
}
|
||||||
|
await requestJson('/auth/sms-code', 'POST', { phone })
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 位数字'
|
||||||
|
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'
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loginWithPassword(phone: string, password: string): Promise<string> {
|
||||||
|
if (API_BASE_URL.length == 0) {
|
||||||
|
await wait(300)
|
||||||
|
return 'demo-password-token'
|
||||||
|
}
|
||||||
|
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'
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
export async function submitAssessment(payload: UTSJSONObject): Promise<string> {
|
export async function submitAssessment(payload: UTSJSONObject): Promise<string> {
|
||||||
if (API_BASE_URL.length == 0) {
|
if (API_BASE_URL.length == 0) {
|
||||||
await wait(650)
|
await wait(650)
|
||||||
uni.setStorageSync('latestAssessment', payload)
|
uni.setStorageSync('latestAssessment', payload)
|
||||||
return 'ASSESSMENT-DEMO-001'
|
return 'ASSESSMENT-DEMO-001'
|
||||||
}
|
}
|
||||||
return ''
|
const result = await requestJson('/assessments', 'POST', payload)
|
||||||
|
const id = result['id'] as string | null
|
||||||
|
if (id == null || id.length == 0) throw '评估提交成功但未返回编号'
|
||||||
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchMatches(): Promise<UTSJSONObject[]> {
|
export async function fetchMatches(): Promise<UTSJSONObject[]> {
|
||||||
await wait(320)
|
if (API_BASE_URL.length == 0) {
|
||||||
return [
|
await wait(320)
|
||||||
{ rank: 1, bank: '招商银行·闪电贷', amount: '50万', rate: '3.85%', term: '12-36期', score: 96 },
|
return [
|
||||||
{ rank: 2, bank: '平安银行·新一贷', amount: '30万', rate: '4.20%', term: '12-60期', score: 91 },
|
{ rank: 1, bank: '招商银行·闪电贷', amount: '50万', rate: '3.85%', term: '12-36期', score: 96 },
|
||||||
{ rank: 3, bank: '建设银行·快e贷', amount: '20万', rate: '4.35%', term: '12-36期', score: 88 }
|
{ rank: 2, bank: '平安银行·新一贷', amount: '30万', rate: '4.20%', term: '12-60期', score: 91 },
|
||||||
]
|
{ rank: 3, bank: '建设银行·快e贷', amount: '20万', rate: '4.35%', term: '12-36期', score: 88 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const result = await requestJson('/matches', 'GET', new UTSJSONObject())
|
||||||
|
const items = result['items'] as UTSJSONObject[] | null
|
||||||
|
return items == null ? [] : items
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendAssistantMessage(message: string): Promise<string> {
|
export async function sendAssistantMessage(message: string): Promise<string> {
|
||||||
await wait(600)
|
if (API_BASE_URL.length == 0) {
|
||||||
return '结合您的评估结果,建议优先选择年化利率较低、可随借随还的产品,并重点确认提前还款规则。'
|
await wait(600)
|
||||||
|
return '结合您的评估结果,建议优先选择年化利率较低、可随借随还的产品,并重点确认提前还款规则。'
|
||||||
|
}
|
||||||
|
const result = await requestJson('/assistant/messages', 'POST', { message })
|
||||||
|
const reply = result['reply'] as string | null
|
||||||
|
if (reply == null || reply.length == 0) throw '助手暂时没有返回内容'
|
||||||
|
return reply
|
||||||
}
|
}
|
||||||
|
|
||||||
export { API_BASE_URL }
|
export { apiMessage }
|
||||||
|
|||||||
Reference in New Issue
Block a user