工单详情
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f4f5f7;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.nav {
|
||||
@@ -17,6 +17,7 @@
|
||||
padding-bottom: 16rpx;
|
||||
box-sizing: border-box;
|
||||
z-index: 100;
|
||||
border-bottom: 1rpx solid #e5e7eb;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
@@ -50,11 +51,64 @@
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 176rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
padding-bottom: 60rpx;
|
||||
min-height: calc(100vh - 176rpx);
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx;
|
||||
padding-bottom: 140rpx;
|
||||
}
|
||||
|
||||
// 工单头部卡片
|
||||
.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 {
|
||||
@@ -88,43 +142,102 @@
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// 表单容器
|
||||
.form-container {
|
||||
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;
|
||||
flex-direction: row;
|
||||
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 {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
width: 160rpx;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
.picker-text {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #222;
|
||||
text-align: right;
|
||||
font-size: 28rpx;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.info-desc {
|
||||
font-size: 26rpx;
|
||||
color: #222;
|
||||
line-height: 1.6;
|
||||
margin-top: 8rpx;
|
||||
text-align: left;
|
||||
.picker-arrow {
|
||||
font-size: 32rpx;
|
||||
color: #9ca3af;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
// 铭牌照片
|
||||
.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 {
|
||||
@@ -192,25 +305,88 @@
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.photo-list {
|
||||
.section-title {
|
||||
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;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.photo-item {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
position: relative;
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
background: #f5f5f5;
|
||||
border: 1rpx solid #e5e7eb;
|
||||
}
|
||||
|
||||
.photo-item image {
|
||||
.photo-image {
|
||||
width: 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 {
|
||||
position: relative;
|
||||
}
|
||||
@@ -227,63 +403,129 @@
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
background: #d0d5dd;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
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;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.timeline-dot.active {
|
||||
background: #173294;
|
||||
.dot-system {
|
||||
background: #60a5fa;
|
||||
}
|
||||
|
||||
.dot-manager {
|
||||
background: #fb923c;
|
||||
}
|
||||
|
||||
.dot-engineer {
|
||||
background: #34d399;
|
||||
}
|
||||
|
||||
.timeline-line {
|
||||
position: absolute;
|
||||
left: 6rpx;
|
||||
top: 28rpx;
|
||||
left: 14rpx;
|
||||
top: 44rpx;
|
||||
width: 4rpx;
|
||||
height: calc(100% - 20rpx);
|
||||
background: #e5ebff;
|
||||
height: calc(100% - 32rpx);
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
.timeline-body {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
border: 1rpx solid #e5e7eb;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.timeline-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.timeline-time {
|
||||
font-size: 26rpx;
|
||||
color: #173294;
|
||||
font-weight: 600;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.timeline-date {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
display: block;
|
||||
.timeline-actor {
|
||||
font-size: 28rpx;
|
||||
color: #222;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6rpx;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.timeline-action {
|
||||
font-size: 28rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.timeline-desc {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
color: #6b7280;
|
||||
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">
|
||||
<!-- #endif -->
|
||||
<view class="page">
|
||||
<!-- 自定义导航栏 -->
|
||||
<!-- 导航栏 -->
|
||||
<view class="nav" :style="{ paddingTop: headerPaddingTop + 'px', height: headerTotalHeight + 'px' }">
|
||||
<view class="nav-back" @tap="goBack">
|
||||
<view class="nav-back-icon"></view>
|
||||
</view>
|
||||
<text class="nav-title">工单详情</text>
|
||||
<text class="nav-title">{{ mode === 'create' ? '创建工单' : '工单详情' }}</text>
|
||||
<view class="nav-capsule"></view>
|
||||
</view>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<scroll-view class="content" scroll-y="true" :style="{ marginTop: headerTotalHeight + 'px' }">
|
||||
<!-- 工单信息 -->
|
||||
<view class="section">
|
||||
<view class="section-title">
|
||||
<view class="title-bar"></view>
|
||||
<text class="title-text">工单信息</text>
|
||||
<!-- 工单头部 -->
|
||||
<view class="header-card">
|
||||
<view class="header-info">
|
||||
<text class="workcase-id">{{ workcase.workcaseId }}</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 class="info-card">
|
||||
<view class="info-item">
|
||||
<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 class="header-action" v-if="workcase.workcaseId" @tap="handleViewChat">
|
||||
<text class="action-btn">查看对话</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 故障信息 -->
|
||||
<!-- 工单信息表单 -->
|
||||
<view class="section">
|
||||
<view class="section-title">
|
||||
<view class="title-bar"></view>
|
||||
<text class="title-text">故障信息</text>
|
||||
</view>
|
||||
<view class="info-card">
|
||||
<view class="info-item">
|
||||
<text class="info-label">故障类型</text>
|
||||
<text class="info-value">{{ workcase.type || '-' }}</text>
|
||||
<view class="form-container">
|
||||
<!-- 客户姓名 -->
|
||||
<view class="form-item">
|
||||
<text class="form-label">客户姓名</text>
|
||||
<input v-if="mode === 'create'" class="form-input" v-model="workcase.username" placeholder="请输入客户姓名" />
|
||||
<text v-else class="form-value">{{ workcase.username || '-' }}</text>
|
||||
</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 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 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 class="section" v-if="workcase.imgs && workcase.imgs.length > 0">
|
||||
<view class="section">
|
||||
<view class="section-title">
|
||||
<view class="title-bar"></view>
|
||||
<view class="title-icon">📷</view>
|
||||
<text class="title-text">故障图片</text>
|
||||
</view>
|
||||
<view class="photo-list">
|
||||
<view class="photo-item" v-for="(img, index) in workcase.imgs" :key="index">
|
||||
<image :src="img" mode="aspectFill" @tap="previewImage(img)" />
|
||||
<view class="photos-grid">
|
||||
<view class="photo-item" v-for="(img, index) in workcase.imgs" :key="index" @tap="previewFaultImages(index)">
|
||||
<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>
|
||||
<text v-if="mode === 'create'" class="upload-tip">最多上传9张图片,支持拍照或从相册选择</text>
|
||||
</view>
|
||||
|
||||
<!-- 处理记录 -->
|
||||
<view class="section">
|
||||
<view class="section" v-if="mode !== 'create' && processList.length > 0">
|
||||
<view class="section-title">
|
||||
<view class="title-bar"></view>
|
||||
<text class="title-text">处理记录</text>
|
||||
</view>
|
||||
<view class="timeline">
|
||||
<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-content">
|
||||
<view class="timeline-body">
|
||||
<view class="timeline-header">
|
||||
<text class="timeline-time">{{ item.time }}</text>
|
||||
<text class="timeline-date">{{ item.date }}</text>
|
||||
<text class="timeline-actor">{{ item.actor }}</text>
|
||||
<text class="timeline-action">{{ item.action }}</text>
|
||||
</view>
|
||||
<text class="timeline-title">{{ item.title }}</text>
|
||||
<text class="timeline-desc" v-if="item.desc">{{ item.desc }}</text>
|
||||
<text class="timeline-time">{{ item.time }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部占位 -->
|
||||
<view class="footer-placeholder"></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>
|
||||
<!-- #ifdef APP -->
|
||||
</scroll-view>
|
||||
@@ -125,80 +191,165 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import type { TbWorkcaseDTO } from '@/types/workcase'
|
||||
|
||||
// 接口定义
|
||||
interface ProcessItem {
|
||||
time: string
|
||||
date: string
|
||||
title: string
|
||||
status: 'system' | 'manager' | 'engineer'
|
||||
actor: string
|
||||
action: string
|
||||
desc?: string
|
||||
time: string
|
||||
}
|
||||
|
||||
// 响应式数据
|
||||
const headerPaddingTop = ref<number>(44)
|
||||
const headerTotalHeight = ref<number>(88)
|
||||
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>({
|
||||
workcaseId: 'TH20241217001',
|
||||
workcaseId: 'W0202501130001',
|
||||
userId: '1',
|
||||
username: '李经理',
|
||||
phone: '13800138001',
|
||||
type: '控制系统故障',
|
||||
username: '张伟',
|
||||
phone: '138****5678',
|
||||
type: '电气系统故障',
|
||||
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',
|
||||
status: 'processing',
|
||||
processor: '张三',
|
||||
remark: '发电机组无法启动,控制面板显示E03错误代码',
|
||||
createTime: '2024-12-17 15:30:00',
|
||||
processorName: '小李',
|
||||
createTime: '2025-01-13 09:30:00',
|
||||
imgs: []
|
||||
})
|
||||
|
||||
// 处理记录
|
||||
const processList = ref<ProcessItem[]>([
|
||||
{ time: '16:45', date: '2024-12-17', title: '更换控制器主板', desc: '' },
|
||||
{ time: '16:30', date: '2024-12-17', title: '发现控制器主板故障', desc: '经检测,主板供电模块损坏' },
|
||||
{ time: '16:15', date: '2024-12-17', title: '到达现场,开始检查设备', desc: '' },
|
||||
{ time: '15:45', date: '2024-12-17', title: '工程师张三已接单', desc: '' },
|
||||
{ time: '15:30', date: '2024-12-17', title: '工单已创建', desc: '' }
|
||||
{
|
||||
status: 'engineer',
|
||||
actor: '小李',
|
||||
action: '开始处理',
|
||||
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(() => {
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
try {
|
||||
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
||||
headerPaddingTop.value = menuButtonInfo.top
|
||||
headerTotalHeight.value = menuButtonInfo.bottom + 8
|
||||
} catch (e) {
|
||||
headerPaddingTop.value = res.statusBarHeight || 44
|
||||
headerTotalHeight.value = (res.statusBarHeight || 44) + 44
|
||||
onLoad((options: any) => {
|
||||
|
||||
// 处理 mode 参数
|
||||
if (options.mode === 'create') {
|
||||
mode.value = 'create'
|
||||
|
||||
// 从 storage 读取登录信息
|
||||
try {
|
||||
let loginDomainRaw = uni.getStorageSync('loginDomain')
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
// 获取页面参数
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1] as any
|
||||
if (currentPage && currentPage.options && currentPage.options.workcaseId) {
|
||||
workcaseId.value = currentPage.options.workcaseId
|
||||
} else if (options.workcaseId) {
|
||||
// 查看模式
|
||||
workcaseId.value = options.workcaseId
|
||||
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) {
|
||||
console.log('加载工单详情:', id)
|
||||
// TODO: 调用 workcaseAPI.getWorkcaseById(id) 获取数据
|
||||
}
|
||||
|
||||
@@ -222,14 +373,131 @@ function getStatusText(status?: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
// 预览图片
|
||||
function previewImage(url: string) {
|
||||
uni.previewImage({
|
||||
urls: workcase.value.imgs || [],
|
||||
current: url
|
||||
// 获取时间线圆点样式类
|
||||
function getTimelineDotClass(status: string): string {
|
||||
switch (status) {
|
||||
case 'system': return 'dot-system'
|
||||
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() {
|
||||
uni.navigateBack()
|
||||
|
||||
@@ -140,24 +140,23 @@ const filteredOrders = computed(() => {
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
try {
|
||||
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
||||
headerPaddingTop.value = menuButtonInfo.top
|
||||
headerTotalHeight.value = menuButtonInfo.bottom + 8
|
||||
} catch (e) {
|
||||
headerPaddingTop.value = res.statusBarHeight || 44
|
||||
headerTotalHeight.value = (res.statusBarHeight || 44) + 44
|
||||
}
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
headerPaddingTop.value = res.statusBarHeight || 44
|
||||
headerTotalHeight.value = (res.statusBarHeight || 44) + 44
|
||||
// #endif
|
||||
}
|
||||
})
|
||||
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
|
||||
|
||||
// TODO: 实际调用API获取工单列表
|
||||
loadWorkcaseList()
|
||||
|
||||
Reference in New Issue
Block a user