| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507 |
- <script lang="ts" setup>
- import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
- import { FormInstance, FormRules } from 'element-plus'
- import { 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()
- 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: formType.value === 'time',
- 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: formType.value === 'time' ? 'Y' : ''
- }
- responseData.push(platformData)
- })
- await IotRdDailyReportApi.saveBatch(responseData)
- message.success(t('common.updateSuccess'))
- handleCancel()
- } 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('审批驳回')
- }
- handleCancel()
- } catch (error) {
- console.log('审批失败:', error)
- } finally {
- formLoading.value = false
- }
- }
- function handleOpenForm(id: number, type: 'edit' | 'approval' | 'detail' | 'time') {
- console.log('id :>> ', id)
- formType.value = type
- form.value = original()
- loadDetail(id).then(() => {
- nextTick(() => formRef.value?.clearValidate())
- })
- }
- const route = useRoute()
- const router = useRouter()
- function handleCancel() {
- router.push({
- path: (route.query.backpath ?? '') as any
- })
- }
- onMounted(() => {
- if (Object.keys(route.query).length > 0) {
- handleOpenForm(
- Number(route.query.id),
- route.query.mode as 'edit' | 'approval' | 'detail' | 'time'
- )
- }
- })
- 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') {
- if (
- NON_PROD_FIELDS.some((field) => field.key === key) ||
- key === 'constructionBrief' ||
- key === 'otherNptReason'
- ) {
- return true
- }
- 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('至少填写一条生产动态')
- return
- }
- 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>
- <div
- class="bg-white rounded-xl shadow size-full flex flex-col gap-4 p-4 mb-12"
- v-loading="loading"
- >
- <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)]!" />
- <el-form
- ref="formRef"
- size="default"
- :rules="rules"
- label-position="top"
- :model="form"
- require-asterisk-position="right"
- class="flex flex-col"
- :disabled="formDisabled()"
- >
- <el-form-item 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, pindex) in form.platformIds" :key="pid">
- <el-divider v-if="pindex !== 0" 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-form-item class="mt-4 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>
- <el-divider class="mt-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> -->
- <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>
- <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="120"
- 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 }"
- show-word-limit
- :maxlength="2000"
- 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="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>
- <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>
- </div>
- <div
- class="h-16 z-10 flex items-center justify-end px-6 shadow bg-white absolute bottom-0 left-0 w-full border-solid border-0 border-t-1 border-gray-200"
- >
- <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="handleCancel">取 消</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="handleCancel">取 消</el-button>
- </div>
- </div>
- </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>
|