FillDailyReportForm.vue 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507
  1. <script lang="ts" setup>
  2. import { IotRdDailyReportApi } from '@/api/pms/iotrddailyreport'
  3. import { FormInstance, FormRules } from 'element-plus'
  4. import { CircleCheck, Plus, Delete } from '@element-plus/icons-vue'
  5. import { calculateDuration, formatDateNoTime, formatT } from '@/utils/formatTime'
  6. import { getStrDictOptions } from '@/utils/dict'
  7. import { IotDailyReportAttrsApi } from '@/api/pms/iotdailyreportattrs'
  8. import { useTableComponents } from '@/components/ZmTable/useTableComponents'
  9. import dayjs from 'dayjs'
  10. import { useDebounceFn } from '@vueuse/core'
  11. import { cloneDeep } from 'lodash-es'
  12. import { Base64 } from 'js-base64'
  13. const NON_PROD_FIELDS = [
  14. { key: 'repairTime', label: '设备故障' },
  15. { key: 'selfStopTime', label: '设备保养' },
  16. { key: 'accidentTime', label: '工程质量' },
  17. { key: 'complexityTime', label: '技术受限' },
  18. { key: 'rectificationTime', label: '生产组织' },
  19. { key: 'waitingStopTime', label: '不可抗力' },
  20. { key: 'partyaDesign', label: '甲方设计' },
  21. { key: 'partyaPrepare', label: '甲方准备' },
  22. { key: 'partyaResource', label: '甲方资源' },
  23. { key: 'relocationTime', label: '生产配合' },
  24. { key: 'winterBreakTime', label: '待命' },
  25. { key: 'otherNptTime', label: '其他非生产时间' }
  26. ]
  27. const message = useMessage()
  28. const { t } = useI18n()
  29. const formType = ref<'edit' | 'approval' | 'detail' | 'time'>('edit')
  30. interface ExtProperty {
  31. name: string
  32. unit: string
  33. dataType: 'double' | 'string'
  34. actualValue: string | number
  35. identifier: string
  36. required?: number
  37. }
  38. interface Platform {
  39. id: number
  40. wellName: string
  41. reportId: number
  42. rdStatusLabel: string
  43. techniqueNames: string
  44. extProperty: Array<ExtProperty>
  45. }
  46. interface PlatformData {
  47. rdStatus: string
  48. techniqueIds: string[]
  49. extProperty: Array<ExtProperty>
  50. [key: (typeof NON_PROD_FIELDS)[number]['key']]: any
  51. }
  52. interface Fule {
  53. deviceCode: string
  54. deviceName: string
  55. queryDate: number
  56. zhbdFuel: number
  57. customFuel: number
  58. }
  59. interface Data {
  60. id: number
  61. platformWell: number
  62. platforms: Platform[]
  63. finishedPlatforms: Platform[]
  64. taskId: number
  65. wellName: string
  66. constructionStartDate: number
  67. manufactureName: string
  68. contractName: string
  69. deptName: string
  70. location: string
  71. techniqueNames: string
  72. workloadDesign: string
  73. commencementDate: string
  74. completionDate: string
  75. constructionPeriod: string
  76. idleTime: string
  77. responsiblePersonNames: string
  78. deviceNames: string
  79. taskName: string
  80. taskProgresses: {
  81. createTime: string
  82. rdStatusLabel: string
  83. }[]
  84. selectedDevices: {
  85. deviceName: string
  86. deviceCode: string
  87. id: number
  88. }[]
  89. deviceIds: number[]
  90. nextPlan: string
  91. externalRental: string
  92. malfunction: string
  93. faultDowntime: number
  94. startTime: number[]
  95. endTime: number[]
  96. dailyFuel: number
  97. reportFuels: Fule[]
  98. reportedFuels: Fule[]
  99. createTime: number
  100. auditStatus: number
  101. status: number
  102. opinion: string
  103. companyId: number
  104. deptId: number
  105. reportDetails: Omit<ReportDetail, 'startTime' | 'endTime'> &
  106. {
  107. startTime: number[]
  108. endTime: number[]
  109. }[]
  110. attachments: any[]
  111. constructionBrief: string
  112. }
  113. interface ReportDetail {
  114. startTime: string
  115. endTime: string
  116. constructionDetail: string
  117. duration: number
  118. }
  119. interface Form {
  120. timeRange: string[]
  121. deviceIds: number[]
  122. dailyFuel: number
  123. nextPlan: string
  124. externalRental: string
  125. malfunction: string
  126. faultDowntime: number
  127. platformIds: number[]
  128. reportFuels: Fule[]
  129. reportDetails: ReportDetail[]
  130. constructionBrief: string
  131. attachments: any[]
  132. [key: number]: PlatformData | any
  133. }
  134. const formRef = ref<FormInstance>()
  135. const rules = ref<FormRules<Form>>({
  136. timeRange: [{ required: true, message: '请选择时间节点', trigger: 'change', type: 'array' }],
  137. dailyFuel: [{ required: true, message: '请输入当日油耗', trigger: 'change' }],
  138. nextPlan: [{ required: true, message: '请输入下计划', trigger: 'change' }],
  139. reportDetails: [{ required: true, message: '请填写生产动态', type: 'array' }],
  140. constructionBrief: [
  141. {
  142. required: formType.value === 'time',
  143. message: '请填写施工简报',
  144. type: 'string',
  145. trigger: ['blur', 'change']
  146. }
  147. ]
  148. })
  149. function noProductionTimeRule(id: number) {
  150. const wellName =
  151. wellOptions.value.find((item) => item.value === id)?.label ?? data.value.wellName ?? ''
  152. return {
  153. validator: (_rule: any, _value: any, callback: any) => {
  154. const currentRow = form.value[id]
  155. if (!currentRow) {
  156. callback()
  157. return
  158. }
  159. let totalTime = 0
  160. NON_PROD_FIELDS.forEach((field) => {
  161. const val = parseFloat(currentRow[field.key])
  162. if (!isNaN(val)) {
  163. totalTime += val
  164. }
  165. })
  166. const fixedTotal = Number(totalTime.toFixed(2))
  167. if (fixedTotal > 24) {
  168. callback(new Error(`【${wellName}】总时间(${fixedTotal}h)不能超过 24 小时`))
  169. } else {
  170. callback()
  171. }
  172. },
  173. trigger: 'blur'
  174. }
  175. }
  176. const handleRowValidate = (pid: number, key: string) => {
  177. if (!formRef.value) return
  178. const propsToValidate = NON_PROD_FIELDS.map((field) => `${pid}.${field.key}`)
  179. if (key === 'otherNptTime') propsToValidate.push(`${pid}.otherNptReason`)
  180. formRef.value.validateField(propsToValidate)
  181. }
  182. const data = ref<Partial<Data>>({})
  183. const original = (): Form => ({
  184. timeRange: [],
  185. deviceIds: [],
  186. dailyFuel: 0,
  187. nextPlan: '',
  188. externalRental: '',
  189. malfunction: '',
  190. faultDowntime: 0,
  191. platformIds: [],
  192. reportDetails: [],
  193. reportFuels: [],
  194. constructionBrief: '',
  195. attachments: []
  196. })
  197. const opinion = ref('')
  198. const form = ref<Form>(original())
  199. function initPlatformData(reportId: number, sourceData: any) {
  200. form.value[reportId] = {
  201. rdStatus: sourceData.rdStatus,
  202. techniqueIds: sourceData.techniqueIds || [],
  203. extProperty: (sourceData.extProperty || []).map((item) => {
  204. if (item.dataType === 'double') {
  205. item.actualValue = Number(item.actualValue)
  206. }
  207. return item
  208. }),
  209. otherNptReason: sourceData.otherNptReason || ''
  210. }
  211. NON_PROD_FIELDS.forEach((field) => {
  212. form.value[reportId][field.key] = sourceData[field.key] || 0
  213. })
  214. }
  215. // const dailyFuel = ref(0)
  216. const initDailyFuel = () => {
  217. const propVal = data.value.dailyFuel
  218. const hasPropValue = propVal !== undefined && propVal !== null && !isNaN(propVal)
  219. if (hasPropValue) {
  220. // dailyFuel.value = propVal
  221. form.value.dailyFuel = propVal
  222. } else {
  223. const list1 = data.value.reportFuels || []
  224. const list2 = data.value.reportedFuels || []
  225. const validList = list1.length > 0 ? list1 : list2.length > 0 ? list2 : []
  226. form.value.reportFuels = validList.map((v) => ({
  227. ...v,
  228. customFuel: Number(
  229. Number(true ? (v.customFuel ?? 0) : (v.customFuel ?? v.zhbdFuel ?? 0)).toFixed(2)
  230. )
  231. }))
  232. let total = 0
  233. form.value.reportFuels.forEach((item) => {
  234. total += item.customFuel
  235. })
  236. // dailyFuel.value = total
  237. form.value.dailyFuel = total
  238. }
  239. }
  240. const loading = ref(false)
  241. async function loadDetail(id: number) {
  242. loading.value = true
  243. try {
  244. const res = await IotRdDailyReportApi.getIotRdDailyReport(id)
  245. data.value = res
  246. opinion.value = data.value.opinion || ''
  247. form.value.deviceIds = data.value.deviceIds || []
  248. form.value.attachments = data.value.attachments || []
  249. form.value.nextPlan = data.value.nextPlan || ''
  250. form.value.externalRental = data.value.externalRental || ''
  251. form.value.malfunction = data.value.malfunction || ''
  252. form.value.faultDowntime = data.value.faultDowntime || 0
  253. form.value.constructionBrief = data.value.constructionBrief || ''
  254. form.value.reportDetails = (data.value.reportDetails || []).map((item) => ({
  255. duration: item.duration || 0,
  256. constructionDetail: item.constructionDetail || '',
  257. startTime: formatT(item.startTime),
  258. endTime: formatT(item.endTime)
  259. }))
  260. if (!form.value.reportDetails.length) {
  261. addReportDetailRow()
  262. }
  263. if (data.value.startTime && data.value.endTime) {
  264. form.value.timeRange = [formatT(data.value.startTime), formatT(data.value.endTime)]
  265. }
  266. if (data.value.platformWell === 1) {
  267. form.value.platformIds = data.value.platforms?.map((v) => v.reportId) ?? []
  268. data.value.platforms?.forEach((p) => {
  269. initPlatformData(p.reportId, p)
  270. })
  271. } else {
  272. form.value.platformIds = [data.value.id!]
  273. initPlatformData(data.value.id!, data.value)
  274. }
  275. initDailyFuel()
  276. } finally {
  277. loading.value = false
  278. }
  279. }
  280. const formLoading = ref(false)
  281. const submitForm = useDebounceFn(async function submitForm() {
  282. try {
  283. formLoading.value = true
  284. const deleteId = wellOptions.value.filter((o) => !form.value.platformIds.includes(o.value))
  285. deleteId.forEach((o) => {
  286. delete form.value[o.value]
  287. })
  288. await formRef.value?.validate()
  289. const copyForm = cloneDeep(form.value)
  290. const responseData: any[] = []
  291. form.value.platformIds.forEach((pid) => {
  292. const platformAttachments = cloneDeep(copyForm.attachments).map((item) => {
  293. item.bizId = pid
  294. return item
  295. })
  296. let platformWell = data.value.platformWell
  297. if (platformWell === 1) {
  298. platformWell = pid === data.value.id ? 1 : 2
  299. }
  300. const platformData = {
  301. id: pid,
  302. timeRange: ['1970-01-01T00:00:00.008Z', '1970-01-01T00:00:00.008Z'],
  303. projectDepartment: '',
  304. costCenter: '',
  305. dynamicFields: {},
  306. platformWell,
  307. companyId: data.value.companyId,
  308. deptId: data.value.deptId,
  309. startTime: copyForm.timeRange[0],
  310. endTime: copyForm.timeRange[1],
  311. deviceIds: copyForm.deviceIds,
  312. dailyFuel: copyForm.dailyFuel,
  313. nextPlan: copyForm.nextPlan,
  314. externalRental: copyForm.externalRental,
  315. malfunction: copyForm.malfunction,
  316. faultDowntime: copyForm.faultDowntime,
  317. reportFuels: copyForm.reportFuels.map((item) => ({
  318. ...item,
  319. reportId: pid
  320. })),
  321. reportDetails: copyForm.reportDetails,
  322. constructionBrief: copyForm.constructionBrief,
  323. attachments: platformAttachments,
  324. ...(data.value.platformWell === 1
  325. ? {
  326. platformId: data.value.platforms?.find((v) => v.reportId === pid)?.id
  327. }
  328. : {}),
  329. extProperty: copyForm[pid].extProperty,
  330. rdStatus: copyForm[pid].rdStatus,
  331. techniqueIds: copyForm[pid].techniqueIds,
  332. ...NON_PROD_FIELDS.reduce(
  333. (acc, field) => {
  334. acc[field.key] = copyForm[pid][field.key] ?? 0
  335. return acc
  336. },
  337. {} as Record<string, number>
  338. ),
  339. otherNptReason: copyForm[pid].otherNptReason || '',
  340. nonProduct: formType.value === 'time' ? 'Y' : ''
  341. }
  342. responseData.push(platformData)
  343. })
  344. await IotRdDailyReportApi.saveBatch(responseData)
  345. message.success(t('common.updateSuccess'))
  346. handleCancel()
  347. } catch (error) {
  348. console.log('提交失败:', error)
  349. } finally {
  350. formLoading.value = false
  351. }
  352. })
  353. async function submitApprovalForm(auditStatus: number) {
  354. try {
  355. formLoading.value = true
  356. await IotRdDailyReportApi.approveRdDailyReport({
  357. ...data.value,
  358. startTime: form.value.timeRange[0],
  359. endTime: form.value.timeRange[1],
  360. reportDetails: data.value.reportDetails?.map((item) => ({
  361. ...item,
  362. startTime: formatT(item.startTime),
  363. endTime: formatT(item.endTime)
  364. })),
  365. id: data.value.id!,
  366. auditStatus,
  367. opinion: opinion.value
  368. })
  369. if (auditStatus === 20) {
  370. message.success('审批通过')
  371. } else {
  372. message.success('审批驳回')
  373. }
  374. handleCancel()
  375. } catch (error) {
  376. console.log('审批失败:', error)
  377. } finally {
  378. formLoading.value = false
  379. }
  380. }
  381. function handleOpenForm(id: number, type: 'edit' | 'approval' | 'detail' | 'time') {
  382. console.log('id :>> ', id)
  383. formType.value = type
  384. form.value = original()
  385. loadDetail(id).then(() => {
  386. nextTick(() => formRef.value?.clearValidate())
  387. })
  388. }
  389. const route = useRoute()
  390. const router = useRouter()
  391. function handleCancel() {
  392. router.push({
  393. path: (route.query.backpath ?? '') as any
  394. })
  395. }
  396. onMounted(() => {
  397. if (Object.keys(route.query).length > 0) {
  398. handleOpenForm(
  399. Number(route.query.id),
  400. route.query.mode as 'edit' | 'approval' | 'detail' | 'time'
  401. )
  402. }
  403. })
  404. const formDisabled = computed(() => (key?: string) => {
  405. if (formType.value === 'approval' || formType.value === 'detail' || formType.value === 'time') {
  406. if (formType.value === 'approval' && key === 'opinion') {
  407. return data.value.auditStatus !== 10
  408. }
  409. if (formType.value === 'approval' && key === 'button') {
  410. return data.value.auditStatus !== 10
  411. }
  412. if (
  413. formType.value === 'time' &&
  414. (NON_PROD_FIELDS.some((field) => field.key === key) ||
  415. key === 'constructionBrief' ||
  416. key === 'otherNptReason')
  417. ) {
  418. return false
  419. }
  420. if (formType.value === 'time' && key === 'button') {
  421. return false
  422. }
  423. return true
  424. }
  425. if (formType.value === 'edit') {
  426. if (
  427. NON_PROD_FIELDS.some((field) => field.key === key) ||
  428. key === 'constructionBrief' ||
  429. key === 'otherNptReason'
  430. ) {
  431. return true
  432. }
  433. return data.value.status !== 0
  434. }
  435. return false
  436. })
  437. const header = computed(function () {
  438. const suffix =
  439. formType.value === 'edit'
  440. ? '日报填报'
  441. : formType.value === 'approval'
  442. ? '日报审批'
  443. : formType.value === 'time'
  444. ? '日报时效'
  445. : '日报详情'
  446. let title: string = ''
  447. if (data.value.platformWell === 1) {
  448. const platformSource: Platform[] = data.value.platforms || data.value.finishedPlatforms || []
  449. if (platformSource.length > 0) {
  450. const isMainWellInPlatforms = platformSource.find(
  451. (platform) => platform.id === data.value.taskId
  452. )
  453. if (!isMainWellInPlatforms) {
  454. title = platformSource[0].wellName ?? ''
  455. } else title = isMainWellInPlatforms.wellName ?? ''
  456. }
  457. } else title = data.value.wellName ?? ''
  458. return { title, suffix, date: formatDateNoTime(data.value.constructionStartDate) }
  459. })
  460. const modeNotice = computed(() => {
  461. if (formType.value === 'approval') {
  462. return '审批模式:所有字段均为只读'
  463. } else if (formType.value === 'detail') {
  464. return '详情模式:所有字段均为只读'
  465. } else if (formType.value === 'time') {
  466. return '时效模式:非生产时间、当日生产简报可编辑'
  467. }
  468. return ''
  469. })
  470. const statusClass = computed(() => {
  471. return formType.value === 'edit'
  472. ? 'bg-blue-50 text-blue-500 border-blue-200'
  473. : formType.value === 'approval'
  474. ? 'bg-orange-50 text-orange-600 border-orange-200'
  475. : formType.value === 'time'
  476. ? 'bg-green-50 text-green-600 border-green-200'
  477. : 'bg-gray-100 text-gray-500 border-gray-200'
  478. })
  479. const progressList = computed(() => {
  480. return data.value.taskProgresses ?? []
  481. })
  482. const deviceOptions = computed(() => {
  483. return data.value.selectedDevices ?? []
  484. })
  485. const noSelectedDevices = computed(() => {
  486. if (!deviceOptions.value) return []
  487. return deviceOptions.value.filter((item) => !form.value.deviceIds.includes(item.id))
  488. })
  489. const wellOptions = computed(() => {
  490. return (
  491. data.value.platforms?.map((v) => ({
  492. label: v.wellName,
  493. value: v.reportId
  494. })) ?? []
  495. )
  496. })
  497. const rdStatusOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_STATUS)
  498. const techniqueOptions = getStrDictOptions(DICT_TYPE.PMS_PROJECT_RD_TECHNOLOGY).map((v) => {
  499. v.value = Number(v.value) as any
  500. return v
  501. })
  502. function handleTechniqueChange(val: string[], platformId: number) {
  503. if (!val || val.length === 0) {
  504. if (form.value[platformId]) form.value[platformId].extProperty = []
  505. return
  506. }
  507. IotDailyReportAttrsApi.dailyReportAttrs({ techniqueIds: val.join(',') }).then((res) => {
  508. const newData = res || []
  509. const currentExtProps = form.value[platformId].extProperty
  510. const uniqueMap = new Map()
  511. newData.forEach((item: any) => {
  512. const key =
  513. item.identifier && item.unit ? `${item.identifier}-${item.unit}` : Math.random().toString()
  514. uniqueMap.set(key, item)
  515. })
  516. const uniqueData = Array.from(uniqueMap.values())
  517. const mergedData = uniqueData.map((newItem: any) => {
  518. const newKey =
  519. newItem.identifier && newItem.unit ? `${newItem.identifier}-${newItem.unit}` : ''
  520. const oldItem = currentExtProps.find((old: any) => {
  521. const oldKey = old.identifier && old.unit ? `${old.identifier}-${old.unit}` : ''
  522. return newKey && oldKey && newKey === oldKey
  523. })
  524. return {
  525. ...newItem,
  526. actualValue: oldItem?.actualValue ?? newItem.actualValue
  527. }
  528. })
  529. form.value[platformId].extProperty = mergedData
  530. })
  531. }
  532. const { ZmTable, ZmTableColumn } = useTableComponents<Fule | ReportDetail | Platform>()
  533. const addReportDetailRow = () => {
  534. if (!form.value.reportDetails) {
  535. form.value.reportDetails = []
  536. }
  537. form.value.reportDetails.push({
  538. startTime: '',
  539. endTime: '',
  540. duration: 0,
  541. constructionDetail: ''
  542. })
  543. }
  544. const removeReportDetailRow = (index: number) => {
  545. if (index === 0) {
  546. message.warning('至少填写一条生产动态')
  547. return
  548. }
  549. form.value.reportDetails?.splice(index, 1)
  550. }
  551. const handleListChange = useDebounceFn(() => {
  552. let total = 0
  553. form.value.reportFuels.forEach((item) => {
  554. total += item.customFuel
  555. })
  556. form.value.dailyFuel = total
  557. }, 500)
  558. const platformWorkloadData = computed(() => {
  559. if (!data.value) return []
  560. // 需要调整
  561. return data.value.platforms || data.value.finishedPlatforms || []
  562. })
  563. const getWorkloadColumns = () => {
  564. const dataSource = platformWorkloadData.value
  565. if (!dataSource?.length) return []
  566. const columnMap = new Map()
  567. dataSource.forEach((platform) => {
  568. platform.extProperty?.forEach((extProp) => {
  569. const { identifier, name, unit } = extProp
  570. if (!columnMap.has(identifier)) {
  571. columnMap.set(identifier, {
  572. prop: identifier,
  573. label: unit ? `${name}(${unit})` : name
  574. })
  575. }
  576. })
  577. })
  578. return Array.from(columnMap.values())
  579. }
  580. const getWorkloadValue = (platform: Platform, identifier: string) => {
  581. if (!platform || !platform.extProperty) return ''
  582. const prop = platform.extProperty.find((item) => item.identifier === identifier)
  583. return prop ? prop.actualValue || '' : ''
  584. }
  585. const getFileType = (filename: string) => {
  586. const ext = filename.split('.').pop()?.toLowerCase()
  587. if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(ext || '')) {
  588. return 'image'
  589. } else if (['pdf'].includes(ext || '')) {
  590. return 'pdf'
  591. } else if (['doc', 'docx'].includes(ext || '')) {
  592. return 'word'
  593. } else if (['xls', 'xlsx'].includes(ext || '')) {
  594. return 'excel'
  595. } else {
  596. return 'other'
  597. }
  598. }
  599. const formatFileSize = (bytes: number) => {
  600. if (bytes === 0) return '0 Bytes'
  601. const k = 1024
  602. const sizes = ['Bytes', 'KB', 'MB', 'GB']
  603. const i = Math.floor(Math.log(bytes) / Math.log(k))
  604. return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
  605. }
  606. const handleUploadSuccess = (result: any) => {
  607. console.log('上传成功', result)
  608. try {
  609. if (!result.response) {
  610. message.error('上传响应数据异常')
  611. return
  612. }
  613. if (result.response.code !== 0) {
  614. message.error(result.response.msg || '文件上传失败')
  615. return
  616. }
  617. const responseData = result.response.data
  618. if (!responseData) {
  619. message.error('上传数据为空')
  620. return
  621. }
  622. // 处理返回的文件列表
  623. if (responseData.files && Array.isArray(responseData.files) && responseData.files.length > 0) {
  624. responseData.files.forEach((file: any) => {
  625. if (!file.filePath) {
  626. console.warn('文件缺少 filePath:', file)
  627. return
  628. }
  629. // 根据后端返回的数据结构构建附件对象
  630. const attachment = {
  631. id: undefined,
  632. category: 'daily_report',
  633. bizId: data.value.id,
  634. type: 'attachment',
  635. filename: file.name || '未知文件',
  636. fileType: getFileType(file.name),
  637. filePath: file.filePath, //使用正确的 filePath
  638. fileSize: formatFileSize(file.size || 0),
  639. remark: ''
  640. }
  641. // 添加到附件列表
  642. if (!form.value.attachments) {
  643. form.value.attachments = []
  644. }
  645. form.value.attachments.push(attachment)
  646. })
  647. message.success(`成功上传 ${responseData.files.length} 个文件`)
  648. } else {
  649. console.warn('上传成功但没有返回文件信息')
  650. message.warning('上传成功但未获取到文件信息')
  651. }
  652. } catch (error) {
  653. console.error('处理上传结果时发生错误:', error)
  654. message.error('处理上传结果失败')
  655. }
  656. }
  657. const removeAttachment = (index: number) => {
  658. if (form.value.attachments && form.value.attachments.length > index) {
  659. form.value.attachments.splice(index, 1)
  660. }
  661. }
  662. const inContent = async (attachment) => {
  663. if (!attachment || !attachment.filePath) {
  664. message.error('附件路径不存在')
  665. return
  666. }
  667. try {
  668. const filePath = attachment.filePath
  669. const encodedPath = encodeURIComponent(Base64.encode(filePath))
  670. window.open(`http://doc.deepoil.cc:8012/onlinePreview?url=${encodedPath}`)
  671. } catch (error) {
  672. console.error('预览附件失败:', error)
  673. message.error('预览附件失败')
  674. }
  675. }
  676. </script>
  677. <template>
  678. <div
  679. class="bg-white rounded-xl shadow size-full flex flex-col gap-4 p-4 mb-12"
  680. v-loading="loading"
  681. >
  682. <div class="flex justify-between items-start">
  683. <div class="flex flex-col gap-1">
  684. <div class="flex items-center gap-3">
  685. <span class="text-xl font-bold text-gray-900 leading-tight">
  686. {{ header.title ?? header.suffix }}
  687. </span>
  688. <div
  689. v-if="header.title"
  690. class="px-2 py-0.5 rounded text-xs font-medium border"
  691. :class="statusClass"
  692. >
  693. {{ header.suffix }}
  694. </div>
  695. </div>
  696. <div v-if="header.date" class="text-sm text-gray-400 font-medium">
  697. {{ header.date }}
  698. </div>
  699. </div>
  700. <!-- <el-button link @click="handleCloseForm">
  701. <el-icon size="24"><Close /></el-icon>
  702. </el-button> -->
  703. </div>
  704. <el-alert
  705. class="min-h-12"
  706. v-if="formType !== 'edit'"
  707. :title="modeNotice"
  708. type="info"
  709. :closable="false"
  710. />
  711. <el-alert
  712. class="min-h-12"
  713. v-if="formType !== 'approval' && data.opinion"
  714. :title="data.opinion"
  715. type="warning"
  716. :closable="false"
  717. />
  718. <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
  719. <div class="grid grid-cols-3 gap-y-8 gap-x-4">
  720. <div class="info-item">
  721. <label>甲方</label>
  722. <div class="truncate">{{ data.manufactureName || '-' }}</div>
  723. </div>
  724. <div class="info-item">
  725. <label>合同号</label>
  726. <div>{{ data.contractName || '-' }}</div>
  727. </div>
  728. <div class="info-item">
  729. <label>井号</label>
  730. <div class="text-primary font-bold">
  731. {{ data.wellName || data.taskName || '-' }}
  732. </div>
  733. </div>
  734. <!-- 第二行 -->
  735. <div class="info-item">
  736. <label>施工队伍</label>
  737. <div>{{ data.deptName || '-' }}</div>
  738. </div>
  739. <div class="info-item">
  740. <label>施工地点</label>
  741. <div>{{ data.location || '-' }}</div>
  742. </div>
  743. <div class="info-item">
  744. <label>工艺</label>
  745. <div>{{ data.techniqueNames || '-' }}</div>
  746. </div>
  747. <!-- 第三行 -->
  748. <div class="info-item">
  749. <label>设计工作量</label>
  750. <div class="font-mono text-gray-700">{{ data.workloadDesign || '-' }}</div>
  751. </div>
  752. <div class="info-item">
  753. <label>开工日期</label>
  754. <div class="font-mono text-gray-700">{{ data.commencementDate || '-' }}</div>
  755. </div>
  756. <div class="info-item">
  757. <label>完工日期</label>
  758. <div class="font-mono text-gray-700">{{ data.completionDate || '-' }}</div>
  759. </div>
  760. <!-- 第四行 -->
  761. <div class="info-item">
  762. <label>施工周期 (D)</label>
  763. <div>{{ data.constructionPeriod ?? '-' }}</div>
  764. </div>
  765. <div class="info-item">
  766. <label>停待时间 (D)</label>
  767. <div>
  768. {{ data.idleTime ?? '-' }}
  769. </div>
  770. </div>
  771. <div class="info-item">
  772. <label>带班干部</label>
  773. <div>{{ data.responsiblePersonNames || '-' }}</div>
  774. </div>
  775. <div class="info-item col-span-3">
  776. <label>设备配置</label>
  777. <div>{{ data.deviceNames || '-' }}</div>
  778. </div>
  779. </div>
  780. <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
  781. <div v-if="progressList.length > 0">
  782. <h3 class="text-lg font-bold text-gray-800 mb-6 flex items-center gap-2">
  783. <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
  784. 任务进度
  785. </h3>
  786. <el-scrollbar class="h-24!" view-class="w-full flex items-start px-2">
  787. <div
  788. v-for="(item, index) in progressList"
  789. :key="index"
  790. class="group relative flex flex-col items-center flex-1 min-w-[160px] cursor-default select-none"
  791. >
  792. <div
  793. v-if="index !== progressList.length - 1"
  794. class="absolute top-[34px] left-1/2 w-full h-[2px] bg-gray-100 group-hover:bg-blue-50 transition-colors duration-300"
  795. >
  796. </div>
  797. <span
  798. class="text-xs font-medium text-gray-400 mb-3 font-mono transition-colors duration-300 group-hover:text-blue-500"
  799. >
  800. {{ item.createTime || '--' }}
  801. </span>
  802. <div
  803. class="relative z-10 mb-3 transition-transform duration-300 group-hover:-translate-y-0.5"
  804. >
  805. <div
  806. 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"
  807. >
  808. </div>
  809. </div>
  810. <span
  811. class="text-sm font-bold text-gray-700 px-2 text-center leading-relaxed transition-colors duration-300 group-hover:text-blue-600"
  812. >
  813. {{ item.rdStatusLabel || '未知状态' }}
  814. </span>
  815. </div>
  816. </el-scrollbar>
  817. </div>
  818. <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
  819. <el-form
  820. ref="formRef"
  821. size="default"
  822. :rules="rules"
  823. label-position="top"
  824. :model="form"
  825. require-asterisk-position="right"
  826. class="flex flex-col"
  827. :disabled="formDisabled()"
  828. >
  829. <el-form-item v-if="data.platformWell === 1" label="平台井" prop="platformIds">
  830. <el-select
  831. v-model="form.platformIds"
  832. multiple
  833. :options="wellOptions"
  834. placeholder="请选择平台井"
  835. clearable
  836. filterable
  837. collapse-tags
  838. collapse-tags-tooltip
  839. :max-collapse-tags="5"
  840. tag-type="primary"
  841. />
  842. </el-form-item>
  843. <template v-for="(pid, pindex) in form.platformIds" :key="pid">
  844. <el-divider v-if="pindex !== 0" class="my-6 border-2! border-[var(--el-color-primary)]!" />
  845. <div
  846. class="p-6 rounded-lg shadow border border-solid border-gray-100 grid grid-cols-4 gap-x-8"
  847. >
  848. <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 col-span-4 mb-6">
  849. <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
  850. {{ wellOptions.find((item) => item.value === pid)?.label ?? data.wellName ?? '' }}
  851. </h3>
  852. <el-form-item
  853. label="施工状态"
  854. :prop="`${pid}.rdStatus`"
  855. :rules="{ required: true, message: '请选择施工状态', trigger: 'change' }"
  856. class="col-span-2"
  857. >
  858. <el-select
  859. v-model="form[pid].rdStatus"
  860. :options="rdStatusOptions"
  861. placeholder="请选择"
  862. class="w-full"
  863. clearable
  864. />
  865. </el-form-item>
  866. <el-form-item
  867. label="施工工艺"
  868. :prop="`${pid}.techniqueIds`"
  869. :rules="{
  870. required: true,
  871. message: '请选择施工工艺',
  872. trigger: 'change',
  873. type: 'array'
  874. }"
  875. class="col-span-2"
  876. >
  877. <el-select
  878. v-model="form[pid].techniqueIds"
  879. :options="techniqueOptions"
  880. multiple
  881. collapse-tags
  882. collapse-tags-tooltip
  883. placeholder="请选择"
  884. class="w-full"
  885. @change="(val) => handleTechniqueChange(val, pid)"
  886. clearable
  887. />
  888. </el-form-item>
  889. <template v-if="form[pid] && form[pid].extProperty">
  890. <el-form-item
  891. v-for="(attr, idx) in form[pid].extProperty"
  892. :key="idx"
  893. :label="`${attr.name}${attr.unit ? '(' + attr.unit + ')' : ''}`"
  894. :prop="`${pid}.extProperty.${idx}.actualValue`"
  895. :rules="
  896. attr.required === 1
  897. ? [{ required: true, message: `请输入${attr.name}`, trigger: 'blur' }]
  898. : []
  899. "
  900. >
  901. <el-input-number
  902. v-if="attr.dataType === 'double'"
  903. v-model="attr.actualValue"
  904. :controls="false"
  905. class="w-full!"
  906. align="left"
  907. placeholder="请输入"
  908. />
  909. <el-input type="textarea" v-else v-model="attr.actualValue" placeholder="请输入" />
  910. </el-form-item>
  911. </template>
  912. <el-divider content-position="left" class="m-0! mt-2! mb-6! border-2! col-span-4">
  913. 非生产时间
  914. </el-divider>
  915. <el-form-item
  916. v-for="field in NON_PROD_FIELDS"
  917. :key="field.key"
  918. :label="field.label"
  919. :prop="`${pid}.${field.key}`"
  920. :rules="noProductionTimeRule(pid)"
  921. >
  922. <el-input-number
  923. v-model="form[pid][field.key]"
  924. :min="0"
  925. :max="24"
  926. :controls="false"
  927. class="w-full!"
  928. align="left"
  929. @blur="handleRowValidate(pid, field.key)"
  930. :disabled="formDisabled(field.key)"
  931. >
  932. <template #suffix>小时(H)</template>
  933. </el-input-number>
  934. </el-form-item>
  935. <el-form-item
  936. class="col-span-4"
  937. label="其他非生产原因"
  938. :prop="`${pid}.otherNptReason`"
  939. :rules="
  940. form[pid].otherNptTime > 0
  941. ? { required: true, message: '请填写原因', trigger: 'change' }
  942. : {}
  943. "
  944. >
  945. <el-input
  946. v-model="form[pid].otherNptReason"
  947. type="textarea"
  948. :autosize="{ minRows: 2 }"
  949. resize="none"
  950. show-word-limit
  951. :maxlength="1000"
  952. placeholder="当'其他非生产时间'大于0时必填"
  953. :disabled="formDisabled('otherNptReason')"
  954. />
  955. </el-form-item>
  956. </div>
  957. </template>
  958. <el-form-item class="mt-4 col-span-2" label="当日施工简报" prop="constructionBrief">
  959. <el-input
  960. v-model="form.constructionBrief"
  961. type="textarea"
  962. :autosize="{ minRows: 2 }"
  963. show-word-limit
  964. resize="none"
  965. :maxlength="1000"
  966. placeholder="请输入当日施工简报"
  967. :disabled="formDisabled('constructionBrief')"
  968. />
  969. </el-form-item>
  970. <el-divider class="mt-0! border-2! border-[var(--el-color-primary)]!" />
  971. <!-- <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
  972. <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
  973. 基础信息
  974. </h3> -->
  975. <div
  976. class="p-6 rounded-lg shadow border border-solid border-gray-100 grid grid-cols-2 gap-x-8"
  977. >
  978. <el-form-item label="时间节点" prop="timeRange">
  979. <el-time-picker
  980. v-model="form.timeRange"
  981. is-range
  982. range-separator="至"
  983. start-placeholder="开始时间"
  984. end-placeholder="结束时间"
  985. placeholder="选择时间范围"
  986. clearable
  987. format="HH:mm"
  988. value-format="HH:mm"
  989. />
  990. </el-form-item>
  991. <el-form-item label="当日油耗(L)" prop="dailyFuel">
  992. <el-input-number
  993. v-model="form.dailyFuel"
  994. :min="0"
  995. :controls="false"
  996. align="left"
  997. class="w-full!"
  998. placeholder="请输入当日油耗"
  999. >
  1000. <template #suffix>升(L)</template>
  1001. </el-input-number>
  1002. </el-form-item>
  1003. <el-form-item class="col-span-2" label="施工设备" prop="deviceIds">
  1004. <el-select
  1005. v-model="form.deviceIds"
  1006. multiple
  1007. placeholder="请选择施工设备"
  1008. clearable
  1009. filterable
  1010. tag-type="primary"
  1011. >
  1012. <el-option
  1013. v-for="item in deviceOptions"
  1014. :key="item.id"
  1015. :label="item.deviceName"
  1016. :value="item.id"
  1017. >
  1018. <span class="font-medium">{{ item.deviceCode + ' - ' + item.deviceName }}</span>
  1019. </el-option>
  1020. </el-select>
  1021. </el-form-item>
  1022. <el-form-item class="col-span-2" label="闲置/未施工设备">
  1023. <div
  1024. class="w-full min-h-[40px] p-3 rounded bg-gray-50 border border-gray-200 border-dashed transition-all"
  1025. >
  1026. <template v-if="noSelectedDevices.length > 0">
  1027. <div class="flex flex-wrap gap-2">
  1028. <el-tag
  1029. v-for="device in noSelectedDevices"
  1030. :key="device.id"
  1031. type="info"
  1032. effect="plain"
  1033. class="!border-gray-300"
  1034. >
  1035. {{ device.deviceName }}
  1036. </el-tag>
  1037. </div>
  1038. </template>
  1039. <template v-else>
  1040. <div class="text-gray-400 text-sm flex items-center">
  1041. <el-icon class="mr-1"><CircleCheck /></el-icon>
  1042. 所有设备均已投入施工
  1043. </div>
  1044. </template>
  1045. </div>
  1046. </el-form-item>
  1047. <div class="col-span-2 flex items-center justify-between mb-6">
  1048. <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
  1049. <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
  1050. 生产动态
  1051. </h3>
  1052. <el-button type="primary" link :icon="Plus" @click="addReportDetailRow">
  1053. 添加一行
  1054. </el-button>
  1055. </div>
  1056. <el-form-item prop="reportDetails" class="col-span-2">
  1057. <ZmTable :data="form.reportDetails" :loading="false" class="col-span-2">
  1058. <ZmTableColumn
  1059. :width="120"
  1060. label="日期"
  1061. cover-formatter
  1062. :real-value="
  1063. () => (data.createTime ? dayjs(data.createTime).format('YYYY-MM-DD') : '')
  1064. "
  1065. />
  1066. <ZmTableColumn :width="160" label="开始时间" prop="startTime">
  1067. <template #default="{ row, $index }">
  1068. <el-form-item
  1069. v-if="$index >= 0"
  1070. class="mb-0!"
  1071. :prop="`reportDetails.${$index}.startTime`"
  1072. :rules="{ required: true, message: '请选择开始时间', trigger: 'change' }"
  1073. >
  1074. <el-time-picker
  1075. v-model="row.startTime"
  1076. placeholder="选择开始时间"
  1077. clearable
  1078. format="HH:mm"
  1079. value-format="HH:mm"
  1080. class="w-full!"
  1081. @change="calculateDuration(row)"
  1082. />
  1083. </el-form-item>
  1084. </template>
  1085. </ZmTableColumn>
  1086. <ZmTableColumn :width="160" label="结束时间" prop="endTime">
  1087. <template #default="{ row, $index }">
  1088. <el-form-item
  1089. v-if="$index >= 0"
  1090. class="mb-0!"
  1091. :prop="`reportDetails.${$index}.endTime`"
  1092. :rules="{ required: true, message: '请选择结束时间', trigger: 'change' }"
  1093. >
  1094. <el-time-picker
  1095. v-model="row.endTime"
  1096. placeholder="选择结束时间"
  1097. clearable
  1098. format="HH:mm"
  1099. value-format="HH:mm"
  1100. class="w-full!"
  1101. @change="calculateDuration(row)"
  1102. />
  1103. </el-form-item>
  1104. </template>
  1105. </ZmTableColumn>
  1106. <ZmTableColumn :width="80" label="时长(H)" prop="duration" />
  1107. <ZmTableColumn label="施工详情" prop="constructionDetail">
  1108. <template #default="{ row, $index }">
  1109. <el-form-item
  1110. v-if="$index >= 0"
  1111. class="mb-0!"
  1112. :prop="`reportDetails.${$index}.constructionDetail`"
  1113. :rules="{ required: true, message: '请输入施工详情', trigger: 'change' }"
  1114. >
  1115. <el-input
  1116. v-model="row.constructionDetail"
  1117. placeholder="输入施工详情"
  1118. type="textarea"
  1119. :autosize="{ minRows: 1 }"
  1120. show-word-limit
  1121. :maxlength="2000"
  1122. class="w-full!"
  1123. />
  1124. </el-form-item>
  1125. </template>
  1126. </ZmTableColumn>
  1127. <ZmTableColumn label="操作" :width="80" fixed="right" align="center">
  1128. <template #default="{ $index }">
  1129. <el-button link type="danger" :icon="Delete" @click="removeReportDetailRow($index)">
  1130. 删除
  1131. </el-button>
  1132. </template>
  1133. </ZmTableColumn>
  1134. </ZmTable>
  1135. </el-form-item>
  1136. <el-form-item class="col-span-2" label="下步工作计划" prop="nextPlan">
  1137. <el-input
  1138. v-model="form.nextPlan"
  1139. type="textarea"
  1140. :autosize="{ minRows: 2 }"
  1141. resize="none"
  1142. show-word-limit
  1143. :maxlength="1000"
  1144. placeholder="请输入下步工作计划"
  1145. />
  1146. </el-form-item>
  1147. <el-form-item class="col-span-2" label="外组设备" prop="externalRental">
  1148. <el-input
  1149. v-model="form.externalRental"
  1150. type="textarea"
  1151. :autosize="{ minRows: 2 }"
  1152. resize="none"
  1153. show-word-limit
  1154. :maxlength="1000"
  1155. placeholder="请输入外组设备"
  1156. />
  1157. </el-form-item>
  1158. <el-form-item class="col-span-2" label="故障情况" prop="malfunction">
  1159. <el-input
  1160. v-model="form.malfunction"
  1161. type="textarea"
  1162. :autosize="{ minRows: 2 }"
  1163. show-word-limit
  1164. resize="none"
  1165. :maxlength="1000"
  1166. placeholder="请输入故障情况"
  1167. />
  1168. </el-form-item>
  1169. <el-form-item label="故障误工(H)" prop="faultDowntime">
  1170. <el-input-number
  1171. v-model="form.faultDowntime"
  1172. :min="0"
  1173. :controls="false"
  1174. align="left"
  1175. class="w-full!"
  1176. >
  1177. <template #suffix>小时(H)</template>
  1178. </el-input-number>
  1179. </el-form-item>
  1180. <el-form-item label="附件">
  1181. <FileUpload
  1182. v-if="formType === 'edit'"
  1183. ref="fileUploadRef"
  1184. :device-id="undefined"
  1185. :show-folder-button="false"
  1186. @upload-success="handleUploadSuccess"
  1187. />
  1188. <div v-if="form.attachments && form.attachments.length > 0" class="attachment-container">
  1189. <div class="attachment-list">
  1190. <div
  1191. v-for="(attachment, index) in form.attachments"
  1192. :key="attachment.id || index"
  1193. class="attachment-item"
  1194. >
  1195. <a class="attachment-name" @click="inContent(attachment)">
  1196. {{ attachment.filename }}
  1197. </a>
  1198. <el-button
  1199. :disabled="formDisabled()"
  1200. type="danger"
  1201. link
  1202. size="small"
  1203. @click="removeAttachment(index)"
  1204. >
  1205. 删除
  1206. </el-button>
  1207. </div>
  1208. </div>
  1209. </div>
  1210. <div v-else-if="!form.attachments || form.attachments.length === 0" class="no-attachment">
  1211. 无附件
  1212. </div>
  1213. </el-form-item>
  1214. </div>
  1215. <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
  1216. <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 mb-6">
  1217. <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
  1218. 油耗信息
  1219. </h3>
  1220. <ZmTable :data="form.reportFuels" :loading="false">
  1221. <ZmTableColumn label="设备编号" :width="160" prop="deviceCode" />
  1222. <ZmTableColumn label="设备名称" prop="deviceName" />
  1223. <ZmTableColumn
  1224. label="发生日期"
  1225. prop="queryDate"
  1226. :width="110"
  1227. cover-formatter
  1228. :real-value="(row) => (row.queryDate ? dayjs(row.queryDate).format('YYYY-MM-DD') : '')"
  1229. />
  1230. <ZmTableColumn label="中航北斗油耗(L)" :width="140" prop="zhbdFuel" />
  1231. <ZmTableColumn label="实际油耗(L)" prop="customFuel">
  1232. <template #default="{ row, $index }">
  1233. <el-form-item class="mb-0!" :prop="`reportFuels.${$index}.customFuel`">
  1234. <el-input-number
  1235. v-model="row.customFuel"
  1236. :min="0"
  1237. :controls="false"
  1238. class="w-full!"
  1239. align="left"
  1240. @input="handleListChange"
  1241. >
  1242. <template #suffix> L </template>
  1243. </el-input-number>
  1244. </el-form-item>
  1245. </template>
  1246. </ZmTableColumn>
  1247. </ZmTable>
  1248. <template v-if="platformWorkloadData.length > 0">
  1249. <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
  1250. <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 mb-6">
  1251. <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
  1252. 平台井工作量
  1253. </h3>
  1254. <ZmTable :data="platformWorkloadData" :loading="false">
  1255. <ZmTableColumn label="井号" prop="wellName" />
  1256. <ZmTableColumn label="施工状态" prop="rdStatusLabel" />
  1257. <ZmTableColumn label="施工工艺" prop="techniqueNames" />
  1258. <template v-for="{ prop, label } in getWorkloadColumns()" :key="prop">
  1259. <ZmTableColumn
  1260. :label="label"
  1261. :prop="prop"
  1262. cover-formatter
  1263. :real-value="(row) => getWorkloadValue(row, prop)"
  1264. />
  1265. </template>
  1266. </ZmTable>
  1267. </template>
  1268. <template v-if="formType === 'approval'">
  1269. <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
  1270. <el-form-item label="审批意见" prop="opinion">
  1271. <el-input
  1272. v-model="opinion"
  1273. type="textarea"
  1274. :autosize="{ minRows: 2 }"
  1275. resize="none"
  1276. show-word-limit
  1277. :maxlength="1000"
  1278. placeholder="请输入审批意见"
  1279. :disabled="formDisabled('opinion')"
  1280. />
  1281. </el-form-item>
  1282. </template>
  1283. </el-form>
  1284. </div>
  1285. <div
  1286. 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"
  1287. >
  1288. <div v-if="formType === 'edit' || formType === 'time'">
  1289. <el-button
  1290. size="default"
  1291. type="primary"
  1292. @click="submitForm"
  1293. :disabled="formDisabled('button')"
  1294. :loading="formLoading"
  1295. >
  1296. 确 定
  1297. </el-button>
  1298. <el-button size="default" @click="handleCancel">取 消</el-button>
  1299. </div>
  1300. <div v-if="formType === 'approval'">
  1301. <el-button
  1302. size="default"
  1303. type="primary"
  1304. @click="submitApprovalForm(20)"
  1305. :disabled="formDisabled('button')"
  1306. :loading="formLoading"
  1307. >
  1308. 审批通过
  1309. </el-button>
  1310. <el-button
  1311. size="default"
  1312. type="danger"
  1313. @click="submitApprovalForm(30)"
  1314. :disabled="formDisabled('button')"
  1315. :loading="formLoading"
  1316. >
  1317. 审批拒绝
  1318. </el-button>
  1319. <el-button size="default" @click="handleCancel">取 消</el-button>
  1320. </div>
  1321. </div>
  1322. </template>
  1323. <style scoped>
  1324. .info-item {
  1325. display: flex;
  1326. flex-direction: column;
  1327. gap: 0.25rem;
  1328. label {
  1329. font-size: 0.75rem;
  1330. font-weight: 500;
  1331. line-height: 1rem;
  1332. color: #9ca3af;
  1333. }
  1334. > div {
  1335. min-height: 1.25rem;
  1336. font-size: 0.875rem;
  1337. font-weight: 600;
  1338. line-height: 1.25rem;
  1339. color: #1f2937;
  1340. word-break: break-all;
  1341. }
  1342. }
  1343. :deep(.el-form-item__label) {
  1344. font-weight: 500;
  1345. }
  1346. :deep(.el-scrollbar__bar.is-horizontal) {
  1347. height: 4px;
  1348. }
  1349. </style>