courseDetail.vue 9.7 KB

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