report-form.vue 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116
  1. <script setup>
  2. import { useDataDictStore } from '@/store/modules/dataDict';
  3. import { onMounted, reactive, ref, computed, getCurrentInstance, watch } from 'vue';
  4. import tpfTimeRange from '@/components/tpf-time-range/tpf-time-range.vue';
  5. import deviceTransfer from '@/components/device-transfer/index.vue';
  6. import { updateRuiDuReportBatch, getRuiDuReportAttrs } from '@/api/ruiDu.js';
  7. import config from '@/utils/config';
  8. import dayjs from 'dayjs';
  9. import { getTenantId, getAccessToken } from '@/utils/auth.js';
  10. const { appContext } = getCurrentInstance();
  11. const t = appContext.config.globalProperties.$t;
  12. const { getIntDictOptions, getStrDictOptions, loadDataDictList } = useDataDictStore();
  13. const rdStatusRange = ref([]);
  14. const techniqueRange = ref([]);
  15. const handleInitSelect = () => {
  16. rdStatusRange.value = getStrDictOptions('rdStatus').map(item => {
  17. return {
  18. ...item,
  19. text: item.label,
  20. };
  21. });
  22. // 施工工艺
  23. techniqueRange.value = getIntDictOptions('rq_iot_project_technology_rd').map(item => {
  24. return {
  25. ...item,
  26. text: item.label,
  27. };
  28. });
  29. };
  30. onMounted(() => {
  31. loadDataDictList().then(() => {
  32. handleInitSelect();
  33. });
  34. });
  35. const selectPlaceholder = computed(() => {
  36. return props.formDisable ? ' ' : t('operation.PleaseSelect');
  37. });
  38. const inputPlaceholder = computed(() => {
  39. return props.formDisable ? ' ' : t('operation.PleaseInput');
  40. });
  41. const props = defineProps({
  42. reportId: {
  43. type: String,
  44. default: '',
  45. },
  46. reportData: {
  47. type: Object,
  48. default: () => {},
  49. },
  50. formDisable: {
  51. type: Boolean,
  52. default: false, // 是否禁用表单
  53. },
  54. });
  55. const isRequired = computed(() => {
  56. return props.formDisable ? false : true;
  57. });
  58. const reportFormRef = ref(null);
  59. // 设备选择器
  60. const deviceTransferRef = ref(null);
  61. // 已选择的设备(名称)
  62. const selectedEquipmentNames = ref('');
  63. // 未选择的设备(名称)
  64. const unselectedEquipmentNames = ref('');
  65. const timeRangeRef = ref(null);
  66. const startTime = ref('00:00');
  67. const startDefaultTime = ref('06:00');
  68. const endTime = ref('24:00');
  69. const endDefaultTime = ref('06:00');
  70. const timeRange = data => {
  71. form.startTime = data[0];
  72. form.endTime = data[1];
  73. };
  74. const form = reactive({
  75. startTime: startDefaultTime.value,
  76. endTime: endDefaultTime.value,
  77. platformIds: [],
  78. deviceIds: [],
  79. productionStatus: '',
  80. nextPlan: '',
  81. externalRental: '',
  82. faultDowntime: '',
  83. malfunction: '',
  84. attachments: [],
  85. reportFuels: [],
  86. });
  87. const formDataBaseRules = reactive({
  88. // 时间节点 - 开始时间
  89. startTime: {
  90. rules: [
  91. {
  92. required: true,
  93. errorMessage: `${t('operation.PleaseSelect')}${t('ruiDu.timeNode')}`,
  94. },
  95. ],
  96. },
  97. // 时间节点 - 结束时间
  98. endTime: {
  99. rules: [
  100. {
  101. required: true,
  102. errorMessage: `${t('operation.PleaseSelect')}${t('ruiDu.timeNode')}`,
  103. },
  104. ],
  105. },
  106. // 当日生产动态
  107. productionStatus: {
  108. rules: [
  109. {
  110. required: true,
  111. errorMessage: `${t('operation.PleaseFillIn')}${t('ruiDu.dailyProductionDynamic')}`,
  112. },
  113. ],
  114. },
  115. });
  116. const validate = async () => {
  117. return await reportFormRef.value.validate();
  118. };
  119. const submitForm = async () => {
  120. const deleteId = wellOptions.value.filter(o => !form.platformIds.includes(o.value));
  121. deleteId.forEach(o => {
  122. delete form[o.value];
  123. });
  124. await validate();
  125. // // 处理表单数据
  126. const formDataCopy = JSON.parse(JSON.stringify(form));
  127. const responseData = [];
  128. // // 处理施工工艺
  129. form.platformIds.forEach(id => {
  130. formDataCopy[id].extProperty.forEach(attr => {
  131. if (attr.dataType === 'double') {
  132. attr.actualValue = Number(attr.actualValue);
  133. }
  134. });
  135. const attachments = JSON.parse(JSON.stringify(formDataCopy.attachments)).map(item => {
  136. item.bizId = id;
  137. return item;
  138. });
  139. let platformWell = props.reportData.platformWell;
  140. if (platformWell === 1) {
  141. platformWell = id === props.reportData.id ? 1 : 2;
  142. }
  143. const data = {
  144. id,
  145. timeRange: ['1970-01-01T00:00:00.008Z', '1970-01-01T00:00:00.008Z'],
  146. projectDepartment: '',
  147. costCenter: '',
  148. dynamicFields: {},
  149. platformWell,
  150. companyId: props.reportData.companyId,
  151. deptId: props.reportData.deptId,
  152. attachments,
  153. deviceIds: formDataCopy.deviceIds,
  154. startTime: formDataCopy.startTime,
  155. endTime: formDataCopy.endTime,
  156. extProperty: formDataCopy[id].extProperty,
  157. externalRental: formDataCopy.externalRental,
  158. faultDowntime: formDataCopy.faultDowntime,
  159. malfunction: formDataCopy.malfunction,
  160. nextPlan: formDataCopy.nextPlan,
  161. ...(props.reportData.platformWell === 1
  162. ? { platformId: props.reportData.platforms.find(v => v.reportId === id).id }
  163. : {}),
  164. productionStatus: formDataCopy.productionStatus,
  165. rdStatus: formDataCopy[id].rdStatus,
  166. techniqueIds: formDataCopy[id].techniqueIds.map(v => v.toString()),
  167. reportFuels: formDataCopy.reportFuels.map(item => ({
  168. ...item,
  169. customFuel: Number(item.customFuel),
  170. reportId: id,
  171. })),
  172. };
  173. responseData.push(data);
  174. });
  175. // 提交表单
  176. updateRuiDuReportBatch(responseData).then(res => {
  177. // 提交成功
  178. if (res.code === 0) {
  179. uni.showToast({ title: t('operation.success'), icon: 'none' });
  180. // 返回上一页
  181. uni.navigateBack();
  182. } else {
  183. uni.showToast({ title: res.msg, icon: 'none' });
  184. }
  185. });
  186. };
  187. const handleClickSelectDevice = () => {
  188. deviceTransferRef.value.open();
  189. };
  190. const handleEquipmentNames = deviceIds => {
  191. form.deviceIds = deviceIds || []; //施工设备
  192. const { selectedDevices = [] } = props.reportData || {};
  193. const deviceIdSet = new Set(deviceIds);
  194. // 已选择的设备(名称)
  195. selectedEquipmentNames.value = selectedDevices
  196. .filter(item => deviceIdSet.has(item.id))
  197. .map(item => item.deviceName)
  198. .join(',');
  199. // 未选择的设备(名称)
  200. unselectedEquipmentNames.value =
  201. selectedDevices
  202. .filter(item => !deviceIdSet.has(item.id))
  203. .map(item => item.deviceName)
  204. .join(',') || t('ruiDu.allEquipmentConstructed');
  205. };
  206. // 设备选择器回调
  207. const handleTransferChange = selectedIds => {
  208. // 更新已选择的设备及名称
  209. handleEquipmentNames(selectedIds);
  210. };
  211. const formDataFormat = () => {
  212. // 处理时间范围
  213. timeRangeFormat();
  214. // 处理已选择的设备
  215. if (props.reportData?.deviceIds) {
  216. const { deviceIds = [] } = props.reportData || {};
  217. handleEquipmentNames(deviceIds);
  218. }
  219. if (props.reportData.platformWell === 1) {
  220. form.platformIds = props.reportData.platforms?.map(v => v.reportId) ?? [];
  221. } else {
  222. form.platformIds = [props.reportData.id];
  223. }
  224. // if (props.formDisable) {
  225. // } else {
  226. // form.platformIds = [props.reportData.id];
  227. // }
  228. if (props.reportData.platformWell === 1) {
  229. props.reportData.platforms.forEach(p => {
  230. form[p.reportId] = { rdStatus: p.rdStatus, techniqueIds: p.techniqueIds, extProperty: p.extProperty };
  231. });
  232. } else {
  233. form[props.reportData.id] = {
  234. rdStatus: props.reportData.rdStatus,
  235. techniqueIds: props.reportData.techniqueIds,
  236. extProperty: props.reportData.extProperty,
  237. };
  238. }
  239. // 当日生产动态
  240. form.productionStatus = props.reportData.productionStatus || ''; //当日生产动态
  241. // 下步工作计划
  242. form.nextPlan = props.reportData.nextPlan || ''; //下步工作计划
  243. // 外租设备
  244. form.externalRental = props.reportData.externalRental || ''; //外租设备
  245. // 故障情况
  246. form.malfunction = props.reportData.malfunction || ''; //故障情况
  247. // 故障误工H
  248. form.faultDowntime = props.reportData.faultDowntime || ''; //故障误工H
  249. // 附件
  250. form.attachments = props.reportData.attachments || [];
  251. form.reportFuels = (props.reportData.reportFuels || []).map(v => ({
  252. ...v,
  253. customFuel: Number(Number(v.zhbdFuel ?? 0).toFixed(2)),
  254. }));
  255. form.dailyFuel = form.reportFuels.reduce((prev, cur) => prev + Number(cur.customFuel), 0);
  256. // form.
  257. // 展示用的文件列表
  258. // attachmentsFileList.value =
  259. // props.reportData?.attachments?.map(item => ({
  260. // ...item,
  261. // name: item.filename,
  262. // url: item.filePath,
  263. // })) || [];
  264. };
  265. // 初始化时间范围
  266. const timeRangeFormat = () => {
  267. if (props.reportData.startTime) {
  268. const startArr = props.reportData.startTime;
  269. form.startTime = `${startArr[0].toString().padStart(2, '0')}:${startArr[1].toString().padStart(2, '0')}`;
  270. }
  271. if (props.reportData.endTime) {
  272. const endArr = props.reportData.endTime;
  273. form.endTime = `${endArr[0].toString().padStart(2, '0')}:${endArr[1].toString().padStart(2, '0')}`;
  274. }
  275. };
  276. watch(
  277. () => props.reportData,
  278. val => {
  279. if (val.id) {
  280. formDataFormat();
  281. }
  282. },
  283. { deep: true, immediate: true }
  284. );
  285. const wellOptions = computed(() => {
  286. return (
  287. props.reportData.platforms?.map(v => ({
  288. text: v.wellName,
  289. value: v.reportId,
  290. })) ?? []
  291. );
  292. });
  293. const handleClickTimeRange = () => {
  294. // 打开时间范围选择器
  295. timeRangeRef.value.open();
  296. };
  297. const uploadRef = ref(null);
  298. const attachmentList = ref([]);
  299. const chooseFile = () => {
  300. uploadRef.value.chooseFile({
  301. count: 9,
  302. size: 50,
  303. success: files => {
  304. attachmentList.value = attachmentList.value.concat(files);
  305. attachmentList.value.forEach(file => {
  306. // 将等待上传和上传失败的文件提交上传到服务器
  307. // 提示:::如果接口不支持跨域,改成调用this.getTempFilePath(file)
  308. if (file.status === 'waiting' || file.status === 'fail') {
  309. uploadHandle(file);
  310. }
  311. });
  312. },
  313. });
  314. };
  315. const uploadHandle = file => {
  316. uploadRef.value.upload({
  317. url: config.default.apiUrl + config.default.apiUrlSuffix + '/rq/file/upload',
  318. file,
  319. name: 'files',
  320. header: {
  321. 'Authorization': getAccessToken() ? 'Bearer ' + getAccessToken() : '',
  322. 'Tenant-id': getTenantId() ? getTenantId() : '1',
  323. 'Device-id': 'undefined',
  324. },
  325. method: 'post',
  326. success: e => {
  327. file.status = 'success';
  328. const result = JSON.parse(e.result);
  329. if (result.code !== 0) {
  330. uni.showToast({
  331. title: `【${file.name}】${t('operation.uploadFail')}`,
  332. icon: 'none',
  333. });
  334. return;
  335. }
  336. form.attachments.push({
  337. bizId: props.reportId,
  338. category: 'daily_report',
  339. filePath: result.data.files[0].filePath,
  340. filename: result.data.files[0].name,
  341. // fileSize: data.files[0].size,
  342. // fileType: data.files[0].type,
  343. remark: '',
  344. type: 'attachment',
  345. });
  346. },
  347. fail: e => {
  348. file.status = 'fail';
  349. console.error('上传异常:', err);
  350. uni.showToast({ title: t('operation.uploadFail'), icon: 'none' });
  351. },
  352. });
  353. };
  354. // const uploadHandle = file => {
  355. // uploadRef.value.getTempFilePath({
  356. // file,
  357. // success: e => {
  358. // uni.uploadFile({
  359. // url: config.default.apiUrl + config.default.apiUrlSuffix + '/rq/file/upload',
  360. // header: {
  361. // 'Authorization': getAccessToken() ? 'Bearer ' + getAccessToken() : '',
  362. // 'tenant-id': getTenantId() ? getTenantId() : '1',
  363. // 'device-id': undefined,
  364. // },
  365. // name: 'files',
  366. // // #ifdef H5
  367. // file: e.result,
  368. // // #endif
  369. // // #ifndef H5
  370. // filePath: e.result,
  371. // // #endif
  372. // success: e => {
  373. // console.log('e :>> ', e);
  374. // // file.status = 'success';
  375. // // const result = JSON.parse(e.result);
  376. // // if (e.code !== 0) {
  377. // // uni.showToast({
  378. // // title: `【${file.name}】${t('operation.uploadFail')}`,
  379. // // icon: 'none',
  380. // // });
  381. // // return;
  382. // // }
  383. // // form.attachments.push({
  384. // // bizId: props.reportId,
  385. // // category: 'daily_report',
  386. // // filePath: data.files[0].filePath,
  387. // // filename: data.files[0].name,
  388. // // // fileSize: data.files[0].size,
  389. // // // fileType: data.files[0].type,
  390. // // remark: '',
  391. // // type: 'attachment',
  392. // // });
  393. // },
  394. // fail: e => {
  395. // console.log('e :>> ', e);
  396. // },
  397. // });
  398. // },
  399. // });
  400. // };
  401. // 删除附件
  402. const deleteFiles = index => {
  403. // 1. 从formData.attachments中移除选中项
  404. form.attachments.splice(index, 1);
  405. };
  406. // 下载文件
  407. const downloadFile = async file => {
  408. console.log('🚀 ~ downloadFile ~ file:', file);
  409. const { filePath: fileUrl, name: fileName } = file;
  410. if (!fileUrl) {
  411. uni.showToast({ title: t('operation.fileUrlEmpty'), icon: 'none' });
  412. return;
  413. }
  414. // 获取平台
  415. const platform = uni.getSystemInfoSync().platform;
  416. console.log('🚀 ~ downloadFile ~ platform:', platform);
  417. // 判断平台
  418. if (platform === 'android') {
  419. uni.downloadFile({
  420. url: fileUrl,
  421. success: res => {
  422. console.log('🚀 ~ downloadFile ~ res:', res);
  423. if (res.statusCode === 200) {
  424. uni.saveFile({
  425. tempFilePath: res.tempFilePath,
  426. success: res => {
  427. console.log('🚀 ~ downloadFile saveFile ~ res:', res);
  428. uni.showToast({
  429. title: t('operation.downloadSuccess'),
  430. icon: 'none',
  431. });
  432. },
  433. fail: err => {
  434. console.log('🚀 ~ downloadFile saveFile ~ err:', err);
  435. uni.showToast({
  436. title: t('operation.downloadFail'),
  437. icon: 'none',
  438. });
  439. },
  440. });
  441. }
  442. },
  443. fail: err => {
  444. console.log('🚀 ~ downloadFile ~ err:', err);
  445. uni.showToast({
  446. title: t('operation.downloadFail'),
  447. icon: 'none',
  448. });
  449. },
  450. });
  451. } else {
  452. try {
  453. // 2.1 处理文件名(避免特殊字符乱码,如中文、空格)
  454. const safeFileName = decodeURIComponent(fileName || '未命名文件'); // 解码URL编码的文件名
  455. // 2.2 创建隐藏的 <a> 标签(核心:利用 download 属性触发下载)
  456. const link = document.createElement('a');
  457. // 关键:设置 download 属性指定文件名(Web 端独有)
  458. link.download = safeFileName;
  459. // 设置文件地址(若跨域,需后端配置 CORS + Content-Disposition 响应头)
  460. link.href = fileUrl;
  461. // 隐藏 <a> 标签(不影响页面布局)
  462. link.style.display = 'none';
  463. // 将 <a> 标签添加到文档中(否则部分浏览器无法触发点击)
  464. document.body.appendChild(link);
  465. // 2.3 模拟点击 <a> 标签触发下载
  466. link.click();
  467. // 2.4 下载后清理资源(避免内存泄漏)
  468. setTimeout(() => {
  469. document.body.removeChild(link); // 移除 <a> 标签
  470. URL.revokeObjectURL(link.href); // 释放 URL 资源(若使用 Blob 时必需)
  471. }, 100);
  472. } catch (err) {
  473. console.error('🚀 ~ Web 端下载失败:', err);
  474. // 若直接通过 <a> 标签下载失败(如跨域),尝试通过 Blob 流下载(场景2兼容)
  475. await downloadFileByBlob(fileUrl, fileName);
  476. }
  477. }
  478. };
  479. // 兼容场景2:通过 Blob 流下载(解决跨域或后端返回流的情况)
  480. const downloadFileByBlob = async (fileUrl, fileName) => {
  481. try {
  482. // 1. 发起请求获取文件流(注意:需设置 responseType: 'blob')
  483. const response = await fetch(fileUrl, {
  484. method: 'GET',
  485. headers: {
  486. // 若需要登录态,添加 token(根据项目授权方式调整)
  487. Authorization: `Bearer ${uni.getStorageSync('token')}`,
  488. },
  489. });
  490. if (!response.ok) {
  491. throw new Error(`请求失败: ${response.status}`);
  492. }
  493. // 2. 将响应转换为 Blob 对象(根据文件类型设置 MIME,如 PDF 为 'application/pdf')
  494. const blob = await response.blob();
  495. // 3. 生成 Blob 临时 URL
  496. const blobUrl = URL.createObjectURL(blob);
  497. // 4. 用 <a> 标签触发下载(同场景1逻辑)
  498. const safeFileName = decodeURIComponent(fileName || '未命名文件');
  499. const link = document.createElement('a');
  500. link.download = safeFileName;
  501. link.href = blobUrl;
  502. link.style.display = 'none';
  503. document.body.appendChild(link);
  504. link.click();
  505. // 5. 清理资源
  506. setTimeout(() => {
  507. document.body.removeChild(link);
  508. URL.revokeObjectURL(blobUrl); // 必须释放 Blob URL,避免内存泄漏
  509. uni.hideLoading();
  510. // uni.showToast({ title: t("operation.downloadSuccess"), icon: "success" });
  511. }, 100);
  512. } catch (err) {
  513. console.error('🚀 ~ Blob 下载失败:', err);
  514. uni.hideLoading();
  515. // uni.showToast({ title: t("operation.downloadFail"), icon: "error" });
  516. }
  517. };
  518. const removeSelectedItem = (platformId, value) => {
  519. form[platformId].techniqueIds = form[platformId].techniqueIds.filter(item => item !== value);
  520. getWorkloadInfoByTechnique(platformId);
  521. };
  522. const getWorkloadInfoByTechnique = platformId => {
  523. const ids = form[platformId].techniqueIds;
  524. if (!ids.length) {
  525. form[platformId].extProperty = [];
  526. return;
  527. }
  528. getRuiDuReportAttrs({
  529. techniqueIds: ids.join(','),
  530. }).then(res => {
  531. const { data = [] } = res;
  532. // 1. 按 "identifier+unit" 去重:用Map保证唯一,key为拼接字段
  533. const uniqueMap = new Map();
  534. data.forEach(item => {
  535. // 生成去重key(identifier和unit都存在才拼接,避免异常)
  536. const key =
  537. item.identifier && item.unit ? `${item.identifier}-${item.unit}` : Math.random().toString(36).slice(2, 11); // 异常情况用随机key避免重复
  538. uniqueMap.set(key, item); // 重复key会覆盖,实现去重
  539. });
  540. // 去重后的数组
  541. const uniqueData = Array.from(uniqueMap.values());
  542. // 2. 对比formData.extProperty,保留已有actualValue(避免覆盖用户输入)
  543. const handledData = uniqueData.map(newItem => {
  544. // 生成当前新项的去重key
  545. const newKey = newItem.identifier && newItem.unit ? `${newItem.identifier}-${newItem.unit}` : '';
  546. // 在原有extProperty中找匹配项
  547. const oldItem = form[platformId].extProperty.find(old => {
  548. const oldKey = old.identifier && old.unit ? `${old.identifier}-${old.unit}` : '';
  549. return newKey && oldKey && newKey === oldKey;
  550. });
  551. // 有匹配项则复用原有actualValue,无则用新项的(默认空)
  552. return {
  553. ...newItem,
  554. actualValue: oldItem?.actualValue ?? newItem.actualValue,
  555. };
  556. });
  557. // 3. 重新赋值给formData.extProperty(更新页面展示)
  558. form[platformId].extProperty = handledData;
  559. });
  560. };
  561. defineExpose({
  562. submitForm,
  563. });
  564. </script>
  565. <template>
  566. <scroll-view scroll-y="true" class="report-form">
  567. <uni-forms
  568. ref="reportFormRef"
  569. labelWidth="140px"
  570. :modelValue="form"
  571. :rules="formDataBaseRules"
  572. err-show-type="toast">
  573. <uni-forms-item
  574. v-if="reportData.platformWell === 1"
  575. class="form-item well-form-item"
  576. label="平台井"
  577. :required="isRequired">
  578. <uni-data-select
  579. class="form-item-select"
  580. :clear="false"
  581. align="right"
  582. placeholder="请选择平台井"
  583. :disabled="formDisable"
  584. :localdata="wellOptions"
  585. multiple
  586. v-model="form.platformIds" />
  587. </uni-forms-item>
  588. <div class="content">
  589. <uni-forms-item class="form-item" :label="`${$t('ruiDu.timeNode')}:`" :required="isRequired">
  590. <view class="item-content" @click="props.formDisable ? '' : handleClickTimeRange()">
  591. <view class="time-range-item" v-if="form.startTime && form.endTime">
  592. {{ form.startTime }} 至 {{ form.endTime }}
  593. </view>
  594. <view class="time-range-item" v-else>
  595. {{ selectPlaceholder }}
  596. </view>
  597. </view>
  598. </uni-forms-item>
  599. <uni-forms-item class="form-item" :label="`${$t('ruiDu.constructionEquipment')}:`">
  600. <view>
  601. <uni-row class="item-content">
  602. <button
  603. class="mini-btn form-item-btn"
  604. type="primary"
  605. :disabled="formDisable"
  606. v-if="!formDisable"
  607. @click="handleClickSelectDevice">
  608. {{ $t('device.selectDevice') }}
  609. </button>
  610. </uni-row>
  611. <uni-row>
  612. <uni-easyinput
  613. style="text-align: right"
  614. type="textarea"
  615. :autoHeight="true"
  616. :inputBorder="false"
  617. :clearable="false"
  618. :styles="{ disableColor: '#fff' }"
  619. :placeholder="$t('ruiDu.unselectedEquipment')"
  620. :disabled="true"
  621. v-model="selectedEquipmentNames" />
  622. </uni-row>
  623. </view>
  624. </uni-forms-item>
  625. <uni-forms-item class="form-item" :label="`${$t('ruiDu.unselectedEquipment')}:`">
  626. <uni-easyinput
  627. style="text-align: right"
  628. type="textarea"
  629. :autoHeight="true"
  630. :inputBorder="false"
  631. :clearable="false"
  632. :styles="{ disableColor: '#fff' }"
  633. :placeholder="' '"
  634. :disabled="true"
  635. v-model="unselectedEquipmentNames" />
  636. </uni-forms-item>
  637. <uni-forms-item class="form-item" label="当日油耗(L):">
  638. <uni-easyinput
  639. class="digit-item"
  640. type="number"
  641. :inputBorder="false"
  642. :clearable="false"
  643. :styles="{ disableColor: '#fff' }"
  644. :placeholder="inputPlaceholder"
  645. :disabled="formDisable"
  646. v-model="form.dailyFuel" />
  647. </uni-forms-item>
  648. <uni-forms-item
  649. class="form-item"
  650. :label="`${$t('ruiDu.dailyProductionDynamic')}:`"
  651. :required="isRequired"
  652. name="productionStatus">
  653. <uni-easyinput
  654. style="text-align: right"
  655. type="textarea"
  656. :autoHeight="true"
  657. :inputBorder="false"
  658. :clearable="false"
  659. :styles="{ disableColor: '#fff' }"
  660. :placeholder="inputPlaceholder"
  661. :disabled="formDisable"
  662. v-model="form.productionStatus"
  663. :maxlength="1000" />
  664. </uni-forms-item>
  665. <!-- 下步工作计划 -->
  666. <uni-forms-item class="form-item" :label="`${$t('ruiDu.nextWorkPlan')}:`" name="nextPlan">
  667. <uni-easyinput
  668. style="text-align: right"
  669. type="textarea"
  670. :autoHeight="true"
  671. :inputBorder="false"
  672. :clearable="false"
  673. :styles="{ disableColor: '#fff' }"
  674. :placeholder="inputPlaceholder"
  675. :disabled="formDisable"
  676. v-model="form.nextPlan"
  677. :maxlength="1000" />
  678. </uni-forms-item>
  679. <!-- 外租设备 -->
  680. <uni-forms-item class="form-item" :label="`${$t('ruiDu.externalRentalEquipment')}:`" name="externalRental">
  681. <uni-easyinput
  682. style="text-align: right"
  683. type="textarea"
  684. :autoHeight="true"
  685. :inputBorder="false"
  686. :clearable="false"
  687. :styles="{ disableColor: '#fff' }"
  688. :placeholder="inputPlaceholder"
  689. :disabled="formDisable"
  690. v-model="form.externalRental"
  691. :maxlength="1000" />
  692. </uni-forms-item>
  693. <!-- 故障情况 -->
  694. <uni-forms-item class="form-item" :label="`${$t('ruiDu.faultSituation')}:`" name="malfunction">
  695. <uni-easyinput
  696. style="text-align: right"
  697. type="textarea"
  698. :autoHeight="true"
  699. :inputBorder="false"
  700. :clearable="false"
  701. :styles="{ disableColor: '#fff' }"
  702. :placeholder="inputPlaceholder"
  703. :disabled="formDisable"
  704. v-model="form.malfunction"
  705. :maxlength="1000" />
  706. </uni-forms-item>
  707. <!-- 故障误工H -->
  708. <uni-forms-item class="form-item" :label="`${$t('ruiDu.faultDowntimeH')}:`" name="faultDowntime">
  709. <uni-easyinput
  710. class="digit-item"
  711. type="digit"
  712. :inputBorder="false"
  713. :clearable="true"
  714. :styles="{ disableColor: '#fff' }"
  715. :placeholder="inputPlaceholder"
  716. :disabled="formDisable"
  717. v-model="form.faultDowntime"
  718. :maxlength="1000" />
  719. </uni-forms-item>
  720. <!-- 附件 -->
  721. <uni-forms-item class="form-item" :label="`${$t('ruiDu.attachment')}:`" :required="false" name="attachments">
  722. <view v-if="!formDisable" class="file-parent">
  723. <view class="file-picker-container item-end">
  724. <view class="file-size-limit">
  725. {{ $t('ruiDu.fileSizeLimit') }}
  726. </view>
  727. <button type="primary" size="mini" class="file-picker-btn" @click="chooseFile">
  728. {{ $t('ruiDu.selectFile') }}
  729. </button>
  730. </view>
  731. <view class="file-list">
  732. <view v-for="(file, index) in form.attachments" :key="index">
  733. {{ file.filename }}
  734. <button @click="deleteFiles" type="primary" size="mini" class="file-picker-btn">删除文件</button>
  735. </view>
  736. </view>
  737. </view>
  738. <view class="file-list item-col" v-else>
  739. <view v-for="(file, index) in form.attachments" :key="index">
  740. {{ file.filename }}
  741. <!-- <button @click="downloadFile(file)" type="primary" size="mini" class="file-picker-btn">下载文件</button> -->
  742. </view>
  743. </view>
  744. </uni-forms-item>
  745. </div>
  746. <div v-for="(platformId, index) in form.platformIds" :key="platformId" class="content">
  747. <div class="content-title">{{
  748. wellOptions.find(item => item.value === platformId)?.text ?? reportData.wellName ?? ''
  749. }}</div>
  750. <uni-forms-item
  751. class="form-item"
  752. :label="`${$t('ruiDu.constructionStatus')}:`"
  753. :required="isRequired"
  754. :name="[platformId, 'rdStatus']"
  755. :rules="[{ required: true, errorMessage: `${t('operation.PleaseSelect')}${t('ruiDu.constructionStatus')}` }]">
  756. <uni-data-select
  757. class="form-item-select items-center"
  758. :clear="false"
  759. :align="'right'"
  760. placement="top"
  761. :localdata="rdStatusRange"
  762. :placeholder="selectPlaceholder"
  763. :disabled="formDisable"
  764. v-model="form[platformId].rdStatus">
  765. </uni-data-select>
  766. </uni-forms-item>
  767. <uni-forms-item
  768. class="form-item"
  769. :label="`${$t('ruiDu.constructionProcess')}:`"
  770. :required="reportData.virtualProject !== 'Y' ? isRequired : false"
  771. :name="[platformId, 'techniqueIds']"
  772. :rules="
  773. reportData.virtualProject !== 'Y'
  774. ? [{ required: true, errorMessage: `${t('operation.PleaseSelect')}${t('ruiDu.constructionProcess')}` }]
  775. : []
  776. ">
  777. <uni-data-select
  778. ref="uniDataSelect"
  779. mode="underline"
  780. :multiple="true"
  781. :clear="false"
  782. :localdata="techniqueRange"
  783. :disabled="formDisable"
  784. placement="top"
  785. @change="getWorkloadInfoByTechnique(platformId)"
  786. v-model="form[platformId].techniqueIds">
  787. <template v-slot:selected="{ selectedItems }" class="form-item">
  788. <view class="slot-box flex flex-row items-center justify-end">
  789. <view
  790. v-for="item in selectedItems"
  791. :key="item.value"
  792. class="slot-content-item selected items-center justify-start">
  793. {{ item.text }}
  794. <uni-icons
  795. type="close"
  796. size="18"
  797. color="#909399"
  798. v-if="!formDisable"
  799. @click="removeSelectedItem(platformId, item.value)"></uni-icons>
  800. </view>
  801. <view v-if="selectedItems.length == 0" class="slot-content-item items-center justify-start">
  802. {{ selectPlaceholder }}
  803. </view>
  804. </view>
  805. </template>
  806. <template v-slot:option="{ item, itemSelected }">
  807. <view class="slot-item">
  808. <uni-list-item class="flex-row items-center justify-between">
  809. <template v-slot:body>
  810. <text class="slot-item-text justify-start" :style="{ color: itemSelected ? '#007aff' : '#303133' }">
  811. {{ item.text }}
  812. </text>
  813. </template>
  814. <template v-slot:footer>
  815. <uni-icons
  816. class="items-center justify-center"
  817. v-if="itemSelected"
  818. type="checkmarkempty"
  819. size="20"
  820. color="#007aff"></uni-icons>
  821. </template>
  822. </uni-list-item>
  823. </view>
  824. </template>
  825. <template v-slot:empty>
  826. <view class="empty-box">
  827. <view>{{ $t('common.noData') }}</view>
  828. </view>
  829. </template>
  830. </uni-data-select>
  831. </uni-forms-item>
  832. <uni-forms-item
  833. class="form-item"
  834. v-for="(attr, index) in form[platformId].extProperty"
  835. :key="index"
  836. :label="`${attr.name}(${attr.unit}):`"
  837. :required="attr.required == 1 && isRequired"
  838. :name="[platformId, 'extProperty', index, 'actualValue']"
  839. :rules="[{ required: true, errorMessage: `${t('operation.PleaseFillIn')}${attr.name}` }]">
  840. <uni-easyinput
  841. v-if="attr.dataType === 'double'"
  842. class="digit-item"
  843. type="digit"
  844. :inputBorder="false"
  845. :clearable="true"
  846. :styles="{ disableColor: '#fff' }"
  847. :placeholder="inputPlaceholder"
  848. :disabled="formDisable"
  849. v-model="form[platformId].extProperty[index].actualValue" />
  850. <uni-easyinput
  851. v-else
  852. style="text-align: right"
  853. :styles="{ disableColor: '#fff' }"
  854. :inputBorder="false"
  855. :clearable="true"
  856. :placeholder="inputPlaceholder"
  857. :disabled="formDisable"
  858. v-model="form[platformId].extProperty[index].actualValue"
  859. :type="'textarea'"></uni-easyinput>
  860. </uni-forms-item>
  861. </div>
  862. <div v-for="(fuel, index) in form.reportFuels" :key="index" class="content">
  863. <div class="content-title">{{ fuel.deviceCode }}</div>
  864. <uni-forms-item class="form-item" label="设备名称:">
  865. <view style="text-align: right; width: 100%; padding-right: 10px; box-sizing: border-box">{{
  866. fuel.deviceName
  867. }}</view>
  868. </uni-forms-item>
  869. <uni-forms-item class="form-item" label="发生日期:">
  870. <view style="text-align: right; width: 100%; padding-right: 10px; box-sizing: border-box">{{
  871. dayjs(fuel.queryDate).format('YYYY-MM-DD')
  872. }}</view>
  873. </uni-forms-item>
  874. <uni-forms-item class="form-item" label="中航北斗油耗(L):">
  875. <view style="text-align: right; width: 100%; padding-right: 10px; box-sizing: border-box">{{
  876. (fuel.zhbdFuel ?? 0).toFixed(2)
  877. }}</view>
  878. </uni-forms-item>
  879. <uni-forms-item class="form-item" label="实际油耗(L):">
  880. <uni-easyinput
  881. class="digit-item"
  882. type="number"
  883. :inputBorder="false"
  884. :clearable="false"
  885. :styles="{ disableColor: '#fff' }"
  886. :placeholder="inputPlaceholder"
  887. :disabled="formDisable"
  888. v-model="fuel.customFuel" />
  889. </uni-forms-item>
  890. </div>
  891. </uni-forms>
  892. </scroll-view>
  893. <lsj-upload ref="uploadRef"></lsj-upload>
  894. <tpf-time-range
  895. ref="timeRangeRef"
  896. :startTime="startTime"
  897. :startDefaultTime="startDefaultTime"
  898. :endTime="endTime"
  899. :endDefaultTime="endDefaultTime"
  900. @timeRange="timeRange"></tpf-time-range>
  901. <device-transfer
  902. ref="deviceTransferRef"
  903. :allList="reportData.selectedDevices"
  904. :selected="form.deviceIds"
  905. @confirm="handleTransferChange" />
  906. </template>
  907. <style lang="scss" scoped>
  908. @import '@/style/work-order-form.scss';
  909. .report-form {
  910. height: 100%;
  911. color: #333;
  912. }
  913. :deep(.uni-textarea-textarea:disabled),
  914. :deep(.uni-input-input:disabled) {
  915. color: #333;
  916. }
  917. :deep(.uni-date-x) {
  918. color: #333;
  919. }
  920. .digit-item {
  921. text-align: right;
  922. :deep(.uni-easyinput__content-input) {
  923. padding-right: 10px;
  924. }
  925. }
  926. .well-form-item {
  927. background-color: #fff;
  928. border-bottom: none;
  929. padding: 0 10px;
  930. }
  931. .form-item-select {
  932. width: 100%;
  933. }
  934. .form-item-btn {
  935. margin: 10px 0 0 0;
  936. width: 80px;
  937. text-align: center;
  938. }
  939. .digit-item {
  940. text-align: right;
  941. :deep(.uni-easyinput__content-input) {
  942. padding-right: 10px;
  943. }
  944. }
  945. .file-picker-container {
  946. width: 100%;
  947. }
  948. .file-picker-btn {
  949. margin-left: unset;
  950. margin-right: unset;
  951. }
  952. .file-size-limit {
  953. font-size: 10px;
  954. color: #ff4500;
  955. padding: 0 10px;
  956. }
  957. .file-parent {
  958. display: flex;
  959. flex-direction: column;
  960. align-items: end;
  961. padding: 10px 0;
  962. }
  963. .file-list {
  964. display: flex;
  965. flex-direction: column;
  966. align-items: end;
  967. justify-content: end;
  968. max-width: 300px;
  969. & > view {
  970. width: 100%;
  971. padding: 10px;
  972. padding-right: 0;
  973. display: flex;
  974. align-items: center;
  975. justify-content: space-between;
  976. }
  977. }
  978. .slot-box {
  979. width: 100%;
  980. flex-wrap: wrap;
  981. }
  982. .slot-content-item {
  983. font-size: 12px;
  984. color: #6a6a6a;
  985. &.selected {
  986. background: #f4f4f5;
  987. // color: #909399;
  988. margin: 5px;
  989. padding: 5px;
  990. border-radius: 3px;
  991. }
  992. }
  993. .slot-item-text {
  994. width: 90%;
  995. }
  996. .content {
  997. margin-top: 10px;
  998. padding: 8px;
  999. background-color: #fff;
  1000. }
  1001. .content-title {
  1002. font-weight: 600;
  1003. height: 44px;
  1004. display: flex;
  1005. align-items: center;
  1006. border-bottom: 1px dashed #cacccf;
  1007. }
  1008. .item-content {
  1009. display: flex;
  1010. align-items: center;
  1011. justify-content: end;
  1012. }
  1013. .item-end {
  1014. display: flex;
  1015. align-items: end;
  1016. justify-content: end;
  1017. }
  1018. .item-col {
  1019. justify-content: end;
  1020. }
  1021. .time-range-item {
  1022. margin: 10px;
  1023. }
  1024. </style>