taskDetail.vue 16 KB

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