courseDetail.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. <template>
  2. <view>
  3. <view v-if="!loginShow" class="container" :style="{ height : `${loginShow ? '100vh' : 'auto' }`,
  4. overflow: `${loginShow ? 'hidden' : 'auto' }` }">
  5. <view class="container-poster" style="width: 100%;padding: 0 20rpx;">
  6. <image show-menu-by-longpress :src="courseDetail.poster?courseDetail.poster:''" mode="widthFix"
  7. style="width: 100%;"></image>
  8. </view>
  9. <view class="course-tab-list">
  10. <view class="course-tab-item" v-for="(data, index) in items" :key="index" @click="onClickItem(index)"
  11. :class="currentTab === index ? 'tab-active' : ''">
  12. {{ data }}
  13. </view>
  14. </view>
  15. <view class="content" v-if="currentTab === 0">
  16. <view class="content-text">
  17. <view class="text-title">{{courseDetail.courseName}}</view>
  18. <view class="text-title">课程概述</view>
  19. <view class="text-content">{{courseDetail.summary}}</view>
  20. <view class="text-title">课程时间</view>
  21. <view class="text-content">{{courseDetail.courseDate}}</view>
  22. <view class="text-title">培训地点</view>
  23. <view class="text-content">{{courseDetail.loc}}</view>
  24. <view class="text-tip" v-if="!isMember">个人会员或单位会员免费,点击现在入会></view>
  25. </view>
  26. </view>
  27. <view class="content" v-if="currentTab === 1" style="overflow: hidden;">
  28. <view class=""
  29. style="margin-bottom: 140rpx;padding-bottom: 150rpx;height: 100%;overflow: scroll;padding-left: 20rpx;padding-right: 20rpx;">
  30. <view v-for="(comment, index) in sortedCommentList" :key="index" class="comment-list-item">
  31. <view class="comment-list-left">
  32. <image :src="comment.icon" class="comment-list-avator"></image>
  33. </view>
  34. <view class="comment-list-right">
  35. <view style="margin-bottom: 15rpx;">
  36. <text class="comment-list-username">{{ comment.username }}</text>
  37. <text class="comment-list-moment">{{ formatTime(comment.commentTime) }}</text>
  38. </view>
  39. <view>{{ comment.content }}</view>
  40. </view>
  41. </view>
  42. </view>
  43. <view class="section-bottom " style="background-color: #f2f2f2;">
  44. <view class="comment-input-box">
  45. <u-input :custom-style="inputStyle" class="comment-input" v-model="comment" :border="false"
  46. placeholder="写留言" height="60" adjust-position />
  47. <u-button class="comment-button" :hair-line="false" :custom-style="customStyle"
  48. @click="toSend">发送</u-button>
  49. </view>
  50. </view>
  51. </view>
  52. </view>
  53. <u-popup v-model="loginShow"
  54. :mask="false" :closeable='false' mode="bottom" :mask-close-able='false'
  55. safe-area-inset-bottom>
  56. <view style="height: 70vh;padding: 40rpx;position: relative;background:none;">
  57. <view style="text-align: center;margin: 70rpx 0;">
  58. <u-button size="medium" type="error" @click="toLogin">登录查看</u-button>
  59. </view>
  60. </view>
  61. </u-popup>
  62. </view>
  63. </template>
  64. <script setup>
  65. import {
  66. loadCourseDetail,
  67. loadCommentList,
  68. sendComment
  69. } from "@/api/edu.js"
  70. import { getToken } from '@/utils/auth.js'
  71. import {
  72. useAuthStore
  73. } from '@/store/authStore'
  74. const authStore = useAuthStore();
  75. // const isMember = ref(false)
  76. import {
  77. ref,
  78. computed
  79. } from 'vue'
  80. import {
  81. onLoad,
  82. onShow,
  83. onShareAppMessage,
  84. onShareTimeline
  85. } from '@dcloudio/uni-app'
  86. const courseDetail = ref({});
  87. const courseId = ref(null);
  88. const courseName = ref(null);
  89. const items = ref(['课程简介', '观看评论']);
  90. const currentTab = ref(0);
  91. const comment = ref("");
  92. // 评论发送按钮样式
  93. const customStyle = ref({
  94. backgroundColor: '#e6e6e6',
  95. color: '#333333',
  96. fontWeight: 'bold',
  97. height: '60rpx',
  98. marginLeft: '20rpx',
  99. border: 'none',
  100. fontSize: '26rpx'
  101. })
  102. // 评论输入框样式
  103. const inputStyle = ref({
  104. backgroundColor: '#e6e6e6',
  105. color: '#333333',
  106. borderRadius: '5px',
  107. padding: '0 20rpx',
  108. fontSize: '26rpx'
  109. })
  110. // 评论列表
  111. const commentList = ref([])
  112. // 点击tabs,切换
  113. function onClickItem(e) {
  114. if (currentTab.value != e) {
  115. currentTab.value = e;
  116. if (e === 2) {
  117. getComment(courseId.value)
  118. }
  119. }
  120. }
  121. // 初始化
  122. function init(id) {
  123. loadCourseDetail(id).then(res => {
  124. if (res?.data) {
  125. courseDetail.value = res.data;
  126. showBuy.value = showBuyAction()
  127. // console.log(courseDetail, "课程详情")
  128. }
  129. })
  130. }
  131. function getComment(id) {
  132. loadCommentList(id).then(res => {
  133. if (res?.data) {
  134. commentList.value = res.data;
  135. }
  136. })
  137. }
  138. // 购买课程
  139. function toBuy() {
  140. uni.navigateTo({
  141. url: "/pages/goOnEdu/course/courseDetail/courseOrder?id=" + courseId.value
  142. })
  143. // console.log("购买该课程", courseDetail.value.id)
  144. }
  145. function toSend() {
  146. sendComment({
  147. courseId: courseId.value,
  148. content: comment.value,
  149. commentTime: formatDate(new Date())
  150. }).then(res => {
  151. getComment(courseId.value)
  152. comment.value = ""
  153. })
  154. }
  155. function formatDate(date) {
  156. const pad = (num) => num.toString().padStart(2, '0');
  157. const year = date.getFullYear();
  158. const month = pad(date.getMonth() + 1); // 月份从0开始,需加1
  159. const day = pad(date.getDate());
  160. const hours = pad(date.getHours());
  161. const minutes = pad(date.getMinutes());
  162. const seconds = pad(date.getSeconds());
  163. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  164. }
  165. function showBuyAction() {
  166. if (courseDetail.value.viewMode === '2' &&
  167. !isMember.value &&
  168. !courseDetail.value.hasBuy &&
  169. currentTab.value === 0) {
  170. // console.log(1)
  171. return true
  172. }
  173. // 付费,不管是不是会员,并且没买的
  174. if (courseDetail.value.viewMode === '3' &&
  175. !courseDetail.value.hasBuy &&
  176. currentTab.value === 0) {
  177. // console.log(2)
  178. return true
  179. }
  180. // console.log(3)
  181. return false
  182. }
  183. const showBuy = ref(false)
  184. const isMember = computed(() => {
  185. return authStore.userInfo.isMember == '0' ? false : true
  186. })
  187. const isLogin = computed(() => {
  188. if(getToken() && authStore.isAuthenticated){
  189. return true
  190. }else{
  191. return false
  192. }
  193. })
  194. const loginShow = ref(true)
  195. function toLogin() {
  196. // loginShow.value = false
  197. let url = {
  198. url: '/pages/goOnEdu/course/courseDetail/courseDetail',
  199. id: courseId.value,
  200. title: courseName.value
  201. }
  202. uni.setStorageSync("redirect", url)
  203. uni.navigateTo({
  204. url: `/pages/login/login`
  205. })
  206. }
  207. // 初始化页面
  208. onLoad((option) => {
  209. const {
  210. id,
  211. title
  212. } = option;
  213. courseId.value = id
  214. courseName.value = title
  215. uni.setNavigationBarTitle({
  216. title: title
  217. });
  218. if(isLogin.value){
  219. loginShow.value = false
  220. }else{
  221. loginShow.value = true
  222. }
  223. // loginShow.value = true
  224. })
  225. onShow(() => {
  226. if(isLogin.value){
  227. init(courseId.value)
  228. getComment(courseId.value)
  229. }
  230. })
  231. function formatDateS(dateStr) {
  232. return dateStr.replace(" ", "T");
  233. }
  234. function formatTime(timeString) {
  235. const commentDate = new Date(formatDateS(timeString));
  236. const now = new Date();
  237. const diff = now - commentDate;
  238. const minutes = Math.floor(diff / 60000);
  239. const hours = Math.floor(diff / 3600000);
  240. const days = Math.floor(diff / 86400000);
  241. if (minutes < 1) { // 修改这里以处理0分钟
  242. return "刚刚";
  243. } else if (minutes < 60) {
  244. return `${minutes}分钟前`;
  245. } else if (hours < 24) {
  246. return `${hours}小时前`;
  247. } else {
  248. return commentDate.toISOString().split('T')[0];
  249. }
  250. }
  251. const sortedCommentList = computed(() => {
  252. return commentList.value.sort((a, b) =>
  253. new Date(formatDateS(b.commentTime)) - new Date(formatDateS(a.commentTime))
  254. );
  255. });
  256. onShareAppMessage(async (res) => {
  257. return {
  258. title: courseName.value,
  259. path: `/pages/goOnEdu/course/courseDetail/courseDetail?id=${courseId.value}&title=${courseName.value}`,
  260. imageUrl: courseDetail.value.cover
  261. };
  262. })
  263. onShareTimeline(async () => {
  264. return {
  265. title: courseName.value,
  266. query: `id=${courseId.value}&title=${courseName.value}`,
  267. imageUrl: courseDetail.value.cover
  268. };
  269. })
  270. </script>
  271. <style lang="scss">
  272. .u-drawer-bottom{
  273. background-color: transparent!important;
  274. }
  275. </style>
  276. <style lang="scss" scoped>
  277. .container {
  278. // height: 100vh;
  279. width: 100vw;
  280. background-color: #fff;
  281. // padding: 0 20rpx;
  282. }
  283. .course-tab-list {
  284. display: flex;
  285. background-color: #f2f2f2;
  286. flex: 0 0 auto;
  287. margin: 0 20rpx;
  288. .course-tab-item {
  289. width: 100%;
  290. height: 80rpx;
  291. line-height: 80rpx;
  292. text-align: center;
  293. }
  294. .tab-active {
  295. border-bottom: 1px solid #0069f6;
  296. }
  297. }
  298. // .container-poster{
  299. // height: calc(100vh - 500rpx - env(safe-area-inset-bottom, 0));
  300. // }
  301. .content {
  302. overflow: scroll;
  303. // height: calc(100vh - 500rpx - env(safe-area-inset-bottom, 0));
  304. height: 700rpx;
  305. position: relative;
  306. .content-text {
  307. padding: 0 20rpx env(safe-area-inset-bottom, 0);
  308. font-size: 38rpx;
  309. .text-title {
  310. font-weight: bold;
  311. margin-bottom: 15rpx;
  312. }
  313. .text-content {
  314. font-size: 32rpx;
  315. margin-bottom: 20rpx;
  316. }
  317. .text-tip {
  318. color: red;
  319. // margin-bottom: 20rpx;
  320. margin-bottom: env(safe-area-inset-bottom, 0);
  321. }
  322. }
  323. }
  324. .section-bottom {
  325. height: 90rpx;
  326. color: #fff;
  327. font-size: 34rpx;
  328. text-align: center;
  329. line-height: 80rpx;
  330. background-color: #fe0000;
  331. width: 100%;
  332. position: absolute;
  333. bottom: 0;
  334. box-sizing: content-box;
  335. padding-bottom: env(safe-area-inset-bottom, 0);
  336. }
  337. .comment-input-box {
  338. width: 100%;
  339. height: 100%;
  340. display: flex;
  341. box-sizing: border-box;
  342. padding: 0 20rpx;
  343. align-items: center;
  344. .comment-input {
  345. flex: 1;
  346. }
  347. .comment-button {
  348. flex: 0 0 auto;
  349. }
  350. }
  351. .comment-list-item {
  352. display: flex;
  353. padding: 20rpx 0;
  354. font-size: 28rpx;
  355. .comment-list-left {
  356. flex: 0 0 auto;
  357. padding-right: 20rpx;
  358. padding-left: 10rpx;
  359. .comment-list-avator {
  360. width: 100rpx;
  361. height: 100rpx;
  362. border-radius: 50%;
  363. }
  364. }
  365. .comment-list-right {
  366. flex: 1;
  367. .comment-list-username {
  368. padding-right: 25rpx;
  369. font-size: 32rpx;
  370. font-weight: bold;
  371. }
  372. }
  373. }
  374. </style>