fjrcloud-community-app/pages/index/auth-form.vue

756 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!-- 业主认证表单页 -->
<template>
<s-layout title="业主认证">
<view class="auth-page">
<scroll-view scroll-y class="form-scroll">
<!-- 小区名称 -->
<view class="form-item">
<text class="form-label">小区名称</text>
<view class="form-value" @tap="showCommunityPicker">
<text class="value-text">{{ state.form.communityName || '请选择小区' }}</text>
<image class="arrow-icon" src="/static/img/right-icon-black.png" mode="aspectFit" />
</view>
</view>
<!-- 序号楼号/单元/ -->
<view class="form-item form-row" @tap="showHousePicker">
<text class="form-label">序号</text>
<view class="row-inputs">
<view class="col-item">
<input class="col-input" v-model="state.form.buildingNo" placeholder="请选择" disabled />
<text class="col-label">号楼</text>
</view>
<view class="col-item">
<input class="col-input" v-model="state.form.unitNo" placeholder="请选择" disabled />
<text class="col-label">单元</text>
</view>
<view class="col-item">
<input class="col-input" v-model="state.form.roomNo" placeholder="请选择" disabled />
<text class="col-label">号</text>
</view>
</view>
</view>
<!-- 是否产权人 -->
<view class="form-item">
<text class="form-label">是否产权人</text>
<view class="checkbox-group">
<label class="checkbox-item" @tap="state.form.isOwner = true">
<view :class="['checkbox-box', { active: state.form.isOwner }]">
<text v-if="state.form.isOwner" class="check-mark">✓</text>
</view>
<text class="checkbox-text">是</text>
</label>
<label class="checkbox-item" @tap="state.form.isOwner = false">
<view :class="['checkbox-box', { active: !state.form.isOwner && state.form.isOwner !== null }]">
<text v-if="!state.form.isOwner && state.form.isOwner !== null" class="check-mark">✓</text>
</view>
<text class="checkbox-text">否</text>
</label>
</view>
</view>
<!-- 与产权人关系 -->
<view class="form-item">
<text class="form-label">与产权人关系</text>
<view class="form-value" @tap="showRelationPicker">
<text class="value-text placeholder-color">{{ state.form.relationType || '请输入' }}</text>
<image class="arrow-icon" src="/static/img/right-icon-black.png" mode="aspectFit" />
</view>
</view>
<!-- 姓名 -->
<view class="form-item">
<text class="form-label">姓名</text>
<view class="form-value">
<input
v-model="state.form.name"
class="form-input"
placeholder="请输入姓名"
placeholder-class="placeholder"
placeholder-style="color: #333333"
/>
<image class="arrow-icon" src="/static/img/right-icon-black.png" mode="aspectFit" />
</view>
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="form-label">手机号</text>
<view class="form-value">
<input
v-model="state.form.mobile"
class="form-input"
type="number"
maxlength="11"
placeholder="请输入手机号"
placeholder-class="placeholder"
placeholder-style="color: #333333"
/>
<image class="arrow-icon" src="/static/img/right-icon-black.png" mode="aspectFit" />
</view>
</view>
<!-- 证件类型 -->
<view class="form-item">
<text class="form-label">证件类型</text>
<view class="form-value" @tap="showIdTypePicker">
<text class="value-text placeholder-color">{{ state.form.idType || '请选择证件类型' }}</text>
<image class="arrow-icon" src="/static/img/right-icon-black.png" mode="aspectFit" />
</view>
</view>
<!-- 证件号 -->
<view class="form-item">
<text class="form-label">证件号</text>
<view class="form-value">
<input
v-model="state.form.idNumber"
class="form-input"
placeholder="请输入证件号"
placeholder-class="placeholder"
placeholder-style="color: #333333"
/>
<image class="arrow-icon" src="/static/img/right-icon-black.png" mode="aspectFit" />
</view>
</view>
<!-- 性别 -->
<view class="form-item">
<text class="form-label">性别</text>
<view class="form-value" @tap="showGenderPicker">
<text class="value-text placeholder-color">{{ state.form.sex === 1 ? '男' : state.form.sex === 2 ? '女' : '请选择' }}</text>
<image class="arrow-icon" src="/static/img/right-icon-black.png" mode="aspectFit" />
</view>
</view>
<!-- 出生日期 -->
<view class="form-item">
<text class="form-label">出生日期</text>
<view class="form-value" @click="triggerDatePicker">
<uni-datetime-picker
ref="datePickerRef"
v-model="state.form.birthday"
type="date"
:border="false"
@change="onDateChange"
class="form-date"
/>
<image class="arrow-icon" src="/static/img/right-icon-black.png" mode="aspectFit" />
</view>
</view>
<!-- 上传附件 -->
<view class="form-section-title">
<text>上传附件</text>
</view>
<view class="upload-area">
<view v-if="state.attachmentUrl" class="upload-preview" @tap="handleUploadAttachment">
<image class="preview-img" :src="state.attachmentUrl" mode="aspectFill" />
</view>
<view v-else class="upload-btn" @tap="handleUploadAttachment">
<view class="upload-icon-wrapper">
<text class="upload-folder-icon">📁</text>
</view>
<text class="upload-text">选择文件</text>
</view>
</view>
<!-- 上传人脸照片 -->
<view class="form-section-title">
<text>上传人脸照片</text>
</view>
<view class="upload-area">
<view v-if="state.facePhotoUrl" class="upload-preview" @tap="handleUploadFace">
<image class="preview-img" :src="state.facePhotoUrl" mode="aspectFill" />
</view>
<view v-else class="upload-btn" @tap="handleUploadFace">
<view class="upload-icon-wrapper">
<text class="upload-folder-icon">📁</text>
</view>
<text class="upload-text">选择照片</text>
</view>
</view>
</scroll-view>
<!-- 底部按钮 -->
<view class="bottom-btn-wrapper">
<button class="submit-btn" @tap="handleSubmit"></button>
</view>
</view>
</s-layout>
</template>
<script setup>
import { reactive, ref, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import MemberHouseApi from '@/sheep/api/community/memberHouse';
import CommunityApi from '@/sheep/api/community/community';
import FileApi from '@/sheep/api/infra/file';
import { tenantId } from '@/sheep/config';
import sheep from '@/sheep';
// 用户信息
const userStore = sheep.$store('user');
const userInfo = computed(() => userStore.userInfo || {});
// 表单数据
const state = reactive({
form: {
communityId: null, // 选中的小区ID
communityName: '',
houseId: null, // 选中的房屋ID
buildingNo: '',
unitNo: '',
roomNo: '',
isOwner: true,
relationType: '',
name: '',
mobile: '',
idType: '',
idNumber: '',
sex: '',
birthday: ''
},
communityList: [], // 小区列表
houseTree: [], // 房屋树(楼号->单元->房号)
attachmentUrl: '', // 附件URL
facePhotoUrl: '', // 人脸照片URL
});
onLoad(async () => {
// 获取小区列表
const { code, data } = await CommunityApi.getSimpleList();
if (code === 0 && data && data.length > 0) {
state.communityList = data;
// 优先匹配 SHOPRO_TENANT_ID 配置的小区,没有则选第一个
const target = data.find((item) => String(item.id) === String(tenantId)) || data[0];
state.form.communityId = target.id;
state.form.communityName = target.communityName;
// 加载该小区的房屋树
await loadHouseTree(target.id);
} else {
// 兜底:使用当前用户信息里的小区
state.form.communityName = userInfo.value.currentCommunityName || '请选择小区';
}
});
// 加载房屋树
const loadHouseTree = async (communityId) => {
if (!communityId) return;
const { code, data } = await CommunityApi.getHouseTree(communityId);
if (code === 0) {
state.houseTree = data || [];
}
};
// 显示小区选择器
const showCommunityPicker = () => {
if (!state.communityList.length) return;
const itemList = state.communityList.map((item) => item.communityName);
uni.showActionSheet({
itemList,
success: async (res) => {
const selected = state.communityList[res.tapIndex];
state.form.communityId = selected.id;
state.form.communityName = selected.communityName;
// 切换小区后加载对应房屋树
await loadHouseTree(selected.id);
// 清空已选房屋
state.form.houseId = null;
state.form.buildingNo = '';
state.form.unitNo = '';
state.form.roomNo = '';
}
});
};
// 级联选择房屋(楼号 -> 单元 -> 房号)
const showHousePicker = () => {
if (!state.houseTree.length) {
uni.showToast({ title: '该小区暂无房屋数据', icon: 'none' });
return;
}
// 第一级:选择楼号
const buildings = state.houseTree;
const buildingLabels = buildings.map((item) => item.label);
uni.showActionSheet({
itemList: buildingLabels,
success: (buildingRes) => {
const building = buildings[buildingRes.tapIndex];
// 第二级:选择单元
const units = building.children || [];
if (!units.length) {
state.form.houseId = building.value;
state.form.buildingNo = building.label;
state.form.unitNo = '';
state.form.roomNo = '';
return;
}
const unitLabels = units.map((item) => item.label);
uni.showActionSheet({
itemList: unitLabels,
success: (unitRes) => {
const unit = units[unitRes.tapIndex];
// 第三级:选择房号
const rooms = unit.children || [];
if (!rooms.length) {
state.form.houseId = unit.value;
state.form.buildingNo = building.label;
state.form.unitNo = unit.label;
state.form.roomNo = '';
return;
}
const roomLabels = rooms.map((item) => item.label);
uni.showActionSheet({
itemList: roomLabels,
success: (roomRes) => {
const room = rooms[roomRes.tapIndex];
state.form.houseId = room.value;
state.form.buildingNo = building.label;
state.form.unitNo = unit.label;
state.form.roomNo = room.label;
}
});
}
});
}
});
};
// 显示关系选择器
const showRelationPicker = () => {
uni.showActionSheet({
itemList: ['本人', '配偶', '父母', '子女', '其他'],
success: (res) => {
const relations = ['本人', '配偶', '父母', '子女', '其他'];
state.form.relationType = relations[res.tapIndex];
}
});
};
// 显示证件类型选择器
const showIdTypePicker = () => {
uni.showActionSheet({
itemList: ['身份证', '护照', '军官证', '港澳通行证'],
success: (res) => {
const types = ['身份证', '护照', '军官证', '港澳通行证'];
state.form.idType = types[res.tapIndex];
}
});
};
// 显示性别选择器
const showGenderPicker = () => {
uni.showActionSheet({
itemList: ['男', '女'],
success: (res) => {
state.form.sex = res.tapIndex === 0 ? 1 : 2;
}
});
};
// 日期选择回调
const onDateChange = (e) => {
console.log('选择的日期:', e);
};
// 点击整行触发日期选择器
const datePickerRef = ref(null);
const triggerDatePicker = () => {
if (datePickerRef.value) {
const picker = datePickerRef.value.$children?.[0] || datePickerRef.value;
if (picker.show) {
picker.show();
}
}
};
// 上传图片通用方法
const uploadImage = (field) => {
uni.chooseImage({
count: 1,
sourceType: ['album', 'camera'],
success: async (res) => {
const filePath = res.tempFilePaths[0];
const result = await FileApi.uploadFile(filePath, 'member-house');
if (result && result.data) {
state[field] = result.data;
uni.showToast({ title: '上传成功', icon: 'success' });
} else {
uni.showToast({ title: '上传失败', icon: 'none' });
}
}
});
};
// 上传附件
const handleUploadAttachment = () => uploadImage('attachmentUrl');
// 上传人脸照片
const handleUploadFace = () => uploadImage('facePhotoUrl');
// 表单校验
const validateForm = () => {
const f = state.form;
if (!f.communityName) return '请选择小区';
if (!f.buildingNo) return '请输入楼号';
if (!f.unitNo) return '请输入单元号';
if (!f.roomNo) return '请输入门牌号';
if (f.isOwner === null) return '请选择是否产权人';
if (!f.isOwner && !f.relationType) return '请选择与产权人关系';
if (!f.name) return '请输入姓名';
if (!f.mobile) return '请输入手机号';
if (!/^1[3-9]\d{9}$/.test(f.mobile)) return '手机号格式不正确';
if (!f.idType) return '请选择证件类型';
if (!f.idNumber) return '请输入证件号';
if (!f.sex) return '请选择性别';
if (!f.birthday) return '请选择出生日期';
return '';
};
// 提交表单
const handleSubmit = async () => {
const errMsg = validateForm();
if (errMsg) {
uni.showToast({ title: errMsg, icon: 'none' });
return;
}
const params = {
communityId: state.form.communityId || userInfo.value.currentCommunityId || '',
communityName: state.form.communityName,
houseId: state.form.houseId || null,
buildingNo: state.form.buildingNo,
unitNo: state.form.unitNo,
roomNo: state.form.roomNo,
isOwner: state.form.isOwner,
relationType: state.form.relationType,
name: state.form.name,
mobile: state.form.mobile,
idType: state.form.idType,
idNumber: state.form.idNumber,
sex: state.form.sex,
birthday: state.form.birthday,
attachmentUrl: state.attachmentUrl,
facePhotoUrl: state.facePhotoUrl,
};
const { code } = await MemberHouseApi.create(params);
if (code === 0) {
uni.redirectTo({ url: '/pages/index/auth-success' });
}
};
</script>
<style lang="scss" scoped>
/* 页面容器 */
.auth-page {
background-color: #F5F5F5;
}
/* 表单滚动区域 */
.form-scroll {
height: calc(100vh - 176rpx - 250rpx);
padding-top: 20rpx;
}
/* 表单项 - 上下结构 */
.form-item {
background-color: #FFFFFF;
padding: 24rpx 32rpx;
display: flex;
flex-direction: column;
border-bottom: 1rpx solid #F0F0F0;
}
/* 标签文字 - 上方 */
.form-label {
font-size: 28rpx;
color: #333333;
font-weight: 500;
margin-bottom: 16rpx;
}
/* 值区域 - 下方 */
.form-value {
display: flex;
align-items: center;
.value-text {
font-size: 28rpx;
flex: 1;
// 已选中值:深色加粗
&:not(.placeholder-color) {
color: #222222;
font-weight: 600;
}
}
.placeholder-color {
color: #666666;
}
.form-input {
flex: 1;
font-size: 28rpx;
color: #666666;
}
.form-date{
width:100%;
}
.arrow-icon, .calendar-icon {
width: 24rpx;
height: 24rpx;
opacity: 0.4;
}
.calendar-icon {
opacity: 0.6;
}
/* 右侧箭头图标(日期字段用) */
.date-arrow-icon {
width: 24rpx;
height: 24rpx;
opacity: 0.4;
flex-shrink: 0;
margin-left: 8rpx;
}
}
/* 占位符样式 */
:deep(.placeholder) {
color: #666666 !important;
}
/* 日期选择器 - 融入表单整体风格 + 橙色主题 */
.form-value {
:deep(.uni-date-editor--x) {
padding: 0 !important;
border: none !important;
background: transparent !important;
}
:deep(.uni-date-x) {
border: none !important;
justify-content: flex-start !important;
background: transparent !important;
padding: 0 !important;
}
:deep(.uni-date__x-input) {
font-size: 28rpx !important;
color: #333333 !important;
padding-left: 0 !important;
background: transparent !important;
&::placeholder {
color: #CCCCCC !important;
}
}
/* 隐藏日历图标、清除图标 */
:deep(.uni-icons),
:deep(.uni-date__icon-clear) {
display: none !important;
}
}
/* 全局覆盖 datetime-picker 弹窗橙色主题 */
:deep(.uni-datetime-picker--btn) {
background-color: #FF6B35 !important;
}
/* 选中日期 - 蓝色圆圈 → 橙色 */
:deep(.uni-calendar-item--checked) {
background-color: #FF6B35 !important;
border-color: #fff !important;
}
/* 今天标记点 - 红色 → 橙色 */
:deep(.uni-calendar-item--isDay) {
background-color: #FF6B35 !important;
}
/* 范围选中的起止日期 */
:deep(.uni-calendar-item--before-checked),
:deep(.uni-calendar-item--after-checked) {
background-color: #FF6B35 !important;
}
/* 序号行 - 三列:文字和输入框同行 */
.form-row {
.row-inputs {
display: flex;
align-items: center;
.col-item {
display: flex;
align-items: center;
margin-right: 24rpx;
flex:1;
&:last-child {
margin-right: 0;
}
.col-label {
font-size: 26rpx;
color: #666666;
margin-left: 8rpx;
}
.col-input {
font-size: 28rpx;
color: #333333;
width: 100rpx;
padding-bottom: 8rpx;
border-bottom: 1rpx solid #E5E5E5;
flex:1;
}
}
}
}
/* 单选 - checkbox方框样式 */
.checkbox-group {
display: flex;
align-items: center;
gap: 48rpx;
.checkbox-item {
display: flex;
align-items: center;
margin-right:80rpx;
.checkbox-box {
width: 36rpx;
height: 36rpx;
border-radius: 6rpx;
border: 2rpx solid #DDDDDD;
margin-right: 12rpx;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
&.active {
background-color: #FF7B54;
border-color: #FF7B54;
.check-mark {
color: #FFFFFF;
font-size: 24rpx;
line-height: 1;
}
}
}
.checkbox-text {
font-size: 28rpx;
color: #666666;
}
}
}
/* 区块标题 */
.form-section-title {
padding: 32rpx 32rpx 20rpx;
text {
font-size: 28rpx;
color: #333333;
font-weight: 500;
}
}
/* 上传区域 */
.upload-area {
background-color: #FFFFFF;
padding: 32rpx;
.upload-btn {
width: 180rpx;
height: 180rpx;
border: 2rpx dashed #DDDDDD;
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
&:active {
border-color: #FF7B54;
background-color: rgba(255, 123, 84, 0.02);
}
.upload-icon-wrapper {
margin-bottom: 12rpx;
.upload-folder-icon {
font-size: 56rpx;
opacity: 0.4;
}
}
.upload-text {
font-size: 24rpx;
color: #999999;
}
}
.upload-preview {
width: 180rpx;
height: 180rpx;
border-radius: 16rpx;
overflow: hidden;
.preview-img {
width: 100%;
height: 100%;
}
}
}
/* 底部按钮 */
.bottom-btn-wrapper {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 24rpx 38rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
background-color: #F5F5F5;
.submit-btn {
width: 100%;
height: 96rpx;
background: linear-gradient(135deg, #FF8A65 0%, #FF6B35 100%);
border-radius: 48rpx;
border: none;
font-size: 32rpx;
font-weight: 500;
color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.3);
&::after {
border: none;
}
&:active {
opacity: 0.9;
transform: scale(0.98);
}
}
}
</style>