Files
property-resident-side/property-uniapp-project/pageSubPack/online-repair/personalRepair.vue
2026-04-29 17:39:55 +08:00

692 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="form-container">
<!-- 报修房屋 -->
<view class="form-item">
<text class="label">报修房屋</text>
<view class="value" @click="goToSelectHouse">
{{ houseName || '请选择房屋' }}
<!-- <text class="switch" v-if="houseName">切换</text> -->
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
</view>
<!-- 维修项目 -->
<view class="form-item">
<text class="label">维修项目</text>
<view class="value" @click="goToSelectProject">
{{ projectName || '请选择维修项目' }}
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
</view>
<!-- 标题 -->
<view class="form-item">
<text class="label">标题</text>
<uni-easyinput v-model="form.title" placeholder="请输入内容" :inputBorder="false"></uni-easyinput>
</view>
<!-- 问题描述 -->
<view class="form-item-wenti">
<text class="label">问题描述</text>
<uni-easyinput v-model="form.desc" placeholder="请描述你的问题" type="textarea"></uni-easyinput>
</view>
<!-- 手机号 -->
<view class="form-item">
<text class="label">手机号</text>
<uni-easyinput type="number" v-model="form.phone" placeholder="请输入手机号" :inputBorder="false"></uni-easyinput>
</view>
<!-- 预约时间 -->
<view class="form-item time-item" @click="openTimePopup">
<text class="label">预约时间</text>
<text class="value time-value">{{ showTimeText }}</text>
</view>
<!-- <view class="form-item">
<text class="label">预约时间</text>
<view class="value" @click="">
<uni-datetime-picker type="datetime" v-model="form.datetime" @change="changeTime" />
</view>
</view> -->
<!-- 问题照片 -->
<view class="form-item-wenti">
<text class="label">问题照片</text>
<view class="upload-wrap">
<!-- 已上传图片 -->
<view class="upload-item" v-for="(img, index) in form.images" :key="index">
<image :src="img" mode="aspectFill" @click="previewImage(img)"></image>
<uni-icons type="close" size="16" @click="deleteImage(index)"></uni-icons>
</view>
<!-- 上传按钮 -->
<view class="upload-btn" @click="chooseImage">
<uni-icons type="plus" size="24"></uni-icons>
<text>上传照片</text>
</view>
</view>
</view>
<!-- 提交按钮 -->
<button class="submit-btn" @click="submitForm">提交</button>
<!-- 时间选择弹窗 -->
<uni-popup ref="timePopupRef" type="bottom" :mask-click="false" border-radius="16rpx">
<view class="time-popup-container">
<!-- 弹窗头部取消 + 标题 + 确认 -->
<view class="time-popup-header">
<text class="cancel-btn" @click="closeTimePopup">取消</text>
<text class="popup-title">预约时间({{ tempTime.format('YYYY-MM-DD HH:mm') }})</text>
<text class="confirm-btn" @click="confirmTime">确认</text>
</view>
<!-- 时间选择列日期 | 小时 | 分钟 -->
<view class="time-selector">
<!-- 日期列 -->
<scroll-view
class="time-column"
scroll-y
:scroll-top="dateScrollTop"
@scroll="onDateScroll"
scroll-with-animation
>
<view
class="time-item"
:class="{ active: dateItem.value === selectedDate }"
v-for="(dateItem, index) in dateList"
:key="index"
@click="selectDate(index)"
>
{{ dateItem.value }}{{dateItem.label}}
</view>
</scroll-view>
<!-- 小时列 -->
<scroll-view
class="time-column"
scroll-y
:scroll-top="hourScrollTop"
@scroll="onHourScroll"
scroll-with-animation
>
<view
class="time-item"
:class="{ active: hourItem.time === selectedHour }"
v-for="(hourItem, index) in hourList"
:key="index"
@click="selectHour(index)"
>
{{ hourItem.time }}
</view>
</scroll-view>
<!-- 分钟列 -->
<scroll-view
class="time-column"
scroll-y
:scroll-top="minuteScrollTop"
@scroll="onMinuteScroll"
scroll-with-animation
>
<view
class="time-item"
:class="{ active: minuteItem.minute === selectedMinute }"
v-for="(minuteItem, index) in minuteList"
:key="index"
@click="selectMinute(index)"
>
{{ minuteItem.minute }}
</view>
</scroll-view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref,computed,onMounted} from 'vue'
import {
onLoad, onUnload
} from '@dcloudio/uni-app'
import { addQueryRepair, uploadFile } from '../api/api.js'
import moment from 'moment/moment'
// 选中的房屋/项目名称
const houseName = ref('')
const houseId = ref('')
const projectName = ref('')
// 表单数据
const form = ref({
title: '',
desc: '',
phone: '',
datetime: '', // 默认时间
images: []
})
// ========== 基础数据 ==========
const timePopupRef = ref(null)
// 初始时间()
const selectedTime = ref(moment().hour(9).minute(30).second(0));
// 选择的时间
const tempTime = ref(moment().hour(9).minute(30).second(0));
// ========== 时间选择列数据 ==========
// 日期列表(今天、明天、后天)
const dateList = ref([
{ label: '(今天)', value: moment().format('YYYY-MM-DD') },
{ label: '(明天)', value: moment().add(1, 'day').format('YYYY-MM-DD') },
{ label: '(后天)', value: moment().add(2, 'day').format('YYYY-MM-DD') }
]);
// 小时列表()
const hourList = ref([
{time: moment().hours(8).format('HH')},
{time:moment().hours(9).format('HH')},
{time:moment().hours(10).format('HH')},
{time:moment().hours(11).format('HH')},
{time:moment().hours(13).format('HH')},
{time:moment().hours(14).format('HH')},
{time:moment().hours(15).format('HH')},
{time:moment().hours(16).format('HH')},
{time:moment().hours(17).format('HH')},
{time:moment().hours(18).format('HH')},
]);
// 分钟列表()
const minuteList = ref([
{minute:moment().minute(0).format('mm')},
{minute:moment().minute(30).format('mm')}
]);
// ========== 选中状态 ==========
const selectedDate = ref(dateList.value[0].value);
const selectedHour = ref(hourList.value[1].time); // 默认选中8点
const selectedMinute = ref(minuteList.value[1].minute); // 默认选中00分
// ========== 滚动配置 ==========
const itemHeight = 80; // 每个时间项的高度rpx
const dateScrollTop = ref(0 * itemHeight); // 初始选中明天
const hourScrollTop = ref(1 * itemHeight); // 初始选中9点
const minuteScrollTop = ref(1 * itemHeight); // 初始选中00分
// ========== 显示文本 ==========
const showTimeText = computed(() => {
return selectedTime.value.format('YYYY-MM-DD HH:mm');
});
// ========== Popup 操作 ==========
// 打开时间弹窗
const openTimePopup = () => {
// 打开前同步临时时间为当前选中时间
tempTime.value = moment(selectedTime.value);
selectedDate.value = tempTime.value.format('YYYY-MM-DD');
selectedHour.value = tempTime.value.format('HH');
selectedMinute.value = tempTime.value.format('mm');
// 同步滚动位置
syncScrollPosition();
// 打开uni-popup
if(timePopupRef.value){
timePopupRef.value.open();
}
};
// 关闭时间弹窗
const closeTimePopup = () => {
timePopupRef.value.close();
};
// 确认选择时间
const confirmTime = () => {
// 拼接选中的时间
const selectedDateTime = `${selectedDate.value} ${selectedHour.value}:${selectedMinute.value}`;
selectedTime.value = moment(selectedDateTime, 'YYYY-MM-DD HH:mm');
// 给表单赋值
form.value.datetime = selectedTime.value.format('YYYY-MM-DD HH:mm')
// 关闭弹窗
closeTimePopup();
// 提示
uni.showToast({
title: `已选择 ${selectedTime.value.format('MM月DD日 HH:mm')}`,
icon: 'none'
});
};
// ========== 滚动/点击选择逻辑 ==========
// 同步滚动位置
const syncScrollPosition = () => {
// 日期滚动位置
const dateIndex = dateList.value.findIndex(item => item.value === selectedDate.value);
dateScrollTop.value = dateIndex * itemHeight;
// 小时滚动位置
const hourIndex = hourList.value.findIndex(item => item.time === selectedHour.value);
hourScrollTop.value = hourIndex * itemHeight;
// 分钟滚动位置
const minuteIndex = minuteList.value.findIndex(item => item.minute === selectedMinute.value);
minuteScrollTop.value = minuteIndex * itemHeight;
};
// 选择日期(点击)
const selectDate = (index) => {
selectedDate.value = dateList.value[index].value;
dateScrollTop.value = index * itemHeight;
updateTempTime();
};
// 选择小时(点击)
const selectHour = (index) => {
selectedHour.value = hourList.value[index].time;
hourScrollTop.value = index * itemHeight;
updateTempTime();
};
// 选择分钟(点击)
const selectMinute = (index) => {
selectedMinute.value = minuteList.value[index].minute;
minuteScrollTop.value = index * itemHeight;
updateTempTime();
};
// 滚动日期列(滑动)
const onDateScroll = (e) => {
const index = Math.round(e.detail.scrollTop / itemHeight);
if (index >= 0 && index < dateList.value.length) {
selectedDate.value = dateList.value[index].value;
updateTempTime();
}
};
// 滚动小时列(滑动)
const onHourScroll = (e) => {
const index = Math.round(e.detail.scrollTop / itemHeight);
if (index >= 0 && index < hourList.value.length) {
selectedHour.value = hourList.value[index].time;
updateTempTime();
}
};
// 滚动分钟列(滑动)
const onMinuteScroll = (e) => {
const index = Math.round(e.detail.scrollTop / itemHeight);
minuteScrollTop.value = index * itemHeight;
if (index >= 0 && index < minuteList.value.length) {
selectedMinute.value = minuteList.value[index].minute;
updateTempTime();
}
};
// 更新临时时间(弹窗预览)
const updateTempTime = () => {
tempTime.value = moment(`${selectedDate.value} ${selectedHour.value}:${selectedMinute.value}`, 'YYYY-MM-DD HH:mm');
};
// ========== 初始化 ==========
onMounted(() => {
syncScrollPosition();
});
// 页面加载时监听事件
onLoad(() => {
// 监听选择房屋事件
uni.$on('selectHouse', (data) => {
houseName.value = data.name
houseId.value = data.id
})
// 监听选择维修项目事件
uni.$on('selectProject', (data) => {
projectName.value = data.name
})
})
// 页面销毁移除监听
onUnload(() => {
uni.$off('selectHouse')
uni.$off('selectProject')
})
// 跳转到选择房屋页
const goToSelectHouse = () => {
uni.navigateTo({
url: `/pageSubPack/online-repair/selectHouse?houseId=${houseId.value}`
})
}
// 跳转到选择维修项目页
const goToSelectProject = () => {
uni.navigateTo({
url: '/pageSubPack/online-repair/personalSelectPoject'
})
}
// 选择预约时间
const changeTime = () =>{
}
// const chooseDateTime = () => {
// uni.datePicker({
// type: 'datetime',
// value: form.value.datetime,
// success: (res) => {
// form.value.datetime = res.value
// }
// })
// }
// 选择图片
const chooseImage = () => {
uni.chooseImage({
count: 3,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
form.value.images = [...form.value.images, ...res.tempFilePaths]
}
})
}
// 预览图片
const previewImage = (url) => {
uni.previewImage({
urls: form.value.images,
current: url
})
}
// 删除图片
const deleteImage = (index) => {
form.value.images.splice(index, 1)
}
// 上传图片到服务器
const uploadImages = async () => {
const uploadedUrls = []
for (const path of form.value.images) {
if (path.startsWith('http')) {
uploadedUrls.push(path)
continue
}
try {
const res = await uploadFile(path)
uploadedUrls.push(res.data || res.msg || '')
} catch (e) {
console.error('图片上传失败', e)
}
}
return uploadedUrls
}
// 表单提交
const submitForm = async () => {
// 校验
if (!houseName.value) {
uni.showToast({ title: '请选择报修房屋', icon: 'none' })
return
}
if (!projectName.value) {
uni.showToast({ title: '请选择维修项目', icon: 'none' })
return
}
if (!form.value.title) {
uni.showToast({ title: '请输入问题标题', icon: 'none' })
return
}
if (!form.value.desc) {
uni.showToast({ title: '请输入问题描述', icon: 'none' })
return
}
if (!/^1[3-9]\d{9}$/.test(form.value.phone)) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
return
}
if (!form.value.datetime) {
uni.showToast({ title: '请选择预约时间', icon: 'none' })
return
}
const villageId = uni.getStorageSync('currentVillageId')
if (!villageId || villageId === '0') {
uni.showToast({ title: '请先选择小区', icon: 'none' })
return
}
uni.showLoading({ title: '提交中...' })
try {
const imageUrls = await uploadImages()
const userId = uni.getStorageSync('userId')
const params = {
villageId,
houseId: houseId.value,
maintenanceItemId: projectId.value,
title: form.value.title,
problem: form.value.desc,
residentId: userId,
orderDate: form.value.datetime,
phone: form.value.phone,
problemImageUrl: imageUrls.join(','),
projectType: '1',
problemStatus: ''
}
const res = await addQueryRepair(params)
if (res.code === 200) {
uni.showToast({ title: '提交成功', icon: 'success' })
form.value = { title: '', desc: '', phone: '', datetime: '', images: [] }
houseName.value = ''
projectName.value = ''
uni.navigateBack({ delta: 1 })
} else {
uni.showToast({ title: res.msg || '提交失败', icon: 'none' })
}
} catch (e) {
uni.showToast({ title: '网络异常', icon: 'none' })
} finally {
uni.hideLoading()
}
}
</script>
<style scoped>
.form-container {
padding: 15px;
background: #fff;
min-height: 100vh;
}
.form-item {
display: flex;
margin-bottom: 30rpx;
align-items: flex-start;
border-bottom: 1px solid #eee;
}
.label {
width: 140rpx;
font-size: 28rpx;
color: #333;
line-height: 80rpx;
}
.value {
flex: 1;
font-size: 28rpx;
color: #666;
line-height: 80rpx;
text-align: right;
display: flex;
justify-content: space-between;
}
.time-value {
color: #000;
}
.input, .textarea {
flex: 1;
padding: 20rpx;
font-size: 28rpx;
border: 1px solid #eee;
border-radius: 8rpx;
}
.textarea {
min-height: 160rpx;
line-height: 40rpx;
}
.form-item-wenti {
margin-bottom: 20px;
/* border-bottom: 1px solid #eee; */
padding-bottom: 10px;
}
/* .label {
font-size: 14px;
color: #333;
margin-bottom: 8px;
display: block;
}
.value {
font-size: 14px;
color: #666;
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
} */
.switch {
font-size: 12px;
color: #007aff;
margin-right: 5px;
}
input, textarea {
font-size: 14px;
width: 100%;
padding: 8px 0;
box-sizing: border-box;
}
textarea {
resize: none;
}
.upload-wrap {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 8px;
}
.upload-item {
position: relative;
width: 80px;
height: 80px;
}
.upload-item image {
width: 100%;
height: 100%;
border-radius: 4px;
}
.upload-item uni-icons {
position: absolute;
top: -5px;
right: -5px;
background: #ff3b30;
color: #fff;
border-radius: 50%;
padding: 2px;
}
.upload-btn {
width: 80px;
height: 80px;
border: 1px dashed #ccc;
border-radius: 4px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #999;
font-size: 12px;
}
.submit-btn {
width: 100%;
background: #007aff;
color: #fff;
border-radius: 8px;
height: 44px;
line-height: 44px;
font-size: 16px;
margin-top: 20px;
}
/* ========== uni-popup 时间选择器样式 ========== */
.time-popup-container {
background-color: #fff;
border-top-left-radius: 16rpx;
border-top-right-radius: 16rpx;
}
/* 弹窗头部 */
.time-popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
border-bottom: 1px solid #eee;
}
.cancel-btn {
font-size: 28rpx;
color: #666;
}
.popup-title {
font-size: 28rpx;
color: #333;
}
.confirm-btn {
font-size: 28rpx;
color: #007aff;
font-weight: 600;
}
/* 时间选择器容器 */
.time-selector {
display: flex;
height: 400rpx;
padding: 20rpx 0;
}
/* 单个时间列 */
.time-column {
flex: 1;
height: 100%;
text-align: center;
}
/* 时间项 */
.time-item {
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
color: #666;
position: relative;
}
/* 选中项样式(高亮) */
.time-item.active {
color: #007aff;
font-weight: 600;
background-color: #d0d0d0;
}
.time-item.active::after {
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
/* width: 80%; */
/* height: 1px; */
background-color: #d0d0d0;
}
</style>