| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- <template>
- <view class="complaint-detail-root">
- <erp-nav-bar title="反馈详情" />
- <scroll-view scroll-y class="scroll-view-container" :show-scrollbar="false">
- <view class="detail-body" v-if="detail">
- <view class="section-card">
- <view class="section-title">反馈信息</view>
- <view class="info-row">
- <text class="info-label">反馈类型</text>
- <text class="info-value type-tag">{{ detail.feedbackTypeLabel }}</text>
- </view>
- <view class="info-row">
- <text class="info-label">反馈时间</text>
- <text class="info-value">{{ detail.createTime }}</text>
- </view>
- </view>
- <view class="section-card">
- <view class="section-title">反馈内容</view>
- <view class="content-block">{{ detail.content }}</view>
- </view>
- <view class="section-card" v-if="detail.imageUrls && detail.imageUrls.length > 0">
- <view class="section-title">上传图片</view>
- <view class="upload-grid">
- <view class="img-item" v-for="(url, index) in detail.imageUrls" :key="index"
- @click="previewImage(index)">
- <image :src="url" mode="aspectFill"></image>
- </view>
- </view>
- </view>
- <view class="section-card">
- <view class="section-title">处理状态</view>
- <view class="status-block">
- <view class="status-dot" :class="detail.status === '1' ? 'done' : 'pending'"></view>
- <text class="status-label">{{ detail.status === '1' ? '已处理' : '待处理' }}</text>
- </view>
- </view>
- <view class="section-card" v-if="detail.status === '1'">
- <view class="section-title">处理结果</view>
- <view class="content-block">{{ detail.dealResult || '暂无处理结果说明' }}</view>
- </view>
- <view class="section-card"
- v-if="detail.status === '1' && detail.dealImageUrls && detail.dealImageUrls.length > 0">
- <view class="section-title">处理凭证</view>
- <view class="upload-grid">
- <view class="img-item" v-for="(url, index) in detail.dealImageUrls" :key="'deal-' + index"
- @click="previewDealImage(index)">
- <image :src="url" mode="aspectFill"></image>
- </view>
- </view>
- </view>
- <view class="bottom-placeholder"></view>
- </view>
- <view class="loading-state" v-if="!detail && loading">
- <text>加载中...</text>
- </view>
- </scroll-view>
- </view>
- </template>
- <script>
- import ErpNavBar from '@/components/erp-nav-bar.vue';
- import { getComplaintDetail } from '@/api/system/complaint.js';
- import { getDictByType } from '@/api/system/dict.js';
- export default {
- components: { ErpNavBar },
- data() {
- return {
- id: '',
- loading: false,
- detail: null,
- typeMap: {}
- }
- },
- onLoad(options) {
- this.id = options.id || '';
- this.loadDict().then(() => this.loadDetail());
- },
- methods: {
- goBack() { uni.navigateBack(); },
- async loadDict() {
- try {
- const res = await getDictByType('sys_complaint_type');
- if (res && res.data) {
- const map = {};
- res.data.forEach(d => { map[d.dictValue] = d.dictLabel; });
- this.typeMap = map;
- }
- } catch (e) { /* 忽略字典加载失败 */ }
- },
- async loadDetail() {
- if (!this.id) return;
- this.loading = true;
- try {
- const res = await getComplaintDetail(this.id);
- const data = res.data || res;
- if (data) {
- this.detail = {
- id: data.id,
- feedbackType: data.feedbackType,
- feedbackTypeLabel: this.typeMap[data.feedbackType] || data.feedbackType || '未知类型',
- content: data.content,
- status: data.status || '0',
- createTime: data.createTime,
- imageUrls: data.imageUrls ? data.imageUrls.split(',').filter(u => u) : [],
- dealResult: data.dealResult || '',
- dealImageUrls: data.dealImageUrls ? data.dealImageUrls.split(',').filter(u => u) : []
- };
- }
- } catch (e) {
- console.error('加载反馈详情失败', e);
- uni.showToast({ title: e || '加载反馈详情失败', icon: 'none' });
- } finally {
- this.loading = false;
- }
- },
- previewImage(index) {
- uni.previewImage({
- urls: this.detail.imageUrls,
- current: index
- });
- },
- previewDealImage(index) {
- uni.previewImage({
- urls: this.detail.dealImageUrls,
- current: index
- });
- }
- }
- }
- </script>
- <style scoped>
- .complaint-detail-root {
- width: 100vw;
- height: 100vh;
- background: #f8fafb;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- }
- .scroll-view-container {
- flex: 1;
- height: 0;
- width: 100%;
- }
- .detail-body {
- padding: 30rpx;
- }
- .section-card {
- background: #fff;
- border-radius: 24rpx;
- padding: 40rpx 30rpx;
- margin-bottom: 30rpx;
- box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.02);
- }
- .section-title {
- font-size: 30rpx;
- font-weight: bold;
- color: #1a1a1a;
- margin-bottom: 30rpx;
- border-left: 8rpx solid #C1001C;
- padding-left: 20rpx;
- line-height: 1.2;
- }
- .info-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16rpx 0;
- }
- .info-label {
- font-size: 28rpx;
- color: #999;
- }
- .info-value {
- font-size: 28rpx;
- color: #333;
- }
- .type-tag {
- color: #C1001C;
- font-weight: bold;
- }
- .content-block {
- font-size: 28rpx;
- color: #333;
- line-height: 1.8;
- white-space: pre-wrap;
- word-break: break-all;
- }
- .upload-grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 20rpx;
- }
- .img-item {
- position: relative;
- width: 100%;
- padding-top: 100%;
- border-radius: 16rpx;
- overflow: hidden;
- }
- .img-item image {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- }
- .status-block {
- display: flex;
- align-items: center;
- }
- .status-dot {
- width: 16rpx;
- height: 16rpx;
- border-radius: 50%;
- margin-right: 16rpx;
- }
- .status-dot.pending {
- background: #ff9800;
- }
- .status-dot.done {
- background: #4caf50;
- }
- .status-label {
- font-size: 28rpx;
- font-weight: bold;
- color: #333;
- }
- .bottom-placeholder {
- height: 40rpx;
- }
- .loading-state {
- padding-top: 200rpx;
- display: flex;
- justify-content: center;
- font-size: 28rpx;
- color: #999;
- }
- </style>
|