index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. <template>
  2. <!-- 头部 -->
  3. <nav-bar height="400" backPath="workspace" title="材料采集" :filterPickerData="state.filterPickerData" :tabList="state.tabList" :onlyTitle="false" :showFilterPicker="true" :showTabs="true" rightWidth="0rpx" @CommonFilterData="CommonFilterData">
  4. <template #tabs_top_content>
  5. <view class="task_status_list">
  6. <view :class="['item_status',{'active':item.value==state.params.taskStatus}]" v-for="item in state.taskStatusList" :key="item.value" @click="ChangeTaskStatus(item.value)">{{item.label}}</view>
  7. </view>
  8. </template>
  9. </nav-bar>
  10. <view class="page_content">
  11. <scroll-view
  12. class="scroll_view_y"
  13. :scroll-top="state.scrollTop"
  14. scroll-y="true"
  15. :refresher-enabled="false"
  16. :enable-back-to-top="true"
  17. :show-scrollbar="false"
  18. :scroll-with-animation="true"
  19. refresher-default-style="none"
  20. refresher-background="#F8F9FD"
  21. :refresher-triggered="state.isRefreshing"
  22. @scrolltolower="OnLoadMore"
  23. @refresherrefresh="OnRefresh"
  24. @scroll="OnScroll">
  25. <view class="refresh_loading" v-if="state.isRefreshing"><uni-load-more status="loading" /></view>
  26. <view class="notice_list">
  27. <view v-for="item in state.pageList" :key="item.id" class="notice_item">
  28. <!-- 任务内容 -->
  29. <view class="item_content">
  30. <view class="notice_title_row">
  31. <image class="notice_icon" src="@/static/image/materialCollection/docIcon.png" mode="aspectFit"></image>
  32. <text class="notice_title">{{ item.taskName }}</text>
  33. </view>
  34. <view class="notice_meta">
  35. <view class="status_tag" :class="statusClassMap[item.taskStatus] || ''">
  36. <text>{{ taskStatusMap[item.taskStatus] }}</text>
  37. </view>
  38. <text class="meta_text ">{{ truncateText(item.departmentName, 5) }}</text>
  39. <text class="meta_text ">{{ truncateText(item.materialCategoryName, 5) }}</text>
  40. <text class="meta_text">{{ truncateText(item.createdBy, 5) }}</text>
  41. </view>
  42. </view>
  43. <!-- 任务底部 -->
  44. <view class="item_bottom">
  45. <view class="notice_stats">
  46. <view class="status_tag" :class="state.params.auditStatus">
  47. <text>{{ tabStatusClassMap[state.params.auditStatus] }}:{{ item?.taskTargetVos?.length || 0 }}</text>
  48. </view>
  49. <text class="stats_text">
  50. <template v-if="remainingDays(item.endTime) > 0">剩余时间:{{remainingDays(item.endTime) }}天</template>
  51. <template v-else>结束时间:{{item.endTime }}</template>
  52. </text>
  53. </view>
  54. <view class="notice_actions">
  55. <view class="action_btn edit" @click="handleDetail(item)">
  56. <text>查看</text>
  57. </view>
  58. </view>
  59. </view>
  60. </view>
  61. </view>
  62. <view class="no_data" v-if="!state.isLoading && state.pageList.length == 0">
  63. <image class="no_data_img" src="@/static/image/bg/no_data.png"></image>
  64. <text class="no_data_text">暂无数据</text>
  65. </view>
  66. <uni-load-more v-if="state.pageList.length > 0" :status="state.loadStatus" />
  67. </scroll-view>
  68. </view>
  69. </template>
  70. <script setup>
  71. import { reactive,ref,nextTick, onMounted,onUnmounted } from 'vue';
  72. import navBar from '@/components/navBar.vue';
  73. import {findTeacherTaskTargetList,getSchoolYearList,getCategoryList} from '@/reqApi/notification.js';
  74. import { truncateText,remainingDays } from '@/common/common.js';
  75. const state = reactive({
  76. filterPickerData: [{
  77. valueIndex:0,//默认值索引
  78. defaultValue:'',//默认值
  79. list:[]
  80. },{
  81. valueIndex:0,//默认值索引
  82. defaultValue:'',//默认值
  83. list:[]
  84. },{
  85. valueIndex:0,//默认值索引
  86. defaultValue:'',//默认值
  87. list:[]
  88. }],
  89. tabList: [{
  90. label: '待提交',
  91. value: 'notSubmitList'
  92. }, {
  93. label: '审核中',
  94. value: 'notReviewList'
  95. }, {
  96. label: '已驳回',
  97. value: 'reviewReasonList'
  98. }, {
  99. label: '已提交',
  100. value: 'reviewList'
  101. }],
  102. taskStatusList:[{
  103. label:'进行中',
  104. value:1
  105. },{
  106. label:'已结束',
  107. value:2
  108. }],
  109. params:{//参数
  110. schoolYearCode: '',//学年
  111. schoolYearId:'',//学年id
  112. departmentId:'',//归属部门
  113. materialCategory:'',//材料类目
  114. taskName:'',//任务名称
  115. taskStatus:1,//任务状态 1-进行中 2-已结束
  116. auditStatus:'notSubmitList',//审核状态
  117. pageSize: 30,
  118. pageNum: 1,
  119. },
  120. pageLists:[],//全部数据
  121. pageList:[],//列表数据
  122. pages:0,
  123. scrollTop:0,
  124. oldScrollTop:0,
  125. isRefreshing:false,//设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
  126. isLoading:true,//加载中
  127. noMoreData:false,//没有更多数据了
  128. loadStatus:'more',//loading状态
  129. })
  130. const statusClassMap = {
  131. 0: 'status_pending',
  132. 1: 'status_published',
  133. 2: 'status_closed'
  134. };
  135. const taskStatusMap = {
  136. 0: '待开始',
  137. 1: '进行中',
  138. 2: '已结束'
  139. }
  140. const tabStatusClassMap = {
  141. 'notSubmitList':'待提交', // 待提交
  142. 'notReviewList':'审核中', // 审核中
  143. 'reviewReasonList':'已驳回', // 已驳回
  144. 'reviewList':'已提交' // 已提交
  145. };
  146. // 学年学期列表
  147. const fetchSchoolYearList = async () => {
  148. try {
  149. const res = await getSchoolYearList();
  150. if (res.data && Array.isArray(res.data)) {
  151. const resData = res.data;
  152. state.filterPickerData[0].list = resData.map(item=>({
  153. ...item,
  154. label:item.schoolYearName,
  155. value:item.id
  156. }))
  157. const schoolYearId = state.filterPickerData?.[0]?.list?.[0]?.value || '';
  158. const schoolYearCode = state.filterPickerData?.[0]?.list?.[0]?.schoolYearCode || '';
  159. state.filterPickerData[0].defaultValue = schoolYearId;
  160. state.params.schoolYearId = schoolYearId;//默认值学年id
  161. state.params.schoolYearCode = schoolYearCode;//默认值学年code
  162. }
  163. } catch (error) {
  164. console.error('获取学年学期列表失败:', error);
  165. }
  166. };
  167. // 归属分类
  168. const fetchCategoryList = async () => {
  169. try {
  170. const res = await getCategoryList();
  171. if (res.data && Array.isArray(res.data)) {
  172. const categoryData = res.data;
  173. const categoryList = categoryData.map(item=>({
  174. ...item,
  175. label:item.categoryName,
  176. value:item.id
  177. }))
  178. state.filterPickerData[1].list = [{label:'归属分类',value:''}].concat(...categoryList);
  179. }
  180. } catch (error) {
  181. console.error('获取分类列表失败:', error);
  182. }
  183. };
  184. //切换条件选择器
  185. const CommonFilterData = (filterOptions,type) => {
  186. const {searchWord,filterOpts,tabsSelected} = filterOptions;
  187. const {valueIndex, index,filterPickerValue} = filterOpts;
  188. if(type == 'filterPicker'){
  189. state.params.departmentId = filterPickerValue[1];//归属部门
  190. state.params.materialCategory = filterPickerValue[2];//材料类目
  191. state.filterPickerData[index].valueIndex = valueIndex;
  192. if(index == 1){
  193. state.filterPickerData[2].valueIndex = 0;
  194. const parentData = state.filterPickerData[1].list;//分类归属
  195. const childrenData = parentData?.[valueIndex]?.childList || [];//材料类目
  196. const childrenList = childrenData.map(item=>({
  197. ...item,
  198. label:item?.categoryName || '',
  199. value:item?.id ?? ''
  200. }))
  201. if(childrenList.length){
  202. childrenList.unshift({
  203. label:'材料类目',
  204. value:''
  205. })
  206. }
  207. state.filterPickerData[2].list = childrenList;
  208. }else if(index == 0){//学年
  209. state.params.schoolYearId = filterPickerValue[0];//学年id
  210. state.params.schoolYearCode = state.filterPickerData?.[index]?.list?.[valueIndex]?.schoolYearCode || '';//学年code
  211. }
  212. }else if(type == 'search'){
  213. state.params.taskName = searchWord;//任务名称
  214. }else{
  215. state.params.auditStatus = tabsSelected;//审核状态
  216. }
  217. OnLoadData(true);
  218. }
  219. //切换任务状态
  220. const ChangeTaskStatus = (val) => {
  221. state.params.taskStatus = val;
  222. OnLoadData(true);
  223. }
  224. /*
  225. *任务列表
  226. * initPage:初始分页 从第一开始
  227. */
  228. const OnLoadData = (initPage) => {
  229. // uni.showLoading({
  230. // title: '加载中'
  231. // });
  232. state.isLoading = true;
  233. state.loadStatus = 'loading';
  234. if (initPage) {
  235. state.params.pageNum = 1;//如果是下拉刷新、重新查询 默认加载第一页
  236. state.pageLists = [];
  237. state.pageList = [];
  238. const params = {
  239. schoolYearCode: state.params.schoolYearCode,
  240. schoolYearId: state.params.schoolYearId,
  241. departmentId: state.params.departmentId,
  242. materialCategory: state.params.materialCategory,
  243. taskName: state.params.taskName,
  244. taskStatus: state.params.taskStatus,
  245. pageNum: state.params.pageNum,
  246. pageSize: 999
  247. };
  248. findTeacherTaskTargetList(params).then(res=>{
  249. if (res.code == 200) {
  250. const tableData = res?.data?.[state.params.auditStatus] || [];
  251. state.pageLists = tableData;//全部数据
  252. const total = tableData?.length || 0;//总数
  253. state.pages = Math.ceil(total / state.params.pageSize);//总页数
  254. //默认加载
  255. state.pageList = state.pageLists.slice(0, state.params.pageSize);
  256. //没有更多了
  257. state.noMoreData = state.params.pageNum == state.pages;
  258. //分页+1
  259. state.params.pageNum++;
  260. state.isLoading = false;
  261. state.isRefreshing = false;//下拉刷新未被触发
  262. state.loadStatus = state.noMoreData?'no-more':'more';
  263. }
  264. }).finally(()=>{
  265. // uni.hideLoading();
  266. //返回到页面顶部
  267. if(initPage){
  268. GoTop();
  269. }
  270. });
  271. }else{
  272. const start = (state.params.pageNum - 1) * state.params.pageSize;
  273. const end = start + state.params.pageSize;
  274. const list = state.pageLists.slice(start, end);
  275. state.pageList = [...state.pageList, ...list];
  276. state.noMoreData = state.params.pageNum == state.pages;
  277. state.params.pageNum++;
  278. setTimeout(() => {
  279. state.isLoading = false;
  280. state.isRefreshing = false; //下拉刷新未被触发
  281. state.loadStatus = state.noMoreData?'no-more':'more';
  282. }, 0)
  283. }
  284. };
  285. // 自定义下拉刷新被触发 下拉刷新
  286. const OnRefresh = () =>{
  287. state.isRefreshing = true;//下拉刷新已经被触发
  288. OnLoadData(true);
  289. }
  290. //滚动到底部/右边 触发上拉刷新
  291. const OnLoadMore = () => {
  292. if (state.isLoading || state.noMoreData) return;
  293. OnLoadData(false);
  294. }
  295. // 滚动时触发
  296. const OnScroll = (e) => {
  297. state.oldScrollTop = e.detail.scrollTop;
  298. }
  299. //返回到顶部
  300. const GoTop = () => {
  301. // 解决view层不同步的问题
  302. state.scrollTop = state.oldScrollTop;
  303. nextTick(() => {
  304. state.scrollTop = 0
  305. });
  306. }
  307. //任务详情
  308. const handleDetail = (item) => {
  309. state.details = uni.setStorageSync('submitMaterialDetailInfo',item);
  310. if(item?.taskTargetVos?.length > 1){
  311. uni.navigateTo({
  312. url: `/pages/materialCollection/submitMaterial/taskDetail?taskId=${item.id}&auditStatus=${state.params.auditStatus}`
  313. });
  314. }else{
  315. const targetId = item?.taskTargetVos?.[0].id;
  316. uni.navigateTo({
  317. url: `/pages/materialCollection/submitMaterial/submitDetail?taskId=${item.id}&targetId=${targetId}`
  318. });
  319. }
  320. };
  321. onMounted(async () => {
  322. await fetchSchoolYearList();//学年学期列表
  323. fetchCategoryList();//归属分类
  324. OnLoadData(true);
  325. });
  326. onUnmounted(() => {
  327. uni.removeStorageSync('submitMaterialDetailInfo');//移除缓存
  328. })
  329. </script>
  330. <style lang="scss" scoped>
  331. .page_content{
  332. height: calc(100% - 536rpx);
  333. .scroll_view_y{
  334. height: 100%;
  335. .refresh_loading{
  336. padding: 10rpx 0;
  337. }
  338. }
  339. }
  340. .notice_list {
  341. width: 100%;
  342. display: flex;
  343. flex-direction: column;
  344. }
  345. .notice_item {
  346. background-color: #F9FAFF;
  347. border-radius: 20rpx;
  348. margin-top: 24rpx;
  349. border: 2rpx solid #E4E7ED;
  350. &:first-child{
  351. margin-top: 16rpx;
  352. }
  353. .item_content {
  354. padding: 24rpx 16rpx;
  355. .notice_title_row {
  356. display: flex;
  357. align-items: center;
  358. margin-bottom: 16rpx;
  359. .notice_icon {
  360. width: 32rpx;
  361. height: 32rpx;
  362. margin-right: 16rpx;
  363. }
  364. .notice_title {
  365. flex: 1;
  366. font-weight: 500;
  367. font-size: 28rpx;
  368. color: #333333;
  369. overflow: hidden;
  370. text-overflow: ellipsis;
  371. white-space: nowrap;
  372. }
  373. }
  374. .notice_meta {
  375. display: flex;
  376. align-items: center;
  377. flex-wrap: wrap;
  378. gap: 16rpx;
  379. .status_tag {
  380. font-size: 24rpx;
  381. height: 48rpx;
  382. width: 96rpx;
  383. text-align: center;
  384. line-height: 48rpx;
  385. border-radius: 4rpx;
  386. font-weight: 400;
  387. border-radius: 8rpx;
  388. &.status_pending {
  389. background: rgba(46, 100, 250, 0.1);
  390. color: #2E64FA;
  391. }
  392. &.status_published {
  393. background: rgba(82, 196, 26, 0.1);
  394. color: #2BC644;
  395. }
  396. &.status_closed {
  397. background: #6666661a;
  398. color: #666666;
  399. }
  400. }
  401. .meta_text {
  402. font-size: 24rpx;
  403. color: #999999;
  404. line-height: 48rpx;
  405. }
  406. .textLength {
  407. max-width: 160rpx;
  408. overflow: hidden;
  409. text-overflow: ellipsis;
  410. white-space: nowrap;
  411. }
  412. }
  413. }
  414. .item_bottom {
  415. border-top: 2rpx solid #E4E7ED;
  416. display: flex;
  417. flex-direction: row;
  418. justify-content: space-between;
  419. align-items: center;
  420. padding: 24rpx 16rpx;
  421. .notice_stats {
  422. display: flex;
  423. align-items: center;
  424. gap: 16rpx;
  425. .stats_text {
  426. font-size: 24rpx;
  427. color: #999999;
  428. }
  429. .status_tag{
  430. font-size: 24rpx;
  431. text-align: center;
  432. padding: 8rpx 12rpx;
  433. // height: 48rpx;
  434. // line-height: 48rpx;
  435. font-weight: 400;
  436. border-radius: 8rpx;
  437. flex-shrink: 0;
  438. &.notSubmitList {
  439. background: rgba(46,100,250,0.1);
  440. color: #2E64FA;
  441. }
  442. &.notReviewList {
  443. background: rgba(251,159,52,0.1);
  444. color: #FB9F34;
  445. }
  446. &.reviewReasonList {
  447. background: rgba(245,108,108,0.1);
  448. color: #F56C6C;
  449. }
  450. &.reviewList {
  451. background: rgba(43,198,68,0.1);
  452. color: #2BC644;
  453. }
  454. }
  455. }
  456. .notice_actions {
  457. display: flex;
  458. justify-content: flex-end;
  459. gap: 16rpx;
  460. flex-shrink: 0;
  461. .action_btn {
  462. font-size: 24rpx;
  463. padding: 12rpx 24rpx;
  464. border-radius: 8rpx;
  465. border: 2rpx solid transparent;
  466. &.delete {
  467. border-color: #F56C6C;
  468. color: #F56C6C;
  469. background-color: #FFFFFF;
  470. &.disabled {
  471. border-color: #C0C4CC;
  472. color: #C0C4CC;
  473. background-color: #FFFFFF;
  474. }
  475. }
  476. &.edit {
  477. border-color: #2E64FA;
  478. color: #2E64FA;
  479. background-color: #FFFFFF;
  480. &.disabled {
  481. border-color: #C0C4CC;
  482. color: #C0C4CC;
  483. background-color: #FFFFFF;
  484. }
  485. }
  486. &.disable {
  487. color: #C0C4CC !important;
  488. cursor: not-allowed;
  489. border-color: #C0C4CC !important;
  490. }
  491. &.disableLock{
  492. color: #C0C4CC !important;
  493. cursor: not-allowed;
  494. background-color: #F3F3F3 !important;
  495. border-color: #F3F3F3 !important;
  496. }
  497. }
  498. }
  499. }
  500. }
  501. :deep(.scroll_tabs){
  502. height: 72rpx;
  503. align-items: center;
  504. .scroll_tabs_view{
  505. height: 56rpx;
  506. }
  507. }
  508. :deep(.filter_picker){
  509. gap: 16rpx !important;
  510. .item_filter_picker{
  511. width: calc((100% - 16rpx * 2) / 3) !important;
  512. }
  513. }
  514. .task_status_list{
  515. margin-top: 24rpx;
  516. padding: 0 24rpx;
  517. display: flex;
  518. width: 100%;
  519. gap:24rpx 30rpx;
  520. box-sizing: border-box;
  521. .item_status{
  522. flex: 1;
  523. display: inline-flex;
  524. justify-content: center;
  525. align-items: center;
  526. height: 64rpx;
  527. background-color: #FFFFFF;
  528. border-radius: 8rpx;
  529. border: 2rpx solid #DCDFE6;
  530. font-weight: 400;
  531. font-size: 28rpx;
  532. color: #333333;
  533. &.active{
  534. background-color: rgba(46,100,250,0.1);
  535. border: 2rpx solid #2E64FA;
  536. color: #2E64FA;
  537. }
  538. }
  539. }
  540. </style>