detail.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. <template>
  2. <view class="page">
  3. <cu-custom :isBack="true"></cu-custom>
  4. <view class="header" :style="[{top:topHeader + 'px'}]">
  5. <view class="header-title">
  6. <view class="header-title-main">
  7. <view class="margin-bottom-xs">{{tabList[cate_type].title}}</view>
  8. <view class="point"></view>
  9. </view>
  10. </view>
  11. <swiper-tab :menuList="tabList[cate_type].list" @changeTab="changeTab"></swiper-tab>
  12. </view>
  13. <scroll-view v-if="currentIndex===0" :scroll-y="true"
  14. :style="[{height:'calc(100vh - 70px - '+ topHeader+'px)'}]"
  15. class="scroll-main">
  16. <view class="content">
  17. <view class="course-img">
  18. <swiper class="swiper" :indicator-dots="swiper.indicatorDots" :autoplay="swiper.autoplay" :interval="swiper.interval" :duration="swiper.duration">
  19. <swiper-item v-for="(item,index) in detail.image" :key="index">
  20. <view class="swiper-item">
  21. <image :src="item" mode="widthFix"></image>
  22. </view>
  23. </swiper-item>
  24. </swiper>
  25. </view>
  26. <!-- 详情 -->
  27. <view class="detail">
  28. <view class="detail-title">{{detail.name}}</view>
  29. <view class="detail-item">
  30. <view class="detail-item-point"></view>
  31. <view class="detail-label">周期</view>
  32. <view class="detail-text">{{detail.period}}<text class="text-gray">({{detail.time[0]}})</text></view>
  33. </view>
  34. <view class="detail-item">
  35. <view class="detail-item-point"></view>
  36. <view class="detail-label">适用年级</view>
  37. <view class="detail-text">{{detail.grade}}年级通用</view>
  38. </view>
  39. <view class="detail-item">
  40. <view class="detail-item-point"></view>
  41. <view class="detail-label">课时</view>
  42. <view class="detail-text">{{detail.class_total}}课时</view>
  43. </view>
  44. <view class="detail-item">
  45. <view class="detail-item-point"></view>
  46. <view class="detail-label">单课时价</view>
  47. <view class="detail-text">¥{{detail.single_money}}</view>
  48. </view>
  49. <view class="detail-item">
  50. <view class="detail-item-point"></view>
  51. <view class="detail-label">补贴</view>
  52. <view class="detail-text">¥{{detail.subsidy}}/节课</view>
  53. </view>
  54. <view class="detail-item">
  55. <view class="detail-item-point"></view>
  56. <view class="detail-label">培养目标</view>
  57. <view class="detail-text">{{detail.tenet}}</view>
  58. </view>
  59. </view>
  60. </view>
  61. <!-- 老师介绍 -->
  62. <view class="teacher flex margin-top-sm shadow" v-if="cate_type===0">
  63. <image mode="scaleToFill" :src="defaultTeacher" class="avatar lg"></image>
  64. <view class="flex-sub margin-left">
  65. <view class="teacher-title">{{detail.teachers.username||''}}老师授课</view>
  66. <view class="teacher-item">毕业于{{detail.teachers.agency_name||''}}</view>
  67. <view class="teacher-item">所属机构:{{detail.teachers.orz||''}}</view>
  68. <view class="teacher-exp flex">
  69. <view class="exp-item">{{detail.teachers.honor||''}}</view>
  70. <view class="exp-item">{{detail.teachers.introduce||''}}</view>
  71. </view>
  72. </view>
  73. </view>
  74. <!-- 道具选择 -->
  75. <view class="shop margin-top-sm shadow" v-if="detail.prop&&detail.prop.length>0">
  76. <view class="shop-title">请选择课程所需要的道具</view>
  77. <radio-group @change="radioChange" class="margin-top-xs">
  78. <label v-for="radio in radios" :key="radio.value" class="margin-right-sm">
  79. <radio :value="radio.value" class="cyan" :checked="radio.checked" style="transform:scale(0.7)"/>{{radio.name}}
  80. </label>
  81. </radio-group>
  82. <checkbox-group @change="checkboxChange" v-if="needTool==='1'" class="checkbox-group margin-top-xs">
  83. <label v-for="prop in detail.prop" :key="prop.id">
  84. <view>
  85. <checkbox :value="prop.id" class="cyan" style="transform:scale(0.7)" :checked="goods.props.includes(prop.id)"/>{{prop.name}}<text>¥{{prop.money}}</text>
  86. </view>
  87. </label>
  88. </checkbox-group>
  89. </view>
  90. <!-- 评论 -->
  91. <view class="comment">
  92. <rich-text :nodes="detail.present"></rich-text>
  93. </view>
  94. </scroll-view>
  95. <!-- 评价页 -->
  96. <scroll-view
  97. :style="[{height:'calc(100vh - 70px - '+ topHeader+'px)'}]"
  98. class="scroll-main"
  99. v-if="currentIndex===1">
  100. <view class="cu-list menu-avatar comment">
  101. <view class="cu-item" v-for="(comment,index) in comments" :key="index">
  102. <image mode="scaleToFill" :src="comment.img" class="avatar md left"></image>
  103. <view class="content">
  104. <view class="text-grey">{{comment.name}}</view>
  105. <view class="text-gray text-content text-df">
  106. {{comment.content}}
  107. </view>
  108. <view class="margin-top-sm flex justify-between">
  109. <view class="text-gray text-df">{{comment.date}}</view>
  110. <!-- <view>
  111. <text class="cuIcon-appreciatefill text-red"></text>
  112. <text class="cuIcon-messagefill text-gray margin-left-sm"></text>
  113. </view> -->
  114. </view>
  115. </view>
  116. </view>
  117. </view>
  118. </scroll-view>
  119. <!-- 视频页 -->
  120. <scroll-view
  121. :style="[{height:'calc(100vh - 70px - '+ topHeader+'px)'}]"
  122. class="scroll-main"
  123. v-if="currentIndex===2">
  124. <view class="list">
  125. <view class="cu-card case" v-for="(video,index) in detail.video" :key="index">
  126. <view class="cu-item shadow">
  127. <view class="image">
  128. <video :src="video" style="width:100%;"></video>
  129. </view>
  130. </view>
  131. </view>
  132. </view>
  133. </scroll-view>
  134. <!-- 大纲页 -->
  135. <scroll-view
  136. :scroll-y="true"
  137. :style="[{height:'calc(100vh - 70px - '+ topHeader+'px)'}]"
  138. class="scroll-main"
  139. v-if="currentIndex===3">
  140. <view class="cu-bar bg-white">
  141. <view class="action border-title">
  142. <text class="cuIcon-title text-student"></text>
  143. <text class="text-xl text-bold">{{tabList[cate_type].list[currentIndex]}}</text>
  144. </view>
  145. </view>
  146. <view class="bg-white padding-lr">
  147. <view class="text-content text-lg" v-for="(outline,index) in detail.out_line" :key="index">
  148. {{outline.course_content}}
  149. </view>
  150. </view>
  151. </scroll-view>
  152. <view class="static flex shadow">
  153. <view class="static-price">
  154. <text class="satic-label text-lg">小计:</text>
  155. <text class="text-price text-red text-lg">{{goods.sum_money}}</text>
  156. </view>
  157. <view class="static-choose text-ellipsis" v-if="goods.choose.length>0">(已选:{{goods.choose.join(',')}})</view>
  158. <view class="static-order flex align-center">
  159. <shop-cart :class_attend_id="attend_id"></shop-cart>
  160. <button class="cu-btn round bg-student text-white margin-left-xs" :class="{'disabled':carts.includes(attend_id)||!detail.enable}" @tap="addCart">报课</button>
  161. </view>
  162. </view>
  163. </view>
  164. </template>
  165. <script>
  166. import { _detail, _comments, _joinShop } from '@/api/course'
  167. import swiperTab from '@/components/swiper-tab.vue'
  168. import shopCart from '@/components/shop-cart.vue'
  169. import NP from 'number-precision'
  170. import { mapGetters } from 'vuex'
  171. export default {
  172. components: {
  173. swiperTab, shopCart
  174. },
  175. data() {
  176. return {
  177. topHeader: this.globalCustomBarHeight,
  178. swiper: {
  179. indicatorDots: true,
  180. autoplay: true,
  181. interval: 2000,
  182. duration: 500
  183. },
  184. defaultTeacher: '/static/imgs/class/logo0.png',
  185. attend_id: 0,
  186. cate_type: 0,
  187. currentIndex: 0,
  188. money: 0, // 初始金额
  189. comments: [], // 评论
  190. detail: {}, // 详情
  191. needTool: '1', // 需要道具
  192. disableBtn: false, // 避免多次重复点击
  193. radios: [
  194. { value: '1', name: '需要道具', checked: true },
  195. { value: '2', name: '不需要道具', checked: false }
  196. ],
  197. goods: { // 动态选中值
  198. props: [], // 选中值
  199. choose: [], // 选中名称
  200. sum_money: 0// 金额总计
  201. }, // 选中课程
  202. tabList: [
  203. {
  204. title: '课程详细',
  205. list: ['课程介绍', '课程评价', '课程视频', '课程大纲'],
  206. enable: '报课',
  207. disable: '报课',
  208. options: [
  209. { title: '周期', content: ':正在加载,请稍后', des: ':正在加载,请稍后' },
  210. { title: '适用年级', content: ':正在加载,请稍后', des: '' },
  211. { title: '课时', content: ':正在加载,请稍后', des: '' },
  212. { title: '单课时价', content: ':正在加载,请稍后', des: '' },
  213. { title: '补贴', content: ':正在加载,请稍后', des: '' },
  214. { title: '培养目标', content: ':正在加载,请稍后', des: '' }
  215. ]
  216. },
  217. {
  218. title: '餐饮详细',
  219. list: ['餐饮介绍', '餐饮评价', '餐饮视频', '每日菜谱'],
  220. enable: '报餐',
  221. disable: '报餐',
  222. options: [
  223. { title: '周期', content: ':正在加载,请稍后', des: ':正在加载,请稍后' },
  224. { title: '适用年级', content: ':正在加载,请稍后', des: '' },
  225. { title: '用餐次数', content: ':正在加载,请稍后', des: '' },
  226. { title: '单次价格', content: ':正在加载,请稍后', des: '' },
  227. { title: '补贴', content: ':正在加载,请稍后', des: '' },
  228. { title: '用餐介绍', content: ':正在加载,请稍后', des: '' }
  229. ]
  230. },
  231. {
  232. title: '活动详细',
  233. list: ['活动介绍', '活动评价', '活动视频', '活动大纲'],
  234. enable: '报名',
  235. disable: '报名',
  236. options: [
  237. { title: '周期', content: ':正在加载,请稍后', des: ':正在加载,请稍后' },
  238. { title: '适用年级', content: ':正在加载,请稍后', des: '' },
  239. { title: '活动次数', content: ':正在加载,请稍后', des: '' },
  240. { title: '单次价格', content: ':正在加载,请稍后', des: '' },
  241. { title: '补贴', content: ':正在加载,请稍后', des: '' },
  242. { title: '活动目标', content: ':正在加载,请稍后', des: '' }
  243. ]
  244. }
  245. ]
  246. }
  247. },
  248. computed: {
  249. ...mapGetters([
  250. 'carts'
  251. ])
  252. },
  253. onShow() {
  254. this.disableBtn = false
  255. },
  256. onLoad(option) {
  257. this.attend_id = Number(decodeURIComponent(option.attend_id))
  258. if (this.attend_id === 0) {
  259. uni.navigateBack()
  260. }
  261. this.get_detail()
  262. this.get_comments()
  263. },
  264. methods: {
  265. get_detail() {
  266. _detail({ class_attend_id: this.attend_id }).then(res => {
  267. this.detail = res.data
  268. this.money = res.data.sum_money
  269. const props = res.data.prop
  270. if (props && props.length > 0) {
  271. this.setDefaultGoods(props)
  272. } else {
  273. this.goods.sum_money = this.money
  274. }
  275. })
  276. },
  277. addCart() {
  278. if (this.disableBtn) return false
  279. this.disableBtn = true
  280. if (this.carts.includes(this.attend_id)) {
  281. uni.showToast({ title: '课程已存在,请勿重复添加!', icon: 'none' })
  282. setTimeout(() => {
  283. // this.disableBtn = false
  284. this.globalNavigateTo('classCart')
  285. }, 1000)
  286. return false
  287. }
  288. if (!this.detail.enable) {
  289. uni.showToast({ title: '课程已停止!', icon: 'none' })
  290. this.disableBtn = false
  291. return false
  292. }
  293. const props = this.goods.props.join(',')
  294. _joinShop({ class_attend_id: this.attend_id, prop: props }).then(res => {
  295. // this.disableBtn = false
  296. this.globalNavigateTo('classCart')
  297. })
  298. },
  299. setDefaultGoods(props) {
  300. this.goods.props[0] = props[0].id
  301. this.goods.sum_money = NP.plus(this.money, props[0].money)
  302. this.goods.choose[0] = props[0].name
  303. },
  304. get_comments() {
  305. _comments({ class_attend_id: this.attend_id }).then(res => {
  306. this.comemnts = res.data
  307. })
  308. },
  309. changeTab(index) {
  310. this.currentIndex = index
  311. },
  312. radioChange(e) {
  313. const value = e.detail.value
  314. this.needTool = value
  315. if (value === '1') {
  316. this.setDefaultGoods(this.detail.prop)
  317. } else {
  318. this.goods.sum_money = this.money
  319. this.goods.props = []
  320. this.goods.choose = []
  321. }
  322. },
  323. checkboxChange(e) {
  324. const values = e.detail.value
  325. const choose = []
  326. const props = []
  327. let money = 0
  328. values.forEach(item => {
  329. const one = this.detail.prop.find(e => e.id === Number(item))
  330. choose.push(one.name)
  331. props.push(Number(item))
  332. money = NP.plus(money, Number(one.money))
  333. })
  334. const sum = NP.plus(this.money, money)
  335. this.goods = {
  336. props: props,
  337. choose: choose,
  338. sum_money: sum
  339. }
  340. }
  341. }
  342. }
  343. </script>
  344. <style lang="scss" scoped>
  345. .scroll-main{
  346. padding-top:86px;
  347. }
  348. .detail{
  349. padding:20rpx;
  350. background-color:#fff;
  351. &-title{
  352. margin-bottom:1rem;
  353. font-size:22px;
  354. }
  355. &-item{
  356. margin: 20rpx 0;
  357. display: flex;
  358. color: #999;
  359. &-point{
  360. margin-top: 7px;
  361. margin-right: 7px;
  362. border-radius: 50%;
  363. width: 5px;
  364. height: 5px;
  365. background: #5ecfde;
  366. }
  367. }
  368. &-label{
  369. width: 66px;
  370. padding-right: 6px;
  371. text-align: justify;
  372. text-align-last: justify;
  373. }
  374. &-text{
  375. color:#000;
  376. }
  377. }
  378. .teacher{
  379. padding:20rpx;
  380. background:#fff;
  381. font-size:14px;
  382. color:#999;
  383. &-title{
  384. font-size:18px;
  385. color:#000;
  386. }
  387. }
  388. .shop{
  389. padding:20rpx;
  390. background:#fff;
  391. }
  392. .checkbox-group{
  393. height:80px;
  394. overflow-y:scroll;
  395. border:1px solid #f5f5f5;
  396. }
  397. .comment{
  398. padding:20rpx;
  399. min-height:60px;
  400. }
  401. .static{
  402. position: fixed;
  403. left: 0;
  404. right: 0;
  405. bottom: 0;
  406. display: flex;
  407. background: #fff;
  408. height: 70px;
  409. align-items: center;
  410. padding: 0 0.8rem;
  411. justify-content: space-between;
  412. &-price{
  413. font-size:14px;
  414. }
  415. &-choose{
  416. color: #666;
  417. font-size: 12px;
  418. width: calc(100vw - 240px);
  419. }
  420. }
  421. </style>