1轮修复

This commit is contained in:
2026-01-20 16:17:39 +08:00
parent 0bf7361672
commit 8ab6107f25
23 changed files with 2587 additions and 612 deletions

View File

@@ -2,34 +2,53 @@
<!-- #ifdef APP -->
<scroll-view style="flex:1">
<!-- #endif -->
<view class="page">
<!-- 自定义导航栏 -->
<view class="nav" :style="{ paddingTop: headerPaddingTop + 'px', height: headerTotalHeight + 'px' }">
<view class="nav-back" @tap="goBack">
<view class="nav-back-icon"></view>
<view class="page">
<!-- 自定义导航栏 -->
<view class="nav" :style="{ paddingTop: headerPaddingTop + 'px', height: headerTotalHeight + 'px' }">
<view class="nav-back" @tap="goBack">
<view class="nav-back-icon"></view>
</view>
<text class="nav-title">我的工单</text>
<view class="nav-capsule"></view>
</view>
<text class="nav-title">我的工单</text>
<view class="nav-capsule"></view>
</view>
<!-- Tab切换 -->
<view class="tabs" :style="{ marginTop: headerTotalHeight + 'px' }">
<view class="tab-item" :class="{ active: activeTab === 'all' }" @tap="changeTab('all')">
<text class="tab-text">全部</text>
<!-- Tab切换 -->
<view class="tabs" :style="{ marginTop: headerTotalHeight + 'px' }">
<view class="tab-item" :class="{ active: activeTab === 'all' }" @tap="changeTab('all')">
<text class="tab-text">全部</text>
</view>
<view class="tab-item" :class="{ active: activeTab === 'pending' }" @tap="changeTab('pending')">
<text class="tab-text">待处理</text>
</view>
<view class="tab-item" :class="{ active: activeTab === 'processing' }" @tap="changeTab('processing')">
<text class="tab-text">处理中</text>
</view>
<view class="tab-item" :class="{ active: activeTab === 'done' }" @tap="changeTab('done')">
<text class="tab-text">已完成</text>
</view>
</view>
<view class="tab-item" :class="{ active: activeTab === 'pending' }" @tap="changeTab('pending')">
<text class="tab-text">待处理</text>
<!-- 用户筛选组件仅非guest用户可见 -->
<view class="filter-section" v-if="!isGuest" :style="{ marginTop: headerTotalHeight + 44 + 'px' }">
<view class="filter-label">选择用户:</view>
<view class="filter-content">
<view class="guest-selector" @tap="showGuestSelector = true">
<text v-if="selectedGuestId" class="selected-guest">
{{ guestsList.find(g => g.userId === selectedGuestId)?.name || '请选择用户' }}
</text>
<text v-else class="placeholder">请选择用户</text>
<view class="selector-arrow">▼</view>
</view>
<view class="btn-group">
<view class="reset-btn" @tap="clearGuestSelect">重置</view>
<view class="query-btn" @tap="handleQuery">查询</view>
</view>
</view>
</view>
<view class="tab-item" :class="{ active: activeTab === 'processing' }" @tap="changeTab('processing')">
<text class="tab-text">处理中</text>
</view>
<view class="tab-item" :class="{ active: activeTab === 'done' }" @tap="changeTab('done')">
<text class="tab-text">已完成</text>
</view>
</view>
<!-- 工单列表 -->
<scroll-view class="list" scroll-y="true">
<!-- 工单列表 -->
<scroll-view class="list" scroll-y="true"
:style="{ marginTop: headerTotalHeight + 44 + (isGuest ? 0 : 44) + 'px' }"
@scrolltolower="handleScrollToLower">
<view class="card" v-for="(item, index) in filteredOrders" :key="index">
<view class="card-header">
<view class="card-title">
@@ -74,128 +93,569 @@
<view class="empty-state" v-if="filteredOrders.length === 0">
<text class="empty-text">暂无工单数据</text>
</view>
<!-- 加载更多提示 -->
<view class="load-more" v-if="loading">
<text class="load-text">加载中...</text>
</view>
<view class="load-more" v-else-if="!hasMore && filteredOrders.length > 0">
<text class="load-text">没有更多数据了</text>
</view>
</scroll-view>
</view>
<!-- 用户选择弹窗 -->
<view class="modal-mask" v-if="showGuestSelector" @tap="showGuestSelector = false"></view>
<view class="modal-content" v-if="showGuestSelector">
<view class="modal-header">
<text class="modal-title">选择用户</text>
</view>
<view class="modal-body">
<view class="guest-item" v-for="guest in guestsList" :key="guest.userId"
@tap="handleGuestSelect(guest)">
<text class="guest-name">{{ guest.name }} {{ guest.phone || '无电话' }}</text>
</view>
<view class="empty-item" v-if="guestsList.length === 0">
<text>暂无可选用户</text>
</view>
</view>
</view>
</view>
<!-- #ifdef APP -->
</scroll-view>
<!-- #endif -->
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import type { TbWorkcaseDTO } from '@/types/workcase'
import { workcaseAPI } from '@/api/workcase/workcase'
import { ref, computed, onMounted } from 'vue'
import type { TbWorkcaseDTO, TbGuestDTO, PageRequest, PageParam } from '@/types'
import { workcaseAPI } from '@/api/workcase/workcase'
import { guestAPI } from '@/api/sys/guest'
// 响应式数据
const headerPaddingTop = ref<number>(44)
const headerTotalHeight = ref<number>(88)
const activeTab = ref<string>('all')
const orders = ref<TbWorkcaseDTO[]>([])
const loading = ref<boolean>(false)
const error = ref<string>('')
// 响应式数据
const headerPaddingTop = ref<number>(44)
const headerTotalHeight = ref<number>(88)
const activeTab = ref<string>('all')
const orders = ref<TbWorkcaseDTO[]>([])
const loading = ref<boolean>(false)
const loadingUsers = ref<boolean>(false)
const error = ref<string>('')
// 计算属性根据tab筛选工单
const filteredOrders = computed(() => {
if (activeTab.value === 'all') {
// 分页相关
const currentPage = ref<number>(1)
const pageSize = ref<number>(10)
const total = ref<number>(0)
const hasMore = ref<boolean>(true)
// 用户筛选相关
const isGuest = ref(true)
const guestsList = ref<TbGuestDTO[]>([])
const selectedGuestId = ref<string>('')
const showGuestSelector = ref<boolean>(false)
// 计算属性直接返回分页数据API已处理筛选
const filteredOrders = computed(() => {
return orders.value
}
return orders.value.filter(o => o.status === activeTab.value)
})
})
// 生命周期
onMounted(() => {
const windowInfo = uni.getWindowInfo()
const statusBarHeight = windowInfo.statusBarHeight || 44
// #ifdef MP-WEIXIN
try {
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
headerPaddingTop.value = menuButtonInfo.top
headerTotalHeight.value = menuButtonInfo.bottom + 8
} catch (e) {
// 生命周期
onMounted(() => {
const windowInfo = uni.getWindowInfo()
const statusBarHeight = windowInfo.statusBarHeight || 44
// #ifdef MP-WEIXIN
try {
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
headerPaddingTop.value = menuButtonInfo.top
headerTotalHeight.value = menuButtonInfo.bottom + 8
} catch (e) {
headerPaddingTop.value = statusBarHeight
headerTotalHeight.value = statusBarHeight + 44
}
// #endif
// #ifndef MP-WEIXIN
headerPaddingTop.value = statusBarHeight
headerTotalHeight.value = statusBarHeight + 44
}
// #endif
// #ifndef MP-WEIXIN
headerPaddingTop.value = statusBarHeight
headerTotalHeight.value = statusBarHeight + 44
// #endif
// 调用API获取工单列表
loadWorkcaseList()
})
// #endif
// 加载工单列表
async function loadWorkcaseList() {
loading.value = true
error.value = ''
try {
const filter: TbWorkcaseDTO = {}
if (activeTab.value !== 'all') {
filter.status = activeTab.value as TbWorkcaseDTO['status']
// 检查用户类型
checkUserType()
// 调用API获取工单列表
loadWorkcaseList()
})
// 检查用户类型
function checkUserType() {
try {
const userInfo = uni.getStorageSync('userInfo')
const parsedUserInfo = typeof userInfo === 'string' ? JSON.parse(userInfo) : userInfo
isGuest.value = parsedUserInfo.status === 'guest'
// 如果是非guest用户加载可选人员列表
if (!isGuest.value) {
loadGuestsList()
}
} catch (error) {
console.error('检查用户类型失败:', error)
isGuest.value = true
}
const res = await workcaseAPI.getWorkcaseList(filter)
if (res.success && res.dataList) {
orders.value = res.dataList || []
} else {
error.value = res.message || '加载失败'
}
// 加载可选人员列表
async function loadGuestsList() {
loadingUsers.value = true
try {
const res = await guestAPI.listGuest()
if (res.success && res.dataList) {
guestsList.value = res.dataList
}
} catch (error) {
console.error('加载可选人员列表失败:', error)
} finally {
loadingUsers.value = false
}
}
// 加载工单列表 - 滚动分页查询
async function loadWorkcaseList(isLoadMore = false) {
// 如果正在加载或没有更多数据,直接返回
if (loading.value || (!isLoadMore && !hasMore.value)) return
loading.value = true
error.value = ''
try {
const filter: TbWorkcaseDTO = {}
if (activeTab.value !== 'all') {
filter.status = activeTab.value as TbWorkcaseDTO['status']
}
// 如果是非guest用户且选择了特定用户添加userId筛选
if (!isGuest.value && selectedGuestId.value) {
filter.userId = selectedGuestId.value
}
// 计算当前页码
const currentPageNum = isLoadMore ? currentPage.value + 1 : 1
const pageParam: PageParam = {
page: currentPageNum,
pageSize: pageSize.value
}
const pageRequest: PageRequest<TbWorkcaseDTO> = {
filter,
pageParam
}
const res = await workcaseAPI.getWorkcasePage(pageRequest)
if (res.success) {
const newOrders = res.dataList || res.pageDomain?.dataList || []
total.value = res.pageDomain?.pageParam?.total || 0
// 根据是否加载更多来处理数据
if (isLoadMore) {
// 加载更多:追加数据
orders.value = [...orders.value, ...newOrders]
currentPage.value++
} else {
// 刷新:替换数据
orders.value = newOrders
currentPage.value = 1
}
// 判断是否还有更多数据
hasMore.value = orders.value.length < total.value
} else {
error.value = res.message || '加载失败'
uni.showToast({
title: res.message || '加载失败',
icon: 'error'
})
// 如果是加载更多失败保持hasMore不变
if (!isLoadMore) {
hasMore.value = false
}
}
} catch (e) {
error.value = '网络错误,请稍后重试'
uni.showToast({
title: res.message || '加载失败',
title: '网络错误,请稍后重试',
icon: 'error'
})
// 如果是加载更多失败保持hasMore不变
if (!isLoadMore) {
hasMore.value = false
}
} finally {
loading.value = false
}
} catch (e) {
error.value = '网络错误,请稍后重试'
uni.showToast({
title: '网络错误,请稍后重试',
icon: 'error'
}
// 处理滚动到底部事件
function handleScrollToLower() {
if (hasMore.value && !loading.value) {
loadWorkcaseList(true)
}
}
// 处理用户选择
function handleGuestSelect(guest : TbGuestDTO) {
selectedGuestId.value = guest.userId || ''
showGuestSelector.value = false
// 重置分页状态
hasMore.value = true
loadWorkcaseList() // 根据选中的用户重新加载工单列表
}
// 清除用户选择
function clearGuestSelect() {
selectedGuestId.value = ''
// 重置分页状态
hasMore.value = true
loadWorkcaseList() // 重新加载所有工单列表
}
// 查询工单列表
function handleQuery() {
// 重置分页状态
hasMore.value = true
loadWorkcaseList() // 根据当前筛选条件重新加载工单列表
}
// 切换Tab
function changeTab(tab : string) {
activeTab.value = tab
// 重置分页状态
hasMore.value = true
loadWorkcaseList()
}
// 获取状态样式类
function getStatusClass(status ?: string) : string {
switch (status) {
case 'pending': return 'status-pending'
case 'processing': return 'status-processing'
case 'done': return 'status-done'
default: return 'status-pending'
}
}
// 获取状态文本
function getStatusText(status ?: string) : string {
switch (status) {
case 'pending': return '待处理'
case 'processing': return '处理中'
case 'done': return '已完成'
default: return '未知'
}
}
// 返回上一页
function goBack() {
uni.navigateBack()
}
// 跳转到工单详情
function goDetail(workcaseId ?: string) {
if (!workcaseId) return
uni.navigateTo({
url: `/pages/workcase/workcaseDetail/workcaseDetail?workcaseId=${workcaseId}`
})
} finally {
loading.value = false
}
}
// 切换Tab
function changeTab(tab: string) {
activeTab.value = tab
loadWorkcaseList()
}
// 获取状态样式类
function getStatusClass(status?: string): string {
switch (status) {
case 'pending': return 'status-pending'
case 'processing': return 'status-processing'
case 'done': return 'status-done'
default: return 'status-pending'
}
}
// 获取状态文本
function getStatusText(status?: string): string {
switch (status) {
case 'pending': return '待处理'
case 'processing': return '处理中'
case 'done': return '已完成'
default: return '未知'
}
}
// 返回上一页
function goBack() {
uni.navigateBack()
}
// 跳转到工单详情
function goDetail(workcaseId?: string) {
if (!workcaseId) return
uni.navigateTo({
url: `/pages/workcase/workcaseDetail/workcaseDetail?workcaseId=${workcaseId}`
})
}
</script>
<style lang="scss" scoped>
@import "./workcaseList.scss";
@import "./workcaseList.scss";
// 用户筛选组件样式
.filter-section {
position: fixed;
left: 0;
right: 0;
height: 44px;
background: #fff;
border-bottom: 1px solid #eee;
padding: 0 16px;
display: flex;
flex-direction: row;
align-items: center;
z-index: 10;
}
.filter-label {
font-size: 14px;
color: #333;
margin-right: 8px;
}
.filter-content {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.guest-selector {
flex: 1;
flex-direction: row;
height: 32px;
border: 1px solid #ddd;
border-radius: 16px;
padding: 0 12px;
display: flex;
align-items: center;
justify-content: space-between;
background: #f9f9f9;
cursor: pointer;
transition: all 0.3s ease;
}
.guest-selector:active {
background: #e9e9e9;
border-color: #bbb;
}
.selected-guest {
font-size: 14px;
color: #333;
}
.placeholder {
font-size: 14px;
color: #999;
}
.selector-arrow {
font-size: 12px;
color: #666;
transition: transform 0.3s ease;
}
.filter-section:active .selector-arrow {
transform: rotate(180deg);
}
/* 按钮组样式 */
.btn-group {
display: flex;
flex-direction: row;
gap: 8px;
}
.reset-btn,
.query-btn {
height: 32px;
padding: 0 16px;
border-radius: 16px;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
}
.reset-btn {
background: #f5f5f5;
border: 1px solid #ddd;
color: #666;
}
.reset-btn:active {
background: #e9e9e9;
border-color: #bbb;
}
.query-btn {
background: #1989fa;
border: 1px solid #1989fa;
color: #fff;
}
.query-btn:active {
background: #0066cc;
border-color: #0066cc;
}
// 弹窗样式
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
animation: fadeIn 0.3s ease;
}
.modal-content {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 400px;
background: #fff;
border-radius: 20px;
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.25);
z-index: 1001;
animation: slideUp 0.3s ease;
overflow: hidden;
}
/* 弹窗动画 */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translate(-50%, -45%);
}
to {
opacity: 1;
transform: translate(-50%, -50%);
}
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 20px;
border-bottom: 1px solid #f0f0f0;
background: linear-gradient(135deg, #fafafa 0%, #f5f5f5 100%);
}
.modal-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.modal-close {
font-size: 24px;
color: #999;
cursor: pointer;
transition: all 0.3s ease;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.modal-close:active {
background: #f0f0f0;
color: #666;
transform: rotate(90deg);
}
.modal-body {
max-height: 350px;
overflow-y: auto;
padding: 0;
}
/* 滚动条样式 */
.modal-body::-webkit-scrollbar {
width: 6px;
}
.modal-body::-webkit-scrollbar-track {
background: #f1f1f1;
}
.modal-body::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.modal-body::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
.guest-item {
display: flex;
flex-direction: column;
padding: 16px 20px;
cursor: pointer;
transition: all 0.3s ease;
border-bottom: 1px solid #f5f5f5;
}
.guest-item:last-child {
border-bottom: none;
}
.guest-item:active {
background-color: #f8f8f8;
transform: translateX(5px);
}
.guest-item:active .guest-name {
color: #1989fa;
}
.guest-name {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 6px;
transition: color 0.3s ease;
}
.guest-phone {
font-size: 14px;
color: #666;
display: flex;
align-items: center;
gap: 6px;
}
.guest-phone::before {
content: "📞";
font-size: 12px;
}
.empty-item {
padding: 30px 20px;
text-align: center;
color: #999;
font-size: 14px;
background: #fafafa;
}
.empty-item::before {
content: "👤";
font-size: 48px;
display: block;
margin-bottom: 12px;
opacity: 0.5;
}
/* 加载更多样式 */
.load-more {
padding: 20px;
text-align: center;
background: #fff;
border-top: 1px solid #eee;
margin-top: 0;
}
.load-text {
font-size: 14px;
color: #999;
}
</style>