6 files modified
7 files added
| | |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="事件名称" prop="eventName"> |
| | | <el-input class="gd-input" v-model="formData.eventName" placeholder="请输入" clearable /> |
| | | <el-input |
| | | :disabled="distributeStatus === 2" |
| | | class="gd-input" |
| | | v-model="formData.eventName" |
| | | placeholder="请输入" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="24"> |
| | |
| | | const userList = ref([]) |
| | | const currentRow = ref(null) // 当前线索数据 |
| | | const workOrderId = ref(null) // 工单ID |
| | | const distributeStatus = ref(null) |
| | | |
| | | // 校验规则 |
| | | const rules = { |
| | |
| | | eventName: formData.value.eventName, |
| | | latitude: currentRow.value.latitude || 0, |
| | | longitude: currentRow.value.longitude || 0, |
| | | resultId: currentRow.value.id, |
| | | resultId: currentRow.value.resultId ? currentRow.value.resultId : currentRow.value.id, |
| | | workOrderId: workOrderId.value, |
| | | } |
| | | await gdTaskResultDistributeApi(params) |
| | |
| | | |
| | | // 打开弹框 |
| | | async function open({ row, workOrderId: orderId } = {}) { |
| | | console.log('333',row) |
| | | currentRow.value = row |
| | | workOrderId.value = orderId |
| | | formData.value = initForm() |
| | | formData.value.eventName = row.eventName |
| | | distributeStatus.value = row.distributeStatus || row.eventStatus |
| | | } |
| | | |
| | | defineExpose({ open }) |
| | |
| | | <template> |
| | | <el-dialog class="gd-dialog" v-model="visible" title="查看" @closed="visible = false" width="1000px" destroy-on-close> |
| | | <el-dialog class="gd-dialog" v-model="visible" title="查看" @closed="handleClose" width="1000px" destroy-on-close> |
| | | <div v-loading="loading"> |
| | | <el-table :data="list" class="gd-dialog-table"> |
| | | <el-table-column label="线索缩略图" width="120"> |
| | |
| | | /> |
| | | </el-dialog> |
| | | |
| | | <VideoPlayDialog |
| | | ref="videoPlayDialogRef" |
| | | v-if="VideoShow" |
| | | v-model="VideoShow" |
| | | :playUrl="currentVideo.resultUrl" |
| | | /> |
| | | <VideoPlayDialog ref="videoPlayDialogRef" v-if="VideoShow" v-model="VideoShow" :playUrl="currentVideo.resultUrl" /> |
| | | </template> |
| | | |
| | | <script setup> |
| | |
| | | const permission = computed(() => store.state.user.permission) |
| | | console.log(permission.value.clueEvents_distribute, 'permission') |
| | | |
| | | const emit = defineEmits(['success']) |
| | | const visible = defineModel() |
| | | const loading = ref(false) |
| | | const list = ref([]) |
| | |
| | | if (!currentRow.value?.id) return |
| | | loading.value = true |
| | | try { |
| | | const res = await gdTaskResultListApi({ patrolTaskId: currentRow.value.id}) |
| | | const res = await gdTaskResultListApi({ patrolTaskId: currentRow.value.id }) |
| | | list.value = await Promise.all( |
| | | (res?.data?.data ?? []).map(async item => { |
| | | if (item.attachmentType !== 2) return item |
| | | const aiImg = await getAiImg(item.resultUrl,item.geojson) |
| | | const aiImg = await getAiImg(item.resultUrl, item.geojson) |
| | | return { ...item, aiImg } |
| | | }) |
| | | ) |
| | |
| | | await getList() |
| | | } |
| | | |
| | | function handleClose() { |
| | | emit('success') |
| | | visible = false |
| | | } |
| | | |
| | | defineExpose({ open }) |
| | | </script> |
| | | |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <ViewDiaLog ref="viewDiaLogRef" v-if="viewDiaLogVisible" v-model="viewDiaLogVisible" /> |
| | | <ViewDiaLog ref="viewDiaLogRef" v-if="viewDiaLogVisible" v-model="viewDiaLogVisible" @success="getList" /> |
| | | </basic-container> |
| | | </template> |
| | | <script setup> |
| | |
| | | import { getDeptTree } from '@/api/system/dept' |
| | | import ViewDiaLog from './ViewDiaLog.vue' |
| | | import { useStore } from 'vuex' |
| | | import { |
| | | getTaskTypeLabel, |
| | | normalizeTaskTypeOptions, |
| | | taskTypeCascaderProps, |
| | | } from '../taskTypeOptions' |
| | | import { getTaskTypeLabel, normalizeTaskTypeOptions, taskTypeCascaderProps } from '../taskTypeOptions' |
| | | |
| | | // 初始化查询参数 |
| | | const initSearchParams = () => ({ |
| New file |
| | |
| | | <template> |
| | | <div class="timeline-wrapper"> |
| | | <div v-for="(stage, stageIndex) in processList" :key="stage.id || stageIndex" class="stage-group"> |
| | | <!-- 大节点 Stage Node --> |
| | | <!-- <div class="stage-node">--> |
| | | <!-- <div class="stage-icon" :class="{ reached: 1 === 1 }">--> |
| | | <!-- <el-icon v-if="1 === 1"><Check /></el-icon>--> |
| | | <!-- <span v-else class="stage-index">{{ stageIndex + 1 }}</span>--> |
| | | <!-- </div>--> |
| | | <!-- <span class="stage-name" :class="{ reached: true }">{{ stage.newStatusName || stage.statusName || '-' }}</span>--> |
| | | <!-- </div>--> |
| | | |
| | | <!-- 小节点 Child Nodes --> |
| | | <div class="child-list"> |
| | | <template v-for="(record, recordIndex) in getStageRecords(stage)" :key="record.id || recordIndex"> |
| | | <div class="child-node"> |
| | | <div class="child-left"> |
| | | <span class="child-status"> |
| | | {{ |
| | | record.operateContent || |
| | | record.operationContent || |
| | | record.disposeContent || |
| | | record.content || |
| | | record.remark || |
| | | stage.operateContent || |
| | | '-' |
| | | }} |
| | | </span> |
| | | </div> |
| | | <div class="child-dot"></div> |
| | | <div class="child-right"> |
| | | <span class="child-operator"> |
| | | {{ |
| | | record.operateUserName || |
| | | record.operatorName || |
| | | record.operateUser || |
| | | record.operator || |
| | | record.userName || |
| | | stage.operateUserName || |
| | | '-' |
| | | }} |
| | | </span> |
| | | <span class="child-time"> |
| | | {{ record.operateTime || record.createTime || record.updateTime || stage.operateTime || '-' }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | <!-- 连接线 --> |
| | | <div class="connector-line" :class="{ reached: 1 === 1 }"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Check } from '@element-plus/icons-vue' |
| | | |
| | | const props = defineProps({ |
| | | processList: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | }) |
| | | |
| | | // 获取状态对应的事件记录 |
| | | function getStageRecords(stage) { |
| | | if (Array.isArray(stage.child) && stage.child.length) return stage.child |
| | | if (Array.isArray(stage.children) && stage.children.length) return stage.children |
| | | if (Array.isArray(stage.records) && stage.records.length) return stage.records |
| | | return [stage] |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .timeline-wrapper { |
| | | padding-left: 100px; |
| | | position: relative; |
| | | |
| | | .stage-group { |
| | | position: relative; |
| | | } |
| | | |
| | | // 大节点 - Stage Node |
| | | .stage-node { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | |
| | | .stage-icon { |
| | | width: 28px; |
| | | height: 28px; |
| | | border-radius: 50%; |
| | | background: #dbdde2; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: #fff; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | flex-shrink: 0; |
| | | position: relative; |
| | | z-index: 2; |
| | | |
| | | &.reached { |
| | | background: #1a1a2e; |
| | | } |
| | | |
| | | .el-icon { |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .stage-index { |
| | | font-size: 12px; |
| | | color: #4e5969; |
| | | } |
| | | } |
| | | |
| | | .stage-name { |
| | | width: 70px; |
| | | text-align: right; |
| | | position: absolute; |
| | | left: -80px; |
| | | font-family: Roboto, Roboto; |
| | | font-weight: bold; |
| | | font-size: 16px; |
| | | color: rgba(0, 0, 0, 0.45); |
| | | |
| | | &.reached { |
| | | color: #4c34ff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 小节点 - Child Nodes |
| | | .child-list { |
| | | position: relative; |
| | | left: -92px; |
| | | min-height: 80px; |
| | | overflow: visible; |
| | | |
| | | // 连接线 |
| | | .connector-line { |
| | | position: absolute; |
| | | left: 105px; |
| | | width: 1px; |
| | | height: calc(100% - 5px); |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | background: #cccccc; |
| | | z-index: 1; |
| | | |
| | | &.reached { |
| | | background: #4c34ff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .child-node { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | position: relative; |
| | | padding: 40px 0; |
| | | |
| | | &:last-child::before { |
| | | bottom: 50%; |
| | | } |
| | | |
| | | .child-left { |
| | | width: 100px; |
| | | flex-shrink: 0; |
| | | text-align: right; |
| | | padding-right: 20px; |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: rgba(0, 0, 0, 0.85); |
| | | |
| | | .child-status { |
| | | white-space: normal; |
| | | overflow-wrap: anywhere; |
| | | } |
| | | } |
| | | |
| | | .child-dot { |
| | | width: 12px; |
| | | height: 12px; |
| | | border-radius: 50%; |
| | | background: #1a1a2e; |
| | | flex-shrink: 0; |
| | | position: relative; |
| | | z-index: 2; |
| | | margin-top: 4px; |
| | | } |
| | | |
| | | .child-right { |
| | | flex: 1; |
| | | min-width: 0; |
| | | padding-left: 12px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 2px; |
| | | position: relative; |
| | | |
| | | .child-operator { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: rgba(0, 0, 0, 0.85); |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .child-time { |
| | | position: absolute; |
| | | top: 30px; |
| | | white-space: nowrap; |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: rgba(0, 0, 0, 0.45); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <el-dialog |
| | | class="gd-dialog eventDialog" |
| | | v-model="visible" |
| | | title="查看" |
| | | @closed="visible = false" |
| | | width="80%" |
| | | destroy-on-close |
| | | > |
| | | <div class="content" style="display: flex"> |
| | | <div class="processBox"> |
| | | <div class="detail-title">事件记录</div> |
| | | <div class="process"> |
| | | <!-- <OrderStepBar :processList="statusFlows" />--> |
| | | <!-- <el-steps direction="vertical" :active="1 === 1" space="100">--> |
| | | <!-- <el-step--> |
| | | <!-- v-for="(item, index) in statusFlows"--> |
| | | <!-- @key="item.id"--> |
| | | <!-- :title="item.newStatusName"--> |
| | | <!-- :description="item.operateUserName"--> |
| | | <!-- ></el-step>--> |
| | | <!-- </el-steps>--> |
| | | <el-steps direction="vertical" :active="statusFlows.length" space="100" finish-status="success"> |
| | | <el-step v-for="(item, index) in statusFlows" :key="item.id"> |
| | | <template #title> |
| | | <div class="step-title"> |
| | | <span class="status-name">{{ item.newStatusName }}</span> |
| | | <span class="operator-name">{{ item.operateUserName }}</span> |
| | | </div> |
| | | </template> |
| | | <template #description> |
| | | <div class="step-desc"> |
| | | <span>{{ item.operateTime }}</span> |
| | | <span v-if="item.remark" class="remark">{{ item.remark }}</span> |
| | | </div> |
| | | </template> |
| | | </el-step> |
| | | </el-steps> |
| | | </div> |
| | | </div> |
| | | <div class="leftBox"> |
| | | <div> |
| | | <div class="detail-title">事件详情</div> |
| | | <el-row class="detail-row-view"> |
| | | <el-col :span="12"> |
| | | <div class="label">事件名称</div> |
| | | <div class="val">{{ eventInfo.eventName }}</div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">事件编号</div> |
| | | <div class="val">{{ eventInfo.eventNum }}</div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">事件状态</div> |
| | | <div class="val">{{ getStatusLabel(eventInfo.eventStatus) }}</div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">事件时间</div> |
| | | <div class="val"> |
| | | {{ dayjs(eventInfo.shootTime).format('YYYY-MM-DD') }} |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">处置人员</div> |
| | | <div class="val">{{ eventInfo.disposeUserName }}</div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="label">处置部门</div> |
| | | <div class="val">{{ eventInfo.disposeDeptName }}</div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <div class="img_flow"> |
| | | <div class="imgurl"> |
| | | <el-image |
| | | :src="eventInfo.eventImageUrl" |
| | | :preview-src-list="[eventInfo.eventImageUrl]" |
| | | fit="cover" |
| | | preview-teleported |
| | | /> |
| | | </div> |
| | | <div class="flow"> |
| | | <div v-if="disposeRecords.length === 0"> |
| | | <el-empty description="暂无数据" /> |
| | | </div> |
| | | <div v-else class="flow-card" v-for="item in disposeRecords" :key="item.id"> |
| | | <div class="flow-row1"> |
| | | <span class="flow-name">{{ item.disposeUserName }}</span> |
| | | <span class="flow-status">{{ item.disposeStatusName }}</span> |
| | | </div> |
| | | <div class="flow-row2">{{ item.disposeContent }}</div> |
| | | <div class="flow-row3" v-if="item.attachUrl?.length"> |
| | | <el-image |
| | | v-for="(url, imageIndex) in item.attachUrl" |
| | | :key="`${item.id || 'record'}-${imageIndex}`" |
| | | class="flow-img-item" |
| | | :src="url" |
| | | :preview-src-list="item.attachUrl" |
| | | fit="cover" |
| | | preview-teleported |
| | | /> |
| | | <!-- <img--> |
| | | <!-- v-for="(url, imageIndex) in item.attachUrl"--> |
| | | <!-- :key="`${item.id || 'record'}-${imageIndex}`"--> |
| | | <!-- class="flow-img-item"--> |
| | | <!-- :src="url"--> |
| | | <!-- alt=""--> |
| | | <!-- />--> |
| | | </div> |
| | | <div class="flow-row4">{{ item.operateTime }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import dayjs from 'dayjs' |
| | | import { getDetailFull } from './eventManageApi' |
| | | import OrderStepBar from './OrderStepBar.vue' |
| | | import { ref } from 'vue' |
| | | |
| | | const visible = defineModel() |
| | | const eventInfo = ref({}) |
| | | const disposeRecords = ref([]) |
| | | const statusFlows = ref([]) |
| | | |
| | | // 获取状态文本 |
| | | const getStatusLabel = status => { |
| | | const map = { |
| | | 0: '待确认', |
| | | 1: '处置中', |
| | | 2: '已退回', |
| | | 3: '已处置', |
| | | 4: '已完成', |
| | | } |
| | | return map[status] || '未知' |
| | | } |
| | | |
| | | // 兼容事件记录接口中不同版本的字段名称 |
| | | function normalizeStatusFlow(item = {}) { |
| | | return { |
| | | ...item, |
| | | newStatusName: item.newStatusName || item.statusName || item.eventStatusName || '-', |
| | | operateContent: |
| | | item.operateContent || item.operationContent || item.disposeContent || item.content || item.remark || '-', |
| | | operateUserName: |
| | | item.operateUserName || item.operatorName || item.operateUser || item.operator || item.userName || '-', |
| | | operateTime: item.operateTime || item.createTime || item.updateTime || '-', |
| | | } |
| | | } |
| | | |
| | | // 打开弹框 |
| | | async function open({ row } = {}) { |
| | | if (!row?.id) return |
| | | const res = await getDetailFull({ id: row.id }) |
| | | const detail = res?.data?.data || {} |
| | | eventInfo.value = detail.eventInfo || {} |
| | | disposeRecords.value = detail.disposeRecords || [] |
| | | statusFlows.value = (detail.statusFlows || []).map(normalizeStatusFlow) |
| | | } |
| | | |
| | | defineExpose({ open }) |
| | | </script> |
| | | <style lang="scss"> |
| | | .eventDialog { |
| | | .el-dialog__body { |
| | | display: flex; |
| | | flex-direction: column; |
| | | .content { |
| | | height: 0; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | | <style scoped lang="scss"> |
| | | .content { |
| | | display: flex; |
| | | gap: 0 20px; |
| | | //height: 630px; |
| | | |
| | | .leftBox { |
| | | width: 0; |
| | | flex: 1; |
| | | display: flex; |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | flex-direction: column; |
| | | scrollbar-width: none; |
| | | |
| | | &::-webkit-scrollbar { |
| | | display: none; |
| | | } |
| | | |
| | | //.gd-cesium { |
| | | // position: relative; |
| | | // //overflow: hidden; |
| | | // width: 100%; |
| | | // height: 390px; |
| | | //} |
| | | .img_flow { |
| | | //border: 1px solid red; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | height: 436px; |
| | | margin-top: 20px; |
| | | .imgurl { |
| | | flex: 1; |
| | | height: 100%; |
| | | img { |
| | | height: 100%; |
| | | width: 100%; |
| | | } |
| | | } |
| | | .flow { |
| | | margin-left: 10px; |
| | | flex: 1; |
| | | min-width: 0; |
| | | min-height: 0; |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | scrollbar-width: none; |
| | | |
| | | &::-webkit-scrollbar { |
| | | display: none; |
| | | } |
| | | |
| | | .flow-card { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | margin-bottom: 8px; |
| | | padding: 12px; |
| | | border: 1px solid #e4e4e4; |
| | | border-radius: 8px; |
| | | background: #fff; |
| | | .flow-row1 { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .flow-row2 { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | } |
| | | .flow-row3 { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | .flow-img-item { |
| | | width: 60px; |
| | | height: 60px; |
| | | flex: 0 0 60px; |
| | | border: 1px solid #e4e4e4; |
| | | border-radius: 4px; |
| | | background: #f5f5f5; |
| | | object-fit: cover; |
| | | } |
| | | } |
| | | .flow-row4 { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .rightBox { |
| | | width: 350px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .title { |
| | | font-weight: 500; |
| | | font-size: 14px; |
| | | color: #272e37; |
| | | } |
| | | |
| | | ::v-deep(.el-table) { |
| | | tr th { |
| | | font-weight: normal; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .processBox { |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .process { |
| | | height: 100%; |
| | | padding: 12px 16px 24px 16px; |
| | | width: 312px; |
| | | border: 1px solid #e4e4e4; |
| | | border-radius: 8px; |
| | | background: #fff; |
| | | overflow: auto; |
| | | } |
| | | } |
| | | |
| | | .step-title { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | width: 100%; |
| | | padding-right: 10px; |
| | | } |
| | | |
| | | .status-name { |
| | | font-weight: 500; |
| | | color: #4c34ff; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .operator-name { |
| | | color: #909399; |
| | | font-size: 13px; |
| | | margin-left: 20px; |
| | | } |
| | | |
| | | .step-desc { |
| | | color: #999999; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .remark { |
| | | margin-left: 12px; |
| | | color: #e6a23c; |
| | | } |
| | | :deep(.el-step__icon.is-text) { |
| | | border-color: #4c34ff; |
| | | } |
| | | :deep(.el-step__head.is-success) { |
| | | color: #4c34ff; |
| | | height: 100px; |
| | | border-color: #4c34ff; |
| | | } |
| | | } |
| | | </style> |
| New file |
| | |
| | | import request from '@/axios' |
| | | |
| | | // 获取工单列表 |
| | | export const getGdList = params => { |
| | | return request({ |
| | | url: '/drone-gd/workOrder/gdClueEvent/list', |
| | | method: 'get', |
| | | params, |
| | | }) |
| | | } |
| | | export const getDetailFull = params => { |
| | | return request({ |
| | | url: '/drone-gd/workOrder/gdClueEvent/detailFull', |
| | | method: 'get', |
| | | params, |
| | | }) |
| | | } |
| | | // 数量统计 |
| | | export const getCount = params => { |
| | | return request({ |
| | | url: '/drone-gd/workOrder/gdClueEvent/count', |
| | | method: 'get', |
| | | params, |
| | | }) |
| | | } |
| New file |
| | |
| | | <!-- 线索事件管理 --> |
| | | <template> |
| | | <basic-container> |
| | | <el-form ref="queryParamsRef" :model="searchParams" class="gd-search-form"> |
| | | <el-form-item label="模糊搜索" prop="patrolTaskName"> |
| | | <el-input |
| | | class="gd-input gray" |
| | | v-model="searchParams.keyword" |
| | | placeholder="请输入" |
| | | clearable |
| | | @clear="handleSearch" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="事件状态" prop="taskStatus"> |
| | | <el-select |
| | | class="gd-select" |
| | | popper-class="gd-select-popper" |
| | | v-model="searchParams.eventStatus" |
| | | placeholder="请选择" |
| | | clearable |
| | | @change="handleSearch" |
| | | > |
| | | <el-option v-for="item in eventStatus" :key="item.label" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="执行时间" prop="executeTime"> |
| | | <el-date-picker |
| | | class="gd-date-picker gray" |
| | | popper-class="gd-date-picker-popper" |
| | | v-model="dateRange" |
| | | type="daterange" |
| | | range-separator="-" |
| | | start-placeholder="开始日期" |
| | | end-placeholder="结束日期" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | @change="handleSearch" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item class="gd-search-actions"> |
| | | <el-button :icon="RefreshRight" @click="resetForm"></el-button> |
| | | <el-button class="search-btn" :icon="Search" @click="handleSearch"></el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="gd-table-container" v-loading="loading"> |
| | | <div class="gd-table-content gd-table-content-bg"> |
| | | <el-table class="gd-table" :data="list"> |
| | | <el-table-column type="index" width="64" label="序号" /> |
| | | <el-table-column prop="eventName" show-overflow-tooltip label="事件名称" /> |
| | | <el-table-column prop="eventNum" show-overflow-tooltip label="事件编号" /> |
| | | <el-table-column prop="eventStatus" show-overflow-tooltip label="事件状态"> |
| | | <template v-slot="{ row }"> |
| | | {{ eventStatusMap[row.eventStatus] }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="shootTime" show-overflow-tooltip label="事件时间" /> |
| | | <el-table-column prop="disposeUserName" show-overflow-tooltip label="处置人员" /> |
| | | <el-table-column prop="disposeDeptName" show-overflow-tooltip label="处置部门" /> |
| | | <el-table-column label="操作" class-name="operation-btns" fixed="right"> |
| | | <template v-slot="{ row }"> |
| | | <el-link type="primary" @click="viewDiaLogView(row)">查看</el-link> |
| | | <el-link v-if="row.eventStatus === 2" type="primary" @click="viewDistribute(row)">分发</el-link> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div class="gd-pagination-parent"> |
| | | <el-pagination |
| | | popper-class="gd-select-popper" |
| | | v-model:current-page="searchParams.current" |
| | | v-model:page-size="searchParams.size" |
| | | layout="total, prev, pager, next, sizes" |
| | | :total="total" |
| | | @change="getList" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <ViewDiaLog ref="viewDiaLogRef" v-if="viewDiaLogVisible" v-model="viewDiaLogVisible" /> |
| | | |
| | | <DistributeDiaLog |
| | | ref="distributeDialogRef" |
| | | v-if="distributeDialogVisible" |
| | | v-model="distributeDialogVisible" |
| | | @success="getList" |
| | | /> |
| | | </basic-container> |
| | | </template> |
| | | <script setup> |
| | | import { Search, RefreshRight } from '@element-plus/icons-vue' |
| | | import { computed, onMounted, ref } from 'vue' |
| | | import { getDictionaryByCode } from '@/api/system/dictbiz' |
| | | import { dateRangeFormat, getDictLabel } from '@ztzf/utils' |
| | | // import { getDictListApi } from '@/api/zkxt' |
| | | import { getGdList } from './eventManageApi' |
| | | import { getDeptTree } from '@/api/system/dept' |
| | | import ViewDiaLog from './ViewDiaLog.vue' |
| | | import { useStore } from 'vuex' |
| | | import { getTaskTypeLabel, normalizeTaskTypeOptions, taskTypeCascaderProps } from '../taskTypeOptions' |
| | | import DistributeDiaLog from '@/views/orderView/orderManage/clueEvents/DistributeDiaLog.vue' |
| | | |
| | | // 初始化查询参数 |
| | | const initSearchParams = () => ({ |
| | | eventStatus: '', |
| | | keyword: '', |
| | | current: 1, // 当前页 |
| | | size: 10, // 每页大小 |
| | | startTime: '', |
| | | endTime: '', |
| | | onlyMine: 0, |
| | | }) |
| | | const distributeDialogVisible = ref(false) |
| | | const searchParams = ref(initSearchParams()) // 查询参数 |
| | | const dateRange = ref([]) // 执行时间范围 |
| | | const total = ref(0) // 总条数 |
| | | const loading = ref(true) // 列表加载中 |
| | | const list = ref([]) // 列表数据 |
| | | const queryParamsRef = ref(null) // 查询表单实例 |
| | | const viewDiaLogRef = ref(null) |
| | | const distributeDialogRef = ref(null) |
| | | const viewDiaLogVisible = ref(false) |
| | | const deptTree = ref([]) // 部门树 |
| | | const treeProps = { |
| | | label: 'name', |
| | | children: 'children', |
| | | } |
| | | const dictObj = ref({ |
| | | workOrderType: [], // 巡查任务类型 |
| | | taskStatus: [], // 巡查任务状态 |
| | | }) |
| | | |
| | | provide('dictObj', dictObj) |
| | | provide('deptTree', deptTree) |
| | | provide('treeProps', treeProps) |
| | | |
| | | // 事件状态 |
| | | const eventStatus = ref([ |
| | | { label: '待确认', value: 0 }, |
| | | { label: '处置中', value: 1 }, |
| | | { label: '已退回', value: 2 }, |
| | | { label: '已处置', value: 3 }, |
| | | { label: '已完成', value: 4 }, |
| | | ]) |
| | | |
| | | // 事件状态映射 |
| | | const eventStatusMap = computed(() => { |
| | | const map = {} |
| | | eventStatus.value.forEach(item => { |
| | | map[item.value] = item.label |
| | | }) |
| | | return map |
| | | }) |
| | | |
| | | // 获取列表 |
| | | async function getList() { |
| | | const range = dateRangeFormat(dateRange.value) |
| | | loading.value = true |
| | | try { |
| | | const res = await getGdList({ |
| | | ...searchParams.value, |
| | | startTime: range[0], |
| | | endTime: range[1], |
| | | }) |
| | | list.value = res?.data?.data?.records ?? [] |
| | | total.value = res?.data?.data?.total ?? 0 |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // 查询 |
| | | function handleSearch() { |
| | | searchParams.value.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | // 重置查询 |
| | | function resetForm() { |
| | | queryParamsRef.value?.resetFields() |
| | | dateRange.value = [] |
| | | searchParams.value.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | // 查看弹框 |
| | | function viewDiaLogView(row) { |
| | | viewDiaLogVisible.value = true |
| | | nextTick(() => { |
| | | viewDiaLogRef.value?.open({ row }) |
| | | }) |
| | | } |
| | | |
| | | // 分发 |
| | | function viewDistribute(row) { |
| | | distributeDialogVisible.value = true |
| | | nextTick(() => { |
| | | distributeDialogRef.value?.open({ row }) |
| | | }) |
| | | } |
| | | |
| | | // 获取字典 |
| | | function getDictList() { |
| | | getDictionaryByCode('workOrderType,taskStatus').then(res => { |
| | | dictObj.value = res.data.data |
| | | }) |
| | | // getDictListApi('task_inspection_type').then(res => { |
| | | // workOrderTypeXT.value = normalizeTaskTypeOptions(res.data.data || []) |
| | | // }) |
| | | } |
| | | |
| | | const store = useStore() |
| | | const userInfo = computed(() => store.state.user.userInfo) |
| | | |
| | | // 获取部门树 |
| | | function getDeptTreeFun() { |
| | | getDeptTree(userInfo.value.dept_id).then(res => { |
| | | deptTree.value = res.data.data |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | getDictList() |
| | | getDeptTreeFun() |
| | | }) |
| | | </script> |
| | | <style scoped lang="scss"></style> |
| | |
| | | import { request } from "@/utils/requestGd/index.js" |
| | | import website from '@/config/website' |
| | | import { getEnvObj } from '@/utils/index.js' |
| | | import { useUserStore } from '@/store/index.js' |
| | | import { Base64 } from 'js-base64' |
| | | |
| | | |
| | | // 工单用户登录接口 |
| | |
| | | // 分享缓存数据 |
| | | export const shareCacheApi = (data) => { |
| | | return request({ |
| | | url: '/drone-gd/workOrder/gdPatrolTask/share-cache-data', |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | url: '/drone-gd/workOrder/gdPatrolTask/share-cache-data', |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | // 新增处置记录 |
| | | export const addDisposeRecord = data => { |
| | | return request({ |
| | | url: '/drone-gd/workOrder/gdClueEvent/addDisposeRecord', |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | // 更新状态 |
| | | export const updateStatus = data => { |
| | | return request({ |
| | | url: '/drone-gd/workOrder/gdClueEvent/updateStatus', |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | |
| | | |
| | | // 获取处置记录列表 |
| | | export const getDisposeRecords = params => { |
| | | return request({ |
| | | url: '/drone-gd/workOrder/gdClueEvent/disposeRecords', |
| | | method: 'get', |
| | | params, |
| | | }) |
| | | } |
| | | |
| | | // 上传文件 |
| | | export const uploadFileAPI = filePath => { |
| | | let baseURL = getEnvObj().VITE_API_BASE_URL_GD |
| | | // #ifdef H5 |
| | | if (import.meta.env.VITE_APP_PROXY === 'true') { |
| | | baseURL = import.meta.env.VITE_API_PREFIX_GD |
| | | } |
| | | // #endif |
| | | const userInfo = useUserStore()?.$state?.userInfo || {} |
| | | const accessToken = userInfo.gd_access_token |
| | | const areaCode = userInfo.detail?.areaCode |
| | | const header = { |
| | | 'Blade-Requested-With': 'BladeHttpRequest', |
| | | Authorization: `Basic ${Base64.encode(`${website.clientId}:${website.clientSecret}`)}`, |
| | | } |
| | | if (accessToken) header['Blade-Auth'] = `bearer ${accessToken}` |
| | | if (areaCode) header.areaCode = areaCode |
| | | |
| | | return new Promise((resolve, reject) => { |
| | | uni.uploadFile({ |
| | | url: `${baseURL.replace(/\/$/, '')}/blade-resource/oss/endpoint/put-file`, |
| | | filePath, |
| | | name: 'file', |
| | | header, |
| | | success: response => { |
| | | try { |
| | | const responseData = typeof response.data === 'string' ? JSON.parse(response.data) : response.data |
| | | const status = Number(responseData?.error_code ?? responseData?.code ?? response.statusCode) |
| | | if (![0, 200].includes(status)) { |
| | | reject(responseData) |
| | | return |
| | | } |
| | | resolve(responseData) |
| | | } catch (error) { |
| | | reject(error) |
| | | } |
| | | }, |
| | | fail: reject, |
| | | }) |
| | | }) |
| | | } |
| | |
| | | <template> |
| | | <div class="eventTickets" :style="{ paddingTop: topMargin + 'px' }"> |
| | | |
| | | <div class="searchTop"> |
| | | <up-search placeholder="请输入关键字搜索" :animation="true" v-model="listParams.keyword" :show-action="false" @search="handleSearch" @clear="handleClear"></up-search> |
| | | </div> |
| | | <div class="listBox"> |
| | | <div class="tabs-container"> |
| | | <up-tabs :list="tabList" @click="handleClick"></up-tabs> |
| | | </div> |
| | | <!-- @scrolltolower="loadMore" --> |
| | | <scroll-view class="eventBox" scroll-y :lower-threshold="80" > |
| | | <div class="eventList"> |
| | | <div class="eventItem" v-for="(item,index) in dataList" :key="index"> |
| | | <image |
| | | <div class="eventTickets" :style="{ paddingTop: topMargin + 'px' }"> |
| | | <div class="searchTop"> |
| | | <up-search |
| | | placeholder="请输入关键字搜索" |
| | | :animation="true" |
| | | v-model="listParams.keyword" |
| | | :show-action="false" |
| | | @search="handleSearch" |
| | | @clear="handleClear" |
| | | ></up-search> |
| | | </div> |
| | | <div class="listBox"> |
| | | <div class="tabs-container"> |
| | | <up-tabs :scrollable="true" :list="tabList" @click="handleClick"></up-tabs> |
| | | </div> |
| | | <scroll-view class="eventBox" scroll-y :lower-threshold="80" @scrolltolower="loadMore"> |
| | | <div class="eventList"> |
| | | <div class="eventItem" v-for="(item, index) in dataList" :key="index"> |
| | | <image |
| | | v-if="[1, 2].includes(item.attachmentType)" |
| | | :src="item?.aiImg || item?.eventImageUrl" |
| | | mode="aspectFill" |
| | |
| | | <div v-if="item.attachmentType === 3" class="videoBox" @click="detailHandle(item)"> |
| | | <div class="playIcon"></div> |
| | | </div> |
| | | <div class="informationDisplay"> |
| | | <div class="informationDisplay"> |
| | | <div class="itemTitle">{{ item.eventName }}</div> |
| | | <div class="itemContent">{{ formatDate(item.createTime) }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="itemContent">{{ formatDate(item.createTime) }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 暂无数据提示 --> |
| | | <div class="noData" v-if="!loading && dataList.length === 0"> |
| | | <text>暂无数据</text> |
| | | </div> |
| | | <!-- 暂无数据提示 --> |
| | | <div class="noData" v-if="!loading && dataList.length === 0"> |
| | | <text>暂无数据</text> |
| | | </div> |
| | | |
| | | <!-- 加载提示 --> |
| | | <div class="loadingMore"> |
| | | <text v-if="loading">加载中...</text> |
| | | |
| | | </div> |
| | | </scroll-view> |
| | | </div> |
| | | <!-- 加载提示 --> |
| | | <div class="loadingMore"> |
| | | <text v-if="loading">加载中...</text> |
| | | <text v-else-if="!hasMore">没有更多了</text> |
| | | </div> |
| | | </scroll-view> |
| | | </div> |
| | | <canvas |
| | | canvas-id="ai-image-canvas" |
| | | id="ai-image-canvas" |
| | | class="ai-image-canvas" |
| | | :style="{ width: canvasSize.width + 'px', height: canvasSize.height + 'px' }" |
| | | ></canvas> |
| | | |
| | | </div> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | |
| | | import { getCurrentInstance, nextTick } from 'vue' |
| | | import { useUserStore } from '@/store/index.js' |
| | | import {getGdList,getstatusCount} from '@/api/work/index.js' |
| | | import dayjs from 'dayjs'; |
| | | import { getStatusBarHeight } from '@/utils/common'; |
| | | import { getGdList, getstatusCount } from '@/api/work/index.js' |
| | | import dayjs from 'dayjs' |
| | | import { getStatusBarHeight } from '@/utils/common' |
| | | |
| | | const canvasOwner = getCurrentInstance()?.proxy |
| | | const userStore = useUserStore() |
| | | const userInfo = userStore.userInfo |
| | | const dataList = ref([]) |
| | | const currentTab=ref('all') |
| | | const currentTab = ref('all') |
| | | const loading = ref(false) |
| | | const hasMore = ref(true) |
| | | const tabList = ref([ |
| | | { |
| | | name: '全部工单', |
| | | key: 'all', |
| | | badge: { |
| | | value: 0, |
| | | showZero: true |
| | | }, |
| | | |
| | | }, |
| | | { |
| | | name: '我的工单', |
| | | key: 'myTickets', |
| | | badge: { |
| | | value: 0, |
| | | showZero: true |
| | | } |
| | | } |
| | | |
| | | |
| | | { |
| | | name: '全部工单', |
| | | key: 'all', |
| | | badge: { |
| | | value: 0, |
| | | showZero: true, |
| | | }, |
| | | }, |
| | | { |
| | | name: '我的工单', |
| | | key: 'myTickets', |
| | | badge: { |
| | | value: 0, |
| | | showZero: true, |
| | | }, |
| | | }, |
| | | { |
| | | name: '处置中', |
| | | key: 'disposing', |
| | | badge: { |
| | | value: 0, |
| | | showZero: true, |
| | | }, |
| | | }, |
| | | { |
| | | name: '已处置', |
| | | key: 'handled', |
| | | badge: { |
| | | value: 0, |
| | | showZero: true, |
| | | }, |
| | | }, |
| | | { |
| | | name: '已完成', |
| | | key: 'completed', |
| | | badge: { |
| | | value: 0, |
| | | showZero: true, |
| | | }, |
| | | }, |
| | | ]) |
| | | const formatDate = (dateString) => { |
| | | return dayjs(dateString).format('MM/DD HH:mm'); |
| | | }; |
| | | // tab key 与事件状态码映射 |
| | | const tabStatusMap = { |
| | | disposing: 1, |
| | | handled: 3, |
| | | completed: 4, |
| | | } |
| | | const formatDate = dateString => { |
| | | return dayjs(dateString).format('MM/DD HH:mm') |
| | | } |
| | | const listParams = ref({ |
| | | |
| | | current: 1, |
| | | size: 12, |
| | | source: 1, |
| | | department:'', |
| | | keyword:'' |
| | | |
| | | current: 1, |
| | | size: 12, |
| | | source: 1, |
| | | department: '', |
| | | keyword: '', |
| | | eventStatus: '', |
| | | }) |
| | | const mockGeojson = "[{\"score\":0.89990234375,\"bbox\":{\"x_cen\":195.5,\"y_cen\":326.5,\"width\":117.0,\"height\":265.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.89306640625,\"bbox\":{\"x_cen\":1194.5,\"y_cen\":559.5,\"width\":115.0,\"height\":261.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.88720703125,\"bbox\":{\"x_cen\":179.0,\"y_cen\":955.5,\"width\":124.0,\"height\":249.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.88330078125,\"bbox\":{\"x_cen\":1198.5,\"y_cen\":260.5,\"width\":115.0,\"height\":285.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.84716796875,\"bbox\":{\"x_cen\":204.5,\"y_cen\":71.5,\"width\":115.0,\"height\":143.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.83203125,\"bbox\":{\"x_cen\":186.0,\"y_cen\":657.5,\"width\":114.0,\"height\":269.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"},{\"score\":0.78662109375,\"bbox\":{\"x_cen\":1205.5,\"y_cen\":49.5,\"width\":117.0,\"height\":99.0},\"class_name\":\"car\",\"algorithmId\":\"e71116098eeb1d60cfebd04d30653b151\"}]" |
| | | const mockGeojson = |
| | | '[{"score":0.89990234375,"bbox":{"x_cen":195.5,"y_cen":326.5,"width":117.0,"height":265.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"},{"score":0.89306640625,"bbox":{"x_cen":1194.5,"y_cen":559.5,"width":115.0,"height":261.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"},{"score":0.88720703125,"bbox":{"x_cen":179.0,"y_cen":955.5,"width":124.0,"height":249.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"},{"score":0.88330078125,"bbox":{"x_cen":1198.5,"y_cen":260.5,"width":115.0,"height":285.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"},{"score":0.84716796875,"bbox":{"x_cen":204.5,"y_cen":71.5,"width":115.0,"height":143.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"},{"score":0.83203125,"bbox":{"x_cen":186.0,"y_cen":657.5,"width":114.0,"height":269.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"},{"score":0.78662109375,"bbox":{"x_cen":1205.5,"y_cen":49.5,"width":117.0,"height":99.0},"class_name":"car","algorithmId":"e71116098eeb1d60cfebd04d30653b151"}]' |
| | | const canvasSize = ref({ |
| | | width: 1, |
| | | height: 1 |
| | | height: 1, |
| | | }) |
| | | |
| | | const parseAiFrame = aiFrameSource => { |
| | |
| | | fail: error => { |
| | | console.log(error) |
| | | resolve(null) |
| | | } |
| | | }, |
| | | }) |
| | | }) |
| | | |
| | | const canvasToTempFilePath = (width, height) => |
| | | new Promise(resolve => { |
| | | uni.canvasToTempFilePath({ |
| | | canvasId: 'ai-image-canvas', |
| | | width, |
| | | height, |
| | | destWidth: width, |
| | | destHeight: height, |
| | | fileType: 'jpg', |
| | | quality: 0.92, |
| | | success: res => resolve(res.tempFilePath), |
| | | fail: error => { |
| | | console.log(error) |
| | | resolve('') |
| | | } |
| | | }, canvasOwner) |
| | | uni.canvasToTempFilePath( |
| | | { |
| | | canvasId: 'ai-image-canvas', |
| | | width, |
| | | height, |
| | | destWidth: width, |
| | | destHeight: height, |
| | | fileType: 'jpg', |
| | | quality: 0.92, |
| | | success: res => resolve(res.tempFilePath), |
| | | fail: error => { |
| | | console.log(error) |
| | | resolve('') |
| | | }, |
| | | }, |
| | | canvasOwner |
| | | ) |
| | | }) |
| | | |
| | | const drawAiImage = async (url, aiFrameSource) => { |
| | |
| | | } |
| | | |
| | | const getDataList = () => { |
| | | if (loading.value || !hasMore.value) return |
| | | if (loading.value || !hasMore.value) return |
| | | |
| | | loading.value = true |
| | | const params = { |
| | | current: listParams.value.current, |
| | | size: listParams.value.size, |
| | | keyword:listParams.value.keyword, |
| | | onlyMine:currentTab.value=== 'myTickets' ? 1 : 0 |
| | | } |
| | | getGdList(params).then(async res => { |
| | | const response = res.data.data |
| | | const list = [] |
| | | for (const item of response) { |
| | | // const aiImg = await drawAiImage(item.eventImageUrl, mockGeojson) |
| | | if (item.attachmentType !== 2) { |
| | | list.push(item) |
| | | }else{ |
| | | const aiImg = await drawAiImage(item.eventImageUrl, item.geojson) |
| | | list.push({ ...item, aiImg }) |
| | | loading.value = true |
| | | const params = { |
| | | current: listParams.value.current, |
| | | size: listParams.value.size, |
| | | keyword: listParams.value.keyword, |
| | | onlyMine: currentTab.value === 'myTickets' ? 1 : 0, |
| | | eventStatus: listParams.value.eventStatus, |
| | | } |
| | | getGdList(params) |
| | | .then(async res => { |
| | | const resData = res.data.data |
| | | const response = resData.records |
| | | const list = [] |
| | | for (const item of response) { |
| | | // const aiImg = await drawAiImage(item.eventImageUrl, mockGeojson) |
| | | if (item.attachmentType !== 2) { |
| | | list.push(item) |
| | | } else { |
| | | const aiImg = await drawAiImage(item.eventImageUrl, item.geojson) |
| | | list.push({ ...item, aiImg }) |
| | | } |
| | | } |
| | | } |
| | | dataList.value = list |
| | | // 根据当前页码决定是替换还是追加数据 |
| | | // if (listParams.value.current === 1) { |
| | | // dataList.value = response |
| | | // } else { |
| | | // dataList.value = [...dataList.value, ...response] |
| | | // } |
| | | |
| | | // // 判断是否还有更多数据 |
| | | // if (response.length < listParams.value.size || response.current >= response.pages) { |
| | | // hasMore.value = false |
| | | // } else { |
| | | // hasMore.value = true |
| | | // } |
| | | }).finally(() => { |
| | | loading.value = false |
| | | }) |
| | | } |
| | | const getstatusCountData=()=>{ |
| | | const params={ |
| | | keyword:listParams.value.keyword, |
| | | } |
| | | getstatusCount(params).then(res=>{ |
| | | const response = res.data.data |
| | | const { totalCount,myCount } = response |
| | | tabList.value.forEach(tab=>{ |
| | | |
| | | if(tab.key === 'all'){ |
| | | tab.badge.value = (totalCount || 0).toString() |
| | | tab.badge.showZero = true |
| | | |
| | | }else if(tab.key === 'myTickets'){ |
| | | tab.badge.value = (myCount || 0).toString() |
| | | tab.badge.showZero = true |
| | | // 根据当前页码决定是替换还是追加数据 |
| | | if (listParams.value.current === 1) { |
| | | dataList.value = list |
| | | } else { |
| | | dataList.value = [...dataList.value, ...list] |
| | | } |
| | | // 判断是否还有更多数据 |
| | | if (list.length < listParams.value.size || resData.current >= resData.pages) { |
| | | hasMore.value = false |
| | | } else { |
| | | hasMore.value = true |
| | | } |
| | | }) |
| | | |
| | | .finally(() => { |
| | | loading.value = false |
| | | }) |
| | | } |
| | | const getstatusCountData = () => { |
| | | const params = { |
| | | keyword: listParams.value.keyword, |
| | | } |
| | | getstatusCount(params).then(res => { |
| | | const response = res.data.data |
| | | const { totalCount, myCount, disposingCount, disposedCount, completedCount } = response |
| | | tabList.value.forEach(tab => { |
| | | if (tab.key === 'all') { |
| | | tab.badge.value = (totalCount || 0).toString() |
| | | tab.badge.showZero = true |
| | | } else if (tab.key === 'myTickets') { |
| | | tab.badge.value = (myCount || 0).toString() |
| | | tab.badge.showZero = true |
| | | } else if (tab.key === 'disposing') { |
| | | tab.badge.value = (disposingCount || 0).toString() |
| | | tab.badge.showZero = true |
| | | } else if (tab.key === 'handled') { |
| | | tab.badge.value = (disposedCount || 0).toString() |
| | | tab.badge.showZero = true |
| | | } else if (tab.key === 'completed') { |
| | | tab.badge.value = (completedCount || 0).toString() |
| | | tab.badge.showZero = true |
| | | } |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | const handleClick = (item) => { |
| | | currentTab.value = item.key |
| | | const handleClick = item => { |
| | | currentTab.value = item.key |
| | | listParams.value.eventStatus = tabStatusMap[item.key] ?? '' |
| | | |
| | | listParams.value.current = 1 |
| | | hasMore.value = true |
| | | dataList.value = [] |
| | | getDataList() |
| | | listParams.value.current = 1 |
| | | hasMore.value = true |
| | | dataList.value = [] |
| | | getDataList() |
| | | } |
| | | const loadMore = () => { |
| | | if (loading.value || !hasMore.value) return |
| | | listParams.value.current++ |
| | | getDataList() |
| | | if (loading.value || !hasMore.value) return |
| | | listParams.value.current++ |
| | | getDataList() |
| | | } |
| | | |
| | | const detailHandle = (val) => { |
| | | uni.navigateTo({ |
| | | url: `/subPackages/workDetail/index?id=${val.id}` + (val.aiImg ? `&aiImg=${val.aiImg}` : ''), |
| | | }) |
| | | const detailHandle = val => { |
| | | uni.navigateTo({ |
| | | url: `/subPackages/workDetail/index?id=${val.id}` + (val.aiImg ? `&aiImg=${val.aiImg}` : ''), |
| | | }) |
| | | } |
| | | |
| | | // 搜索功能 |
| | | const handleSearch = () => { |
| | | listParams.value.current = 1 |
| | | hasMore.value = true |
| | | dataList.value = [] |
| | | getDataList() |
| | | getstatusCountData() |
| | | listParams.value.current = 1 |
| | | hasMore.value = true |
| | | dataList.value = [] |
| | | getDataList() |
| | | getstatusCountData() |
| | | } |
| | | // 清除搜索 |
| | | const handleClear = () => { |
| | | listParams.value.keyword = '' |
| | | listParams.value.current = 1 |
| | | hasMore.value = true |
| | | dataList.value = [] |
| | | getDataList() |
| | | getstatusCountData() |
| | | listParams.value.keyword = '' |
| | | listParams.value.current = 1 |
| | | hasMore.value = true |
| | | dataList.value = [] |
| | | getDataList() |
| | | getstatusCountData() |
| | | } |
| | | const topMargin = getStatusBarHeight() |
| | | onShow(() => { |
| | | listParams.value.current = 1 |
| | | hasMore.value = true |
| | | dataList.value = [] |
| | | getDataList() |
| | | getstatusCountData() |
| | | listParams.value.current = 1 |
| | | hasMore.value = true |
| | | dataList.value = [] |
| | | getDataList() |
| | | getstatusCountData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .eventTickets { |
| | | padding: 0 20rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100%; |
| | | box-sizing: border-box; |
| | | width: 100%; |
| | | overflow-x: hidden; |
| | | .searchTop { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding-right: 24rpx; |
| | | width: 100%; |
| | | height: 108rpx; |
| | | :deep(){ |
| | | .u-search__content{ |
| | | background: #fff !important; |
| | | } |
| | | .u-search__content__input{ |
| | | background: #fff !important; |
| | | } |
| | | } |
| | | } |
| | | :deep(){ |
| | | padding: 0 20rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100%; |
| | | box-sizing: border-box; |
| | | width: 100%; |
| | | overflow-x: hidden; |
| | | .searchTop { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding-right: 24rpx; |
| | | width: 100%; |
| | | height: 108rpx; |
| | | :deep() { |
| | | .u-search__content { |
| | | background: #fff !important; |
| | | } |
| | | .u-search__content__input { |
| | | background: #fff !important; |
| | | } |
| | | } |
| | | } |
| | | :deep() { |
| | | .u-tabs__wrapper__nav__line { |
| | | // width: 80rpx !important; |
| | | background: #1d6fe9 !important; |
| | | } |
| | | .u-badge { |
| | | background-color: #1d6fe9 !important; |
| | | margin-top: -14px !important; |
| | | margin-left: -5px !important; |
| | | } |
| | | } |
| | | tabs-container { |
| | | display: flex; |
| | | align-items: center; |
| | | height: 98rpx; |
| | | width: 100%; |
| | | overflow-x: auto; // 横向滚动 |
| | | overflow-y: hidden; |
| | | white-space: nowrap; // 防止换行 |
| | | -webkit-overflow-scrolling: touch; // 移动端平滑滚动 |
| | | |
| | | .u-tabs__wrapper__nav__line{ |
| | | // width: 80rpx !important; |
| | | background: #1D6FE9 !important; |
| | | } |
| | | .u-badge { |
| | | background-color: #1d6fe9 !important; |
| | | margin-top: -14px !important; |
| | | margin-left: -5px !important; |
| | | } |
| | | } |
| | | // 隐藏滚动条(可选) |
| | | &::-webkit-scrollbar { |
| | | height: 0; |
| | | display: none; |
| | | } |
| | | } |
| | | |
| | | // 子元素不压缩 |
| | | .tabs-container > * { |
| | | flex-shrink: 0; |
| | | } |
| | | .listBox { |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | width: 100%; |
| | | box-sizing: border-box; |
| | | } |
| | | .eventBox { |
| | | padding: 20rpx 0; |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | height: 0; |
| | | flex-grow: 1; |
| | | align-content: flex-start; |
| | | width: 100%; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .tabs-container{ |
| | | display: flex; |
| | | justify-content: center; |
| | | height: 98rpx; |
| | | } |
| | | .listBox { |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | flex: 1; |
| | | width: 100%; |
| | | box-sizing: border-box; |
| | | } |
| | | .eventBox { |
| | | padding: 20rpx 0; |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | height: 0; |
| | | flex-grow: 1; |
| | | align-content: flex-start; |
| | | width: 100%; |
| | | box-sizing: border-box; |
| | | } |
| | | .eventList { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 20rpx; |
| | | width: 100%; |
| | | box-sizing: border-box; |
| | | justify-content: space-between; |
| | | |
| | | .eventList { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 20rpx; |
| | | width: 100%; |
| | | box-sizing: border-box; |
| | | justify-content: space-between; |
| | | .eventItem { |
| | | width: calc(50% - 10rpx); |
| | | background-color: #fff; |
| | | border-radius: 12rpx; |
| | | overflow: hidden; |
| | | position: relative; |
| | | box-sizing: border-box; |
| | | max-width: 100%; |
| | | |
| | | .eventItem { |
| | | width: calc(50% - 10rpx); |
| | | background-color: #fff; |
| | | border-radius: 12rpx; |
| | | overflow: hidden; |
| | | position: relative; |
| | | box-sizing: border-box; |
| | | max-width: 100%; |
| | | |
| | | image, |
| | | image, |
| | | .videoBox { |
| | | width: 100%; |
| | | height: 208rpx; |
| | | border-radius: 12rpx; |
| | | overflow: hidden; |
| | | width: 100%; |
| | | height: 208rpx; |
| | | border-radius: 12rpx; |
| | | overflow: hidden; |
| | | display: block; |
| | | } |
| | | } |
| | | .videoBox { |
| | | background: linear-gradient(135deg, #eef4ff 0%, #dfe8f8 100%); |
| | | display: flex; |
| | |
| | | border-left: 22rpx solid #fff; |
| | | } |
| | | } |
| | | .informationDisplay{ |
| | | width: 100%; |
| | | position: absolute; |
| | | bottom: 3px; |
| | | background: rgba(11,11,11,0.35); |
| | | border-radius: 0rpx 0rpx 12rpx 12rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 10rpx 14rpx; |
| | | .informationDisplay { |
| | | width: 100%; |
| | | position: absolute; |
| | | bottom: 3px; |
| | | background: rgba(11, 11, 11, 0.35); |
| | | border-radius: 0rpx 0rpx 12rpx 12rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 10rpx 14rpx; |
| | | box-sizing: border-box; |
| | | .itemTitle { |
| | | flex: 1; |
| | | .itemTitle { |
| | | flex: 1; |
| | | min-width: 0; |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 500; |
| | | font-size: 24rpx; |
| | | color: #FFFFFF; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .itemContent { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 24rpx; |
| | | color: #FFFFFF; |
| | | margin-left: 12rpx; |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 500; |
| | | font-size: 24rpx; |
| | | color: #ffffff; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .itemContent { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 24rpx; |
| | | color: #ffffff; |
| | | margin-left: 12rpx; |
| | | white-space: nowrap; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .loadingMore { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 80rpx; |
| | | font-size: 28rpx; |
| | | color: #999; |
| | | width: 100%; |
| | | } |
| | | .loadingMore { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 80rpx; |
| | | font-size: 28rpx; |
| | | color: #999; |
| | | width: 100%; |
| | | } |
| | | |
| | | .noData { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 300rpx; |
| | | font-size: 28rpx; |
| | | color: #999; |
| | | width: 100%; |
| | | } |
| | | .noData { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 300rpx; |
| | | font-size: 28rpx; |
| | | color: #999; |
| | | width: 100%; |
| | | } |
| | | |
| | | .ai-image-canvas { |
| | | position: fixed; |
| New file |
| | |
| | | <template> |
| | | <view class="handle-work-page"> |
| | | <u-navbar title="处置记录" :is-back="true" back-text="" :back-icon-size="40" @left-click="onBackClick"></u-navbar> |
| | | |
| | | <!-- 把上传处置放在滚动区域外面,固定在头部 --> |
| | | <view class="upload_handle" @click="uploadHadle">+上传处置</view> |
| | | |
| | | <scroll-view class="record-scroll" scroll-y :lower-threshold="80" @scrolltolower="loadMore"> |
| | | <view class="record-list"> |
| | | <view class="record-card" v-for="(item, index) in recordList" :key="index"> |
| | | <!-- 第一行:名称 + 状态 --> |
| | | <view class="card-row-top"> |
| | | <text class="card-name">{{ item.disposeUserName }}</text> |
| | | <text class="card-status" :class="statusClass(item.disposeStatus)"> |
| | | {{ getStatusLabel(item.disposeStatus) }} |
| | | </text> |
| | | </view> |
| | | <!-- 第二行:标题 --> |
| | | <view class="card-title"> |
| | | <text>{{ item.disposeContent }}</text> |
| | | </view> |
| | | <!-- 第三行:图片列表 --> |
| | | <view class="card-images" v-if="item.attachUrl && item.attachUrl.length"> |
| | | <image |
| | | v-for="(img, imgIdx) in item.attachUrl" |
| | | :key="imgIdx" |
| | | :src="img" |
| | | mode="aspectFill" |
| | | class="card-image" |
| | | @click="previewImage(item.attachUrl, imgIdx)" |
| | | /> |
| | | </view> |
| | | <!-- 第四行:日期 --> |
| | | <view class="card-row-bottom"> |
| | | <text class="card-date">{{ formatDate(item.operateTime) }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 暂无数据 --> |
| | | <view class="noData" v-if="!loading && recordList.length === 0"> |
| | | <text>暂无处置记录</text> |
| | | </view> |
| | | <!-- 加载提示 --> |
| | | <view class="loadingMore"> |
| | | <text v-if="loading">加载中...</text> |
| | | <text v-else-if="!hasMore && recordList.length > 0">没有更多了</text> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from 'vue' |
| | | import { onLoad } from '@dcloudio/uni-app' |
| | | import dayjs from 'dayjs' |
| | | import { getDisposeRecords } from '@/api/work/index.js' |
| | | |
| | | const eventId = ref('') |
| | | const eventStatus = ref('') |
| | | const recordList = ref([]) |
| | | const loading = ref(false) |
| | | const hasMore = ref(true) |
| | | const listParams = ref({ |
| | | current: 1, |
| | | size: 3, |
| | | }) |
| | | |
| | | // 获取状态文本 |
| | | const getStatusLabel = status => { |
| | | const map = { |
| | | 0: '待确认', |
| | | 1: '处置中', |
| | | 2: '已退回', |
| | | 3: '已处置', |
| | | 4: '已完成', |
| | | } |
| | | return map[status] || '未知' |
| | | } |
| | | |
| | | // 状态样式类名 |
| | | const statusClass = status => { |
| | | if (status === 1) return 'status-processing' |
| | | if (status === 3) return 'status-handled' |
| | | if (status === 4) return 'status-completed' |
| | | return '' |
| | | } |
| | | |
| | | // 格式化日期 |
| | | const formatDate = dateString => { |
| | | if (!dateString) return '' |
| | | return dayjs(dateString).format('YYYY-MM-DD HH:mm') |
| | | } |
| | | |
| | | // 读取工单标识 |
| | | onLoad(options => { |
| | | eventId.value = options?.id ?? '' |
| | | eventStatus.value = options?.eventStatus ?? '' |
| | | getRecordList() |
| | | }) |
| | | |
| | | onShow(() => { |
| | | getRecordList() |
| | | }) |
| | | |
| | | // 获取处置记录列表 |
| | | const getRecordList = async () => { |
| | | if (loading.value || !hasMore.value) return |
| | | loading.value = true |
| | | const res = await getDisposeRecords({ eventId: eventId.value }) |
| | | recordList.value = res.data.data |
| | | loading.value = false |
| | | } |
| | | |
| | | function uploadHadle() { |
| | | uni.navigateTo({ |
| | | url: `/subPackages/workDetail/HandleWorking/index?id=${eventId.value}&eventStatus=${eventStatus.value}`, |
| | | }) |
| | | } |
| | | |
| | | // 加载更多 |
| | | const loadMore = () => { |
| | | if (loading.value || !hasMore.value) return |
| | | listParams.value.current++ |
| | | getRecordList() |
| | | } |
| | | |
| | | // 图片预览 |
| | | const previewImage = (attachments, index) => { |
| | | uni.previewImage({ |
| | | urls: attachments, |
| | | current: index, |
| | | }) |
| | | } |
| | | |
| | | // 返回按钮点击事件 |
| | | const onBackClick = () => { |
| | | uni.navigateBack() |
| | | } |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .handle-work-page { |
| | | padding-top: 88rpx; |
| | | :deep() { |
| | | .u-navbar__content { |
| | | background: url('https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets/images/user/bg.png') |
| | | no-repeat !important; |
| | | background-size: 100% !important; |
| | | height: 88rpx !important; |
| | | } |
| | | } |
| | | |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100vh; // 新增:占满全屏 |
| | | background: #f6f6f6; |
| | | box-sizing: border-box; |
| | | |
| | | :deep(.u-navbar) { |
| | | position: fixed !important; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | z-index: 100; |
| | | } |
| | | |
| | | :deep(.u-navbar__content) { |
| | | height: 88rpx !important; |
| | | } |
| | | } |
| | | |
| | | // 上传处置按钮 - 移到滚动区域外面 |
| | | .upload_handle { |
| | | height: 80rpx; // 改大一点,方便点击 |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: flex-end; |
| | | padding-right: 24rpx; |
| | | margin: 60rpx 0 12rpx 0; // 上下间距 |
| | | color: #1d6fe9; |
| | | font-size: 28rpx; |
| | | flex-shrink: 0; // 防止被压缩 |
| | | background: #f6f6f6; // 背景色,避免和滚动区域混在一起 |
| | | } |
| | | |
| | | .record-scroll { |
| | | flex: 1; // 占满剩余高度 |
| | | // padding-top: 24rpx; |
| | | padding: 0 24rpx 24rpx 24rpx; // 左右下内边距 |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .record-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20rpx; |
| | | padding-bottom: 10rpx; |
| | | } |
| | | |
| | | .record-card { |
| | | background: #ffffff; |
| | | border-radius: 12rpx; |
| | | padding: 24rpx; |
| | | box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06); |
| | | } |
| | | |
| | | .card-row-top { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 16rpx; |
| | | } |
| | | |
| | | .card-name { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 500; |
| | | font-size: 28rpx; |
| | | color: #222324; |
| | | } |
| | | |
| | | .card-status { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 24rpx; |
| | | padding: 4rpx 16rpx; |
| | | border-radius: 6rpx; |
| | | color: #ffffff; |
| | | background: #999; |
| | | |
| | | &.status-processing { |
| | | background: #1d6fe9; |
| | | } |
| | | &.status-handled { |
| | | background: #e6a23c; |
| | | } |
| | | &.status-completed { |
| | | background: #00b578; |
| | | } |
| | | } |
| | | |
| | | .card-title { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 30rpx; |
| | | color: #222324; |
| | | line-height: 1.6; |
| | | margin-bottom: 16rpx; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .card-images { |
| | | display: flex; |
| | | gap: 12rpx; |
| | | margin-bottom: 16rpx; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .card-image { |
| | | width: 160rpx; |
| | | height: 160rpx; |
| | | border-radius: 8rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .card-row-bottom { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .card-date { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | |
| | | .noData { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 400rpx; |
| | | font-size: 28rpx; |
| | | color: #999; |
| | | } |
| | | |
| | | .loadingMore { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 80rpx; |
| | | font-size: 28rpx; |
| | | color: #999; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="handle-page"> |
| | | <u-navbar title="待办处置" :is-back="true" back-text="" :back-icon-size="40" @left-click="onBackClick"></u-navbar> |
| | | |
| | | <scroll-view class="form-scroll" scroll-y> |
| | | <view class="form-section description-section"> |
| | | <view class="section-header"> |
| | | <text class="section-title">处置描述</text> |
| | | </view> |
| | | <view class="description-wrapper"> |
| | | <textarea |
| | | v-model="formData.disposeContent" |
| | | class="description-input" |
| | | maxlength="140" |
| | | placeholder="请输入" |
| | | placeholder-class="description-placeholder" |
| | | :show-confirm-bar="false" |
| | | /> |
| | | <text class="word-count">{{ descriptionLength }}/140</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- <view class="form-section status-section">--> |
| | | <!-- <text class="section-title">处置中</text>--> |
| | | <!-- <switch class="status-switch" :checked="formData.isHandling" color="#00b578" @change="handleStatusChange" />--> |
| | | <!-- </view>--> |
| | | |
| | | <view class="form-section upload-section"> |
| | | <view class="section-header upload-header"> |
| | | <text class="section-title">附件上传</text> |
| | | </view> |
| | | <!-- <button class="select-file-btn" @click="handleChooseFile">选择文件</button>--> |
| | | <u-button color="#1D6FE9" class="select-file-btn" :disabled="uploading" @click="handleChooseFile"> |
| | | {{ uploading ? '上传中...' : '选择文件' }} |
| | | </u-button> |
| | | |
| | | <view v-if="attachmentList.length" class="attachment-list"> |
| | | <view v-for="(file, index) in attachmentList" :key="`${file.path}-${index}`" class="attachment-item"> |
| | | <view class="file-info"> |
| | | <text class="file-name">{{ file.name }}</text> |
| | | <text v-if="file.size" class="file-size">{{ formatFileSize(file.size) }}</text> |
| | | </view> |
| | | <text class="remove-file-btn" @click.stop="handleRemoveFile(index)">×</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <view class="action-footer"> |
| | | <u-button color="#AEAEAE" class="custom-style" @click="handleCancel">取消</u-button> |
| | | <u-button color="#1D6FE9" class="custom-style" :disabled="submitting || uploading" @click="handleSubmit"> |
| | | {{ submitting ? '提交中...' : '确定' }} |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, ref } from 'vue' |
| | | import { onLoad } from '@dcloudio/uni-app' |
| | | import { backGdApi, addDisposeRecord, uploadFileAPI } from '@/api/work/index.js' |
| | | |
| | | const MAX_FILE_COUNT = 9 |
| | | const eventId = ref('') |
| | | const eventStatus = ref(null) |
| | | const submitting = ref(false) |
| | | const uploading = ref(false) |
| | | const attachmentList = ref([]) |
| | | const formData = ref({ |
| | | disposeContent: '', |
| | | isHandling: true, |
| | | }) |
| | | const navbarTitleStyle = { |
| | | color: '#ffffff', |
| | | fontSize: '34rpx', |
| | | fontWeight: '500', |
| | | } |
| | | |
| | | // 计算处置描述字数 |
| | | const descriptionLength = computed(() => formData.value.disposeContent.length) |
| | | |
| | | // 读取当前工单标识 |
| | | onLoad(options => { |
| | | eventId.value = options?.id ?? '' |
| | | }) |
| | | |
| | | // 返回上一页 |
| | | function handleBack() { |
| | | uni.navigateBack() |
| | | } |
| | | |
| | | // 切换处置状态 |
| | | // function handleStatusChange(event) { |
| | | // formData.value.isHandling = event.detail.value |
| | | // } |
| | | |
| | | // 提取文件名称 |
| | | function getFileName(filePath = '') { |
| | | const normalizedPath = filePath.replace(/\\/g, '/') |
| | | return decodeURIComponent(normalizedPath.split('/').pop() || '未命名文件') |
| | | } |
| | | |
| | | // 上传单个已选择文件 |
| | | async function uploadSelectedFile(file) { |
| | | const response = await uploadFileAPI(file.path) |
| | | const link = response?.data?.link || response?.data?.url || response?.link |
| | | if (!link) throw new Error('上传接口未返回文件地址') |
| | | return { ...file, link } |
| | | } |
| | | |
| | | // 处理已选择文件 |
| | | async function handleChooseSuccess(result) { |
| | | const selectedFiles = result.tempFiles?.length |
| | | ? result.tempFiles |
| | | : (result.tempFilePaths ?? []).map(path => ({ path })) |
| | | const availableCount = MAX_FILE_COUNT - attachmentList.value.length |
| | | const normalizedFiles = selectedFiles |
| | | .slice(0, availableCount) |
| | | .map(file => { |
| | | const path = file.path || file.tempFilePath || '' |
| | | return { |
| | | name: file.name || getFileName(path), |
| | | path, |
| | | size: Number(file.size) || 0, |
| | | } |
| | | }) |
| | | .filter(file => file.path) |
| | | if (!normalizedFiles.length) return |
| | | |
| | | uploading.value = true |
| | | uni.showLoading({ title: '上传中...', mask: true }) |
| | | let failedCount = 0 |
| | | try { |
| | | const uploadResults = await Promise.all( |
| | | normalizedFiles.map(async file => { |
| | | try { |
| | | return { success: true, file: await uploadSelectedFile(file) } |
| | | } catch { |
| | | return { success: false, file } |
| | | } |
| | | }) |
| | | ) |
| | | const uploadedFiles = uploadResults.filter(item => item.success).map(item => item.file) |
| | | failedCount = uploadResults.length - uploadedFiles.length |
| | | attachmentList.value.push(...uploadedFiles) |
| | | } finally { |
| | | uni.hideLoading() |
| | | uploading.value = false |
| | | } |
| | | uni.showToast({ |
| | | title: failedCount ? `${failedCount}个文件上传失败` : '上传成功', |
| | | icon: failedCount ? 'none' : 'success', |
| | | }) |
| | | } |
| | | |
| | | // 处理选择文件失败 |
| | | function handleChooseFail(error) { |
| | | if (error?.errMsg?.includes('cancel')) return |
| | | uni.showToast({ title: '选择文件失败', icon: 'none' }) |
| | | } |
| | | |
| | | // 选择附件 |
| | | function handleChooseFile() { |
| | | if (uploading.value) return |
| | | const availableCount = MAX_FILE_COUNT - attachmentList.value.length |
| | | if (availableCount <= 0) { |
| | | uni.showToast({ title: `最多选择${MAX_FILE_COUNT}个附件`, icon: 'none' }) |
| | | return |
| | | } |
| | | const options = { |
| | | count: availableCount, |
| | | type: 'all', |
| | | success: handleChooseSuccess, |
| | | fail: handleChooseFail, |
| | | } |
| | | |
| | | // #ifdef H5 |
| | | uni.chooseFile(options) |
| | | // #endif |
| | | |
| | | // #ifdef MP-WEIXIN |
| | | uni.chooseMessageFile(options) |
| | | // #endif |
| | | |
| | | // #ifdef APP-PLUS |
| | | uni.chooseImage({ ...options, sourceType: ['album', 'camera'] }) |
| | | // #endif |
| | | } |
| | | |
| | | // 移除附件 |
| | | function handleRemoveFile(index) { |
| | | attachmentList.value.splice(index, 1) |
| | | } |
| | | |
| | | // 格式化文件大小 |
| | | function formatFileSize(size) { |
| | | if (size < 1024) return `${size} B` |
| | | if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB` |
| | | return `${(size / 1024 / 1024).toFixed(1)} MB` |
| | | } |
| | | |
| | | // 取消处置 |
| | | function handleCancel() { |
| | | uni.navigateBack() |
| | | } |
| | | |
| | | // 提交处置信息 |
| | | async function handleSubmit() { |
| | | if (uploading.value) { |
| | | uni.showToast({ title: '请等待附件上传完成', icon: 'none' }) |
| | | return |
| | | } |
| | | const disposeContent = formData.value.disposeContent.trim() |
| | | if (!disposeContent) { |
| | | uni.showToast({ title: '请输入处置描述', icon: 'none' }) |
| | | return |
| | | } |
| | | if (!eventId.value) { |
| | | uni.showToast({ title: '工单信息缺失', icon: 'none' }) |
| | | return |
| | | } |
| | | |
| | | submitting.value = true |
| | | try { |
| | | await addDisposeRecord({ |
| | | eventId: eventId.value, |
| | | recordType: 1, |
| | | disposeContent: formData.value.disposeContent, |
| | | targetEventStatus: eventStatus.value == 1 ? 3 : eventStatus.value, |
| | | updateEventStatus: true, |
| | | attachUrl: attachmentList.value.map(file => file.link), |
| | | // operateTime: '2026-07-30 14:00:00', |
| | | // eventStatus: formData.value.isHandling ? '1' : '3', |
| | | // handleDesc, |
| | | // attachmentList: attachmentList.value.map(file => ({ |
| | | // attachName: file.name, |
| | | // filePath: file.path, |
| | | // })), |
| | | }) |
| | | uni.showToast({ title: '提交成功', icon: 'success' }) |
| | | setTimeout(() => { |
| | | submitting.value = false |
| | | uni.navigateBack() |
| | | // uni.navigateTo({ |
| | | // url: `/subPackages/workDetail/HandleWork/index?id=${eventId.value}`, |
| | | // }) |
| | | }, 1200) |
| | | } catch { |
| | | submitting.value = false |
| | | uni.showToast({ title: '提交失败,请重试', icon: 'none' }) |
| | | } |
| | | } |
| | | |
| | | // 返回按钮点击事件 |
| | | const onBackClick = () => { |
| | | uni.navigateBack() |
| | | } |
| | | |
| | | onLoad(options => { |
| | | console.log(options, '9090') |
| | | eventId.value = options?.id || '' |
| | | eventStatus.value = options?.eventStatus || '' |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .handle-page { |
| | | padding-top: 88rpx; |
| | | :deep() { |
| | | .u-navbar__content { |
| | | background: url('https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets/images/user/bg.png') |
| | | no-repeat !important; |
| | | background-size: 100% !important; |
| | | height: 88rpx !important; |
| | | } |
| | | } |
| | | |
| | | display: flex; |
| | | flex-direction: column; |
| | | box-sizing: border-box; |
| | | height: calc(100vh); |
| | | //padding-top: 88rpx; |
| | | //background: #111827; |
| | | //color: #f5f7fa; |
| | | |
| | | //:deep(.u-navbar__content) { |
| | | // height: 88rpx !important; |
| | | // background: #151c29 !important; |
| | | //} |
| | | } |
| | | |
| | | .form-scroll { |
| | | box-sizing: border-box; |
| | | height: 0; |
| | | flex: 1; |
| | | padding: 60rpx 24rpx 24rpx; |
| | | } |
| | | |
| | | .form-section { |
| | | box-sizing: border-box; |
| | | margin-bottom: 20rpx; |
| | | background: #ffffff; |
| | | border: 1rpx solid rgba(255, 255, 255, 0.03); |
| | | } |
| | | |
| | | .section-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | height: 82rpx; |
| | | padding: 0 22rpx; |
| | | border-bottom: 1rpx solid #eeeeee; |
| | | } |
| | | |
| | | .section-title { |
| | | font-size: 30rpx; |
| | | font-weight: 400; |
| | | color: #222324; |
| | | } |
| | | |
| | | .description-wrapper { |
| | | position: relative; |
| | | } |
| | | |
| | | .word-count { |
| | | position: absolute; |
| | | right: 22rpx; |
| | | bottom: 16rpx; |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | |
| | | .description-input { |
| | | display: block; |
| | | box-sizing: border-box; |
| | | width: 100%; |
| | | height: 350rpx; |
| | | padding: 24rpx 22rpx; |
| | | font-size: 30rpx; |
| | | line-height: 1.6; |
| | | color: #222324; |
| | | background: transparent; |
| | | } |
| | | |
| | | :deep(.description-placeholder) { |
| | | color: #999; |
| | | } |
| | | |
| | | .status-section { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | height: 104rpx; |
| | | padding: 0 20rpx 0 22rpx; |
| | | } |
| | | |
| | | .status-switch { |
| | | transform: scale(0.84); |
| | | transform-origin: right center; |
| | | } |
| | | |
| | | .upload-section { |
| | | padding-bottom: 22rpx; |
| | | } |
| | | |
| | | .upload-header { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .select-file-btn { |
| | | width: 210rpx; |
| | | height: 70rpx; |
| | | margin: 0; |
| | | font-size: 28rpx; |
| | | line-height: 70rpx; |
| | | //color: #ffffff; |
| | | //background: #409eff; |
| | | //border-radius: 4rpx; |
| | | } |
| | | |
| | | .select-file-btn::after, |
| | | .action-btn::after { |
| | | border: 0; |
| | | } |
| | | |
| | | .custom-style { |
| | | width: 276rpx; |
| | | height: 76rpx; |
| | | font-family: 'Source Han Sans CN'; |
| | | font-weight: 400; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .attachment-list { |
| | | padding: 10rpx 22rpx 0; |
| | | } |
| | | |
| | | .attachment-item { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | min-height: 70rpx; |
| | | border-bottom: 1rpx solid #eeeeee; |
| | | } |
| | | |
| | | .file-info { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 0; |
| | | flex: 1; |
| | | } |
| | | |
| | | .file-name { |
| | | overflow: hidden; |
| | | font-size: 26rpx; |
| | | color: #222324; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .file-size { |
| | | margin-left: 14rpx; |
| | | font-size: 22rpx; |
| | | color: #999; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .remove-file-btn { |
| | | width: 56rpx; |
| | | font-size: 42rpx; |
| | | line-height: 56rpx; |
| | | color: #999; |
| | | text-align: right; |
| | | } |
| | | |
| | | .action-footer { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 28rpx; |
| | | box-sizing: border-box; |
| | | padding: 22rpx 28rpx calc(22rpx + env(safe-area-inset-bottom)); |
| | | //background: #111827; |
| | | } |
| | | |
| | | .action-btn { |
| | | height: 82rpx; |
| | | margin: 0; |
| | | flex: 1; |
| | | font-size: 30rpx; |
| | | line-height: 80rpx; |
| | | color: #ffffff; |
| | | border-radius: 6rpx; |
| | | } |
| | | |
| | | .cancel-btn { |
| | | color: #00b578; |
| | | background: transparent; |
| | | border: 2rpx solid #00b578; |
| | | } |
| | | |
| | | .confirm-btn { |
| | | background: #00b578; |
| | | border: 2rpx solid #00b578; |
| | | } |
| | | |
| | | .confirm-btn[disabled] { |
| | | color: rgba(255, 255, 255, 0.72); |
| | | background: #087b58; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <script setup> |
| | | import { onLoad } from '@dcloudio/uni-app' |
| | | |
| | | onLoad(options => { |
| | | console.log(options, '9090') |
| | | // eventId.value = options?.id || '' |
| | | // eventStatus.value = options?.eventStatus || '' |
| | | }) |
| | | </script> |
| | | <template> |
| | | <div class="workDetailContainer"> |
| | | <!-- 自定义导航栏 --> |
| | | <u-navbar title="工单详情" :is-back="true" back-text="" :back-icon-size="40" @left-click="onBackClick"> |
| | | <template #right> |
| | | <div v-if="isH5" class="tapShare-icon"><img src="/static/images/work/share.svg" @click="onShareClick" /></div> |
| | | <div v-else class="share-btn" @click="onShareClick"></div> |
| | | </template> |
| | | </u-navbar> |
| | | <div class="detailTop">打发打发打发打发</div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style lang="scss" scoped> |
| | | .workDetailContainer { |
| | | // 添加导航栏高度的padding-top,避免内容被遮挡 |
| | | padding-top: 88rpx; |
| | | :deep() { |
| | | .u-navbar__content { |
| | | background: url('https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets/images/user/bg.png') |
| | | no-repeat !important; |
| | | background-size: 100% !important; |
| | | height: 88rpx !important; |
| | | } |
| | | } // 分享按钮样式 |
| | | .share-btn { |
| | | // #ifdef APP-PLUS |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | background-image: url('/static/images/work/share.svg'); |
| | | background-size: contain; |
| | | background-repeat: no-repeat; |
| | | background-position: center; |
| | | // #endif |
| | | } |
| | | .tapShare-icon { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | img { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | } |
| | | } |
| | | .detailTop { |
| | | // #ifdef APP-PLUS |
| | | padding-top: 60rpx; |
| | | // #endif |
| | | .image-container { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 410rpx; |
| | | |
| | | .detailImage { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: block; |
| | | object-fit: cover; |
| | | } |
| | | // 轮播图容器 |
| | | :deep(.u-swiper) { |
| | | // 指示器容器 - 修改为居中对齐 |
| | | .u-swiper__indicator { |
| | | position: absolute; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | bottom: 20rpx; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | z-index: 10; |
| | | } |
| | | .u-swiper-indicator__wrapper__dot--active { |
| | | width: 5px !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .worderContainer { |
| | | padding: 0 24rpx; |
| | | padding-bottom: 4rpx; |
| | | background: #f6f6f6; |
| | | margin-top: 20rpx; |
| | | height: calc(100vh - 530rpx); |
| | | overflow: auto; |
| | | } |
| | | .workOrderContent { |
| | | margin-top: 30epx; |
| | | background-color: #fff; |
| | | border-radius: 12rpx; |
| | | padding: 20rpx; |
| | | margin-bottom: 34rpx; |
| | | .workOrderTitle { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: bold; |
| | | font-size: 32rpx; |
| | | color: #222324; |
| | | } |
| | | .workOrderContainer { |
| | | .orderRow { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | min-height: 96rpx; |
| | | border-bottom: 1px solid #f5f5f5; |
| | | color: #7b7b7b; |
| | | .rowTitle { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 30rpx; |
| | | color: #222324; |
| | | white-space: nowrap; |
| | | } |
| | | .rowAddress { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 28rpx; |
| | | color: #1d6fe9; |
| | | text-decoration: underline; |
| | | text-align: right; |
| | | |
| | | padding-top: 1px; |
| | | padding-left: 5px; |
| | | padding-right: 2px; |
| | | } |
| | | .status-picker { |
| | | .picker-value { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 28rpx; |
| | | color: #1d6fe9; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8rpx; |
| | | } |
| | | .picker-arrow { |
| | | font-size: 20rpx; |
| | | color: #999; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .actionButton { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | box-sizing: border-box; |
| | | width: 100%; |
| | | height: 61px; |
| | | padding: 0 24rpx; |
| | | |
| | | border-radius: 6px 6px 6px 6px; |
| | | |
| | | .btngroups { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 100%; |
| | | min-width: 0; |
| | | :deep(.u-button) { |
| | | width: 0 !important; |
| | | min-width: 0; |
| | | margin: 0 !important; |
| | | flex: 1; |
| | | height: 76rpx !important; |
| | | } |
| | | &:not(.three-actions) { |
| | | :deep(.u-button + .u-button) { |
| | | margin-left: 20rpx !important; |
| | | } |
| | | } |
| | | &.three-actions { |
| | | justify-content: center; |
| | | :deep(.u-button) { |
| | | width: 210rpx !important; |
| | | flex: 0 0 210rpx; |
| | | } |
| | | :deep(.u-button:last-child) { |
| | | margin-left: 20rpx !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .complete-modal-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | left: 0; |
| | | z-index: 10000; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-sizing: border-box; |
| | | padding: 40rpx; |
| | | background: rgba(0, 0, 0, 0.55); |
| | | } |
| | | |
| | | .complete-modal { |
| | | box-sizing: border-box; |
| | | width: 620rpx; |
| | | max-width: 100%; |
| | | padding: 32rpx; |
| | | background: #ffffff; |
| | | border-radius: 12rpx; |
| | | } |
| | | |
| | | .complete-modal-title { |
| | | margin-bottom: 28rpx; |
| | | font-size: 32rpx; |
| | | font-weight: 500; |
| | | color: #222324; |
| | | text-align: center; |
| | | } |
| | | |
| | | .complete-input-wrapper { |
| | | position: relative; |
| | | box-sizing: border-box; |
| | | height: 220rpx; |
| | | padding-bottom: 38rpx; |
| | | background: #f6f7f9; |
| | | border: 1rpx solid #e5e7eb; |
| | | border-radius: 6rpx; |
| | | } |
| | | |
| | | .complete-input { |
| | | box-sizing: border-box; |
| | | width: 100%; |
| | | height: 100%; |
| | | padding: 20rpx; |
| | | font-size: 28rpx; |
| | | line-height: 1.5; |
| | | color: #222324; |
| | | } |
| | | |
| | | .complete-word-count { |
| | | position: absolute; |
| | | right: 16rpx; |
| | | bottom: 10rpx; |
| | | font-size: 22rpx; |
| | | color: #999999; |
| | | } |
| | | |
| | | .complete-modal-actions { |
| | | display: flex; |
| | | gap: 24rpx; |
| | | margin-top: 30rpx; |
| | | } |
| | | |
| | | .complete-action-btn { |
| | | height: 76rpx; |
| | | margin: 0; |
| | | flex: 1; |
| | | font-size: 28rpx; |
| | | line-height: 76rpx; |
| | | border-radius: 6rpx; |
| | | } |
| | | |
| | | .complete-action-btn::after { |
| | | border: 0; |
| | | } |
| | | |
| | | .complete-cancel-btn { |
| | | color: #666666; |
| | | background: #f1f2f4; |
| | | } |
| | | |
| | | .complete-confirm-btn { |
| | | color: #ffffff; |
| | | background: #1d6fe9; |
| | | } |
| | | |
| | | .complete-action-btn[disabled] { |
| | | opacity: 0.65; |
| | | } |
| | | |
| | | /* 分享弹出层样式 */ |
| | | .share-modal-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background-color: rgba(0, 0, 0, 0.6); |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: flex-end; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | .share-modal-content { |
| | | background-color: #fff; |
| | | border-radius: 20rpx 20rpx 0 0; |
| | | padding: 30rpx; |
| | | width: 100%; |
| | | max-height: 80vh; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .share-modal-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 30rpx; |
| | | } |
| | | |
| | | .share-modal-title { |
| | | font-size: 36rpx; |
| | | font-weight: bold; |
| | | color: #222324; |
| | | text-align: center; |
| | | flex: 1; |
| | | } |
| | | |
| | | .share-modal-close { |
| | | font-size: 60rpx; |
| | | color: #999; |
| | | line-height: 40rpx; |
| | | } |
| | | |
| | | .share-modal-body { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | justify-content: space-around; |
| | | padding-bottom: 30rpx; |
| | | } |
| | | |
| | | .share-modal-footer { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | padding-top: 30rpx; |
| | | border-top: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | .share-item { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | margin: 20rpx; |
| | | width: 120rpx; |
| | | } |
| | | |
| | | .share-icon { |
| | | width: 90rpx; |
| | | height: 90rpx; |
| | | border-radius: 50%; |
| | | margin-bottom: 15rpx; |
| | | background-size: contain; |
| | | background-repeat: no-repeat; |
| | | background-position: center; |
| | | } |
| | | |
| | | .share-icon.wechat { |
| | | background-image: url('@/static/images/work/wechat.svg'); |
| | | } |
| | | |
| | | .share-icon.moments { |
| | | background-image: url('@/static/images/work/moments.svg'); |
| | | } |
| | | |
| | | .share-icon.link { |
| | | background-image: url('@/static/images/work/link.svg'); |
| | | } |
| | | .share-name { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | margin-top: 10rpx; |
| | | } |
| | | </style> |
| | |
| | | <!-- 自定义导航栏 --> |
| | | <u-navbar title="工单详情" :is-back="true" back-text="" :back-icon-size="40" @left-click="onBackClick"> |
| | | <template #right> |
| | | <div v-if="isH5" class="tapShare-icon"><img src="/static/images/work/share.svg" @click="onShareClick"/></div> |
| | | <div v-if="isH5" class="tapShare-icon"><img src="/static/images/work/share.svg" @click="onShareClick" /></div> |
| | | <div v-else class="share-btn" @click="onShareClick"></div> |
| | | </template> |
| | | </u-navbar> |
| | |
| | | ></u-swiper> |
| | | </div> |
| | | </div> |
| | | <!-- 工单内容 --> |
| | | <div class="worderContainer"> |
| | | <div class="workOrderContent"> |
| | | <div class="workOrderTitle">工单内容</div> |
| | | <div class="workOrderContainer"> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">工单名称</div> |
| | | <div>{{ workDetailData.eventName }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">工单编号</div> |
| | | <div>{{ workDetailData.eventNum }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">工单处置人</div> |
| | | <div>{{ workDetailData.disposeUserName }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">处置部门</div> |
| | | <div>{{ workDetailData.disposeDeptName }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">拍摄时间</div> |
| | | <div>{{ workDetailData.shootTime }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">分发人员</div> |
| | | <div>{{ workDetailData.distributeUserName }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">分发部门</div> |
| | | <div>{{ workDetailData.distributeDeptName }}</div> |
| | | </div> |
| | | <!-- 工单内容 --> |
| | | <div class="worderContainer"> |
| | | <div class="workOrderContent"> |
| | | <div class="workOrderTitle">工单内容</div> |
| | | <div class="workOrderContainer"> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">工单名称</div> |
| | | <div>{{ workDetailData.eventName }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">工单编号</div> |
| | | <div>{{ workDetailData.eventNum }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">工单状态</div> |
| | | <template v-if="[1, 3].includes(workDetailData.eventStatus)"> |
| | | <picker |
| | | mode="selector" |
| | | :range="statusOptions" |
| | | range-key="name" |
| | | :value="statusPickerIndex" |
| | | @change="onStatusChange" |
| | | class="status-picker" |
| | | > |
| | | <div class="picker-value"> |
| | | {{ currentStatusLabel || '请选择' }} |
| | | <text class="picker-arrow">▼</text> |
| | | </div> |
| | | </picker> |
| | | </template> |
| | | <div v-else>{{ getStatusLabel(workDetailData.eventStatus) }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">工单处置人</div> |
| | | <div>{{ workDetailData.disposeUserName }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">处置部门</div> |
| | | <div>{{ workDetailData.disposeDeptName }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">拍摄时间</div> |
| | | <div>{{ workDetailData.shootTime }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">分发人员</div> |
| | | <div>{{ workDetailData.distributeUserName }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">分发部门</div> |
| | | <div>{{ workDetailData.distributeDeptName }}</div> |
| | | </div> |
| | | |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">分发时间</div> |
| | | <div>{{ workDetailData.distributeTime }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">工单位置</div> |
| | | <div class="rowAddress" @click="jumpMap(workDetailData)">{{ workDetailData.eventLocation }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 操作按钮 --> |
| | | <div class="actionButton" > |
| | | <div class="btngroups" v-if="workDetailData.eventStatus !== 3"> |
| | | <up-button type="primary" color="#AEAEAE" text="退回" @click="rejectTicket"></up-button> |
| | | <up-button type="primary" color="#1D6FE9" text="确认" @click="confirmTheTicket"></up-button> |
| | | </div> |
| | | <div class="btngroups" v-if="workDetailData.eventStatus === 3"> |
| | | <up-button type="primary" color="#1D6FE9" text="已确认" @click="confirmedHandle"></up-button> |
| | | </div> |
| | | </div> |
| | | <!-- 分享弹出层 --> |
| | | <div v-if="showShareModal" class="share-modal-overlay" @click="onShareModalClose"> |
| | | <div class="share-modal-content" @click.stop> |
| | | <div class="share-modal-header"> |
| | | <div class="share-modal-title">分享到</div> |
| | | <!-- <div class="share-modal-close" @click="onShareModalClose">×</div> --> |
| | | </div> |
| | | <div class="share-modal-body"> |
| | | <!-- <div class="share-item" @click="shareToWechat">--> |
| | | <!-- <div class="share-icon wechat"></div>--> |
| | | <!-- <div class="share-name">微信好友</div>--> |
| | | <!-- </div>--> |
| | | <!-- <div class="share-item" @click="shareToMoments">--> |
| | | <!-- <div class="share-icon moments"></div>--> |
| | | <!-- <div class="share-name">朋友圈</div>--> |
| | | <!-- </div>--> |
| | | <div class="share-item" @click="copyLink"> |
| | | <div class="share-icon link"></div> |
| | | <div class="share-name">复制链接</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">分发时间</div> |
| | | <div>{{ workDetailData.distributeTime }}</div> |
| | | </div> |
| | | <div class="orderRow"> |
| | | <div class="rowTitle">工单位置</div> |
| | | <div class="rowAddress" @click="jumpMap(workDetailData)">{{ workDetailData.eventLocation }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 操作按钮 --> |
| | | <div class="actionButton"> |
| | | <div class="btngroups" v-if="workDetailData.eventStatus === 0"> |
| | | <up-button type="primary" color="#AEAEAE" text="退回" @click="rejectTicket"></up-button> |
| | | <up-button type="primary" color="#1D6FE9" text="待确认" @click="confirmTheTicket"></up-button> |
| | | </div> |
| | | <div class="btngroups" v-if="workDetailData.eventStatus === 1"> |
| | | <up-button type="primary" color="#1D6FE9" text="处置记录" @click="handlelogs"></up-button> |
| | | <up-button type="primary" color="#1D6FE9" text="上传处置" @click="uploadHandle"></up-button> |
| | | </div> |
| | | <div class="btngroups" v-if="workDetailData.eventStatus === 3"> |
| | | <up-button type="primary" color="#1D6FE9" text="处置记录" @click="handlelogs"></up-button> |
| | | <up-button type="primary" color="#1D6FE9" text="继续上传处置" @click="uploadHandle"></up-button> |
| | | <up-button type="primary" color="#1D6FE9" text="完成" @click="confirmedHandle"></up-button> |
| | | </div> |
| | | <div class="btngroups" v-if="workDetailData.eventStatus === 4"> |
| | | <up-button type="primary" color="#1D6FE9" text="处置记录" @click="handlelogs"></up-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 完成情况弹窗 --> |
| | | <view v-if="showCompleteModal" class="complete-modal-overlay" @click="closeCompleteModal"> |
| | | <view class="complete-modal" @click.stop> |
| | | <view class="complete-modal-title">填写完成情况</view> |
| | | <view class="complete-input-wrapper"> |
| | | <textarea |
| | | v-model="completeContent" |
| | | class="complete-input" |
| | | maxlength="200" |
| | | placeholder="请输入完成情况" |
| | | :show-confirm-bar="false" |
| | | /> |
| | | <text class="complete-word-count">{{ completeContent.length }}/200</text> |
| | | </view> |
| | | <view class="complete-modal-actions"> |
| | | <button class="complete-action-btn complete-cancel-btn" :disabled="completing" @click="closeCompleteModal"> |
| | | 取消 |
| | | </button> |
| | | <button class="complete-action-btn complete-confirm-btn" :disabled="completing" @click="submitComplete"> |
| | | {{ completing ? '提交中...' : '确认' }} |
| | | </button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- 分享弹出层 --> |
| | | <div v-if="showShareModal" class="share-modal-overlay" @click="onShareModalClose"> |
| | | <div class="share-modal-content" @click.stop> |
| | | <div class="share-modal-header"> |
| | | <div class="share-modal-title">分享到</div> |
| | | <!-- <div class="share-modal-close" @click="onShareModalClose">×</div> --> |
| | | </div> |
| | | <div class="share-modal-body"> |
| | | <!-- <div class="share-item" @click="shareToWechat">--> |
| | | <!-- <div class="share-icon wechat"></div>--> |
| | | <!-- <div class="share-name">微信好友</div>--> |
| | | <!-- </div>--> |
| | | <!-- <div class="share-item" @click="shareToMoments">--> |
| | | <!-- <div class="share-icon moments"></div>--> |
| | | <!-- <div class="share-name">朋友圈</div>--> |
| | | <!-- </div>--> |
| | | <div class="share-item" @click="copyLink"> |
| | | <div class="share-icon link"></div> |
| | | <div class="share-name">复制链接</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | // import { getShowImg, getSmallImg } from '@/utils/util' |
| | | import {getGddetailedData,backGdApi,shareCacheApi} from '/src/api/work/index.js' |
| | | import { getGddetailedData, backGdApi, shareCacheApi, updateStatus } from '/src/api/work/index.js' |
| | | import { getWebViewUrl } from '@/utils/index.js' |
| | | import WebViewPlus from '@/components/WebViewPlus.vue' |
| | | import { onShow } from '@dcloudio/uni-app' |
| | | |
| | | const eventNum = ref('') |
| | | // 工单内容 |
| | | const workDetailData = ref({}) |
| | | // 环境判断 |
| | | const isH5 = ref(false) |
| | | const sWebViewRef = ref(null); |
| | | const viewUrl = ref(""); |
| | | onLoad(async options => { |
| | | eventNum.value = options.id |
| | | await getDataList(options.id,options.aiImg) |
| | | checkEnvironment() |
| | | viewUrl.value = getWebViewUrl("/workDetail", { workDetailData: JSON.stringify(workDetailData.value) }); |
| | | const sWebViewRef = ref(null) |
| | | const viewUrl = ref('') |
| | | const showCompleteModal = ref(false) |
| | | const completeContent = ref('') |
| | | const completing = ref(false) |
| | | |
| | | // 状态选项列表 |
| | | const statusOptions = [ |
| | | { name: '处置中', value: 1 }, |
| | | { name: '已处置', value: 3 }, |
| | | // { name: '已完成', value: 4 }, |
| | | ] |
| | | // 当前选中状态的索引 |
| | | const statusPickerIndex = computed(() => { |
| | | const status = workDetailData.value?.eventStatus |
| | | return statusOptions.findIndex(item => item.value === status) |
| | | }) |
| | | const getDataList = async (val,aiImg) => { |
| | | const params = { |
| | | id: val, |
| | | } |
| | | const res = await getGddetailedData(params) |
| | | const response = res.data.data |
| | | workDetailData.value = {...response,aiImg} |
| | | // 当前状态文本 |
| | | const currentStatusLabel = computed(() => { |
| | | const option = statusOptions.find(item => item.value === workDetailData.value?.eventStatus) |
| | | return option?.name || '' |
| | | }) |
| | | |
| | | onLoad(async options => { |
| | | eventNum.value = options.id |
| | | await getDataList(options.id, options.aiImg) |
| | | checkEnvironment() |
| | | viewUrl.value = getWebViewUrl('/workDetail', { workDetailData: JSON.stringify(workDetailData.value) }) |
| | | }) |
| | | |
| | | onShow(() => { |
| | | getDataList(eventNum.value) |
| | | }) |
| | | |
| | | const getDataList = async (val, aiImg) => { |
| | | const params = { |
| | | id: val, |
| | | } |
| | | const res = await getGddetailedData(params) |
| | | const response = res.data.data |
| | | workDetailData.value = { ...response, aiImg } |
| | | } |
| | | |
| | | // 获取状态文本 |
| | | const getStatusLabel = status => { |
| | | const map = { |
| | | 0: '待确认', |
| | | 1: '处置中', |
| | | 2: '已退回', |
| | | 3: '已处置', |
| | | 4: '已完成', |
| | | } |
| | | return map[status] || '未知' |
| | | } |
| | | |
| | | // 切换状态 |
| | | const onStatusChange = e => { |
| | | const index = e.detail.value |
| | | const selectedOption = statusOptions[index] |
| | | if (!selectedOption) return |
| | | |
| | | const id = workDetailData.value.id |
| | | |
| | | if (workDetailData.value.eventStatus === 1 && selectedOption.value === 3) { |
| | | // 跳转上传处置页面 |
| | | uni.navigateTo({ |
| | | url: `/subPackages/workDetail/HandleWorking/index?id=${workDetailData.value.id}&eventStatus=${selectedOption.value}`, |
| | | }) |
| | | } |
| | | |
| | | if (workDetailData.value.eventStatus === 3 && selectedOption.value === 1) { |
| | | updateStatus({ |
| | | eventId: workDetailData.value.id, |
| | | targetStatus: 1, |
| | | }).then(res => { |
| | | workDetailData.value.eventStatus = selectedOption.value |
| | | }) |
| | | } |
| | | |
| | | // console.log(selectedOption, '3333') |
| | | // backGdApi({ eventId: id, eventStatus: String(selectedOption.value) }) |
| | | // .then(res => { |
| | | // uni.showToast({ |
| | | // title: '状态更新成功', |
| | | // icon: 'success', |
| | | // duration: 1500, |
| | | // }) |
| | | // workDetailData.value.eventStatus = selectedOption.value |
| | | // }) |
| | | // .catch(() => { |
| | | // uni.showToast({ |
| | | // title: '状态更新失败', |
| | | // icon: 'none', |
| | | // }) |
| | | // }) |
| | | } |
| | | |
| | | // 检查运行环境 |
| | | const checkEnvironment = () => { |
| | | const systemInfo = uni.getSystemInfoSync() |
| | | // #ifdef APP-PLUS |
| | | isH5.value = false |
| | | // #endif |
| | | const systemInfo = uni.getSystemInfoSync() |
| | | // #ifdef APP-PLUS |
| | | isH5.value = false |
| | | // #endif |
| | | |
| | | // #ifdef H5 |
| | | isH5.value = true |
| | | // #endif |
| | | // #ifdef H5 |
| | | isH5.value = true |
| | | // #endif |
| | | |
| | | // console.log('当前环境:', isH5.value ? 'H5' : 'App') |
| | | // console.log('当前环境:', isH5.value ? 'H5' : 'App') |
| | | } |
| | | // 图片预览 |
| | | const previewImage = index => { |
| | | if (getImageList.value.length === 0) return |
| | | const currentIndex = typeof index === 'number' ? index : 0 |
| | | uni.previewImage({ |
| | | urls: getImageList.value, |
| | | current: currentIndex, |
| | | }) |
| | | if (getImageList.value.length === 0) return |
| | | const currentIndex = typeof index === 'number' ? index : 0 |
| | | uni.previewImage({ |
| | | urls: getImageList.value, |
| | | current: currentIndex, |
| | | }) |
| | | } |
| | | const getImageList = computed(() => { |
| | | const imageArr = [] |
| | | const detail = workDetailData.value |
| | | if (detail.eventImageUrl) { |
| | | imageArr.push(detail.aiImg || detail.eventImageUrl) |
| | | } |
| | | return imageArr |
| | | const imageArr = [] |
| | | const detail = workDetailData.value |
| | | if (detail.eventImageUrl) { |
| | | imageArr.push(detail.aiImg || detail.eventImageUrl) |
| | | } |
| | | return imageArr |
| | | }) |
| | | // 跳转地图 |
| | | const jumpMap = item => { |
| | | // const contactStr = encodeURIComponent(JSON.stringify(item)) |
| | | const contactStr = JSON.stringify(item) |
| | | uni.navigateTo({ |
| | | url: `/subPackages/workDetail/mapWork/index?eventNum=${contactStr}`, |
| | | }) |
| | | // const contactStr = encodeURIComponent(JSON.stringify(item)) |
| | | const contactStr = JSON.stringify(item) |
| | | uni.navigateTo({ |
| | | url: `/subPackages/workDetail/mapWork/index?eventNum=${contactStr}`, |
| | | }) |
| | | } |
| | | // 退回 |
| | | const rejectTicket = () => { |
| | | const id = workDetailData.value.id |
| | | backGdApi({eventId : id,eventStatus:'0'}).then(res => { |
| | | uni.showToast({ |
| | | title: '退回成功', |
| | | icon: 'success', |
| | | duration: 1500 |
| | | }) |
| | | setTimeout(() => { |
| | | uni.navigateBack() |
| | | }) |
| | | }) |
| | | |
| | | // const id = workDetailData.value.id |
| | | // backGdApi({ eventId: id, eventStatus: '0' }).then(res => { |
| | | // uni.showToast({ |
| | | // title: '退回成功', |
| | | // icon: 'success', |
| | | // duration: 1500, |
| | | // }) |
| | | // setTimeout(() => { |
| | | // uni.navigateBack() |
| | | // }) |
| | | // }) |
| | | updateStatus({ |
| | | eventId: workDetailData.value.id, |
| | | targetStatus: 2, |
| | | }).then(res => { |
| | | uni.showToast({ |
| | | title: '退回成功', |
| | | icon: 'success', |
| | | duration: 1500, |
| | | }) |
| | | setTimeout(() => { |
| | | uni.navigateBack() |
| | | }) |
| | | }) |
| | | } |
| | | // 确认工单 |
| | | const confirmTheTicket = () => { |
| | | const id = workDetailData.value.id |
| | | backGdApi({eventId : id,eventStatus:'1'}).then(res => { |
| | | uni.showToast({ |
| | | title: '确认成功', |
| | | icon: 'success', |
| | | duration: 1500 |
| | | }) |
| | | setTimeout(() => { |
| | | uni.navigateBack() |
| | | }) |
| | | }) |
| | | // const id = workDetailData.value.id |
| | | // backGdApi({ eventId: id, eventStatus: '1' }).then(res => { |
| | | // uni.showToast({ |
| | | // title: '确认成功', |
| | | // icon: 'success', |
| | | // duration: 1500, |
| | | // }) |
| | | // setTimeout(() => { |
| | | // uni.navigateBack() |
| | | // }) |
| | | // }) |
| | | updateStatus({ |
| | | eventId: workDetailData.value.id, |
| | | targetStatus: 1, |
| | | }).then(res => { |
| | | uni.showToast({ |
| | | title: '确认成功', |
| | | icon: 'success', |
| | | duration: 1500, |
| | | }) |
| | | setTimeout(() => { |
| | | // uni.navigateBack() |
| | | workDetailData.value.eventStatus = 1 |
| | | }) |
| | | }) |
| | | } |
| | | // 已确认 |
| | | |
| | | // 处置记录 - 跳转至处置记录页面 |
| | | function handlelogs() { |
| | | uni.navigateTo({ |
| | | url: `/subPackages/workDetail/HandleWork/index?id=${workDetailData.value.id}&eventStatus=${workDetailData.value.eventStatus}`, |
| | | }) |
| | | } |
| | | // 上传处置记录 |
| | | function uploadHandle() { |
| | | uni.navigateTo({ |
| | | url: `/subPackages/workDetail/HandleWorking/index?id=${workDetailData.value.id}&eventStatus=${workDetailData.value.eventStatus}`, |
| | | }) |
| | | } |
| | | |
| | | // 打开完成情况弹窗 |
| | | const confirmedHandle = () => { |
| | | uni.navigateBack() |
| | | completeContent.value = '' |
| | | showCompleteModal.value = true |
| | | } |
| | | |
| | | // 关闭完成情况弹窗 |
| | | const closeCompleteModal = () => { |
| | | if (completing.value) return |
| | | showCompleteModal.value = false |
| | | } |
| | | |
| | | // 提交完成情况 |
| | | const submitComplete = async () => { |
| | | const operateContent = completeContent.value.trim() |
| | | if (!operateContent) { |
| | | uni.showToast({ title: '请输入完成情况', icon: 'none' }) |
| | | return |
| | | } |
| | | completing.value = true |
| | | try { |
| | | await updateStatus({ |
| | | eventId: workDetailData.value.id, |
| | | targetStatus: 4, |
| | | operateContent, |
| | | }) |
| | | uni.showToast({ title: '操作成功', icon: 'success' }) |
| | | setTimeout(() => { |
| | | uni.navigateBack() |
| | | }, 1000) |
| | | } catch { |
| | | completing.value = false |
| | | uni.showToast({ title: '操作失败,请重试', icon: 'none' }) |
| | | } |
| | | } |
| | | // 分享模态框状态 |
| | | const showShareModal = ref(false) |
| | |
| | | |
| | | // 复制链接 |
| | | const copyLink = async () => { |
| | | const res = await shareCacheApi(workDetailData.value) |
| | | const shareUrl = getWebViewUrl("/workDetail", { workDetailData: JSON.stringify(res.data.data) }) |
| | | const res = await shareCacheApi(workDetailData.value) |
| | | const shareUrl = getWebViewUrl('/workDetail', { workDetailData: JSON.stringify(res.data.data) }) |
| | | try { |
| | | await uni.setClipboardData({ |
| | | data: shareUrl, |
| | |
| | | type: 0, |
| | | title: workDetailData.value.event_name || '工单详情', |
| | | summary: '查看工单详情', |
| | | href: getWebViewUrl("/workDetail", { workDetailData: JSON.stringify(workDetailData.value) }), |
| | | href: getWebViewUrl('/workDetail', { workDetailData: JSON.stringify(workDetailData.value) }), |
| | | imageUrl: workDetailData.value.eventImageUrl, |
| | | success: () => { |
| | | uni.showToast({ title: '分享成功', icon: 'success' }) |
| | |
| | | |
| | | // 返回按钮点击事件 |
| | | const onBackClick = () => { |
| | | uni.navigateBack() |
| | | uni.navigateBack() |
| | | } |
| | | |
| | | // 监听导航栏右侧按钮点击事件(兼容原来的配置) |
| | | const onNavigationBarButtonTap = e => { |
| | | if (e.index === 0) { |
| | | onShareClick() |
| | | } |
| | | if (e.index === 0) { |
| | | onShareClick() |
| | | } |
| | | } |
| | | </script> |
| | | <style lang="scss" scoped> |
| | | .workDetailContainer { |
| | | // 添加导航栏高度的padding-top,避免内容被遮挡 |
| | | padding-top: 88rpx; |
| | | :deep(){ |
| | | .u-navbar__content{ |
| | | background: url("https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets/images/user/bg.png") no-repeat !important; |
| | | background-size: 100%!important; |
| | | height: 88rpx !important; |
| | | } |
| | | } // 分享按钮样式 |
| | | .share-btn { |
| | | // #ifdef APP-PLUS |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | background-image: url('/static/images/work/share.svg'); |
| | | background-size: contain; |
| | | background-repeat: no-repeat; |
| | | background-position: center; |
| | | // #endif |
| | | } |
| | | .tapShare-icon { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | img { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | } |
| | | } |
| | | .detailTop { |
| | | // #ifdef APP-PLUS |
| | | padding-top: 60rpx; |
| | | // #endif |
| | | .image-container { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 410rpx; |
| | | // 添加导航栏高度的padding-top,避免内容被遮挡 |
| | | padding-top: 88rpx; |
| | | :deep() { |
| | | .u-navbar__content { |
| | | background: url('https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets/images/user/bg.png') |
| | | no-repeat !important; |
| | | background-size: 100% !important; |
| | | height: 88rpx !important; |
| | | } |
| | | } // 分享按钮样式 |
| | | .share-btn { |
| | | // #ifdef APP-PLUS |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | background-image: url('/static/images/work/share.svg'); |
| | | background-size: contain; |
| | | background-repeat: no-repeat; |
| | | background-position: center; |
| | | // #endif |
| | | } |
| | | .tapShare-icon { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | img { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | } |
| | | } |
| | | .detailTop { |
| | | // #ifdef APP-PLUS |
| | | padding-top: 60rpx; |
| | | // #endif |
| | | .image-container { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 410rpx; |
| | | |
| | | .detailImage { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: block; |
| | | object-fit: cover; |
| | | } |
| | | // 轮播图容器 |
| | | :deep(.u-swiper) { |
| | | // 指示器容器 - 修改为居中对齐 |
| | | .u-swiper__indicator { |
| | | position: absolute; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | bottom: 20rpx; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | z-index: 10; |
| | | } |
| | | .u-swiper-indicator__wrapper__dot--active { |
| | | width: 5px !important; |
| | | } |
| | | } |
| | | .detailImage { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: block; |
| | | object-fit: cover; |
| | | } |
| | | // 轮播图容器 |
| | | :deep(.u-swiper) { |
| | | // 指示器容器 - 修改为居中对齐 |
| | | .u-swiper__indicator { |
| | | position: absolute; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | bottom: 20rpx; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | z-index: 10; |
| | | } |
| | | .u-swiper-indicator__wrapper__dot--active { |
| | | width: 5px !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | } |
| | | } |
| | | .worderContainer { |
| | | padding: 0 24rpx; |
| | | padding-bottom: 4rpx; |
| | | background: #f6f6f6; |
| | | margin-top: 20rpx; |
| | | height: calc(100vh - 530rpx); |
| | | overflow: auto; |
| | | } |
| | | .workOrderContent { |
| | | margin-top: 30epx; |
| | | background-color: #fff; |
| | | border-radius: 12rpx; |
| | | padding: 20rpx; |
| | | margin-bottom: 34rpx; |
| | | .workOrderTitle { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: bold; |
| | | font-size: 32rpx; |
| | | color: #222324; |
| | | } |
| | | .workOrderContainer { |
| | | .orderRow { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | min-height: 96rpx; |
| | | border-bottom: 1px solid #f5f5f5; |
| | | color: #7b7b7b; |
| | | .rowTitle { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 30rpx; |
| | | color: #222324; |
| | | white-space: nowrap; |
| | | } |
| | | .rowAddress { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 28rpx; |
| | | color: #1d6fe9; |
| | | text-decoration: underline; |
| | | text-align: right; |
| | | |
| | | .worderContainer { |
| | | padding: 0 24rpx; |
| | | padding-bottom: 4rpx; |
| | | background: #f6f6f6; |
| | | margin-top: 20rpx; |
| | | } |
| | | .workOrderContent { |
| | | margin-top: 30epx; |
| | | background-color: #fff; |
| | | border-radius: 12rpx; |
| | | padding: 20rpx; |
| | | margin-bottom: 34rpx; |
| | | .workOrderTitle { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: bold; |
| | | font-size: 32rpx; |
| | | color: #222324; |
| | | } |
| | | .workOrderContainer { |
| | | .orderRow { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | min-height: 96rpx; |
| | | border-bottom: 1px solid #f5f5f5; |
| | | color: #7b7b7b; |
| | | .rowTitle { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 30rpx; |
| | | color: #222324; |
| | | white-space: nowrap; |
| | | } |
| | | .rowAddress { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 28rpx; |
| | | color: #1d6fe9; |
| | | text-decoration: underline; |
| | | text-align: right; |
| | | padding-top: 1px; |
| | | padding-left: 5px; |
| | | padding-right: 2px; |
| | | } |
| | | .status-picker { |
| | | .picker-value { |
| | | font-family: Source Han Sans CN, Source Han Sans CN; |
| | | font-weight: 400; |
| | | font-size: 28rpx; |
| | | color: #1d6fe9; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8rpx; |
| | | } |
| | | .picker-arrow { |
| | | font-size: 20rpx; |
| | | color: #999; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | padding-top: 1px; |
| | | padding-left: 5px; |
| | | padding-right: 2px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .actionButton { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | box-sizing: border-box; |
| | | width: 100%; |
| | | height: 61px; |
| | | padding: 0 24rpx; |
| | | |
| | | .actionButton { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | width: 100%; |
| | | height: 61px; |
| | | border-radius: 6px 6px 6px 6px; |
| | | |
| | | border-radius: 6px 6px 6px 6px; |
| | | .btngroups { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 100%; |
| | | min-width: 0; |
| | | :deep(.u-button) { |
| | | width: 0 !important; |
| | | min-width: 0; |
| | | margin: 0 !important; |
| | | flex: 1; |
| | | height: 76rpx !important; |
| | | } |
| | | &:not(.three-actions) { |
| | | :deep(.u-button + .u-button) { |
| | | margin-left: 20rpx !important; |
| | | } |
| | | } |
| | | &.three-actions { |
| | | justify-content: center; |
| | | :deep(.u-button) { |
| | | width: 210rpx !important; |
| | | flex: 0 0 210rpx; |
| | | } |
| | | :deep(.u-button:last-child) { |
| | | margin-left: 20rpx !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .btngroups { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | :deep(.u-button) { |
| | | width: 276rpx !important; |
| | | height: 76rpx !important; |
| | | &:last-child { |
| | | margin-left: 30rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .complete-modal-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | left: 0; |
| | | z-index: 10000; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-sizing: border-box; |
| | | padding: 40rpx; |
| | | background: rgba(0, 0, 0, 0.55); |
| | | } |
| | | |
| | | .complete-modal { |
| | | box-sizing: border-box; |
| | | width: 620rpx; |
| | | max-width: 100%; |
| | | padding: 32rpx; |
| | | background: #ffffff; |
| | | border-radius: 12rpx; |
| | | } |
| | | |
| | | .complete-modal-title { |
| | | margin-bottom: 28rpx; |
| | | font-size: 32rpx; |
| | | font-weight: 500; |
| | | color: #222324; |
| | | text-align: center; |
| | | } |
| | | |
| | | .complete-input-wrapper { |
| | | position: relative; |
| | | box-sizing: border-box; |
| | | height: 220rpx; |
| | | padding-bottom: 38rpx; |
| | | background: #f6f7f9; |
| | | border: 1rpx solid #e5e7eb; |
| | | border-radius: 6rpx; |
| | | } |
| | | |
| | | .complete-input { |
| | | box-sizing: border-box; |
| | | width: 100%; |
| | | height: 100%; |
| | | padding: 20rpx; |
| | | font-size: 28rpx; |
| | | line-height: 1.5; |
| | | color: #222324; |
| | | } |
| | | |
| | | .complete-word-count { |
| | | position: absolute; |
| | | right: 16rpx; |
| | | bottom: 10rpx; |
| | | font-size: 22rpx; |
| | | color: #999999; |
| | | } |
| | | |
| | | .complete-modal-actions { |
| | | display: flex; |
| | | gap: 24rpx; |
| | | margin-top: 30rpx; |
| | | } |
| | | |
| | | .complete-action-btn { |
| | | height: 76rpx; |
| | | margin: 0; |
| | | flex: 1; |
| | | font-size: 28rpx; |
| | | line-height: 76rpx; |
| | | border-radius: 6rpx; |
| | | } |
| | | |
| | | .complete-action-btn::after { |
| | | border: 0; |
| | | } |
| | | |
| | | .complete-cancel-btn { |
| | | color: #666666; |
| | | background: #f1f2f4; |
| | | } |
| | | |
| | | .complete-confirm-btn { |
| | | color: #ffffff; |
| | | background: #1d6fe9; |
| | | } |
| | | |
| | | .complete-action-btn[disabled] { |
| | | opacity: 0.65; |
| | | } |
| | | |
| | | /* 分享弹出层样式 */ |
| | |
| | | .share-icon.moments { |
| | | background-image: url('@/static/images/work/moments.svg'); |
| | | } |
| | | |
| | | |
| | | .share-icon.link { |
| | | background-image: url('@/static/images/work/link.svg'); |