taskDetails.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. <template>
  2. <view class="page_body">
  3. <!-- 头部 -->
  4. <nav-bar height="96" title="任务详情" :onlyTitle="true" :showHeaderborder="true" @GoBack="GoBack"></nav-bar>
  5. <view class="page_content padding_btm">
  6. <view class="task_title">{{state?.details?.courseName}}</view>
  7. <view class="task_class_collect">
  8. <view class="task_class_name">
  9. <text class="class_item">{{state?.details?.courseType===0?'必修任务':'自主研修'}}</text>
  10. <text class="class_item"
  11. v-if="state?.details?.teachCourseTypeName">{{state?.details?.teachCourseTypeName}}</text>
  12. <text class="class_item"
  13. v-if="state?.details?.teachCourseTypeClassName">{{state?.details?.teachCourseTypeClassName}}</text>
  14. <text class="class_item" v-if="state?.details?.createdBy">{{state?.details?.createdBy}}</text>
  15. </view>
  16. <view class="task_collect">
  17. <view class="preview">
  18. <image class="img" src="@/static/image/icon/preview.png"></image>
  19. <text class="text">{{state?.details?.watchCount || 0}}</text>
  20. </view>
  21. <view class="collect">
  22. <image class="img" src="@/static/image/icon/collect.png"></image>
  23. <text class="text">{{state?.details?.followCount || 0}}</text>
  24. </view>
  25. </view>
  26. </view>
  27. <view class="task_time">
  28. <view class="time_day">
  29. <text class="time_item">{{state?.details?.courseStartDatetime?.split(' ')?.[0]}}</text>
  30. <template v-if="state?.details?.courseType===0">
  31. <text class="time_item">至</text>
  32. <text class="time_item">{{state?.details?.courseEndDatetime?.split(' ')?.[0]}}</text>
  33. </template>
  34. <text class="time_item"
  35. v-if="state?.details?.courseType===0 && state?.details?.courseStatus!=2">剩余{{state?.details?.remainingTime}}</text>
  36. </view>
  37. <view
  38. v-if="state?.details?.courseType===0 || (state?.details?.courseStatus===0 || state?.details?.courseStatus===2)"
  39. :class="['task_status', {'going':state?.details?.courseStatus===0,'finished':state?.details?.courseStatus===2}]">
  40. {{state?.details?.courseStatus===0 ? '进行中' : '已结束'}}</view>
  41. </view>
  42. <view :class="['task_desc',{'over':!state.isExpand}]">
  43. 课程简介:{{state?.details?.courseContent}}
  44. </view>
  45. <view class="task_desc_all">
  46. 课程简介:{{state?.details?.courseContent}}
  47. </view>
  48. <view class="expand_btn" v-if="state.showExpandBtn" @click="toggleExpand"><text class="expand_text">{{!state.isExpand?'展开':'收起'}}</text><uni-icons
  49. class="expand_icon" :type="!state.isExpand?'down':'up'"></uni-icons></view>
  50. </view>
  51. <view class="row_gap"></view>
  52. <!-- 吸顶 -->
  53. <uv-sticky style="top:98rpx">
  54. <!-- 搜索框 -->
  55. <uni-easyinput class="search_box" v-model="state.searchWord" trim="all" :styles="state.searchInputStyles"
  56. :placeholderStyle="state.searchInputPlaceholderStyle" placeholder="请输入关键字搜索" @input="HandleSearchInput">
  57. <template #left>
  58. <image src="/static/image/overview/search.png" class="search_icon"></image>
  59. </template>
  60. </uni-easyinput>
  61. <scroll-view class="scroll_tabs" scroll-x="true" scroll-left="0" :show-scrollbar="false">
  62. <view :class="['tab_item',{'selected':item.value==state.tabsSelected}]" v-for="item in state.tabList"
  63. :key="item.value" @click="SelectTabsValue(item.value)">
  64. {{item.label}}
  65. </view>
  66. </scroll-view>
  67. </uv-sticky>
  68. <view class="page_content">
  69. <view class="total">
  70. <template v-if="state?.details?.courseType===0">
  71. <template v-if="state.tabsSelected==''">共 {{state?.details?.learnCount || 0}} 人</template>
  72. <template v-else-if="state.tabsSelected=='3'">共 {{state?.details?.learnUnOverCount || 0}}
  73. 人</template>
  74. <template v-else>共 {{state?.details?.learnFinishCount || 0}} 人</template>
  75. </template>
  76. <template v-else>
  77. <template v-if="state.tabsSelected=='4'">共 {{state?.details?.learnFinishCount || 0}} 人</template>
  78. <template v-else>共 {{state?.details?.learnUnOverCount || 0}} 人</template>
  79. </template>
  80. </view>
  81. <view class="list_item" v-for="item in state.tabListData" :Key="item.teacherId">
  82. <view class="created_progress_status">
  83. <text class="created_by">{{item.teacherName}}</text>
  84. <view class="item_progress">
  85. <view class="progress_bar">
  86. <view class="bar_width" :style="{'width': `${item.progress.replace(/%/g, '')}%`}"></view>
  87. </view>
  88. <view class="progress_label">{{`${item.progress.replace(/%/g, '') || '0'}`}}%</view>
  89. </view>
  90. <view
  91. :class="['item_status',{'await':item.learnStatus == 1 || item.learnStatus == 3,'finish':item.learnStatus == 4}]"
  92. v-if="state.courseType==='0' || (state.courseType==='1' && state.tabsSelected =='4')">
  93. {{item.learnStatus == 1 || item.learnStatus == 3 ?'待完成':'已完成'}}
  94. </view>
  95. </view>
  96. <view class="item_time"
  97. v-if="(item.learnStartDatetime && item.learnStartDatetime!='-') || (item.learnEndDatetime && item.learnEndDatetime!='-')">
  98. <text class="time"
  99. v-if="item.learnStartDatetime && item.learnStartDatetime!='-'">开始时间:{{item.learnStartDatetime}}</text>
  100. <text class="time"
  101. v-if="item.learnEndDatetime && item.learnEndDatetime!='-'">结束时间:{{item.learnEndDatetime}}</text>
  102. </view>
  103. </view>
  104. </view>
  105. </view>
  106. </template>
  107. <script setup>
  108. import uniEasyinput from '@/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue';
  109. import uniIcons from '@/uni_modules/uni-icons/components/uni-icons/uni-icons.vue';
  110. import uvSticky from '@/uni_modules/uv-sticky/components/uv-sticky/uv-sticky.vue'; //吸顶
  111. import navBar from '@/components/nav-bar.vue';
  112. import {
  113. queryMinitorDetailData
  114. } from '@/reqApi/teacherStudy.js';
  115. import {
  116. onLoad
  117. } from '@dcloudio/uni-app';
  118. import {
  119. reactive,
  120. ref,
  121. nextTick,
  122. getCurrentInstance,
  123. onMounted
  124. } from 'vue';
  125. const state = reactive({
  126. id: '', //任务id
  127. courseType: '', //研修场景
  128. searchWord: '', //搜索关键词
  129. searchInputStyles: { //搜素框样式
  130. borderColor: '#DCDFE6',
  131. color: '#333333'
  132. },
  133. searchInputPlaceholderStyle: 'color: #C0C4CC;fontSize: 28rpx', //搜素框样式
  134. tabsSelected: '', //默认选中tab值
  135. tabList: [], //tab列表
  136. mustTabList: [{
  137. label: '全部',
  138. value: ''
  139. }, {
  140. label: '待完成',
  141. value: '3'
  142. }, {
  143. label: '已完成',
  144. value: '4'
  145. }], //必修任务
  146. selfTabList: [{
  147. label: '已完成',
  148. value: '4'
  149. }, {
  150. label: '学习中',
  151. value: '3'
  152. }], //自主研修
  153. showExpandBtn: false, //是否显示展开按钮
  154. isExpand:false,//是否展开
  155. pageSize: 100,
  156. pageNum: 1,
  157. details: {}, //详情信息
  158. tabListData: [], //数据
  159. listDatatotal: '',
  160. });
  161. onLoad((option) => {
  162. state.id = option.id;
  163. state.courseType = option.courseType;
  164. state.tabList = option.courseType === '0' ? [...state.mustTabList] : [...state.selfTabList];
  165. state.tabsSelected = option.courseType === '0' ? '' : '4';
  166. })
  167. onMounted(() => {
  168. getMinitorDetailData(); // 数据加载完成后触发 checkTextOverflow
  169. });
  170. const checkTextOverflow = () => {
  171. nextTick(() => {
  172. setTimeout(() => {
  173. const instance = getCurrentInstance();
  174. // 安全保护
  175. if (!instance || !instance.proxy) {
  176. // 备用方案:全局查询(要求元素 class 唯一)
  177. let taskDescAllHeight = 0,
  178. taskDescHeight = 0;
  179. const query = uni.createSelectorQuery();
  180. query.select('.task_desc_all').boundingClientRect((rect) => {
  181. taskDescAllHeight = rect?.height || 0;
  182. });
  183. query.select('.task_desc').boundingClientRect((rect) => {
  184. taskDescHeight = rect?.height || 0;
  185. });
  186. query.exec(() => {
  187. // 所有回调执行完毕后触发
  188. state.showExpandBtn = taskDescAllHeight > taskDescHeight + 1;
  189. });
  190. return;
  191. }
  192. let taskDescAllHeight = 0,
  193. taskDescHeight = 0;
  194. const query = uni.createSelectorQuery().in(instance.proxy);
  195. query.select('.task_desc_all').boundingClientRect((rect) => {
  196. taskDescAllHeight = rect?.height || 0;
  197. });
  198. query.select('.task_desc').boundingClientRect((rect) => {
  199. taskDescHeight = rect?.height || 0;
  200. });
  201. query.exec(() => {
  202. // 所有回调执行完毕后触发
  203. state.showExpandBtn = taskDescAllHeight > taskDescHeight + 1;
  204. });
  205. }, 100);
  206. });
  207. }
  208. // 展开收起切换
  209. const toggleExpand = () => {
  210. state.isExpand = !state.isExpand;
  211. }
  212. //切换tab
  213. const SelectTabsValue = (val) => {
  214. state.tabsSelected = val;
  215. getMinitorDetailData();
  216. }
  217. //搜索
  218. const HandleSearchInput = (val) => {
  219. state.searchWord = val;
  220. getMinitorDetailData();
  221. }
  222. //详情
  223. const getMinitorDetailData = () => {
  224. queryMinitorDetailData({
  225. id: state.id,
  226. learnStatus: state.tabsSelected,
  227. queryStr: state.searchWord,
  228. pageSize: state.pageSize,
  229. pageNum: state.pageNum
  230. }).then(res => {
  231. if (res.code == 200) {
  232. state.details = res?.data || null;
  233. if (res?.data?.courseType === 0) { //必修任务
  234. const tableTaskData = res?.data?.tableTaskData?.records || [];
  235. const total = res?.data?.tableTaskData?.total || '0';
  236. state.tabListData = tableTaskData;
  237. state.listDatatotal = total;
  238. } else {
  239. if (state.tabsSelected == '4') { //自主研修-已完成
  240. const tableSelfOverData = res?.data?.tableSelfOverData?.records || [];
  241. const total = res?.data?.tableSelfOverData?.total || '0';
  242. state.tabListData = tableSelfOverData;
  243. state.listDatatotal = total;
  244. } else { //自主研修-学习中
  245. const tableSelfUnOverData = res?.data?.tableSelfUnOverData?.records || [];
  246. const total = res?.data?.tableSelfUnOverData?.total || '0';
  247. state.tabListData = tableSelfUnOverData;
  248. state.listDatatotal = total;
  249. }
  250. }
  251. checkTextOverflow();
  252. }
  253. })
  254. }
  255. //返回
  256. const GoBack = () => {
  257. uni.navigateBack({
  258. delta: 1
  259. });
  260. }
  261. </script>
  262. <style scoped lang="scss">
  263. .page_content {
  264. &.padding_btm {
  265. padding-bottom: 24rpx;
  266. .task_title {
  267. font-weight: 500;
  268. font-size: 32rpx;
  269. color: #333333;
  270. margin-top: 24rpx;
  271. }
  272. .task_class_collect {
  273. display: flex;
  274. align-items: flex-start;
  275. width: 100%;
  276. margin-top: 16rpx;
  277. .task_class_name {
  278. display: inline-flex;
  279. align-items: center;
  280. flex-wrap: wrap;
  281. flex: 1;
  282. gap: 16rpx;
  283. .class_item {
  284. font-weight: 400;
  285. font-size: 24rpx;
  286. color: #999999;
  287. }
  288. }
  289. .task_collect {
  290. display: inline-flex;
  291. gap: 40rpx;
  292. .preview,
  293. .collect {
  294. display: inline-flex;
  295. align-items: center;
  296. }
  297. .img {
  298. width: 24rpx;
  299. height: 24rpx;
  300. margin-right: 12rpx;
  301. }
  302. .text {
  303. font-weight: 400;
  304. font-size: 24rpx;
  305. color: #999999;
  306. }
  307. }
  308. }
  309. .task_time {
  310. display: flex;
  311. align-items: flex-start;
  312. width: 100%;
  313. margin-top: 16rpx;
  314. .time_day {
  315. display: inline-flex;
  316. align-items: center;
  317. flex-wrap: wrap;
  318. flex: 1;
  319. .time_item {
  320. font-weight: 400;
  321. font-size: 24rpx;
  322. line-height: 48rpx;
  323. color: #999999;
  324. &:nth-child(2) {
  325. margin: 0 8rpx;
  326. }
  327. &:nth-child(4) {
  328. color: #2E64FA;
  329. margin-left: 16rpx;
  330. }
  331. }
  332. }
  333. .task_status {
  334. display: inline-flex;
  335. align-items: center;
  336. height: 48rpx;
  337. padding: 0 12rpx;
  338. border-radius: 8rpx;
  339. font-weight: 400;
  340. font-size: 24rpx;
  341. &.going {
  342. background: rgba(43, 198, 68, 0.1);
  343. color: #2BC644;
  344. }
  345. &.finished {
  346. background: rgba(245, 108, 108, 0.1);
  347. color: #F56C6C;
  348. }
  349. }
  350. }
  351. .task_desc {
  352. margin-top: 24rpx;
  353. display: flex;
  354. width: 100%;
  355. padding-top: 24rpx;
  356. border-top: 2rpx solid #F3F3F3;
  357. font-weight: 400;
  358. font-size: 28rpx;
  359. color: #999999;
  360. line-height: 44rpx;
  361. box-sizing: border-box;
  362. position: relative;
  363. &.over{
  364. max-height: 168rpx;
  365. overflow: hidden;
  366. transition: all 0.25s;
  367. display: -webkit-box;
  368. -webkit-line-clamp: 3;
  369. -webkit-box-orient: vertical;
  370. }
  371. }
  372. .task_desc_all {
  373. display: flex;
  374. width: 100%;
  375. padding-top: 24rpx;
  376. border-top: 2rpx solid #F3F3F3;
  377. font-weight: 400;
  378. font-size: 28rpx;
  379. color: #999999;
  380. line-height: 44rpx;
  381. box-sizing: border-box;
  382. position: absolute;
  383. left: -9999px;
  384. top: 0;
  385. visibility: hidden; // 不可见但占据空间(不影响布局,因为absolute)
  386. pointer-events: none;
  387. }
  388. .expand_btn {
  389. width: 100%;
  390. height: 40rpx;
  391. background-color: #FFFFFF;
  392. display: flex;
  393. justify-content: flex-end;
  394. align-items: center;
  395. .expand_text {
  396. font-weight: 400;
  397. font-size: 28rpx;
  398. color: #2E64FA;
  399. margin-right: 8rpx;
  400. }
  401. .expand_icon {
  402. font-size: 32rpx !important;
  403. color: #2E64FA !important;
  404. }
  405. }
  406. }
  407. }
  408. .row_gap {
  409. background-color: #F8F9FD;
  410. width: 100%;
  411. height: 24rpx;
  412. }
  413. :deep(.search_box) {
  414. display: flex;
  415. align-items: center;
  416. box-sizing: border-box;
  417. width: 100%;
  418. padding: 24rpx;
  419. box-sizing: border-box;
  420. background-color: #FFFFFF;
  421. .is-input-border {
  422. border-radius: 8rpx;
  423. border: 2rpx solid #DCDFE6;
  424. border-color: #DCDFE6 !important;
  425. }
  426. .search_icon {
  427. width: 32rpx;
  428. height: 32rpx;
  429. margin-left: 24rpx;
  430. }
  431. .uni-easyinput__content-input {
  432. padding-left: 8rpx !important;
  433. font-size: 28rpx;
  434. color: #333333;
  435. height: 72rpx;
  436. }
  437. }
  438. .scroll_tabs {
  439. padding-bottom: 24rpx;
  440. background-color: #FFFFFF;
  441. }
  442. .total {
  443. width: 100%;
  444. display: flex;
  445. font-weight: 400;
  446. font-size: 28rpx;
  447. color: #333333;
  448. padding-bottom: 24rpx;
  449. border-bottom: 2rpx solid #F3F3F3;
  450. }
  451. .list_item {
  452. display: flex;
  453. flex-direction: column;
  454. width: 100%;
  455. padding: 24rpx 0;
  456. border-bottom: 2rpx solid #F3F3F3;
  457. .created_progress_status {
  458. display: flex;
  459. width: 100%;
  460. justify-content: space-between;
  461. align-items: center;
  462. .created_by {
  463. flex: 1;
  464. font-weight: 500;
  465. font-size: 32rpx;
  466. color: #333333;
  467. }
  468. .item_progress {
  469. display: inline-flex;
  470. align-items: center;
  471. width: 346rpx;
  472. .progress_bar {
  473. width: 200rpx;
  474. height: 12rpx;
  475. background-color: #E2E6F0;
  476. border-radius: 14rpx;
  477. position: relative;
  478. flex-shrink: 0;
  479. .bar_width {
  480. position: absolute;
  481. left: 0;
  482. top: 0;
  483. height: 12rpx;
  484. background-color: #2E64FA;
  485. border-radius: 14rpx;
  486. }
  487. }
  488. .progress_label {
  489. flex: 1;
  490. margin-left: 24rpx;
  491. font-weight: 400;
  492. font-size: 32rpx;
  493. color: #666666;
  494. }
  495. }
  496. .item_status {
  497. display: inline-flex;
  498. align-items: center;
  499. justify-content: center;
  500. width: 96rpx;
  501. height: 48rpx;
  502. border-radius: 8rpx;
  503. font-weight: 400;
  504. font-size: 24rpx;
  505. margin-left: 30rpx;
  506. &.await {
  507. background-color: rgba(46, 100, 250, 0.1);
  508. color: #2E64FA;
  509. }
  510. &.finish {
  511. background-color: rgba(43, 198, 68, 0.1);
  512. color: #2BC644;
  513. }
  514. }
  515. }
  516. .item_time {
  517. display: flex;
  518. width: 100%;
  519. align-items: center;
  520. flex-wrap: wrap;
  521. gap: 16rpx;
  522. margin-top: 18rpx;
  523. .time {
  524. font-weight: 400;
  525. font-size: 24rpx;
  526. color: #999999;
  527. }
  528. }
  529. }
  530. </style>