工单详情
This commit is contained in:
@@ -0,0 +1,199 @@
|
|||||||
|
.chat-message-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header {
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
.header-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #4b87ff;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-container {
|
||||||
|
padding: 0 24px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 32px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button {
|
||||||
|
padding: 12px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #6b7280;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
position: relative;
|
||||||
|
bottom: -1px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #4b87ff;
|
||||||
|
border-bottom-color: #4b87ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-main {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-container {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
height: 420px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-ai {
|
||||||
|
background: #a855f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-guest {
|
||||||
|
background: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-meta {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-meta-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble-left {
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-bubble-right {
|
||||||
|
background: #4b87ff;
|
||||||
|
color: #fff;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-container {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-section {
|
||||||
|
.summary-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111827;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #374151;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
<template>
|
||||||
|
<div class="chat-message-container">
|
||||||
|
<!-- 头部信息 -->
|
||||||
|
<header class="chat-header">
|
||||||
|
<div class="header-content">
|
||||||
|
<div class="header-title">{{ chatRoom?.workcaseId || '' }} 对话详情</div>
|
||||||
|
<div class="header-subtitle">
|
||||||
|
<span>客户:{{ chatRoom?.guestName || '未知' }}</span>
|
||||||
|
<span class="separator"> </span>
|
||||||
|
<span>聊天室:{{ chatRoom?.roomName || '未知' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Tab 切换 -->
|
||||||
|
<div class="tabs-container">
|
||||||
|
<div class="tabs-wrapper">
|
||||||
|
<button
|
||||||
|
class="tab-button"
|
||||||
|
:class="{ active: activeTab === 'record' }"
|
||||||
|
@click="activeTab = 'record'"
|
||||||
|
>
|
||||||
|
对话记录
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="tab-button"
|
||||||
|
:class="{ active: activeTab === 'summary' }"
|
||||||
|
@click="activeTab = 'summary'"
|
||||||
|
>
|
||||||
|
对话纪要
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<main class="chat-main">
|
||||||
|
<!-- 对话记录 -->
|
||||||
|
<div v-if="activeTab === 'record'" class="messages-container">
|
||||||
|
<div class="messages-list">
|
||||||
|
<div v-for="message in messages" :key="message.messageId"
|
||||||
|
class="message-item"
|
||||||
|
:class="message.senderType === 'guest' ? 'message-right' : 'message-left'"
|
||||||
|
>
|
||||||
|
<!-- 客服/AI消息(左侧) -->
|
||||||
|
<template v-if="message.senderType !== 'guest'">
|
||||||
|
<div class="avatar avatar-ai">{{ getAvatarText(message) }}</div>
|
||||||
|
<div class="message-content">
|
||||||
|
<div class="message-meta">{{ message.senderName || '小电' }} {{ formatTime(message.sendTime) }}</div>
|
||||||
|
<div class="message-bubble message-bubble-left">
|
||||||
|
{{ message.content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 客户消息(右侧) -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="message-content">
|
||||||
|
<div class="message-meta message-meta-right">{{ message.senderName || '客户' }} {{ formatTime(message.sendTime) }}</div>
|
||||||
|
<div class="message-bubble message-bubble-right">
|
||||||
|
{{ message.content }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="avatar avatar-guest">{{ getAvatarText(message) }}</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-if="messages.length === 0" class="empty-state">
|
||||||
|
<div class="empty-text">暂无对话记录</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 对话纪要 -->
|
||||||
|
<div v-else class="summary-container">
|
||||||
|
<div class="summary-content">
|
||||||
|
<div class="summary-section">
|
||||||
|
<div class="summary-title">问题概述</div>
|
||||||
|
<div class="summary-text">
|
||||||
|
{{ summary.overview || '暂无概述' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="summary-section">
|
||||||
|
<div class="summary-title">客户诉求</div>
|
||||||
|
<div class="summary-text">
|
||||||
|
{{ summary.demand || '暂无诉求' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import type { ChatRoomVO, ChatRoomMessageVO } from '@/types/workcase/chatRoom'
|
||||||
|
import { workcaseChatAPI } from '@/api/workcase/workcaseChat'
|
||||||
|
|
||||||
|
interface Summary {
|
||||||
|
overview?: string
|
||||||
|
demand?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
chatRoom?: ChatRoomVO
|
||||||
|
roomId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
chatRoom: undefined,
|
||||||
|
roomId: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const activeTab = ref<'record' | 'summary'>('record')
|
||||||
|
const messages = ref<ChatRoomMessageVO[]>([])
|
||||||
|
const summary = ref<Summary>({
|
||||||
|
overview: '',
|
||||||
|
demand: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
const formatTime = (timeStr?: string) => {
|
||||||
|
if (!timeStr) return ''
|
||||||
|
try {
|
||||||
|
const date = new Date(timeStr)
|
||||||
|
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
||||||
|
} catch {
|
||||||
|
return timeStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取头像文本
|
||||||
|
const getAvatarText = (message: ChatRoomMessageVO) => {
|
||||||
|
if (message.senderType === 'guest') {
|
||||||
|
return message.senderName?.charAt(0) || '客'
|
||||||
|
}
|
||||||
|
return message.senderName?.charAt(0) || '电'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载消息数据
|
||||||
|
const loadMessages = async () => {
|
||||||
|
const targetRoomId = props.roomId || props.chatRoom?.roomId
|
||||||
|
if (!targetRoomId) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await workcaseChatAPI.getChatMessagePage({
|
||||||
|
filter: { roomId: targetRoomId },
|
||||||
|
pageParam: { page: 1, pageSize: 100 }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.success && res.dataList) {
|
||||||
|
// 后端降序返回,需要反转
|
||||||
|
messages.value = [...res.dataList].reverse()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载消息失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成对话纪要(可以后续接入AI生成)
|
||||||
|
const generateSummary = () => {
|
||||||
|
if (messages.value.length === 0) return
|
||||||
|
|
||||||
|
// 简单提取第一条和最后一条消息作为概述
|
||||||
|
const firstMsg = messages.value[0]
|
||||||
|
const lastMsg = messages.value[messages.value.length - 1]
|
||||||
|
|
||||||
|
summary.value = {
|
||||||
|
overview: `客户反馈:${firstMsg.content?.substring(0, 50) || ''}`,
|
||||||
|
demand: lastMsg.content?.substring(0, 100) || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadMessages()
|
||||||
|
generateSummary()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import url("./ChatMessage.scss");
|
||||||
|
</style>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {default as ChatMessage} from './ChatMessage/ChatMessage.vue'
|
||||||
@@ -0,0 +1,325 @@
|
|||||||
|
.workcase-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 24px;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workcase-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workcase-id {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #4b87ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge,
|
||||||
|
.urgency-badge {
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-pending {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-processing {
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #1e40af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-done {
|
||||||
|
background: #d1fae5;
|
||||||
|
color: #065f46;
|
||||||
|
}
|
||||||
|
|
||||||
|
.urgency-normal {
|
||||||
|
background: #fed7aa;
|
||||||
|
color: #9a3412;
|
||||||
|
}
|
||||||
|
|
||||||
|
.urgency-emergency {
|
||||||
|
background: #fecaca;
|
||||||
|
color: #991b1b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-chat-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: #4b87ff;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #3b77ef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-main {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-table {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(0, 3fr) minmax(0, 1fr) minmax(0, 3fr);
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-label {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f9fafb;
|
||||||
|
color: #4b5563;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
border-right: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-value {
|
||||||
|
padding: 12px;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 14px;
|
||||||
|
border-right: 1px solid #e5e7eb;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-value-full {
|
||||||
|
grid-column: 2 / 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 铭牌照片
|
||||||
|
.nameplate-photo {
|
||||||
|
max-width: 400px;
|
||||||
|
height: 200px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
background: #f9fafb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.photos-section,
|
||||||
|
.timeline-section {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #111827;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
color: #a855f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-bar {
|
||||||
|
width: 4px;
|
||||||
|
height: 16px;
|
||||||
|
background: #a855f7;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photos-grid {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-item {
|
||||||
|
width: 128px;
|
||||||
|
height: 96px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-upload {
|
||||||
|
width: 128px;
|
||||||
|
height: 96px;
|
||||||
|
border: 2px dashed #d1d5db;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #9ca3af;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #4b87ff;
|
||||||
|
color: #4b87ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-content {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-line {
|
||||||
|
position: absolute;
|
||||||
|
left: 8px;
|
||||||
|
top: 8px;
|
||||||
|
bottom: 16px;
|
||||||
|
width: 2px;
|
||||||
|
background: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 32px;
|
||||||
|
padding-bottom: 32px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-dot {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 6px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-dot-system {
|
||||||
|
background: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-dot-manager {
|
||||||
|
background: #fb923c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-dot-engineer {
|
||||||
|
background: #34d399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-body {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-actor {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-action {
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-desc {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px 24px;
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-dialog {
|
||||||
|
:deep(.el-dialog__body) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,319 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="workcase-detail">
|
||||||
|
<!-- Header -->
|
||||||
|
<header class="detail-header">
|
||||||
|
<!-- Left Part -->
|
||||||
|
<div class="header-left">
|
||||||
|
<div class="workcase-info">
|
||||||
|
<span class="workcase-id">{{ formData.workcaseId || '新建工单' }}</span>
|
||||||
|
<span v-if="mode === 'view' && formData.status" class="status-badge" :class="statusClass(formData.status)">
|
||||||
|
{{ statusLabel(formData.status) }}
|
||||||
|
</span>
|
||||||
|
<span v-if="mode === 'view' && formData.emergency" class="urgency-badge" :class="urgencyClass(formData.emergency)">
|
||||||
|
{{ urgencyLabel(formData.emergency) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Part -->
|
||||||
|
<div class="header-right">
|
||||||
|
<button v-if="mode === 'view' && formData.workcaseId" class="view-chat-btn" @click="handleViewChat">
|
||||||
|
<MessageSquare :size="14" />
|
||||||
|
查看对话
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="detail-main">
|
||||||
|
<!-- 工单信息表格 -->
|
||||||
|
<div class="info-table">
|
||||||
|
<!-- 客户姓名 & 联系电话 -->
|
||||||
|
<div class="table-row">
|
||||||
|
<div class="table-label">客户姓名</div>
|
||||||
|
<div class="table-value">
|
||||||
|
<ElInput v-if="mode !== 'view'" v-model="formData.username" placeholder="请输入客户姓名" size="small" />
|
||||||
|
<span v-else>{{ formData.username || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="table-label">联系电话</div>
|
||||||
|
<div class="table-value">
|
||||||
|
<ElInput v-if="mode !== 'view'" v-model="formData.phone" placeholder="请输入联系电话" size="small" />
|
||||||
|
<span v-else>{{ formData.phone || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 设备名称 & 故障类型 -->
|
||||||
|
<div class="table-row">
|
||||||
|
<div class="table-label">设备名称</div>
|
||||||
|
<div class="table-value">
|
||||||
|
<ElInput v-if="mode !== 'view'" v-model="formData.device" placeholder="请输入设备名称" size="small" />
|
||||||
|
<span v-else>{{ formData.device || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="table-label">故障类型</div>
|
||||||
|
<div class="table-value">
|
||||||
|
<ElInput v-if="mode !== 'view'" v-model="formData.type" placeholder="请输入故障类型" size="small" />
|
||||||
|
<span v-else>{{ formData.type || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 现场地址 -->
|
||||||
|
<div class="table-row">
|
||||||
|
<div class="table-label">现场地址</div>
|
||||||
|
<div class="table-value table-value-full">
|
||||||
|
<ElInput v-if="mode !== 'view'" v-model="formData.address" placeholder="请输入现场地址" size="small" />
|
||||||
|
<span v-else>{{ formData.address || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 故障描述 -->
|
||||||
|
<div class="table-row">
|
||||||
|
<div class="table-label">故障描述</div>
|
||||||
|
<div class="table-value table-value-full">
|
||||||
|
<ElInput
|
||||||
|
v-if="mode !== 'view'"
|
||||||
|
v-model="formData.description"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="请详细描述故障现象"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<span v-else>{{ formData.description || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 设备铭牌 -->
|
||||||
|
<div class="table-row">
|
||||||
|
<div class="table-label">设备铭牌</div>
|
||||||
|
<div class="table-value table-value-full">
|
||||||
|
<ElInput v-if="mode !== 'view'" v-model="formData.deviceNamePlate" placeholder="请输入设备铭牌" size="small" />
|
||||||
|
<span v-else>{{ formData.deviceNamePlate || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 铭牌照片 -->
|
||||||
|
<div class="table-row" v-if="formData.deviceNamePlateImg">
|
||||||
|
<div class="table-label">铭牌照片</div>
|
||||||
|
<div class="table-value table-value-full">
|
||||||
|
<div class="nameplate-photo" @click="previewNameplateImage">
|
||||||
|
<img :src="formData.deviceNamePlateImg" alt="设备铭牌" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 紧急程度 -->
|
||||||
|
<div class="table-row" v-if="mode !== 'view'">
|
||||||
|
<div class="table-label">紧急程度</div>
|
||||||
|
<div class="table-value table-value-full">
|
||||||
|
<ElSelect v-model="formData.emergency" placeholder="请选择紧急程度" size="small">
|
||||||
|
<ElOption label="普通" value="normal" />
|
||||||
|
<ElOption label="紧急" value="emergency" />
|
||||||
|
</ElSelect>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 处理人 (仅查看模式) -->
|
||||||
|
<div class="table-row" v-if="mode === 'view'">
|
||||||
|
<div class="table-label">处理人</div>
|
||||||
|
<div class="table-value">
|
||||||
|
<span>{{ formData.processorName || '未指派' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="table-label">创建时间</div>
|
||||||
|
<div class="table-value">
|
||||||
|
<span>{{ formData.createTime || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 故障照片 -->
|
||||||
|
<div class="photos-section" v-if="mode !== 'create' || formData.imgs?.length">
|
||||||
|
<div class="section-title">
|
||||||
|
<ImageIcon :size="18" class="title-icon" />
|
||||||
|
故障照片
|
||||||
|
</div>
|
||||||
|
<div class="photos-grid">
|
||||||
|
<div v-for="(img, index) in formData.imgs" :key="index" class="photo-item">
|
||||||
|
<img :src="img" alt="故障照片" />
|
||||||
|
</div>
|
||||||
|
<div v-if="mode !== 'view'" class="photo-upload">
|
||||||
|
<Plus :size="32" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 处理记录 (仅查看模式) -->
|
||||||
|
<div class="timeline-section" v-if="mode === 'view' && timeline.length">
|
||||||
|
<div class="section-title">
|
||||||
|
<div class="title-bar"></div>
|
||||||
|
处理记录
|
||||||
|
</div>
|
||||||
|
<div class="timeline-content">
|
||||||
|
<div class="timeline-line"></div>
|
||||||
|
<div v-for="(item, index) in timeline" :key="index" class="timeline-item">
|
||||||
|
<div class="timeline-dot" :class="`timeline-dot-${item.status}`"></div>
|
||||||
|
<div class="timeline-body">
|
||||||
|
<div class="timeline-header">
|
||||||
|
<span class="timeline-actor">{{ item.title.split(' ')[0] }}</span>
|
||||||
|
<span class="timeline-action">{{ item.title.split(' ').slice(1).join(' ') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-desc">{{ item.desc }}</div>
|
||||||
|
<div class="timeline-time">{{ item.time }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer Actions -->
|
||||||
|
<footer class="detail-footer">
|
||||||
|
<ElButton @click="handleCancel">{{ mode === 'view' ? '关闭' : '取消' }}</ElButton>
|
||||||
|
<ElButton v-if="mode === 'create'" type="primary" @click="handleSubmit">创建工单</ElButton>
|
||||||
|
<ElButton v-if="mode === 'edit'" type="primary" @click="handleSubmit">保存修改</ElButton>
|
||||||
|
<ElButton v-if="mode === 'view' && formData.status === 'pending'" type="warning" @click="handleAssign">指派工程师</ElButton>
|
||||||
|
<ElButton v-if="mode === 'view' && formData.status === 'processing'" type="success" @click="handleComplete">完成工单</ElButton>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- ChatMessage Dialog -->
|
||||||
|
<ElDialog v-model="showChatMessage" title="对话详情" width="800px" class="chat-dialog">
|
||||||
|
<ChatMessage v-if="showChatMessage" :room-id="currentRoomId" />
|
||||||
|
</ElDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { ChatMessage } from '@/views/public/ChatRoom/'
|
||||||
|
import { ElButton, ElInput, ElSelect, ElOption, ElDialog, ElMessage } from 'element-plus'
|
||||||
|
import { MessageSquare, ImageIcon as ImageIcon, Plus } from 'lucide-vue-next'
|
||||||
|
import type { TbWorkcaseDTO } from '@/types/workcase/workcase'
|
||||||
|
|
||||||
|
interface TimelineItem {
|
||||||
|
status: 'system' | 'manager' | 'engineer'
|
||||||
|
title: string
|
||||||
|
desc: string
|
||||||
|
time: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
mode?: 'view' | 'edit' | 'create'
|
||||||
|
workcase?: TbWorkcaseDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
mode: 'view',
|
||||||
|
workcase: () => ({} as TbWorkcaseDTO)
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
cancel: []
|
||||||
|
submit: [data: TbWorkcaseDTO]
|
||||||
|
assign: [workcaseId: string]
|
||||||
|
complete: [workcaseId: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const formData = ref<TbWorkcaseDTO>({
|
||||||
|
...props.workcase
|
||||||
|
})
|
||||||
|
|
||||||
|
const showChatMessage = ref(false)
|
||||||
|
const currentRoomId = ref<string>('')
|
||||||
|
const timeline = ref<TimelineItem[]>([
|
||||||
|
{
|
||||||
|
status: 'system',
|
||||||
|
title: '系统 工单创建',
|
||||||
|
desc: '客户通过小电对话提交',
|
||||||
|
time: ''
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
// 根据 workcaseId 获取聊天室ID
|
||||||
|
const loadChatRoom = async () => {
|
||||||
|
if (!formData.value.workcaseId) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: 调用 API 根据 workcaseId 查询聊天室
|
||||||
|
// const res = await workcaseChatAPI.getChatRoomByWorkcaseId(formData.value.workcaseId)
|
||||||
|
// if (res.success && res.data) {
|
||||||
|
// currentRoomId.value = res.data.roomId || ''
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 临时:假设 roomId 和 workcaseId 相关联
|
||||||
|
console.log('需要根据 workcaseId 查询聊天室:', formData.value.workcaseId)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载聊天室失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.workcase, (newVal) => {
|
||||||
|
formData.value = { ...newVal }
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
const statusLabel = (status: string) => {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
pending: '待处理',
|
||||||
|
processing: '处理中',
|
||||||
|
done: '已完成'
|
||||||
|
}
|
||||||
|
return map[status] || status
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusClass = (status: string) => {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
pending: 'status-pending',
|
||||||
|
processing: 'status-processing',
|
||||||
|
done: 'status-done'
|
||||||
|
}
|
||||||
|
return map[status] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const urgencyLabel = (urgency: string) => {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
normal: '普通',
|
||||||
|
emergency: '紧急'
|
||||||
|
}
|
||||||
|
return map[urgency] || urgency
|
||||||
|
}
|
||||||
|
|
||||||
|
const urgencyClass = (urgency: string) => {
|
||||||
|
const map: Record<string, string> = {
|
||||||
|
normal: 'urgency-normal',
|
||||||
|
emergency: 'urgency-emergency'
|
||||||
|
}
|
||||||
|
return map[urgency] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleViewChat = () => {
|
||||||
|
showChatMessage.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewNameplateImage = () => {
|
||||||
|
if (!formData.value.deviceNamePlateImg) return
|
||||||
|
// TODO: 使用图片预览组件
|
||||||
|
window.open(formData.value.deviceNamePlateImg, '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
emit('cancel')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (props.mode === 'create' || props.mode === 'edit') {
|
||||||
|
if (!formData.value.username || !formData.value.phone) {
|
||||||
|
ElMessage.warning('请填写必填项')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit('submit', formData.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAssign = () => {
|
||||||
|
emit('assign', formData.value.workcaseId!)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleComplete = () => {
|
||||||
|
emit('complete', formData.value.workcaseId!)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import url("./WorkcaseDetail.scss");
|
@import url("./WorkcaseDetail.scss");
|
||||||
</style>
|
</style>
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
.workcase-creator-mask {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
z-index: 999;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-end;
|
|
||||||
animation: fadeIn 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.workcase-creator {
|
|
||||||
max-height: 85vh;
|
|
||||||
background-color: #FFFFFF;
|
|
||||||
border-top-left-radius: 16px;
|
|
||||||
border-top-right-radius: 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
animation: slideUp 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideUp {
|
|
||||||
from {
|
|
||||||
transform: translateY(100%);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.creator-header {
|
|
||||||
background-color: #FFFFFF;
|
|
||||||
padding: 20px 16px 16px;
|
|
||||||
border-bottom: 1px solid #F0F0F0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.creator-header::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 8px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 36px;
|
|
||||||
height: 4px;
|
|
||||||
background-color: #E0E0E0;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
color: #1F2329;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: 14px;
|
|
||||||
background-color: #F5F5F5;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-icon {
|
|
||||||
color: #8F959E;
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.creator-content {
|
|
||||||
flex: 1;
|
|
||||||
background-color: #FFFFFF;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-item {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
display: block;
|
|
||||||
color: #333333;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input, .textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border: 1px solid #E0E0E0;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 16px;
|
|
||||||
background-color: #FAFAFA;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textarea {
|
|
||||||
min-height: 100px;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.char-count {
|
|
||||||
color: #999999;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: right;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker-content {
|
|
||||||
padding: 12px 16px;
|
|
||||||
border: 1px solid #E0E0E0;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: #FAFAFA;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker-text {
|
|
||||||
color: #333333;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker-arrow {
|
|
||||||
color: #999999;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-area {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-item {
|
|
||||||
position: relative;
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: -6px;
|
|
||||||
right: -6px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
background-color: #FF5722;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-icon {
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-btn {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border: 1px dashed #CCCCCC;
|
|
||||||
border-radius: 8px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: #FAFAFA;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-plus {
|
|
||||||
color: #999999;
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-text {
|
|
||||||
color: #999999;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-tip {
|
|
||||||
color: #999999;
|
|
||||||
font-size: 12px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.creator-footer {
|
|
||||||
background-color: #FFFFFF;
|
|
||||||
padding: 16px;
|
|
||||||
padding-bottom: calc(16px + env(safe-area-inset-bottom));
|
|
||||||
border-top: 1px solid #F0F0F0;
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn, .submit-btn {
|
|
||||||
flex: 1;
|
|
||||||
height: 44px;
|
|
||||||
border-radius: 22px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn {
|
|
||||||
background-color: #F5F5F5;
|
|
||||||
color: #666666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn {
|
|
||||||
background-color: #5B8FF9;
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn.is-disabled {
|
|
||||||
background-color: #CCCCCC;
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="workcase-creator-mask" @tap="onClose" v-if="show">
|
|
||||||
<view class="workcase-creator" @tap.stop>
|
|
||||||
<view class="creator-header">
|
|
||||||
<text class="header-title">创建工单</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<scroll-view class="creator-content" scroll-y="true">
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="label">工单标题</text>
|
|
||||||
<input class="input" v-model="form.title" placeholder="请输入工单标题" maxlength="50" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="label">问题分类</text>
|
|
||||||
<picker class="picker" :value="categoryIndex" :range="categories" @change="onCategoryChange">
|
|
||||||
<view class="picker-content">
|
|
||||||
<text class="picker-text">{{categories[categoryIndex]}}</text>
|
|
||||||
<text class="picker-arrow">></text>
|
|
||||||
</view>
|
|
||||||
</picker>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="label">紧急程度</text>
|
|
||||||
<picker class="picker" :value="priorityIndex" :range="priorities" @change="onPriorityChange">
|
|
||||||
<view class="picker-content">
|
|
||||||
<text class="picker-text">{{priorities[priorityIndex]}}</text>
|
|
||||||
<text class="picker-arrow">></text>
|
|
||||||
</view>
|
|
||||||
</picker>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="label">问题描述</text>
|
|
||||||
<textarea class="textarea" v-model="form.description" placeholder="请详细描述遇到的问题..." maxlength="500" />
|
|
||||||
<text class="char-count">{{form.description.length}}/500</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="label">联系方式</text>
|
|
||||||
<input class="input" v-model="form.contact" placeholder="请输入您的联系方式" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="label">上传图片</text>
|
|
||||||
<view class="upload-area">
|
|
||||||
<view class="upload-item" v-for="(item, index) in form.images" :key="index">
|
|
||||||
<image class="upload-image" :src="item" mode="aspectFill" />
|
|
||||||
<view class="delete-btn" @tap="deleteImage(index)">
|
|
||||||
<text class="delete-icon">×</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="upload-btn" v-if="form.images.length < 3" @tap="chooseImage">
|
|
||||||
<text class="upload-plus">+</text>
|
|
||||||
<text class="upload-text">添加图片</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<text class="upload-tip">最多上传3张图片</text>
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
|
|
||||||
<view class="creator-footer">
|
|
||||||
<button class="cancel-btn" @tap="onCancel">取消</button>
|
|
||||||
<button class="submit-btn" :class="{ 'is-disabled': !canSubmit }" @tap="onSubmit" :disabled="!canSubmit">提交工单</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, defineProps, defineEmits } from 'vue'
|
|
||||||
|
|
||||||
// 接口定义
|
|
||||||
interface WorkcaseForm {
|
|
||||||
title : string
|
|
||||||
description : string
|
|
||||||
contact : string
|
|
||||||
images : string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WorkcaseData extends WorkcaseForm {
|
|
||||||
category : string
|
|
||||||
priority : string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Props 定义
|
|
||||||
interface Props {
|
|
||||||
show ?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
show: false
|
|
||||||
})
|
|
||||||
|
|
||||||
// Emits 定义
|
|
||||||
const emits = defineEmits<{
|
|
||||||
close : []
|
|
||||||
success : [data: WorkcaseData]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
// 响应式数据
|
|
||||||
const form = ref<WorkcaseForm>({
|
|
||||||
title: '',
|
|
||||||
description: '',
|
|
||||||
contact: '',
|
|
||||||
images: []
|
|
||||||
})
|
|
||||||
|
|
||||||
const categories = ref<string[]>(['设施报修', '环境卫生', '交通问题', '安全隐患', '其他问题'])
|
|
||||||
const categoryIndex = ref<number>(0)
|
|
||||||
const priorities = ref<string[]>(['一般', '紧急', '非常紧急'])
|
|
||||||
const priorityIndex = ref<number>(0)
|
|
||||||
|
|
||||||
// 计算属性
|
|
||||||
const canSubmit = computed(() => {
|
|
||||||
return form.value.title.trim() &&
|
|
||||||
form.value.description.trim() &&
|
|
||||||
form.value.contact.trim()
|
|
||||||
})
|
|
||||||
|
|
||||||
// 方法定义
|
|
||||||
function onClose() {
|
|
||||||
emits('close')
|
|
||||||
}
|
|
||||||
|
|
||||||
function onCancel() {
|
|
||||||
resetForm()
|
|
||||||
emits('close')
|
|
||||||
}
|
|
||||||
|
|
||||||
function onCategoryChange(e : any) {
|
|
||||||
categoryIndex.value = e.detail.value
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPriorityChange(e : any) {
|
|
||||||
priorityIndex.value = e.detail.value
|
|
||||||
}
|
|
||||||
|
|
||||||
function chooseImage() {
|
|
||||||
uni.chooseImage({
|
|
||||||
count: 3 - form.value.images.length,
|
|
||||||
sizeType: ['compressed'],
|
|
||||||
sourceType: ['camera', 'album'],
|
|
||||||
success: (res) => {
|
|
||||||
form.value.images.push(...res.tempFilePaths)
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.log('选择图片失败:', err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteImage(index : number) {
|
|
||||||
form.value.images.splice(index, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSubmit() {
|
|
||||||
if (!canSubmit.value) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '请完善必填信息',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const workcaseData : WorkcaseData = {
|
|
||||||
title: form.value.title.trim(),
|
|
||||||
category: categories.value[categoryIndex.value],
|
|
||||||
priority: priorities.value[priorityIndex.value],
|
|
||||||
description: form.value.description.trim(),
|
|
||||||
contact: form.value.contact.trim(),
|
|
||||||
images: form.value.images
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showLoading({
|
|
||||||
title: '提交中...'
|
|
||||||
})
|
|
||||||
|
|
||||||
// 模拟提交
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.hideLoading()
|
|
||||||
uni.showToast({
|
|
||||||
title: '工单提交成功',
|
|
||||||
icon: 'success'
|
|
||||||
})
|
|
||||||
resetForm()
|
|
||||||
emits('success', workcaseData)
|
|
||||||
}, 1500)
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetForm() {
|
|
||||||
form.value = {
|
|
||||||
title: '',
|
|
||||||
description: '',
|
|
||||||
contact: '',
|
|
||||||
images: []
|
|
||||||
}
|
|
||||||
categoryIndex.value = 0
|
|
||||||
priorityIndex.value = 0
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import './WorkcaseCreator.scss';
|
|
||||||
</style>
|
|
||||||
@@ -98,9 +98,6 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 工单创建弹窗 -->
|
|
||||||
<WorkcaseCreator v-if="showWorkcaseCreator" :show="showWorkcaseCreator"
|
|
||||||
@close="hideCreator" @success="onWorkcaseCreated" />
|
|
||||||
</view>
|
</view>
|
||||||
<!-- #ifdef APP -->
|
<!-- #ifdef APP -->
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
@@ -109,7 +106,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, nextTick, onMounted, onUnmounted, watch } from 'vue'
|
import { ref, reactive, computed, nextTick, onMounted, onUnmounted, watch } from 'vue'
|
||||||
import WorkcaseCreator from '@/components/WorkcaseCreator/WorkcaseCreator.uvue'
|
|
||||||
import type { ChatRoomMessageVO, CustomerVO, ChatMemberVO, TbChatRoomMessageDTO } from '@/types/workcase'
|
import type { ChatRoomMessageVO, CustomerVO, ChatMemberVO, TbChatRoomMessageDTO } from '@/types/workcase'
|
||||||
import { workcaseChatAPI } from '@/api/workcase'
|
import { workcaseChatAPI } from '@/api/workcase'
|
||||||
import { wsClient } from '@/utils/websocket'
|
import { wsClient } from '@/utils/websocket'
|
||||||
@@ -122,7 +118,6 @@ const workcaseId = ref<string>('')
|
|||||||
const roomName = ref<string>('聊天室')
|
const roomName = ref<string>('聊天室')
|
||||||
const inputText = ref<string>('')
|
const inputText = ref<string>('')
|
||||||
const scrollTop = ref<number>(0)
|
const scrollTop = ref<number>(0)
|
||||||
const showWorkcaseCreator = ref<boolean>(false)
|
|
||||||
const loading = ref<boolean>(false)
|
const loading = ref<boolean>(false)
|
||||||
const sending = ref<boolean>(false)
|
const sending = ref<boolean>(false)
|
||||||
const loadingMore = ref<boolean>(false)
|
const loadingMore = ref<boolean>(false)
|
||||||
@@ -204,24 +199,23 @@ const totalMembers = computed<MemberDisplay[]>(() => {
|
|||||||
|
|
||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.getSystemInfo({
|
const windowInfo = uni.getWindowInfo()
|
||||||
success: (res) => {
|
const statusBarHeight = windowInfo.statusBarHeight || 44
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
try {
|
// #ifdef MP-WEIXIN
|
||||||
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
try {
|
||||||
headerPaddingTop.value = menuButtonInfo.top
|
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
||||||
headerTotalHeight.value = menuButtonInfo.bottom + 8
|
headerPaddingTop.value = menuButtonInfo.top
|
||||||
} catch (e) {
|
headerTotalHeight.value = menuButtonInfo.bottom + 8
|
||||||
headerPaddingTop.value = res.statusBarHeight || 44
|
} catch (e) {
|
||||||
headerTotalHeight.value = (res.statusBarHeight || 44) + 44
|
headerPaddingTop.value = statusBarHeight
|
||||||
}
|
headerTotalHeight.value = statusBarHeight + 44
|
||||||
// #endif
|
}
|
||||||
// #ifndef MP-WEIXIN
|
// #endif
|
||||||
headerPaddingTop.value = res.statusBarHeight || 44
|
// #ifndef MP-WEIXIN
|
||||||
headerTotalHeight.value = (res.statusBarHeight || 44) + 44
|
headerPaddingTop.value = statusBarHeight
|
||||||
// #endif
|
headerTotalHeight.value = statusBarHeight + 44
|
||||||
}
|
// #endif
|
||||||
})
|
|
||||||
|
|
||||||
// 获取页面参数
|
// 获取页面参数
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages()
|
||||||
@@ -448,30 +442,38 @@ function scrollToBottom() {
|
|||||||
|
|
||||||
// 处理工单操作
|
// 处理工单操作
|
||||||
function handleWorkcaseAction() {
|
function handleWorkcaseAction() {
|
||||||
|
console.log('[handleWorkcaseAction] 开始执行')
|
||||||
|
console.log('[handleWorkcaseAction] workcaseId:', workcaseId.value)
|
||||||
|
console.log('[handleWorkcaseAction] roomId:', roomId.value)
|
||||||
|
|
||||||
if (workcaseId.value) {
|
if (workcaseId.value) {
|
||||||
|
const url = `/pages/workcase/workcaseDetail/workcaseDetail?workcaseId=${workcaseId.value}`
|
||||||
|
console.log('[handleWorkcaseAction] 查看工单,跳转URL:', url)
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/workcase/workcaseDetail/workcaseDetail?workcaseId=${workcaseId.value}`
|
url: url,
|
||||||
|
success: () => {
|
||||||
|
console.log('[handleWorkcaseAction] 跳转成功')
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('[handleWorkcaseAction] 跳转失败:', err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
showWorkcaseCreator.value = true
|
// 跳转到创建工单页面
|
||||||
|
const url = `/pages/workcase/workcaseDetail/workcaseDetail?mode=create&roomId=${roomId.value}`
|
||||||
|
console.log('[handleWorkcaseAction] 创建工单,跳转URL:', url)
|
||||||
|
uni.navigateTo({
|
||||||
|
url: url,
|
||||||
|
success: () => {
|
||||||
|
console.log('[handleWorkcaseAction] 跳转成功')
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('[handleWorkcaseAction] 跳转失败:', err)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏工单创建器
|
|
||||||
function hideCreator() {
|
|
||||||
showWorkcaseCreator.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 工单创建成功
|
|
||||||
function onWorkcaseCreated(data: any) {
|
|
||||||
hideCreator()
|
|
||||||
workcaseId.value = data.workcaseId || 'new-workcase'
|
|
||||||
uni.showToast({
|
|
||||||
title: '工单创建成功',
|
|
||||||
icon: 'success'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发起会议
|
// 发起会议
|
||||||
function startMeeting() {
|
function startMeeting() {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ const chatRooms = ref<ChatRoomVO[]>([])
|
|||||||
|
|
||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
const windowInfo = uni.getWindowInfo()
|
||||||
|
const statusBarHeight = windowInfo.statusBarHeight || 20
|
||||||
|
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
try {
|
try {
|
||||||
const menuButton = uni.getMenuButtonBoundingClientRect()
|
const menuButton = uni.getMenuButtonBoundingClientRect()
|
||||||
@@ -76,14 +79,12 @@ onMounted(() => {
|
|||||||
capsuleHeight.value = menuButton.height
|
capsuleHeight.value = menuButton.height
|
||||||
navHeight.value = menuButton.bottom + 8
|
navHeight.value = menuButton.bottom + 8
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const sysInfo = uni.getSystemInfoSync()
|
navPaddingTop.value = statusBarHeight
|
||||||
navPaddingTop.value = sysInfo.statusBarHeight || 20
|
|
||||||
navHeight.value = navPaddingTop.value + 44
|
navHeight.value = navPaddingTop.value + 44
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
// #ifndef MP-WEIXIN
|
// #ifndef MP-WEIXIN
|
||||||
const sysInfo = uni.getSystemInfoSync()
|
navPaddingTop.value = statusBarHeight
|
||||||
navPaddingTop.value = sysInfo.statusBarHeight || 20
|
|
||||||
navHeight.value = navPaddingTop.value + 44
|
navHeight.value = navPaddingTop.value + 44
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
|
|||||||
@@ -109,15 +109,12 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 工单创建弹窗 -->
|
|
||||||
<WorkcaseCreator v-if="showWorkcaseCreator" :show="showWorkcaseCreator" @close="hideCreator"
|
|
||||||
@success="onWorkcaseCreated" />
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, nextTick, onMounted } from 'vue'
|
import { ref, nextTick, onMounted } from 'vue'
|
||||||
import WorkcaseCreator from '@/components/WorkcaseCreator/WorkcaseCreator.uvue'
|
|
||||||
import { guestAPI, aiChatAPI, workcaseChatAPI } from '@/api'
|
import { guestAPI, aiChatAPI, workcaseChatAPI } from '@/api'
|
||||||
import type { TbWorkcaseDTO } from '@/types'
|
import type { TbWorkcaseDTO } from '@/types'
|
||||||
import { AGENT_ID } from '@/config'
|
import { AGENT_ID } from '@/config'
|
||||||
@@ -233,38 +230,22 @@
|
|||||||
title: '智能助手'
|
title: '智能助手'
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取系统信息和安全区域
|
// 获取窗口信息
|
||||||
uni.getSystemInfo({
|
const windowInfo = uni.getWindowInfo()
|
||||||
success: (res) => {
|
statusBarHeight.value = windowInfo.statusBarHeight || 0
|
||||||
console.log('系统信息:', res)
|
|
||||||
console.log('状态栏高度:', res.statusBarHeight)
|
// #ifdef MP-WEIXIN
|
||||||
statusBarHeight.value = res.statusBarHeight || 0
|
// 获取胶囊按钮信息(仅小程序),计算header位置
|
||||||
console.log('安全区域:', res.safeArea)
|
try {
|
||||||
console.log('安全区域insets:', res.safeAreaInsets)
|
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
||||||
|
headerPaddingTop.value = menuButtonInfo.top
|
||||||
// #ifdef MP-WEIXIN
|
headerTotalHeight.value = menuButtonInfo.bottom
|
||||||
// 获取胶囊按钮信息(仅小程序),计算header位置
|
} catch (e) {
|
||||||
try {
|
// 使用默认值
|
||||||
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
headerPaddingTop.value = 44
|
||||||
console.log('胶囊按钮信息:', menuButtonInfo)
|
headerTotalHeight.value = 76
|
||||||
|
}
|
||||||
// 计算header的paddingTop和总高度
|
// #endif
|
||||||
// paddingTop = 胶囊按钮的top值
|
|
||||||
// 总高度 = 胶囊按钮bottom值
|
|
||||||
headerPaddingTop.value = menuButtonInfo.top
|
|
||||||
headerTotalHeight.value = menuButtonInfo.bottom
|
|
||||||
|
|
||||||
console.log('header paddingTop:', headerPaddingTop.value)
|
|
||||||
console.log('header totalHeight:', headerTotalHeight.value)
|
|
||||||
} catch (e) {
|
|
||||||
console.log('获取胶囊按钮信息失败:', e)
|
|
||||||
// 使用默认值
|
|
||||||
headerPaddingTop.value = 44
|
|
||||||
headerTotalHeight.value = 76
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 发送消息
|
// 发送消息
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.page {
|
.page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #f4f5f7;
|
background: #f8fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
padding-bottom: 16rpx;
|
padding-bottom: 16rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
border-bottom: 1rpx solid #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-back {
|
.nav-back {
|
||||||
@@ -50,11 +51,64 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
margin-top: 176rpx;
|
padding: 24rpx;
|
||||||
padding: 20rpx 24rpx;
|
padding-bottom: 140rpx;
|
||||||
padding-bottom: 60rpx;
|
}
|
||||||
min-height: calc(100vh - 176rpx);
|
|
||||||
box-sizing: border-box;
|
// 工单头部卡片
|
||||||
|
.header-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 32rpx 24rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workcase-id {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #4b87ff;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge,
|
||||||
|
.urgency-badge {
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-action {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
padding: 12rpx 24rpx;
|
||||||
|
background: #4b87ff;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
@@ -88,43 +142,102 @@
|
|||||||
color: #222;
|
color: #222;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-card {
|
// 表单容器
|
||||||
display: flex;
|
.form-container {
|
||||||
flex-direction: column;
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-item {
|
.form-item {
|
||||||
|
padding: 24rpx;
|
||||||
|
border-bottom: 1rpx solid #f3f4f6;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 68rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1rpx solid #e5e7eb;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 200rpx;
|
||||||
|
padding: 16rpx 24rpx;
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1rpx solid #e5e7eb;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #111827;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #111827;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16rpx 0;
|
justify-content: space-between;
|
||||||
|
height: 68rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1rpx solid #e5e7eb;
|
||||||
|
border-radius: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-item.column {
|
.picker-text {
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
width: 160rpx;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 26rpx;
|
font-size: 28rpx;
|
||||||
color: #222;
|
color: #111827;
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-desc {
|
.picker-arrow {
|
||||||
font-size: 26rpx;
|
font-size: 32rpx;
|
||||||
color: #222;
|
color: #9ca3af;
|
||||||
line-height: 1.6;
|
margin-left: 16rpx;
|
||||||
margin-top: 8rpx;
|
}
|
||||||
text-align: left;
|
|
||||||
|
// 铭牌照片
|
||||||
|
.nameplate-photo {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400rpx;
|
||||||
|
height: 300rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1rpx solid #e5e7eb;
|
||||||
|
background: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameplate-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-tag {
|
.status-tag {
|
||||||
@@ -192,25 +305,88 @@
|
|||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo-list {
|
.section-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #222;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon {
|
||||||
|
font-size: 32rpx;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 照片网格
|
||||||
|
.photos-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo-item {
|
.photo-item {
|
||||||
width: 160rpx;
|
position: relative;
|
||||||
height: 160rpx;
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #f5f5f5;
|
border: 1rpx solid #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo-item image {
|
.photo-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.photo-upload {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
border: 2rpx dashed #d1d5db;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-plus {
|
||||||
|
font-size: 60rpx;
|
||||||
|
color: #9ca3af;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-tip {
|
||||||
|
display: block;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片删除按钮
|
||||||
|
.photo-delete {
|
||||||
|
position: absolute;
|
||||||
|
top: 4rpx;
|
||||||
|
right: 4rpx;
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.timeline {
|
.timeline {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -227,63 +403,129 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.timeline-dot {
|
.timeline-dot {
|
||||||
width: 16rpx;
|
width: 32rpx;
|
||||||
height: 16rpx;
|
height: 32rpx;
|
||||||
background: #d0d5dd;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin-right: 20rpx;
|
border: 4rpx solid #fff;
|
||||||
|
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||||
|
margin-right: 24rpx;
|
||||||
margin-top: 8rpx;
|
margin-top: 8rpx;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-dot.active {
|
.dot-system {
|
||||||
background: #173294;
|
background: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-manager {
|
||||||
|
background: #fb923c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot-engineer {
|
||||||
|
background: #34d399;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-line {
|
.timeline-line {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 6rpx;
|
left: 14rpx;
|
||||||
top: 28rpx;
|
top: 44rpx;
|
||||||
width: 4rpx;
|
width: 4rpx;
|
||||||
height: calc(100% - 20rpx);
|
height: calc(100% - 32rpx);
|
||||||
background: #e5ebff;
|
background: #f3f4f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-content {
|
.timeline-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
flex-direction: column;
|
background: #fff;
|
||||||
|
border: 1rpx solid #e5e7eb;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
padding: 20rpx 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-header {
|
.timeline-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
margin-bottom: 8rpx;
|
margin-bottom: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-time {
|
.timeline-actor {
|
||||||
font-size: 26rpx;
|
|
||||||
color: #173294;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-right: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-date {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-title {
|
|
||||||
display: block;
|
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #222;
|
font-weight: 700;
|
||||||
font-weight: 500;
|
color: #111827;
|
||||||
margin-bottom: 6rpx;
|
}
|
||||||
|
|
||||||
|
.timeline-action {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-desc {
|
.timeline-desc {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 24rpx;
|
font-size: 26rpx;
|
||||||
color: #666;
|
color: #6b7280;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-time {
|
||||||
|
display: block;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 底部占位
|
||||||
|
.footer-placeholder {
|
||||||
|
height: 120rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 底部操作栏
|
||||||
|
.footer-actions {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1rpx solid #e5e7eb;
|
||||||
|
padding: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 24rpx;
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
flex: 1;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1rpx solid #e5e7eb;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
background: #f59e0b;
|
||||||
|
border-color: #f59e0b;
|
||||||
|
|
||||||
|
.button-text {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
background: #10b981;
|
||||||
|
border-color: #10b981;
|
||||||
|
|
||||||
|
.button-text {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #6b7280;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -3,120 +3,186 @@
|
|||||||
<scroll-view style="flex:1">
|
<scroll-view style="flex:1">
|
||||||
<!-- #endif -->
|
<!-- #endif -->
|
||||||
<view class="page">
|
<view class="page">
|
||||||
<!-- 自定义导航栏 -->
|
<!-- 导航栏 -->
|
||||||
<view class="nav" :style="{ paddingTop: headerPaddingTop + 'px', height: headerTotalHeight + 'px' }">
|
<view class="nav" :style="{ paddingTop: headerPaddingTop + 'px', height: headerTotalHeight + 'px' }">
|
||||||
<view class="nav-back" @tap="goBack">
|
<view class="nav-back" @tap="goBack">
|
||||||
<view class="nav-back-icon"></view>
|
<view class="nav-back-icon"></view>
|
||||||
</view>
|
</view>
|
||||||
<text class="nav-title">工单详情</text>
|
<text class="nav-title">{{ mode === 'create' ? '创建工单' : '工单详情' }}</text>
|
||||||
<view class="nav-capsule"></view>
|
<view class="nav-capsule"></view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<scroll-view class="content" scroll-y="true" :style="{ marginTop: headerTotalHeight + 'px' }">
|
<scroll-view class="content" scroll-y="true" :style="{ marginTop: headerTotalHeight + 'px' }">
|
||||||
<!-- 工单信息 -->
|
<!-- 工单头部 -->
|
||||||
<view class="section">
|
<view class="header-card">
|
||||||
<view class="section-title">
|
<view class="header-info">
|
||||||
<view class="title-bar"></view>
|
<text class="workcase-id">{{ workcase.workcaseId }}</text>
|
||||||
<text class="title-text">工单信息</text>
|
<view class="header-tags">
|
||||||
|
<view class="status-badge" :class="getStatusClass(workcase.status)">
|
||||||
|
<text class="badge-text">{{ getStatusText(workcase.status) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="urgency-badge" :class="workcase.emergency === 'emergency' ? 'urgent' : 'normal'">
|
||||||
|
<text class="badge-text">{{ workcase.emergency === 'emergency' ? '紧急' : '普通' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="info-card">
|
<view class="header-action" v-if="workcase.workcaseId" @tap="handleViewChat">
|
||||||
<view class="info-item">
|
<text class="action-btn">查看对话</text>
|
||||||
<text class="info-label">工单号</text>
|
|
||||||
<text class="info-value">{{ workcase.workcaseId }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">工单状态</text>
|
|
||||||
<view class="status-tag" :class="getStatusClass(workcase.status)">
|
|
||||||
<text class="tag-text">{{ getStatusText(workcase.status) }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">客户姓名</text>
|
|
||||||
<text class="info-value">{{ workcase.username || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">联系电话</text>
|
|
||||||
<text class="info-value">{{ workcase.phone || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">紧急程度</text>
|
|
||||||
<view class="level-tag" :class="workcase.emergency === 'emergency' ? 'urgent' : 'normal'">
|
|
||||||
<text class="level-text">{{ workcase.emergency === 'emergency' ? '紧急' : '普通' }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">设备型号</text>
|
|
||||||
<text class="info-value">{{ workcase.device || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">设备序列号</text>
|
|
||||||
<text class="info-value">{{ workcase.deviceCode || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 故障信息 -->
|
<!-- 工单信息表单 -->
|
||||||
<view class="section">
|
<view class="section">
|
||||||
<view class="section-title">
|
<view class="form-container">
|
||||||
<view class="title-bar"></view>
|
<!-- 客户姓名 -->
|
||||||
<text class="title-text">故障信息</text>
|
<view class="form-item">
|
||||||
</view>
|
<text class="form-label">客户姓名</text>
|
||||||
<view class="info-card">
|
<input v-if="mode === 'create'" class="form-input" v-model="workcase.username" placeholder="请输入客户姓名" />
|
||||||
<view class="info-item">
|
<text v-else class="form-value">{{ workcase.username || '-' }}</text>
|
||||||
<text class="info-label">故障类型</text>
|
|
||||||
<text class="info-value">{{ workcase.type || '-' }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="info-item column">
|
|
||||||
<text class="info-label">故障描述</text>
|
<!-- 联系电话 -->
|
||||||
<text class="info-desc">{{ workcase.remark || '暂无描述' }}</text>
|
<view class="form-item">
|
||||||
|
<text class="form-label">联系电话</text>
|
||||||
|
<input v-if="mode === 'create'" class="form-input" v-model="workcase.phone" placeholder="请输入联系电话" />
|
||||||
|
<text v-else class="form-value">{{ workcase.phone || '-' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">创建时间</text>
|
<!-- 设备名称 -->
|
||||||
<text class="info-value">{{ workcase.createTime || '-' }}</text>
|
<view class="form-item">
|
||||||
|
<text class="form-label">设备名称</text>
|
||||||
|
<input v-if="mode === 'create'" class="form-input" v-model="workcase.device" placeholder="请输入设备名称" />
|
||||||
|
<text v-else class="form-value">{{ workcase.device || '-' }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="info-item">
|
|
||||||
<text class="info-label">处理人</text>
|
<!-- 故障类型 -->
|
||||||
<text class="info-value">{{ workcase.processor || '待分配' }}</text>
|
<view class="form-item">
|
||||||
|
<text class="form-label">故障类型</text>
|
||||||
|
<picker v-if="mode === 'create'" class="form-picker" :value="typeIndex" :range="faultTypes" @change="onTypeChange">
|
||||||
|
<view class="picker-content">
|
||||||
|
<text class="picker-text">{{ workcase.type || '请选择故障类型' }}</text>
|
||||||
|
<text class="picker-arrow">></text>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
<text v-else class="form-value">{{ workcase.type || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 紧急程度 -->
|
||||||
|
<view class="form-item" v-if="mode === 'create'">
|
||||||
|
<text class="form-label">紧急程度</text>
|
||||||
|
<picker class="form-picker" :value="emergencyIndex" :range="emergencies" @change="onEmergencyChange">
|
||||||
|
<view class="picker-content">
|
||||||
|
<text class="picker-text">{{ emergencies[emergencyIndex] }}</text>
|
||||||
|
<text class="picker-arrow">></text>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 现场地址 -->
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">现场地址</text>
|
||||||
|
<input v-if="mode === 'create'" class="form-input" v-model="workcase.address" placeholder="请输入现场地址" />
|
||||||
|
<text v-else class="form-value">{{ workcase.address || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 故障描述 -->
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="form-label">故障描述</text>
|
||||||
|
<textarea v-if="mode === 'create'" class="form-textarea" v-model="workcase.description" placeholder="请详细描述故障现象..." maxlength="500" />
|
||||||
|
<text v-else class="form-value">{{ workcase.description || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 设备铭牌 -->
|
||||||
|
<view class="form-item" v-if="mode !== 'create'">
|
||||||
|
<text class="form-label">设备铭牌</text>
|
||||||
|
<text class="form-value">{{ workcase.deviceNamePlate || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 铭牌照片 -->
|
||||||
|
<view class="form-item" v-if="mode !== 'create' && workcase.deviceNamePlateImg">
|
||||||
|
<text class="form-label">铭牌照片</text>
|
||||||
|
<view class="nameplate-photo" @tap="previewNameplateImage">
|
||||||
|
<image class="nameplate-image" :src="workcase.deviceNamePlateImg" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 处理人 -->
|
||||||
|
<view class="form-item" v-if="mode !== 'create'">
|
||||||
|
<text class="form-label">处理人</text>
|
||||||
|
<text class="form-value">{{ workcase.processorName || '未指派' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 创建时间 -->
|
||||||
|
<view class="form-item" v-if="mode !== 'create'">
|
||||||
|
<text class="form-label">创建时间</text>
|
||||||
|
<text class="form-value">{{ workcase.createTime || '-' }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 故障图片 -->
|
<!-- 故障图片 -->
|
||||||
<view class="section" v-if="workcase.imgs && workcase.imgs.length > 0">
|
<view class="section">
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<view class="title-bar"></view>
|
<view class="title-icon">📷</view>
|
||||||
<text class="title-text">故障图片</text>
|
<text class="title-text">故障图片</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="photo-list">
|
<view class="photos-grid">
|
||||||
<view class="photo-item" v-for="(img, index) in workcase.imgs" :key="index">
|
<view class="photo-item" v-for="(img, index) in workcase.imgs" :key="index" @tap="previewFaultImages(index)">
|
||||||
<image :src="img" mode="aspectFill" @tap="previewImage(img)" />
|
<image class="photo-image" :src="img" mode="aspectFill" />
|
||||||
|
<view v-if="mode === 'create'" class="photo-delete" @tap.stop="deleteFaultImage(index)">
|
||||||
|
<text class="delete-icon">×</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="mode === 'create' && workcase.imgs.length < 9" class="photo-upload" @tap="chooseFaultImages">
|
||||||
|
<text class="upload-plus">+</text>
|
||||||
|
<text class="upload-text">添加图片</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<text v-if="mode === 'create'" class="upload-tip">最多上传9张图片,支持拍照或从相册选择</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 处理记录 -->
|
<!-- 处理记录 -->
|
||||||
<view class="section">
|
<view class="section" v-if="mode !== 'create' && processList.length > 0">
|
||||||
<view class="section-title">
|
<view class="section-title">
|
||||||
<view class="title-bar"></view>
|
<view class="title-bar"></view>
|
||||||
<text class="title-text">处理记录</text>
|
<text class="title-text">处理记录</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="timeline">
|
<view class="timeline">
|
||||||
<view class="timeline-item" v-for="(item, index) in processList" :key="index">
|
<view class="timeline-item" v-for="(item, index) in processList" :key="index">
|
||||||
<view class="timeline-dot" :class="{ active: index === 0 }"></view>
|
<view class="timeline-dot" :class="getTimelineDotClass(item.status)"></view>
|
||||||
<view class="timeline-line" v-if="index < processList.length - 1"></view>
|
<view class="timeline-line" v-if="index < processList.length - 1"></view>
|
||||||
<view class="timeline-content">
|
<view class="timeline-body">
|
||||||
<view class="timeline-header">
|
<view class="timeline-header">
|
||||||
<text class="timeline-time">{{ item.time }}</text>
|
<text class="timeline-actor">{{ item.actor }}</text>
|
||||||
<text class="timeline-date">{{ item.date }}</text>
|
<text class="timeline-action">{{ item.action }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="timeline-title">{{ item.title }}</text>
|
|
||||||
<text class="timeline-desc" v-if="item.desc">{{ item.desc }}</text>
|
<text class="timeline-desc" v-if="item.desc">{{ item.desc }}</text>
|
||||||
|
<text class="timeline-time">{{ item.time }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部占位 -->
|
||||||
|
<view class="footer-placeholder"></view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 底部操作栏 -->
|
||||||
|
<view class="footer-actions">
|
||||||
|
<view class="action-button" @tap="goBack">
|
||||||
|
<text class="button-text">{{ mode === 'create' ? '取消' : '关闭' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="action-button primary" v-if="mode === 'create'" @tap="submitWorkcase">
|
||||||
|
<text class="button-text">提交工单</text>
|
||||||
|
</view>
|
||||||
|
<view class="action-button primary" v-if="mode === 'view' && workcase.status === 'pending'" @tap="handleAssign">
|
||||||
|
<text class="button-text">指派工程师</text>
|
||||||
|
</view>
|
||||||
|
<view class="action-button success" v-if="mode === 'view' && workcase.status === 'processing'" @tap="handleComplete">
|
||||||
|
<text class="button-text">完成工单</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- #ifdef APP -->
|
<!-- #ifdef APP -->
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
@@ -125,80 +191,165 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import type { TbWorkcaseDTO } from '@/types/workcase'
|
import type { TbWorkcaseDTO } from '@/types/workcase'
|
||||||
|
|
||||||
// 接口定义
|
// 接口定义
|
||||||
interface ProcessItem {
|
interface ProcessItem {
|
||||||
time: string
|
status: 'system' | 'manager' | 'engineer'
|
||||||
date: string
|
actor: string
|
||||||
title: string
|
action: string
|
||||||
desc?: string
|
desc?: string
|
||||||
|
time: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const headerPaddingTop = ref<number>(44)
|
const headerPaddingTop = ref<number>(44)
|
||||||
const headerTotalHeight = ref<number>(88)
|
const headerTotalHeight = ref<number>(88)
|
||||||
const workcaseId = ref<string>('')
|
const workcaseId = ref<string>('')
|
||||||
|
const mode = ref<'view' | 'create'>('view')
|
||||||
|
|
||||||
|
// 表单选项
|
||||||
|
const faultTypes = ref<string[]>(['电气系统故障', '机械故障', '控制系统故障', '油路系统故障', '其他故障'])
|
||||||
|
const typeIndex = ref<number>(0)
|
||||||
|
const emergencies = ref<string[]>(['普通', '紧急'])
|
||||||
|
const emergencyIndex = ref<number>(0)
|
||||||
|
|
||||||
// 工单数据
|
// 工单数据
|
||||||
const workcase = ref<TbWorkcaseDTO>({
|
const workcase = ref<TbWorkcaseDTO>({
|
||||||
workcaseId: 'TH20241217001',
|
workcaseId: 'W0202501130001',
|
||||||
userId: '1',
|
userId: '1',
|
||||||
username: '李经理',
|
username: '张伟',
|
||||||
phone: '13800138001',
|
phone: '138****5678',
|
||||||
type: '控制系统故障',
|
type: '电气系统故障',
|
||||||
device: 'TH-500GF',
|
device: 'TH-500GF',
|
||||||
deviceCode: 'TH20230501001',
|
deviceCode: 'TH-500GF-2023-001',
|
||||||
|
deviceNamePlate: 'TH-500GF',
|
||||||
|
deviceNamePlateImg: 'https://via.placeholder.com/400x300?text=设备铭牌',
|
||||||
|
address: '江西省南昌市红谷滩区xxx路xxx号',
|
||||||
|
description: '发电机启动后电压不稳定,波动范围较大,影响正常使用',
|
||||||
emergency: 'emergency',
|
emergency: 'emergency',
|
||||||
status: 'processing',
|
status: 'processing',
|
||||||
processor: '张三',
|
processorName: '小李',
|
||||||
remark: '发电机组无法启动,控制面板显示E03错误代码',
|
createTime: '2025-01-13 09:30:00',
|
||||||
createTime: '2024-12-17 15:30:00',
|
|
||||||
imgs: []
|
imgs: []
|
||||||
})
|
})
|
||||||
|
|
||||||
// 处理记录
|
// 处理记录
|
||||||
const processList = ref<ProcessItem[]>([
|
const processList = ref<ProcessItem[]>([
|
||||||
{ time: '16:45', date: '2024-12-17', title: '更换控制器主板', desc: '' },
|
{
|
||||||
{ time: '16:30', date: '2024-12-17', title: '发现控制器主板故障', desc: '经检测,主板供电模块损坏' },
|
status: 'engineer',
|
||||||
{ time: '16:15', date: '2024-12-17', title: '到达现场,开始检查设备', desc: '' },
|
actor: '小李',
|
||||||
{ time: '15:45', date: '2024-12-17', title: '工程师张三已接单', desc: '' },
|
action: '开始处理',
|
||||||
{ time: '15:30', date: '2024-12-17', title: '工单已创建', desc: '' }
|
desc: '已联系客户,计划今日上门检修',
|
||||||
|
time: '2025-01-13 08:15:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 'manager',
|
||||||
|
actor: '管理员',
|
||||||
|
action: '指派工程师',
|
||||||
|
desc: '指派给小李工程师处理',
|
||||||
|
time: '2025-01-12 15:00:00'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 'system',
|
||||||
|
actor: '系统',
|
||||||
|
action: '工单创建',
|
||||||
|
desc: '客户通过小电对话提交',
|
||||||
|
time: '2025-01-12 14:20:00'
|
||||||
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onLoad((options: any) => {
|
||||||
uni.getSystemInfo({
|
|
||||||
success: (res) => {
|
// 处理 mode 参数
|
||||||
// #ifdef MP-WEIXIN
|
if (options.mode === 'create') {
|
||||||
try {
|
mode.value = 'create'
|
||||||
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
|
||||||
headerPaddingTop.value = menuButtonInfo.top
|
// 从 storage 读取登录信息
|
||||||
headerTotalHeight.value = menuButtonInfo.bottom + 8
|
try {
|
||||||
} catch (e) {
|
let loginDomainRaw = uni.getStorageSync('loginDomain')
|
||||||
headerPaddingTop.value = res.statusBarHeight || 44
|
|
||||||
headerTotalHeight.value = (res.statusBarHeight || 44) + 44
|
let username = ''
|
||||||
|
let phone = ''
|
||||||
|
|
||||||
|
if (loginDomainRaw) {
|
||||||
|
// 如果是字符串,需要先解析
|
||||||
|
let loginDomain = loginDomainRaw
|
||||||
|
if (typeof loginDomainRaw === 'string') {
|
||||||
|
loginDomain = JSON.parse(loginDomainRaw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试多种可能的字段路径
|
||||||
|
username = loginDomain.userInfo?.username ||
|
||||||
|
loginDomain.user?.username ||
|
||||||
|
loginDomain.username || ''
|
||||||
|
|
||||||
|
phone = loginDomain.user?.phone ||
|
||||||
|
loginDomain.userInfo?.phone ||
|
||||||
|
loginDomain.phone || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建模式,初始化表单并填充用户信息
|
||||||
|
workcase.value = {
|
||||||
|
username: username,
|
||||||
|
phone: phone,
|
||||||
|
device: '',
|
||||||
|
type: '',
|
||||||
|
address: '',
|
||||||
|
description: '',
|
||||||
|
emergency: 'normal',
|
||||||
|
imgs: []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('读取用户信息失败:', error)
|
||||||
|
// 初始化空表单
|
||||||
|
workcase.value = {
|
||||||
|
username: '',
|
||||||
|
phone: '',
|
||||||
|
device: '',
|
||||||
|
type: '',
|
||||||
|
address: '',
|
||||||
|
description: '',
|
||||||
|
emergency: 'normal',
|
||||||
|
imgs: []
|
||||||
}
|
}
|
||||||
// #endif
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
headerPaddingTop.value = res.statusBarHeight || 44
|
|
||||||
headerTotalHeight.value = (res.statusBarHeight || 44) + 44
|
|
||||||
// #endif
|
|
||||||
}
|
}
|
||||||
})
|
} else if (options.workcaseId) {
|
||||||
|
// 查看模式
|
||||||
// 获取页面参数
|
workcaseId.value = options.workcaseId
|
||||||
const pages = getCurrentPages()
|
|
||||||
const currentPage = pages[pages.length - 1] as any
|
|
||||||
if (currentPage && currentPage.options && currentPage.options.workcaseId) {
|
|
||||||
workcaseId.value = currentPage.options.workcaseId
|
|
||||||
loadWorkcaseDetail(workcaseId.value)
|
loadWorkcaseDetail(workcaseId.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理 roomId 参数
|
||||||
|
if (options.roomId) {
|
||||||
|
// TODO: 可以将 roomId 关联到工单
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
})
|
})
|
||||||
|
|
||||||
// 加载工单详情
|
// 加载工单详情
|
||||||
function loadWorkcaseDetail(id: string) {
|
function loadWorkcaseDetail(id: string) {
|
||||||
console.log('加载工单详情:', id)
|
|
||||||
// TODO: 调用 workcaseAPI.getWorkcaseById(id) 获取数据
|
// TODO: 调用 workcaseAPI.getWorkcaseById(id) 获取数据
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,14 +373,131 @@ function getStatusText(status?: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预览图片
|
// 获取时间线圆点样式类
|
||||||
function previewImage(url: string) {
|
function getTimelineDotClass(status: string): string {
|
||||||
uni.previewImage({
|
switch (status) {
|
||||||
urls: workcase.value.imgs || [],
|
case 'system': return 'dot-system'
|
||||||
current: url
|
case 'manager': return 'dot-manager'
|
||||||
|
case 'engineer': return 'dot-engineer'
|
||||||
|
default: return 'dot-system'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看对话
|
||||||
|
function handleViewChat() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/chatRoom/chatRoom/chatRoom?roomId=${workcase.value.workcaseId}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 指派工程师
|
||||||
|
function handleAssign() {
|
||||||
|
uni.showToast({
|
||||||
|
title: '指派工程师',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
// TODO: 实现指派逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完成工单
|
||||||
|
function handleComplete() {
|
||||||
|
uni.showModal({
|
||||||
|
title: '完成确认',
|
||||||
|
content: '确认完成该工单?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
// TODO: 调用 API 完成工单
|
||||||
|
uni.showToast({
|
||||||
|
title: '工单已完成',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单选择器事件
|
||||||
|
function onTypeChange(e: any) {
|
||||||
|
typeIndex.value = e.detail.value
|
||||||
|
workcase.value.type = faultTypes.value[e.detail.value]
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEmergencyChange(e: any) {
|
||||||
|
emergencyIndex.value = e.detail.value
|
||||||
|
workcase.value.emergency = emergencyIndex.value === 0 ? 'normal' : 'emergency'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择故障图片
|
||||||
|
function chooseFaultImages() {
|
||||||
|
uni.chooseImage({
|
||||||
|
count: 9 - (workcase.value.imgs?.length || 0),
|
||||||
|
sizeType: ['compressed'],
|
||||||
|
sourceType: ['camera', 'album'],
|
||||||
|
success: (res) => {
|
||||||
|
if (!workcase.value.imgs) {
|
||||||
|
workcase.value.imgs = []
|
||||||
|
}
|
||||||
|
workcase.value.imgs.push(...res.tempFilePaths)
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.log('选择图片失败:', err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除故障图片
|
||||||
|
function deleteFaultImage(index: number) {
|
||||||
|
if (workcase.value.imgs) {
|
||||||
|
workcase.value.imgs.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预览铭牌照片
|
||||||
|
function previewNameplateImage() {
|
||||||
|
if (!workcase.value.deviceNamePlateImg) return
|
||||||
|
uni.previewImage({
|
||||||
|
urls: [workcase.value.deviceNamePlateImg],
|
||||||
|
current: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预览故障图片
|
||||||
|
function previewFaultImages(index: number) {
|
||||||
|
if (!workcase.value.imgs || workcase.value.imgs.length === 0) return
|
||||||
|
uni.previewImage({
|
||||||
|
urls: workcase.value.imgs,
|
||||||
|
current: index
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交工单
|
||||||
|
function submitWorkcase() {
|
||||||
|
// 校验必填项
|
||||||
|
if (!workcase.value.username || !workcase.value.phone || !workcase.value.description) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请填写必填项',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.showLoading({
|
||||||
|
title: '提交中...'
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: 调用 API 提交工单
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: '工单创建成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateBack()
|
||||||
|
}, 1500)
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
// 返回上一页
|
// 返回上一页
|
||||||
function goBack() {
|
function goBack() {
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
|
|||||||
@@ -140,24 +140,23 @@ const filteredOrders = computed(() => {
|
|||||||
|
|
||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.getSystemInfo({
|
const windowInfo = uni.getWindowInfo()
|
||||||
success: (res) => {
|
const statusBarHeight = windowInfo.statusBarHeight || 44
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
try {
|
// #ifdef MP-WEIXIN
|
||||||
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
try {
|
||||||
headerPaddingTop.value = menuButtonInfo.top
|
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
||||||
headerTotalHeight.value = menuButtonInfo.bottom + 8
|
headerPaddingTop.value = menuButtonInfo.top
|
||||||
} catch (e) {
|
headerTotalHeight.value = menuButtonInfo.bottom + 8
|
||||||
headerPaddingTop.value = res.statusBarHeight || 44
|
} catch (e) {
|
||||||
headerTotalHeight.value = (res.statusBarHeight || 44) + 44
|
headerPaddingTop.value = statusBarHeight
|
||||||
}
|
headerTotalHeight.value = statusBarHeight + 44
|
||||||
// #endif
|
}
|
||||||
// #ifndef MP-WEIXIN
|
// #endif
|
||||||
headerPaddingTop.value = res.statusBarHeight || 44
|
// #ifndef MP-WEIXIN
|
||||||
headerTotalHeight.value = (res.statusBarHeight || 44) + 44
|
headerPaddingTop.value = statusBarHeight
|
||||||
// #endif
|
headerTotalHeight.value = statusBarHeight + 44
|
||||||
}
|
// #endif
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: 实际调用API获取工单列表
|
// TODO: 实际调用API获取工单列表
|
||||||
loadWorkcaseList()
|
loadWorkcaseList()
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user