Files
property-resident-side/property-uniapp-project/pageSubPack/online-repair/personalRepair.vue

613 lines
16 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 projectId = ref('') // ✅ 修复新增项目ID
// 表单数据
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: '08' }, { time: '09' }, { time: '10' }, { time: '11' },
{ time: '13' }, { time: '14' }, { time: '15' }, { time: '16' },
{ time: '17' }, { time: '18' }
])
const minuteList = ref([
{ minute: '00' },
{ minute: '30' }
])
// ========== 选中状态 ==========
const selectedDate = ref('')
const selectedHour = ref('')
const selectedMinute = ref('')
// ========== 滚动配置 ==========
const itemHeight = 80
const dateScrollTop = ref(0)
const hourScrollTop = ref(1)
const minuteScrollTop = ref(1)
// ========== 显示文本 ==========
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()
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 dateIdx = dateList.value.findIndex(i => i.value === selectedDate.value)
const hourIdx = hourList.value.findIndex(i => i.time === selectedHour.value)
const minIdx = minuteList.value.findIndex(i => i.minute === selectedMinute.value)
dateScrollTop.value = dateIdx * itemHeight
hourScrollTop.value = hourIdx * itemHeight
minuteScrollTop.value = minIdx * 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 idx = Math.round(e.detail.scrollTop / itemHeight)
if (idx >= 0 && idx < dateList.value.length) {
selectedDate.value = dateList.value[idx].value
dateScrollTop.value = idx * itemHeight
updateTempTime()
}
}
const onHourScroll = (e) => {
const idx = Math.round(e.detail.scrollTop / itemHeight)
if (idx >= 0 && idx < hourList.value.length) {
selectedHour.value = hourList.value[idx].time
hourScrollTop.value = idx * itemHeight
updateTempTime()
}
}
const onMinuteScroll = (e) => {
const idx = Math.round(e.detail.scrollTop / itemHeight)
if (idx >= 0 && idx < minuteList.value.length) {
selectedMinute.value = minuteList.value[idx].minute
minuteScrollTop.value = idx * itemHeight
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
projectId.value = data.id // 接收项目ID
})
})
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 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 urls = []
for (const path of form.value.images) {
if (path.startsWith('http')) {
urls.push(path)
continue
}
try {
const res = await uploadFile(path)
urls.push(res.data || res.msg || '')
} catch (e) {
console.error('上传失败', e)
}
}
return urls
}
// ========== 表单提交 ✅ 修复:所有错误都解决 ==========
const submitForm = async () => {
if (!houseName.value) return uni.showToast({ title: '请选择报修房屋', icon: 'none' })
if (!projectName.value) return uni.showToast({ title: '请选择维修项目', icon: 'none' })
if (!form.value.title) return uni.showToast({ title: '请输入标题', icon: 'none' })
if (!form.value.desc) return uni.showToast({ title: '请输入问题描述', icon: 'none' })
if (!/^1[3-9]\d{9}$/.test(form.value.phone)) return uni.showToast({ title: '手机号不正确', icon: 'none' })
if (!form.value.datetime) return uni.showToast({ title: '请选择预约时间', icon: 'none' })
const villageId = uni.getStorageSync('currentVillageId')
if (!villageId || villageId === '0') return uni.showToast({ title: '请先选择小区', icon: 'none' })
const userId = uni.getStorageSync('userId')
if (!userId) return uni.showToast({ title: '用户信息失效', icon: 'none' })
uni.showLoading({ title: '提交中...' })
try {
const imageUrls = await uploadImages()
// 参数
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' })
// const tempProjectId = projectId.value
// 清空表单
form.value = { title: '', desc: '', phone: '', datetime: '', images: [] }
houseName.value = ''
houseId.value = ''
projectName.value = ''
projectId.value = ''
setTimeout(() => {
uni.navigateTo({
url:'/pageSubPack/online-repair/onlineRepair'
})
// uni.navigateTo({ url: `/pageSubPack/online-repair/onlineRepair?maintenanceProjectId=${tempProjectId}` })
}, 1500)
} else {
uni.showToast({ title: res.msg || '提交失败', icon: 'none' })
}
} catch (e) {
console.error('提交异常:', 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>