<template>
<view class="page-container" :style="isInputFocused ? { position: 'fixed', top: `-${scrollTop}px`, left: 0, right: 0, width: '100%' } : {}">
<!-- 重要提示弹窗 -->
<view class="modal-overlay" v-if="showNoticeModal">
<view class="modal-content">
<view class="modal-header">
<text class="modal-title">重要提示</text>
<image class="modal-warning-icon" src="/static/icons/waring.png" mode="aspectFit"></image>
</view>
<text class="modal-subtitle">本平台仅受理以下6个类别违纪违法问题:</text>
<view class="modal-list">
<view class="modal-list-item" v-for="(item, index) in noticeItems" :key="index">
<text class="modal-list-num">{{index + 1}}.</text>
<text class="modal-list-text">{{item}}</text>
</view>
</view>
<button class="modal-btn" :class="{'modal-btn-disabled': countdown > 0}" @click="closeNoticeModal">
{{ countdown > 0 ? `我已知晓(${countdown}s)` : '我已知晓' }}
</button>
</view>
</view>
<view class="scroll-content">
<!-- 举报人信息 -->
<view class="card" id="section1">
<text class="card-title">举报人信息</text>
<view class="form-item form-row">
<view class="form-label-group">
<text class="form-label">匿名提交</text>
<text class="form-desc">开启后无需填写个人信息</text>
</view>
<switch :checked="isAnonymous === 0" @change="handleAnonymousChange" color="#4A90E2" style="transform: scale(0.8);" />
</view>
<view v-if="isAnonymous !== 0">
<view class="form-item">
<text class="form-label">举报人姓名</text>
<input @focus="handleFocus" @blur="handleBlur" class="input-field" placeholder="请输入您的姓名" placeholder-class="placeholder" v-model="formData.reportName" maxlength="20" :adjust-position="false" />
</view>
<view class="form-item">
<text class="form-label">举报人联系方式</text>
<input @focus="handleFocus" @blur="handleBlur" class="input-field" placeholder="请输入手机号或座机号" placeholder-class="placeholder" v-model="formData.reportPhone" maxlength="20" :adjust-position="false" />
</view>
</view>
</view>
<!-- 被举报人信息 -->
<view class="card" id="section2">
<text class="card-title">被举报人信息</text>
<view class="form-item">
<view class="label-row">
<text class="form-label">被举报人姓名</text>
<text class="required">*</text>
</view>
<input @focus="handleFocus" @blur="handleBlur" class="input-field" placeholder="请输入被举报人姓名" placeholder-class="placeholder" v-model="formData.reportedName" maxlength="50" :adjust-position="false" />
</view>
<view class="form-item">
<view class="label-row"><text class="form-label">被举报人所在地区</text><text class="required">*</text></view>
<view class="picker-view-wrap">
<region-picker
v-model="formData.reportedArea"
:region-data="regionList"
placeholder="请选择被举报人所在地区"
/>
</view>
</view>
<view class="form-item">
<text class="form-label">被举报人职务</text>
<input @focus="handleFocus" @blur="handleBlur" class="input-field" placeholder="请输入职务" placeholder-class="placeholder" v-model="formData.reportedPost" maxlength="50" :adjust-position="false" />
</view>
<view class="form-item">
<text class="form-label">被举报人联系方式</text>
<input @focus="handleFocus" @blur="handleBlur" class="input-field" placeholder="请输入联系方式" placeholder-class="placeholder" v-model="formData.reportedPhone" maxlength="20" :adjust-position="false" />
</view>
</view>
<!-- 问题详情 -->
<view class="card" id="section3">
<text class="card-title">问题详情</text>
<view class="form-item">
<view class="label-row"><text class="form-label">问题类型</text><text class="required">*</text></view>
<view class="type-list">
<view class="type-item"
:class="{'type-item-active': currentProblemType === index}"
v-for="(item, index) in problemTypes"
:key="index"
@click="currentProblemType = index">
<text class="type-title" :class="{'type-title-active': currentProblemType === index}">{{item.dictLabel}}</text>
<text class="type-desc" :class="{'type-desc-active': currentProblemType === index}">{{item.remark}}</text>
</view>
</view>
</view>
<view class="form-item">
<view class="label-row"><text class="form-label">问题发生时间</text><text class="required">*</text></view>
<datetime-picker v-model="formData.happenTime" placeholder="请选择问题发生时间" />
</view>
<view class="form-item">
<view class="label-row"><text class="form-label">问题发生地点</text><text class="required">*</text></view>
<input @focus="handleFocus" @blur="handleBlur" class="input-field" placeholder="请输入问题发生地点" placeholder-class="placeholder" v-model="formData.happenPlace" maxlength="100" :adjust-position="false" />
</view>
<view class="form-item">
<view class="label-row"><text class="form-label">涉及人员/业务</text><text class="required">*</text></view>
<input @focus="handleFocus" @blur="handleBlur" class="input-field" placeholder="请描述涉及的具体人员或业务事项" placeholder-class="placeholder" v-model="formData.relatedBusiness" maxlength="200" :adjust-position="false" />
</view>
<view class="form-item">
<view class="label-row"><text class="form-label">关键事实描述</text><text class="required">*</text></view>
<textarea :fixed="true" @focus="handleFocus" @blur="handleBlur" class="textarea-field" placeholder="请详细描述问题经过,包括时间、地点、涉及人员/业务、关键事实" placeholder-class="placeholder" v-model="formData.factDesc" maxlength="2000" :adjust-position="false"></textarea>
</view>
<view class="form-item">
<text class="form-label">佐证材料</text>
<view class="upload-area" @click="chooseFiles" v-if="fileList.length < 9">
<image class="upload-icon" src="/static/icons/upload.png" mode="widthFix"></image>
<text class="upload-text">点击上传文件</text>
<text class="upload-desc">支持图片、视频、录音格式,单个文件不超过50MB,最多9个文件</text>
</view>
<view class="file-list" v-if="fileList.length > 0">
<!-- 已经选择的文件列表 -->
<view class="file-item" v-for="(file, index) in fileList" :key="index">
<text class="file-icon">{{ file.icon }}</text>
<view class="file-info">
<text class="file-name">{{ file.name }}</text>
<text class="file-size">{{ file.sizeText }}</text>
</view>
<view class="file-delete" @click="removeFile(index)">
<text class="delete-icon">×</text>
</view>
</view>
</view>
</view>
</view>
<!-- 底部提示 -->
<view class="tips-area">
<view class="tip-item"><text class="tip-dot">•</text><text class="tip-text">实名举报将在5个工作日内反馈处理</text></view>
<view class="tip-item"><text class="tip-dot">•</text><text class="tip-text">匿名举报直接办结,不及微处理结果</text></view>
<view class="tip-item"><text class="tip-dot">•</text><text class="tip-text">您的个人信息将严格保密,严禁泄露</text></view>
</view>
<!-- 底部安全距离 -->
<view class="safe-bottom-area"></view>
</view>
<!-- 底部固定按钮 -->
<view class="bottom-bar">
<button class="submit-btn" :loading="isSubmitting" :disabled="isSubmitting" @click="confirmSubmit">提交举报</button>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { onPageScroll } from '@dcloudio/uni-app'
import { config } from '@/utils/config.uts'
import { submitReportInfo, getDictDataByType, getRegionList, type DictData } from '@/utils/api.uts'
import {
validateFile,
validateFileCount,
getFileIcon,
formatFileSize,
MAX_FILE_COUNT
} from '@/utils/file-validator.uts'
import {
handleNetworkError,
handleBusinessError,
handleAuthError,
handleValidationError
} from '@/utils/error-handler.uts'
import datetimePicker from '@/components/datetime-picker/datetime-picker.uvue'
import regionPicker from '@/components/region-picker/region-picker.uvue'
const isAnonymous = ref(1) // 0 是开启匿名,1 是关闭匿名
const currentProblemType = ref(0)
const isSubmitting = ref(false)
const isInputFocused = ref(false)
const scrollTop = ref(0)
let focusTimer: number | null = null
onPageScroll((e) => {
if (!isInputFocused.value) {
scrollTop.value = e.scrollTop
}
})
const handleFocus = () => {
if (focusTimer !== null) {
clearTimeout(focusTimer)
}
isInputFocused.value = true
}
const handleBlur = (e: any) => {
isInputFocused.value = false
// 恢复滚动位置
uni.pageScrollTo({
scrollTop: scrollTop.value,
duration: 0
})
}
type FileItem = {
id?: string
name: string
path: string
size: number
type?: string
icon: string
sizeText: string
}
const fileList = ref<FileItem[]>([])
const showNoticeModal = ref(true)
const countdown = ref(10)
let timer: number | null = null
const noticeItems = ref([
'违规收费,搭车收费。',
'刁难客户,吃拿卡要。',
'违规收受礼品礼金或可能影响公正执行公务宴请、健身、娱乐活动',
'推诿搪塞、态度粗暴',
'为亲友或他人谋取利益',
'失职渎职,玩忽职守'
])
const closeNoticeModal = () => {
if (countdown.value <= 0) {
showNoticeModal.value = false
}
}
const regionList = ref([])
const getRegionData = async () => {
try {
const res = await getRegionList()
if (res.code === 200 && res.data) {
regionList.value = res.data
}
} catch (e) {
console.error('获取地区列表失败', e)
}
}
const formData = reactive({
reportName: '',
reportPhone: '',
reportedName: '',
reportedArea: '',
reportedPost: '',
reportedPhone: '',
happenTime: '',
happenPlace: '',
relatedBusiness: '',
factDesc: '',
attachmentIds: []
})
const problemTypes = ref<DictData[]>([])
const getProblemTypes = async () => {
try {
const res = await getDictDataByType('problem_type')
if (res.code === 200 && res.data) {
problemTypes.value = res.data
}
} catch (e) {
console.error('获取问题类型失败', e)
}
}
const handleAnonymousChange = (e: any) => {
isAnonymous.value = e.detail.value ? 0 : 1
}
const handleFiles = (tempFiles: Array<any>) => {
for (let i = 0; i < tempFiles.length; i++) {
const rawFile = tempFiles[i]
console.log(rawFile)
// 兼容 chooseMedia 和 chooseFile 的返回结构
const filePath = rawFile.path || rawFile.tempFilePath
console.log(filePath)
const type = filePath.split('.').pop()
const fileName = rawFile.name || `file_${Date.now()}_${i}.${type}`
const fileSize = rawFile.size
console.log(type)
// 验证文件
const validationResult = validateFile({
name: fileName,
size: fileSize,
type,
path: filePath
})
if (!validationResult.valid) {
uni.showToast({
title: validationResult.message,
icon: 'none',
duration: 3000
})
continue
}
// 显示上传中提示
uni.showLoading({ title: '上传中...', mask: true })
// 上传文件
uploadFile({
path: filePath,
name: fileName,
size: fileSize,
type: type
})
}
}
const chooseFiles = () => {
const currentCount = fileList.value.length
const remainCount = MAX_FILE_COUNT - currentCount
// 验证文件数量
const countResult = validateFileCount(currentCount, 1)
if (!countResult.valid) {
uni.showToast({ title: countResult.message, icon: 'none', duration: 3000 })
return
}
uni.showActionSheet({
itemList: ['图片或视频', '其他文件'],
success: (res) => {
if (res.tapIndex === 0) {
// 选择图片或视频
uni.chooseMedia({
count: remainCount,
mediaType: ['image', 'video'],
sourceType: ['album', 'camera'],
success: (mediaRes) => {
console.log(mediaRes,111)
handleFiles(mediaRes.tempFiles as Array<any>)
},
fail: (err) => {
console.error('选择媒体失败', err)
const errMsg = err['errMsg'] as string | null
if (errMsg != null && !errMsg.includes('cancel')) {
uni.showToast({ title: '选择取消或失败', icon: 'none' })
}
}
})
} else if (res.tapIndex === 1) {
// 选择其他文件
uni.chooseMessageFile({
count: remainCount,
type: 'file',
success: (fileRes) => {
handleFiles(fileRes.tempFiles as Array<any>)
},
fail: (err) => {
console.error('选择文件失败', err)
const errMsg = err['errMsg'] as string | null
if (errMsg != null && !errMsg.includes('cancel')) {
uni.showToast({ title: '选择取消或失败', icon: 'none' })
}
}
})
}
}
})
}
// 上传文件
const uploadFile = (file: any) => {
uni.uploadFile({
url: `${config.baseUrl}/msjd-report-info/upload`,
filePath: file.path,
name: 'file',
header: {
'Authorization': `Bearer ${uni.getStorageSync('token') || ''}`
},
success: (uploadFileRes) => {
try {
const resData = JSON.parse(uploadFileRes.data as string)
if (resData.code === 200) {
// 添加到文件列表
formData.attachmentIds.push(resData.data.id)
fileList.value.push({
id: resData.data.id,
name: file.name || `image_${Date.now()}.jpg`,
path: resData.data.url || file.path,
size: file.size,
type: file.type,
icon: getFileIcon(file.name || 'image.jpg'),
sizeText: formatFileSize(file.size)
})
uni.showToast({ title: '上传成功', icon: 'success' })
} else {
uni.showToast({
title: resData.msg || '上传失败',
icon: 'none',
duration: 3000
})
}
} catch (e) {
console.error('解析响应失败', e)
uni.showToast({
title: '解析响应失败',
icon: 'none',
duration: 3000
})
}
},
fail: (err) => {
console.error('上传异常', err)
uni.showToast({
title: '网络异常,上传失败',
icon: 'none',
duration: 3000
})
},
complete: () => {
uni.hideLoading()
}
})
}
const removeFile = (index: number) => {
uni.showModal({
title: '确认删除',
content: '确定要删除这个文件吗?',
success: (res) => {
if (res.confirm) {
formData.attachmentIds.splice(index, 1)
fileList.value.splice(index, 1)
uni.showToast({ title: '已删除', icon: 'success' })
}
}
})
}
onMounted(() => {
// 启动倒计时
timer = setInterval(() => {
if (countdown.value > 0) {
countdown.value--
} else {
if (timer !== null) {
clearInterval(timer)
timer = null
}
}
}, 1000)
// 获取问题类型字典数据
getProblemTypes()
getRegionData()
})
onUnmounted(() => {
if (timer !== null) {
clearInterval(timer)
timer = null
}
})
const confirmSubmit = () => {
// 手机号格式验证
const phoneReg = /^(?:(?:\+|00)86)?1[3-9]\d{9}$|^0\d{2,3}-?\d{7,8}$/
if (isAnonymous.value !== 0) {
if (!formData.reportName) {
uni.showToast({ title: '请输入举报人姓名', icon: 'none' })
return
}
if (!formData.reportPhone) {
uni.showToast({ title: '请输入举报人联系方式', icon: 'none' })
return
}
if (!phoneReg.test(formData.reportPhone)) {
uni.showToast({ title: '举报人联系方式格式不正确', icon: 'none' })
return
}
}
// 先进行必填项校验
if (!formData.reportedName) {
uni.showToast({ title: '请输入被举报人姓名', icon: 'none' })
return
}
if (!formData.reportedArea) {
uni.showToast({ title: '请选择被举报人所在地区', icon: 'none' })
return
}
if (formData.reportedPhone && !phoneReg.test(formData.reportedPhone)) {
uni.showToast({ title: '被举报人联系方式格式不正确', icon: 'none' })
return
}
if (!formData.happenTime) {
uni.showToast({ title: '请选择问题发生时间', icon: 'none' })
return
}
if (!formData.happenPlace) {
uni.showToast({ title: '请输入问题发生地点', icon: 'none' })
return
}
if (!formData.relatedBusiness) {
uni.showToast({ title: '请描述涉及的具体人员或业务事项', icon: 'none' })
return
}
if (!formData.factDesc) {
uni.showToast({ title: '请填写关键事实描述', icon: 'none' })
return
}
uni.showModal({
title: '提交确认',
content: '请确认举报信息是否真实有效,一经提交将不可修改。',
confirmText: '确认提交',
cancelText: '再检查下',
confirmColor: '#4A90E2',
success: (res) => {
if (res.confirm) {
handleSubmit()
}
}
})
}
const handleSubmit = async () => {
// 简单的XSS防护及特殊字符过滤
const xssFilter = (str: string) => {
if (!str) return str
return str.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''')
}
// 组装提交参数
const params = {
reportName: isAnonymous.value === 0 ? '' : xssFilter(formData.reportName),
reportPhone: isAnonymous.value === 0 ? '' : formData.reportPhone,
isAnonymous: isAnonymous.value,
reportedName: xssFilter(formData.reportedName),
reportedArea: formData.reportedArea,
reportedPost: xssFilter(formData.reportedPost),
reportedPhone: formData.reportedPhone,
problemType: problemTypes.value.length > 0 ? problemTypes.value[currentProblemType.value].dictValue : '',
happenTime: formData.happenTime,
happenPlace: xssFilter(formData.happenPlace),
relatedBusiness: xssFilter(formData.relatedBusiness),
factDesc: xssFilter(formData.factDesc),
attachmentIds: formData.attachmentIds.join(',')
}
isSubmitting.value = true
uni.showLoading({ title: '提交中...', mask: true })
try {
const res = await submitReportInfo(params)
uni.hideLoading()
if (res.code === 200) {
uni.showToast({ title: '提交成功', icon: 'success' })
// 跳转到成功页,携带返回的查询编码
let queryNo = ''
if (res.data != null) {
// 根据后端实际返回的数据结构来取查询编码
if (typeof res.data === 'string') {
queryNo = res.data as string
} else {
const resData = res.data as UTSJSONObject
const codeVal = resData['code'] ?? resData['queryNo']
if (codeVal != null) {
queryNo = `${codeVal}`
}
}
}
setTimeout(() => {
uni.redirectTo({
url: `/pages/success/success?type=report&queryNo=${queryNo || '--------'}`
})
}, 1500)
} else if (res.code === 401) {
handleAuthError()
} else {
handleBusinessError(res.code, res.msg || '提交失败')
}
} catch (err) {
uni.hideLoading()
console.error('表单提交失败', err)
handleNetworkError(err)
} finally {
isSubmitting.value = false
}
}
</script>
<style>
.page-container {
background-color: #F5F7FA;
min-height: 100vh;
/* 解决内部子元素无法撑开外部导致无法滚动的问题 */
overflow: visible;
padding-bottom: 200rpx; /* 给底部按钮留出空间 */
}
/* 弹窗样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
align-items: center;
justify-content: center;
}
.modal-content {
width: 678rpx;
background-color: #FFFFFF;
border-radius: 24rpx;
padding: 40rpx;
/* 顶部淡粉色渐变效果,如果有兼容问题可改用纯色 */
background-image: linear-gradient(180deg, #FFF0F0 0%, #FFFFFF 15%);
}
.modal-header {
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 24rpx;
}
.modal-title {
font-size: 36rpx;
font-weight: bold;
color: #333333;
}
.modal-warning-icon {
width: 98rpx;
height: 97rpx;
margin-top: -20rpx;
margin-right: 20rpx;
}
.modal-subtitle {
font-size: 28rpx;
color: #7C889C;
margin-bottom: 30rpx;
}
.modal-list {
margin-bottom: 40rpx;
}
.modal-list-item {
flex-direction: row;
margin-bottom: 20rpx;
align-items: flex-start;
}
.modal-list-num {
font-size: 28rpx;
color: #3BA7DF;
font-weight: bold;
margin-right: 12rpx;
width: 32rpx;
}
.modal-list-text {
flex: 1;
font-size: 28rpx;
color: #3B3D3F;
line-height: 1.5;
}
.modal-btn {
background-color: #3BA7DF;
color: #FFFFFF;
font-size: 32rpx;
border-radius: 16rpx;
height: 88rpx;
line-height: 88rpx;
width: 100%;
}
.modal-btn-disabled {
opacity: 0.6;
}
.modal-btn:active {
background-color: #2b8cbe;
}
/* 顶部步骤指示器 */
.step-indicator {
flex-direction: row;
align-items: center;
justify-content: center;
padding: 30rpx 0;
background-color: #FFFFFF;
position: sticky;
top: 0;
z-index: 100;
}
.step {
flex-direction: row;
align-items: center;
}
.step-circle {
width: 40rpx;
height: 40rpx;
border-radius: 20rpx;
background-color: #E4E7ED;
align-items: center;
justify-content: center;
margin-right: 12rpx;
}
.active-circle {
background-color: #35ACDF;
}
.circle-text {
font-size: 24rpx;
color: #FFFFFF;
}
.step-label {
font-size: 26rpx;
color: #999999;
}
.active-label {
color: #35ACDF;
font-weight: bold;
}
.step-arrow {
width: 24rpx;
height: 24rpx;
margin: 0 20rpx;
}
/* 滚动区域 */
.scroll-content {
padding: 30rpx;
}
/* 卡片样式 */
.card {
background-color: #FFFFFF;
border-radius: 24rpx;
padding: 40rpx 30rpx;
margin-bottom: 30rpx;
}
.card-title {
font-size: 32rpx;
font-weight: bold;
color: #1E2939;
margin-bottom: 40rpx;
}
/* 表单项 */
.form-item {
margin-bottom: 36rpx;
}
.form-row {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.label-row {
flex-direction: row;
align-items: center;
margin-bottom: 16rpx;
}
.form-label {
font-size: 28rpx;
color: #3B3D3F;
margin-bottom: 16rpx;
}
.label-row .form-label {
margin-bottom: 0;
}
.required {
color: #E02020;
font-size: 28rpx;
margin-left: 8rpx;
}
.form-desc {
font-size: 24rpx;
color: #999999;
margin-top: 8rpx;
}
/* 输入框 */
.input-field {
height: 88rpx;
background-color: #F8F9FA;
border-radius: 16rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #333333;
}
.textarea-field {
height: 200rpx;
background-color: #F8F9FA;
border-radius: 16rpx;
padding: 24rpx;
font-size: 28rpx;
color: #333333;
width: 100%;
box-sizing: border-box;
}
.placeholder {
color: #1E293980;
}
/* 选择器模拟 */
.picker-view-wrap {
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 80rpx;
background-color: #F8F9FA;
border-radius: 12rpx;
padding: 0 24rpx;
}
.picker-text {
font-size: 28rpx;
color: #1E293980
}
.picker-arrow {
width: 24rpx;
height: 24rpx;
}
/* 问题类型列表 */
.type-list {
flex-direction: column;
}
.type-item {
background-color: #F8F9FA;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
border: 2rpx solid transparent;
}
.type-item-active {
background-color: #FFFFFF;
border: 2rpx solid #4A90E2;
}
.type-title {
font-size: 28rpx;
font-weight: bold;
color: #333333;
margin-bottom: 8rpx;
}
.type-title-active {
color: #333333;
}
.type-desc {
font-size: 24rpx;
color: #999999;
}
.type-desc-active {
color: #999999;
}
/* 上传区域 */
.upload-area {
background-color: #F8F9FA;
border: 2rpx dashed #DCDFE6;
border-radius: 16rpx;
padding: 40rpx;
align-items: center;
justify-content: center;
}
.upload-icon {
width: 64rpx;
height: 64rpx;
margin-bottom: 20rpx;
}
.upload-text {
font-size: 28rpx;
color: #666666;
margin-bottom: 12rpx;
}
.upload-desc {
font-size: 24rpx;
color: #999999;
text-align: center;
}
.file-list {
margin-top: 20rpx;
}
.file-item {
flex-direction: row;
align-items: center;
background-color: #F8F9FA;
padding: 16rpx 24rpx;
border-radius: 8rpx;
margin-bottom: 20rpx;
}
.file-icon {
font-size: 40rpx;
margin-right: 16rpx;
}
.file-info {
flex: 1;
flex-direction: column;
}
.file-size {
font-size: 24rpx;
color: #999999;
margin-top: 4rpx;
}
.file-name {
font-size: 28rpx;
color: #333333;
lines: 1;
text-overflow: ellipsis;
}
.file-delete {
width: 40rpx;
height: 40rpx;
align-items: center;
justify-content: center;
margin-left: 12rpx;
}
.delete-icon {
color: #999999;
font-size: 36rpx;
line-height: 1;
}
/* 底部提示 */
.tips-area {
padding: 0 20rpx 40rpx 20rpx;
}
.tip-item {
flex-direction: row;
margin-bottom: 12rpx;
}
.tip-dot {
color: #999999;
margin-right: 12rpx;
font-size: 24rpx;
}
.tip-text {
color: #999999;
font-size: 24rpx;
flex: 1;
}
/* 底部安全距离 */
.safe-bottom-area {
height: 20rpx;
}
/* 底部固定按钮 */
.bottom-bar {
z-index: 100;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #FFFFFF;
padding: 20rpx 30rpx 60rpx 30rpx; /* 底部留白适配全面屏 */
box-shadow: 0rpx -4rpx 20rpx rgba(0, 0, 0, 0.05);
box-sizing: border-box;
}
.submit-btn {
background-color: #4A90E2;
color: #FFFFFF;
font-size: 32rpx;
border-radius: 25rpx;
height: 88rpx;
line-height: 88rpx;
width: 700rpx;
height: 88rpx;
}
.submit-btn:active {
background-color: #3A7CC8;
}
/* 修正 uni-datetime-picker 在部分环境下的弹窗层级问题 */
.picker-container {
width: 100%;
}
:deep(.uni-datetime-picker) {
width: 100%;
}
:deep(.uni-date-x) {
background-color: transparent;
}
</style>
- 发布:2026-04-28 11:17
- 更新:2026-04-28 11:28
- 阅读:139
产品分类: uniapp/小程序/微信
PC开发环境操作系统: Mac
PC开发环境操作系统版本号: 15.7.2
HBuilderX类型: 正式
HBuilderX版本号: 5.06
第三方开发者工具版本号: 2.01.2510290
基础库版本号: 3.15.1
项目创建方式: HBuilderX
示例代码:
操作步骤:
输入框多聚焦几次就会出现该问题
输入框多聚焦几次就会出现该问题
预期结果:
输入框多聚焦几次就会出现页面滚动到其他地方
输入框多聚焦几次就会出现页面滚动到其他地方
实际结果:
输入框多聚焦几次就会出现页面滚动到其他地方
输入框多聚焦几次就会出现页面滚动到其他地方
bug描述:
长表单页面输入框聚焦会导致页面滚动到其他地方
使用的uni-app x
adjustPosition 设置为 false 无效
1***@qq.com (作者)
微信原生给输入框加上adjust-position="{{false}}" 可以解决 ,在 uniapp 中加上无效
2026-04-28 13:09