Sin descripción

index.vue 33KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184
  1. <script lang="ts" setup>
  2. import { computed, defineComponent, h, onMounted, ref } from 'vue';
  3. import { useRoute } from 'vue-router';
  4. // @ts-ignore
  5. import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
  6. // @ts-ignore
  7. import { useTabs } from '@vben/hooks';
  8. import { useUserStore } from '@vben/stores';
  9. import dayjs from 'dayjs';
  10. import {
  11. ElButton,
  12. ElDescriptions,
  13. ElDescriptionsItem,
  14. ElMessage,
  15. ElMessageBox,
  16. ElTag,
  17. } from 'element-plus';
  18. import { keyBy } from 'lodash-es';
  19. // @ts-ignore
  20. import {
  21. closeTask,
  22. getTaskDetail,
  23. setPlanTimeAPI,
  24. transferTask,
  25. } from '#/api/schedule';
  26. // @ts-ignore
  27. import { confirmDoneTask, handleLicense } from '#/api/task/confirm';
  28. // @ts-ignore
  29. import AttachmentViewer from '#/components/AttachmentViewer.vue';
  30. // @ts-ignore
  31. import { SelectUsers } from '#/components/selectUsers';
  32. // @ts-ignore: 忽略
  33. // @ts-ignore
  34. import { taskLevelOptions, taskStatusOptions } from '#/utils/dicts';
  35. // @ts-ignore: 应急防护月检
  36. import emergencyInfoPage from '#/views/oilstation/emergency/index.vue';
  37. // @ts-ignore: 灭火器档案
  38. import extinguisherInfoPage from '#/views/oilstation/extinguisher/index.vue';
  39. // @ts-ignore: 过滤器档案
  40. import filterInfoPage from '#/views/oilstation/filter/index.vue';
  41. // @ts-ignore: 急救箱档案
  42. import firstaidkitInfoPage from '#/views/oilstation/firstaidkit/index.vue';
  43. // @ts-ignore: 证照档案
  44. import idPhotoInfoPage from '#/views/oilstation/idphoto/index.vue';
  45. // @ts-ignore
  46. import AdditiveComponent from './components/additive.vue';
  47. // @ts-ignore
  48. import BiddingComponent from './components/bidding.vue';
  49. // @ts-ignore
  50. import CheckComponent from './components/check.vue';
  51. // 引入检查项组件、评论组件和查阅组件
  52. // @ts-ignore
  53. import CommentComponent from './components/comment.vue';
  54. // @ts-ignore
  55. import CompetitionComponent from './components/competition.vue';
  56. // @ts-ignore
  57. import EmergencyComponent from './components/emergency.vue';
  58. // @ts-ignore
  59. import EntranceComponent from './components/entrance.vue';
  60. // 引入班组日志组件
  61. // @ts-ignore
  62. import GroupLogComponent from './components/group-log.vue';
  63. // @ts-ignore
  64. import GunCheck2Component from './components/gun-check2.vue';
  65. // @ts-ignore
  66. import GunCheckComponent from './components/gun-check.vue';
  67. // @ts-ignore
  68. import JoinEmergencyComponent from './components/join-emergency.vue';
  69. // @ts-ignore
  70. import MeetingComponent from './components/meeting.vue';
  71. // @ts-ignore
  72. import ManagementPlanComponent from './components/plan.vue';
  73. // @ts-ignore
  74. import SalesSurveyComponent from './components/sales-survey.vue';
  75. // @ts-ignore
  76. import TankWaterComponent from './components/tank-water.vue';
  77. // @ts-ignore
  78. import WeeklyEvaluationComponent from './components/weekly-evaluation.vue';
  79. // @ts-ignore
  80. import WeeklyReportComponent from './components/weekly-report.vue';
  81. import { mobileTaskTypes } from './config-data';
  82. import {
  83. AdditiveDrawer,
  84. CompetitionDrawer,
  85. EmergencyWyDrawer,
  86. EntranceDrawer,
  87. GroupLogDrawer,
  88. GunCheck2Drawer,
  89. GunCheckDrawer,
  90. JoinEmergencyDrawer,
  91. ManagementPlanDrawer,
  92. MeetingDrawer,
  93. OperatingPlanDrawer,
  94. SalesSurveyDrawer,
  95. TankWaterDrawer,
  96. UploadDrawer,
  97. WeeklyEvaluationDrawer,
  98. WeeklyReportDrawer,
  99. } from './drawer';
  100. const userStore = useUserStore();
  101. const { refreshTab } = useTabs();
  102. const route = useRoute();
  103. // const router = useRouter();
  104. const taskId = ref(Number(route.params.id));
  105. // 图片预览相关
  106. const previewVisible = ref(false);
  107. const previewImages = ref<string[]>([]);
  108. // 关闭图片预览
  109. const closePreview = () => {
  110. previewVisible.value = false;
  111. };
  112. // 下载文件
  113. const downloadFile = (fileUrl: string, fileName: string) => {
  114. // 创建临时链接
  115. const link = document.createElement('a');
  116. // 设置下载属性,强制所有文件下载
  117. link.download = fileName;
  118. // 使用 rel="noopener noreferrer" 提高安全性
  119. link.rel = 'noopener noreferrer';
  120. // 对于图片等浏览器会直接打开的文件,使用 fetch 方式获取 blob 再下载
  121. fetch(fileUrl)
  122. .then((response) => response.blob())
  123. .then((blob) => {
  124. // 创建 blob URL
  125. const blobUrl = URL.createObjectURL(blob);
  126. link.href = blobUrl;
  127. // 触发下载
  128. document.body.append(link);
  129. link.click();
  130. // 清理
  131. link.remove();
  132. // 释放 blob URL
  133. setTimeout(() => {
  134. URL.revokeObjectURL(blobUrl);
  135. }, 100);
  136. })
  137. .catch((error) => {
  138. console.error('下载失败:', error);
  139. // 如果 fetch 失败,回退到直接下载
  140. link.href = fileUrl;
  141. document.body.append(link);
  142. link.click();
  143. link.remove();
  144. });
  145. };
  146. // 状态标签配置
  147. const statusConfig: any = keyBy(taskStatusOptions, 'value');
  148. // 优先级标签配置
  149. const priorityConfig: any = keyBy(taskLevelOptions, 'value');
  150. // 关闭任务
  151. const handleCloseTask = () => {
  152. // console.log('关闭任务');
  153. // 二次确认并输入关闭原因
  154. ElMessageBox.prompt('请输入关闭原因', '确认关闭', {
  155. confirmButtonText: '确定',
  156. cancelButtonText: '取消',
  157. type: 'warning',
  158. }).then((reason) => {
  159. // 关闭任务
  160. closeTask({
  161. taskId: taskId.value,
  162. cancelReason: reason.value,
  163. }).then(() => {
  164. ElMessage.success('任务关闭成功');
  165. // 刷新任务详情
  166. getTaskDetail(taskId.value).then((res) => {
  167. taskInfo.value = res;
  168. });
  169. });
  170. });
  171. };
  172. const getTaskTime = () => {
  173. return `${dayjs(taskInfo.value.startTime).format('MM-DD HH:mm')}~${dayjs(taskInfo.value.endTime).format('MM-DD HH:mm')}`;
  174. };
  175. const executorRef: any = ref(null);
  176. const [ExecutorModal, executorModalApi] = useVbenModal({
  177. title: '选择考试执行人',
  178. width: 1000,
  179. height: 800,
  180. showCancelBtn: false,
  181. onConfirm: async () => {
  182. const users = await executorRef.value.confirmSelection();
  183. if (users?.length) {
  184. if (users.length !== 1) {
  185. ElMessage.error('请选择一个执行人');
  186. return;
  187. }
  188. // 二次确认
  189. const confirm = await ElMessageBox.confirm(
  190. `确认将任务分配给 ${users[0].name} 吗?`,
  191. '确认分配',
  192. {
  193. confirmButtonText: '确定',
  194. cancelButtonText: '取消',
  195. type: 'warning',
  196. },
  197. );
  198. if (confirm !== 'confirm') {
  199. return;
  200. }
  201. transferTask({
  202. id: taskId.value,
  203. executorId: users[0].id,
  204. }).then(() => {
  205. ElMessage.success('任务转交成功');
  206. // 刷新任务详情
  207. getTaskDetail(taskId.value).then((res) => {
  208. taskInfo.value = res;
  209. });
  210. });
  211. executorModalApi.close();
  212. }
  213. },
  214. });
  215. // 转交任务
  216. const handleTransferTask = () => {
  217. // console.log('转交任务');
  218. executorModalApi.open();
  219. };
  220. const taskInfo: any = ref({});
  221. const taskOther: any = ref({
  222. taskTypeName: '',
  223. });
  224. const taskResult: any = ref({
  225. content: '',
  226. images: [],
  227. files: [],
  228. fileNames: [],
  229. });
  230. const oneSelf = computed(() => {
  231. return taskInfo.value.executorId === Number(userStore.userInfo?.userId);
  232. });
  233. const buttons = ref({
  234. deal: false,
  235. transfer: false,
  236. close: false,
  237. });
  238. const init = async () => {
  239. const res = await getTaskDetail(taskId.value);
  240. taskInfo.value = res;
  241. if (!taskInfo.value.status) {
  242. taskInfo.value.status = 0;
  243. }
  244. if (taskInfo.value.status === 1) {
  245. const userId = Number(userStore.userInfo?.userId);
  246. const isExecutor = taskInfo.value.executorId === userId;
  247. const isCreator = userStore?.userInfo?.username === taskInfo.value.createBy;
  248. const isHeadquarters = userStore?.userInfo?.post?.type === '"headquarters"';
  249. const isMobileTask = mobileTaskTypes.includes(taskInfo.value.formType);
  250. if (isExecutor && !isMobileTask) {
  251. buttons.value.deal = true;
  252. }
  253. if (isCreator || isHeadquarters || isExecutor) {
  254. if (taskInfo.value.executorId !== userId) {
  255. buttons.value.close = true;
  256. }
  257. if (taskInfo.value.taskTemplate?.isMust === 0) {
  258. buttons.value.close = true;
  259. }
  260. if (taskInfo.value.authStatus === 0 && taskInfo.value.isLicensable) {
  261. buttons.value.transfer = true;
  262. }
  263. }
  264. }
  265. taskOther.value.taskTypeName = taskInfo.value?.formTypeName || '-';
  266. if (!taskInfo.value.executorName && taskInfo.value.taskList?.length) {
  267. taskInfo.value.executorName = taskInfo.value.taskList[0].executorName;
  268. }
  269. if (taskInfo.value.formType === 'photo') {
  270. const result = taskInfo.value?.taskList?.[0] || {};
  271. if (result.remark) {
  272. taskResult.value.content = result.remark;
  273. }
  274. if (result?.attachmentUrl?.length) {
  275. taskResult.value.images = result.attachmentUrl;
  276. }
  277. } else if (taskInfo.value.formType === 'upload') {
  278. // attachmentUrl
  279. const result = taskInfo.value?.taskList?.[0] || {};
  280. if (result.remark) {
  281. taskResult.value.content = result.remark;
  282. }
  283. if (result?.attachmentUrl?.length) {
  284. taskResult.value.files = result.attachmentUrl;
  285. taskResult.value.fileNames = result.attachmentName.split(',');
  286. }
  287. } else {
  288. // taskResult.value = taskInfo.value?.taskList?.[0] || {};
  289. }
  290. if (taskInfo.value?.taskTemplate) {
  291. taskOther.value.taskFrequencyName =
  292. taskInfo.value?.taskTemplate?.taskFrequencyName || '-';
  293. }
  294. };
  295. // 动态组件包装器,根据 taskInfo 动态渲染正确的抽屉组件
  296. const DynamicDrawerComponent = defineComponent({
  297. setup() {
  298. return () => {
  299. let Component = null;
  300. if (taskInfo.value.formType === 'upload') {
  301. Component = UploadDrawer;
  302. } else if (taskInfo.value.formType === 'form') {
  303. switch (taskInfo.value.subFormType) {
  304. case 'additive_inventory':
  305. case 'additive_inventory2': {
  306. Component = AdditiveDrawer;
  307. break;
  308. }
  309. case 'competition_price_survey': {
  310. Component = CompetitionDrawer;
  311. break;
  312. }
  313. case 'competition_sales_survey': {
  314. Component = SalesSurveyDrawer;
  315. break;
  316. }
  317. case 'emergency_wy': {
  318. Component = EmergencyWyDrawer;
  319. break;
  320. }
  321. case 'employee_weekly_evaluation': {
  322. Component = WeeklyEvaluationDrawer;
  323. break;
  324. }
  325. case 'entrance_rate_survey': {
  326. Component = EntranceDrawer;
  327. break;
  328. }
  329. case 'filter_maintenance': {
  330. break;
  331. }
  332. case 'group_log': {
  333. Component = GroupLogDrawer;
  334. break;
  335. }
  336. case 'management_plan': {
  337. Component = ManagementPlanDrawer;
  338. break;
  339. }
  340. case 'meeting': {
  341. Component = MeetingDrawer;
  342. break;
  343. }
  344. case 'oil_gun_self_test': {
  345. Component = GunCheckDrawer;
  346. break;
  347. }
  348. case 'oil_gun_self_test_height': {
  349. Component = GunCheck2Drawer;
  350. break;
  351. }
  352. case 'operating_plan': {
  353. Component = OperatingPlanDrawer;
  354. break;
  355. }
  356. case 'operating_weekly_report': {
  357. Component = WeeklyReportDrawer;
  358. break;
  359. }
  360. case 'participate_emergency_wy':
  361. case 'participate_oil_station_meeting': {
  362. Component = JoinEmergencyDrawer;
  363. break;
  364. }
  365. case 'tank_water': {
  366. Component = TankWaterDrawer;
  367. break;
  368. }
  369. default: {
  370. // ElMessage.error('当前任务无法处理');
  371. Component = null;
  372. }
  373. }
  374. }
  375. return Component ? h(Component) : null;
  376. };
  377. },
  378. });
  379. const [DealDrawer, dealDrawerApi] = useVbenDrawer({
  380. connectedComponent: DynamicDrawerComponent,
  381. onConfirm: () => {
  382. // 刷新当前页面
  383. refreshTab();
  384. },
  385. });
  386. const dealAnnualExaminationTask = async () => {
  387. // 二次弹框确认
  388. const confirm = await ElMessageBox.confirm(
  389. `确认完成任务 ${taskInfo.value.taskName} 吗?`,
  390. '确认完成',
  391. {
  392. confirmButtonText: '确定',
  393. cancelButtonText: '取消',
  394. type: 'warning',
  395. },
  396. );
  397. if (confirm !== 'confirm') {
  398. return;
  399. }
  400. try {
  401. await handleLicense(taskInfo.value.id || 0);
  402. init();
  403. ElMessage.success('处理任务成功');
  404. } catch {
  405. ElMessage.error('处理任务失败');
  406. }
  407. };
  408. const dealTaskEvent = async () => {
  409. // 二次弹框确认
  410. const confirm = await ElMessageBox.confirm(
  411. `确认完成任务 ${taskInfo.value.taskName} 吗?`,
  412. '确认完成',
  413. {
  414. confirmButtonText: '确定',
  415. cancelButtonText: '取消',
  416. type: 'warning',
  417. },
  418. );
  419. if (confirm !== 'confirm') {
  420. return;
  421. }
  422. try {
  423. await confirmDoneTask(taskInfo.value.id || 0);
  424. init();
  425. ElMessage.success('确认完成任务成功');
  426. } catch {
  427. ElMessage.error('确认完成任务失败');
  428. }
  429. };
  430. /**
  431. * 处理任务
  432. */
  433. const dealEvent = async () => {
  434. let params: any = {};
  435. let isModelPage = false;
  436. switch (taskInfo.value.formType) {
  437. case 'confirm': {
  438. await dealTaskEvent();
  439. return;
  440. }
  441. case 'form': {
  442. if (
  443. ['annualExamination_task', 'replacement_task'].includes(
  444. taskInfo.value.subFormType,
  445. )
  446. ) {
  447. try {
  448. await dealAnnualExaminationTask();
  449. } catch {
  450. ElMessage.error('处理任务失败');
  451. }
  452. return;
  453. }
  454. params = {
  455. taskId: taskInfo.value.taskList?.[0]?.id || '',
  456. taskName: taskInfo.value.taskName,
  457. taskList: taskInfo.value.taskList || [],
  458. remark: taskResult.value.content,
  459. stationId: taskInfo.value.stationId || 0,
  460. subFormType: taskInfo.value.subFormType,
  461. attachmentUrl: taskResult.value.images,
  462. };
  463. if (
  464. [
  465. 'ambulance_check',
  466. 'emergency_protection_monthly_check',
  467. 'filter_maintenance',
  468. 'fire_extinguisher_check',
  469. ].includes(taskInfo.value.subFormType)
  470. ) {
  471. isModelPage = true;
  472. }
  473. break;
  474. }
  475. case 'upload': {
  476. params = {
  477. taskId: taskInfo.value.taskList?.[0]?.id || '',
  478. taskName: taskInfo.value.taskName,
  479. };
  480. break;
  481. }
  482. default: {
  483. return ElMessage.error('当前任务无法处理');
  484. }
  485. }
  486. if (isModelPage) {
  487. pageModalApi.open();
  488. } else {
  489. dealDrawerApi.setData(params).open();
  490. }
  491. };
  492. const [PageModal, pageModalApi] = useVbenModal({
  493. title: taskInfo.value.taskName || '确认任务',
  494. fullscreen: true,
  495. onConfirm: async () => {
  496. // 二次提醒确认是否完成任务
  497. await dealTaskEvent();
  498. pageModalApi.close();
  499. },
  500. });
  501. onMounted(async () => {
  502. await init();
  503. });
  504. const PlannedTimeValue = ref('') as any;
  505. const [PlannedTimeModal, plannedTimeModalApi] = useVbenModal({
  506. title: '修改计划时间',
  507. showCancelBtn: false,
  508. contentClass: 'plannedTimeModalclass',
  509. onConfirm: async () => {
  510. if (
  511. new Date(PlannedTimeValue.value).getTime() <
  512. new Date(taskInfo.value.startTime).getTime() ||
  513. new Date(PlannedTimeValue.value).getTime() >
  514. new Date(taskInfo.value.endTime).getTime()
  515. ) {
  516. ElMessage.error('计划时间必须在任务开始时间和结束时间之间');
  517. return;
  518. }
  519. try {
  520. await setPlanTimeAPI({
  521. taskId: taskId.value,
  522. planTime: PlannedTimeValue.value,
  523. });
  524. await init();
  525. ElMessage.success('修改计划时间成功');
  526. plannedTimeModalApi.close();
  527. } catch {
  528. ElMessage.error('修改计划时间失败');
  529. }
  530. },
  531. });
  532. const handlePlannedTimeClick = () => {
  533. PlannedTimeValue.value = taskInfo.value.displayDate;
  534. plannedTimeModalApi.open();
  535. };
  536. </script>
  537. <template>
  538. <Page title="" :auto-content-height="true">
  539. <template #description>
  540. <!-- 任务头部信息 -->
  541. <div class="flex items-center justify-between">
  542. <div class="flex items-center">
  543. <div
  544. class="task-icon mr-4 flex h-12 w-12 items-center justify-center rounded-full bg-green-500"
  545. >
  546. <img
  547. :src="taskInfo.iconUrl ? taskInfo.iconUrl[0] : '/icon/4.png'"
  548. alt="任务图标"
  549. class="h-6 w-6"
  550. />
  551. </div>
  552. <div>
  553. <h2 class="mb-1 text-xl font-bold">
  554. {{ taskInfo.taskName }}
  555. <span
  556. v-if="mobileTaskTypes.includes(taskInfo.formType)"
  557. class="ml-2 text-sm text-red-500"
  558. >请在手机端钉钉完成
  559. </span>
  560. </h2>
  561. <div class="flex items-center space-x-2">
  562. <ElTag
  563. :type="priorityConfig[taskInfo.level]?.color || 'info'"
  564. size="small"
  565. effect="dark"
  566. >
  567. {{ priorityConfig[taskInfo.level]?.label || taskInfo.level }}
  568. </ElTag>
  569. <ElTag
  570. :type="
  571. statusConfig[`${taskInfo?.status > 0 || 0}`]?.color || 'info'
  572. "
  573. size="small"
  574. effect="dark"
  575. >
  576. {{
  577. statusConfig[`${taskInfo.status > 0 || 0}`]?.label ||
  578. taskInfo.status
  579. }}
  580. <!-- {{ taskInfo.status }} -->
  581. </ElTag>
  582. <ElTag
  583. v-if="taskInfo.isLicensable && taskInfo.authStatus === 0"
  584. size="small"
  585. effect="dark"
  586. color="#215ACD"
  587. >
  588. 可授权
  589. </ElTag>
  590. <ElTag
  591. v-if="taskInfo.isLicensable && taskInfo.authStatus === 1"
  592. size="small"
  593. color="#339169"
  594. effect="dark"
  595. >
  596. 已授权
  597. </ElTag>
  598. <ElTag
  599. v-if="taskInfo.isLicensable && taskInfo.authStatus === 2"
  600. size="small"
  601. color="#EB5E12"
  602. effect="dark"
  603. >
  604. 被授权
  605. </ElTag>
  606. </div>
  607. </div>
  608. </div>
  609. <div class="flex items-center space-x-4">
  610. <ElButton v-if="buttons.deal" type="primary" @click="dealEvent">
  611. 处理任务
  612. </ElButton>
  613. <ElButton
  614. type="primary"
  615. v-if="buttons.transfer"
  616. @click="handleTransferTask"
  617. >
  618. 转交任务
  619. </ElButton>
  620. <ElButton v-if="buttons.close" type="danger" @click="handleCloseTask">
  621. 关闭任务
  622. </ElButton>
  623. </div>
  624. </div>
  625. </template>
  626. <!-- <div>
  627. <span style="font-size: 20px; font-weight: 400">运营异常-任务详情</span>
  628. </div> -->
  629. <div class="boxdev">
  630. <div>
  631. <!-- 基础信息组件 -->
  632. <ElCard>
  633. <template #header>
  634. <div class="flex items-center gap-4">
  635. <div
  636. style="width: 4px; height: 12px; background-color: #215acd"
  637. ></div>
  638. <span
  639. class="text-lg font-bold text-gray-800"
  640. style="font-size: 14px; font-weight: 600"
  641. >
  642. 任务信息
  643. </span>
  644. </div>
  645. </template>
  646. <ElDescriptions class="task-info" :column="4">
  647. <ElDescriptionsItem label="场站:">
  648. {{ taskInfo.stationName || '-' }}
  649. </ElDescriptionsItem>
  650. <ElDescriptionsItem label="标准指引:">
  651. {{ taskInfo.standardGuide || '-' }}
  652. </ElDescriptionsItem>
  653. <ElDescriptionsItem label="执行人:">
  654. {{ taskInfo.executorName || '-' }}
  655. </ElDescriptionsItem>
  656. <ElDescriptionsItem label="计划时间:">
  657. <ElButton type="primary" text @click="handlePlannedTimeClick">
  658. {{ taskInfo.displayDate || '-' }}
  659. </ElButton>
  660. </ElDescriptionsItem>
  661. <ElDescriptionsItem label="创建:">
  662. {{ taskInfo.createBy || '' }}
  663. </ElDescriptionsItem>
  664. <ElDescriptionsItem label="任务类型:">
  665. {{ taskInfo.taskTypeName || '-' }}
  666. </ElDescriptionsItem>
  667. <ElDescriptionsItem label="任务负责人:">
  668. {{ taskInfo.taskLeaderName || '-' }}
  669. </ElDescriptionsItem>
  670. <ElDescriptionsItem label="任务频率:">
  671. {{ taskOther.taskFrequencyName || '-' }}
  672. </ElDescriptionsItem>
  673. <!-- <ElDescriptionsItem label="负责人:">
  674. {{ taskInfo.executorName }}
  675. </ElDescriptionsItem> -->
  676. <ElDescriptionsItem label="任务时间:" :span="4">
  677. {{ getTaskTime() || '-' }}
  678. </ElDescriptionsItem>
  679. <ElDescriptionsItem label="任务描述:" :span="4">
  680. <div v-html="taskInfo.description"></div>
  681. </ElDescriptionsItem>
  682. <ElDescriptionsItem
  683. v-if="
  684. taskInfo.descriptionFilesUrl &&
  685. taskInfo.descriptionFilesUrl.length > 0
  686. "
  687. label="详情附件:"
  688. :span="4"
  689. >
  690. <AttachmentViewer :files="taskInfo.descriptionFilesUrl" />
  691. </ElDescriptionsItem>
  692. <ElDescriptionsItem
  693. v-if="taskResult.handleContent || taskInfo.cancelReason"
  694. label="处理情况:"
  695. :span="4"
  696. >
  697. {{ taskResult.handleContent || taskInfo.cancelReason || '' }}
  698. </ElDescriptionsItem>
  699. <ElDescriptionsItem label="处理时间:" :span="4">
  700. {{ taskInfo.completeTime || taskInfo.cancelTime || '-' }}
  701. </ElDescriptionsItem>
  702. <ElDescriptionsItem
  703. v-if="taskResult.files && taskResult.files.length > 0"
  704. label="处理附件:"
  705. :span="4"
  706. >
  707. <div class="attachment-list">
  708. <div
  709. v-for="(file, index) in taskResult.files"
  710. :key="index"
  711. class="attachment-item"
  712. @click="
  713. () =>
  714. downloadFile(
  715. file,
  716. taskResult.fileNames[index] || `附件${+index + 1}`,
  717. )
  718. "
  719. >
  720. <span class="attachment-name">{{
  721. taskResult.fileNames[index] || `附件${+index + 1}`
  722. }}</span>
  723. </div>
  724. </div>
  725. </ElDescriptionsItem>
  726. </ElDescriptions>
  727. </ElCard>
  728. <!--竞价-->
  729. <ElCard v-if="taskInfo.formType === 'bidding'">
  730. <template #header>
  731. <div class="flex items-center gap-4">
  732. <div
  733. style="width: 4px; height: 12px; background-color: #215acd"
  734. ></div>
  735. <span
  736. class="text-lg font-bold text-gray-800"
  737. style="font-size: 14px; font-weight: 600"
  738. >
  739. 竞价信息
  740. </span>
  741. </div>
  742. </template>
  743. <BiddingComponent />
  744. </ElCard>
  745. <!-- 检查项组件 -->
  746. <ElCard v-if="taskInfo.formType === 'inspection'">
  747. <template #header>
  748. <div class="flex items-center gap-4">
  749. <div
  750. style="width: 4px; height: 12px; background-color: #215acd"
  751. ></div>
  752. <span
  753. class="text-lg font-bold text-gray-800"
  754. style="font-size: 14px; font-weight: 600"
  755. >
  756. 检查项
  757. </span>
  758. </div>
  759. </template>
  760. <CheckComponent :task-detail="taskInfo.taskList[0]" />
  761. </ElCard>
  762. <!-- 班组日志组件 -->
  763. <GroupLogComponent
  764. :task-detail="taskInfo.taskList[0]"
  765. v-if="
  766. taskInfo.formType === 'form' && taskInfo.subFormType === 'group_log'
  767. "
  768. />
  769. <!-- 管理计划组件 -->
  770. <ManagementPlanComponent
  771. :task-detail="taskInfo.taskList[0]"
  772. v-if="
  773. taskInfo.formType === 'form' &&
  774. taskInfo.subFormType === 'management_plan'
  775. "
  776. />
  777. <!-- 销售调查组件 -->
  778. <SalesSurveyComponent
  779. :task-detail="taskInfo.taskList"
  780. v-if="
  781. taskInfo.formType === 'form' &&
  782. taskInfo.subFormType === 'competition_sales_survey'
  783. "
  784. />
  785. <!-- 紧急情况组件 -->
  786. <EmergencyComponent
  787. :task-detail="taskInfo.taskList[0]"
  788. v-if="
  789. taskInfo.formType === 'form' &&
  790. taskInfo.subFormType === 'emergency_wy'
  791. "
  792. />
  793. <!-- 会议组件 -->
  794. <MeetingComponent
  795. :task-detail="taskInfo.taskList[0]"
  796. v-if="
  797. taskInfo.formType === 'form' && taskInfo.subFormType === 'meeting'
  798. "
  799. />
  800. <!-- 入场率调查组件 -->
  801. <EntranceComponent
  802. :task-list="taskInfo.taskList"
  803. v-if="
  804. taskInfo.formType === 'form' &&
  805. taskInfo.subFormType === 'entrance_rate_survey'
  806. "
  807. />
  808. <!-- 竞价结果组件 -->
  809. <CompetitionComponent
  810. :task-detail="taskInfo.taskList"
  811. v-if="
  812. taskInfo.formType === 'form' &&
  813. taskInfo.subFormType === 'competition_price_survey'
  814. "
  815. />
  816. <!-- 周评级组件 -->
  817. <WeeklyEvaluationComponent
  818. :task-detail="taskInfo.taskList"
  819. v-if="
  820. taskInfo.formType === 'form' &&
  821. taskInfo.subFormType === 'employee_weekly_evaluation'
  822. "
  823. />
  824. <!-- 枪试检查组件 -->
  825. <GunCheckComponent
  826. :task-detail="taskInfo.taskList[0]"
  827. v-if="
  828. taskInfo.formType === 'form' &&
  829. taskInfo.subFormType === 'oil_gun_self_test'
  830. "
  831. />
  832. <!-- 枪试检查高度型组件 -->
  833. <GunCheck2Component
  834. :task-detail="taskInfo.taskList[0]"
  835. v-if="
  836. taskInfo.formType === 'form' &&
  837. taskInfo.subFormType === 'oil_gun_self_test_height'
  838. "
  839. />
  840. <!-- 参与紧急情况组件 -->
  841. <JoinEmergencyComponent
  842. :task-detail="{
  843. ...taskInfo.taskList[0],
  844. subFormType: taskInfo.subFormType,
  845. }"
  846. v-if="
  847. taskInfo.formType === 'form' &&
  848. [
  849. 'participate_oil_station_meeting',
  850. 'participate_emergency_wy',
  851. ].includes(taskInfo.subFormType)
  852. "
  853. />
  854. <!-- 量缸试水组件 -->
  855. <TankWaterComponent
  856. :task-detail="taskInfo.taskList[0]"
  857. v-if="
  858. taskInfo.formType === 'form' &&
  859. taskInfo.subFormType === 'tank_water'
  860. "
  861. />
  862. <!-- 处理信息组件 -->
  863. <AdditiveComponent
  864. :task-detail="taskInfo.taskList"
  865. v-if="
  866. taskInfo.formType === 'form' &&
  867. ['additive_inventory', 'additive_inventory2'].includes(
  868. taskInfo.subFormType,
  869. )
  870. "
  871. />
  872. <!-- 周报信息 -->
  873. <WeeklyReportComponent
  874. :task-detail="taskInfo.taskList[0]"
  875. v-if="
  876. taskInfo.formType === 'form' &&
  877. taskInfo.subFormType === 'operating_weekly_report'
  878. "
  879. />
  880. </div>
  881. <div style="width: 412px" v-if="taskInfo.status === 3">
  882. <!-- 评论组件 -->
  883. <ElCard>
  884. <template #header>
  885. <div class="flex items-center gap-4">
  886. <div
  887. style="width: 4px; height: 12px; background-color: #215acd"
  888. ></div>
  889. <span
  890. class="text-lg font-bold text-gray-800"
  891. style="font-size: 14px; font-weight: 600"
  892. >
  893. 评论
  894. </span>
  895. </div>
  896. </template>
  897. <CommentComponent :task-id="taskId" :task-detail="taskInfo" />
  898. </ElCard>
  899. </div>
  900. </div>
  901. <!-- 图片预览组件 -->
  902. <ElImageViewer
  903. v-if="previewVisible"
  904. :url-list="previewImages"
  905. @close="closePreview"
  906. />
  907. <ExecutorModal class="w-[700px]">
  908. <SelectUsers
  909. ref="executorRef"
  910. :params="{
  911. stationId: taskInfo.stationId,
  912. }"
  913. :only-users="true"
  914. :user-params="{
  915. stationId: taskInfo.stationId,
  916. }"
  917. />
  918. </ExecutorModal>
  919. <PlannedTimeModal>
  920. <div>
  921. <span>计划时间:</span>
  922. <el-date-picker
  923. v-model="PlannedTimeValue"
  924. type="datetime"
  925. placeholder="请选择计划时间"
  926. value-format="YYYY-MM-DD HH:mm:ss"
  927. />
  928. </div>
  929. </PlannedTimeModal>
  930. <DealDrawer class="w-3/5" @reload="refreshTab()" />
  931. <PageModal>
  932. <!-- 应急防护月检 -->
  933. <emergencyInfoPage
  934. v-if="taskInfo.subFormType === 'emergency_protection_monthly_check'"
  935. />
  936. <!-- 灭火器档案 -->
  937. <extinguisherInfoPage
  938. v-if="taskInfo.subFormType === 'fire_extinguisher_check'"
  939. />
  940. <!-- 急救箱档案 -->
  941. <firstaidkitInfoPage v-if="taskInfo.subFormType === 'ambulance_check'" />
  942. <!-- 过滤器档案 -->
  943. <filterInfoPage v-if="taskInfo.subFormType === 'filter_maintenance'" />
  944. <!-- 证照档案 -->
  945. <idPhotoInfoPage v-if="taskInfo.subFormType === 'idphoto_check'" />
  946. </PageModal>
  947. </Page>
  948. </template>
  949. <style scoped lang="scss">
  950. .boxdev {
  951. display: flex;
  952. gap: 20px;
  953. height: 100%;
  954. overflow: hidden;
  955. }
  956. .boxdev :deep(.el-card) {
  957. border: none !important;
  958. box-shadow: none !important;
  959. // background: transparent !important;
  960. }
  961. :global(.plannedTimeModalclass) {
  962. min-height: 0px !important;
  963. }
  964. .boxdev :deep(.el-card__header) {
  965. padding-top: 18px !important;
  966. padding-bottom: 4px !important;
  967. padding-left: 0px !important;
  968. border-bottom: none !important;
  969. }
  970. .boxdev :deep(.el-card__body) {
  971. padding-top: 0px !important;
  972. }
  973. ::v-deep .el-descriptions__body {
  974. background: #ffffff00 !important;
  975. }
  976. .boxdev > div:nth-child(1) {
  977. flex: 1;
  978. min-width: 0;
  979. overflow-y: auto;
  980. }
  981. .boxdev > div:nth-child(2) {
  982. width: 28%;
  983. overflow-y: auto;
  984. }
  985. .task-detail-container {
  986. width: 100%;
  987. height: 100%;
  988. }
  989. .task-header {
  990. display: flex;
  991. align-items: center;
  992. justify-content: space-between;
  993. }
  994. .task-icon {
  995. img {
  996. filter: invert(1);
  997. }
  998. }
  999. .task-info {
  1000. // border: 1px solid red;
  1001. :deep(.el-descriptions__label) {
  1002. font-size: 14px;
  1003. font-weight: 400;
  1004. color: var(--text-color-secondary);
  1005. }
  1006. :deep(.el-descriptions__content) {
  1007. font-size: 14px;
  1008. font-weight: 500;
  1009. color: var(--text-color-primary);
  1010. }
  1011. :deep(.el-descriptions__table) {
  1012. background: transparent !important;
  1013. }
  1014. }
  1015. .task-images {
  1016. display: flex;
  1017. gap: 12px;
  1018. margin-top: 4px;
  1019. .task-image {
  1020. width: 60px;
  1021. height: 60px;
  1022. cursor: pointer;
  1023. object-fit: cover;
  1024. border-radius: 4px;
  1025. transition: transform 0.2s;
  1026. }
  1027. }
  1028. .attachment-list {
  1029. display: flex;
  1030. flex-direction: column;
  1031. gap: 8px;
  1032. margin-top: 8px;
  1033. }
  1034. .attachment-item {
  1035. display: flex;
  1036. align-items: center;
  1037. gap: 12px;
  1038. padding: 12px 16px;
  1039. background-color: #f8f9fa;
  1040. border: 1px solid #e9ecef;
  1041. border-radius: 8px;
  1042. cursor: pointer;
  1043. transition: all 0.3s ease;
  1044. max-width: 100%;
  1045. overflow: hidden;
  1046. text-overflow: ellipsis;
  1047. white-space: nowrap;
  1048. box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  1049. &:hover {
  1050. background-color: #e7f5ff;
  1051. border-color: #91d5ff;
  1052. color: #409eff;
  1053. box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
  1054. transform: translateY(-1px);
  1055. }
  1056. &::before {
  1057. content: '';
  1058. display: inline-block;
  1059. width: 20px;
  1060. height: 20px;
  1061. background-color: #409eff;
  1062. border-radius: 4px;
  1063. background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff'%3E%3Cpath d='M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z'/%3E%3C/svg%3E");
  1064. background-size: 12px;
  1065. background-position: center;
  1066. background-repeat: no-repeat;
  1067. flex-shrink: 0;
  1068. }
  1069. }
  1070. .attachment-name {
  1071. font-size: 14px;
  1072. font-weight: 500;
  1073. color: #333;
  1074. transition: color 0.3s ease;
  1075. flex: 1;
  1076. overflow: hidden;
  1077. text-overflow: ellipsis;
  1078. white-space: nowrap;
  1079. }
  1080. .attachment-item:hover .attachment-name {
  1081. color: #409eff;
  1082. }
  1083. .attachment-item::after {
  1084. content: '';
  1085. display: inline-block;
  1086. width: 16px;
  1087. height: 16px;
  1088. background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23909399'%3E%3Cpath d='M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z'/%3E%3C/svg%3E");
  1089. background-size: 14px;
  1090. background-position: center;
  1091. background-repeat: no-repeat;
  1092. flex-shrink: 0;
  1093. transition: transform 0.3s ease;
  1094. }
  1095. .attachment-item:hover::after {
  1096. transform: translateX(4px);
  1097. background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23409eff'%3E%3Cpath d='M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z'/%3E%3C/svg%3E");
  1098. }
  1099. </style>