577 lines
16 KiB
Vue
577 lines
16 KiB
Vue
<template>
|
|
<view class="complaint-detail-page">
|
|
<!-- 自定义导航栏 -->
|
|
<wd-navbar :bordered="false"
|
|
custom-style="background: transparent !important; backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important;"
|
|
safeAreaInsetTop fixed placeholder>
|
|
<template #left>
|
|
<view class="li-ml-15 li-mt-10 li-flex li-items-center">
|
|
<text v-if="hasMultiplePages" class="ri-arrow-left-s-line li-text-70"
|
|
@click="toPages({type:'nav'})"></text>
|
|
<text v-if="!hasMultiplePages" class="ri-home-5-line li-text-55 li-mb-8 li-mr-10"
|
|
@click="toPages({type:'home'})"></text>
|
|
<text class="li-text-42">投诉详情</text>
|
|
</view>
|
|
</template>
|
|
</wd-navbar>
|
|
<!-- 导航栏背景 -->
|
|
<view class="nav-bg-layer"></view>
|
|
|
|
<!-- 主要内容区域 -->
|
|
<view class="li-w-92% li-mx-auto li-mt-30 li-pb-30">
|
|
<!-- 基本信息卡片 -->
|
|
<view class="detail-card li-bg-white li-rd-20 li-p-30 li-mb-20 li-shadow-sm">
|
|
<view class="li-flex li-items-center li-justify-between li-pb-15 li-bottom-border2">
|
|
<view class="li-flex li-items-center">
|
|
<text class="ri-error-warning-line li-text-34 li-mr-10 li-text-#999999"></text>
|
|
<text class="li-text-30">{{complaintDetail.complaint_no}}</text>
|
|
</view>
|
|
<view class="status-badge" :style="{
|
|
backgroundColor: getStatusBgColor(complaintDetail.status),
|
|
color: getStatusColor(complaintDetail.status)
|
|
}">
|
|
{{getStatusText(complaintDetail.status)}}
|
|
</view>
|
|
</view>
|
|
|
|
<view class="li-mt-25">
|
|
<!-- 投诉内容 -->
|
|
<view class="li-flex li-items-start li-mb-20">
|
|
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">投诉内容</text>
|
|
<text class="content-text li-text-28 li-text-#333 li-flex-1">{{complaintDetail.content}}</text>
|
|
</view>
|
|
|
|
<!-- 投诉类型标签 -->
|
|
<view class="li-flex li-items-center li-flex-wrap li-mt-10 li-mb-20"
|
|
v-if="complaintDetail.tags && complaintDetail.tags.length">
|
|
<wd-tag v-for="(tag, idx) in complaintDetail.tags" :key="idx" color="#0083ff" bg-color="#d0e8ff"
|
|
custom-class="li-mr-10 li-mb-10">
|
|
{{tag}}
|
|
</wd-tag>
|
|
</view>
|
|
|
|
<!-- 投诉图片 -->
|
|
<view class="li-flex li-flex-wrap li-mt-15 li-mb-20"
|
|
v-if="complaintDetail.images && complaintDetail.images.length">
|
|
<image v-for="(img, imgIdx) in complaintDetail.images" :key="imgIdx" :src="img"
|
|
mode="aspectFill" class="detail-image li-mr-10 li-mb-10"
|
|
@click="previewImage(complaintDetail.images, imgIdx)">
|
|
</image>
|
|
</view>
|
|
|
|
<!-- 投诉详情信息 -->
|
|
<view class="li-flex li-items-center li-mb-15">
|
|
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">投诉人</text>
|
|
<text class="content-text li-text-28 li-text-#333">{{complaintDetail.user_name}}</text>
|
|
</view>
|
|
|
|
<view class="li-flex li-items-center li-mb-15">
|
|
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">联系电话</text>
|
|
<text class="content-text li-text-28 li-text-#333">{{complaintDetail.user_mobile}}</text>
|
|
<text class="ri-phone-line li-text-28 li-text-#0070F0 li-ml-20"
|
|
@click="callPhone(complaintDetail.user_mobile)"></text>
|
|
</view>
|
|
|
|
<view class="li-flex li-items-center li-mb-15">
|
|
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">所在小区</text>
|
|
<text class="content-text li-text-28 li-text-#333">{{complaintDetail.village_name}}</text>
|
|
</view>
|
|
|
|
<view class="li-flex li-items-center li-mb-15">
|
|
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">具体位置</text>
|
|
<text class="content-text li-text-28 li-text-#333">{{complaintDetail.location || '未提供'}}</text>
|
|
</view>
|
|
|
|
<view class="li-flex li-items-center li-mb-15">
|
|
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">提交时间</text>
|
|
<text class="content-text li-text-28 li-text-#999">{{complaintDetail.create_time}}</text>
|
|
</view>
|
|
|
|
<view class="li-flex li-items-center li-mb-15" v-if="complaintDetail.process_time">
|
|
<text class="content-label li-text-28 li-text-#9a9a9a li-w-160">处理时间</text>
|
|
<text class="content-text li-text-28 li-text-#999">{{complaintDetail.process_time}}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 处理记录 -->
|
|
<view class="detail-card li-bg-white li-rd-20 li-p-30 li-mb-20 li-shadow-sm"
|
|
v-if="processRecords.length > 0">
|
|
<view class="li-flex li-items-center li-pb-15 li-bottom-border2">
|
|
<text class="ri-history-line li-text-32 li-text-#0070F0 li-mr-10"></text>
|
|
<text class="li-text-32 li-font-bold">处理记录</text>
|
|
</view>
|
|
|
|
<view class="process-timeline li-mt-20">
|
|
<view v-for="(record, index) in processRecords" :key="index" class="timeline-item">
|
|
<view class="timeline-dot" :class="{'active': index === 0}"></view>
|
|
<view class="timeline-content">
|
|
<view class="li-flex li-items-center li-justify-between">
|
|
<text class="li-text-30">{{record.action}}</text>
|
|
<text class="li-text-24 li-text-#999">{{record.time}}</text>
|
|
</view>
|
|
<view class="li-text-28 li-text-#666 li-mt-10">{{record.remark}}</view>
|
|
<view class="li-text-26 li-text-#999 li-mt-5">处理人: {{record.operator}}</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 操作按钮区域 -->
|
|
<view v-if="complaintDetail.status !== 2 && complaintDetail.status !== 3">
|
|
<view class="action-buttons li-flex li-justify-between li-mt-30 li-px-30">
|
|
<wd-button type="info" plain custom-class="action-btn" v-if="complaintDetail.status === 0"
|
|
@click="handleProcess">开始处理</wd-button>
|
|
<wd-button type="primary" custom-class="action-btn" v-if="complaintDetail.status === 1"
|
|
@click="handleComplete">处理完成</wd-button>
|
|
<wd-button type="danger" plain custom-class="action-btn" @click="handleClose">关闭投诉</wd-button>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 底部固定操作区 -->
|
|
<view class="fixed-bottom" v-if="complaintDetail.status === 1">
|
|
<wd-button type="primary" block @click="showAddRecord = true">添加处理记录</wd-button>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 处理记录弹窗 -->
|
|
<wd-popup v-model="showAddRecord" position="bottom" closable :safe-area-inset-bottom="true">
|
|
<view class="add-record-popup li-p-30">
|
|
<view class="li-text-32 li-font-bold li-mb-20">添加处理记录</view>
|
|
|
|
<view class="li-mt-20 li-mb-30">
|
|
<view class="li-text-28 li-mb-10">处理备注</view>
|
|
<wd-textarea v-model="recordForm.remark" placeholder="请输入处理信息" autosize show-word-limit
|
|
max-length="200"></wd-textarea>
|
|
</view>
|
|
|
|
<view class="li-mt-20 li-mb-30">
|
|
<view class="li-text-28 li-mb-10">上传图片凭证</view>
|
|
<wd-upload v-model="recordForm.images" max-size="10485760" multiple :limit="4"
|
|
:before-preview="beforePreview"></wd-upload>
|
|
</view>
|
|
|
|
<wd-button type="primary" block :loading="submitting" @click="submitRecord">提交记录</wd-button>
|
|
</view>
|
|
</wd-popup>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, computed, onMounted } from 'vue';
|
|
import { onLoad } from '@dcloudio/uni-app';
|
|
import { useToast } from '@/uni_modules/wot-design-uni';
|
|
import { useNavigation } from '@/hooks/useNavigation';
|
|
|
|
|
|
// 使用导航composable
|
|
const {
|
|
hasMultiplePages,
|
|
isTabBarPage,
|
|
checkRouteStack
|
|
} = useNavigation();
|
|
|
|
const Toast = useToast();
|
|
|
|
// 页面状态
|
|
const loading = ref(false);
|
|
const submitting = ref(false);
|
|
const showAddRecord = ref(false);
|
|
|
|
// 表单数据
|
|
const recordForm = reactive({
|
|
remark: '',
|
|
images: []
|
|
});
|
|
|
|
// 根据查询参数获取投诉ID
|
|
const complaintId = ref(null);
|
|
onLoad((option) => {
|
|
complaintId.value = option.id;
|
|
loadComplaintDetail();
|
|
checkRouteStack();
|
|
});
|
|
|
|
// 模拟数据 - 投诉详情
|
|
const complaintDetail = ref({
|
|
complaint_id: 1,
|
|
complaint_no: 'TS20240607001',
|
|
content: '楼道灯已经坏了三天,晚上上下楼很不方便,希望物业尽快解决!由于光线问题,我家小孩差点摔倒,这是安全隐患,希望物业能够尽快修复。',
|
|
village_name: '阳光花园小区',
|
|
location: 'A栋3单元楼道',
|
|
user_name: '张三',
|
|
user_mobile: '13800138000',
|
|
create_time: '2024-06-07 10:23',
|
|
process_time: '2024-06-07 14:35',
|
|
status: 1,
|
|
urgency: 2,
|
|
tags: ['公共设施', '照明', '安全隐患'],
|
|
images: [
|
|
'https://img.yzcdn.cn/vant/cat.jpeg',
|
|
'https://img.yzcdn.cn/vant/tree.jpeg'
|
|
]
|
|
});
|
|
|
|
// 模拟数据 - 处理记录
|
|
const processRecords = ref([
|
|
{
|
|
record_id: 1,
|
|
action: '开始处理',
|
|
remark: '已联系维修人员前往现场查看情况',
|
|
operator: '李维修',
|
|
time: '2024-06-07 14:35'
|
|
}
|
|
]);
|
|
|
|
// 状态颜色配置
|
|
const getStatusColor = (status) => {
|
|
const colorMap = {
|
|
0: '#ff9d00', // 待处理
|
|
1: '#37A5FF', // 处理中
|
|
2: '#00b42a', // 已处理
|
|
3: '#999999' // 已关闭
|
|
};
|
|
return colorMap[status] || '#666666';
|
|
};
|
|
|
|
// 状态背景色配置
|
|
const getStatusBgColor = (status) => {
|
|
const bgColorMap = {
|
|
0: '#fff6e9', // 待处理
|
|
1: '#e8f4ff', // 处理中
|
|
2: '#e8ffea', // 已处理
|
|
3: '#f5f5f5' // 已关闭
|
|
};
|
|
return bgColorMap[status] || '#f5f5f5';
|
|
};
|
|
|
|
// 状态文字配置
|
|
const getStatusText = (status) => {
|
|
const textMap = {
|
|
0: '待处理',
|
|
1: '处理中',
|
|
2: '已处理',
|
|
3: '已关闭'
|
|
};
|
|
return textMap[status] || '未知状态';
|
|
};
|
|
|
|
// 加载投诉详情
|
|
const loadComplaintDetail = async () => {
|
|
try {
|
|
loading.value = true;
|
|
// 实际项目中这里应该调用API获取数据
|
|
// await getComplaintDetail(complaintId.value)
|
|
|
|
// 模拟加载延迟
|
|
await new Promise(resolve => setTimeout(resolve, 800));
|
|
loading.value = false;
|
|
} catch (error) {
|
|
console.error('加载投诉详情失败', error);
|
|
Toast.fail('加载失败,请重试');
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
// 返回上一页
|
|
const goBack = () => {
|
|
uni.navigateBack();
|
|
};
|
|
|
|
// 拨打电话
|
|
const callPhone = (phone) => {
|
|
uni.makePhoneCall({
|
|
phoneNumber: phone,
|
|
success: () => {
|
|
console.log('拨打电话成功');
|
|
},
|
|
fail: (err) => {
|
|
console.log('拨打电话失败', err);
|
|
}
|
|
});
|
|
};
|
|
|
|
// 图片预览
|
|
const previewImage = (images, current) => {
|
|
uni.previewImage({
|
|
urls: images,
|
|
current: images[current]
|
|
});
|
|
};
|
|
|
|
// 处理操作
|
|
const handleProcess = async () => {
|
|
try {
|
|
Toast.loading('处理中...');
|
|
// 实际项目中这里应该调用API
|
|
// await startProcess(complaintId.value)
|
|
|
|
// 模拟操作延迟
|
|
await new Promise(resolve => setTimeout(resolve, 800));
|
|
|
|
// 更新状态
|
|
complaintDetail.value.status = 1;
|
|
complaintDetail.value.process_time = new Date().toLocaleString();
|
|
|
|
// 添加处理记录
|
|
processRecords.value.unshift({
|
|
record_id: new Date().getTime(),
|
|
action: '开始处理',
|
|
remark: '物业已接单,开始处理',
|
|
operator: '当前操作员',
|
|
time: '2025-06-23'
|
|
});
|
|
|
|
Toast.success('已开始处理');
|
|
} catch (error) {
|
|
console.error('操作失败', error);
|
|
Toast.fail('操作失败,请重试');
|
|
}
|
|
};
|
|
|
|
// 完成处理
|
|
const handleComplete = async () => {
|
|
try {
|
|
Toast.loading('处理中...');
|
|
// 实际项目中这里应该调用API
|
|
// await completeProcess(complaintId.value)
|
|
|
|
// 模拟操作延迟
|
|
await new Promise(resolve => setTimeout(resolve, 800));
|
|
|
|
// 更新状态
|
|
complaintDetail.value.status = 2;
|
|
|
|
// 添加处理记录
|
|
processRecords.value.unshift({
|
|
record_id: new Date().getTime(),
|
|
action: '处理完成',
|
|
remark: '问题已解决,楼道灯已修复',
|
|
operator: '当前操作员',
|
|
time: '2025-06-23'
|
|
});
|
|
|
|
Toast.success('处理完成');
|
|
} catch (error) {
|
|
console.error('操作失败', error);
|
|
Toast.fail('操作失败,请重试');
|
|
}
|
|
};
|
|
|
|
// 关闭投诉
|
|
const handleClose = async () => {
|
|
try {
|
|
Toast.loading('处理中...');
|
|
// 实际项目中这里应该调用API
|
|
// await closeComplaint(complaintId.value)
|
|
|
|
// 模拟操作延迟
|
|
await new Promise(resolve => setTimeout(resolve, 800));
|
|
|
|
// 更新状态
|
|
complaintDetail.value.status = 3;
|
|
|
|
// 添加处理记录
|
|
processRecords.value.unshift({
|
|
record_id: new Date().getTime(),
|
|
action: '关闭投诉',
|
|
remark: '已与业主沟通,投诉已关闭',
|
|
operator: '当前操作员',
|
|
time: '2025-06-23'
|
|
});
|
|
|
|
Toast.success('投诉已关闭');
|
|
} catch (error) {
|
|
console.error('操作失败', error);
|
|
Toast.fail('操作失败,请重试');
|
|
}
|
|
};
|
|
|
|
// 预览文件
|
|
const beforePreview = (file) => {
|
|
console.log('预览文件', file);
|
|
return true;
|
|
};
|
|
|
|
// 页面跳转
|
|
const toPages = (item) => {
|
|
switch (item.type) {
|
|
case 'nav':
|
|
uni.navigateBack({
|
|
delta: 1
|
|
});
|
|
break;
|
|
case 'home':
|
|
uni.switchTab({
|
|
url: '/pages/index/index'
|
|
});
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
// 提交处理记录
|
|
const submitRecord = async () => {
|
|
if (!recordForm.remark.trim()) {
|
|
return Toast.fail('请输入处理备注');
|
|
}
|
|
|
|
try {
|
|
submitting.value = true;
|
|
|
|
// 实际项目中这里应该调用API
|
|
// await addProcessRecord({
|
|
// complaint_id: complaintId.value,
|
|
// remark: recordForm.remark,
|
|
// images: recordForm.images.map(img => img.url)
|
|
// })
|
|
|
|
// 模拟提交延迟
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
// 添加处理记录
|
|
processRecords.value.unshift({
|
|
record_id: new Date().getTime(),
|
|
action: '处理进展',
|
|
remark: recordForm.remark,
|
|
operator: '当前操作员',
|
|
time: '2025-06-23'
|
|
});
|
|
|
|
// 重置表单
|
|
recordForm.remark = '';
|
|
recordForm.images = [];
|
|
|
|
// 关闭弹窗
|
|
showAddRecord.value = false;
|
|
|
|
Toast.success('记录已添加');
|
|
submitting.value = false;
|
|
} catch (error) {
|
|
console.error('提交记录失败', error);
|
|
Toast.fail('提交失败,请重试');
|
|
submitting.value = false;
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
page {
|
|
background-color: #F7F8FA;
|
|
}
|
|
|
|
.complaint-detail-page {
|
|
min-height: 100vh;
|
|
|
|
|
|
/* 有底部固定按钮时的底部间距 */
|
|
padding-bottom: 140rpx;
|
|
}
|
|
|
|
.nav-bg-layer {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: calc(var(--status-bar-height) + 88rpx);
|
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.9) 100%);
|
|
z-index: -1;
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.detail-card {
|
|
.status-badge {
|
|
padding: 4rpx 20rpx;
|
|
border-radius: 20rpx;
|
|
font-size: 24rpx;
|
|
}
|
|
}
|
|
|
|
.li-bottom-border2 {
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.detail-image {
|
|
width: 180rpx;
|
|
height: 180rpx;
|
|
border-radius: 8rpx;
|
|
object-fit: cover;
|
|
}
|
|
|
|
/* 处理记录时间线 */
|
|
.process-timeline {
|
|
position: relative;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
left: 10rpx;
|
|
width: 2rpx;
|
|
background-color: #e0e0e0;
|
|
}
|
|
|
|
.timeline-item {
|
|
position: relative;
|
|
padding-left: 40rpx;
|
|
margin-bottom: 30rpx;
|
|
|
|
&:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.timeline-dot {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 15rpx;
|
|
width: 20rpx;
|
|
height: 20rpx;
|
|
border-radius: 50%;
|
|
background-color: #e0e0e0;
|
|
|
|
&.active {
|
|
background-color: #0070F0;
|
|
box-shadow: 0 0 0 4rpx rgba(0, 112, 240, 0.2);
|
|
}
|
|
}
|
|
|
|
.timeline-content {
|
|
background-color: #f9f9f9;
|
|
border-radius: 12rpx;
|
|
padding: 20rpx;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 底部固定区域 */
|
|
.fixed-bottom {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
background-color: #fff;
|
|
padding: 20rpx 30rpx;
|
|
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
/* #ifdef MP-WEIXIN */
|
|
padding-bottom: constant(safe-area-inset-bottom);
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
/* #endif */
|
|
/* #ifdef APP-PLUS || H5 */
|
|
padding-bottom: 50rpx
|
|
/* #endif */
|
|
}
|
|
|
|
.action-btn {
|
|
width: 290rpx !important;
|
|
}
|
|
|
|
|
|
|
|
/* 弹窗样式 */
|
|
.add-record-popup {
|
|
max-height: 80vh;
|
|
}
|
|
</style> |