chuangte_bike_newxcx/page_fenbao/tousu/shlist.vue

596 lines
16 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="page">
<u-navbar title="客户反馈管理" :border-bottom="false" :background="bgc" title-color='#000' title-size='36' back-icon-color="#000"
height='44'></u-navbar>
<view class="fixed-header">
<!-- 温馨提示 -->
<view class="warning-tip">
<u-icon name="info-circle" size="28" style="margin-right: 10rpx;"></u-icon>
温馨提示请在24小时内处理订单反馈否则24小时后平台将自动介入
</view>
<!-- 搜索栏 -->
<view class="search-box">
<view class="search-input">
<u-icon name="search" color="#999" size="32"></u-icon>
<input type="text" placeholder="搜索反馈编号/内容" v-model="searchKeyword" confirm-type="search" @confirm="handleSearch" />
</view>
<view class="search-btn" @click="handleSearch">
搜索
</view>
</view>
<!-- 标签页导航 -->
<view class="tabs-box">
<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>
</view>
<!-- 占位符,高度需要根据 fixed-header 的实际高度调整,或者使用 padding-top -->
<view class="header-placeholder"></view>
<!-- 反馈列表 -->
<view class="list-container">
<view class="card" v-for="(item, index) in filteredList" :key="index" @click="btnxq(item)">
<view class="card-header">
<view class="title-wrap">
<text class="title">{{item.title || '无标题'}}</text>
</view>
<view class="status-tag" :class="'status-' + item.status">
{{ getStatusText(item.status) }}
</view>
</view>
<view class="divider"></view>
<view class="card-body">
<view class="info-row">
<text class="label">反馈编号</text>
<text class="value">{{item.no}}</text>
</view>
<view class="info-row">
<text class="label">反馈原因</text>
<text class="value content-text">{{item.content}}</text>
</view>
<view class="info-row">
<text class="label">反馈时间</text>
<text class="value">{{item.createTime}}</text>
</view>
<view class="info-row" v-if="item.finishTime">
<text class="label">处理时间</text>
<text class="value">{{item.finishTime}}</text>
</view>
<!-- 剩余时间提示 -->
<view class="expire-box" v-if="item.expireTime && !item.finishTime">
<u-icon name="clock" size="28" :color="getRemainingColor(item.expireTime)"></u-icon>
<text class="expire-text" :style="{color: getRemainingColor(item.expireTime)}">
{{ getRemainingText(item.expireTime) }}
</text>
</view>
</view>
<view class="card-footer">
<text class="detail-link">查看详情</text>
<u-icon name="arrow-right" color="#999" size="24"></u-icon>
</view>
</view>
<view class="loading-text" v-if="list.length > 0">
{{ finished ? '没有更多了' : '加载中...' }}
</view>
<view class="no-data" v-if="list.length === 0 && !loading">
<u-image width="300" height="300" src="https://api.ccttiot.com/smartmeter/img/static/uZFUpcz0YLe0fC7iH0q8" mode="aspectFit"></u-image>
<text>暂无反馈记录</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
bgc: {
backgroundColor: "#F7F7F7",
},
activeTab: 'pending',
searchKeyword: '',
pageNum: 1,
pageSize: 20,
total: 0,
list: [],
loading: false,
finished: false,
statusParam: '',
areaId:''
}
},
onLoad(option) {
this.areaId = option.areaId
this.getlist()
},
// 页面触底事件
onReachBottom() {
this.loadMore();
},
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: {
getStatusText(status) {
const map = {
1: '商家处理中',
2: '用户处理中', // 假设
3: '平台处理中',
4: '已完成'
};
// 根据实际业务调整,这里沿用原代码的数字逻辑
// 原代码:
// 1: 商家处理中
// 2: 用户处理中
// 3: 平台处理中
// 4: 已完成
return map[status] || '待处理';
},
getRemainingColor(expireTime) {
const text = this.getRemainingText(expireTime);
if (text === '已逾期' || text === '剩余不足1分钟') return '#FF4D4F';
return '#4C97E7';
},
// 计算剩余时间或已逾期
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) {
if (this.activeTab === tab) return;
this.activeTab = tab;
if (tab === 'pending') {
this.statusParam = '';
} else if (tab === 'processed') {
this.statusParam = '1,3';
} else if (tab === 'rejected') {
this.statusParam = '2,4'; // 原代码逻辑rejected 对应 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&areaId=${this.areaId}`;
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 为对象场景
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;
}
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)) {
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" scoped>
page {
background-color: #F7F7F7;
}
.page {
min-height: 100vh;
background-color: #F7F7F7;
padding-bottom: 40rpx;
}
.fixed-header {
position: fixed;
top: 44px; /* 假设 navbar 高度为 44px */
/* #ifdef H5 */
top: 88rpx;
/* #endif */
left: 0;
width: 100%;
z-index: 100;
background-color: #fff;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
}
.header-placeholder {
height: 200rpx; /* 根据 fixed-header 高度估算 */
}
.warning-tip {
background-color: #FFFBE6;
padding: 16rpx 24rpx;
color: #E6A23C;
font-size: 24rpx;
display: flex;
align-items: center;
}
.search-box {
padding: 20rpx 30rpx;
display: flex;
align-items: center;
background: #fff;
.search-input {
flex: 1;
height: 72rpx;
background: #F5F7FA;
border-radius: 36rpx;
display: flex;
align-items: center;
padding: 0 30rpx;
margin-right: 20rpx;
input {
flex: 1;
font-size: 28rpx;
color: #333;
margin-left: 10rpx;
}
}
.search-btn {
width: 120rpx;
height: 72rpx;
background: linear-gradient(90deg, #4C97E7, #6ab0ff);
border-radius: 36rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 28rpx;
font-weight: 500;
&:active {
opacity: 0.9;
}
}
}
.tabs-box {
display: flex;
background: #fff;
padding: 0 10rpx;
border-bottom: 1rpx solid #f5f5f5;
.tab-item {
flex: 1;
text-align: center;
padding: 24rpx 0;
font-size: 28rpx;
color: #666;
position: relative;
transition: all 0.3s;
&.active {
color: #4C97E7;
font-weight: 600;
font-size: 30rpx;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40rpx;
height: 4rpx;
background: #4C97E7;
border-radius: 4rpx;
}
}
}
}
.list-container {
padding: 24rpx;
}
.card {
background: #FFFFFF;
border-radius: 20rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03);
overflow: hidden;
transition: all 0.3s;
&:active {
transform: scale(0.99);
}
.card-header {
padding: 24rpx 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
.title-wrap {
flex: 1;
margin-right: 20rpx;
.title {
font-size: 30rpx;
font-weight: 600;
color: #333;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
}
}
.status-tag {
font-size: 22rpx;
padding: 4rpx 12rpx;
border-radius: 8rpx;
white-space: nowrap;
&.status-1 { // 商家处理中
background: #EBF4FF;
color: #4C97E7;
}
&.status-2 { // 用户处理中
background: #FFF0E6;
color: #FF8C00;
}
&.status-3 { // 平台处理中
background: #F2EBFF;
color: #7B2BF9;
}
&.status-4 { // 已完成
background: #F0F9EB;
color: #67C23A;
}
}
}
.divider {
height: 1rpx;
background: #f9f9f9;
margin: 0 30rpx;
}
.card-body {
padding: 24rpx 30rpx;
.info-row {
display: flex;
margin-bottom: 16rpx;
font-size: 26rpx;
line-height: 1.5;
&:last-child {
margin-bottom: 0;
}
.label {
color: #999;
width: 140rpx;
flex-shrink: 0;
}
.value {
color: #333;
flex: 1;
&.content-text {
color: #666;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
}
}
.expire-box {
margin-top: 20rpx;
background: #FFFBE6;
padding: 12rpx 20rpx;
border-radius: 8rpx;
display: flex;
align-items: center;
.expire-text {
font-size: 24rpx;
margin-left: 10rpx;
font-weight: 500;
}
}
}
.card-footer {
border-top: 1rpx solid #f9f9f9;
padding: 20rpx 30rpx;
display: flex;
justify-content: flex-end;
align-items: center;
.detail-link {
font-size: 24rpx;
color: #999;
margin-right: 6rpx;
}
}
}
.loading-text {
text-align: center;
color: #999;
font-size: 24rpx;
padding: 30rpx 0;
}
.no-data {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 100rpx;
text {
font-size: 28rpx;
color: #999;
margin-top: 20rpx;
}
}
</style>