You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

455 lines
11 KiB

3 weeks ago
<template>
<view class="page">
<view class="title">
<view class="title-sreach">
<view class="back-btn" @tap="back" :style="{'top': menuButtonInfo.top +'px'}">
<uni-icons type="left" size="28"></uni-icons>
</view>
<view class="title-name" :style="{'padding-top': menuButtonInfo.top +'px'}">
投诉吐槽
</view>
</view>
</view>
<view class="content">
<view class="add-card">
<view class="card-title">我要吐槽</view>
<picker :range="typeOptions" @change="changeType">
<view class="type-picker">{{form.feedbackType || '请选择类型'}} <text></text></view>
</picker>
<textarea class="feedback-textarea" maxlength="300" v-model="form.problemContent"
placeholder="遇到了什么问题?写下来我们会尽快处理"></textarea>
<input class="phone-input" v-model="form.contactPhone" placeholder="联系电话(选填,方便我们联系你)" />
<view class="upload-row">
<view class="upload-btn" @tap="chooseImage">
<uni-icons type="camera" size="28" color="#777"></uni-icons>
</view>
<view class="upload-preview" v-for="(pic,index) in pictureList" :key="index">
<image :src="pic" mode="aspectFill" @tap="previewImage(index)"></image>
<text class="remove-img" @tap.stop="removeImage(index)">×</text>
</view>
</view>
<view class="submit-btn" @tap="submitFeedback">提交</view>
</view>
<view class="list-head">
<text>我的反馈</text>
<text class="list-tip">按处理状态和时间排序</text>
</view>
<scroll-view scroll-y class="feedback-scroll" @scrolltolower="loadMore">
<view class="feedback-item" v-for="(item,index) in feedbackList" :key="index">
<view class="item-head">
<text class="item-type">{{item.feedbackType || '投诉吐槽'}}</text>
<text :class="item.status == 1 ? 'status done' : 'status'">{{item.status == 1 ? '已回复' : '待回复'}}</text>
</view>
<view class="item-content">{{item.problemContent}}</view>
<view class="item-pictures" v-if="getPictureList(item.problemPicture).length">
<image v-for="(pic,picIndex) in getPictureList(item.problemPicture)" :key="picIndex"
:src="pic" mode="aspectFill" @tap="previewItemImage(item.problemPicture, picIndex)"></image>
</view>
<view class="item-time">{{formatTime(item.createTime)}}</view>
<view class="reply-box" v-if="item.handleOpinion">
<view class="reply-title">平台回复</view>
<view class="reply-content">{{item.handleOpinion}}</view>
<view class="item-time" v-if="item.handleTime">{{formatTime(item.handleTime)}}</view>
</view>
</view>
<view class="empty" v-if="!loading && feedbackList.length == 0">还没有提交过投诉吐槽</view>
<view class="load-more" v-if="feedbackList.length > 0">{{hasMore ? '上拉加载更多' : '没有更多了'}}</view>
</scroll-view>
</view>
<common-loading />
</view>
</template>
<script>
export default {
data() {
return {
menuButtonInfo: {},
typeOptions: ['投诉', '吐槽', '建议', '其他'],
pictureList: [],
loading: false,
totalPages: 0,
feedbackList: [],
form: {
feedbackType: '',
problemContent: '',
contactPhone: '',
problemPicture: ''
},
searchForm: {
pageNum: 1,
pageSize: 10,
userId: uni.getStorageSync('id')
}
}
},
onLoad() {
this.menuButtonInfo = uni.getMenuButtonBoundingClientRect ? uni.getMenuButtonBoundingClientRect() : {}
this.form.contactPhone = uni.getStorageSync('mobile') || uni.getStorageSync('phone') || ''
this.getFeedbackList()
},
computed: {
hasMore() {
return this.searchForm.pageNum < this.totalPages
}
},
methods: {
changeType(e) {
this.form.feedbackType = this.typeOptions[e.detail.value]
},
getArea() {
try {
return uni.getStorageSync('area') ? JSON.parse(uni.getStorageSync('area')) : {}
} catch (e) {
return {}
}
},
getFeedbackList() {
if (this.loading) return
this.loading = true
this.tui.request("/app/problemFeedback/getProblemFeedbackList", "POST", this.searchForm, false, false).then((res) => {
this.loading = false
if (res.code == 200) {
const records = (res.result && res.result.records) || []
this.totalPages = (res.result && res.result.pages) || 0
if (this.searchForm.pageNum == 1) {
this.feedbackList = records
} else {
this.feedbackList = this.feedbackList.concat(records)
}
} else {
this.tui.toast(res.message)
}
uni.hideLoading()
}).catch(() => {
this.loading = false
uni.hideLoading()
})
},
loadMore() {
if (!this.hasMore || this.loading) return
this.searchForm.pageNum += 1
this.getFeedbackList()
},
submitFeedback() {
const content = (this.form.problemContent || '').trim()
if (!content) {
this.tui.toast('请输入投诉吐槽内容')
return
}
const area = this.getArea()
const data = {
problemContent: content,
problemPicture: this.pictureList.join(','),
userId: uni.getStorageSync('id'),
userName: uni.getStorageSync('nickName') || uni.getStorageSync('nickname') || uni.getStorageSync('userName'),
contactPhone: this.form.contactPhone,
feedbackType: this.form.feedbackType || '投诉',
regionId: area.id || '',
regionName: area.title || uni.getStorageSync('schoolName') || '',
status: 0
}
this.tui.request("/app/problemFeedback/saveOrUpdateProblemFeedback", "POST", data, false, false).then((res) => {
if (res.code == 200) {
this.tui.toast('提交成功')
this.resetForm()
this.searchForm.pageNum = 1
this.getFeedbackList()
} else {
this.tui.toast(res.message)
}
uni.hideLoading()
}).catch(() => {
uni.hideLoading()
})
},
resetForm() {
this.form.feedbackType = ''
this.form.problemContent = ''
this.form.problemPicture = ''
this.pictureList = []
},
chooseImage() {
if (this.pictureList.length >= 3) {
this.tui.toast('最多上传3张图片')
return
}
uni.chooseMedia({
count: 3 - this.pictureList.length,
mediaType: ['image'],
sourceType: ['camera', 'album'],
success: (res) => {
for (let i = 0; i < res.tempFiles.length; i++) {
this.uploadFile(res.tempFiles[i].tempFilePath)
}
}
})
},
async uploadFile(path) {
let hiverToken = uni.getStorageSync("hiver_token")
await uni.uploadFile({
url: this.tui.interfaceUrl() + '/upload/file',
filePath: path,
name: 'file',
header: {
"content-type": "multipart/form-data",
'accessToken': hiverToken
},
success: (uploadFileRes) => {
let pathData = JSON.parse(uploadFileRes.data)
if (pathData.result) {
this.pictureList.push(pathData.result)
}
},
fail: (err) => {
uni.showToast({
title: JSON.stringify(err),
icon: 'none'
})
}
})
},
removeImage(index) {
this.pictureList.splice(index, 1)
},
getPictureList(picture) {
if (!picture) return []
if (Array.isArray(picture)) return picture.filter(Boolean)
return String(picture).split(',').map(item => item.trim()).filter(Boolean)
},
previewImage(index) {
uni.previewImage({
urls: this.pictureList,
current: this.pictureList[index]
})
},
previewItemImage(picture, index) {
const urls = this.getPictureList(picture)
uni.previewImage({
urls,
current: urls[index]
})
},
formatTime(value) {
if (!value) return ''
const date = new Date(value)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hour = String(date.getHours()).padStart(2, '0')
const minute = String(date.getMinutes()).padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}`
},
back() {
uni.navigateBack()
}
}
}
</script>
<style lang="scss">
page {
width: 100%;
height: 100%;
background: #F5F8F5;
color: #00231C;
}
.page {
min-height: 100vh;
}
.title {
height: 200rpx;
background: #F5F8F5;
}
.title-sreach {
position: relative;
display: flex;
height: 200rpx;
}
.back-btn {
position: absolute;
left: 24rpx;
bottom: 0;
}
.title-name {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: 900;
}
.content {
padding: 20rpx 24rpx 40rpx;
}
.add-card,
.feedback-item {
border-radius: 28rpx;
background: #fff;
box-shadow: 0 12rpx 40rpx rgba(30, 80, 60, .06);
}
.add-card {
padding: 28rpx;
}
.card-title,
.list-head {
font-size: 32rpx;
font-weight: 900;
}
.type-picker,
.phone-input {
height: 82rpx;
margin-top: 22rpx;
padding: 0 24rpx;
border-radius: 20rpx;
background: #F7F8F8;
font-size: 28rpx;
line-height: 82rpx;
}
.feedback-textarea {
width: 100%;
height: 210rpx;
margin-top: 22rpx;
padding: 24rpx;
border-radius: 20rpx;
background: #F7F8F8;
box-sizing: border-box;
font-size: 28rpx;
}
.upload-row,
.item-pictures {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-top: 22rpx;
}
.upload-btn,
.upload-preview image,
.item-pictures image {
width: 150rpx;
height: 150rpx;
border-radius: 18rpx;
background: #F7F8F8;
}
.upload-btn {
text-align: center;
line-height: 150rpx;
}
.upload-preview {
position: relative;
}
.remove-img {
position: absolute;
top: -12rpx;
right: -12rpx;
width: 36rpx;
height: 36rpx;
border-radius: 50%;
background: #FF5A4F;
color: #fff;
text-align: center;
line-height: 36rpx;
}
.submit-btn {
height: 86rpx;
margin-top: 28rpx;
border-radius: 999rpx;
background: #34C8A0;
color: #fff;
font-size: 30rpx;
font-weight: 900;
line-height: 86rpx;
text-align: center;
}
.list-head {
display: flex;
justify-content: space-between;
margin: 34rpx 4rpx 18rpx;
}
.list-tip,
.item-time {
color: #8A9199;
font-size: 22rpx;
font-weight: 400;
}
.feedback-scroll {
height: calc(100vh - 760rpx);
min-height: 520rpx;
}
.feedback-item {
margin-bottom: 20rpx;
padding: 24rpx;
}
.item-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.item-type {
font-size: 28rpx;
font-weight: 900;
}
.status {
padding: 6rpx 14rpx;
border-radius: 999rpx;
background: #FFF0E6;
color: #FF7A3D;
font-size: 22rpx;
}
.status.done {
background: #E8FFF5;
color: #34C8A0;
}
.item-content,
.reply-content {
margin-top: 16rpx;
font-size: 28rpx;
line-height: 44rpx;
word-break: break-all;
}
.reply-box {
margin-top: 20rpx;
padding: 18rpx;
border-radius: 18rpx;
background: #F7F8F8;
}
.reply-title {
color: #34C8A0;
font-weight: 900;
}
.empty,
.load-more {
padding: 38rpx 0;
color: #8A9199;
text-align: center;
}
</style>