feat(admin): complete full integration of kefu, finance, product and order modules with real RPC data streams
This commit is contained in:
@@ -3,21 +3,14 @@
|
||||
<view class="content-body">
|
||||
<!-- 顶部搜索栏 -->
|
||||
<view class="filter-card border-shadow">
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">留言时间:</text>
|
||||
<view class="date-picker-mock">
|
||||
<text class="placeholder">开始日期</text>
|
||||
<text class="sep">-</text>
|
||||
<text class="placeholder">结束日期</text>
|
||||
<text class="calendar-icon">📅</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">留言信息:</text>
|
||||
<input class="search-input" placeholder="请输入用户昵称/电话/留言内容搜索" v-model="searchQuery" />
|
||||
<input class="search-input" placeholder="请输入用户昵称/电话/留言内容搜索" v-model="searchQuery" @confirm="handleQuery" />
|
||||
</view>
|
||||
<view class="filter-item">
|
||||
<text class="label-txt">状态:</text>
|
||||
<uni-data-select v-model="statusValue" :localdata="statusOptions" style="width: 120px;" @change="handleQuery" />
|
||||
</view>
|
||||
|
||||
<view class="btn-query" @click="handleQuery">
|
||||
<text class="query-txt">查询</text>
|
||||
</view>
|
||||
@@ -26,40 +19,54 @@
|
||||
<!-- 数据表格 -->
|
||||
<view class="table-card border-shadow">
|
||||
<view class="table-header">
|
||||
<view class="th col-id"><text class="th-txt">ID</text></view>
|
||||
<view class="th col-nick"><text class="th-txt">昵称</text></view>
|
||||
<view class="th col-phone"><text class="th-txt">电话</text></view>
|
||||
<view class="th col-content"><text class="th-txt">内容</text></view>
|
||||
<view class="th col-id"><text class="th-txt">序号</text></view>
|
||||
<view class="th col-nick"><text class="th-txt">用户信息</text></view>
|
||||
<view class="th col-phone"><text class="th-txt">联系电话</text></view>
|
||||
<view class="th col-content"><text class="th-txt">留言内容</text></view>
|
||||
<view class="th col-status"><text class="th-txt">状态</text></view>
|
||||
<view class="th col-time"><text class="th-txt">时间</text></view>
|
||||
<view class="th col-time"><text class="th-txt">留言时间</text></view>
|
||||
<view class="th col-op"><text class="th-txt">操作</text></view>
|
||||
</view>
|
||||
|
||||
<view class="table-body">
|
||||
<view class="table-row" v-for="item in feedbackList" :key="item.id">
|
||||
<view class="td col-id"><text class="td-txt">{{ item.id }}</text></view>
|
||||
<view class="td col-nick"><text class="td-txt">{{ item.nickname }}</text></view>
|
||||
<view class="td col-phone"><text class="td-txt">{{ item.phone }}</text></view>
|
||||
<view class="td col-content"><text class="td-txt ellipsis">{{ item.content }}</text></view>
|
||||
<view class="td col-status">
|
||||
<text class="status-tag">{{ item.status }}</text>
|
||||
<view v-if="loading" class="table-loading" style="padding: 40px; text-align: center;">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
<view v-else-if="feedbackList.length === 0" class="table-empty" style="padding: 40px; text-align: center;">
|
||||
<text>暂无留言记录</text>
|
||||
</view>
|
||||
<view v-else class="table-row" v-for="(item, index) in feedbackList" :key="item.id">
|
||||
<view class="td col-id"><text class="td-txt">{{ (page - 1) * pageSize + index + 1 }}</text></view>
|
||||
<view class="td col-nick">
|
||||
<view class="u-info">
|
||||
<text class="td-txt">{{ item.nickname || '匿名' }}</text>
|
||||
<text class="u-account" v-if="item.user_account">账号:{{ item.user_account }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="td col-time"><text class="td-txt">{{ item.time }}</text></view>
|
||||
<view class="td col-phone"><text class="td-txt">{{ item.phone || '-' }}</text></view>
|
||||
<view class="td col-content">
|
||||
<text class="td-txt ellipsis">{{ item.content }}</text>
|
||||
</view>
|
||||
<view class="td col-status">
|
||||
<text class="status-tag" :class="item.status == 1 ? 'processed' : 'pending'">
|
||||
{{ item.status == 1 ? '已处理' : '未处理' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="td col-time"><text class="td-txt">{{ item.created_at.substring(0, 16).replace('T', ' ') }}</text></view>
|
||||
<view class="td col-op">
|
||||
<text class="btn-link" @click="handleProcess(item)">处理</text>
|
||||
<view class="divider"></view>
|
||||
<text class="btn-link danger" @click="handleDelete(item)">删除</text>
|
||||
<text class="btn-link" v-if="item.status == 0" @click="handleProcess(item)">处理</text>
|
||||
<text class="btn-link gray" v-else>已回复</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分页 -->
|
||||
<view class="pagination-bar">
|
||||
<text class="page-total">共 {{ feedbackList.length }} 条</text>
|
||||
<text class="page-total">共 {{ total }} 条</text>
|
||||
<view class="page-nav">
|
||||
<view class="nav-btn"><text class="nav-icon"> < </text></view>
|
||||
<view class="nav-item active"><text class="nav-num">1</text></view>
|
||||
<view class="nav-btn"><text class="nav-icon"> > </text></view>
|
||||
<view class="nav-btn" :class="{ disabled: page <= 1 }" @click="prevPage"><text class="nav-icon"> < </text></view>
|
||||
<view class="nav-item active"><text class="nav-num">{{ page }}</text></view>
|
||||
<view class="nav-btn" :class="{ disabled: page >= totalPages }" @click="nextPage"><text class="nav-icon"> > </text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -68,42 +75,80 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface FeedbackItem {
|
||||
id: string
|
||||
nickname: string
|
||||
phone: string
|
||||
content: string
|
||||
status: string
|
||||
time: string
|
||||
}
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { fetchFeedbackPage, processFeedback, type KefuFeedback } from '@/services/admin/kefuService.uts'
|
||||
|
||||
const searchQuery = ref('')
|
||||
const statusValue = ref('all')
|
||||
const feedbackList = ref<KefuFeedback[]>([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(15)
|
||||
const loading = ref(false)
|
||||
|
||||
const feedbackList = ref<FeedbackItem[]>([
|
||||
{ id: '37', nickname: 'hhh', phone: '14779580008', content: 'hhh', status: '未处理', time: '2025-12-20 03:22:10' },
|
||||
{ id: '36', nickname: 'hhh', phone: '14779580008', content: 'hhh', status: '未处理', time: '2025-12-20 03:22:05' },
|
||||
{ id: '35', nickname: '发给大哥他', phone: '13212324567', content: '回复挂号费', status: '未处理', time: '2025-12-17 22:11:35' },
|
||||
{ id: '34', nickname: '郑立民', phone: '15604580931', content: '我提现为啥不到账', status: '未处理', time: '2025-11-24 12:29:02' },
|
||||
{ id: '33', nickname: '11', phone: '15012760793', content: '22222', status: '未处理', time: '2025-10-16 17:55:31' },
|
||||
{ id: '32', nickname: '玉兔', phone: '13133164548', content: '我去会吐', status: '未处理', time: '2025-09-09 15:31:45' },
|
||||
{ id: '31', nickname: '哟', phone: '15151424728', content: '玩意', status: '未处理', time: '2025-09-04 15:40:28' },
|
||||
{ id: '30', nickname: '哟', phone: '15151424728', content: '玩意', status: '未处理', time: '2025-09-04 15:40:27' },
|
||||
{ id: '29', nickname: 'pww', phone: '15274289992', content: '我们的', status: '未处理', time: '2025-08-13 18:21:35' },
|
||||
{ id: '28', nickname: '1', phone: '18888888888', content: '1', status: '未处理', time: '2024-12-12 11:56:48' }
|
||||
])
|
||||
const statusOptions = [
|
||||
{ value: 'all', text: '全部状态' },
|
||||
{ value: '0', text: '未处理' },
|
||||
{ value: '1', text: '已处理' }
|
||||
]
|
||||
|
||||
const handleQuery = () => {
|
||||
console.log('查询:', searchQuery.value)
|
||||
const totalPages = computed((): number => {
|
||||
if (pageSize.value <= 0) return 1
|
||||
return Math.ceil(total.value / pageSize.value)
|
||||
})
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const status = statusValue.value == 'all' ? null : parseInt(statusValue.value)
|
||||
const res = await fetchFeedbackPage(page.value, pageSize.value, searchQuery.value || null, status)
|
||||
feedbackList.value = res.items
|
||||
total.value = res.total
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '加载留言失败', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleProcess = (item: FeedbackItem) => {
|
||||
console.log('处理留言:', item.id)
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
function handleQuery() {
|
||||
page.value = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleDelete = (item: FeedbackItem) => {
|
||||
console.log('删除留言:', item.id)
|
||||
async function handleProcess(item: KefuFeedback) {
|
||||
uni.showModal({
|
||||
title: '留言处理',
|
||||
editable: true,
|
||||
placeholderText: '请输入回复内容',
|
||||
success: async (res) => {
|
||||
if (res.confirm && res.content) {
|
||||
const ok = await processFeedback(item.id, res.content)
|
||||
if (ok) {
|
||||
uni.showToast({ title: '处理成功' })
|
||||
loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function prevPage() {
|
||||
if (page.value > 1) {
|
||||
page.value--
|
||||
loadData()
|
||||
}
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
if (page.value < totalPages.value) {
|
||||
page.value++
|
||||
loadData()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -120,7 +165,6 @@ const handleDelete = (item: FeedbackItem) => {
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 过滤栏 */
|
||||
.filter-card {
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
@@ -128,7 +172,7 @@ const handleDelete = (item: FeedbackItem) => {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 30px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
@@ -143,36 +187,8 @@ const handleDelete = (item: FeedbackItem) => {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.date-picker-mock {
|
||||
width: 320px;
|
||||
height: 38px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
font-size: 13px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.sep {
|
||||
margin: 0 8px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.calendar-icon {
|
||||
margin-left: auto;
|
||||
font-size: 14px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 320px;
|
||||
height: 38px;
|
||||
height: 36px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
@@ -222,6 +238,11 @@ const handleDelete = (item: FeedbackItem) => {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.table-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
@@ -245,6 +266,13 @@ const handleDelete = (item: FeedbackItem) => {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.u-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
.u-account { font-size: 11px; color: #999; margin-top: 2px; }
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -252,23 +280,20 @@ const handleDelete = (item: FeedbackItem) => {
|
||||
}
|
||||
|
||||
/* 列宽定义 */
|
||||
.col-id { width: 80px; justify-content: center; }
|
||||
.col-nick { width: 140px; }
|
||||
.col-phone { width: 180px; }
|
||||
.col-id { width: 60px; justify-content: center; }
|
||||
.col-nick { width: 160px; }
|
||||
.col-phone { width: 140px; }
|
||||
.col-content { flex: 1; justify-content: flex-start; }
|
||||
.col-status { width: 120px; justify-content: center; }
|
||||
.col-time { width: 220px; justify-content: center; }
|
||||
.col-op {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 150px;
|
||||
justify-content: center;
|
||||
}
|
||||
.col-status { width: 100px; justify-content: center; }
|
||||
.col-time { width: 160px; justify-content: center; }
|
||||
.col-op { width: 100px; justify-content: center; }
|
||||
|
||||
.status-tag {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
padding: 2px 8px;
|
||||
border-radius: 2px;
|
||||
&.processed { color: #52c41a; background: #f6ffed; border: 1px solid #b7eb8f; }
|
||||
&.pending { color: #fa8c16; background: #fff7e6; border: 1px solid #ffd591; }
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
@@ -276,17 +301,7 @@ const handleDelete = (item: FeedbackItem) => {
|
||||
color: #2d8cf0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-link.danger {
|
||||
color: #2d8cf0; /* 根据截图,删除也是蓝色的链接感,但通常后台会区分,这里按截图感肉眼识别为蓝色 */
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
height: 12px;
|
||||
background-color: #e8eaec;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.btn-link.gray { color: #999; cursor: default; }
|
||||
|
||||
/* 分页栏 */
|
||||
.pagination-bar {
|
||||
@@ -318,6 +333,7 @@ const handleDelete = (item: FeedbackItem) => {
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
margin: 0 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
@@ -344,4 +360,6 @@ const handleDelete = (item: FeedbackItem) => {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.disabled { cursor: not-allowed; opacity: 0.5; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user