Files
property-resident-side/property-uniapp-project/pageSubPack/service-list/addComplaints.vue
zhouyanli f74fb33e11 修复bug
2026-08-01 11:56:28 +08:00

564 lines
12 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="contentStyle">
<view class="status-bar"></view>
<uni-nav-bar title="投诉建议" backgroundColor="transparent" :border="false" left-icon="left" @clickLeft="goBack">
</uni-nav-bar>
<!-- 表单区域 -->
<scroll-view class="page" scroll-y>
<!-- 表单主体 -->
<view class="form-content">
<text class="form-tip">请填写反馈信息</text>
<!-- 1. 投诉类型点击弹出popup -->
<view class="form-row" @click="showTypePopup = true">
<view class="row-left">
<text class="required-star">*</text>
<text class="label-text">投诉类型</text>
</view>
<view class="row-right">
<text class="value-text" :class="{placeholderStyle:selectedType}">
{{ selectedType || '请选择投诉类型' }}
</text>
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
</view>
<!-- 2. 投诉物业选中物业服务时显示 -->
<view class="form-row" v-if="selectedType === '物业服务'" @click="showPropertyPopup = true">
<view class="row-left">
<text class="required-star">*</text>
<text class="label-text">投诉物业</text>
</view>
<view class="row-right">
<text class="value-text" :class="{placeholderStyle:selectedProperty}">
{{ selectedProperty || '请选择' }}
</text>
<uni-icons type="arrowright" size="16"></uni-icons>
<!-- <text class="arrow-icon"> > </text> -->
</view>
</view>
<!-- 3. 问题描述 -->
<view class="form-row desc-row" style="flex-direction: column;">
<view class="row-left">
<text class="required-star">*</text>
<text class="label-text">问题描述</text>
</view>
<view class="row-right desc-right">
<textarea v-model="description" placeholder="请描述您的问题" class="desc-input" auto-height maxlength="500"></textarea>
</view>
</view>
<!-- 4. 投诉人姓名 -->
<view class="form-row">
<view class="row-left">
<text class="label-text">投诉人姓名</text>
</view>
<view class="row-right">
<input v-model="name" placeholder="请输入姓名" class="text-input" type="text" />
</view>
</view>
<!-- 5. 手机号码 -->
<view class="form-row">
<view class="row-left">
<text class="label-text">手机号码</text>
</view>
<view class="row-right">
<input v-model="phone" placeholder="请输入手机号" class="text-input" type="number" maxlength="11" />
</view>
</view>
<!-- 6. 相关照片多图上传+删除 -->
<view class="form-row photo-row">
<view class="row-left">
<text class="label-text">相关照片</text>
</view>
<view class="row-right photo-right">
<view class="photo-grid">
<!-- 已上传图片 -->
<view v-for="(img, index) in tempFilePaths" :key="index" class="photo-item">
<image :src="img" mode="aspectFill" class="photo-img"></image>
<view class="delete-btn" @click="deletePhoto(index)">
<text class="delete-icon">×</text>
</view>
</view>
<!-- 上传加号按钮 -->
<view class="add-btn" v-if="tempFilePaths.length < maxCount" @click="chooseImages">
<text class="add-icon">+</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 底部下一步按钮 -->
<view class="bottom-btn-wrap">
<button class="next-btn" @click="submitForm">提交</button>
</view>
<!-- ================== 自定义Popup弹窗纯view实现 ================== -->
<!-- 蒙层 -->
<view class="mask" v-if="showTypePopup || showPropertyPopup" @click="closeAllPopup"></view>
<!-- 投诉类型弹窗 -->
<view v-if="showTypePopup" class="mask" @click="showTypePopup=false"></view>
<view v-if="showTypePopup" class="popup">
<view class="popup-header">
<text class="title">选择投诉类型</text>
<text class="close" @click="showTypePopup = false"></text>
</view>
<view class="list">
<view :class="{ active: selectedType === item }" class="item" v-for="(item, index) in typeOptions" :key="index" @click="selectType(item)">
{{ item.name }}
</view>
</view>
</view>
<!-- 投诉物业弹窗 -->
<view v-if="showPropertyPopup" class="mask" @click="showPropertyPopup=false"></view>
<view v-if="showPropertyPopup" class="popup">
<view class="popup-header">
<text class="title">选择投诉物业</text>
<text class="close" @click="showPropertyPopup = false"></text>
</view>
<view class="list">
<view :class="{ active: selectedProperty === item }" class="item" v-for="(item, index) in propertyOptions" :key="index" @click="selectProperty(item)">
{{ item }}
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getComplainType, addComplain } from '../api/apiSub.js'
import { uploadFile } from '../api/api.js'
// 弹窗控制
const showTypePopup = ref(false)
const showPropertyPopup = ref(false)
// 下拉选项
const typeOptions = ref([])
const propertyOptions = ref(['XX物业A', 'XX物业B', 'XX物业C'])
// 表单数据
const selectedType = ref('')
const selectedTypeId = ref('')
const selectedProperty = ref('')
const description = ref('')
const name = ref('')
const phone = ref('')
const villageId = ref('')
// 多图上传
const tempFilePaths = ref([])
const maxCount = ref(9)
// 关闭所有弹窗
const closeAllPopup = () => {
showTypePopup.value = false
showPropertyPopup.value = false
}
onLoad(() => {
getSelectType()
villageId.value = uni.getStorageSync('currentVillageId') || ''
})
// 获取投诉类型
const getSelectType = async () => {
let params = {status:'0'}
try {
const res = await getComplainType(params)
if (res.code === 200) {
typeOptions.value = res.data || []
} else {
uni.showToast({ title: res.msg || '获取失败', icon: 'none' })
}
} catch (err) {
uni.showToast({ title: '网络异常', icon: 'none' })
}
}
// 选择投诉类型
const selectType = (item) => {
selectedType.value = item.name
selectedTypeId.value = item.id || ''
showTypePopup.value = false
// 切换类型时清空物业选择
if (item.name !== '物业服务') {
selectedProperty.value = ''
}
}
// 选择物业
const selectProperty = (item) => {
selectedProperty.value = item
showPropertyPopup.value = false
}
// 多图选择
const chooseImages = () => {
uni.chooseImage({
count: maxCount.value - tempFilePaths.value.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
tempFilePaths.value = [...tempFilePaths.value, ...res.tempFilePaths]
}
})
}
// 上传单张图片
const uploadImageData = async (filePath) => {
try {
const res = await uploadFile(filePath)
return res.url || ''
} catch (err) {
console.error('单张图片上传失败', err)
return ''
}
}
// 删除图片
const deletePhoto = (index) => {
tempFilePaths.value.splice(index, 1)
}
// 表单提交
const submitForm = async () => {
// 校验
if (!selectedTypeId.value) {
return uni.showToast({ title: '请选择投诉类型', icon: 'none' })
}
if (selectedType.value === '物业服务' && !selectedProperty.value) {
return uni.showToast({ title: '请选择物业', icon: 'none' })
}
if (!description.value.trim()) {
return uni.showToast({ title: '请输入问题描述', icon: 'none' })
}
uni.showLoading({ title: '提交中...', mask: true })
try {
// 上传图片
let imageUrls = []
for (let path of tempFilePaths.value) {
let url = await uploadImageData(path)
if (url) imageUrls.push(url)
}
// 构造提交参数
const params = {
typeId: selectedTypeId.value,
problemDes: description.value.trim(),
complaintName: name.value || '',
phone: phone.value || '',
villageId: villageId.value || '',
problemImageUrl: imageUrls.join(',')
}
// 调用提交接口
const res = await addComplain(params)
uni.hideLoading()
if (res.code === 200) {
uni.showToast({ title: '提交成功', icon: 'success' })
setTimeout(() => {
uni.navigateTo({
url: '/pageSubPack/service-list/complaintsSuggestionsIndex'
})
}, 1500)
} else {
uni.showToast({ title: res.msg || '提交失败', icon: 'none' })
}
} catch (err) {
uni.hideLoading()
uni.showToast({
title: err.msg || '提交失败,请重试',
icon: 'none'
})
}
}
// 返回
const goBack = () => {
uni.navigateBack()
}
</script>
<style lang="scss">
page {
background: #fff;
}
</style>
<style scoped>
.contentStyle {
display: flex;
flex-direction: column;
height: 100vh;
}
.page {
flex: 1;
overflow-y: auto;
padding: 0px 40rpx;
box-sizing: border-box;
background-color: #fff;
}
/* 表单主体 */
.form-content {}
.form-tip {
display: block;
font-size: 32rpx;
color: #1D1D1D;
margin-bottom: 10rpx;
font-weight: 600;
}
/* 表单项:一行布局,不换行 */
.form-row {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.row-left {
display: flex;
align-items: center;
width: 180rpx;
flex-shrink: 0;
}
.required-star {
color: #ff3b30;
font-size: 30rpx;
margin-right: 8rpx;
}
.label-text {
font-size: 28rpx;
color: #333;
}
.row-right {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
}
.value-text {
font-size: 30rpx;
color: grey;
}
.value-text.placeholderStyle {
color: #333;
}
.arrow-icon {
font-size: 28rpx;
color: #999;
}
/* 输入框样式 */
.text-input {
width: 100%;
font-size: 30rpx;
color: #333;
border: none;
outline: none;
background: transparent;
}
/* 问题描述行 */
.desc-row {
align-items: flex-start;
padding-top: 32rpx;
padding-bottom: 32rpx;
}
.desc-right {
flex: 1;
}
.desc-input {
width: calc(100vw - 84rpx);
min-height: 80px;
margin-top: 20rpx;
padding: 24rpx;
border: 1rpx solid #f0f0f0;
border-radius: 8rpx;
font-size: 30rpx;
color: #333;
line-height: 1.5;
box-sizing: border-box;
background: transparent;
}
/* 照片上传区域 */
.photo-row {
align-items: flex-start;
padding-top: 32rpx;
padding-bottom: 32rpx;
border-bottom: none;
}
.photo-right {
flex: 1;
}
.photo-grid {
display: flex;
flex-wrap: wrap;
gap: 24rpx;
}
.photo-item {
position: relative;
width: 120rpx;
height: 120rpx;
border-radius: 8rpx;
overflow: hidden;
}
.photo-img {
width: 100%;
height: 100%;
}
.delete-btn {
position: absolute;
top: -4rpx;
right: -4rpx;
width: 32rpx;
height: 32rpx;
background-color: rgba(0, 0, 0, 0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.delete-icon {
color: #fff;
font-size: 24rpx;
line-height: 1;
}
.add-btn {
width: 120rpx;
height: 120rpx;
border: 1rpx solid #e5e5e5;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
}
.add-icon {
font-size: 32rpx;
color: #999;
line-height: 1;
}
/* 底部按钮 */
.bottom-btn-wrap {
padding: 20rpx 30rpx 40rpx;
display: flex;
gap: 20rpx;
background-color: #fff;
}
.next-btn {
width: 100%;
height: 96rpx;
line-height: 96rpx;
background-color: #1677ff;
color: #fff;
font-size: 36rpx;
border-radius: 12rpx;
border: none;
}
/* 遮罩 */
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 99;
}
/* 底部弹出框 */
.popup {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #fff;
border-radius: 20rpx 20rpx 0 0;
z-index: 100;
max-height: 60vh;
}
.popup-header {
padding: 30rpx;
text-align: center;
position: relative;
}
.title {
font-size: 32rpx;
font-weight: 500;
}
.close {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
font-size: 36rpx;
color: #666;
}
/* 选项列表 */
.list {
padding: 0 30rpx;
}
.item {
padding: 30rpx 0;
text-align: center;
font-size: 28rpx;
border-bottom: 1rpx solid #f5f5f5;
}
.item:last-child {
border-bottom: none;
}
.item.active {
color: #337eff;
font-weight: 600;
background-color: #f8f9fa;
}
.status-bar {
width: 100vw;
height: 46px;
}
</style>