index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. <template>
  2. <home-container :customStyle="{ background: 'none' }" :customHomeStyle="{ overflow: 'hidden' }">
  3. <!-- <view class="mytodo-container"> -->
  4. <!-- <u-search v-model="keyword" placeholder="搜索任务" @search="handleSearch" @custom="handleSearch"
  5. @clear="handleClear" /> -->
  6. <h-tabs :tabs="tabList" v-model="currentTab" @change="handleTabChange" />
  7. <view class="mytodo-list">
  8. <scroll-view scroll-y="true" @refresherrefresh="onRefresh" refresher-enabled
  9. :refresher-triggered="refresherTriggered" @scrolltolower="loadMore" :style="{ height: '100%' }"
  10. enable-back-to-top="true">
  11. <u-checkbox-group class="checkbox-group">
  12. <list-card v-for="item in filteredList" :key="item.id" :showChecked="true"
  13. @click="navigateToDetail(item)">
  14. <template #checkbox
  15. v-if="canShowCheckbox(item)">
  16. <u-checkbox style="margin-top: 6rpx;" :checked="item.checked" :key="item.id"
  17. @change="itemChange(item)" />
  18. </template>
  19. <template #title>
  20. <view v-if="item.instance && item.instance.businessType === 'SEIZURE_REPORT'"
  21. class="list-title">
  22. {{ getFormData(item, 'forbiddenTypeText') }}
  23. <view v-if="item.isRead === '0'" class="unread-dot"></view>
  24. </view>
  25. <view v-else class="list-title">
  26. {{ `${getFormData(item, 'taskName')}-${item.taskName || item.title}` }}
  27. <view v-if="item.isRead === '0'" class="unread-dot"></view>
  28. </view>
  29. </template>
  30. <template #icon>
  31. <list-icon
  32. v-if="item.instance && item.instance.businessType === 'SEIZURE_REPORT' && getFormData(item, 'handlingMethod') === 'TRANSFER_TO_AIRPORT_POLICE'"
  33. :divStyle="{ backgroundColor: '#FEF8F8', color: '#F55B5D', border: '2rpx dashed #F55B5D' }"
  34. :text="'移交公安'" />
  35. </template>
  36. <template v-if="item.instance && item.instance.businessType === 'SEIZURE_REPORT'">
  37. <view class="list-row">
  38. <view class="list-label">位置:</view>
  39. <view class="list-value">{{ getFormData(item, 'securityLocationText') }}</view>
  40. </view>
  41. <view class="list-row" style="height: auto;align-items: flex-start;">
  42. <view class="list-label">查获人:</view>
  43. <view class="list-value list-value-clamp-2">{{ `${getFormData(item, 'reportTeamText')} /
  44. ${getFormData(item, 'inspectUserName')}` }}</view>
  45. </view>
  46. <view class="list-row">
  47. <view class="list-label">查获时间:</view>
  48. <view class="list-value">{{ getFormData(item, 'seizureTime') }}</view>
  49. </view>
  50. </template>
  51. <template v-else>
  52. <view class="list-row" v-if="item.instance &&
  53. item.instance.businessType">
  54. <view class="list-label">{{ `被检查${getLabel(item.instance &&
  55. item.instance.businessType)}:` }}</view>
  56. <view class="list-value">{{ getChecked(item) }}</view>
  57. </view>
  58. <view class="list-row">
  59. <view class="list-label">整改要求:</view>
  60. <!-- <view class="list-value list-value-clamp-1">{{ getFormData(item, 'rectificationSuggestions') }}</view> -->
  61. </view>
  62. <view class="list-row" style="height: auto;line-height: normal !important;">
  63. <view class="list-value-truncate">{{ getFormData(item, 'rectificationSuggestions') }}
  64. </view>
  65. </view>
  66. <view class="list-row">
  67. <view class="list-label">截止时间:</view>
  68. <view class="list-value">{{ getFormData(item, 'rectificationDeadline') }}</view>
  69. </view>
  70. </template>
  71. <template #footer>
  72. <div
  73. :class="['card-tag', item.instance && item.instance.businessType === 'SEIZURE_REPORT' ? 'seizure-tag' : 'rectification-tag']">
  74. {{ item.instance && item.instance.businessType === 'SEIZURE_REPORT' ? '查获' : '整改' }}
  75. </div>
  76. </template>
  77. </list-card>
  78. </u-checkbox-group>
  79. <!-- 加载状态提示 -->
  80. <view v-if="loading" class="load-more">
  81. <text>加载中...</text>
  82. </view>
  83. <view v-else-if="!hasMore && list.length > 0" class="no-more">
  84. <text>没有更多数据了</text>
  85. </view>
  86. <view v-else-if="list.length === 0" class="no-data">
  87. <text>暂无数据</text>
  88. </view>
  89. </scroll-view>
  90. </view>
  91. <view class="fixed-actions" v-if="currentTab === 'todo'">
  92. <u-checkbox-group>
  93. <u-checkbox :value="'all'" class="select-all" @change="handleSelectAll">全选</u-checkbox>
  94. </u-checkbox-group>
  95. <view class="batch-btn-container">
  96. <view class="custom-btn-white batch-btn" :disabled="selectedItems.length === 0" @click="batchReject">
  97. 批量驳回</view>
  98. <view class="custom-btn-normal batch-btn" :disabled="selectedItems.length === 0" @click="batchApprove">
  99. 批量通过
  100. </view>
  101. </view>
  102. </view>
  103. <!-- </view> -->
  104. </home-container>
  105. </template>
  106. <script>
  107. import HomeContainer from "@/components/HomeContainer.vue";
  108. import HTabs from "@/components/h-tabs/h-tabs.vue";
  109. import { listCheckApprovalCcDetails, listCheckPendingTasks, listCheckFinishedTasks, updateCheckApprovalCcDetails } from "@/api/myToDoList/myToDoList.js"
  110. import { approvePassBatch, approveRejectBatch,rectifyApprovePass,rectifyApproveReject } from "@/api/approve/approve.js";
  111. import { showMessageTabRedDot } from "@/utils/common.js"
  112. export default {
  113. components: { HomeContainer, HTabs },
  114. data() {
  115. return {
  116. keyword: '',
  117. filteredList: [],
  118. selectedItems: [],
  119. currentTab: 'todo',
  120. list: [],
  121. // 分页参数
  122. pageNum: 1,
  123. pageSize: 10,
  124. total: 0,
  125. loading: false,
  126. hasMore: true,
  127. // scroll-view下拉刷新状态控制
  128. refresherTriggered: false,
  129. // 待办任务未读数量
  130. todoUnreadCount: 0,
  131. msgUnreadCount: 0
  132. }
  133. },
  134. computed: {
  135. userInfoRoles() {
  136. return this.$store.state.user && this.$store.state.user.roles
  137. },
  138. tabList() {
  139. return [
  140. {
  141. type: 'msg',
  142. title: '消息',
  143. unreadCount: this.msgUnreadCount
  144. },
  145. {
  146. type: 'todo',
  147. title: '待办',
  148. unreadCount: this.todoUnreadCount
  149. },
  150. {
  151. type: 'done',
  152. title: '已办'
  153. }
  154. ];
  155. },
  156. // 判断是否显示复选框的条件
  157. showCheckboxCondition(item) {
  158. return this.canShowCheckbox(item);
  159. }
  160. },
  161. watch: {
  162. },
  163. methods: {
  164. // 判断是否显示复选框的条件
  165. canShowCheckbox(item) {
  166. return ((item.instance && item.instance.businessType === 'SEIZURE_REPORT') ||
  167. (item.instance && item.instance.businessType === 'DEPARTMENT_CHECK' &&
  168. this.userInfoRoles.includes('jingli'))) &&
  169. this.currentTab === 'todo';
  170. },
  171. // 根据任务类型分组
  172. groupTasksByType(selectedItems) {
  173. const seizureTaskIds = [];
  174. const departmentTaskIds = [];
  175. selectedItems.forEach(item => {
  176. if (item.instance && item.instance.businessType === 'SEIZURE_REPORT') {
  177. seizureTaskIds.push(item.id);
  178. } else if (item.instance && item.instance.businessType === 'DEPARTMENT_CHECK' &&
  179. this.userInfoRoles.includes('jingli')) {
  180. departmentTaskIds.push(item.id);
  181. }
  182. });
  183. return { seizureTaskIds, departmentTaskIds };
  184. },
  185. async getUnreadCount() {
  186. try {
  187. const query = {
  188. pageNum: this.pageNum,
  189. pageSize: this.pageSize
  190. };
  191. // 添加搜索关键词
  192. if (this.keyword) {
  193. query.keyword = this.keyword;
  194. }
  195. let response = await listCheckPendingTasks(query);
  196. this.todoUnreadCount = response.total;
  197. let response1 = await listCheckApprovalCcDetails(query);
  198. this.msgUnreadCount = response1.rows.filter(item => item.isRead == '0').length;
  199. } catch (error) {
  200. console.error('获取待办任务未读数量失败:', error);
  201. }
  202. },
  203. getFormData(row, key) {
  204. if (row.formData) {
  205. const formData = JSON.parse(row.formData);
  206. const value = formData[key] || '';
  207. // 如果是时间戳字段且值为数字,进行格式化
  208. if ((key.includes('Time') || key.includes('Deadline')) && typeof value === 'number' && value > 0) {
  209. return this.formatTimestamp(value);
  210. }
  211. return value;
  212. }
  213. },
  214. // 格式化时间戳为年月日时分秒
  215. formatTimestamp(timestamp) {
  216. const date = new Date(timestamp);
  217. const year = date.getFullYear();
  218. const month = String(date.getMonth() + 1).padStart(2, '0');
  219. const day = String(date.getDate()).padStart(2, '0');
  220. return `${year}-${month}-${day}`;
  221. },
  222. getChecked(item) {
  223. if (this.getLabel(item.instance && item.instance.businessType) == '科室') {
  224. return this.getFormData(item, 'checkedDepartmentName');
  225. }
  226. if (this.getLabel(item.instance && item.instance.businessType) == '班组') {
  227. return this.getFormData(item, 'checkedTeamName');
  228. }
  229. if (this.getLabel(item.instance && item.instance.businessType) == '人') {
  230. return this.getFormData(item, 'checkedPersonnelName');
  231. }
  232. if (this.getLabel(item.instance && item.instance.businessType) == '大队') {
  233. return this.getFormData(item, 'checkedBrigadeName');
  234. }
  235. return '';
  236. },
  237. getLabel(key) {
  238. if (key === 'PERSONAL_CHECK') {
  239. return '人'
  240. }
  241. if (key === 'GROUP_CHECK') {
  242. return '班组'
  243. }
  244. if (key === 'SECTION_CHECK') {
  245. return '科室'
  246. }
  247. if (key === 'BRIGADE_CHECK') {
  248. return '大队'
  249. }
  250. return ''
  251. },
  252. navigateToDetail(row) {
  253. let type = 'view'; // 默认查看模式
  254. if (this.currentTab == 'msg') {
  255. const { businessType, businessId, instanceId } = row;
  256. let obj = {
  257. instanceId,
  258. businessId,
  259. businessType,
  260. id,
  261. nodeCode,
  262. type
  263. }
  264. updateCheckApprovalCcDetails([row.ccId]
  265. ).then(res => {
  266. })
  267. uni.navigateTo({
  268. url: `/pages/problemRect/index?params=${encodeURIComponent(JSON.stringify(obj))}`
  269. });
  270. return;
  271. }
  272. const { instance, id, nodeDefinition } = row;
  273. const { businessId = "", businessType = "", id: instanceId } = instance || {};
  274. const { nodeCode = "" } = nodeDefinition || {};
  275. let url = '';
  276. if (this.currentTab === 'todo') {
  277. type = 'approve'; // 待办时是审批模式
  278. }
  279. let obj = {
  280. instanceId,
  281. businessId,
  282. businessType,
  283. id,
  284. nodeCode,
  285. type // 添加type参数
  286. }
  287. //查获
  288. if (businessType === 'SEIZURE_REPORT') {
  289. url = `/pages/seizedReported/index?params=${encodeURIComponent(JSON.stringify(obj))}`;
  290. }
  291. // 巡检对应个人 班组 科室
  292. if (['PERSONAL_CHECK', 'GROUP_CHECK', 'SECTION_CHECK', 'BRIGADE_CHECK', 'DEPARTMENT_CHECK'].includes(businessType) || this.currentTab == 'msg') {
  293. url = `/pages/problemRect/index?params=${encodeURIComponent(JSON.stringify(obj))}`;
  294. }
  295. if (url) {
  296. uni.navigateTo({
  297. url: url
  298. });
  299. }
  300. },
  301. itemChange(item) {
  302. item.checked = !item.checked
  303. if (item.checked) {
  304. this.selectedItems.push(item);
  305. } else {
  306. this.selectedItems = this.selectedItems.filter(selectedItem => selectedItem.id !== item.id);
  307. }
  308. },
  309. handleSearch(val) {
  310. this.pageNum = 1;
  311. this.keyword = val || '';
  312. this.loadData();
  313. },
  314. handleClear() {
  315. this.pageNum = 1;
  316. this.keyword = '';
  317. this.loadData();
  318. },
  319. handleTabChange(tabValue, tabItem) {
  320. this.pageNum = 1;
  321. this.currentTab = tabValue;
  322. this.loadData();
  323. },
  324. handleSelectAll(e) {
  325. console.log(e, "e")
  326. this.$nextTick(() => {
  327. this.filteredList = this.filteredList.map(item => ({
  328. ...item,
  329. //查获和部门检查可以批量审批
  330. ...(this.canShowCheckbox(item) ? { checked: e } : {}),
  331. }));
  332. this.selectedItems = this.filteredList.filter(item => item.checked);
  333. })
  334. },
  335. async batchReject() {
  336. // 批量驳回逻辑
  337. if (this.selectedItems.length === 0) {
  338. uni.showToast({
  339. title: '请选择要驳回的任务',
  340. icon: 'none'
  341. });
  342. return;
  343. }
  344. try {
  345. // 根据任务类型分组
  346. const { seizureTaskIds, departmentTaskIds } = this.groupTasksByType(this.selectedItems);
  347. // 分别调用不同的驳回接口
  348. const promises = [];
  349. if (seizureTaskIds.length > 0) {
  350. promises.push(approveRejectBatch(seizureTaskIds));
  351. }
  352. if (departmentTaskIds.length > 0) {
  353. promises.push(rectifyApproveReject({taskIdList: departmentTaskIds}));
  354. }
  355. // 等待所有接口调用完成
  356. await Promise.all(promises);
  357. uni.showToast({
  358. title: '批量驳回成功',
  359. icon: 'success'
  360. });
  361. // 清空选中项并刷新数据
  362. this.selectedItems = [];
  363. this.filteredList = this.filteredList.map(item => ({
  364. ...item,
  365. checked: false
  366. }));
  367. this.loadData();
  368. // 刷新未读消息数量
  369. this.getUnreadCount();
  370. } catch (error) {
  371. console.error('批量驳回失败:', error);
  372. uni.showToast({
  373. title: '批量驳回失败',
  374. icon: 'none'
  375. });
  376. }
  377. },
  378. async batchApprove() {
  379. // 批量通过逻辑
  380. if (this.selectedItems.length === 0) {
  381. uni.showToast({
  382. title: '请选择要通过的任务',
  383. icon: 'none'
  384. });
  385. return;
  386. }
  387. try {
  388. // 根据任务类型分组
  389. const { seizureTaskIds, departmentTaskIds } = this.groupTasksByType(this.selectedItems);
  390. // 分别调用不同的通过接口
  391. const promises = [];
  392. if (seizureTaskIds.length > 0) {
  393. promises.push(approvePassBatch(seizureTaskIds));
  394. }
  395. if (departmentTaskIds.length > 0) {
  396. promises.push(rectifyApprovePass({ taskIdList: departmentTaskIds }));
  397. }
  398. debugger
  399. // 等待所有接口调用完成
  400. await Promise.all(promises);
  401. uni.showToast({
  402. title: '批量通过成功',
  403. icon: 'success'
  404. });
  405. // 清空选中项并刷新数据
  406. this.selectedItems = [];
  407. this.filteredList = this.filteredList.map(item => ({
  408. ...item,
  409. checked: false
  410. }));
  411. this.loadData();
  412. // 刷新未读消息数量
  413. this.getUnreadCount();
  414. } catch (error) {
  415. console.error('批量通过失败:', error);
  416. uni.showToast({
  417. title: '批量通过失败',
  418. icon: 'none'
  419. });
  420. }
  421. },
  422. onRefresh() {
  423. this.pageNum = 1;
  424. this.refresherTriggered = true;
  425. this.loadData()
  426. },
  427. // 加载数据方法
  428. async loadData() {
  429. if (this.loading) return;
  430. this.loading = true;
  431. try {
  432. const query = {
  433. pageNum: this.pageNum,
  434. pageSize: this.pageSize
  435. };
  436. // 添加搜索关键词
  437. if (this.keyword) {
  438. query.keyword = this.keyword;
  439. }
  440. let response;
  441. // 根据当前tab调用不同的API
  442. if (this.currentTab === 'msg') {
  443. response = await listCheckApprovalCcDetails(query);
  444. } else if (this.currentTab === 'todo') {
  445. response = await listCheckPendingTasks(query);
  446. } else if (this.currentTab === 'done') {
  447. response = await listCheckFinishedTasks(query);
  448. }
  449. // 处理响应数据
  450. const data = response.rows || response.list || [];
  451. if (this.pageNum === 1) {
  452. this.list = data;
  453. } else {
  454. this.list = [...this.list, ...data];
  455. }
  456. this.total = response.total || 0;
  457. this.hasMore = this.list.length < this.total;
  458. // 更新过滤列表
  459. this.filteredList = [...this.list];
  460. } catch (error) {
  461. console.error('加载数据失败:', error);
  462. uni.showToast({
  463. title: '加载失败',
  464. icon: 'none'
  465. });
  466. } finally {
  467. this.loading = false;
  468. // 重置下拉刷新状态
  469. this.refresherTriggered = false;
  470. uni.stopPullDownRefresh();
  471. }
  472. },
  473. // 加载更多数据
  474. loadMore() {
  475. if (this.loading || !this.hasMore) return;
  476. this.pageNum++;
  477. this.loadData();
  478. }
  479. },
  480. mounted() {
  481. this.loadData();
  482. },
  483. onShow() {
  484. // 页面再次展示时刷新数据
  485. this.pageNum = 1;
  486. this.selectedItems = [];
  487. this.loadData();
  488. this.getUnreadCount();
  489. showMessageTabRedDot();
  490. }
  491. }
  492. </script>
  493. <style lang="scss" scoped>
  494. // .mytodo-container {
  495. // min-height: 100vh;
  496. // background: none !important;
  497. // overflow: hidden;
  498. .mytodo-list {
  499. height: calc(100vh - 260rpx);
  500. // overflow: hidden; /* 防止与scroll-view的滚动冲突 */
  501. margin-bottom: 100rpx;
  502. .checkbox-group {
  503. display: flex;
  504. flex-direction: column;
  505. }
  506. .inspection-item {
  507. display: flex;
  508. flex-flow: column;
  509. justify-content: space-between;
  510. padding: 40rpx;
  511. background: #FFFFFF;
  512. box-shadow: 0 8rpx 20rpx 0 rgba(0, 0, 0, 0.08);
  513. border-radius: 16rpx;
  514. border: 2rpx solid #F0F8FF;
  515. margin-top: 32rpx;
  516. }
  517. .inspection-item-title {
  518. font-size: 32rpx;
  519. color: #333333;
  520. line-height: 38rpx;
  521. }
  522. .inspection-item-desc {
  523. margin: 20rpx 0 0;
  524. font-size: 28rpx;
  525. color: #666666;
  526. }
  527. /* 加载状态样式 */
  528. .load-more,
  529. .no-more,
  530. .no-data {
  531. display: flex;
  532. justify-content: center;
  533. align-items: center;
  534. padding: 40rpx;
  535. font-size: 28rpx;
  536. color: #999;
  537. }
  538. /* 未读红点样式 */
  539. .list-title {
  540. position: relative;
  541. }
  542. .unread-dot {
  543. position: absolute;
  544. top: -4rpx;
  545. right: -20rpx;
  546. width: 16rpx;
  547. height: 16rpx;
  548. background-color: #FF4D4F;
  549. border-radius: 50%;
  550. }
  551. /* list-row和list-value样式 */
  552. .list-row {
  553. .list-value-clamp-1 {
  554. word-break: break-all;
  555. /* 文字截断样式 - 超过一行显示省略号 */
  556. display: -webkit-box;
  557. -webkit-line-clamp: 1;
  558. -webkit-box-orient: vertical;
  559. overflow: hidden;
  560. text-overflow: ellipsis;
  561. }
  562. /* 三行文字截断样式 */
  563. .list-value-truncate {
  564. flex: 1;
  565. color: #999999;
  566. word-break: break-all;
  567. /* 三行文字截断 - 超过三行显示省略号 */
  568. display: -webkit-box;
  569. -webkit-line-clamp: 2;
  570. -webkit-box-orient: vertical;
  571. overflow: hidden;
  572. text-overflow: ellipsis;
  573. max-height: 120rpx;
  574. /* 3行 * 40rpx行高 */
  575. }
  576. }
  577. }
  578. // }
  579. .fixed-actions {
  580. position: fixed;
  581. bottom: 87rpx;
  582. left: 0;
  583. right: 0;
  584. display: flex;
  585. justify-content: space-between;
  586. align-items: center;
  587. padding: 20rpx 32rpx;
  588. background: #fff;
  589. box-shadow: 0 -4rpx 10rpx rgba(0, 0, 0, 0.1);
  590. z-index: 100;
  591. .select-all {
  592. flex: 1;
  593. }
  594. .batch-btn-container {
  595. display: flex;
  596. .batch-btn {
  597. width: 240rpx;
  598. margin-top: 0;
  599. margin-left: 20rpx;
  600. }
  601. }
  602. }
  603. /* 卡片标签样式 */
  604. .card-tag {
  605. position: absolute;
  606. bottom: 0rpx;
  607. right: 0rpx;
  608. padding: 8rpx 16rpx;
  609. border-radius: 8rpx 0 8rpx 0;
  610. font-size: 24rpx;
  611. font-weight: 500;
  612. color: #FFFFFF;
  613. z-index: 10;
  614. white-space: nowrap;
  615. pointer-events: none;
  616. }
  617. .seizure-tag {
  618. background-color: #1890FF;
  619. /* 蓝色背景 */
  620. }
  621. .rectification-tag {
  622. background-color: #FA8C16;
  623. /* 橙色背景 */
  624. }
  625. </style>