chuangte_bike_newxcx/page_fenbao/tousu/shlist.vue
2025-08-30 17:38:15 +08:00

388 lines
12 KiB
Vue
Raw Permalink 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="pages">
<u-navbar title="客户反馈管理" :border-bottom="false" :background="bgc" title-color='#fff' back-icon-color="#fff"
height='44'></u-navbar>
<!-- 温馨提示 -->
<view class="warning-tip">
温馨提示请在24小时内处理订单反馈否则24小时后平台将自动介入
</view>
<!-- 搜索栏 -->
<view class="search-section">
<view class="search-bar">
<view class="search-input">
<u-icon name="search" color="#333" size="28" style="margin-right: 20rpx;"></u-icon>
<input type="text" placeholder="搜索" v-model="searchKeyword" />
</view>
<view class="search-btn" @click="handleSearch">搜索</view>
</view>
</view>
<!-- 标签页导航 -->
<view class="tab-navigation">
<view
class="tab-item"
:class="{ active: activeTab === 'pending' }"
@click="switchTab('pending')"
>
全部
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'processed' }"
@click="switchTab('processed')"
>
处理中
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'rejected' }"
@click="switchTab('rejected')"
>
已处理
</view>
</view>
<!-- 反馈列表 -->
<scroll-view class="feedback-list" @scrolltolower="loadMore" scroll-y>
<view class="feedback-item" v-for="(item, index) in filteredList" :key="index" @click="btnxq(item)">
<view class="row header" style="display: flex;justify-content: space-between;align-items: center;">
<view class="label strong">{{item.title}}</view>
<view class="">
<view class="" v-if="item.status == 1">
商家处理中 〉
</view>
<view class="" v-if="item.status == 2">
用户处理中 〉
</view>
<view class="" v-if="item.status == 3">
平台处理中 〉
</view>
<view class="" v-if="item.status == 4">
已完成 〉
</view>
</view>
</view>
<view class="row">
<view class="label">反馈编号</view>
<view class="value">{{item.no}}</view>
</view>
<view class="row">
<view class="label">反馈原因</view>
<view class="value">{{item.content}}</view>
</view>
<view class="row">
<view class="label">反馈时间</view>
<view class="value">{{item.createTime}}</view>
</view>
<view class="row" v-if="item.finishTime">
<view class="label">处理时间</view>
<view class="value">{{item.finishTime}}</view>
</view>
<view class="row" v-if="item.expireTime && !item.finishTime">
<view class="label">剩余时间</view>
<view class="value c-red">{{ getRemainingText(item.expireTime) }}</view>
</view>
</view>
<view class="no-more" v-if="filteredList.length === 0">
暂无反馈记录
</view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
bgc: {
backgroundColor: "#4C97E7",
},
activeTab: 'pending',
searchKeyword: '',
pageNum: 1,
pageSize: 20,
total: 0,
list: [],
loading: false,
finished: false,
statusParam: ''
}
},
onLoad() {
this.getlist()
},
computed: {
filteredList() {
let source = this.list || [];
if (this.searchKeyword) {
const kw = this.searchKeyword.trim().toLowerCase();
source = source.filter(item => {
const text = `${item.no || item.id || ''}${item.title || ''}${item.content || ''}`.toLowerCase();
return text.includes(kw);
});
}
return source;
},
pendingCount() {
return (this.list || []).filter(i => this.normalizeStatus(i.status) === 'pending').length;
}
},
methods: {
// 计算剩余时间或已逾期
getRemainingText(expireTime){
if(!expireTime){ return '--' }
let expireMs = NaN
if (typeof expireTime === 'number') {
expireMs = expireTime
} else if (typeof expireTime === 'string') {
const str = expireTime.replace(/-/g,'/').replace(/T/,' ').replace(/\.\d{3}Z?$/,'')
expireMs = new Date(str).getTime()
}
if (!expireMs || isNaN(expireMs)) { return '--' }
const now = this.nowTs || Date.now()
let diff = expireMs - now
if (diff <= 0) { return '已逾期' }
const minute = 60000
const hour = 60 * minute
const day = 24 * hour
const d = Math.floor(diff / day); diff %= day
const h = Math.floor(diff / hour); diff %= hour
const m = Math.floor(diff / minute)
if (d > 0) { return `剩余${d}天${h}小时` }
if (h > 0) { return `剩余${h}小时${m}分钟` }
if (m > 0) { return `剩余${m}分钟` }
return '剩余不足1分钟'
},
// 切换tab
switchTab(tab) {
this.activeTab = tab;
if (tab === 'pending') {
this.statusParam = '';
} else if (tab === 'processed') {
this.statusParam = '1,3';
} else if (tab === 'rejected') {
this.statusParam = '2,4';
} else {
this.statusParam = '';
}
this.pageNum = 1;
this.list = [];
this.finished = false;
this.getlist();
},
// 点击跳转到商户投诉详情
btnxq(item){
console.log(item);
uni.navigateTo({
url:'/page_fenbao/tousu/shtsxq?id=' + item.id
})
},
// 正常化状态
normalizeStatus(status) {
if (status === undefined || status === null) return 'pending';
const s = String(status).toLowerCase();
if (['0', 'pending', 'wait', 'waiting', '未处理'].includes(s)) return 'pending';
if (['1', 'processed', 'done', '已处理', '已完成'].includes(s)) return 'processed';
if (['2', 'rejected', 'refused', '已拒绝'].includes(s)) return 'rejected';
return 'pending';
},
// 搜索
handleSearch() {
this.pageNum = 1;
this.list = [];
this.finished = false;
this.getlist();
},
// 触底加载
loadMore() {
if (this.loading || this.finished) return;
this.pageNum += 1;
this.getlist();
},
// 请求投诉列表
getlist() {
if (this.loading) return;
this.loading = true;
const base = `/bst/complaint/list?pageNum=${this.pageNum}&pageSize=${this.pageSize}&orderByColumn=createTime&isAsc=desc`;
const url = this.statusParam ? `${base}&statusList=${encodeURIComponent(this.statusParam)}` : base;
this.$u.get(url).then((res) => {
if (!res) return;
// 优先解析顶层 rows/total
if (Array.isArray(res.rows)) {
const rows = res.rows;
const total = res.total || res.totalCount || res.count || rows.length || 0;
const mapped = (rows || []).map(r => ({
id: r.id || r.complaintId || r.feedbackId || r.no,
no: r.no || r.feedbackId || r.complaintId || r.id,
title: r.title || r.reason || r.typeName || '',
content: r.content || r.reason || '',
createTime: r.createTime || r.create_time || r.createdAt || r.create_at || r.applyTime,
finishTime: r.finishTime || r.processTime || r.process_time || r.updatedAt || r.updateTime,
expireTime:r.expireTime,
status: r.status
}));
this.total = total;
this.list = this.pageNum === 1 ? mapped : this.list.concat(mapped);
if (this.list.length >= this.total || mapped.length < this.pageSize) {
this.finished = true;
}
return;
}
// 顶层 rows 为对象场景(含 list/rows/records/items/data
if (res.rows && typeof res.rows === 'object' && !Array.isArray(res.rows)) {
let rows = res.rows.list || res.rows.rows || res.rows.records || res.rows.items || res.rows.data || [];
const total = res.total || res.totalCount || res.rows.total || res.rows.totalCount || rows.length || 0;
if (!Array.isArray(rows)) rows = [];
const mapped = (rows || []).map(r => ({
id: r.id || r.complaintId || r.feedbackId || r.no,
no: r.no || r.feedbackId || r.complaintId || r.id,
title: r.title || r.reason || r.typeName || '',
content: r.content || r.reason || '',
createTime: r.createTime || r.create_time || r.createdAt || r.create_at || r.applyTime,
finishTime: r.finishTime || r.processTime || r.process_time || r.updatedAt || r.updateTime,
status: r.status
}));
this.total = total;
this.list = this.pageNum === 1 ? mapped : this.list.concat(mapped);
if (this.list.length >= this.total || mapped.length < this.pageSize) {
this.finished = true;
}
if (typeof uni !== 'undefined' && uni && uni.showToast) {
uni.showToast({ title: `已加载${this.list.length}条`, icon: 'none', duration: 1200 });
}
return;
}
const data = res.data !== undefined ? res.data : (res.result !== undefined ? res.result : (res.body !== undefined ? res.body : res));
// 计算总数
let total = 0;
if (data && typeof data === 'object' && !Array.isArray(data)) {
total = data.total || data.totalCount || data.totalRow || res.total || res.count || 0;
}
// 提取列表
let rows = [];
if (Array.isArray(data)) {
rows = data;
if (!total) total = rows.length;
} else if (data && typeof data === 'object') {
rows = data.list || data.rows || data.records || data.items || data.data || [];
if (!Array.isArray(rows)) {
// 顶层返回
rows = res.list || res.rows || res.records || res.items || [];
if (!Array.isArray(rows)) {
// 在 data 的任意键里找数组
Object.keys(data).forEach(k => { if (Array.isArray(data[k])) rows = data[k]; });
}
}
if (!total) total = data.total || data.totalCount || data.totalRow || rows.length || 0;
}
const mapped = (rows || []).map(r => ({
id: r.id || r.complaintId || r.feedbackId || r.no,
no: r.no || r.feedbackId || r.complaintId || r.id,
title: r.title || r.reason || r.typeName || '',
content: r.content || r.reason || '',
createTime: r.createTime || r.create_time || r.createdAt || r.create_at || r.applyTime,
finishTime: r.finishTime || r.processTime || r.process_time || r.updatedAt || r.updateTime,
status: r.status
}));
this.total = total;
this.list = this.pageNum === 1 ? mapped : this.list.concat(mapped);
if (this.list.length >= this.total || mapped.length < this.pageSize) {
this.finished = true;
}
}).finally(() => {
this.loading = false;
});
}
}
}
</script>
<style lang="scss">
page {
background-color: #f7f7f7;
}
.pages {
min-height: 100vh;
background-color: #f7f7f7;
}
.warning-tip {
background-color: #fff;
padding: 16rpx 24rpx;
color: #999;
font-size: 24rpx;
border-bottom: 1rpx solid #eee;
}
.search-section {
background-color: #fff;
padding: 20rpx 24rpx;
border-bottom: 1rpx solid #f0f0f0;
.search-bar {
display: flex;
align-items: center;
.search-input {
flex: 1;
display: flex;
align-items: center;
border: 1rpx solid #d8e8d8;
border-radius: 40rpx;
padding: 0 20rpx;
background-color: #fff;
height: 68rpx;
.search-icon {
margin-right: 10rpx;
color: #b5b5b5;
font-size: 28rpx;
}
input { flex: 1; font-size: 26rpx; color: #333; }
}
.search-btn {
margin-left: 16rpx;
background-color: #4C97E7;
color: #fff;
padding: 16rpx 28rpx;
border-radius: 12rpx;
font-size: 26rpx;
}
}
}
.tab-navigation {
background-color: #fff;
display: flex;
border-bottom: 1rpx solid #eee;
.tab-item {
flex: 1;
text-align: center;
padding: 26rpx 0;
font-size: 28rpx;
color: #444;
position: relative;
&.active { color: #4C97E7; font-weight: 600; }
&.active::after {
content: '';
position: absolute;
bottom: 0; left: 50%; transform: translateX(-50%);
width: 80rpx; height: 4rpx; background-color: #4C97E7; border-radius: 2rpx;
}
}
}
.feedback-list {
height: 70vh;
background-color: #f0f0f0;
.row { display: flex; align-items: center; padding: 10rpx 24rpx; }
.feedback-item { border-bottom: 1rpx solid #fff; margin-top: 20rpx;background: #fff; }
.feedback-item .row.header { padding-top: 24rpx; border-bottom: 1px solid #f0f0f0;}
.label { width: 160rpx; color: #9a9a9a; font-size: 26rpx; }
.label.strong { width: auto; color: #333; font-weight: 600; margin-right: 8rpx; }
.value { flex: 1; color: #333; font-size: 28rpx; }
.value.id { color: #222; font-weight: 600; }
.status-text { font-size: 24rpx; }
.c-orange { color: #ff8c00; }
.c-green { color: #23ad5b; }
.c-red { color: #e74c3c; }
.c-gray { color: #8b8b8b; }
.no-more { text-align: center; color: #999; font-size: 26rpx; padding: 40rpx 0; }
}
</style>