auditDetail.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. <template>
  2. <view class="page_body">
  3. <!-- 头部 -->
  4. <nav-bar height="96" title="任务详情" :onlyTitle="true" :showHeaderborder="true" @GoBack="GoBack"></nav-bar>
  5. <!-- 吸顶 -->
  6. <uv-sticky offsetTop="0" customNavHeight="98rpx">
  7. <view class="search_tabs" v-if="state.isSticky">
  8. <scroll-view class="scroll_tabs" scroll-x="true" scroll-left="0" :show-scrollbar="false">
  9. <view :class="['tab_item',{'selected':item.value==state.tabsSelected}]" v-for="item in state.tabList"
  10. :key="item.value" @click="SelectTabsValue(item.value)">
  11. {{item.label}}
  12. </view>
  13. </scroll-view>
  14. </view>
  15. </uv-sticky>
  16. <view class="scroll_view_box" :style="{'height': state.isSticky ? 'calc(100% - 296rpx)' : 'calc(100% - 96rpx)'}">
  17. <scroll-view
  18. class="scroll_view_y"
  19. :scroll-top="state.scrollTop"
  20. scroll-y="true"
  21. :refresher-enabled="false"
  22. :enable-back-to-top="true"
  23. :show-scrollbar="false"
  24. :scroll-with-animation="true"
  25. refresher-default-style="none"
  26. refresher-background="#F8F9FD"
  27. :refresher-triggered="state.isRefreshing"
  28. @scrolltolower="OnLoadMore"
  29. @refresherrefresh="OnRefresh"
  30. @scroll="OnScroll">
  31. <view class="refresh_loading" v-if="state.isRefreshing"><uni-load-more status="loading" /></view>
  32. <view class="page_content padding_btm">
  33. <view class="task_title">{{state?.details?.taskName}}</view>
  34. <view class="task_class_collect">
  35. <text class="class_item">{{state?.details?.departmentName}}</text>
  36. <text class="class_item">{{state?.details?.materialCategoryName}}</text>
  37. <text class="class_item">{{state?.details?.createBy}}</text>
  38. </view>
  39. <view class="task_time">
  40. <view class="time_day">
  41. <text class="time_item">{{state?.details?.startTime}}</text>
  42. <text class="time_item">至</text>
  43. <text class="time_item">{{state?.details?.endTime}}</text>
  44. <text class="time_item" v-if="remainingDays(state?.details?.endTime) > 0">剩余{{remainingDays(state?.details?.endTime)}}天</text>
  45. </view>
  46. <view :class="['task_status', statusClassMap[state?.details?.taskStatus]]">
  47. {{taskStatusMap[state?.details?.taskStatus]}}
  48. </view>
  49. </view>
  50. <view :class="['task_desc',{'over':!state.isExpand}]">
  51. 采集要求:{{state?.details?.collectionRequire}}
  52. </view>
  53. <view class="task_desc_all">
  54. 采集要求:{{state?.details?.collectionRequire}}
  55. </view>
  56. <view class="expand_btn" v-if="state.showExpandBtn" @click="toggleExpand"><text
  57. class="expand_text">{{!state.isExpand?'展开':'收起'}}</text><uni-icons class="expand_icon"
  58. :type="!state.isExpand?'down':'up'"></uni-icons></view>
  59. </view>
  60. <!-- 间隔 -->
  61. <view class="row_gap"></view>
  62. <!-- 吸顶 -->
  63. <!-- <uv-sticky style="top:98rpx"> -->
  64. <view class="search_tabs">
  65. <scroll-view class="scroll_tabs" scroll-x="true" scroll-left="0" :show-scrollbar="false">
  66. <view :class="['tab_item',{'selected':item.value==state.tabsSelected}]" v-for="item in state.tabList"
  67. :key="item.value" @click="SelectTabsValue(item.value)">
  68. {{item.label}}
  69. </view>
  70. </scroll-view>
  71. </view>
  72. <!-- </uv-sticky> -->
  73. <view class="page_content page_content_data">
  74. <view class="total">共 {{state.pageList?.length || 0}} 人</view>
  75. <view class="list_item" v-for="item in state.pageList" :Key="item.id">
  76. <view class="item_name_file">
  77. <view class="name_file">
  78. <text class="name">{{item.targetName}}</text>
  79. <text class="file_num">文件数量:{{item?.taskTargetFileVos?.length || 0}}</text>
  80. </view>
  81. <view class="btn" @click="handleAudit(item)">{{state.tabsSelected>0?'查看':'审核'}}</view>
  82. </view>
  83. <view class="item_info">
  84. <text class="info_text">{{item.showInfo}}</text>
  85. <text class="info_text">{{item?.taskTargetFileVos?.[item.taskTargetFileVos.length -1]?.createTime}}</text>
  86. </view>
  87. </view>
  88. <view class="no_data" v-if="!state.isLoading && state.pageList.length == 0">
  89. <image class="no_data_img" src="@/static/image/bg/no_data.png"></image>
  90. <text class="no_data_text">暂无数据</text>
  91. </view>
  92. </view>
  93. <uni-load-more v-if="state.pageList.length > 0" :status="state.loadStatus" />
  94. </scroll-view>
  95. </view>
  96. </view>
  97. </template>
  98. <script setup>
  99. import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
  100. import uvSticky from '@/uni_modules/uv-sticky/components/uv-sticky/uv-sticky.vue'; //吸顶
  101. import navBar from '@/components/navBar.vue';
  102. import { remainingDays } from '@/common/common.js';
  103. import {getReviewTaskList} from '@/reqApi/notification.js';
  104. import {
  105. onLoad,
  106. onShow
  107. } from '@dcloudio/uni-app';
  108. import {
  109. reactive,
  110. ref,
  111. nextTick,
  112. getCurrentInstance,
  113. onMounted,
  114. onUnmounted
  115. } from 'vue';
  116. const state = reactive({
  117. taskId: '', //任务id
  118. tabsSelected: 0, //默认选中tab值
  119. tabList: [{
  120. label: '待审核',
  121. value: 0
  122. }, {
  123. label: '已审核',
  124. value: 1
  125. }, {
  126. label: '已驳回',
  127. value: 2
  128. }], //tab列表
  129. showExpandBtn: false, //是否显示展开按钮
  130. isExpand: false, //是否展开
  131. pageSize: 50,
  132. pageNum: 1,
  133. scrollTop: 0,
  134. oldScrollTop: 0,
  135. isRefreshing: false, //设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
  136. isLoading: true, //加载中
  137. noMoreData: false, //没有更多数据了
  138. loadStatus: 'more', //loading状态
  139. details: {}, //详情信息
  140. pageList: [], //列表数据
  141. searchBoxTop:0,//搜索框距离顶部的高度
  142. isSticky:false,//是否吸顶
  143. });
  144. const statusClassMap = {
  145. 1: 'status_published',
  146. 2: 'status_closed'
  147. };
  148. const taskStatusMap = {
  149. 1: '进行中',
  150. 2: '已结束'
  151. };
  152. onShow(()=>{
  153. const isLoadReviewMaterialDetailList = uni.getStorageSync('isLoadReviewMaterialDetailList');
  154. if(isLoadReviewMaterialDetailList){//审核驳回通过返回刷新列表
  155. OnLoadData(true);
  156. }
  157. })
  158. onLoad((option) => {
  159. state.taskId = option.taskId;
  160. })
  161. onMounted(() => {
  162. state.details = uni.getStorageSync('collectReviewDetailInfo') || {};
  163. OnLoadData(true,true); // 数据加载完成后触发 checkTextOverflow
  164. });
  165. onUnmounted(() => {
  166. // uni.removeStorageSync('isLoadReviewMaterialDetailList');//移除缓存
  167. uni.removeStorageSync('collectReviewDetailInfo');//移除缓存
  168. })
  169. const checkTextOverflow = () => {
  170. nextTick(() => {
  171. const instance = getCurrentInstance();
  172. // 安全保护
  173. if (!instance || !instance.proxy) {
  174. // 备用方案:全局查询(要求元素 class 唯一)
  175. let taskDescAllHeight = 0,
  176. taskDescHeight = 0;
  177. const query = uni.createSelectorQuery();
  178. query.select('.task_desc_all').boundingClientRect((rect) => {
  179. taskDescAllHeight = rect?.height || 0;
  180. });
  181. query.select('.task_desc').boundingClientRect((rect) => {
  182. taskDescHeight = rect?.height || 0;
  183. });
  184. query.select('.page_content_data').boundingClientRect((rect) => {
  185. state.searchBoxTop = rect?.top || 0;
  186. });
  187. query.exec(() => {
  188. // 所有回调执行完毕后触发
  189. state.showExpandBtn = taskDescAllHeight > taskDescHeight + 1;
  190. });
  191. return;
  192. }
  193. let taskDescAllHeight = 0,
  194. taskDescHeight = 0;
  195. const query = uni.createSelectorQuery().in(instance.proxy);
  196. query.select('.task_desc_all').boundingClientRect((rect) => {
  197. taskDescAllHeight = rect?.height || 0;
  198. });
  199. query.select('.task_desc').boundingClientRect((rect) => {
  200. taskDescHeight = rect?.height || 0;
  201. });
  202. query.select('.page_content_data').boundingClientRect((rect) => {
  203. state.searchBoxTop = rect?.top || 0;
  204. });
  205. query.exec(() => {
  206. // 所有回调执行完毕后触发
  207. state.showExpandBtn = taskDescAllHeight > taskDescHeight + 1;
  208. });
  209. });
  210. }
  211. // 展开收起切换
  212. const toggleExpand = () => {
  213. state.isExpand = !state.isExpand;
  214. nextTick(() => {
  215. const instance = getCurrentInstance();
  216. // 安全保护
  217. if (!instance || !instance.proxy) {
  218. // 备用方案:全局查询(要求元素 class 唯一)
  219. query.select('.page_content_data').boundingClientRect((rect) => {
  220. state.searchBoxTop = rect?.top || 0;
  221. });
  222. return;
  223. }
  224. query.select('.page_content_data').boundingClientRect((rect) => {
  225. state.searchBoxTop = rect?.top || 0;
  226. });
  227. });
  228. }
  229. //切换tab
  230. const SelectTabsValue = (val) => {
  231. state.tabsSelected = val;
  232. OnLoadData(true);
  233. }
  234. //搜索
  235. const HandleSearchInput = (val) => {
  236. state.searchWord = val;
  237. OnLoadData(true);
  238. }
  239. const ToSafeArr = (val) => {
  240. if (Array.isArray(val)) return val
  241. return val ? [val] : []
  242. }
  243. /*
  244. *详情
  245. * initPage:初始分页 从第一开始
  246. * initData:初始加载
  247. */
  248. const OnLoadData = (initPage,initData = false) => {
  249. // uni.showLoading({
  250. // title: '加载中'
  251. // });
  252. if (initPage) state.pageNum = 1;//如果是下拉刷新、重新查询 默认加载第一页
  253. state.isLoading = true;
  254. state.loadStatus = 'loading';
  255. getReviewTaskList({
  256. taskId: state.taskId,
  257. reviewStatus: state.tabsSelected,
  258. pageNum: state.pageNum,
  259. pageSize: state.pageSize
  260. }).then(res=>{
  261. if(res.code == 200){
  262. const total = Number(res?.data?.reviewInfoVoPageResult?.total) || 0;//总数
  263. const pages = Math.ceil(total / state.pageSize);//总页数
  264. const recordsData = res?.data?.reviewInfoVoPageResult?.records || [];
  265. state.pageList = initPage ? recordsData : [...state.pageList,...recordsData];
  266. state.noMoreData = state.pageNum == pages;
  267. state.pageNum++;
  268. if(initData){
  269. checkTextOverflow();
  270. }
  271. }
  272. }).finally(()=>{
  273. // uni.hideLoading();
  274. state.isLoading = false;
  275. state.isRefreshing = false;//下拉刷新未被触发
  276. if(state.noMoreData){
  277. state.loadStatus = 'no-more';
  278. }else{
  279. state.loadStatus = 'more';
  280. }
  281. //返回到页面顶部
  282. if(initPage){
  283. GoTop();
  284. }
  285. })
  286. }
  287. // 自定义下拉刷新被触发 下拉刷新
  288. const OnRefresh = () => {
  289. state.isRefreshing = true; //下拉刷新已经被触发
  290. OnLoadData(true);
  291. }
  292. //滚动到底部/右边 触发上拉刷新
  293. const OnLoadMore = () => {
  294. if (state.isLoading || state.noMoreData) return;
  295. OnLoadData(false);
  296. }
  297. // 滚动时触发
  298. const OnScroll = (e) => {
  299. const scrollTop = e.detail.scrollTop;
  300. if(scrollTop > state.searchBoxTop){
  301. state.isSticky = true;
  302. }else{
  303. state.isSticky = false;
  304. }
  305. state.oldScrollTop = scrollTop;
  306. }
  307. //返回到顶部
  308. const GoTop = () => {
  309. // 解决view层不同步的问题
  310. state.scrollTop = state.oldScrollTop;
  311. nextTick(() => {
  312. state.scrollTop = 0
  313. });
  314. }
  315. //去审核
  316. const handleAudit = (item) => {
  317. uni.setStorageSync('reviewMaterialDetailData', item);//详情数据
  318. uni.navigateTo({
  319. url: `/pages/materialCollection/reviewMaterial/auditResult?taskStatus=${state?.details.taskStatus}`
  320. });
  321. };
  322. //返回
  323. const GoBack = () => {
  324. uni.navigateBack({
  325. delta: 1
  326. });
  327. }
  328. </script>
  329. <style scoped lang="scss">
  330. .page_body{
  331. display: flex;
  332. flex-direction: column;
  333. height: 100%;
  334. min-height: auto;
  335. .scroll_view_box{
  336. width: 100%;
  337. display: flex;
  338. flex-direction: column;
  339. // height: calc(100% - 96rpx);
  340. }
  341. .scroll_view_y {
  342. height: 100%;
  343. .refresh_loading {
  344. padding: 10rpx 0;
  345. }
  346. }
  347. }
  348. .page_content {
  349. &.padding_btm {
  350. padding-bottom: 24rpx;
  351. .task_title {
  352. font-weight: 500;
  353. font-size: 32rpx;
  354. color: #333333;
  355. margin-top: 24rpx;
  356. }
  357. .task_class_collect {
  358. display: inline-flex;
  359. align-items: center;
  360. flex-wrap: wrap;
  361. width: 100%;
  362. margin-top: 16rpx;
  363. gap: 16rpx;
  364. .class_item {
  365. font-weight: 400;
  366. font-size: 24rpx;
  367. color: #999999;
  368. }
  369. }
  370. .task_time {
  371. display: flex;
  372. align-items: flex-start;
  373. width: 100%;
  374. margin-top: 16rpx;
  375. .time_day {
  376. display: inline-flex;
  377. align-items: center;
  378. flex-wrap: wrap;
  379. flex: 1;
  380. .time_item {
  381. font-weight: 400;
  382. font-size: 24rpx;
  383. line-height: 48rpx;
  384. color: #999999;
  385. &:nth-child(2) {
  386. margin: 0 8rpx;
  387. }
  388. &:nth-child(4) {
  389. color: #2E64FA;
  390. margin-left: 16rpx;
  391. }
  392. }
  393. }
  394. .task_status {
  395. display: inline-flex;
  396. align-items: center;
  397. height: 48rpx;
  398. padding: 0 12rpx;
  399. border-radius: 8rpx;
  400. font-weight: 400;
  401. font-size: 24rpx;
  402. &.status_pending {
  403. background: rgba(46, 100, 250, 0.1);
  404. color: #2E64FA;
  405. }
  406. &.status_published {
  407. background: rgba(82, 196, 26, 0.1);
  408. color: #2BC644;
  409. }
  410. &.status_closed {
  411. background: #6666661a;
  412. color: #666666;
  413. }
  414. }
  415. }
  416. .task_desc {
  417. margin-top: 24rpx;
  418. display: flex;
  419. width: 100%;
  420. padding-top: 24rpx;
  421. border-top: 2rpx solid #F3F3F3;
  422. font-weight: 400;
  423. font-size: 28rpx;
  424. color: #999999;
  425. line-height: 44rpx;
  426. box-sizing: border-box;
  427. position: relative;
  428. &.over {
  429. max-height: 168rpx;
  430. overflow: hidden;
  431. transition: all 0.25s;
  432. display: -webkit-box;
  433. -webkit-line-clamp: 3;
  434. -webkit-box-orient: vertical;
  435. }
  436. }
  437. .task_desc_all {
  438. display: flex;
  439. width: 100%;
  440. padding-top: 24rpx;
  441. border-top: 2rpx solid #F3F3F3;
  442. font-weight: 400;
  443. font-size: 28rpx;
  444. color: #999999;
  445. line-height: 44rpx;
  446. box-sizing: border-box;
  447. position: absolute;
  448. left: -9999px;
  449. top: 0;
  450. visibility: hidden; // 不可见但占据空间(不影响布局,因为absolute)
  451. pointer-events: none;
  452. }
  453. .expand_btn {
  454. width: 100%;
  455. height: 40rpx;
  456. background-color: #FFFFFF;
  457. display: flex;
  458. justify-content: flex-end;
  459. align-items: center;
  460. .expand_text {
  461. font-weight: 400;
  462. font-size: 28rpx;
  463. color: #2E64FA;
  464. margin-right: 8rpx;
  465. }
  466. .expand_icon {
  467. font-size: 32rpx !important;
  468. color: #2E64FA !important;
  469. }
  470. }
  471. }
  472. }
  473. .row_gap {
  474. background-color: #F8F9FD;
  475. width: 100%;
  476. height: 24rpx;
  477. }
  478. :deep(.search_tabs){
  479. display: flex;
  480. width: 100%;
  481. flex-direction: column;
  482. background-color: #FFFFFF;
  483. padding: 24rpx;
  484. border-bottom: 2rpx solid #F3F3F3;
  485. }
  486. .scroll_tabs {
  487. margin-top:0;
  488. padding: 0;
  489. background-color: #FFFFFF;
  490. }
  491. .total {
  492. width: 100%;
  493. display: flex;
  494. font-weight: 400;
  495. font-size: 28rpx;
  496. color: #333333;
  497. padding: 24rpx 0;
  498. border-bottom: 2rpx solid #F3F3F3;
  499. }
  500. .list_item {
  501. display: flex;
  502. flex-direction: column;
  503. width: 100%;
  504. padding: 24rpx 0;
  505. border-bottom: 2rpx solid #F3F3F3;
  506. .item_name_file{
  507. display: flex;
  508. width: 100%;
  509. align-items: center;
  510. gap: 20rpx;
  511. .name_file{
  512. flex:1;
  513. gap: 20rpx;
  514. display: flex;
  515. align-items: center;
  516. .name{
  517. font-weight: 500;
  518. font-size: 32rpx;
  519. color: #333333;
  520. }
  521. .file_num{
  522. font-weight: 400;
  523. font-size: 24rpx;
  524. color: #2E64FA;
  525. }
  526. }
  527. .btn{
  528. width: 104rpx;
  529. height: 64rpx;
  530. border-radius: 8rpx;
  531. border: 2rpx solid #2E64FA;
  532. font-weight: 400;
  533. font-size: 28rpx;
  534. color: #2E64FA;
  535. display: inline-flex;
  536. align-items: center;
  537. justify-content: center;
  538. }
  539. }
  540. .item_info{
  541. margin-top: 16rpx;
  542. display: flex;
  543. gap: 16rpx;
  544. align-items: center;
  545. flex-wrap: wrap;
  546. .info_text{
  547. font-weight: 400;
  548. font-size: 24rpx;
  549. color: #999999;
  550. flex-shrink: 0;
  551. }
  552. }
  553. }
  554. .no_data{
  555. margin-top: 24rpx;
  556. }
  557. </style>