|
|
@@ -0,0 +1,1518 @@
|
|
|
+<script lang="ts" setup>
|
|
|
+import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
|
|
|
+import { FormInstance, FormRules } from 'element-plus'
|
|
|
+import { Close, CircleCheck, Plus, Delete } from '@element-plus/icons-vue'
|
|
|
+import { calculateDuration, formatDateNoTime, formatT } from '@/utils/formatTime'
|
|
|
+import { getStrDictOptions } from '@/utils/dict'
|
|
|
+import { IotDailyReportAttrsApi } from '@/api/pms/iotdailyreportattrs'
|
|
|
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
|
|
|
+import dayjs from 'dayjs'
|
|
|
+import { useDebounceFn } from '@vueuse/core'
|
|
|
+import { cloneDeep } from 'lodash-es'
|
|
|
+import { Base64 } from 'js-base64'
|
|
|
+
|
|
|
+const NON_PROD_FIELDS = [
|
|
|
+ { key: 'repairTime', label: '设备故障' },
|
|
|
+ { key: 'selfStopTime', label: '设备保养' },
|
|
|
+ { key: 'accidentTime', label: '工程质量' },
|
|
|
+ { key: 'complexityTime', label: '技术受限' },
|
|
|
+ { key: 'rectificationTime', label: '生产组织' },
|
|
|
+ { key: 'waitingStopTime', label: '不可抗力' },
|
|
|
+ { key: 'partyaDesign', label: '甲方设计' },
|
|
|
+ { key: 'partyaPrepare', label: '甲方准备' },
|
|
|
+ { key: 'partyaResource', label: '甲方资源' },
|
|
|
+ { key: 'relocationTime', label: '生产配合' },
|
|
|
+ { key: 'winterBreakTime', label: '待命' },
|
|
|
+ { key: 'otherNptTime', label: '其他非生产时间' }
|
|
|
+]
|
|
|
+
|
|
|
+const message = useMessage()
|
|
|
+
|
|
|
+const { t } = useI18n()
|
|
|
+
|
|
|
+interface Props {
|
|
|
+ visible: boolean
|
|
|
+ loadList: () => void
|
|
|
+}
|
|
|
+
|
|
|
+const props = withDefaults(defineProps<Props>(), {
|
|
|
+ visible: false
|
|
|
+})
|
|
|
+
|
|
|
+const emits = defineEmits(['update:visible'])
|
|
|
+const formType = ref<'edit' | 'approval' | 'detail' | 'time'>('edit')
|
|
|
+
|
|
|
+interface ExtProperty {
|
|
|
+ name: string
|
|
|
+ unit: string
|
|
|
+ dataType: 'double' | 'string'
|
|
|
+ actualValue: string | number
|
|
|
+ identifier: string
|
|
|
+ required?: number
|
|
|
+}
|
|
|
+
|
|
|
+interface Platform {
|
|
|
+ id: number
|
|
|
+ wellName: string
|
|
|
+ reportId: number
|
|
|
+ rdStatusLabel: string
|
|
|
+ techniqueNames: string
|
|
|
+ extProperty: Array<ExtProperty>
|
|
|
+}
|
|
|
+
|
|
|
+interface PlatformData {
|
|
|
+ rdStatus: string
|
|
|
+ techniqueIds: string[]
|
|
|
+ extProperty: Array<ExtProperty>
|
|
|
+ [key: (typeof NON_PROD_FIELDS)[number]['key']]: any
|
|
|
+}
|
|
|
+
|
|
|
+interface Fule {
|
|
|
+ deviceCode: string
|
|
|
+ deviceName: string
|
|
|
+ queryDate: number
|
|
|
+ zhbdFuel: number
|
|
|
+ customFuel: number
|
|
|
+}
|
|
|
+
|
|
|
+interface Data {
|
|
|
+ id: number
|
|
|
+ platformWell: number
|
|
|
+ platforms: Platform[]
|
|
|
+ finishedPlatforms: Platform[]
|
|
|
+ taskId: number
|
|
|
+ wellName: string
|
|
|
+ constructionStartDate: number
|
|
|
+ manufactureName: string
|
|
|
+ contractName: string
|
|
|
+ deptName: string
|
|
|
+ location: string
|
|
|
+ techniqueNames: string
|
|
|
+ workloadDesign: string
|
|
|
+ commencementDate: string
|
|
|
+ completionDate: string
|
|
|
+ constructionPeriod: string
|
|
|
+ idleTime: string
|
|
|
+ responsiblePersonNames: string
|
|
|
+ deviceNames: string
|
|
|
+ taskName: string
|
|
|
+ taskProgresses: {
|
|
|
+ createTime: string
|
|
|
+ rdStatusLabel: string
|
|
|
+ }[]
|
|
|
+ selectedDevices: {
|
|
|
+ deviceName: string
|
|
|
+ deviceCode: string
|
|
|
+ id: number
|
|
|
+ }[]
|
|
|
+ deviceIds: number[]
|
|
|
+ nextPlan: string
|
|
|
+ externalRental: string
|
|
|
+ malfunction: string
|
|
|
+ faultDowntime: number
|
|
|
+ startTime: number[]
|
|
|
+ endTime: number[]
|
|
|
+ dailyFuel: number
|
|
|
+ reportFuels: Fule[]
|
|
|
+ reportedFuels: Fule[]
|
|
|
+ createTime: number
|
|
|
+ auditStatus: number
|
|
|
+ status: number
|
|
|
+ opinion: string
|
|
|
+ companyId: number
|
|
|
+ deptId: number
|
|
|
+ reportDetails: Omit<ReportDetail, 'startTime' | 'endTime'> &
|
|
|
+ {
|
|
|
+ startTime: number[]
|
|
|
+ endTime: number[]
|
|
|
+ }[]
|
|
|
+ attachments: any[]
|
|
|
+ constructionBrief: string
|
|
|
+}
|
|
|
+
|
|
|
+interface ReportDetail {
|
|
|
+ startTime: string
|
|
|
+ endTime: string
|
|
|
+ constructionDetail: string
|
|
|
+ duration: number
|
|
|
+}
|
|
|
+
|
|
|
+interface Form {
|
|
|
+ timeRange: string[]
|
|
|
+ deviceIds: number[]
|
|
|
+ dailyFuel: number
|
|
|
+ nextPlan: string
|
|
|
+ externalRental: string
|
|
|
+ malfunction: string
|
|
|
+ faultDowntime: number
|
|
|
+ platformIds: number[]
|
|
|
+ reportFuels: Fule[]
|
|
|
+ reportDetails: ReportDetail[]
|
|
|
+ constructionBrief: string
|
|
|
+ attachments: any[]
|
|
|
+ [key: number]: PlatformData | any
|
|
|
+}
|
|
|
+
|
|
|
+const formRef = ref<FormInstance>()
|
|
|
+const rules = ref<FormRules<Form>>({
|
|
|
+ timeRange: [{ required: true, message: '请选择时间节点', trigger: 'change', type: 'array' }],
|
|
|
+ dailyFuel: [{ required: true, message: '请输入当日油耗', trigger: 'change' }],
|
|
|
+ nextPlan: [{ required: true, message: '请输入下计划', trigger: 'change' }],
|
|
|
+ reportDetails: [{ required: true, message: '请填写生产动态', type: 'array' }],
|
|
|
+ constructionBrief: [
|
|
|
+ { required: true, message: '请填写施工简报', type: 'string', trigger: ['blur', 'change'] }
|
|
|
+ ]
|
|
|
+})
|
|
|
+
|
|
|
+function noProductionTimeRule(id: number) {
|
|
|
+ const wellName =
|
|
|
+ wellOptions.value.find((item) => item.value === id)?.label ?? data.value.wellName ?? ''
|
|
|
+ return {
|
|
|
+ validator: (_rule: any, _value: any, callback: any) => {
|
|
|
+ const currentRow = form.value[id]
|
|
|
+ if (!currentRow) {
|
|
|
+ callback()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let totalTime = 0
|
|
|
+ NON_PROD_FIELDS.forEach((field) => {
|
|
|
+ const val = parseFloat(currentRow[field.key])
|
|
|
+ if (!isNaN(val)) {
|
|
|
+ totalTime += val
|
|
|
+ }
|
|
|
+ })
|
|
|
+ const fixedTotal = Number(totalTime.toFixed(2))
|
|
|
+ if (fixedTotal > 24) {
|
|
|
+ callback(new Error(`【${wellName}】总时间(${fixedTotal}h)不能超过 24 小时`))
|
|
|
+ } else {
|
|
|
+ callback()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ trigger: 'blur'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleRowValidate = (pid: number, key: string) => {
|
|
|
+ if (!formRef.value) return
|
|
|
+
|
|
|
+ const propsToValidate = NON_PROD_FIELDS.map((field) => `${pid}.${field.key}`)
|
|
|
+
|
|
|
+ if (key === 'otherNptTime') propsToValidate.push(`${pid}.otherNptReason`)
|
|
|
+
|
|
|
+ formRef.value.validateField(propsToValidate)
|
|
|
+}
|
|
|
+
|
|
|
+const data = ref<Partial<Data>>({})
|
|
|
+
|
|
|
+const original = (): Form => ({
|
|
|
+ timeRange: [],
|
|
|
+ deviceIds: [],
|
|
|
+ dailyFuel: 0,
|
|
|
+ nextPlan: '',
|
|
|
+ externalRental: '',
|
|
|
+ malfunction: '',
|
|
|
+ faultDowntime: 0,
|
|
|
+ platformIds: [],
|
|
|
+ reportDetails: [],
|
|
|
+ reportFuels: [],
|
|
|
+ constructionBrief: '',
|
|
|
+ attachments: []
|
|
|
+})
|
|
|
+
|
|
|
+const opinion = ref('')
|
|
|
+
|
|
|
+const form = ref<Form>(original())
|
|
|
+
|
|
|
+function initPlatformData(reportId: number, sourceData: any) {
|
|
|
+ form.value[reportId] = {
|
|
|
+ rdStatus: sourceData.rdStatus,
|
|
|
+ techniqueIds: sourceData.techniqueIds || [],
|
|
|
+ extProperty: (sourceData.extProperty || []).map((item) => {
|
|
|
+ if (item.dataType === 'double') {
|
|
|
+ item.actualValue = Number(item.actualValue)
|
|
|
+ }
|
|
|
+ return item
|
|
|
+ }),
|
|
|
+ otherNptReason: sourceData.otherNptReason || ''
|
|
|
+ }
|
|
|
+ NON_PROD_FIELDS.forEach((field) => {
|
|
|
+ form.value[reportId][field.key] = sourceData[field.key] || 0
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// const dailyFuel = ref(0)
|
|
|
+
|
|
|
+const initDailyFuel = () => {
|
|
|
+ const propVal = data.value.dailyFuel
|
|
|
+
|
|
|
+ const hasPropValue = propVal !== undefined && propVal !== null && !isNaN(propVal)
|
|
|
+
|
|
|
+ if (hasPropValue) {
|
|
|
+ // dailyFuel.value = propVal
|
|
|
+ form.value.dailyFuel = propVal
|
|
|
+ } else {
|
|
|
+ const list1 = data.value.reportFuels || []
|
|
|
+ const list2 = data.value.reportedFuels || []
|
|
|
+
|
|
|
+ const validList = list1.length > 0 ? list1 : list2.length > 0 ? list2 : []
|
|
|
+
|
|
|
+ form.value.reportFuels = validList.map((v) => ({
|
|
|
+ ...v,
|
|
|
+ customFuel: Number(
|
|
|
+ Number(true ? (v.customFuel ?? 0) : (v.customFuel ?? v.zhbdFuel ?? 0)).toFixed(2)
|
|
|
+ )
|
|
|
+ }))
|
|
|
+
|
|
|
+ let total = 0
|
|
|
+ form.value.reportFuels.forEach((item) => {
|
|
|
+ total += item.customFuel
|
|
|
+ })
|
|
|
+ // dailyFuel.value = total
|
|
|
+ form.value.dailyFuel = total
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const loading = ref(false)
|
|
|
+async function loadDetail(id: number) {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const res = await IotRdDailyReportApi.getIotRdDailyReport(id)
|
|
|
+ data.value = res
|
|
|
+
|
|
|
+ opinion.value = data.value.opinion || ''
|
|
|
+
|
|
|
+ form.value.deviceIds = data.value.deviceIds || []
|
|
|
+ form.value.attachments = data.value.attachments || []
|
|
|
+ form.value.nextPlan = data.value.nextPlan || ''
|
|
|
+ form.value.externalRental = data.value.externalRental || ''
|
|
|
+ form.value.malfunction = data.value.malfunction || ''
|
|
|
+ form.value.faultDowntime = data.value.faultDowntime || 0
|
|
|
+ form.value.constructionBrief = data.value.constructionBrief || ''
|
|
|
+
|
|
|
+ form.value.reportDetails = (data.value.reportDetails || []).map((item) => ({
|
|
|
+ duration: item.duration || 0,
|
|
|
+ constructionDetail: item.constructionDetail || '',
|
|
|
+ startTime: formatT(item.startTime),
|
|
|
+ endTime: formatT(item.endTime)
|
|
|
+ }))
|
|
|
+
|
|
|
+ if (!form.value.reportDetails.length) {
|
|
|
+ addReportDetailRow()
|
|
|
+ }
|
|
|
+
|
|
|
+ if (data.value.startTime && data.value.endTime) {
|
|
|
+ form.value.timeRange = [formatT(data.value.startTime), formatT(data.value.endTime)]
|
|
|
+ }
|
|
|
+
|
|
|
+ if (data.value.platformWell === 1) {
|
|
|
+ form.value.platformIds = data.value.platforms?.map((v) => v.reportId) ?? []
|
|
|
+ data.value.platforms?.forEach((p) => {
|
|
|
+ initPlatformData(p.reportId, p)
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ form.value.platformIds = [data.value.id!]
|
|
|
+ initPlatformData(data.value.id!, data.value)
|
|
|
+ }
|
|
|
+
|
|
|
+ initDailyFuel()
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const formLoading = ref(false)
|
|
|
+
|
|
|
+const submitForm = useDebounceFn(async function submitForm() {
|
|
|
+ try {
|
|
|
+ formLoading.value = true
|
|
|
+
|
|
|
+ const deleteId = wellOptions.value.filter((o) => !form.value.platformIds.includes(o.value))
|
|
|
+
|
|
|
+ deleteId.forEach((o) => {
|
|
|
+ delete form.value[o.value]
|
|
|
+ })
|
|
|
+
|
|
|
+ await formRef.value?.validate()
|
|
|
+
|
|
|
+ const copyForm = cloneDeep(form.value)
|
|
|
+
|
|
|
+ const responseData: any[] = []
|
|
|
+
|
|
|
+ form.value.platformIds.forEach((pid) => {
|
|
|
+ const platformAttachments = cloneDeep(copyForm.attachments).map((item) => {
|
|
|
+ item.bizId = pid
|
|
|
+ return item
|
|
|
+ })
|
|
|
+
|
|
|
+ let platformWell = data.value.platformWell
|
|
|
+
|
|
|
+ if (platformWell === 1) {
|
|
|
+ platformWell = pid === data.value.id ? 1 : 2
|
|
|
+ }
|
|
|
+
|
|
|
+ const platformData = {
|
|
|
+ id: pid,
|
|
|
+ timeRange: ['1970-01-01T00:00:00.008Z', '1970-01-01T00:00:00.008Z'],
|
|
|
+ projectDepartment: '',
|
|
|
+ costCenter: '',
|
|
|
+ dynamicFields: {},
|
|
|
+ platformWell,
|
|
|
+ companyId: data.value.companyId,
|
|
|
+ deptId: data.value.deptId,
|
|
|
+ startTime: copyForm.timeRange[0],
|
|
|
+ endTime: copyForm.timeRange[1],
|
|
|
+ deviceIds: copyForm.deviceIds,
|
|
|
+ dailyFuel: copyForm.dailyFuel,
|
|
|
+ nextPlan: copyForm.nextPlan,
|
|
|
+ externalRental: copyForm.externalRental,
|
|
|
+ malfunction: copyForm.malfunction,
|
|
|
+ faultDowntime: copyForm.faultDowntime,
|
|
|
+ reportFuels: copyForm.reportFuels.map((item) => ({
|
|
|
+ ...item,
|
|
|
+ reportId: pid
|
|
|
+ })),
|
|
|
+ reportDetails: copyForm.reportDetails,
|
|
|
+ constructionBrief: copyForm.constructionBrief,
|
|
|
+ attachments: platformAttachments,
|
|
|
+ ...(data.value.platformWell === 1
|
|
|
+ ? {
|
|
|
+ platformId: data.value.platforms?.find((v) => v.reportId === pid)?.id
|
|
|
+ }
|
|
|
+ : {}),
|
|
|
+ extProperty: copyForm[pid].extProperty,
|
|
|
+ rdStatus: copyForm[pid].rdStatus,
|
|
|
+ techniqueIds: copyForm[pid].techniqueIds,
|
|
|
+ ...NON_PROD_FIELDS.reduce(
|
|
|
+ (acc, field) => {
|
|
|
+ acc[field.key] = copyForm[pid][field.key] ?? 0
|
|
|
+ return acc
|
|
|
+ },
|
|
|
+ {} as Record<string, number>
|
|
|
+ ),
|
|
|
+ otherNptReason: copyForm[pid].otherNptReason || '',
|
|
|
+ nonProduct: data.value.auditStatus === 20 ? 'Y' : ''
|
|
|
+ }
|
|
|
+
|
|
|
+ responseData.push(platformData)
|
|
|
+ })
|
|
|
+
|
|
|
+ await IotRdDailyReportApi.saveBatch(responseData)
|
|
|
+ message.success(t('common.updateSuccess'))
|
|
|
+ emits('update:visible', false)
|
|
|
+ props.loadList()
|
|
|
+ } catch (error) {
|
|
|
+ console.log('提交失败:', error)
|
|
|
+ } finally {
|
|
|
+ formLoading.value = false
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+async function submitApprovalForm(auditStatus: number) {
|
|
|
+ try {
|
|
|
+ formLoading.value = true
|
|
|
+ await IotRdDailyReportApi.approveRdDailyReport({
|
|
|
+ ...data.value,
|
|
|
+ startTime: form.value.timeRange[0],
|
|
|
+ endTime: form.value.timeRange[1],
|
|
|
+ reportDetails: data.value.reportDetails?.map((item) => ({
|
|
|
+ ...item,
|
|
|
+ startTime: formatT(item.startTime),
|
|
|
+ endTime: formatT(item.endTime)
|
|
|
+ })),
|
|
|
+ id: data.value.id!,
|
|
|
+ auditStatus,
|
|
|
+ opinion: opinion.value
|
|
|
+ })
|
|
|
+ if (auditStatus === 20) {
|
|
|
+ message.success('审批通过')
|
|
|
+ } else {
|
|
|
+ message.success('审批驳回')
|
|
|
+ }
|
|
|
+ emits('update:visible', false)
|
|
|
+ props.loadList()
|
|
|
+ } catch (error) {
|
|
|
+ console.log('审批失败:', error)
|
|
|
+ } finally {
|
|
|
+ formLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleOpenForm(id: number, type: 'edit' | 'approval' | 'detail' | 'time') {
|
|
|
+ formType.value = type
|
|
|
+ form.value = original()
|
|
|
+ emits('update:visible', true)
|
|
|
+ loadDetail(id).then(() => {
|
|
|
+ nextTick(() => formRef.value?.clearValidate())
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+function handleCloseForm() {
|
|
|
+ emits('update:visible', false)
|
|
|
+}
|
|
|
+
|
|
|
+defineExpose({ handleOpenForm })
|
|
|
+
|
|
|
+const formDisabled = computed(() => (key?: string) => {
|
|
|
+ if (formType.value === 'approval' || formType.value === 'detail' || formType.value === 'time') {
|
|
|
+ if (formType.value === 'approval' && key === 'opinion') {
|
|
|
+ return data.value.auditStatus !== 10
|
|
|
+ }
|
|
|
+
|
|
|
+ if (formType.value === 'approval' && key === 'button') {
|
|
|
+ return data.value.auditStatus !== 10
|
|
|
+ }
|
|
|
+
|
|
|
+ if (
|
|
|
+ formType.value === 'time' &&
|
|
|
+ (NON_PROD_FIELDS.some((field) => field.key === key) ||
|
|
|
+ key === 'constructionBrief' ||
|
|
|
+ key === 'otherNptReason')
|
|
|
+ ) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ if (formType.value === 'time' && key === 'button') {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ if (formType.value === 'edit') {
|
|
|
+ return data.value.status !== 0
|
|
|
+ }
|
|
|
+
|
|
|
+ return false
|
|
|
+})
|
|
|
+
|
|
|
+const header = computed(function () {
|
|
|
+ const suffix =
|
|
|
+ formType.value === 'edit'
|
|
|
+ ? '日报填报'
|
|
|
+ : formType.value === 'approval'
|
|
|
+ ? '日报审批'
|
|
|
+ : formType.value === 'time'
|
|
|
+ ? '日报时效'
|
|
|
+ : '日报详情'
|
|
|
+
|
|
|
+ let title: string = ''
|
|
|
+
|
|
|
+ if (data.value.platformWell === 1) {
|
|
|
+ const platformSource: Platform[] = data.value.platforms || data.value.finishedPlatforms || []
|
|
|
+
|
|
|
+ if (platformSource.length > 0) {
|
|
|
+ const isMainWellInPlatforms = platformSource.find(
|
|
|
+ (platform) => platform.id === data.value.taskId
|
|
|
+ )
|
|
|
+
|
|
|
+ if (!isMainWellInPlatforms) {
|
|
|
+ title = platformSource[0].wellName ?? ''
|
|
|
+ } else title = isMainWellInPlatforms.wellName ?? ''
|
|
|
+ }
|
|
|
+ } else title = data.value.wellName ?? ''
|
|
|
+
|
|
|
+ return { title, suffix, date: formatDateNoTime(data.value.constructionStartDate) }
|
|
|
+})
|
|
|
+
|
|
|
+const modeNotice = computed(() => {
|
|
|
+ if (formType.value === 'approval') {
|
|
|
+ return '审批模式:所有字段均为只读'
|
|
|
+ } else if (formType.value === 'detail') {
|
|
|
+ return '详情模式:所有字段均为只读'
|
|
|
+ } else if (formType.value === 'time') {
|
|
|
+ return '时效模式:非生产时间、当日生产简报可编辑'
|
|
|
+ }
|
|
|
+ return ''
|
|
|
+})
|
|
|
+
|
|
|
+const statusClass = computed(() => {
|
|
|
+ return formType.value === 'edit'
|
|
|
+ ? 'bg-blue-50 text-blue-500 border-blue-200'
|
|
|
+ : formType.value === 'approval'
|
|
|
+ ? 'bg-orange-50 text-orange-600 border-orange-200'
|
|
|
+ : formType.value === 'time'
|
|
|
+ ? 'bg-green-50 text-green-600 border-green-200'
|
|
|
+ : 'bg-gray-100 text-gray-500 border-gray-200'
|
|
|
+})
|
|
|
+
|
|
|
+const progressList = computed(() => {
|
|
|
+ return data.value.taskProgresses ?? []
|
|
|
+})
|
|
|
+
|
|
|
+const deviceOptions = computed(() => {
|
|
|
+ return data.value.selectedDevices ?? []
|
|
|
+})
|
|
|
+
|
|
|
+const noSelectedDevices = computed(() => {
|
|
|
+ if (!deviceOptions.value) return []
|
|
|
+
|
|
|
+ return deviceOptions.value.filter((item) => !form.value.deviceIds.includes(item.id))
|
|
|
+})
|
|
|
+
|
|
|
+const wellOptions = computed(() => {
|
|
|
+ return (
|
|
|
+ data.value.platforms?.map((v) => ({
|
|
|
+ label: v.wellName,
|
|
|
+ value: v.reportId
|
|
|
+ })) ?? []
|
|
|
+ )
|
|
|
+})
|
|
|
+
|
|
|
+const rdStatusOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_STATUS)
|
|
|
+const techniqueOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_TECHNOLOGY).map((v) => {
|
|
|
+ v.value = Number(v.value) as any
|
|
|
+ return v
|
|
|
+})
|
|
|
+
|
|
|
+function handleTechniqueChange(val: string[], platformId: number) {
|
|
|
+ if (!val || val.length === 0) {
|
|
|
+ if (form.value[platformId]) form.value[platformId].extProperty = []
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ IotDailyReportAttrsApi.dailyReportAttrs({ techniqueIds: val.join(',') }).then((res) => {
|
|
|
+ const newData = res || []
|
|
|
+ const currentExtProps = form.value[platformId].extProperty
|
|
|
+
|
|
|
+ const uniqueMap = new Map()
|
|
|
+ newData.forEach((item: any) => {
|
|
|
+ const key =
|
|
|
+ item.identifier && item.unit ? `${item.identifier}-${item.unit}` : Math.random().toString()
|
|
|
+ uniqueMap.set(key, item)
|
|
|
+ })
|
|
|
+ const uniqueData = Array.from(uniqueMap.values())
|
|
|
+
|
|
|
+ const mergedData = uniqueData.map((newItem: any) => {
|
|
|
+ const newKey =
|
|
|
+ newItem.identifier && newItem.unit ? `${newItem.identifier}-${newItem.unit}` : ''
|
|
|
+ const oldItem = currentExtProps.find((old: any) => {
|
|
|
+ const oldKey = old.identifier && old.unit ? `${old.identifier}-${old.unit}` : ''
|
|
|
+ return newKey && oldKey && newKey === oldKey
|
|
|
+ })
|
|
|
+ return {
|
|
|
+ ...newItem,
|
|
|
+ actualValue: oldItem?.actualValue ?? newItem.actualValue
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ form.value[platformId].extProperty = mergedData
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const { ZmTable, ZmTableColumn } = useTableComponents<Fule | ReportDetail | Platform>()
|
|
|
+
|
|
|
+const addReportDetailRow = () => {
|
|
|
+ if (!form.value.reportDetails) {
|
|
|
+ form.value.reportDetails = []
|
|
|
+ }
|
|
|
+ form.value.reportDetails.push({
|
|
|
+ startTime: '',
|
|
|
+ endTime: '',
|
|
|
+ duration: 0,
|
|
|
+ constructionDetail: ''
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const removeReportDetailRow = (index: number) => {
|
|
|
+ if (index === 0) {
|
|
|
+ message.warning('至少填写一条生产动态')
|
|
|
+ }
|
|
|
+
|
|
|
+ form.value.reportDetails?.splice(index, 1)
|
|
|
+}
|
|
|
+
|
|
|
+const handleListChange = useDebounceFn(() => {
|
|
|
+ let total = 0
|
|
|
+ form.value.reportFuels.forEach((item) => {
|
|
|
+ total += item.customFuel
|
|
|
+ })
|
|
|
+ form.value.dailyFuel = total
|
|
|
+}, 500)
|
|
|
+
|
|
|
+const platformWorkloadData = computed(() => {
|
|
|
+ if (!data.value) return []
|
|
|
+ // 需要调整
|
|
|
+ return data.value.platforms || data.value.finishedPlatforms || []
|
|
|
+})
|
|
|
+
|
|
|
+const getWorkloadColumns = () => {
|
|
|
+ const dataSource = platformWorkloadData.value
|
|
|
+ if (!dataSource?.length) return []
|
|
|
+
|
|
|
+ const columnMap = new Map()
|
|
|
+
|
|
|
+ dataSource.forEach((platform) => {
|
|
|
+ platform.extProperty?.forEach((extProp) => {
|
|
|
+ const { identifier, name, unit } = extProp
|
|
|
+
|
|
|
+ if (!columnMap.has(identifier)) {
|
|
|
+ columnMap.set(identifier, {
|
|
|
+ prop: identifier,
|
|
|
+ label: unit ? `${name}(${unit})` : name
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ return Array.from(columnMap.values())
|
|
|
+}
|
|
|
+
|
|
|
+const getWorkloadValue = (platform: Platform, identifier: string) => {
|
|
|
+ if (!platform || !platform.extProperty) return ''
|
|
|
+ const prop = platform.extProperty.find((item) => item.identifier === identifier)
|
|
|
+ return prop ? prop.actualValue || '' : ''
|
|
|
+}
|
|
|
+
|
|
|
+const getFileType = (filename: string) => {
|
|
|
+ const ext = filename.split('.').pop()?.toLowerCase()
|
|
|
+ if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext || '')) {
|
|
|
+ return 'image'
|
|
|
+ } else if (['pdf'].includes(ext || '')) {
|
|
|
+ return 'pdf'
|
|
|
+ } else if (['doc', 'docx'].includes(ext || '')) {
|
|
|
+ return 'word'
|
|
|
+ } else if (['xls', 'xlsx'].includes(ext || '')) {
|
|
|
+ return 'excel'
|
|
|
+ } else {
|
|
|
+ return 'other'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const formatFileSize = (bytes: number) => {
|
|
|
+ if (bytes === 0) return '0 Bytes'
|
|
|
+ const k = 1024
|
|
|
+ const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
|
|
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
|
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
|
+}
|
|
|
+
|
|
|
+const handleUploadSuccess = (result: any) => {
|
|
|
+ console.log('上传成功', result)
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (!result.response) {
|
|
|
+ message.error('上传响应数据异常')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (result.response.code !== 0) {
|
|
|
+ message.error(result.response.msg || '文件上传失败')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const responseData = result.response.data
|
|
|
+
|
|
|
+ if (!responseData) {
|
|
|
+ message.error('上传数据为空')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理返回的文件列表
|
|
|
+ if (responseData.files && Array.isArray(responseData.files) && responseData.files.length > 0) {
|
|
|
+ responseData.files.forEach((file: any) => {
|
|
|
+ if (!file.filePath) {
|
|
|
+ console.warn('文件缺少 filePath:', file)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据后端返回的数据结构构建附件对象
|
|
|
+ const attachment = {
|
|
|
+ id: undefined,
|
|
|
+ category: 'daily_report',
|
|
|
+ bizId: data.value.id,
|
|
|
+ type: 'attachment',
|
|
|
+ filename: file.name || '未知文件',
|
|
|
+ fileType: getFileType(file.name),
|
|
|
+ filePath: file.filePath, //使用正确的 filePath
|
|
|
+ fileSize: formatFileSize(file.size || 0),
|
|
|
+ remark: ''
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加到附件列表
|
|
|
+ if (!form.value.attachments) {
|
|
|
+ form.value.attachments = []
|
|
|
+ }
|
|
|
+ form.value.attachments.push(attachment)
|
|
|
+ })
|
|
|
+
|
|
|
+ message.success(`成功上传 ${responseData.files.length} 个文件`)
|
|
|
+ } else {
|
|
|
+ console.warn('上传成功但没有返回文件信息')
|
|
|
+ message.warning('上传成功但未获取到文件信息')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('处理上传结果时发生错误:', error)
|
|
|
+ message.error('处理上传结果失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const removeAttachment = (index: number) => {
|
|
|
+ if (form.value.attachments && form.value.attachments.length > index) {
|
|
|
+ form.value.attachments.splice(index, 1)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const inContent = async (attachment) => {
|
|
|
+ if (!attachment || !attachment.filePath) {
|
|
|
+ message.error('附件路径不存在')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const filePath = attachment.filePath
|
|
|
+ const encodedPath = encodeURIComponent(Base64.encode(filePath))
|
|
|
+
|
|
|
+ window.open(`http://doc.deepoil.cc:8012/onlinePreview?url=${encodedPath}`)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('预览附件失败:', error)
|
|
|
+ message.error('预览附件失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <el-drawer
|
|
|
+ ref="DrawerRef"
|
|
|
+ :model-value="visible"
|
|
|
+ @update:model-value="emits('update:visible', $event)"
|
|
|
+ header-class="mb-0!"
|
|
|
+ :with-header="false"
|
|
|
+ size="50%"
|
|
|
+ :close-on-press-escape="false"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ >
|
|
|
+ <template #default>
|
|
|
+ <div id="rd-form-content" v-loading="loading" class="size-full flex flex-col gap-4">
|
|
|
+ <div class="flex justify-between items-start">
|
|
|
+ <div class="flex flex-col gap-1">
|
|
|
+ <div class="flex items-center gap-3">
|
|
|
+ <span class="text-xl font-bold text-gray-900 leading-tight">
|
|
|
+ {{ header.title ?? header.suffix }}
|
|
|
+ </span>
|
|
|
+ <div
|
|
|
+ v-if="header.title"
|
|
|
+ class="px-2 py-0.5 rounded text-xs font-medium border"
|
|
|
+ :class="statusClass"
|
|
|
+ >
|
|
|
+ {{ header.suffix }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="header.date" class="text-sm text-gray-400 font-medium">
|
|
|
+ {{ header.date }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-button link @click="handleCloseForm">
|
|
|
+ <el-icon size="24"><Close /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <el-alert
|
|
|
+ class="min-h-12"
|
|
|
+ v-if="formType !== 'edit'"
|
|
|
+ :title="modeNotice"
|
|
|
+ type="info"
|
|
|
+ :closable="false"
|
|
|
+ />
|
|
|
+
|
|
|
+ <el-alert
|
|
|
+ class="min-h-12"
|
|
|
+ v-if="formType !== 'approval' && data.opinion"
|
|
|
+ :title="data.opinion"
|
|
|
+ type="warning"
|
|
|
+ :closable="false"
|
|
|
+ />
|
|
|
+ <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
|
|
|
+
|
|
|
+ <div class="grid grid-cols-3 gap-y-8 gap-x-4">
|
|
|
+ <div class="info-item">
|
|
|
+ <label>甲方</label>
|
|
|
+ <div class="truncate">{{ data.manufactureName || '-' }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="info-item">
|
|
|
+ <label>合同号</label>
|
|
|
+ <div>{{ data.contractName || '-' }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="info-item">
|
|
|
+ <label>井号</label>
|
|
|
+ <div class="text-primary font-bold">
|
|
|
+ {{ data.wellName || data.taskName || '-' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 第二行 -->
|
|
|
+ <div class="info-item">
|
|
|
+ <label>施工队伍</label>
|
|
|
+ <div>{{ data.deptName || '-' }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="info-item">
|
|
|
+ <label>施工地点</label>
|
|
|
+ <div>{{ data.location || '-' }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="info-item">
|
|
|
+ <label>工艺</label>
|
|
|
+ <div>{{ data.techniqueNames || '-' }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 第三行 -->
|
|
|
+ <div class="info-item">
|
|
|
+ <label>设计工作量</label>
|
|
|
+ <div class="font-mono text-gray-700">{{ data.workloadDesign || '-' }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="info-item">
|
|
|
+ <label>开工日期</label>
|
|
|
+ <div class="font-mono text-gray-700">{{ data.commencementDate || '-' }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="info-item">
|
|
|
+ <label>完工日期</label>
|
|
|
+ <div class="font-mono text-gray-700">{{ data.completionDate || '-' }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 第四行 -->
|
|
|
+ <div class="info-item">
|
|
|
+ <label>施工周期 (D)</label>
|
|
|
+ <div>{{ data.constructionPeriod ?? '-' }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="info-item">
|
|
|
+ <label>停待时间 (D)</label>
|
|
|
+ <div>
|
|
|
+ {{ data.idleTime ?? '-' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="info-item">
|
|
|
+ <label>带班干部</label>
|
|
|
+ <div>{{ data.responsiblePersonNames || '-' }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="info-item col-span-3">
|
|
|
+ <label>设备配置</label>
|
|
|
+ <div>{{ data.deviceNames || '-' }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
|
|
|
+ <div v-if="progressList.length > 0">
|
|
|
+ <h3 class="text-lg font-bold text-gray-800 mb-6 flex items-center gap-2">
|
|
|
+ <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
|
|
+ 任务进度
|
|
|
+ </h3>
|
|
|
+ <el-scrollbar class="h-24!" view-class="w-full flex items-start px-2">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in progressList"
|
|
|
+ :key="index"
|
|
|
+ class="group relative flex flex-col items-center flex-1 min-w-[160px] cursor-default select-none"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-if="index !== progressList.length - 1"
|
|
|
+ class="absolute top-[34px] left-1/2 w-full h-[2px] bg-gray-100 group-hover:bg-blue-50 transition-colors duration-300"
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <span
|
|
|
+ class="text-xs font-medium text-gray-400 mb-3 font-mono transition-colors duration-300 group-hover:text-blue-500"
|
|
|
+ >
|
|
|
+ {{ item.createTime || '--' }}
|
|
|
+ </span>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="relative z-10 mb-3 transition-transform duration-300 group-hover:-translate-y-0.5"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="w-4 h-4 rounded-full border-[3px] border-white shadow-[0_0_0_2px_rgba(229,231,235,1)] bg-blue-600 group-hover:shadow-[0_0_0_4px_rgba(219,234,254,1)] group-hover:bg-blue-500 transition-all duration-300"
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <span
|
|
|
+ class="text-sm font-bold text-gray-700 px-2 text-center leading-relaxed transition-colors duration-300 group-hover:text-blue-600"
|
|
|
+ >
|
|
|
+ {{ item.rdStatusLabel || '未知状态' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </el-scrollbar>
|
|
|
+ </div>
|
|
|
+ <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
|
|
|
+ <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
|
|
|
+ <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
|
|
+ 基础信息
|
|
|
+ </h3>
|
|
|
+ <el-form
|
|
|
+ ref="formRef"
|
|
|
+ size="default"
|
|
|
+ :rules="rules"
|
|
|
+ label-position="top"
|
|
|
+ :model="form"
|
|
|
+ require-asterisk-position="right"
|
|
|
+ class="flex flex-col"
|
|
|
+ :disabled="formDisabled()"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="p-6 rounded-lg shadow border border-solid border-gray-100 grid grid-cols-2 gap-x-8"
|
|
|
+ >
|
|
|
+ <el-form-item label="时间节点" prop="timeRange">
|
|
|
+ <el-time-picker
|
|
|
+ v-model="form.timeRange"
|
|
|
+ is-range
|
|
|
+ range-separator="至"
|
|
|
+ start-placeholder="开始时间"
|
|
|
+ end-placeholder="结束时间"
|
|
|
+ placeholder="选择时间范围"
|
|
|
+ clearable
|
|
|
+ format="HH:mm"
|
|
|
+ value-format="HH:mm"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="当日油耗(L)" prop="dailyFuel">
|
|
|
+ <el-input-number
|
|
|
+ v-model="form.dailyFuel"
|
|
|
+ :min="0"
|
|
|
+ :controls="false"
|
|
|
+ align="left"
|
|
|
+ class="w-full!"
|
|
|
+ placeholder="请输入当日油耗"
|
|
|
+ >
|
|
|
+ <template #suffix>升(L)</template>
|
|
|
+ </el-input-number>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item class="col-span-2" label="施工设备" prop="deviceIds">
|
|
|
+ <el-select
|
|
|
+ v-model="form.deviceIds"
|
|
|
+ multiple
|
|
|
+ placeholder="请选择施工设备"
|
|
|
+ clearable
|
|
|
+ filterable
|
|
|
+ tag-type="primary"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in deviceOptions"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.deviceName"
|
|
|
+ :value="item.id"
|
|
|
+ >
|
|
|
+ <span class="font-medium">{{ item.deviceCode + ' - ' + item.deviceName }}</span>
|
|
|
+ </el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item class="col-span-2" label="闲置/未施工设备">
|
|
|
+ <div
|
|
|
+ class="w-full min-h-[40px] p-3 rounded bg-gray-50 border border-gray-200 border-dashed transition-all"
|
|
|
+ >
|
|
|
+ <template v-if="noSelectedDevices.length > 0">
|
|
|
+ <div class="flex flex-wrap gap-2">
|
|
|
+ <el-tag
|
|
|
+ v-for="device in noSelectedDevices"
|
|
|
+ :key="device.id"
|
|
|
+ type="info"
|
|
|
+ effect="plain"
|
|
|
+ class="!border-gray-300"
|
|
|
+ >
|
|
|
+ {{ device.deviceName }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <div class="text-gray-400 text-sm flex items-center">
|
|
|
+ <el-icon class="mr-1"><CircleCheck /></el-icon>
|
|
|
+ 所有设备均已投入施工
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item class="col-span-2" label="下步工作计划" prop="nextPlan">
|
|
|
+ <el-input
|
|
|
+ v-model="form.nextPlan"
|
|
|
+ type="textarea"
|
|
|
+ :autosize="{ minRows: 2 }"
|
|
|
+ resize="none"
|
|
|
+ show-word-limit
|
|
|
+ :maxlength="1000"
|
|
|
+ placeholder="请输入下步工作计划"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item class="col-span-2" label="外组设备" prop="externalRental">
|
|
|
+ <el-input
|
|
|
+ v-model="form.externalRental"
|
|
|
+ type="textarea"
|
|
|
+ :autosize="{ minRows: 2 }"
|
|
|
+ resize="none"
|
|
|
+ show-word-limit
|
|
|
+ :maxlength="1000"
|
|
|
+ placeholder="请输入外组设备"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item class="col-span-2" label="故障情况" prop="malfunction">
|
|
|
+ <el-input
|
|
|
+ v-model="form.malfunction"
|
|
|
+ type="textarea"
|
|
|
+ :autosize="{ minRows: 2 }"
|
|
|
+ show-word-limit
|
|
|
+ resize="none"
|
|
|
+ :maxlength="1000"
|
|
|
+ placeholder="请输入故障情况"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="故障误工(H)" prop="faultDowntime">
|
|
|
+ <el-input-number
|
|
|
+ v-model="form.faultDowntime"
|
|
|
+ :min="0"
|
|
|
+ :controls="false"
|
|
|
+ align="left"
|
|
|
+ class="w-full!"
|
|
|
+ >
|
|
|
+ <template #suffix>小时(H)</template>
|
|
|
+ </el-input-number>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="附件">
|
|
|
+ <FileUpload
|
|
|
+ v-if="formType === 'edit'"
|
|
|
+ ref="fileUploadRef"
|
|
|
+ :device-id="undefined"
|
|
|
+ :show-folder-button="false"
|
|
|
+ @upload-success="handleUploadSuccess"
|
|
|
+ />
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-if="form.attachments && form.attachments.length > 0"
|
|
|
+ class="attachment-container"
|
|
|
+ >
|
|
|
+ <div class="attachment-list">
|
|
|
+ <div
|
|
|
+ v-for="(attachment, index) in form.attachments"
|
|
|
+ :key="attachment.id || index"
|
|
|
+ class="attachment-item"
|
|
|
+ >
|
|
|
+ <a class="attachment-name" @click="inContent(attachment)">
|
|
|
+ {{ attachment.filename }}
|
|
|
+ </a>
|
|
|
+ <el-button
|
|
|
+ :disabled="formDisabled()"
|
|
|
+ type="danger"
|
|
|
+ link
|
|
|
+ size="small"
|
|
|
+ @click="removeAttachment(index)"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-else-if="!form.attachments || form.attachments.length === 0"
|
|
|
+ class="no-attachment"
|
|
|
+ >
|
|
|
+ 无附件
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <div class="col-span-2 flex items-center justify-between mb-6">
|
|
|
+ <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
|
|
|
+ <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
|
|
+ 生产动态
|
|
|
+ </h3>
|
|
|
+ <el-button type="primary" link :icon="Plus" @click="addReportDetailRow">
|
|
|
+ 添加一行
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-form-item prop="reportDetails" class="col-span-2">
|
|
|
+ <ZmTable :data="form.reportDetails" :loading="false" class="col-span-2">
|
|
|
+ <ZmTableColumn
|
|
|
+ :width="105"
|
|
|
+ label="日期"
|
|
|
+ cover-formatter
|
|
|
+ :real-value="
|
|
|
+ () => (data.createTime ? dayjs(data.createTime).format('YYYY-MM-DD') : '')
|
|
|
+ "
|
|
|
+ />
|
|
|
+ <ZmTableColumn :width="160" label="开始时间" prop="startTime">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-form-item
|
|
|
+ v-if="$index >= 0"
|
|
|
+ class="mb-0!"
|
|
|
+ :prop="`reportDetails.${$index}.startTime`"
|
|
|
+ :rules="{ required: true, message: '请选择开始时间', trigger: 'change' }"
|
|
|
+ >
|
|
|
+ <el-time-picker
|
|
|
+ v-model="row.startTime"
|
|
|
+ placeholder="选择开始时间"
|
|
|
+ clearable
|
|
|
+ format="HH:mm"
|
|
|
+ value-format="HH:mm"
|
|
|
+ class="w-full!"
|
|
|
+ @change="calculateDuration(row)"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+ </ZmTableColumn>
|
|
|
+ <ZmTableColumn :width="160" label="结束时间" prop="endTime">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-form-item
|
|
|
+ v-if="$index >= 0"
|
|
|
+ class="mb-0!"
|
|
|
+ :prop="`reportDetails.${$index}.endTime`"
|
|
|
+ :rules="{ required: true, message: '请选择结束时间', trigger: 'change' }"
|
|
|
+ >
|
|
|
+ <el-time-picker
|
|
|
+ v-model="row.endTime"
|
|
|
+ placeholder="选择结束时间"
|
|
|
+ clearable
|
|
|
+ format="HH:mm"
|
|
|
+ value-format="HH:mm"
|
|
|
+ class="w-full!"
|
|
|
+ @change="calculateDuration(row)"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+ </ZmTableColumn>
|
|
|
+ <ZmTableColumn :width="80" label="时长(H)" prop="duration" />
|
|
|
+ <ZmTableColumn label="施工详情" prop="constructionDetail">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-form-item
|
|
|
+ v-if="$index >= 0"
|
|
|
+ class="mb-0!"
|
|
|
+ :prop="`reportDetails.${$index}.constructionDetail`"
|
|
|
+ :rules="{ required: true, message: '请输入施工详情', trigger: 'change' }"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="row.constructionDetail"
|
|
|
+ placeholder="输入施工详情"
|
|
|
+ type="textarea"
|
|
|
+ :autosize="{ minRows: 1 }"
|
|
|
+ resize="none"
|
|
|
+ show-word-limit
|
|
|
+ :maxlength="1000"
|
|
|
+ class="w-full!"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+ </ZmTableColumn>
|
|
|
+ <ZmTableColumn label="操作" :width="80" fixed="right" align="center">
|
|
|
+ <template #default="{ $index }">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="danger"
|
|
|
+ :icon="Delete"
|
|
|
+ @click="removeReportDetailRow($index)"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </ZmTableColumn>
|
|
|
+ </ZmTable>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item class="col-span-2" label="当日施工简报" prop="constructionBrief">
|
|
|
+ <el-input
|
|
|
+ v-model="form.constructionBrief"
|
|
|
+ type="textarea"
|
|
|
+ :autosize="{ minRows: 2 }"
|
|
|
+ show-word-limit
|
|
|
+ resize="none"
|
|
|
+ :maxlength="1000"
|
|
|
+ placeholder="请输入当日施工简报"
|
|
|
+ :disabled="formDisabled('constructionBrief')"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ <el-form-item
|
|
|
+ class="mt-4 mb-0!"
|
|
|
+ v-if="data.platformWell === 1"
|
|
|
+ label="平台井"
|
|
|
+ prop="platformIds"
|
|
|
+ >
|
|
|
+ <el-select
|
|
|
+ v-model="form.platformIds"
|
|
|
+ multiple
|
|
|
+ :options="wellOptions"
|
|
|
+ placeholder="请选择平台井"
|
|
|
+ clearable
|
|
|
+ filterable
|
|
|
+ collapse-tags
|
|
|
+ collapse-tags-tooltip
|
|
|
+ :max-collapse-tags="5"
|
|
|
+ tag-type="primary"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <template v-for="pid in form.platformIds" :key="pid">
|
|
|
+ <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
|
|
|
+ <div
|
|
|
+ class="p-6 rounded-lg shadow border border-solid border-gray-100 grid grid-cols-4 gap-x-8"
|
|
|
+ >
|
|
|
+ <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 col-span-4 mb-6">
|
|
|
+ <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
|
|
+ {{ wellOptions.find((item) => item.value === pid)?.label ?? data.wellName ?? '' }}
|
|
|
+ </h3>
|
|
|
+ <el-form-item
|
|
|
+ label="施工状态"
|
|
|
+ :prop="`${pid}.rdStatus`"
|
|
|
+ :rules="{ required: true, message: '请选择施工状态', trigger: 'change' }"
|
|
|
+ class="col-span-2"
|
|
|
+ >
|
|
|
+ <el-select
|
|
|
+ v-model="form[pid].rdStatus"
|
|
|
+ :options="rdStatusOptions"
|
|
|
+ placeholder="请选择"
|
|
|
+ class="w-full"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item
|
|
|
+ label="施工工艺"
|
|
|
+ :prop="`${pid}.techniqueIds`"
|
|
|
+ :rules="{
|
|
|
+ required: true,
|
|
|
+ message: '请选择施工工艺',
|
|
|
+ trigger: 'change',
|
|
|
+ type: 'array'
|
|
|
+ }"
|
|
|
+ class="col-span-2"
|
|
|
+ >
|
|
|
+ <el-select
|
|
|
+ v-model="form[pid].techniqueIds"
|
|
|
+ :options="techniqueOptions"
|
|
|
+ multiple
|
|
|
+ collapse-tags
|
|
|
+ collapse-tags-tooltip
|
|
|
+ placeholder="请选择"
|
|
|
+ class="w-full"
|
|
|
+ @change="(val) => handleTechniqueChange(val, pid)"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <template v-if="form[pid] && form[pid].extProperty">
|
|
|
+ <el-form-item
|
|
|
+ v-for="(attr, idx) in form[pid].extProperty"
|
|
|
+ :key="idx"
|
|
|
+ :label="`${attr.name}${attr.unit ? '(' + attr.unit + ')' : ''}`"
|
|
|
+ :prop="`${pid}.extProperty.${idx}.actualValue`"
|
|
|
+ :rules="
|
|
|
+ attr.required === 1
|
|
|
+ ? [{ required: true, message: `请输入${attr.name}`, trigger: 'blur' }]
|
|
|
+ : []
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <el-input-number
|
|
|
+ v-if="attr.dataType === 'double'"
|
|
|
+ v-model="attr.actualValue"
|
|
|
+ :controls="false"
|
|
|
+ class="w-full!"
|
|
|
+ align="left"
|
|
|
+ placeholder="请输入"
|
|
|
+ />
|
|
|
+ <el-input
|
|
|
+ type="textarea"
|
|
|
+ v-else
|
|
|
+ v-model="attr.actualValue"
|
|
|
+ placeholder="请输入"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-divider content-position="left" class="m-0! mt-2! mb-6! border-2! col-span-4">
|
|
|
+ 非生产时间
|
|
|
+ </el-divider>
|
|
|
+
|
|
|
+ <el-form-item
|
|
|
+ v-for="field in NON_PROD_FIELDS"
|
|
|
+ :key="field.key"
|
|
|
+ :label="field.label"
|
|
|
+ :prop="`${pid}.${field.key}`"
|
|
|
+ :rules="noProductionTimeRule(pid)"
|
|
|
+ >
|
|
|
+ <el-input-number
|
|
|
+ v-model="form[pid][field.key]"
|
|
|
+ :min="0"
|
|
|
+ :max="24"
|
|
|
+ :controls="false"
|
|
|
+ class="w-full!"
|
|
|
+ align="left"
|
|
|
+ @blur="handleRowValidate(pid, field.key)"
|
|
|
+ :disabled="formDisabled(field.key)"
|
|
|
+ >
|
|
|
+ <template #suffix>小时(H)</template>
|
|
|
+ </el-input-number>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item
|
|
|
+ class="col-span-4"
|
|
|
+ label="其他非生产原因"
|
|
|
+ :prop="`${pid}.otherNptReason`"
|
|
|
+ :rules="
|
|
|
+ form[pid].otherNptTime > 0
|
|
|
+ ? { required: true, message: '请填写原因', trigger: 'change' }
|
|
|
+ : {}
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="form[pid].otherNptReason"
|
|
|
+ type="textarea"
|
|
|
+ :autosize="{ minRows: 2 }"
|
|
|
+ resize="none"
|
|
|
+ show-word-limit
|
|
|
+ :maxlength="1000"
|
|
|
+ placeholder="当'其他非生产时间'大于0时必填"
|
|
|
+ :disabled="formDisabled('otherNptReason')"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
|
|
|
+
|
|
|
+ <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 mb-6">
|
|
|
+ <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
|
|
+ 油耗信息
|
|
|
+ </h3>
|
|
|
+
|
|
|
+ <ZmTable :data="form.reportFuels" :loading="false">
|
|
|
+ <ZmTableColumn label="设备编号" :width="160" prop="deviceCode" />
|
|
|
+ <ZmTableColumn label="设备名称" prop="deviceName" />
|
|
|
+ <ZmTableColumn
|
|
|
+ label="发生日期"
|
|
|
+ prop="queryDate"
|
|
|
+ :width="110"
|
|
|
+ cover-formatter
|
|
|
+ :real-value="
|
|
|
+ (row) => (row.queryDate ? dayjs(row.queryDate).format('YYYY-MM-DD') : '')
|
|
|
+ "
|
|
|
+ />
|
|
|
+ <ZmTableColumn label="中航北斗油耗(L)" :width="140" prop="zhbdFuel" />
|
|
|
+ <ZmTableColumn label="实际油耗(L)" prop="customFuel">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-form-item class="mb-0!" :prop="`reportFuels.${$index}.customFuel`">
|
|
|
+ <el-input-number
|
|
|
+ v-model="row.customFuel"
|
|
|
+ :min="0"
|
|
|
+ :controls="false"
|
|
|
+ class="w-full!"
|
|
|
+ align="left"
|
|
|
+ @input="handleListChange"
|
|
|
+ >
|
|
|
+ <template #suffix> L </template>
|
|
|
+ </el-input-number>
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+ </ZmTableColumn>
|
|
|
+ </ZmTable>
|
|
|
+
|
|
|
+ <template v-if="platformWorkloadData.length > 0">
|
|
|
+ <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
|
|
|
+
|
|
|
+ <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 mb-6">
|
|
|
+ <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
|
|
|
+ 平台井工作量
|
|
|
+ </h3>
|
|
|
+
|
|
|
+ <ZmTable :data="platformWorkloadData" :loading="false">
|
|
|
+ <ZmTableColumn label="井号" prop="wellName" />
|
|
|
+ <ZmTableColumn label="施工状态" prop="rdStatusLabel" />
|
|
|
+ <ZmTableColumn label="施工工艺" prop="techniqueNames" />
|
|
|
+ <template v-for="{ prop, label } in getWorkloadColumns()" :key="prop">
|
|
|
+ <ZmTableColumn
|
|
|
+ :label="label"
|
|
|
+ :prop="prop"
|
|
|
+ cover-formatter
|
|
|
+ :real-value="(row) => getWorkloadValue(row, prop)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </ZmTable>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-if="formType === 'approval'">
|
|
|
+ <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
|
|
|
+
|
|
|
+ <el-form-item label="审批意见" prop="opinion">
|
|
|
+ <el-input
|
|
|
+ v-model="opinion"
|
|
|
+ type="textarea"
|
|
|
+ :autosize="{ minRows: 2 }"
|
|
|
+ resize="none"
|
|
|
+ show-word-limit
|
|
|
+ :maxlength="1000"
|
|
|
+ placeholder="请输入审批意见"
|
|
|
+ :disabled="formDisabled('opinion')"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+ </el-form>
|
|
|
+ <el-backtop target=".el-drawer__body" :right="100" :bottom="100" :visibility-height="40" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template #footer>
|
|
|
+ <div v-if="formType === 'edit' || formType === 'time'">
|
|
|
+ <el-button
|
|
|
+ size="default"
|
|
|
+ type="primary"
|
|
|
+ @click="submitForm"
|
|
|
+ :disabled="formDisabled('button')"
|
|
|
+ :loading="formLoading"
|
|
|
+ >
|
|
|
+ 确 定
|
|
|
+ </el-button>
|
|
|
+ <el-button size="default" @click="emits('update:visible', false)">取 消</el-button>
|
|
|
+ </div>
|
|
|
+ <div v-if="formType === 'approval'">
|
|
|
+ <el-button
|
|
|
+ size="default"
|
|
|
+ type="primary"
|
|
|
+ @click="submitApprovalForm(20)"
|
|
|
+ :disabled="formDisabled('button')"
|
|
|
+ :loading="formLoading"
|
|
|
+ >
|
|
|
+ 审批通过
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ size="default"
|
|
|
+ type="danger"
|
|
|
+ @click="submitApprovalForm(30)"
|
|
|
+ :disabled="formDisabled('button')"
|
|
|
+ :loading="formLoading"
|
|
|
+ >
|
|
|
+ 审批拒绝
|
|
|
+ </el-button>
|
|
|
+ <el-button size="default" @click="emits('update:visible', false)">取 消</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-drawer>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.info-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 0.25rem;
|
|
|
+
|
|
|
+ label {
|
|
|
+ font-size: 0.75rem;
|
|
|
+ font-weight: 500;
|
|
|
+ line-height: 1rem;
|
|
|
+ color: #9ca3af;
|
|
|
+ }
|
|
|
+
|
|
|
+ > div {
|
|
|
+ min-height: 1.25rem;
|
|
|
+ font-size: 0.875rem;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 1.25rem;
|
|
|
+ color: #1f2937;
|
|
|
+ word-break: break-all;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-form-item__label) {
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-scrollbar__bar.is-horizontal) {
|
|
|
+ height: 4px;
|
|
|
+}
|
|
|
+</style>
|