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
+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>