FillDailyReportForm.vue 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525
  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. submitterNames: string
  67. constructionStartDate: number
  68. manufactureName: string
  69. contractName: string
  70. deptName: string
  71. location: string
  72. techniqueNames: string
  73. workloadDesign: string
  74. commencementDate: string
  75. completionDate: string
  76. constructionPeriod: string
  77. idleTime: string
  78. responsiblePersonNames: string
  79. deviceNames: string
  80. taskName: string
  81. taskProgresses: {
  82. createTime: string
  83. rdStatusLabel: string
  84. }[]
  85. selectedDevices: {
  86. deviceName: string
  87. deviceCode: string
  88. id: number
  89. }[]
  90. deviceIds: number[]
  91. nextPlan: string
  92. externalRental: string
  93. malfunction: string
  94. faultDowntime: number
  95. startTime: number[]
  96. endTime: number[]
  97. dailyFuel: number
  98. reportFuels: Fule[]
  99. reportedFuels: Fule[]
  100. createTime: number
  101. auditStatus: number
  102. status: number
  103. opinion: string
  104. companyId: number
  105. deptId: number
  106. reportDetails: Omit<ReportDetail, 'startTime' | 'endTime'> &
  107. {
  108. startTime: number[]
  109. endTime: number[]
  110. }[]
  111. attachments: any[]
  112. constructionBrief: string
  113. }
  114. interface ReportDetail {
  115. startTime: string
  116. endTime: string
  117. constructionDetail: string
  118. duration: number
  119. }
  120. interface Form {
  121. timeRange: string[]
  122. deviceIds: number[]
  123. dailyFuel: number
  124. nextPlan: string
  125. externalRental: string
  126. malfunction: string
  127. faultDowntime: number
  128. platformIds: number[]
  129. reportFuels: Fule[]
  130. reportDetails: ReportDetail[]
  131. constructionBrief: string
  132. attachments: any[]
  133. [key: number]: PlatformData | any
  134. }
  135. const formRef = ref<FormInstance>()
  136. const rules = ref<FormRules<Form>>({
  137. timeRange: [{ required: true, message: '请选择时间节点', trigger: 'change', type: 'array' }],
  138. dailyFuel: [{ required: true, message: '请输入当日油耗', trigger: 'change' }],
  139. nextPlan: [{ required: true, message: '请输入下计划', trigger: 'change' }],
  140. reportDetails: [{ required: true, message: '请填写生产动态', type: 'array' }]
  141. })
  142. function noProductionTimeRule(id: number) {
  143. const wellName =
  144. wellOptions.value.find((item) => item.value === id)?.label ?? data.value.wellName ?? ''
  145. return {
  146. validator: (_rule: any, _value: any, callback: any) => {
  147. const currentRow = form.value[id]
  148. if (!currentRow) {
  149. callback()
  150. return
  151. }
  152. let totalTime = 0
  153. NON_PROD_FIELDS.forEach((field) => {
  154. const val = parseFloat(currentRow[field.key])
  155. if (!isNaN(val)) {
  156. totalTime += val
  157. }
  158. })
  159. const fixedTotal = Number(totalTime.toFixed(2))
  160. if (fixedTotal > 24) {
  161. callback(new Error(`【${wellName}】总时间(${fixedTotal}h)不能超过 24 小时`))
  162. } else {
  163. callback()
  164. }
  165. },
  166. trigger: 'blur'
  167. }
  168. }
  169. const handleRowValidate = (pid: number, key: string) => {
  170. if (!formRef.value) return
  171. const propsToValidate = NON_PROD_FIELDS.map((field) => `${pid}.${field.key}`)
  172. if (key === 'otherNptTime') propsToValidate.push(`${pid}.otherNptReason`)
  173. formRef.value.validateField(propsToValidate)
  174. }
  175. const data = ref<Partial<Data>>({})
  176. const original = (): Form => ({
  177. timeRange: [],
  178. deviceIds: [],
  179. dailyFuel: 0,
  180. nextPlan: '',
  181. externalRental: '',
  182. malfunction: '',
  183. faultDowntime: 0,
  184. platformIds: [],
  185. reportDetails: [],
  186. reportFuels: [],
  187. constructionBrief: '',
  188. attachments: []
  189. })
  190. const opinion = ref('')
  191. const form = ref<Form>(original())
  192. function initPlatformData(reportId: number, sourceData: any) {
  193. form.value[reportId] = {
  194. rdStatus: sourceData.rdStatus,
  195. techniqueIds: sourceData.techniqueIds || [],
  196. extProperty: (sourceData.extProperty || []).map((item) => {
  197. if (item.dataType === 'double') {
  198. item.actualValue = Number(item.actualValue)
  199. }
  200. return item
  201. }),
  202. otherNptReason: sourceData.otherNptReason || ''
  203. }
  204. NON_PROD_FIELDS.forEach((field) => {
  205. form.value[reportId][field.key] = sourceData[field.key] || 0
  206. })
  207. }
  208. // const dailyFuel = ref(0)
  209. const initDailyFuel = () => {
  210. const propVal = data.value.dailyFuel
  211. const hasPropValue = propVal !== undefined && propVal !== null && !isNaN(propVal)
  212. if (hasPropValue) {
  213. // dailyFuel.value = propVal
  214. form.value.dailyFuel = propVal
  215. } else {
  216. const list1 = data.value.reportFuels || []
  217. const list2 = data.value.reportedFuels || []
  218. const validList = list1.length > 0 ? list1 : list2.length > 0 ? list2 : []
  219. form.value.reportFuels = validList.map((v) => ({
  220. ...v,
  221. customFuel: Number(
  222. Number(true ? (v.customFuel ?? 0) : (v.customFuel ?? v.zhbdFuel ?? 0)).toFixed(2)
  223. )
  224. }))
  225. let total = 0
  226. form.value.reportFuels.forEach((item) => {
  227. total += item.customFuel
  228. })
  229. // dailyFuel.value = total
  230. form.value.dailyFuel = total
  231. }
  232. }
  233. const loading = ref(false)
  234. async function loadDetail(id: number) {
  235. loading.value = true
  236. try {
  237. const res = await IotRdDailyReportApi.getIotRdDailyReport(id)
  238. data.value = res
  239. opinion.value = data.value.opinion || ''
  240. form.value.deviceIds = data.value.deviceIds || []
  241. form.value.attachments = data.value.attachments || []
  242. form.value.nextPlan = data.value.nextPlan || ''
  243. form.value.externalRental = data.value.externalRental || ''
  244. form.value.malfunction = data.value.malfunction || ''
  245. form.value.faultDowntime = data.value.faultDowntime || 0
  246. form.value.constructionBrief = data.value.constructionBrief || ''
  247. form.value.reportDetails = (data.value.reportDetails || []).map((item) => ({
  248. duration: item.duration || 0,
  249. constructionDetail: item.constructionDetail || '',
  250. startTime: formatT(item.startTime),
  251. endTime: formatT(item.endTime)
  252. }))
  253. if (!form.value.reportDetails.length) {
  254. addReportDetailRow()
  255. }
  256. if (data.value.startTime && data.value.endTime) {
  257. form.value.timeRange = [formatT(data.value.startTime), formatT(data.value.endTime)]
  258. }
  259. if (data.value.platformWell === 1) {
  260. form.value.platformIds = data.value.platforms?.map((v) => v.reportId) ?? []
  261. data.value.platforms?.forEach((p) => {
  262. initPlatformData(p.reportId, p)
  263. })
  264. } else {
  265. form.value.platformIds = [data.value.id!]
  266. initPlatformData(data.value.id!, data.value)
  267. }
  268. initDailyFuel()
  269. } finally {
  270. loading.value = false
  271. }
  272. }
  273. const formLoading = ref(false)
  274. const submitForm = useDebounceFn(async function submitForm() {
  275. try {
  276. formLoading.value = true
  277. const deleteId = wellOptions.value.filter((o) => !form.value.platformIds.includes(o.value))
  278. deleteId.forEach((o) => {
  279. delete form.value[o.value]
  280. })
  281. if (formType.value === 'time') {
  282. await formRef.value?.validateField([
  283. 'constructionBrief',
  284. ...form.value.platformIds.flatMap((pid) => [
  285. ...NON_PROD_FIELDS.map((item) => `${pid}.${item.key}`),
  286. `${pid}.otherNptReason`
  287. ])
  288. ])
  289. } else await formRef.value?.validate()
  290. const copyForm = cloneDeep(form.value)
  291. const responseData: any[] = []
  292. form.value.platformIds.forEach((pid) => {
  293. const platformAttachments = cloneDeep(copyForm.attachments).map((item) => {
  294. item.bizId = pid
  295. return item
  296. })
  297. let platformWell = data.value.platformWell
  298. if (platformWell === 1) {
  299. platformWell = pid === data.value.id ? 1 : 2
  300. }
  301. const platformData = {
  302. id: pid,
  303. timeRange: ['1970-01-01T00:00:00.008Z', '1970-01-01T00:00:00.008Z'],
  304. projectDepartment: '',
  305. costCenter: '',
  306. dynamicFields: {},
  307. platformWell,
  308. companyId: data.value.companyId,
  309. deptId: data.value.deptId,
  310. startTime: copyForm.timeRange[0],
  311. endTime: copyForm.timeRange[1],
  312. deviceIds: copyForm.deviceIds,
  313. dailyFuel: copyForm.dailyFuel,
  314. nextPlan: copyForm.nextPlan,
  315. externalRental: copyForm.externalRental,
  316. malfunction: copyForm.malfunction,
  317. faultDowntime: copyForm.faultDowntime,
  318. reportFuels: copyForm.reportFuels.map((item) => ({
  319. ...item,
  320. reportId: pid
  321. })),
  322. reportDetails: copyForm.reportDetails,
  323. constructionBrief: copyForm.constructionBrief,
  324. attachments: platformAttachments,
  325. ...(data.value.platformWell === 1
  326. ? {
  327. platformId: data.value.platforms?.find((v) => v.reportId === pid)?.id
  328. }
  329. : {}),
  330. extProperty: copyForm[pid].extProperty,
  331. rdStatus: copyForm[pid].rdStatus,
  332. techniqueIds: copyForm[pid].techniqueIds,
  333. ...NON_PROD_FIELDS.reduce(
  334. (acc, field) => {
  335. acc[field.key] = copyForm[pid][field.key] ?? 0
  336. return acc
  337. },
  338. {} as Record<string, number>
  339. ),
  340. otherNptReason: copyForm[pid].otherNptReason || '',
  341. nonProduct: formType.value === 'time' ? 'Y' : ''
  342. }
  343. responseData.push(platformData)
  344. })
  345. await IotRdDailyReportApi.saveBatch(responseData)
  346. message.success(t('common.updateSuccess'))
  347. handleCancel()
  348. } catch (error) {
  349. console.log('提交失败:', error)
  350. } finally {
  351. formLoading.value = false
  352. }
  353. })
  354. async function submitApprovalForm(auditStatus: number) {
  355. try {
  356. formLoading.value = true
  357. await IotRdDailyReportApi.approveRdDailyReport({
  358. ...data.value,
  359. startTime: form.value.timeRange[0],
  360. endTime: form.value.timeRange[1],
  361. reportDetails: data.value.reportDetails?.map((item) => ({
  362. ...item,
  363. startTime: formatT(item.startTime),
  364. endTime: formatT(item.endTime)
  365. })),
  366. id: data.value.id!,
  367. auditStatus,
  368. opinion: opinion.value
  369. })
  370. if (auditStatus === 20) {
  371. message.success('审批通过')
  372. } else {
  373. message.success('审批驳回')
  374. }
  375. handleCancel()
  376. } catch (error) {
  377. console.log('审批失败:', error)
  378. } finally {
  379. formLoading.value = false
  380. }
  381. }
  382. function handleOpenForm(id: number, type: 'edit' | 'approval' | 'detail' | 'time') {
  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">
  776. <label>填报人</label>
  777. <div>{{ data.submitterNames || '-' }}</div>
  778. </div>
  779. <div class="info-item col-span-3">
  780. <label>设备配置</label>
  781. <div>{{ data.deviceNames || '-' }}</div>
  782. </div>
  783. </div>
  784. <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
  785. <div v-if="progressList.length > 0">
  786. <h3 class="text-lg font-bold text-gray-800 mb-6 flex items-center gap-2">
  787. <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
  788. 任务进度
  789. </h3>
  790. <el-scrollbar class="h-24!" view-class="w-full flex items-start px-2">
  791. <div
  792. v-for="(item, index) in progressList"
  793. :key="index"
  794. class="group relative flex flex-col items-center flex-1 min-w-[160px] cursor-default select-none"
  795. >
  796. <div
  797. v-if="index !== progressList.length - 1"
  798. class="absolute top-[34px] left-1/2 w-full h-[2px] bg-gray-100 group-hover:bg-blue-50 transition-colors duration-300"
  799. >
  800. </div>
  801. <span
  802. class="text-xs font-medium text-gray-400 mb-3 font-mono transition-colors duration-300 group-hover:text-blue-500"
  803. >
  804. {{ item.createTime || '--' }}
  805. </span>
  806. <div
  807. class="relative z-10 mb-3 transition-transform duration-300 group-hover:-translate-y-0.5"
  808. >
  809. <div
  810. 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"
  811. >
  812. </div>
  813. </div>
  814. <span
  815. class="text-sm font-bold text-gray-700 px-2 text-center leading-relaxed transition-colors duration-300 group-hover:text-blue-600"
  816. >
  817. {{ item.rdStatusLabel || '未知状态' }}
  818. </span>
  819. </div>
  820. </el-scrollbar>
  821. </div>
  822. <el-divider class="m-0! border-2! border-[var(--el-color-primary)]!" />
  823. <el-form
  824. ref="formRef"
  825. size="default"
  826. :rules="rules"
  827. label-position="top"
  828. :model="form"
  829. require-asterisk-position="right"
  830. class="flex flex-col"
  831. :disabled="formDisabled()"
  832. >
  833. <!-- <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
  834. <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
  835. 基础信息
  836. </h3> -->
  837. <div
  838. class="p-6 rounded-lg shadow border border-solid border-gray-100 grid grid-cols-2 gap-x-8"
  839. >
  840. <el-form-item label="时间节点" prop="timeRange">
  841. <el-time-picker
  842. v-model="form.timeRange"
  843. is-range
  844. range-separator="至"
  845. start-placeholder="开始时间"
  846. end-placeholder="结束时间"
  847. placeholder="选择时间范围"
  848. clearable
  849. format="HH:mm"
  850. value-format="HH:mm"
  851. />
  852. </el-form-item>
  853. <el-form-item label="当日油耗(L)" prop="dailyFuel">
  854. <el-input-number
  855. v-model="form.dailyFuel"
  856. :min="0"
  857. :controls="false"
  858. align="left"
  859. class="w-full!"
  860. placeholder="请输入当日油耗"
  861. >
  862. <template #suffix>升(L)</template>
  863. </el-input-number>
  864. </el-form-item>
  865. <el-form-item class="col-span-2" label="施工设备" prop="deviceIds">
  866. <el-select
  867. v-model="form.deviceIds"
  868. multiple
  869. placeholder="请选择施工设备"
  870. clearable
  871. filterable
  872. tag-type="primary"
  873. >
  874. <el-option
  875. v-for="item in deviceOptions"
  876. :key="item.id"
  877. :label="item.deviceName"
  878. :value="item.id"
  879. >
  880. <span class="font-medium">{{ item.deviceCode + ' - ' + item.deviceName }}</span>
  881. </el-option>
  882. </el-select>
  883. </el-form-item>
  884. <el-form-item class="col-span-2" label="闲置/未施工设备">
  885. <div
  886. class="w-full min-h-[40px] p-3 rounded bg-gray-50 border border-gray-200 border-dashed transition-all"
  887. >
  888. <template v-if="noSelectedDevices.length > 0">
  889. <div class="flex flex-wrap gap-2">
  890. <el-tag
  891. v-for="device in noSelectedDevices"
  892. :key="device.id"
  893. type="info"
  894. effect="plain"
  895. class="!border-gray-300"
  896. >
  897. {{ device.deviceName }}
  898. </el-tag>
  899. </div>
  900. </template>
  901. <template v-else>
  902. <div class="text-gray-400 text-sm flex items-center">
  903. <el-icon class="mr-1"><CircleCheck /></el-icon>
  904. 所有设备均已投入施工
  905. </div>
  906. </template>
  907. </div>
  908. </el-form-item>
  909. <div class="col-span-2 flex items-center justify-between mb-6">
  910. <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2">
  911. <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
  912. 生产动态
  913. </h3>
  914. <el-button type="primary" link :icon="Plus" @click="addReportDetailRow">
  915. 添加一行
  916. </el-button>
  917. </div>
  918. <el-form-item prop="reportDetails" class="col-span-2">
  919. <ZmTable :data="form.reportDetails" :loading="false" class="col-span-2">
  920. <ZmTableColumn
  921. :width="120"
  922. label="日期"
  923. cover-formatter
  924. :real-value="
  925. () => (data.createTime ? dayjs(data.createTime).format('YYYY-MM-DD') : '')
  926. "
  927. />
  928. <ZmTableColumn :width="160" label="开始时间" prop="startTime">
  929. <template #default="{ row, $index }">
  930. <el-form-item
  931. v-if="$index >= 0"
  932. class="mb-0!"
  933. :prop="`reportDetails.${$index}.startTime`"
  934. :rules="{ required: true, message: '请选择开始时间', trigger: 'change' }"
  935. >
  936. <el-time-picker
  937. v-model="row.startTime"
  938. placeholder="选择开始时间"
  939. clearable
  940. format="HH:mm"
  941. value-format="HH:mm"
  942. class="w-full!"
  943. @change="calculateDuration(row)"
  944. />
  945. </el-form-item>
  946. </template>
  947. </ZmTableColumn>
  948. <ZmTableColumn :width="160" label="结束时间" prop="endTime">
  949. <template #default="{ row, $index }">
  950. <el-form-item
  951. v-if="$index >= 0"
  952. class="mb-0!"
  953. :prop="`reportDetails.${$index}.endTime`"
  954. :rules="{ required: true, message: '请选择结束时间', trigger: 'change' }"
  955. >
  956. <el-time-picker
  957. v-model="row.endTime"
  958. placeholder="选择结束时间"
  959. clearable
  960. format="HH:mm"
  961. value-format="HH:mm"
  962. class="w-full!"
  963. @change="calculateDuration(row)"
  964. />
  965. </el-form-item>
  966. </template>
  967. </ZmTableColumn>
  968. <ZmTableColumn :width="80" label="时长(H)" prop="duration" />
  969. <ZmTableColumn label="施工详情" prop="constructionDetail">
  970. <template #default="{ row, $index }">
  971. <el-form-item
  972. v-if="$index >= 0"
  973. class="mb-0!"
  974. :prop="`reportDetails.${$index}.constructionDetail`"
  975. :rules="{ required: true, message: '请输入施工详情', trigger: 'change' }"
  976. >
  977. <el-input
  978. v-model="row.constructionDetail"
  979. placeholder="输入施工详情"
  980. type="textarea"
  981. :autosize="{ minRows: 1 }"
  982. show-word-limit
  983. :maxlength="2000"
  984. class="w-full!"
  985. />
  986. </el-form-item>
  987. </template>
  988. </ZmTableColumn>
  989. <ZmTableColumn label="操作" :width="80" fixed="right" align="center">
  990. <template #default="{ $index }">
  991. <el-button link type="danger" :icon="Delete" @click="removeReportDetailRow($index)">
  992. 删除
  993. </el-button>
  994. </template>
  995. </ZmTableColumn>
  996. </ZmTable>
  997. </el-form-item>
  998. <el-form-item class="col-span-2" label="下步工作计划" prop="nextPlan">
  999. <el-input
  1000. v-model="form.nextPlan"
  1001. type="textarea"
  1002. :autosize="{ minRows: 2 }"
  1003. resize="none"
  1004. show-word-limit
  1005. :maxlength="1000"
  1006. placeholder="请输入下步工作计划"
  1007. />
  1008. </el-form-item>
  1009. <el-form-item class="col-span-2" label="外组设备" prop="externalRental">
  1010. <el-input
  1011. v-model="form.externalRental"
  1012. type="textarea"
  1013. :autosize="{ minRows: 2 }"
  1014. resize="none"
  1015. show-word-limit
  1016. :maxlength="1000"
  1017. placeholder="请输入外组设备"
  1018. />
  1019. </el-form-item>
  1020. <el-form-item class="col-span-2" label="故障情况" prop="malfunction">
  1021. <el-input
  1022. v-model="form.malfunction"
  1023. type="textarea"
  1024. :autosize="{ minRows: 2 }"
  1025. show-word-limit
  1026. resize="none"
  1027. :maxlength="1000"
  1028. placeholder="请输入故障情况"
  1029. />
  1030. </el-form-item>
  1031. <el-form-item label="故障误工(H)" prop="faultDowntime">
  1032. <el-input-number
  1033. v-model="form.faultDowntime"
  1034. :min="0"
  1035. :controls="false"
  1036. align="left"
  1037. class="w-full!"
  1038. >
  1039. <template #suffix>小时(H)</template>
  1040. </el-input-number>
  1041. </el-form-item>
  1042. <el-form-item label="附件">
  1043. <FileUpload
  1044. v-if="formType === 'edit'"
  1045. ref="fileUploadRef"
  1046. :device-id="undefined"
  1047. :show-folder-button="false"
  1048. @upload-success="handleUploadSuccess"
  1049. />
  1050. <div v-if="form.attachments && form.attachments.length > 0" class="attachment-container">
  1051. <div class="attachment-list">
  1052. <div
  1053. v-for="(attachment, index) in form.attachments"
  1054. :key="attachment.id || index"
  1055. class="attachment-item"
  1056. >
  1057. <a class="attachment-name" @click="inContent(attachment)">
  1058. {{ attachment.filename }}
  1059. </a>
  1060. <el-button
  1061. :disabled="formDisabled()"
  1062. type="danger"
  1063. link
  1064. size="small"
  1065. @click="removeAttachment(index)"
  1066. >
  1067. 删除
  1068. </el-button>
  1069. </div>
  1070. </div>
  1071. </div>
  1072. <div v-else-if="!form.attachments || form.attachments.length === 0" class="no-attachment">
  1073. 无附件
  1074. </div>
  1075. </el-form-item>
  1076. </div>
  1077. <el-divider class="my-6! border-2! border-[var(--el-color-primary)]!" />
  1078. <el-form-item v-if="data.platformWell === 1" label="平台井" prop="platformIds">
  1079. <el-select
  1080. v-model="form.platformIds"
  1081. multiple
  1082. :options="wellOptions"
  1083. placeholder="请选择平台井"
  1084. clearable
  1085. filterable
  1086. collapse-tags
  1087. collapse-tags-tooltip
  1088. :max-collapse-tags="5"
  1089. tag-type="primary"
  1090. />
  1091. </el-form-item>
  1092. <template v-for="(pid, pindex) in form.platformIds" :key="pid">
  1093. <el-divider v-if="pindex !== 0" class="my-6 border-2! border-[var(--el-color-primary)]!" />
  1094. <div
  1095. class="p-6 rounded-lg shadow border border-solid border-gray-100 grid grid-cols-4 gap-x-8"
  1096. >
  1097. <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 col-span-4 mb-6">
  1098. <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
  1099. {{ wellOptions.find((item) => item.value === pid)?.label ?? data.wellName ?? '' }}
  1100. </h3>
  1101. <el-form-item
  1102. label="施工状态"
  1103. :prop="`${pid}.rdStatus`"
  1104. :rules="{ required: true, message: '请选择施工状态', trigger: 'change' }"
  1105. class="col-span-2"
  1106. >
  1107. <el-select
  1108. v-model="form[pid].rdStatus"
  1109. :options="rdStatusOptions"
  1110. placeholder="请选择"
  1111. class="w-full"
  1112. clearable
  1113. />
  1114. </el-form-item>
  1115. <el-form-item
  1116. label="施工工艺"
  1117. :prop="`${pid}.techniqueIds`"
  1118. :rules="{
  1119. required: true,
  1120. message: '请选择施工工艺',
  1121. trigger: 'change',
  1122. type: 'array'
  1123. }"
  1124. class="col-span-2"
  1125. >
  1126. <el-select
  1127. v-model="form[pid].techniqueIds"
  1128. :options="techniqueOptions"
  1129. multiple
  1130. collapse-tags
  1131. collapse-tags-tooltip
  1132. placeholder="请选择"
  1133. class="w-full"
  1134. @change="(val) => handleTechniqueChange(val, pid)"
  1135. clearable
  1136. />
  1137. </el-form-item>
  1138. <template v-if="form[pid] && form[pid].extProperty">
  1139. <el-form-item
  1140. v-for="(attr, idx) in form[pid].extProperty"
  1141. :key="idx"
  1142. :label="`${attr.name}${attr.unit ? '(' + attr.unit + ')' : ''}`"
  1143. :prop="`${pid}.extProperty.${idx}.actualValue`"
  1144. :rules="
  1145. attr.required === 1
  1146. ? [{ required: true, message: `请输入${attr.name}`, trigger: 'blur' }]
  1147. : []
  1148. "
  1149. >
  1150. <el-input-number
  1151. v-if="attr.dataType === 'double'"
  1152. v-model="attr.actualValue"
  1153. :controls="false"
  1154. class="w-full!"
  1155. align="left"
  1156. placeholder="请输入"
  1157. />
  1158. <el-input type="textarea" v-else v-model="attr.actualValue" placeholder="请输入" />
  1159. </el-form-item>
  1160. </template>
  1161. <el-divider content-position="left" class="m-0! mt-2! mb-6! border-2! col-span-4">
  1162. 非生产时间
  1163. </el-divider>
  1164. <el-form-item
  1165. v-for="field in NON_PROD_FIELDS"
  1166. :key="field.key"
  1167. :label="field.label"
  1168. :prop="`${pid}.${field.key}`"
  1169. :rules="noProductionTimeRule(pid)"
  1170. >
  1171. <el-input-number
  1172. v-model="form[pid][field.key]"
  1173. :min="0"
  1174. :max="24"
  1175. :controls="false"
  1176. class="w-full!"
  1177. align="left"
  1178. @blur="handleRowValidate(pid, field.key)"
  1179. :disabled="formDisabled(field.key)"
  1180. >
  1181. <template #suffix>小时(H)</template>
  1182. </el-input-number>
  1183. </el-form-item>
  1184. <el-form-item
  1185. class="col-span-4"
  1186. label="其他非生产原因"
  1187. :prop="`${pid}.otherNptReason`"
  1188. :rules="{
  1189. required: form[pid].otherNptTime > 0,
  1190. message: '请填写原因',
  1191. trigger: ['blur', 'change']
  1192. }"
  1193. >
  1194. <el-input
  1195. v-model="form[pid].otherNptReason"
  1196. type="textarea"
  1197. :autosize="{ minRows: 2 }"
  1198. resize="none"
  1199. show-word-limit
  1200. :maxlength="1000"
  1201. placeholder="当'其他非生产时间'大于0时必填"
  1202. :disabled="formDisabled('otherNptReason')"
  1203. />
  1204. </el-form-item>
  1205. </div>
  1206. </template>
  1207. <el-form-item
  1208. class="mt-4 col-span-2"
  1209. label="当日施工简报"
  1210. prop="constructionBrief"
  1211. :rules="[
  1212. {
  1213. required: formType === 'time',
  1214. message: '请填写施工简报',
  1215. type: 'string',
  1216. trigger: ['blur', 'change']
  1217. }
  1218. ]"
  1219. >
  1220. <el-input
  1221. v-model="form.constructionBrief"
  1222. type="textarea"
  1223. :autosize="{ minRows: 2 }"
  1224. show-word-limit
  1225. resize="none"
  1226. :maxlength="1000"
  1227. placeholder="请输入当日施工简报"
  1228. :disabled="formDisabled('constructionBrief')"
  1229. />
  1230. </el-form-item>
  1231. <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
  1232. <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 mb-6">
  1233. <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
  1234. 油耗信息
  1235. </h3>
  1236. <ZmTable :data="form.reportFuels" :loading="false">
  1237. <ZmTableColumn label="设备编号" :width="160" prop="deviceCode" />
  1238. <ZmTableColumn label="设备名称" prop="deviceName" />
  1239. <ZmTableColumn
  1240. label="发生日期"
  1241. prop="queryDate"
  1242. :width="110"
  1243. cover-formatter
  1244. :real-value="(row) => (row.queryDate ? dayjs(row.queryDate).format('YYYY-MM-DD') : '')"
  1245. />
  1246. <ZmTableColumn label="中航北斗油耗(L)" :width="140" prop="zhbdFuel" />
  1247. <ZmTableColumn label="实际油耗(L)" prop="customFuel">
  1248. <template #default="{ row, $index }">
  1249. <el-form-item class="mb-0!" :prop="`reportFuels.${$index}.customFuel`">
  1250. <el-input-number
  1251. v-model="row.customFuel"
  1252. :min="0"
  1253. :controls="false"
  1254. class="w-full!"
  1255. align="left"
  1256. @input="handleListChange"
  1257. >
  1258. <template #suffix> L </template>
  1259. </el-input-number>
  1260. </el-form-item>
  1261. </template>
  1262. </ZmTableColumn>
  1263. </ZmTable>
  1264. <template v-if="platformWorkloadData.length > 0">
  1265. <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
  1266. <h3 class="text-lg font-bold text-gray-800 flex items-center gap-2 mb-6">
  1267. <div class="w-1 h-4 bg-blue-600 rounded-full"></div>
  1268. 平台井工作量
  1269. </h3>
  1270. <ZmTable :data="platformWorkloadData" :loading="false">
  1271. <ZmTableColumn label="井号" prop="wellName" />
  1272. <ZmTableColumn label="施工状态" prop="rdStatusLabel" />
  1273. <ZmTableColumn label="施工工艺" prop="techniqueNames" />
  1274. <template v-for="{ prop, label } in getWorkloadColumns()" :key="prop">
  1275. <ZmTableColumn
  1276. :label="label"
  1277. :prop="prop"
  1278. cover-formatter
  1279. :real-value="(row) => getWorkloadValue(row, prop)"
  1280. />
  1281. </template>
  1282. </ZmTable>
  1283. </template>
  1284. <template v-if="formType === 'approval'">
  1285. <el-divider class="my-6 border-2! border-[var(--el-color-primary)]!" />
  1286. <el-form-item label="审批意见" prop="opinion">
  1287. <el-input
  1288. v-model="opinion"
  1289. type="textarea"
  1290. :autosize="{ minRows: 2 }"
  1291. resize="none"
  1292. show-word-limit
  1293. :maxlength="1000"
  1294. placeholder="请输入审批意见"
  1295. :disabled="formDisabled('opinion')"
  1296. />
  1297. </el-form-item>
  1298. </template>
  1299. </el-form>
  1300. </div>
  1301. <div
  1302. 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"
  1303. >
  1304. <div v-if="formType === 'edit' || formType === 'time'">
  1305. <el-button
  1306. size="default"
  1307. type="primary"
  1308. @click="submitForm"
  1309. :disabled="formDisabled('button')"
  1310. :loading="formLoading"
  1311. >
  1312. 确 定
  1313. </el-button>
  1314. <el-button size="default" @click="handleCancel">取 消</el-button>
  1315. </div>
  1316. <div v-if="formType === 'approval'">
  1317. <el-button
  1318. size="default"
  1319. type="primary"
  1320. @click="submitApprovalForm(20)"
  1321. :disabled="formDisabled('button')"
  1322. :loading="formLoading"
  1323. >
  1324. 审批通过
  1325. </el-button>
  1326. <el-button
  1327. size="default"
  1328. type="danger"
  1329. @click="submitApprovalForm(30)"
  1330. :disabled="formDisabled('button')"
  1331. :loading="formLoading"
  1332. >
  1333. 审批拒绝
  1334. </el-button>
  1335. <el-button size="default" @click="handleCancel">取 消</el-button>
  1336. </div>
  1337. </div>
  1338. </template>
  1339. <style scoped>
  1340. .info-item {
  1341. display: flex;
  1342. flex-direction: column;
  1343. gap: 0.25rem;
  1344. label {
  1345. font-size: 0.75rem;
  1346. font-weight: 500;
  1347. line-height: 1rem;
  1348. color: #9ca3af;
  1349. }
  1350. > div {
  1351. min-height: 1.25rem;
  1352. font-size: 0.875rem;
  1353. font-weight: 600;
  1354. line-height: 1.25rem;
  1355. color: #1f2937;
  1356. word-break: break-all;
  1357. }
  1358. }
  1359. :deep(.el-form-item__label) {
  1360. font-weight: 500;
  1361. }
  1362. :deep(.el-scrollbar__bar.is-horizontal) {
  1363. height: 4px;
  1364. }
  1365. </style>