feat: 完成项目基础功能搭建与样式优化
1. 新增评估选择组件、本地草稿存储与新闻/金融内容服务 2. 重构页面布局与样式:调整按钮阴影、图标尺寸、导航栏样式 3. 更新启动页、报告页、消息中心等页面UI与交互逻辑 4. 替换图标资源,调整禁用态透明度 5. 补充README文档与本地数据模拟实现
This commit is contained in:
+14
-7
@@ -1,4 +1,5 @@
|
||||
/** Mobile API adapter with deterministic local mocks. */
|
||||
import { demoProducts, parseProduct, FinanceProduct } from '@/services/finance.uts'
|
||||
export const API_BASE_URL = ''
|
||||
const REQUEST_TIMEOUT = 10000
|
||||
const SESSION_KEY = 'aiFinanceSession'
|
||||
@@ -97,18 +98,24 @@ export async function submitAssessment(payload: UTSJSONObject): Promise<string>
|
||||
return id
|
||||
}
|
||||
|
||||
export async function fetchMatches(): Promise<UTSJSONObject[]> {
|
||||
export async function fetchMatches(): Promise<FinanceProduct[]> {
|
||||
if (API_BASE_URL.length == 0) {
|
||||
await wait(320)
|
||||
return [
|
||||
{ rank: 1, bank: '招商银行·闪电贷', amount: '50万', rate: '3.85%', term: '12-36期', score: 96 },
|
||||
{ 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 }
|
||||
]
|
||||
return demoProducts.slice()
|
||||
}
|
||||
const result = await requestJson('/matches', 'GET', new UTSJSONObject())
|
||||
const items = result['items'] as UTSJSONObject[] | null
|
||||
return items == null ? [] : items
|
||||
return items == null ? [] : items.map((item: UTSJSONObject): FinanceProduct => parseProduct(item))
|
||||
}
|
||||
|
||||
export async function fetchProduct(id: string): Promise<FinanceProduct | null> {
|
||||
if (API_BASE_URL.length == 0) {
|
||||
await wait(180)
|
||||
const products = demoProducts.filter((item: FinanceProduct): boolean => item.id == id)
|
||||
return products.length > 0 ? products[0] : null
|
||||
}
|
||||
const result = await requestJson('/products/' + encodeURIComponent(id), 'GET', new UTSJSONObject())
|
||||
return parseProduct(result)
|
||||
}
|
||||
|
||||
export async function sendAssistantMessage(message: string): Promise<string> {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
export 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, enterpriseName: string, businessCode: string, annualRevenue: string, operatingYears: string
|
||||
}
|
||||
export type SelectedDocument = { name: string, path: string }
|
||||
export type DataAuthorization = { key: string, name: string, selected: boolean }
|
||||
|
||||
export function emptyAssessmentForm(): AssessmentForm {
|
||||
return { name: '', idCard: '', phone: '', city: '', maritalStatus: '', occupation: '', company: '', monthlyIncome: '', socialSecurity: '', housingFund: '', house: '', car: '', savings: '', creditCard: '', otherAssets: '', enterpriseName: '', businessCode: '', annualRevenue: '', operatingYears: '' }
|
||||
}
|
||||
export function draftText(saved: UTSJSONObject, key: string): string {
|
||||
const value = saved[key]
|
||||
return typeof value == 'string' ? value as string : ''
|
||||
}
|
||||
export function activeAssessmentType(): string {
|
||||
const type = uni.getStorageSync('assessmentActiveType')
|
||||
if (type == 'enterprise' || type == 'personal') return type as string
|
||||
const legacy = uni.getStorageSync('assessmentDraft')
|
||||
if (legacy != null && typeof legacy == 'object' && draftText(legacy as UTSJSONObject, 'type') == 'enterprise') return 'enterprise'
|
||||
return 'personal'
|
||||
}
|
||||
export function loadAssessmentDraft(type: string): UTSJSONObject | null {
|
||||
const scoped = uni.getStorageSync('assessmentDraft:' + type)
|
||||
if (scoped != null && typeof scoped == 'object') return scoped as UTSJSONObject
|
||||
const legacy = uni.getStorageSync('assessmentDraft')
|
||||
if (legacy != null && typeof legacy == 'object') {
|
||||
const saved = legacy as UTSJSONObject
|
||||
if (draftText(saved, 'type') == type) return saved
|
||||
}
|
||||
return null
|
||||
}
|
||||
export function formFromDraft(saved: UTSJSONObject): AssessmentForm {
|
||||
return {
|
||||
name: draftText(saved, 'name'), idCard: draftText(saved, 'idCard'), phone: draftText(saved, 'phone'), city: draftText(saved, 'city'), maritalStatus: draftText(saved, 'maritalStatus'),
|
||||
occupation: draftText(saved, 'occupation'), company: draftText(saved, 'company'), monthlyIncome: draftText(saved, 'monthlyIncome'), socialSecurity: draftText(saved, 'socialSecurity'), housingFund: draftText(saved, 'housingFund'),
|
||||
house: draftText(saved, 'house'), car: draftText(saved, 'car'), savings: draftText(saved, 'savings'), creditCard: draftText(saved, 'creditCard'), otherAssets: draftText(saved, 'otherAssets'),
|
||||
enterpriseName: draftText(saved, 'enterpriseName'), businessCode: draftText(saved, 'businessCode'), annualRevenue: draftText(saved, 'annualRevenue'), operatingYears: draftText(saved, 'operatingYears')
|
||||
}
|
||||
}
|
||||
export function documentsFromDraft(saved: UTSJSONObject): SelectedDocument[] {
|
||||
const results: SelectedDocument[] = []
|
||||
const items = saved['documents'] as UTSJSONObject[] | null
|
||||
if (items == null) return results
|
||||
items.forEach((item) => {
|
||||
const name = draftText(item, 'name'); const path = draftText(item, 'path')
|
||||
if (name.length > 0 && path.length > 0) { const document: SelectedDocument = { name, path }; results.push(document) }
|
||||
})
|
||||
return results
|
||||
}
|
||||
export function saveAssessmentDraft(type: string, step: number, form: AssessmentForm, documents: SelectedDocument[], authorizations: DataAuthorization[], documentConsent: boolean, dataConsent: boolean): void {
|
||||
const files: UTSJSONObject[] = documents.map((item): UTSJSONObject => ({ name: item.name, path: item.path }))
|
||||
const grants: string[] = authorizations.filter((item) => item.selected).map((item) => item.key)
|
||||
const payload: UTSJSONObject = {
|
||||
type, step, 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,
|
||||
enterpriseName: form.enterpriseName, businessCode: form.businessCode, annualRevenue: form.annualRevenue, operatingYears: form.operatingYears,
|
||||
documents: files, authorizations: grants, documentConsent, dataConsent
|
||||
}
|
||||
uni.setStorageSync('assessmentActiveType', type)
|
||||
uni.setStorageSync('assessmentDraft:' + type, payload)
|
||||
// 保留现有提交与档案入口读取的活动草稿。
|
||||
uni.setStorageSync('assessmentDraft', payload)
|
||||
}
|
||||
export function showAgreementNotice(title: string): void {
|
||||
uni.showModal({ title, content: '正式协议尚未配置。当前为功能演示,勾选仅记录本地选择,不会发起实名认证、查询外部数据或上传资料。正式服务开放前,将展示完整协议供您阅读与确认。', showCancel: false, confirmText: '我知道了' })
|
||||
}
|
||||
|
||||
export type AssessmentProgress = { percent: number, basicDone: boolean, incomeDone: boolean, assetsDone: boolean, authorizationSelected: boolean }
|
||||
export function assessmentProgress(type: string): AssessmentProgress {
|
||||
const saved = loadAssessmentDraft(type)
|
||||
if (saved == null) return { percent: 0, basicDone: false, incomeDone: false, assetsDone: false, authorizationSelected: false }
|
||||
const basic = type == 'enterprise' ? ['enterpriseName', 'businessCode', 'name', 'phone', 'city'] : ['name', 'idCard', 'phone', 'city', 'maritalStatus']
|
||||
const income = type == 'enterprise' ? ['occupation', 'operatingYears', 'annualRevenue'] : ['occupation', 'company', 'monthlyIncome', 'socialSecurity', 'housingFund']
|
||||
const assets = ['house', 'car', 'savings', 'creditCard']
|
||||
const keys = basic.concat(income).concat(assets)
|
||||
const filled = keys.filter((key) => draftText(saved, key).trim().length > 0).length
|
||||
const grants = saved['authorizations'] as string[] | null
|
||||
return { percent: Math.round(filled / keys.length * 100), basicDone: basic.every((key) => draftText(saved, key).length > 0), incomeDone: income.every((key) => draftText(saved, key).length > 0), assetsDone: assets.every((key) => draftText(saved, key).length > 0), authorizationSelected: documentsFromDraft(saved).length > 0 || (grants != null && grants.length > 0) }
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
export type ContentSection = { title: string, body: string }
|
||||
export type NewsArticle = {
|
||||
id: string, title: string, summary: string, category: string,
|
||||
date: string, source: string, readingTime: string, image: string, sections: ContentSection[]
|
||||
}
|
||||
export type FinanceLesson = {
|
||||
id: string, title: string, desc: string, duration: string, tag: string,
|
||||
image: string, intro: string, topics: string[], chapters: ContentSection[]
|
||||
}
|
||||
|
||||
// 新闻与课程独立建模,接入后端时可分别替换为对应接口。
|
||||
export const newsArticles: NewsArticle[] = [
|
||||
{
|
||||
id: 'inclusive-finance', title: '普惠金融政策,企业申请前应关注什么?',
|
||||
summary: '看清适用主体、申请条件与材料要求,找到适合企业的支持方式。', category: '政策解读',
|
||||
date: '2026-09-08', source: 'AI融资编辑整理', readingTime: '4 分钟', image: '/static/mobile/images/news-city.png',
|
||||
sections: [
|
||||
{ title: '先核对政策适用范围', body: '普惠金融支持措施通常对经营主体、企业规模和经营所在地设有适用条件。阅读政策时,先核对发文机构、生效时间与适用范围,再确认企业是否满足基本要求。' },
|
||||
{ title: '区分贷款、担保与贴息', body: '信贷产品、融资担保和财政贴息的申请渠道与审核流程可能不同。获得政策支持不等于贷款必然获批,额度、利率和期限仍需以金融机构的实际审核结果为准。' },
|
||||
{ title: '从官方渠道核实并准备资料', body: '建议通过当地政府、金融管理部门或银行官方网站核实细则,准备营业执照、经营流水、纳税记录与资金用途说明。本文提供阅读方法,不代表具体政策承诺。' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'personal-credit', title: '申请个人信用融资,先核对这三项信息',
|
||||
summary: '征信记录、收入稳定性与现有负债,是申请前的重点检查项。', category: '个人融资',
|
||||
date: '2026-09-05', source: 'AI融资编辑整理', readingTime: '3 分钟', image: '/static/mobile/images/news-calculator.png',
|
||||
sections: [
|
||||
{ title: '查看信用记录是否准确', body: '通过官方渠道查询个人信用报告,核对账户、还款记录与查询记录。发现错误信息时,应通过正规异议渠道处理,不轻信付费“修复征信”承诺。' },
|
||||
{ title: '整理可以核验的收入证明', body: '根据职业情况整理工资流水、经营收款、社保或纳税记录,确保填写的收入与材料一致。不同机构认可的材料范围有所不同,请以申请页面或机构说明为准。' },
|
||||
{ title: '量力确定额度与月供', body: '将现有贷款和信用卡应还金额纳入每月支出,再评估新增还款是否可持续。避免为尝试额度而短期内集中向多家机构提交申请。' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'loan-comparison', title: '比较融资产品时,别只看宣传利率',
|
||||
summary: '年化利率、附加费用与还款方式,需要放在一起比较。', category: '个人融资',
|
||||
date: '2026-09-03', source: 'AI融资编辑整理', readingTime: '3 分钟', image: '/static/mobile/images/news-city.png',
|
||||
sections: [
|
||||
{ title: '先统一利率口径', body: '日利率、月利率与年化利率并不是同一个口径。比较时应查看机构披露的年化利率及计算方式,尤其注意“低至”利率所附的适用条件。' },
|
||||
{ title: '将费用计入总成本', body: '核对合同是否包含担保费、服务费等支出,以及提前还款是否有额外费用。应以正式合同、还款计划和实际审核结果为准。' },
|
||||
{ title: '让还款安排匹配现金流', body: '等额本息、等额本金、先息后本的还款节奏各不相同。除了总利息,还应关注每期应还金额和到期本金,避免因最后一期集中还款造成压力。' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'business-documents', title: '小微企业融资:常见材料如何提前整理',
|
||||
summary: '按主体、经营和用途三类整理资料,减少补充沟通。', category: '企业融资',
|
||||
date: '2026-08-30', source: 'AI融资编辑整理', readingTime: '4 分钟', image: '/static/mobile/images/news-business.png',
|
||||
sections: [
|
||||
{ title: '主体资料保持一致', body: '核对营业执照、实际经营地址、法定代表人及联系人信息。涉及经营主体或股权变更时,准备相应说明,避免不同材料之间的信息不一致。' },
|
||||
{ title: '整理持续经营的证明', body: '经营流水、纳税记录、采购或销售合同可以帮助机构了解业务情况。按照时间顺序整理,并区分经营性收入、借款与其他往来款。' },
|
||||
{ title: '说明资金用途与还款来源', body: '将资金需求对应到采购、周转或设备投入等具体用途,并准备与之匹配的凭证。不同机构要求不同,正式提交前请核对其最新材料清单。' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
export const financeLessons: FinanceLesson[] = [
|
||||
{
|
||||
id: 'credit-basics', title: '个人信用申请准备课', desc: '用三步梳理信用、收入与负债', duration: '6 分钟',
|
||||
tag: '个人融资', image: '/static/mobile/images/news-calculator.png', topics: ['credit', 'documents'],
|
||||
intro: '从自己的实际情况出发,建立一份清晰的申请前检查清单。完成每一节阅读后,可手动记录学习进度。',
|
||||
chapters: [
|
||||
{ title: '读懂自己的信用记录', body: '先通过官方渠道获取个人信用报告,核对账户数量、当前负债与还款记录。\n\n对确有错误的信息,按官方异议流程申请核查;真实的不良记录不能通过付费承诺消除。\n\n本节练习:记录需要核对的账户,并确认最近的还款安排。' },
|
||||
{ title: '整理收入与还款支出', body: '把稳定收入、必要生活支出及已有月供分别列出,观察每月可用于新增还款的资金是否充足。\n\n奖金、临时收入与不确定的经营回款不宜直接视为稳定收入。预留应急资金也有助于避免还款压力。\n\n本节练习:用最近几个月的实际流水,估算可持续的月供范围。' },
|
||||
{ title: '核对材料并选择申请渠道', body: '按照目标机构的清单准备身份证明、收入证明及相关授权,检查姓名、工作与收入信息是否一致。\n\n通过银行或持牌机构的官方渠道申请,不向不明个人账户支付所谓“保证通过”的费用。审批结果以机构审核为准。\n\n本节练习:列出已准备和待补充的材料,提交前再核对一次。' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'business-checklist', title: '小微经营融资准备课', desc: '把经营情况整理成完整材料清单', duration: '8 分钟',
|
||||
tag: '企业融资', image: '/static/mobile/images/news-business.png', topics: ['business', 'documents'],
|
||||
intro: '围绕真实经营、资金用途和还款来源,分步骤整理企业融资所需的信息。具体材料要求以申请机构为准。',
|
||||
chapters: [
|
||||
{ title: '核对企业主体与经营信息', body: '核对营业执照、经营地址、法定代表人与实际控制人信息,确保材料在有效期内。\n\n介绍企业业务时,可以说明主要产品、客户类型和经营年限。信息以真实经营情况为准,无需为了申请夸大规模。\n\n本节练习:核对主体证件与申请表中各项信息是否一致。' },
|
||||
{ title: '归集流水与纳税记录', body: '按照申请机构要求的时间范围整理经营账户流水、纳税记录和业务合同。将经营收入与借款、股东往来等款项区分开。\n\n对季节性波动或较大的单笔交易,准备可核验的业务说明,帮助机构理解经营现金流。\n\n本节练习:按月份归档资料,标记需要补充解释的收支。' },
|
||||
{ title: '写清资金用途与还款计划', body: '将需求拆解为采购、订单周转或设备投入等实际用途,并评估资金回笼时间。\n\n融资期限应尽量与经营周期相匹配,避免依赖不确定的下一笔借款偿还到期本金。\n\n本节练习:写下所需金额、预计使用时间与还款来源。' },
|
||||
{ title: '检查授权范围与提交清单', body: '提交前阅读数据授权说明,确认授权对象、用途、范围与期限。不要将账户密码、短信验证码交给不明中介。\n\n将所有附件与机构清单逐项比对,保留提交记录,后续按机构通知补充材料。\n\n本节练习:完成材料清单核对,并确认使用的是官方申请渠道。' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'loan-cost', title: '利率与还款方式入门', desc: '看懂总成本,选好还款节奏', duration: '5 分钟',
|
||||
tag: '基础知识', image: '/static/mobile/images/news-policy.png', topics: ['credit', 'business'],
|
||||
intro: '把利率、期限和还款方式放在一起理解,学会用统一口径比较融资方案。',
|
||||
chapters: [
|
||||
{ title: '比较利率先看口径', body: '比较产品时,优先查看正式披露的年化利率及计算方式。日息、月息或“每万元每天几元”的宣传表达不能直接互相比高低。\n\n实际适用利率通常与审核结果相关,页面显示的最低利率不代表每个人都能获得。\n\n本节练习:找到一份产品说明中的年化利率、适用条件与费用说明。' },
|
||||
{ title: '认识不同的还款节奏', body: '等额本息通常每期还款额相近;等额本金的本金部分相同,随着剩余本金减少,利息逐步降低。\n\n先息后本需要在到期时集中偿还本金。每月压力较小并不意味着到期压力也小,应提前规划本金来源。\n\n本节练习:对照还款计划,找出金额最大的一期。' },
|
||||
{ title: '把费用与现金流一起算', body: '核对担保费、服务费与提前还款规则,将必要费用计入总成本。比较方案时还要统一金额与期限。\n\n选择能被自身现金流持续覆盖的还款安排,不只追求额度或最长的期限。最终以合同约定和实际审核结果为准。\n\n本节练习:记录总还款额、每期应还金额及提前还款条件。' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
export function findNewsArticle(id: string, legacyTitle: string): NewsArticle | null {
|
||||
let articleId = id
|
||||
if (legacyTitle == 'headline' || legacyTitle == '普惠金融新政解读' || legacyTitle == '普惠金融新政解读:小微企业迎来融资利好') articleId = 'inclusive-finance'
|
||||
if (legacyTitle == '如何提高个人贷款通过率') articleId = 'personal-credit'
|
||||
if (legacyTitle == '银行最新贷款产品汇总') articleId = 'loan-comparison'
|
||||
if (legacyTitle == '小微企业融资材料清单') articleId = 'business-documents'
|
||||
return newsArticles.find((item: NewsArticle): boolean => item.id == articleId || item.title == legacyTitle) ?? null
|
||||
}
|
||||
|
||||
export function findFinanceLesson(id: string, legacyTitle: string): FinanceLesson | null {
|
||||
let lessonId = id
|
||||
if (id == '0' || legacyTitle == '个人信用提升' || legacyTitle == '如何提高个人贷款通过率') lessonId = 'credit-basics'
|
||||
if (id == '1' || legacyTitle == '小微经营融资' || legacyTitle == '材料准备清单' || legacyTitle == '小微企业融资材料清单') lessonId = 'business-checklist'
|
||||
if (id == '2' || legacyTitle == '读懂贷款利率与还款方式') lessonId = 'loan-cost'
|
||||
return financeLessons.find((item: FinanceLesson): boolean => item.id == lessonId || item.title == legacyTitle) ?? null
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export type FinanceProduct = {
|
||||
id: string, bank: string, initials: string, color: string,
|
||||
amount: number, rate: number, term: string, score: number,
|
||||
advantages: string[], age: string, work: string, credit: string, materials: string
|
||||
}
|
||||
|
||||
// 列表和详情共用产品标识;正式数据由 API 适配层提供。
|
||||
export const demoProducts: FinanceProduct[] = [
|
||||
{ id: '1', bank: '招商银行·闪电贷', initials: '招', color: '#c93547', amount: 50, rate: 3.85, term: '12–36个月', score: 96, advantages: ['线上申请', '无需抵押', '额度循环', '还款灵活'], age: '22–55周岁', work: '现单位工作满6个月', credit: '近两年无严重逾期', materials: '身份证 · 银行卡 · 收入证明' },
|
||||
{ id: '2', bank: '平安银行·新一贷', initials: '平', color: '#ed7924', amount: 30, rate: 4.20, term: '12–60个月', score: 91, advantages: ['信用融资', '期限可选', '在线预审', '无需担保'], age: '23–55周岁', work: '有稳定工作或经营收入', credit: '征信记录良好', materials: '身份证 · 收入证明 · 居住证明' },
|
||||
{ id: '3', bank: '建设银行·快e贷', initials: '建', color: '#2869b7', amount: 20, rate: 4.35, term: '12–36个月', score: 88, advantages: ['线上办理', '便捷用款', '信用授信', '支持提前还款'], age: '22–60周岁', work: '有稳定收入来源', credit: '符合机构信用要求', materials: '身份证 · 银行卡 · 资产或收入证明' }
|
||||
]
|
||||
|
||||
export function parseProduct(item: UTSJSONObject): FinanceProduct {
|
||||
return {
|
||||
id: item['id'] as string ?? '', bank: item['bank'] as string ?? '融资产品',
|
||||
initials: item['initials'] as string ?? '融', color: item['color'] as string ?? '#2869b7',
|
||||
amount: item['amount'] as number ?? 0, rate: item['rate'] as number ?? 0,
|
||||
term: item['term'] as string ?? '以审批为准', score: item['score'] as number ?? 0,
|
||||
advantages: item['advantages'] as string[] ?? [], age: item['age'] as string ?? '以机构要求为准',
|
||||
work: item['work'] as string ?? '以机构要求为准', credit: item['credit'] as string ?? '以机构要求为准',
|
||||
materials: item['materials'] as string ?? '以机构要求为准'
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user