508 lines
16 KiB
Plaintext
508 lines
16 KiB
Plaintext
|
|
<template>
|
|||
|
|
<view class="detail-container">
|
|||
|
|
<scroll-view class="detail-content" scroll-y="true">
|
|||
|
|
<!-- 工单基本信息 -->
|
|||
|
|
<view class="info-card">
|
|||
|
|
<view class="card-header">
|
|||
|
|
<text class="card-title">基本信息</text>
|
|||
|
|
<view class="status-tag" :class="getStatusClass(workcase.status)">
|
|||
|
|
<text class="status-text">{{workcase.statusText}}</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<view class="info-item">
|
|||
|
|
<text class="label">工单标题</text>
|
|||
|
|
<text class="value">{{workcase.title}}</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<view class="info-item">
|
|||
|
|
<text class="label">工单编号</text>
|
|||
|
|
<text class="value">{{workcase.number}}</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<view class="info-item">
|
|||
|
|
<text class="label">问题分类</text>
|
|||
|
|
<text class="value">{{workcase.category}}</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<view class="info-item">
|
|||
|
|
<text class="label">紧急程度</text>
|
|||
|
|
<text class="value priority" :class="getPriorityClass(workcase.priority)">
|
|||
|
|
{{workcase.priority}}
|
|||
|
|
</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<view class="info-item">
|
|||
|
|
<text class="label">联系方式</text>
|
|||
|
|
<text class="value">{{workcase.contact}}</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<view class="info-item">
|
|||
|
|
<text class="label">创建时间</text>
|
|||
|
|
<text class="value">{{formatDateTime(workcase.createTime)}}</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 问题描述 -->
|
|||
|
|
<view class="info-card">
|
|||
|
|
<view class="card-header">
|
|||
|
|
<text class="card-title">问题描述</text>
|
|||
|
|
</view>
|
|||
|
|
<text class="description">{{workcase.description}}</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 图片附件 -->
|
|||
|
|
<view class="info-card" v-if="workcase.images && workcase.images.length > 0">
|
|||
|
|
<view class="card-header">
|
|||
|
|
<text class="card-title">相关图片</text>
|
|||
|
|
</view>
|
|||
|
|
<view class="image-gallery">
|
|||
|
|
<image
|
|||
|
|
class="gallery-image"
|
|||
|
|
v-for="(image, index) in workcase.images"
|
|||
|
|
:key="index"
|
|||
|
|
:src="image"
|
|||
|
|
mode="aspectFill"
|
|||
|
|
@tap="previewImage(index)"
|
|||
|
|
/>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 处理进度 -->
|
|||
|
|
<view class="info-card" v-if="workcase.status === 'processing'">
|
|||
|
|
<view class="card-header">
|
|||
|
|
<text class="card-title">处理进度</text>
|
|||
|
|
<text class="progress-text">{{workcase.progress}}%</text>
|
|||
|
|
</view>
|
|||
|
|
<view class="progress-container">
|
|||
|
|
<view class="progress-bar">
|
|||
|
|
<view class="progress-fill" :style="'width: ' + workcase.progress + '%'"></view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
<text class="progress-desc">{{getProgressDesc(workcase.progress)}}</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 处理记录 -->
|
|||
|
|
<view class="info-card">
|
|||
|
|
<view class="card-header">
|
|||
|
|
<text class="card-title">处理记录</text>
|
|||
|
|
</view>
|
|||
|
|
<view class="timeline">
|
|||
|
|
<view
|
|||
|
|
class="timeline-item"
|
|||
|
|
v-for="(record, index) in workcase.records"
|
|||
|
|
:key="index"
|
|||
|
|
>
|
|||
|
|
<view class="timeline-dot" :class="getRecordDotClass(record.type)"></view>
|
|||
|
|
<view class="timeline-content">
|
|||
|
|
<text class="record-title">{{record.title}}</text>
|
|||
|
|
<text class="record-desc" v-if="record.description">{{record.description}}</text>
|
|||
|
|
<view class="record-meta">
|
|||
|
|
<text class="record-time">{{formatDateTime(record.time)}}</text>
|
|||
|
|
<text class="record-operator">{{record.operator}}</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 客服评价 -->
|
|||
|
|
<view class="info-card" v-if="workcase.rating">
|
|||
|
|
<view class="card-header">
|
|||
|
|
<text class="card-title">服务评价</text>
|
|||
|
|
</view>
|
|||
|
|
<view class="rating-section">
|
|||
|
|
<view class="stars">
|
|||
|
|
<text
|
|||
|
|
class="star"
|
|||
|
|
v-for="i in 5"
|
|||
|
|
:key="i"
|
|||
|
|
:class="{active: i <= workcase.rating.score}"
|
|||
|
|
>★</text>
|
|||
|
|
</view>
|
|||
|
|
<text class="rating-text">{{workcase.rating.comment}}</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</scroll-view>
|
|||
|
|
|
|||
|
|
<!-- 底部操作栏 -->
|
|||
|
|
<view class="bottom-actions">
|
|||
|
|
<button
|
|||
|
|
class="action-btn secondary"
|
|||
|
|
@tap="contactService"
|
|||
|
|
>
|
|||
|
|
联系客服
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
class="action-btn primary"
|
|||
|
|
v-if="workcase.status === 'pending'"
|
|||
|
|
@tap="cancelWorkcase"
|
|||
|
|
>
|
|||
|
|
取消工单
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
class="action-btn primary"
|
|||
|
|
v-if="workcase.status === 'processing'"
|
|||
|
|
@tap="confirmComplete"
|
|||
|
|
>
|
|||
|
|
确认完成
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
class="action-btn primary"
|
|||
|
|
v-if="workcase.status === 'completed' && !workcase.rating"
|
|||
|
|
@tap="showRating"
|
|||
|
|
>
|
|||
|
|
服务评价
|
|||
|
|
</button>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 评价弹窗 -->
|
|||
|
|
<view class="rating-modal" v-if="showRatingModal">
|
|||
|
|
<view class="modal-content">
|
|||
|
|
<view class="modal-header">
|
|||
|
|
<text class="modal-title">服务评价</text>
|
|||
|
|
<view class="close-btn" @tap="hideRating">
|
|||
|
|
<text class="close-icon">×</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<view class="rating-form">
|
|||
|
|
<text class="form-label">请为本次服务打分</text>
|
|||
|
|
<view class="star-rating">
|
|||
|
|
<text
|
|||
|
|
class="rating-star"
|
|||
|
|
v-for="i in 5"
|
|||
|
|
:key="i"
|
|||
|
|
:class="{active: i <= ratingScore}"
|
|||
|
|
@tap="setRating(i)"
|
|||
|
|
>★</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<text class="form-label">评价内容</text>
|
|||
|
|
<textarea
|
|||
|
|
class="rating-textarea"
|
|||
|
|
v-model="ratingComment"
|
|||
|
|
placeholder="请输入您的评价内容..."
|
|||
|
|
maxlength="200"
|
|||
|
|
/>
|
|||
|
|
<text class="char-count">{{ratingComment.length}}/200</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<view class="modal-actions">
|
|||
|
|
<button class="modal-btn cancel" @tap="hideRating">取消</button>
|
|||
|
|
<button
|
|||
|
|
class="modal-btn confirm"
|
|||
|
|
@tap="submitRating"
|
|||
|
|
:disabled="ratingScore === 0"
|
|||
|
|
>提交</button>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { ref, onMounted } from 'vue'
|
|||
|
|
|
|||
|
|
// 接口定义
|
|||
|
|
interface Workcase {
|
|||
|
|
id: string
|
|||
|
|
number: string
|
|||
|
|
title: string
|
|||
|
|
category: string
|
|||
|
|
priority: string
|
|||
|
|
status: 'pending' | 'processing' | 'completed' | 'cancelled'
|
|||
|
|
statusText: string
|
|||
|
|
description: string
|
|||
|
|
contact: string
|
|||
|
|
progress: number
|
|||
|
|
createTime: Date
|
|||
|
|
updateTime: Date
|
|||
|
|
images?: string[]
|
|||
|
|
records: ProcessRecord[]
|
|||
|
|
rating?: Rating
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface ProcessRecord {
|
|||
|
|
type: string
|
|||
|
|
title: string
|
|||
|
|
description?: string
|
|||
|
|
time: Date
|
|||
|
|
operator: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface Rating {
|
|||
|
|
score: number
|
|||
|
|
comment: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 响应式数据
|
|||
|
|
const workcaseId = ref<string | null>(null)
|
|||
|
|
const workcase = ref<Workcase>({} as Workcase)
|
|||
|
|
const showRatingModal = ref<boolean>(false)
|
|||
|
|
const ratingScore = ref<number>(0)
|
|||
|
|
const ratingComment = ref<string>('')
|
|||
|
|
|
|||
|
|
// 生命周期
|
|||
|
|
onMounted(() => {
|
|||
|
|
const pages = getCurrentPages()
|
|||
|
|
const currentPage = pages[pages.length - 1]
|
|||
|
|
workcaseId.value = currentPage.options?.id || '1'
|
|||
|
|
loadWorkcaseDetail()
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 方法定义
|
|||
|
|
async function loadWorkcaseDetail() {
|
|||
|
|
try {
|
|||
|
|
// 模拟获取工单详情
|
|||
|
|
workcase.value = getMockWorkcase()
|
|||
|
|
|
|||
|
|
// 设置页面标题
|
|||
|
|
uni.setNavigationBarTitle({
|
|||
|
|
title: workcase.value.title
|
|||
|
|
})
|
|||
|
|
} catch (error) {
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '加载失败',
|
|||
|
|
icon: 'error'
|
|||
|
|
})
|
|||
|
|
setTimeout(() => {
|
|||
|
|
uni.navigateBack()
|
|||
|
|
}, 1500)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getMockWorkcase(): Workcase {
|
|||
|
|
return {
|
|||
|
|
id: workcaseId.value || '1',
|
|||
|
|
number: `WC2024${String(workcaseId.value || '1').padStart(4, '0')}`,
|
|||
|
|
title: '小区公园路灯不亮需要维修',
|
|||
|
|
category: '设施报修',
|
|||
|
|
priority: '紧急',
|
|||
|
|
status: 'processing',
|
|||
|
|
statusText: '处理中',
|
|||
|
|
description: '小区公园内的路灯已经连续三天不亮了,影响居民夜间出行安全。路灯位置在公园主干道上,希望能够尽快派人维修。',
|
|||
|
|
contact: '138****5678',
|
|||
|
|
progress: 65,
|
|||
|
|
createTime: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
|
|||
|
|
updateTime: new Date(),
|
|||
|
|
images: [
|
|||
|
|
'/static/workcase1.jpg',
|
|||
|
|
'/static/workcase2.jpg'
|
|||
|
|
],
|
|||
|
|
records: [
|
|||
|
|
{
|
|||
|
|
type: 'create',
|
|||
|
|
title: '工单创建',
|
|||
|
|
description: '用户提交工单,问题已记录',
|
|||
|
|
time: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
|
|||
|
|
operator: '系统'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
type: 'accept',
|
|||
|
|
title: '工单受理',
|
|||
|
|
description: '客服已受理,安排相关人员处理',
|
|||
|
|
time: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000 + 30 * 60 * 1000),
|
|||
|
|
operator: '客服小王'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
type: 'processing',
|
|||
|
|
title: '现场勘查',
|
|||
|
|
description: '维修人员已到达现场,正在检查路灯故障原因',
|
|||
|
|
time: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000),
|
|||
|
|
operator: '维修师傅张三'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
type: 'processing',
|
|||
|
|
title: '配件采购',
|
|||
|
|
description: '故障原因确认为灯泡损坏,正在采购替换配件',
|
|||
|
|
time: new Date(Date.now() - 12 * 60 * 60 * 1000),
|
|||
|
|
operator: '维修师傅张三'
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取状态样式
|
|||
|
|
function getStatusClass(status: string) {
|
|||
|
|
return {
|
|||
|
|
'status-pending': status === 'pending',
|
|||
|
|
'status-processing': status === 'processing',
|
|||
|
|
'status-completed': status === 'completed',
|
|||
|
|
'status-cancelled': status === 'cancelled'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取优先级样式
|
|||
|
|
function getPriorityClass(priority: string) {
|
|||
|
|
return {
|
|||
|
|
'priority-normal': priority === '一般',
|
|||
|
|
'priority-urgent': priority === '紧急',
|
|||
|
|
'priority-emergency': priority === '非常紧急'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取记录点样式
|
|||
|
|
function getRecordDotClass(type: string) {
|
|||
|
|
return {
|
|||
|
|
'dot-create': type === 'create',
|
|||
|
|
'dot-accept': type === 'accept',
|
|||
|
|
'dot-processing': type === 'processing',
|
|||
|
|
'dot-complete': type === 'complete'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取进度描述
|
|||
|
|
function getProgressDesc(progress: number): string {
|
|||
|
|
if (progress < 25) {
|
|||
|
|
return '刚刚开始处理'
|
|||
|
|
} else if (progress < 50) {
|
|||
|
|
return '正在积极处理中'
|
|||
|
|
} else if (progress < 75) {
|
|||
|
|
return '处理进展顺利'
|
|||
|
|
} else if (progress < 100) {
|
|||
|
|
return '即将完成处理'
|
|||
|
|
} else {
|
|||
|
|
return '处理已完成'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 格式化日期时间
|
|||
|
|
function formatDateTime(date: Date): string {
|
|||
|
|
const d = new Date(date)
|
|||
|
|
const year = d.getFullYear()
|
|||
|
|
const month = String(d.getMonth() + 1).padStart(2, '0')
|
|||
|
|
const day = String(d.getDate()).padStart(2, '0')
|
|||
|
|
const hour = String(d.getHours()).padStart(2, '0')
|
|||
|
|
const minute = String(d.getMinutes()).padStart(2, '0')
|
|||
|
|
return `${year}-${month}-${day} ${hour}:${minute}`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 预览图片
|
|||
|
|
function previewImage(index: number) {
|
|||
|
|
uni.previewImage({
|
|||
|
|
current: index,
|
|||
|
|
urls: workcase.value.images || []
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 联系客服
|
|||
|
|
function contactService() {
|
|||
|
|
uni.showActionSheet({
|
|||
|
|
itemList: ['拨打电话', '在线客服'],
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.tapIndex === 0) {
|
|||
|
|
uni.makePhoneCall({
|
|||
|
|
phoneNumber: '400-123-4567'
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
uni.navigateTo({
|
|||
|
|
url: '/pages/index/index'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 取消工单
|
|||
|
|
function cancelWorkcase() {
|
|||
|
|
uni.showModal({
|
|||
|
|
title: '确认取消',
|
|||
|
|
content: '确定要取消此工单吗?取消后无法恢复。',
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.confirm) {
|
|||
|
|
workcase.value.status = 'cancelled'
|
|||
|
|
workcase.value.statusText = '已取消'
|
|||
|
|
|
|||
|
|
// 添加取消记录
|
|||
|
|
workcase.value.records.push({
|
|||
|
|
type: 'cancel',
|
|||
|
|
title: '工单取消',
|
|||
|
|
description: '用户主动取消工单',
|
|||
|
|
time: new Date(),
|
|||
|
|
operator: '用户'
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '工单已取消',
|
|||
|
|
icon: 'success'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 确认完成
|
|||
|
|
function confirmComplete() {
|
|||
|
|
uni.showModal({
|
|||
|
|
title: '确认完成',
|
|||
|
|
content: '确认问题已经得到解决?',
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.confirm) {
|
|||
|
|
workcase.value.status = 'completed'
|
|||
|
|
workcase.value.statusText = '已完成'
|
|||
|
|
workcase.value.progress = 100
|
|||
|
|
|
|||
|
|
// 添加完成记录
|
|||
|
|
workcase.value.records.push({
|
|||
|
|
type: 'complete',
|
|||
|
|
title: '工单完成',
|
|||
|
|
description: '用户确认问题已解决',
|
|||
|
|
time: new Date(),
|
|||
|
|
operator: '用户'
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '工单已完成',
|
|||
|
|
icon: 'success'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示评价弹窗
|
|||
|
|
function showRating() {
|
|||
|
|
showRatingModal.value = true
|
|||
|
|
ratingScore.value = 0
|
|||
|
|
ratingComment.value = ''
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 隐藏评价弹窗
|
|||
|
|
function hideRating() {
|
|||
|
|
showRatingModal.value = false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置评分
|
|||
|
|
function setRating(score: number) {
|
|||
|
|
ratingScore.value = score
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 提交评价
|
|||
|
|
function submitRating() {
|
|||
|
|
if (ratingScore.value === 0) {
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '请选择评分',
|
|||
|
|
icon: 'none'
|
|||
|
|
})
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
workcase.value.rating = {
|
|||
|
|
score: ratingScore.value,
|
|||
|
|
comment: ratingComment.value || '用户未填写评价内容'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
hideRating()
|
|||
|
|
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '评价提交成功',
|
|||
|
|
icon: 'success'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style lang="scss" scoped>
|
|||
|
|
@import './detail.scss';
|
|||
|
|
</style>
|