|
|
@@ -103,6 +103,8 @@
|
|
|
<el-descriptions-item label="处置结果" :span="2">{{ selectedOrder.repairResult || '—' }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
|
|
|
+ <RepairImagePanel :before-images="selectedOrder.maintenanceBeforeImg" :after-images="selectedOrder.maintenanceAfterImg" />
|
|
|
+
|
|
|
<div class="mt-5 border-t border-[#e9ecef] pt-3">
|
|
|
<div class="mb-3 mt-4 flex items-center gap-2 text-[16px] font-600">
|
|
|
<span><el-icon><Timer /></el-icon> 工单操作</span>
|
|
|
@@ -134,8 +136,24 @@
|
|
|
:timestamp="formatDateTime(log.operTime)"
|
|
|
:type="getLogType(log.operType)"
|
|
|
>
|
|
|
- {{ log.operDesc }}
|
|
|
- <span v-if="log.operUserName" class="ml-3 text-[12px] text-[#909399]">操作人: {{ log.operUserName }}</span>
|
|
|
+ <div>
|
|
|
+ <span>{{ log.operDesc }}</span>
|
|
|
+ <span v-if="log.operUserName" class="ml-3 text-[12px] text-[#909399]">操作人:{{ log.operUserName }}</span>
|
|
|
+ <!-- 显示处理图片(如果有的话) -->
|
|
|
+ <div v-if="log.maintenanceImages && log.maintenanceImages.length" class="mt-2 flex gap-2">
|
|
|
+ <div
|
|
|
+ v-for="(img, idx) in log.maintenanceImages"
|
|
|
+ :key="idx"
|
|
|
+ class="cursor-pointer rounded border p-1 hover:shadow-md"
|
|
|
+ @click="() => previewImage(img.url)"
|
|
|
+ >
|
|
|
+ <img :src="img.url" alt="处理图片" class="h-[60px] w-[80px] object-cover" />
|
|
|
+ <div class="text-center text-xs text-[#909399]">
|
|
|
+ {{ img.type === 'before' ? '⚠️ 处理前' : img.type === 'after' ? '✅ 处理后' : '🖼️ 图片' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</el-timeline-item>
|
|
|
</el-timeline>
|
|
|
<el-empty v-else description="暂无工单日志" :image-size="80" />
|
|
|
@@ -148,6 +166,22 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ <el-dialog v-model="beforeImageDialogVisible" title="开始维修" width="600px">
|
|
|
+ <ImageUpload v-model="maintenanceBeforeImages" :limit="9" :isShowTip="true" :upload-url="imageUploadUrl" />
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="beforeImageDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitStartRepair">确定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <el-dialog v-model="afterImageDialogVisible" title="提交验收" width="600px">
|
|
|
+ <ImageUpload v-model="maintenanceAfterImages" :limit="9" :isShowTip="true" :upload-url="imageUploadUrl" />
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="afterImageDialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitVerify">提交</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
<el-dialog v-model="delayDialogVisible" title="申请延期" width="450px">
|
|
|
<el-form label-width="88px">
|
|
|
<el-form-item label="截止时间">
|
|
|
@@ -216,13 +250,20 @@
|
|
|
<el-button type="primary" @click="confirmDispatchUser">确认选择</el-button>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
+
|
|
|
+ <!-- 图片预览弹窗 -->
|
|
|
+ <el-dialog v-model="imagePreviewVisible" title="图片预览" width="600px" append-to-body>
|
|
|
+ <img :src="currentImagePreviewUrl" alt="工单图片" class="max-w-full" style="border-radius: 4px;" />
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import {computed, nextTick, onMounted, ref, watch} from 'vue'
|
|
|
+import{computed, nextTick, onMounted, ref, watch} from 'vue'
|
|
|
import {Document, Search, Tickets, Timer} from '@element-plus/icons-vue'
|
|
|
import {ElMessage} from 'element-plus'
|
|
|
+import ImageUpload from '@/components/ImageUpload/index.vue'
|
|
|
+import RepairImagePanel from '@/components/RepairImagePanel/index.vue'
|
|
|
import {getManholeWorkOrderDetail, getManholeWorkOrderPage, operManholeWorkOrder} from '@/api/pipeNetwork/basic'
|
|
|
import {deptTreeSelect, listUser} from '@/api/system/user'
|
|
|
|
|
|
@@ -242,6 +283,11 @@ const receiveUserDisplay = ref('')
|
|
|
const delayDialogVisible = ref(false)
|
|
|
const delayDeadline = ref('')
|
|
|
const delayReason = ref('')
|
|
|
+const beforeImageDialogVisible = ref(false)
|
|
|
+const afterImageDialogVisible = ref(false)
|
|
|
+const maintenanceBeforeImages = ref('')
|
|
|
+const maintenanceAfterImages = ref('')
|
|
|
+const imageUploadUrl = import.meta.env.VITE_APP_BASE_API + '/minio/upload'
|
|
|
const dispatchDialogVisible = ref(false)
|
|
|
const dispatchUserLoading = ref(false)
|
|
|
const deptOptions = ref([])
|
|
|
@@ -251,11 +297,24 @@ const dispatchUsers = ref([])
|
|
|
const dispatchSelectedUser = ref(null)
|
|
|
const deptTreeRef = ref(null)
|
|
|
|
|
|
+// 图片预览相关
|
|
|
+const imagePreviewVisible = ref(false)
|
|
|
+const currentImagePreviewUrl = ref('')
|
|
|
+const baseUrl = import.meta.env.VITE_APP_BASE_API
|
|
|
+
|
|
|
function formatDateTime(value) {
|
|
|
if (!value) return '—'
|
|
|
return String(value).replace('T', ' ')
|
|
|
}
|
|
|
|
|
|
+const maintenanceBeforeImageList = computed(() => splitImageList(selectedOrder.value?.maintenanceBeforeImg))
|
|
|
+const maintenanceAfterImageList = computed(() => splitImageList(selectedOrder.value?.maintenanceAfterImg))
|
|
|
+
|
|
|
+function previewImage(url) {
|
|
|
+ currentImagePreviewUrl.value = url
|
|
|
+ imagePreviewVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
function getOrderLevelMeta(level) {
|
|
|
const map = {
|
|
|
1: { text: '紧急', tag: 'danger' },
|
|
|
@@ -581,8 +640,27 @@ async function acceptOrder() {
|
|
|
}
|
|
|
|
|
|
async function startRepair() {
|
|
|
+ maintenanceBeforeImages.value = ''
|
|
|
+ beforeImageDialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+function normalizeImageUrl(value) {
|
|
|
+ const url = String(value || '').trim()
|
|
|
+ if (!url || /^(https?:|blob:|data:)/.test(url)) return url
|
|
|
+ if (baseUrl && (url === baseUrl || url.startsWith(`${baseUrl}/`))) return url
|
|
|
+ return url.startsWith('/') ? baseUrl + url : `${baseUrl}/${url}`
|
|
|
+}
|
|
|
+
|
|
|
+function splitImageList(value) {
|
|
|
+ return value
|
|
|
+ ? String(value).split(',').map(normalizeImageUrl).filter(Boolean)
|
|
|
+ : []
|
|
|
+}
|
|
|
+
|
|
|
+async function submitStartRepair() {
|
|
|
try {
|
|
|
- await operateCurrent(4, '开始维修')
|
|
|
+ await operateCurrent(4, '开始维修', { maintenanceBeforeImg: maintenanceBeforeImages.value })
|
|
|
+ beforeImageDialogVisible.value = false
|
|
|
ElMessage.success('已开始维修')
|
|
|
} catch (error) {
|
|
|
ElMessage.error(error?.message || '开始维修失败')
|
|
|
@@ -590,8 +668,14 @@ async function startRepair() {
|
|
|
}
|
|
|
|
|
|
async function submitVerify() {
|
|
|
+ if (!afterImageDialogVisible.value) {
|
|
|
+ maintenanceAfterImages.value = ''
|
|
|
+ afterImageDialogVisible.value = true
|
|
|
+ return
|
|
|
+ }
|
|
|
try {
|
|
|
- await operateCurrent(5, '提交验收')
|
|
|
+ await operateCurrent(5, '提交验收', { maintenanceAfterImg: maintenanceAfterImages.value })
|
|
|
+ afterImageDialogVisible.value = false
|
|
|
ElMessage.success('已提交验收')
|
|
|
} catch (error) {
|
|
|
ElMessage.error(error?.message || '提交验收失败')
|
|
|
@@ -670,3 +754,116 @@ onMounted(() => {
|
|
|
loadOrders()
|
|
|
})
|
|
|
</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.repair-image-panel {
|
|
|
+ margin-top: 20px;
|
|
|
+ padding-top: 16px;
|
|
|
+ border-top: 1px solid #e9ecef;
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-panel__header,
|
|
|
+.repair-image-column__header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-panel__header {
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-panel__title {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ color: #303133;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-panel__total {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-panel__body {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-column {
|
|
|
+ min-width: 0;
|
|
|
+ padding: 14px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: #fafbfc;
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-column__header {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ color: #606266;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-list {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-item {
|
|
|
+ position: relative;
|
|
|
+ aspect-ratio: 4 / 3;
|
|
|
+ overflow: hidden;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 6px;
|
|
|
+ background: #f2f6fc;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-item:hover img {
|
|
|
+ transform: scale(1.04);
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-item img {
|
|
|
+ display: block;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ transition: transform 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-item__index {
|
|
|
+ position: absolute;
|
|
|
+ right: 5px;
|
|
|
+ bottom: 5px;
|
|
|
+ min-width: 20px;
|
|
|
+ padding: 2px 5px;
|
|
|
+ border-radius: 10px;
|
|
|
+ background: rgb(0 0 0 / 55%);
|
|
|
+ color: #fff;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 16px;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.repair-image-empty {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ min-height: 120px;
|
|
|
+ border: 1px dashed #dcdfe6;
|
|
|
+ border-radius: 6px;
|
|
|
+ color: #a8abb2;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 900px) {
|
|
|
+ .repair-image-panel__body {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|