report-form.vue 44 KB

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