| New file |
| | |
| | | <template> |
| | | <el-dialog |
| | | class="custom-dialog" |
| | | align-center |
| | | v-model="dialogVisible" |
| | | :title="isEditMode ? '编辑工单' : '工单详情'" |
| | | width="80%" |
| | | append-to-body |
| | | @close="handleClose" |
| | | > |
| | | <div class="detail-container"> |
| | | <div class="detail-top-title"> |
| | | <div class="event-title-center event-orderNumber"> |
| | | {{ currentDetail.orderNumber || '工单编号' }} |
| | | </div> |
| | | <div class="event-title-center">{{ currentDetail.orderName || '事件名称' }}</div> |
| | | </div> |
| | | <div v-if="totalTime" class="event-total-time">总耗时:{{ totalTime }}</div> |
| | | <!-- 工单状态流程 --> |
| | | <div class="custom-steps-container"> |
| | | <!-- 标题行 --> |
| | | <div class="steps-titles"> |
| | | <div |
| | | v-for="(status, index) in stepStatusList" |
| | | :key="index" |
| | | :class="{ |
| | | 'step-title': true, |
| | | active: index <= stepStatusList.indexOf(String(currentDetail.status)), |
| | | }" |
| | | > |
| | | {{ mapStatus(status) }} |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- Element Steps 组件 --> |
| | | <el-steps :active="getActiveStep() - 1" align-center class="custom-steps"> |
| | | <el-step v-for="(status, index) in stepStatusList" :key="index"> |
| | | <template #description> |
| | | <span class="step-description"> |
| | | {{ getStepHandler(status) }} |
| | | </span> |
| | | <div class="step-description" v-if="getStepTime(status)"> |
| | | <span class="step-timer"> 耗时:{{ getStepTime(status) }} </span> |
| | | </div> |
| | | <div class="step-description"> |
| | | {{ getStepCreateTime(status) }} |
| | | </div> |
| | | </template> |
| | | </el-step> |
| | | </el-steps> |
| | | </div> |
| | | |
| | | <div class="PopUpTableScrolls"> |
| | | <!-- 基本信息表格 --> |
| | | <el-table :show-header="false" :data="formattedDetailFields" border class="tableCss"> |
| | | <el-table-column prop="label1" label="基本信息" width="150"> |
| | | <template #default="{ row }"> |
| | | <span |
| | | v-if=" |
| | | currentDetail.status === 0 && |
| | | (row.label1 === '关联算法' || row.label1 === '工单名称') |
| | | " |
| | | class="required-label" |
| | | > |
| | | <span class="required-star">*</span>{{ row.label1 }} |
| | | </span> |
| | | <span v-else>{{ row.label1 }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column> |
| | | <template #default="{ row }"> |
| | | <template |
| | | v-if=" |
| | | currentDetail.status === 0 && |
| | | row.label1 === '工单名称' && |
| | | hasProcessingBtnPermission() |
| | | " |
| | | > |
| | | <el-input |
| | | v-model="currentDetail.orderName" |
| | | placeholder="请输入工单名称" |
| | | class="required-input" |
| | | /> |
| | | </template> |
| | | <template |
| | | v-else-if=" |
| | | currentDetail.status === 0 && |
| | | row.label1 === '关联算法' && |
| | | hasProcessingBtnPermission() |
| | | " |
| | | > |
| | | <el-select |
| | | v-model="currentDetail.aiType" |
| | | placeholder="请选择关联算法" |
| | | class="required-input" |
| | | > |
| | | <el-option |
| | | v-for="item in algorithms" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </template> |
| | | <template v-else>{{ row.value1 }}</template> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="label2" label="基本信息" width="150"> |
| | | <template #default="{ row }"> |
| | | <span |
| | | v-if="currentDetail.status === 0 && row.label2 === '工单内容'" |
| | | class="required-label" |
| | | > |
| | | <span class="required-star">*</span>{{ row.label2 }} |
| | | </span> |
| | | <span v-else>{{ row.label2 }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column> |
| | | <template #default="{ row }"> |
| | | <template |
| | | v-if=" |
| | | currentDetail.status === 0 && |
| | | row.label2 === '工单内容' && |
| | | hasProcessingBtnPermission() |
| | | " |
| | | > |
| | | <el-input |
| | | type="textarea" |
| | | :maxlength="200" |
| | | show-word-limit |
| | | v-model="currentDetail.content" |
| | | placeholder="请输入工单内容" |
| | | class="required-input" |
| | | /> |
| | | </template> |
| | | <template v-else>{{ row.value2 }}</template> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <!-- 事件处理详情 --> |
| | | <div v-if="[3, 4].includes(currentDetail.status)" class="form-section"> |
| | | <div class="section-title"> |
| | | <template v-if="currentDetail.status === 3"> |
| | | <span class="required-label"> |
| | | <span class="required-star">*</span>事件处理详情 |
| | | </span> |
| | | </template> |
| | | <template v-else> 事件处理详情</template> |
| | | </div> |
| | | <!-- 处理中状态显示输入框 --> |
| | | <template v-if="currentDetail.status === 3 && hasProcessedAndOverBtnPermission()"> |
| | | <el-input |
| | | type="textarea" |
| | | v-model="currentDetail.processingDetail" |
| | | placeholder="请输入事件处理详情" |
| | | :rows="4" |
| | | :maxlength="200" |
| | | show-word-limit |
| | | style="width: 100%; margin-bottom: 10px" |
| | | :disabled="!isCurrentUserAssigned" |
| | | /> |
| | | </template> |
| | | <!-- 已完成和已完结状态显示只读文本 --> |
| | | <template v-else> |
| | | <div class="readonly-processing-detail"> |
| | | {{ currentDetail.processingDetail }} |
| | | </div> |
| | | </template> |
| | | </div> |
| | | |
| | | <!-- 上传图片 --> |
| | | <div v-if="[3].includes(currentDetail.status)" class="form-section uploadImg"> |
| | | <div class="section-title" v-if="hasProcessedAndOverBtnPermission()"> |
| | | <template v-if="currentDetail.status === 3"> |
| | | <span class="required-label"> <span class="required-star">*</span>上传图片 </span> |
| | | </template> |
| | | <template v-else> 上传图片</template> |
| | | </div> |
| | | <el-upload |
| | | :disabled="!isCurrentUserAssigned" |
| | | v-if="hasProcessedAndOverBtnPermission()" |
| | | ref="uploadRef" |
| | | :action="'#'" |
| | | :auto-upload="false" |
| | | list-type="picture-card" |
| | | :on-change="handleFileChange" |
| | | :on-remove="handleUploadRemove" |
| | | :before-upload="beforeUpload" |
| | | :file-list="currentDetail.photos || []" |
| | | :limit="1" |
| | | accept="image/*" |
| | | class="detail-upload" |
| | | > |
| | | <template v-if="!currentDetail.photos || currentDetail.photos.length < 1"> |
| | | <div class="el-icon-plus"> |
| | | <span>+</span> |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | <div class="el-upload__tip" v-if="hasProcessedAndOverBtnPermission()"> |
| | | (上传照片即可完结工单,只能上传jpg、jpeg、png照片,且不超过5M) |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 图片和地图部分 --> |
| | | <div class="media-section"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="media-box"> |
| | | <div class="media-title">事件图片</div> |
| | | <div class="media-content"> |
| | | <el-image |
| | | v-if="currentDetail.mediaUrl" |
| | | :src="getThumbUrl(currentDetail.mediaUrl)" |
| | | :preview-src-list="[getPreviewUrl(currentDetail.mediaUrl)]" |
| | | fit="contain" |
| | | style="cursor: pointer" |
| | | > |
| | | <template #placeholder> |
| | | <div class="image-placeholder"> |
| | | <i class="el-icon-picture-outline"></i> |
| | | <span>加载中...</span> |
| | | </div> |
| | | </template> |
| | | <template #error> |
| | | <div class="image-error"> |
| | | <i class="el-icon-picture-outline"></i> |
| | | <span>加载失败</span> |
| | | </div> |
| | | </template> |
| | | </el-image> |
| | | <div v-else class="no-media">暂无图片/视频</div> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="media-box"> |
| | | <template v-if="currentDetail.status === 4"> |
| | | <div class="media-title">工单处理图片</div> |
| | | <div class="media-content"> |
| | | <el-image |
| | | v-if="currentDetail.updatePhotoUrl" |
| | | :src="getThumbUrl(currentDetail.updatePhotoUrl)" |
| | | :preview-src-list="[getPreviewUrl(currentDetail.updatePhotoUrl)]" |
| | | fit="fill" |
| | | > |
| | | <template #placeholder> |
| | | <div class="image-placeholder"> |
| | | <i class="el-icon-picture-outline"></i> |
| | | <span>加载中...</span> |
| | | </div> |
| | | </template> |
| | | <template #error> |
| | | <div class="image-error"> |
| | | <i class="el-icon-picture-outline"></i> |
| | | <span>加载失败</span> |
| | | </div> |
| | | </template> |
| | | </el-image> |
| | | <div v-else class="no-media">暂无处理图片</div> |
| | | </div> |
| | | </template> |
| | | <template v-else> |
| | | <div class="media-title"> |
| | | 地图标记事件点 |
| | | <el-popover |
| | | v-if="currentDetail.status === 3 || currentDetail.status === 0" |
| | | popper-class="custom-qrcode-popover" |
| | | :width="120" |
| | | :visible="currentDetail.showQR && dialogVisible" |
| | | placement="top" |
| | | title="" |
| | | trigger="click" |
| | | > |
| | | <template #reference> |
| | | <img |
| | | @click.stop="handleQRCode(currentDetail)" |
| | | class="QRCodeImg" |
| | | src="@/assets/images/dataCenter/qrCode.svg" |
| | | alt="" |
| | | title="事件导航" |
| | | /> |
| | | </template> |
| | | <div class="qrcode-content"> |
| | | <div class="close-btn" @click.stop="currentDetail.showQR = false">×</div> |
| | | <CreateQRcode |
| | | v-if="currentDetail.showQR && dialogVisible" |
| | | :lat-and-lon="currentDetail.location" |
| | | /> |
| | | </div> |
| | | </el-popover> |
| | | </div> |
| | | <div class="media-content"> |
| | | <map-container v-if="dialogVisible" ref="mapContainerRef" /> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 操作按钮 --> |
| | | <div class="dialog-footer1-new"> |
| | | <div |
| | | :class="currentIndex === 0 ? 'disableds' : ''" |
| | | class="leftBtn" |
| | | @click="handlePrev" |
| | | > |
| | | 上一页 |
| | | </div> |
| | | <div class="btngroups"> |
| | | <template v-if="currentDetail.status === 2"> |
| | | <!-- 待审核 --> |
| | | <el-button |
| | | v-if="hasReviewBtnPermission()" |
| | | type="primary" |
| | | :loading="approveLoading" |
| | | @click="handleApprove" |
| | | icon="el-icon-check" |
| | | > |
| | | 通过 |
| | | </el-button> |
| | | <el-button |
| | | v-if="hasReviewBtnPermission()" |
| | | type="danger" |
| | | :loading="rejectLoading" |
| | | @click="handleReject" |
| | | icon="el-icon-close" |
| | | > |
| | | 不通过 |
| | | </el-button> |
| | | <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button> |
| | | </template> |
| | | <template v-else-if="currentDetail.status === 0"> |
| | | <el-button |
| | | v-if="hasProcessingBtnPermission()" |
| | | type="primary" |
| | | :loading="dispatchLoading" |
| | | @click="handleApproveAndDispatch" |
| | | icon="el-icon-check" |
| | | > |
| | | 受理 |
| | | </el-button> |
| | | <el-button |
| | | v-if="hasProcessingBtnPermission()" |
| | | type="danger" |
| | | :loading="rejectLoading" |
| | | @click="handleReject" |
| | | icon="el-icon-close" |
| | | > |
| | | 不受理 |
| | | </el-button> |
| | | <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button> |
| | | </template> |
| | | <template v-if="currentDetail.status === 3"> |
| | | <!-- 处理中 --> |
| | | <el-button |
| | | v-if="hasProcessedAndOverBtnPermission()" |
| | | type="primary" |
| | | :loading="completeLoading" |
| | | @click="handleComplete" |
| | | icon="el-icon-circle-check" |
| | | :disabled="!isCurrentUserAssigned" |
| | | > |
| | | 完成工单 |
| | | </el-button> |
| | | <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button> |
| | | </template> |
| | | <template v-else-if="currentDetail.status === 4"> |
| | | <!-- 已完成 --> |
| | | <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button> |
| | | </template> |
| | | </div> |
| | | <div |
| | | :class="currentIndex === totalItems - 1 ? 'disableds' : ''" |
| | | class="leftBtn" |
| | | @click="handleNext" |
| | | > |
| | | 下一页 |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { useStore } from 'vuex' |
| | | import { ref, computed, watch, nextTick } from 'vue'; |
| | | import { flowEvent, getStepInfo } from '@/api/tickets/ticket'; |
| | | import { getSmallImg, getShowImg } from '@/utils/util'; |
| | | import CreateQRcode from '@/components/CreateQRcode/CreateQRcode.vue'; |
| | | import { ElMessage} from 'element-plus'; |
| | | const store = useStore() |
| | | const userInfo = computed(() => store.state.user.userInfo) |
| | | const isLoading = ref(false); |
| | | // console.log('userInfo',userInfo.value); |
| | | // 定义props |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | currentDetail: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | currentIndex: { |
| | | type: Number, |
| | | default: 0 |
| | | }, |
| | | totalItems: { |
| | | type: Number, |
| | | default: 0 |
| | | }, |
| | | algorithms: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | types: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | permission: { |
| | | type: Object, |
| | | default: () => ({}) |
| | | }, |
| | | stepStatusList: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | workType: { |
| | | type: Number, |
| | | default: 0 |
| | | } |
| | | }); |
| | | |
| | | // 定义emits |
| | | |
| | | const emit = defineEmits([ |
| | | 'update:modelValue', |
| | | 'detail-success', |
| | | 'detail-error', |
| | | 'prev-item', |
| | | 'next-item', |
| | | 'update:currentDetail', |
| | | 'approve-and-dispatch' |
| | | ]); |
| | | |
| | | // 响应式数据 |
| | | const stepInfos = ref([]); |
| | | const totalTime = ref(''); |
| | | const approveLoading = ref(false); |
| | | const rejectLoading = ref(false); |
| | | const dispatchLoading = ref(false); |
| | | const completeLoading = ref(false); |
| | | const finalizeLoading = ref(false); |
| | | const uploadRef = ref(null); |
| | | const mapContainerRef = ref(null); |
| | | |
| | | // 计算属性 |
| | | const dialogVisible = computed({ |
| | | get() { |
| | | return props.modelValue; |
| | | }, |
| | | set(value) { |
| | | emit('update:modelValue', value); |
| | | } |
| | | }); |
| | | |
| | | const isEditMode = computed(() => { |
| | | return props.currentDetail.status === -1; |
| | | }); |
| | | // 是否有权限完成工单 |
| | | const isCurrentUserAssigned =computed(()=>{ |
| | | const handleUserId = props.currentDetail?.handle_user_id; |
| | | const currentUserId = userInfo.value?.user_id; |
| | | return !!handleUserId && !!currentUserId && String(handleUserId) === String(currentUserId); |
| | | }) |
| | | const formattedDetailFields = computed(() => { |
| | | const fields = [ |
| | | { label: '工单名称', value: props.currentDetail.orderName }, |
| | | { |
| | | label: '工单类型', |
| | | value: props.types.find(t => t.value === props.currentDetail.type)?.label || props.currentDetail.type, |
| | | }, |
| | | { label: '关联任务', value: props.currentDetail.job_name || '/' }, |
| | | { label: '工单创建人', value: props.currentDetail.creator }, |
| | | { label: '当前状态', value: mapStatus(props.currentDetail.status) }, |
| | | { label: '事件地址', value: props.currentDetail.address || props.currentDetail.latAndLon }, |
| | | { |
| | | label: '关联算法', |
| | | value: props.algorithms.find(t => t.value === props.currentDetail.aiType)?.label || props.currentDetail.aiType, |
| | | }, |
| | | { label: '发起单位', value: props.currentDetail.department }, |
| | | { label: '发起任务时间', value: props.currentDetail.startTime }, |
| | | { label: '工单内容', value: props.currentDetail.content }, |
| | | ]; |
| | | |
| | | const filteredFields = fields.filter(field => field.value !== '/'); |
| | | const formattedFields = []; |
| | | for (let i = 0; i < filteredFields.length; i += 2) { |
| | | formattedFields.push({ |
| | | label1: filteredFields[i]?.label || '', |
| | | value1: filteredFields[i]?.value || (filteredFields[i]?.label ? '暂无数据' : ''), |
| | | label2: filteredFields[i + 1]?.label || '', |
| | | value2: filteredFields[i + 1]?.value || (filteredFields[i + 1]?.label ? '暂无数据' : ''), |
| | | }); |
| | | } |
| | | |
| | | return formattedFields; |
| | | }); |
| | | |
| | | // 监听 |
| | | watch( |
| | | () => props.modelValue, |
| | | (newVal) => { |
| | | if (newVal) { |
| | | loadStepInfo(); |
| | | initMap(); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | watch( |
| | | () => props.currentDetail, |
| | | (newVal) => { |
| | | if (newVal && dialogVisible.value) { |
| | | isLoading.value = true; |
| | | setTimeout(() => { |
| | | loadStepInfo(); |
| | | initMap(); |
| | | isLoading.value = false; |
| | | }, 50); |
| | | |
| | | } |
| | | }, |
| | | { deep: true } |
| | | ); |
| | | |
| | | // 方法 |
| | | const handleClose = () => { |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | const handlePrev = () => { |
| | | if (props.currentIndex > 0) { |
| | | emit('prev-item'); |
| | | } |
| | | }; |
| | | |
| | | const handleNext = () => { |
| | | if (props.currentIndex < props.totalItems - 1) { |
| | | emit('next-item'); |
| | | } |
| | | }; |
| | | |
| | | const loadStepInfo = async () => { |
| | | try { |
| | | const stepResponse = await getStepInfo(props.currentDetail.orderNumber); |
| | | const steps = Array.isArray(stepResponse.data.data) |
| | | ? stepResponse.data.data |
| | | : stepResponse.data.data?.steps || []; |
| | | |
| | | const finishedStep = steps.find(s => String(s.status) === '4'); |
| | | totalTime.value = finishedStep && finishedStep.total_time ? finishedStep.total_time : ''; |
| | | |
| | | stepInfos.value = steps.map(step => ({ |
| | | status: String(step.status), |
| | | name: step.name, |
| | | time: step.time, |
| | | create_time: step.create_time, |
| | | })); |
| | | } catch (error) { |
| | | stepInfos.value = []; |
| | | } |
| | | }; |
| | | |
| | | const initMap = () => { |
| | | nextTick(() => { |
| | | if (mapContainerRef.value && mapContainerRef.value.initAddEntity && props.currentDetail.location) { |
| | | mapContainerRef.value.initAddEntity('point', props.currentDetail.location); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const mapStatus = (status) => { |
| | | const statusTextMap = { |
| | | 2: '待审核', |
| | | 0: '待处理', |
| | | 3: '处理中', |
| | | 4: '已完成', |
| | | }; |
| | | return statusTextMap[status] || '未知状态'; |
| | | }; |
| | | |
| | | const getStepHandler = (status) => { |
| | | const step = stepInfos.value.find(step => step.status === status); |
| | | return step ? step.name : ''; |
| | | }; |
| | | |
| | | const getStepTime = (status) => { |
| | | const step = stepInfos.value.find(step => step.status === status); |
| | | return step && step.time && step.time !== '0' ? step.time : false; |
| | | }; |
| | | |
| | | const getStepCreateTime = (status) => { |
| | | const step = stepInfos.value.find(step => step.status === status); |
| | | return step ? step.create_time : null; |
| | | }; |
| | | |
| | | const getActiveStep = () => { |
| | | const arr = props.stepStatusList; |
| | | const index = arr.indexOf(String(props.currentDetail.status)); |
| | | return index !== -1 ? index + 2 : 1; |
| | | }; |
| | | |
| | | const handleQRCode = (val) => { |
| | | emit('update:currentDetail', { |
| | | ...props.currentDetail, |
| | | showQR: !val.showQR |
| | | }); |
| | | }; |
| | | |
| | | const getThumbUrl = (url) => { |
| | | if (!url) return ''; |
| | | return getSmallImg(url); |
| | | }; |
| | | |
| | | const getPreviewUrl = (url) => { |
| | | if (!url) return ''; |
| | | return getShowImg(url); |
| | | }; |
| | | |
| | | const hasProcessingBtnPermission = () => { |
| | | return props.permission && props.permission.tickets_processing_btn === true; |
| | | }; |
| | | |
| | | const hasProcessedAndOverBtnPermission = () => { |
| | | return props.permission && props.permission.tickets_view_processedAndOver === true; |
| | | }; |
| | | // 通过不通过 |
| | | const hasReviewBtnPermission = () => { |
| | | return props.permission && props.permission.tickets_review_btn === true; |
| | | }; |
| | | |
| | | // 文件操作方法 |
| | | const handleFileChange = (file, fileList) => { |
| | | emit('update:currentDetail', { |
| | | ...props.currentDetail, |
| | | photos: fileList |
| | | }); |
| | | }; |
| | | |
| | | const handleUploadRemove = (file, fileList) => { |
| | | emit('update:currentDetail', { |
| | | ...props.currentDetail, |
| | | photos: fileList |
| | | }); |
| | | }; |
| | | |
| | | const beforeUpload = (file) => { |
| | | const isImage = file.type.includes('image'); |
| | | const isLt5M = file.size / 1024 / 1024 < 5; |
| | | |
| | | if (!isImage) { |
| | | ElMessage.error('只能上传图片文件!'); |
| | | return false; |
| | | } |
| | | if (!isLt5M) { |
| | | ElMessage.error('图片大小不能超过5MB!'); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | // 工单操作方法 |
| | | const handleApprove = async () => { |
| | | if (approveLoading.value) return; |
| | | approveLoading.value = true; |
| | | |
| | | try { |
| | | const data = { |
| | | id: props.currentDetail.id, |
| | | status: props.currentDetail.status, |
| | | isPass: 0, |
| | | eventNum: props.currentDetail.orderNumber, |
| | | }; |
| | | |
| | | const response = await flowEvent(data); |
| | | if (response.data.code === 0) { |
| | | ElMessage.success('工单已通过'); |
| | | emit('detail-success'); |
| | | handleClose(); |
| | | } else { |
| | | throw new Error(response.data.msg || '操作失败'); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error(error.message || '操作失败,请稍后重试'); |
| | | emit('detail-error', error); |
| | | } finally { |
| | | approveLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleReject = async () => { |
| | | if (rejectLoading.value) return; |
| | | rejectLoading.value = true; |
| | | |
| | | try { |
| | | const data = { |
| | | id: props.currentDetail.id, |
| | | status: props.currentDetail.status, |
| | | isPass: 1, |
| | | }; |
| | | |
| | | const response = await flowEvent(data); |
| | | if (response.data.code === 0) { |
| | | ElMessage.success('工单未通过'); |
| | | emit('detail-success'); |
| | | handleClose(); |
| | | } else { |
| | | throw new Error(response.data.msg || '操作失败'); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error(error.message || '操作失败,请稍后重试'); |
| | | emit('detail-error', error); |
| | | } finally { |
| | | rejectLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleApproveAndDispatch = () => { |
| | | // 触发派发工单操作,由父组件处理 |
| | | emit('approve-and-dispatch', props.currentDetail); |
| | | }; |
| | | |
| | | const handleComplete = async () => { |
| | | if (completeLoading.value) return; |
| | | completeLoading.value = true; |
| | | if (!props.currentDetail.processingDetail) { |
| | | ElMessage.warning('请先填写事件处理详情'); |
| | | completeLoading.value = false; |
| | | return; |
| | | } |
| | | |
| | | if (!props.currentDetail.photos || props.currentDetail.photos.length === 0) { |
| | | ElMessage.warning('请选择上传图片'); |
| | | completeLoading.value = false; |
| | | return; |
| | | } |
| | | try { |
| | | const data = { |
| | | id: props.currentDetail.id, |
| | | status: props.currentDetail.status, |
| | | processingDetails: props.currentDetail.processingDetail, |
| | | eventNum: props.currentDetail.orderNumber, |
| | | }; |
| | | const file = props.currentDetail.photos?.[0]?.raw || null; |
| | | const response = await flowEvent(data, file); |
| | | if (response.data.code === 0) { |
| | | ElMessage.success('工单已完成'); |
| | | emit('detail-success'); |
| | | handleClose(); |
| | | } else { |
| | | throw new Error(response.data.msg || '操作失败'); |
| | | } |
| | | handleClose(); |
| | | } catch (error) { |
| | | console.error('完成工单失败:', error); |
| | | ElMessage.error(error.message || '操作失败,请稍后重试'); |
| | | emit('detail-error', error); |
| | | handleClose(); |
| | | } finally { |
| | | completeLoading.value = false; |
| | | handleClose(); |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | .custom-dialog { |
| | | height: 96vh; |
| | | // min-height: 50vh; |
| | | .el-dialog__body { |
| | | border-top: 0.1rem solid #f0f0f0; |
| | | } |
| | | } |
| | | |
| | | .custom-qrcode-popover { |
| | | min-width: 160px !important; |
| | | min-height: 140px !important; |
| | | position: relative; |
| | | |
| | | .qrcode-content { |
| | | margin-top: 8px; |
| | | |
| | | .close-btn { |
| | | position: absolute; |
| | | top: 2px; |
| | | right: 2px; |
| | | width: 16px; |
| | | height: 16px; |
| | | line-height: 16px; |
| | | text-align: center; |
| | | cursor: pointer; |
| | | color: #999; |
| | | font-size: 16px; |
| | | z-index: 10; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | | <style lang="scss" scoped> |
| | | .uploadImg { |
| | | margin-bottom: 32px; |
| | | } |
| | | |
| | | .tableCss { |
| | | width: 100%; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .step-timer { |
| | | position: absolute; |
| | | right: 92%; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 100px; |
| | | margin-left: 4px; |
| | | color: #666; |
| | | font-size: 12px; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .event-total-time { |
| | | text-align: center; |
| | | color: #666; |
| | | font-size: 15px; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .action-bar { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .el-dialog { |
| | | .el-form-item { |
| | | margin-bottom: 20px; |
| | | } |
| | | } |
| | | |
| | | .el-upload { |
| | | width: 100%; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | |
| | | :deep(.el-upload-list__item) { |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | :deep(.el-upload-list__item:hover) { |
| | | background-color: #f5f7fa; |
| | | } |
| | | } |
| | | |
| | | .el-upload__tip { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .create-ticket-form { |
| | | padding: 20px 10px; |
| | | |
| | | .form-section { |
| | | background-color: #fff; |
| | | border-radius: 4px; |
| | | |
| | | .el-row { |
| | | margin-bottom: 16px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .location-wrapper { |
| | | width: 100%; // 修改为100%以适应父容器 |
| | | height: auto; // 修改为auto以自适应内容 |
| | | |
| | | .map-button { |
| | | width: 100%; // 让按钮填满容器宽度 |
| | | height: 36px; // 与其他输入框保持一致的高度 |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | i { |
| | | margin-right: 4px; |
| | | } |
| | | } |
| | | |
| | | .location-text { |
| | | margin-top: 8px; |
| | | padding: 8px 12px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | color: #606266; |
| | | font-size: 13px; |
| | | line-height: 1.4; |
| | | } |
| | | } |
| | | |
| | | .upload-wrapper { |
| | | .uploader { |
| | | :deep(.el-upload--picture-card) { |
| | | width: 120px; |
| | | height: 100px; |
| | | line-height: 128px; |
| | | } |
| | | |
| | | :deep(.el-upload-list__item) { |
| | | width: 120px; |
| | | height: 120px; |
| | | } |
| | | } |
| | | |
| | | .upload-tip { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | line-height: 1.4; |
| | | margin-top: 8px; |
| | | } |
| | | } |
| | | |
| | | .el-form-item { |
| | | margin-bottom: 18px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | |
| | | :deep(.el-form-item__label) { |
| | | font-weight: 500; |
| | | color: #606266; |
| | | } |
| | | :deep(.el-textarea__inner) { |
| | | padding: 8px 12px; |
| | | } |
| | | .full-width { |
| | | width: 100%; |
| | | } |
| | | } |
| | | |
| | | :deep(.el-dialog__body) { |
| | | border-top: 0.1rem solid #f0f0f0; |
| | | } |
| | | |
| | | .dialog-footer-new { |
| | | text-align: right; |
| | | padding-top: 16px; |
| | | border-top: 1px solid #ebeef5; |
| | | |
| | | .el-button + .el-button { |
| | | margin-left: 12px; |
| | | } |
| | | |
| | | .el-button { |
| | | padding: 9px 20px; |
| | | |
| | | &:last-child { |
| | | margin-left: 12px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .dialog-footer1-new { |
| | | position: sticky; |
| | | bottom: 28px; |
| | | left: 0; |
| | | right: 0; |
| | | background: white; |
| | | z-index: 10; |
| | | padding: 16px 20px; |
| | | border-top: 1px solid #ebeef5; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .el-button + .el-button { |
| | | margin-left: 12px; |
| | | } |
| | | |
| | | .btngroups { |
| | | margin-left: 12px; |
| | | } |
| | | |
| | | .el-button { |
| | | padding: 9px 20px; |
| | | |
| | | &:last-child { |
| | | margin-left: 12px; |
| | | margin-right: 12px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .map-container { |
| | | width: 100%; |
| | | height: 400px; |
| | | margin-bottom: 15px; |
| | | border: 1px solid #dcdfe6; |
| | | border-radius: 4px; |
| | | overflow: hidden; |
| | | |
| | | :deep(.el-input-map) { |
| | | height: 100%; |
| | | } |
| | | } |
| | | |
| | | .location-info { |
| | | margin-top: 10px; |
| | | padding: 10px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | |
| | | p { |
| | | margin: 5px 0; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | |
| | | .map-select { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | gap: 15px; |
| | | |
| | | .selected-location { |
| | | flex: 1; |
| | | padding: 5px 10px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | |
| | | p { |
| | | margin: 5px 0; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .preview-image { |
| | | border-radius: 4px; |
| | | overflow: hidden; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .image-placeholder, |
| | | .image-error, |
| | | .no-media { |
| | | height: 200px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: #909399; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | |
| | | i { |
| | | font-size: 32px; |
| | | margin-bottom: 8px; |
| | | } |
| | | } |
| | | |
| | | .no-media { |
| | | border: 1px dashed #d9d9d9; |
| | | } |
| | | |
| | | .detail-container { |
| | | padding: 0 20px; |
| | | |
| | | .detail-top-title { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | |
| | | .event-orderNumber { |
| | | margin-right: 10px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .status-flow { |
| | | margin-bottom: 20px; |
| | | |
| | | .custom-steps { |
| | | .el-step__description { |
| | | position: relative; |
| | | margin: 0; |
| | | padding: 0; |
| | | } |
| | | |
| | | // 添加发起任务步骤的特殊样式 |
| | | .init-step-info { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | text-align: center; |
| | | width: 130px; |
| | | |
| | | .creator-name { |
| | | font-size: 14px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .create-time { |
| | | font-size: 10px; |
| | | color: #909399; |
| | | } |
| | | } |
| | | |
| | | // 保持其他步骤的原有样式 |
| | | .step-info { |
| | | display: flex; |
| | | margin: 0; |
| | | padding: 0; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | width: 200px; |
| | | |
| | | .process-time { |
| | | font-size: 10px; |
| | | color: #909399; |
| | | text-align: left; |
| | | margin-left: -50%; |
| | | padding-left: 0%; |
| | | flex: 1; |
| | | } |
| | | |
| | | .handler-name { |
| | | font-size: 14px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | text-align: left; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .basic-info { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .media-section { |
| | | // margin-bottom: 20px; |
| | | |
| | | .el-row { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | } |
| | | |
| | | .leftBtn { |
| | | width: 70px; |
| | | height: 32px; |
| | | background-color: #999; |
| | | border-radius: 5px; |
| | | text-align: center; |
| | | line-height: 32px; |
| | | color: #fff; |
| | | cursor: pointer; |
| | | opacity: 0.8; |
| | | } |
| | | |
| | | .disableds { |
| | | background: #999 !important; |
| | | cursor: not-allowed !important; |
| | | pointer-events: none; |
| | | opacity: 0.3 !important; |
| | | } |
| | | |
| | | .PopUpTableScrolls { |
| | | max-height: 600px; |
| | | overflow-y: scroll; |
| | | overflow-x: hidden; |
| | | |
| | | } |
| | | |
| | | .media-box { |
| | | width: 100%; |
| | | border: 1px solid #dcdfe6; |
| | | border-radius: 4px; |
| | | padding: 10px; |
| | | background: #fff; |
| | | box-sizing: border-box; |
| | | |
| | | .media-title { |
| | | font-weight: bold; |
| | | margin-bottom: 5px; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .QRCodeImg { |
| | | width: 18px; |
| | | height: 18px; |
| | | cursor: pointer; |
| | | padding-bottom: 1px; |
| | | } |
| | | } |
| | | |
| | | .media-content { |
| | | position: relative; |
| | | height: 500px; |
| | | |
| | | :deep(.el-image) { |
| | | width: 100% !important; |
| | | max-width: 100%; |
| | | max-height: 100%; |
| | | |
| | | .el-image__inner { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: cover !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .image-placeholder, |
| | | .image-error, |
| | | .no-media { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | height: 100%; |
| | | color: #909399; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .image-placeholder i, |
| | | .image-error i { |
| | | font-size: 32px; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .no-media { |
| | | border: 1px dashed #d9d9d9; |
| | | } |
| | | |
| | | .info-table { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .info-item { |
| | | display: flex; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .info-label { |
| | | font-weight: bold; |
| | | width: 120px; |
| | | color: #606266; |
| | | } |
| | | |
| | | .info-value { |
| | | flex: 1; |
| | | color: #303133; |
| | | word-break: break-word; |
| | | } |
| | | |
| | | .readonly-processing-detail { |
| | | background-color: #f5f7fa; |
| | | padding: 12px; |
| | | border-radius: 4px; |
| | | min-height: 30px; |
| | | color: #606266; |
| | | line-height: 1.5; |
| | | display: block; |
| | | text-align: left; |
| | | margin-bottom: 5px; |
| | | |
| | | &:first-child { |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | } |
| | | |
| | | // 添加删除按钮样式 |
| | | .danger-button { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .danger-button:hover { |
| | | color: #f78989; |
| | | } |
| | | |
| | | .custom-steps-container { |
| | | width: 100%; |
| | | margin: 10px 0; |
| | | } |
| | | |
| | | .steps-titles { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 14px; |
| | | position: relative; |
| | | } |
| | | |
| | | .step-title { |
| | | text-align: center; |
| | | flex: 1; |
| | | font-size: 14px; |
| | | color: #999; |
| | | position: relative; |
| | | padding-bottom: 5px; |
| | | } |
| | | |
| | | .step-title.active { |
| | | color: #409eff; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .event-title-center { |
| | | text-align: center; |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | margin-bottom: 5px; |
| | | color: #333; |
| | | } |
| | | |
| | | .custom-steps { |
| | | margin-top: -20px; |
| | | |
| | | :deep(.el-step__description) { |
| | | margin-top: 8px; |
| | | padding: 0 20px; |
| | | } |
| | | } |
| | | |
| | | .step-description { |
| | | font-size: 14px; |
| | | color: #666; |
| | | line-height: 1.5; |
| | | display: block; |
| | | text-align: center; |
| | | |
| | | &:first-child { |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | } |
| | | |
| | | // 覆盖其他相关样式 |
| | | .status-flow { |
| | | .custom-steps { |
| | | .el-step__description { |
| | | position: relative; |
| | | margin: 0; |
| | | padding: 0; |
| | | } |
| | | |
| | | // 移除之前的样式 |
| | | .init-step-info, |
| | | .step-info { |
| | | display: block; |
| | | width: auto; |
| | | text-align: center; |
| | | } |
| | | } |
| | | } |
| | | /* 新建工单和处理工单的上传组件样式 */ |
| | | .create-upload, |
| | | .detail-upload { |
| | | :deep(.el-upload--picture-card) { |
| | | width: 120px; |
| | | height: 100px; |
| | | line-height: 100px; |
| | | } |
| | | |
| | | /* 隐藏额外的上传按钮 */ |
| | | :deep(.el-upload.el-upload--picture-card) { |
| | | display: none; |
| | | } |
| | | |
| | | /* 当没有图片时显示上传按钮 */ |
| | | :deep(.el-upload.el-upload--picture-card:first-child) { |
| | | display: flex; |
| | | } |
| | | |
| | | /* 上传组件的预览图片样式 */ |
| | | :deep(.el-upload-list--picture-card .el-upload-list__item) { |
| | | width: 120px; |
| | | height: 100px; |
| | | } |
| | | } |
| | | |
| | | /* 原有图片预览的样式 */ |
| | | .el-image-viewer__wrapper { |
| | | :deep(.el-image-viewer__img) { |
| | | max-width: 100%; |
| | | max-height: 100%; |
| | | object-fit: contain; |
| | | } |
| | | } |
| | | |
| | | /* 必填项样式 */ |
| | | .required-label { |
| | | position: relative; |
| | | display: inline-block; |
| | | |
| | | .required-star { |
| | | color: #f56c6c; |
| | | margin-right: 4px; |
| | | } |
| | | } |
| | | |
| | | /* 必填输入框样式 */ |
| | | .required-input { |
| | | width: 100%; |
| | | |
| | | :deep(.el-input__inner), |
| | | :deep(.el-textarea__inner) { |
| | | &:focus { |
| | | border-color: #409eff; |
| | | } |
| | | } |
| | | } |
| | | </style> |