Procházet zdrojové kódy

Merge branch 'master' of http://git.dayaedu.com/lex/orchestra-app

mo před 2 roky
rodič
revize
0d5bd4e386
60 změnil soubory, kde provedl 2594 přidání a 319 odebrání
  1. binární
      src/common/images/icon-address.png
  2. binární
      src/common/images/icon-upload-close.png
  3. binární
      src/common/images/icon-upload.png
  4. 5 1
      src/components/o-popup/index.tsx
  5. 59 0
      src/components/o-upload-all/index.module.less
  6. 308 0
      src/components/o-upload-all/index.tsx
  7. 88 0
      src/components/o-video/index.module.less
  8. 193 0
      src/components/o-video/index.tsx
  9. 2 1
      src/helpers/native-message.ts
  10. 16 0
      src/router/routes-school.ts
  11. 9 4
      src/school/approval-manage/course-adjust.tsx
  12. 2 1
      src/school/companion-teacher/companion-teacher-register.tsx
  13. 17 3
      src/school/companion-teacher/index.tsx
  14. 17 1
      src/school/manage-teacher/index.tsx
  15. 1 1
      src/school/manage-teacher/manage-teacher-register.tsx
  16. binární
      src/school/orchestra-story/images/icon-edit.png
  17. binární
      src/school/orchestra-story/images/icon-step-calendar.png
  18. binární
      src/school/orchestra-story/images/icon-step.png
  19. binární
      src/school/orchestra-story/images/icon-upload-video-cover.png
  20. binární
      src/school/orchestra-story/images/icon-upload-video.png
  21. binární
      src/school/orchestra-story/images/icon-upload.png
  22. 82 0
      src/school/orchestra-story/index.module.less
  23. 237 0
      src/school/orchestra-story/index.tsx
  24. 76 0
      src/school/orchestra-story/story-operation/index.module.less
  25. 397 0
      src/school/orchestra-story/story-operation/index.tsx
  26. 1 1
      src/school/orchestra/compontent/information.tsx
  27. 2 2
      src/school/train-planning/modal/timer/index.tsx
  28. 278 0
      src/student/music-group/goods-detail/index.module.less
  29. 219 0
      src/student/music-group/goods-detail/index.tsx
  30. 10 11
      src/student/music-group/layout/login.tsx
  31. 16 0
      src/student/music-group/member-bao/index.tsx
  32. 16 5
      src/student/music-group/pre-apply/component/addres.tsx
  33. 23 3
      src/student/music-group/pre-apply/component/address.module.less
  34. 116 26
      src/student/music-group/pre-apply/component/payment.tsx
  35. binární
      src/student/music-group/pre-apply/images/banner.png
  36. binární
      src/student/music-group/pre-apply/images/icon-address-border.png
  37. binární
      src/student/music-group/pre-apply/images/icon-gives.png
  38. binární
      src/student/music-group/pre-apply/images/member_bao-1.png
  39. binární
      src/student/music-group/pre-apply/images/member_bao-2.png
  40. 65 37
      src/student/music-group/pre-apply/index.module.less
  41. 28 58
      src/student/music-group/pre-apply/index.tsx
  42. 65 5
      src/student/music-group/pre-apply/order-detail.module.less
  43. 68 13
      src/student/music-group/pre-apply/order-detail.tsx
  44. binární
      src/teacher/screen-projection/images/icon-img.png
  45. binární
      src/teacher/screen-projection/images/icon-music.png
  46. binární
      src/teacher/screen-projection/images/icon-video.png
  47. binární
      src/teacher/screen-projection/images/icon_content.png
  48. 10 0
      src/teacher/screen-projection/index.module.less
  49. 15 31
      src/teacher/screen-projection/index.tsx
  50. 12 0
      src/views/accompany/index.module.less
  51. 1 2
      src/views/accompany/music-list.tsx
  52. 5 1
      src/views/courseList/index.module.less
  53. 3 2
      src/views/courseList/index.tsx
  54. 1 1
      src/views/coursewarePlay/component/musicScore.tsx
  55. 3 1
      src/views/coursewarePlay/component/point.module.less
  56. 22 12
      src/views/coursewarePlay/index.module.less
  57. 95 89
      src/views/coursewarePlay/index.tsx
  58. 8 2
      src/views/exercise-after-class/index.tsx
  59. 2 1
      src/views/lessonCourseware/index.module.less
  60. 1 4
      src/views/lessonCourseware/index.tsx

binární
src/common/images/icon-address.png


binární
src/common/images/icon-upload-close.png


binární
src/common/images/icon-upload.png


+ 5 - 1
src/components/o-popup/index.tsx

@@ -9,6 +9,10 @@ export default defineComponent({
       type: String,
       default: '100%'
     },
+    width: {
+      type: String,
+      default: '100%'
+    },
     destroy: {
       type: Boolean,
       default: false
@@ -86,7 +90,7 @@ export default defineComponent({
         show={this.modelValue}
         transitionAppear={true}
         position={this.position}
-        style={{ height: this.height }}
+        style={{ height: this.height, width: this.width }}
         zIndex={this.zIndex}
         onClosed={() => {
           if (this.destroy) {

+ 59 - 0
src/components/o-upload-all/index.module.less

@@ -0,0 +1,59 @@
+.uploader-section {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  box-sizing: border-box;
+  position: relative;
+  .img-close {
+    position: absolute;
+    top: 5px;
+    right: 12px;
+    z-index: 99;
+    font-size: 13px;
+    background-color: rgba(0, 0, 0, 0.2);
+    color: #fff;
+    font-weight: bold;
+    width: 18px;
+    height: 18px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    border-radius: 50%;
+  }
+  .singleImgClose {
+    right: 5px;
+  }
+
+  .uploader {
+    position: relative;
+    &.default {
+      :global {
+        .van-uploader__upload {
+          width: 98px;
+          height: 94px;
+          background-color: #fff;
+        }
+      }
+      .previewImg {
+        width: 98px;
+        height: 94px;
+        border-radius: 10px;
+        overflow: hidden;
+      }
+
+      .uploadImg {
+        width: 98px;
+        height: 94px;
+        border-radius: 10px;
+        overflow: hidden;
+      }
+    }
+    :global {
+      .van-uploader__upload-icon,
+      .van-icon__image {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+}

+ 308 - 0
src/components/o-upload-all/index.tsx

@@ -0,0 +1,308 @@
+import { closeToast, Icon, Image, showLoadingToast, showToast, Uploader } from 'vant'
+import { defineComponent, PropType, ref } from 'vue'
+import styles from './index.module.less'
+import { useCustomFieldValue } from '@vant/use'
+import { postMessage } from '@/helpers/native-message'
+import umiRequest from 'umi-request'
+import iconUploader from '@common/images/icon-upload.png'
+import iconUploadClose from '@common/images/icon-upload-close.png'
+import request from '@/helpers/request'
+import { getOssUploadUrl, state } from '@/state'
+
+export default defineComponent({
+  name: 'col-upload',
+  props: {
+    modelValue: {
+      type: Array,
+      default: () => []
+    },
+    deletable: {
+      type: Boolean,
+      default: true
+    },
+    maxCount: {
+      type: Number,
+      default: 1
+    },
+    native: {
+      // 是否原生上传
+      type: Boolean,
+      default: false
+    },
+    uploadSize: {
+      // 上传图片大小
+      type: Number,
+      default: 5
+    },
+    uploadType: {
+      type: String as PropType<'IMAGE' | 'VIDEO'>,
+      default: 'IMAGE'
+    },
+    accept: {
+      type: String,
+      default: 'image/*'
+    },
+    onUploadChange: {
+      type: Function,
+      default: (url: string) => {}
+    },
+    bucket: {
+      type: String,
+      default: 'daya'
+    },
+    uploadIcon: {
+      type: String,
+      default: iconUploader
+    },
+    size: {
+      type: String,
+      default: 'default'
+    },
+    disabled: {
+      type: Boolean,
+      default: false
+    }
+  },
+  methods: {
+    nativeUpload() {
+      if (this.disabled) {
+        return
+      }
+      postMessage(
+        {
+          api: 'chooseFile',
+          content: { type: 'img', max: 1, bucket: this.bucket }
+        },
+        (res: any) => {
+          console.log(res, 'fileUrl')
+          this.$emit('update:modelValue', [...this.modelValue, res.fileUrl])
+        }
+      )
+    },
+    beforeRead(file: any) {
+      console.log(file, 'beforeRead')
+      const isLt2M = file.size / 1024 / 1024 < this.uploadSize
+      if (!isLt2M) {
+        showToast(`上传文件大小不能超过 ${this.uploadSize}MB`)
+        return false
+      }
+      return true
+    },
+    beforeDelete(file: any, detail: { index: any }) {
+      // this.dataModel.splice(detail.index, 1)
+      return true
+    },
+    async afterRead(file: any, detail: any) {
+      try {
+        file.status = 'uploading'
+        file.message = '上传中...'
+        await this.uploadFile(file.file)
+      } catch (error) {
+        closeToast()
+      }
+    },
+    onClose(e: any, item: any) {
+      const models = this.modelValue
+      const index = models.findIndex((model) => model == item)
+      if (index > -1) {
+        models.splice(index, 1)
+        this.$emit('update:modelValue', models)
+        this.onUploadChange()
+      }
+
+      e.stopPropagation()
+    },
+    async getFile(file: any) {
+      try {
+        await this.uploadFile(file)
+      } catch {
+        //
+      }
+    },
+    async uploadFile(file: any) {
+      // 上传文件
+      try {
+        // 获取签名
+        if (state.platformType === 'SCHOOL') {
+          state.platformApi = '/api-school'
+        } else if (state.platformType === 'TEACHER') {
+          state.platformApi = '/api-teacher'
+        } else if (state.platformType === 'STUDENT') {
+          state.platformApi = '/api-student'
+        }
+        const signUrl = state.platformApi + '/open/getUploadSign'
+        const tempName = file.name || ''
+        const fileName = tempName && tempName.replace(/ /gi, '_')
+        const key = new Date().getTime() + fileName
+        console.log(file)
+
+        const res = await request.post(signUrl, {
+          data: {
+            filename: fileName,
+            bucketName: this.bucket,
+            postData: {
+              filename: fileName,
+              acl: 'public-read',
+              key: key,
+              unknowValueField: []
+            }
+          }
+        })
+        showLoadingToast({
+          message: '加载中...',
+          forbidClick: true,
+          loadingType: 'spinner',
+          duration: 0
+        })
+        const obj = {
+          policy: res.data.policy,
+          signature: res.data.signature,
+          key: key,
+          KSSAccessKeyId: res.data.kssAccessKeyId,
+          acl: 'public-read',
+          name: fileName
+        }
+        const formData = new FormData()
+        for (const key in obj) {
+          formData.append(key, obj[key])
+        }
+        formData.append('file', file, fileName)
+        await umiRequest(getOssUploadUrl(this.bucket), {
+          method: 'POST',
+          data: formData
+        })
+        console.log(getOssUploadUrl(this.bucket) + key)
+        const uploadUrl = getOssUploadUrl(this.bucket) + key
+        closeToast()
+        // 判断是否是多选
+        this.$emit('update:modelValue', [...this.modelValue, uploadUrl])
+        this.onUploadChange([...this.modelValue, uploadUrl])
+      } catch (error) {
+        console.log(error, 'uploadFile')
+      }
+    }
+  },
+  render() {
+    useCustomFieldValue(() => this.modelValue)
+
+    return (
+      <div class={styles['uploader-section']}>
+        {this.modelValue.length > 0 &&
+          this.maxCount > 1 &&
+          this.modelValue.map((item: any) => (
+            <div class={[styles.uploader, styles[this.size]]}>
+              {/* 删除按钮 */}
+              {this.deletable && !this.disabled && (
+                <Icon
+                  name="cross"
+                  onClick={(e: any) => this.onClose(e, item)}
+                  class={styles['img-close']}
+                />
+              )}
+              <div class={['van-uploader__upload']}>
+                {this.uploadType === 'IMAGE' ? (
+                  <Image src={item} class={styles.previewImg} fit="cover" />
+                ) : (
+                  <video
+                    ref="videoUpload"
+                    style={{ backgroundColor: '#F8F8F8' }}
+                    class={styles.previewImg}
+                    src={item + '#t=1,4'}
+                  />
+                )}
+              </div>
+            </div>
+          ))}
+
+        {this.native ? (
+          this.maxCount > 1 ? (
+            <div class={[styles.uploader, styles[this.size]]} onClick={this.nativeUpload}>
+              {this.modelValue.length > 0 ? (
+                <div class={['van-uploader__upload']}>
+                  {this.modelValue.map((item: any) => (
+                    <>
+                      {/* 删除按钮 */}
+                      {this.deletable && !this.disabled && (
+                        <Icon
+                          name="cross"
+                          onClick={(e: any) => this.onClose(e, item)}
+                          class={[styles['img-close'], styles.singleImgClose]}
+                        />
+                      )}
+                      {this.uploadType === 'IMAGE' ? (
+                        <Image fit="cover" position="center" class={styles.uploadImg} src={item} />
+                      ) : (
+                        <video
+                          ref="videoUpload"
+                          class={styles.uploadImg}
+                          style={{ backgroundColor: '#F8F8F8' }}
+                          src={item + '#t=1,4'}
+                        />
+                      )}
+                    </>
+                  ))}
+                </div>
+              ) : (
+                <Icon name={this.uploadIcon} class={['van-uploader__upload']} size="32" />
+              )}
+            </div>
+          ) : (
+            <div class={[styles.uploader, styles[this.size]]} onClick={this.nativeUpload}>
+              <Icon name={this.uploadIcon} class={['van-uploader__upload']} size="32" />
+            </div>
+          )
+        ) : this.maxCount > 1 ? (
+          <Uploader
+            class={[styles.uploader, styles[this.size]]}
+            afterRead={this.afterRead}
+            beforeRead={this.beforeRead}
+            beforeDelete={this.beforeDelete}
+            uploadIcon={this.uploadIcon}
+            disabled={this.modelValue.length === this.maxCount || this.disabled}
+            accept={this.accept}
+          />
+        ) : (
+          <Uploader
+            class={[styles.uploader, styles[this.size]]}
+            afterRead={this.afterRead}
+            beforeRead={this.beforeRead}
+            beforeDelete={this.beforeDelete}
+            uploadIcon={this.uploadIcon}
+            accept={this.accept}
+            disabled={this.disabled}
+          >
+            {this.modelValue.length > 0 ? (
+              <div class={['van-uploader__upload']}>
+                {this.modelValue.map((item: any) => (
+                  <>
+                    {/* 删除按钮 */}
+                    {this.deletable && !this.disabled && (
+                      <Icon
+                        name="cross"
+                        onClick={(e: any) => this.onClose(e, item)}
+                        class={[styles['img-close'], styles.singleImgClose]}
+                      />
+                    )}
+
+                    {this.uploadType === 'IMAGE' ? (
+                      <Image fit="cover" position="center" class={styles.uploadImg} src={item} />
+                    ) : (
+                      <video
+                        ref="videoUpload"
+                        class={styles.uploadImg}
+                        style={{ backgroundColor: '#F8F8F8' }}
+                        src={item + '#t=1,4'}
+                      />
+                    )}
+                  </>
+                ))}
+              </div>
+            ) : (
+              <Icon name={this.uploadIcon} class={['van-uploader__upload']} size="32" />
+            )}
+          </Uploader>
+        )}
+      </div>
+    )
+  }
+})

+ 88 - 0
src/components/o-video/index.module.less

@@ -0,0 +1,88 @@
+.video-container {
+  position: relative;
+  width: 100%;
+  --plyr-color-main: #01c1b5;
+
+  video {
+    width: 100%;
+    // object-fit: cover;
+  }
+
+  :global {
+    .video-back {
+      position: absolute;
+      left: 20px;
+      top: 20px;
+      color: #fff;
+      z-index: 99;
+      font-size: 24px;
+      width: 30px;
+      height: 30px;
+      background-color: rgba(0, 0, 0, 0.5);
+      border-radius: 50%;
+      padding: 4px 5px 4px 3px;
+    }
+
+    .plyr__poster {
+      background-size: cover;
+    }
+
+    .plyr__control--overlaid {
+      border: 1px solid #fff;
+      background-color: rgba(0, 0, 0, 0.2) !important;
+    }
+    .plyr--video .plyr__control:hover {
+      background-color: transparent !important;
+    }
+  }
+
+  .video {
+    position: relative;
+  }
+}
+
+.loadingVideo {
+  position: absolute;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  background: rgba(0, 0, 0, 0.9);
+  z-index: 10;
+}
+
+.playOver {
+  background: rgba(0, 0, 0, 0.5);
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+
+  .tips {
+    font-size: 15px;
+    color: #ffffff;
+  }
+
+  .btn {
+    margin: 10px 0;
+    min-width: 94px;
+    font-size: 14px;
+    height: 28px;
+    line-height: 28px;
+  }
+
+  .replay {
+    padding-top: 12px;
+  }
+}
+
+.freeTxt {
+  font-size: 15px;
+  color: #ffffff;
+  line-height: 21px;
+  padding-top: 10px;
+}
+.freeRate {
+  color: #32ffd8;
+}

+ 193 - 0
src/components/o-video/index.tsx

@@ -0,0 +1,193 @@
+import { defineComponent, PropType } from 'vue'
+import styles from './index.module.less'
+import Plyr from 'plyr'
+import 'plyr/dist/plyr.css'
+import { Button, Icon, Loading, Toast } from 'vant'
+
+import iconVideoPlay from '@/common/images/icon_video_play.png'
+import { browser } from '@/helpers/utils'
+export default defineComponent({
+  name: 'o-video',
+  props: {
+    setting: {
+      type: Object,
+      default: () => {}
+    },
+    controls: Boolean,
+    height: String,
+    src: {
+      type: String,
+      default: ''
+    },
+    poster: {
+      type: String,
+      default: ''
+    },
+    styleValue: {
+      type: Object,
+      default: () => ({})
+    },
+    preload: {
+      type: String as PropType<'auto' | 'metadata' | 'none'>,
+      default: 'auto'
+    },
+    currentTime: {
+      type: Boolean,
+      default: true
+    },
+    playsinline: {
+      type: Boolean,
+      default: true
+    },
+    onPlay: {
+      type: Function,
+      default: () => {}
+    }
+  },
+  data() {
+    return {
+      player: null as any,
+      loading: true // 首次进入加载中
+    }
+  },
+  mounted() {
+    this._init()
+  },
+  methods: {
+    _init() {
+      // controls: [
+      //   'play-large' ,  // 中间的大播放按钮
+      //   'restart' ,  // 重新开始播放
+      //   'rewind' ,  // 按寻道时间倒带(默认 10 秒)
+      //   'play' ,  // 播放/暂停播放
+      //   'fast-forward' ,  // 快进查找时间(默认 10 秒)
+      //   'progress' ,  // 播放和缓冲的进度条和滑动条
+      //   'current-time' ,  // 播放的当前时间
+      //   ' duration' ,  // 媒体的完整持续时间
+      //   'mute' ,  // 切换静音
+      //   'volume', // 音量控制
+      //   'captions' ,  // 切换字幕
+      //   'settings' ,  // 设置菜单
+      //   'pip' ,  // 画中画(当前仅 Safari)
+      //   'airplay' ,  // Airplay(当前仅 Safari)
+      //   'download ' ,  // 显示一个下载按钮,其中包含指向当前源或您在选项中指定的自定义 URL 的链接
+      //   'fullscreen' ,  // 切换全屏
+      // ] ;
+      const controls = ['play-large', 'play', 'progress', 'captions', 'fullscreen']
+      if (this.currentTime) {
+        controls.push('current-time')
+      }
+      const params: any = {
+        controls: controls,
+        ...this.setting,
+        invertTime: false
+      }
+
+      if (browser().iPhone) {
+        params.fullscreen = {
+          enabled: true,
+          fallback: 'force',
+          iosNative: true
+        }
+      }
+
+      this.player = new Plyr((this as any).$refs.video, params)
+
+      // fullscreen: {
+      //     enabled: true,
+      //     fallback: 'force',
+      //     iosNative: true
+      //   }
+      this.player.elements.container
+        ? (this.player.elements.container.style.height = this.height || '210px')
+        : null
+
+      if (this.preload === 'none') {
+        this.loading = false
+      }
+      this.player.on('loadedmetadata', () => {
+        this.loading = false
+        this.domPlayVisibility(false)
+      })
+
+      this.player.on('play', () => {
+        this.onPlay && this.onPlay()
+      })
+
+      this.player.on('enterfullscreen', () => {
+        console.log('fullscreen')
+        const i = document.createElement('i')
+        i.id = 'fullscreen-back'
+        i.className = 'van-icon van-icon-arrow-left video-back'
+        i.addEventListener('click', () => {
+          this.player.fullscreen.exit()
+        })
+        console.log(document.getElementsByClassName('plyr'))
+        document.getElementsByClassName('plyr')[0].appendChild(i)
+      })
+
+      this.player.on('exitfullscreen', () => {
+        console.log('exitfullscreen')
+        const i = document.getElementById('fullscreen-back')
+        i && i.remove()
+      })
+    },
+    // 操作功能
+    domPlayVisibility(hide = true) {
+      const controls = document.querySelector('.plyr__controls')
+      const controls2 = document.querySelector('.plyr__control--overlaid')
+      if (hide) {
+        controls?.setAttribute('style', 'display:none')
+        controls2?.setAttribute('style', 'display:none')
+      } else {
+        controls?.removeAttribute('style')
+        setTimeout(() => {
+          controls2?.removeAttribute('style')
+        }, 200)
+      }
+    },
+
+    onReplay() {
+      this.player.restart()
+      this.player.play()
+      this.domPlayVisibility(false)
+    }
+  },
+  unmounted() {
+    this.player?.destroy()
+  },
+  render() {
+    return (
+      <div class={styles['video-container']}>
+        <video
+          ref="video"
+          class={styles['video']}
+          src={this.src}
+          playsinline={this.playsinline}
+          poster={this.poster}
+          preload={this.preload}
+          style={{ ...this.styleValue }}
+        ></video>
+        {/* </div> */}
+        {/* 加载视频使用 */}
+        {this.loading && (
+          <div
+            class={styles.loadingVideo}
+            style={{
+              height: this.height || '210px'
+            }}
+          >
+            <Loading
+              size={36}
+              color="#FF8057"
+              vertical
+              style={{ height: '100%', justifyContent: 'center' }}
+            >
+              加载中...
+            </Loading>
+          </div>
+        )}
+      </div>
+    )
+  }
+})

+ 2 - 1
src/helpers/native-message.ts

@@ -36,7 +36,7 @@ const browserInfo = browser()
 if (browserInfo.isApp) {
   window.addEventListener('message', (evt) => {
     try {
-      console.log('message', evt.data)
+      console.log('app交互接受:', evt.data)
       const data = evt.data ? (typeof evt.data === 'object' ? evt.data : JSON.parse(evt.data)) : {}
       const uuid = data.content?.uuid || data.uuid
       // console.log(uuid, data.content, 'uuid')
@@ -78,6 +78,7 @@ export const postMessage = (data: IPostMessage, callback?: CallBack) => {
     const uuid = getRandomKey()
     calls[uuid] = callback || loop
     data.content = data.content ? { ...data.content, uuid } : { uuid }
+    console.log('app交互发送:', data)
     instance.postMessage(JSON.stringify(data))
   }
 }

+ 16 - 0
src/router/routes-school.ts

@@ -285,6 +285,22 @@ export default [
         meta: {
           title: '考勤规则'
         }
+      },
+      {
+        path: '/orchestra-story',
+        name: 'orchestra-story',
+        component: () => import('@/school/orchestra-story/index'),
+        meta: {
+          title: '乐团事迹'
+        }
+      },
+      {
+        path: '/story-operation',
+        name: 'story-operation',
+        component: () => import('@/school/orchestra-story/story-operation/index'),
+        meta: {
+          title: '添加事迹'
+        }
       }
 
       //

+ 9 - 4
src/school/approval-manage/course-adjust.tsx

@@ -145,6 +145,11 @@ export default defineComponent({
         reset()
         setTimeout(() => {
           showToast('调整成功')
+          if (browser().isApp) {
+            postMessage({ api: 'back' })
+          } else {
+            router.back()
+          }
         }, 100)
       } catch (e: any) {
         showToast(e.message)
@@ -177,21 +182,21 @@ export default defineComponent({
                 is-link
               />
               <Field
-                label="课程开始日期"
+                label="课日期"
                 inputAlign="right"
                 readonly
                 isLink
-                placeholder="请选择课程开始日期"
+                placeholder="请选择课日期"
                 onClick={() => (state.showPopoverTime = true)}
                 modelValue={forms.classDate ? dayjs(forms.classDate).format('YYYY-MM-DD') : ''}
               />
 
               <Field
-                label="课程开始时间"
+                label="课时间"
                 inputAlign="right"
                 readonly
                 isLink
-                placeholder="请选择课程开始时间"
+                placeholder="请选择课时间"
                 modelValue={forms.startTime ? dayjs(forms.startTime).format('HH:mm') : ''}
                 onClick={() => {
                   let freeTimeCount = 0

+ 2 - 1
src/school/companion-teacher/companion-teacher-register.tsx

@@ -199,7 +199,8 @@ export default defineComponent({
               paramName: 'qr_code_expire_hours'
             }
           })
-          if (dayjs(Number(state.t)).isBefore(dayjs().add(data.paramValue, 'hour'))) {
+
+          if (dayjs(Number(state.t)).add(data.paramValue, 'hour').isBefore(dayjs())) {
             showDialog({
               title: '提示',
               message: '二维码已失效',

+ 17 - 3
src/school/companion-teacher/index.tsx

@@ -106,8 +106,6 @@ export default defineComponent({
           res.data.schoolName +
           '&t=' +
           +new Date()
-
-        console.log(form.url)
       } catch {
         //
       }
@@ -289,7 +287,23 @@ export default defineComponent({
         >
           <OHeader border={false}>
             {{
-              right: () => <Icon name="plus" size={19} onClick={() => (form.showQrcode = true)} />
+              right: () => (
+                <Icon
+                  name="plus"
+                  size={19}
+                  onClick={() => {
+                    form.url =
+                      location.origin +
+                      '/orchestra-school/#/companion-teacher-register?id=' +
+                      form.schoolId +
+                      '&name=' +
+                      form.schoolName +
+                      '&t=' +
+                      +new Date()
+                    form.showQrcode = true
+                  }}
+                />
+              )
             }}
           </OHeader>
           <OSearch

+ 17 - 1
src/school/manage-teacher/index.tsx

@@ -246,7 +246,23 @@ export default defineComponent({
         >
           <OHeader border={false}>
             {{
-              right: () => <Icon name="plus" size={19} onClick={() => (form.showQrcode = true)} />
+              right: () => (
+                <Icon
+                  name="plus"
+                  size={19}
+                  onClick={() => {
+                    form.url =
+                      location.origin +
+                      '/orchestra-school/#/manage-teacher-register?id=' +
+                      form.schoolId +
+                      '&name=' +
+                      form.schoolName +
+                      '&t=' +
+                      +new Date()
+                    form.showQrcode = true
+                  }}
+                />
+              )
             }}
           </OHeader>
           <OSearch

+ 1 - 1
src/school/manage-teacher/manage-teacher-register.tsx

@@ -127,7 +127,7 @@ export default defineComponent({
               paramName: 'qr_code_expire_hours'
             }
           })
-          if (dayjs(Number(state.t)).isBefore(dayjs().add(data.paramValue, 'hour'))) {
+          if (dayjs(Number(state.t)).add(data.paramValue, 'hour').isBefore(dayjs())) {
             showDialog({
               title: '提示',
               message: '二维码已失效',

binární
src/school/orchestra-story/images/icon-edit.png


binární
src/school/orchestra-story/images/icon-step-calendar.png


binární
src/school/orchestra-story/images/icon-step.png


binární
src/school/orchestra-story/images/icon-upload-video-cover.png


binární
src/school/orchestra-story/images/icon-upload-video.png


binární
src/school/orchestra-story/images/icon-upload.png


+ 82 - 0
src/school/orchestra-story/index.module.less

@@ -0,0 +1,82 @@
+.cellGroup {
+  margin: 12px 13px 0;
+  overflow: hidden;
+  border-radius: 10px;
+  :global {
+    .van-cell {
+      font-size: 16px;
+      color: #333333;
+      padding: 16px 12px;
+    }
+  }
+}
+
+.storySteps {
+  background-color: transparent;
+  padding-top: 20px;
+  margin-left: 8px;
+
+  :global {
+    .van-step--vertical {
+      padding-right: 13px;
+      &::after {
+        border-width: 0 !important;
+      }
+    }
+  }
+
+  .stepTimes {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 20px;
+    font-weight: bold;
+    color: #333333;
+
+    .stepTime {
+      font-family: 'DINA';
+    }
+
+    .stepEdit {
+      font-size: 14px;
+      font-weight: 400;
+      color: #777777;
+      display: flex;
+      align-items: center;
+      :global {
+        .van-icon {
+          font-size: 14px;
+          margin-right: 3px;
+        }
+      }
+    }
+  }
+
+  .content {
+    padding-top: 8px;
+    font-size: 14px;
+    color: #666666;
+    line-height: 20px;
+  }
+
+  .storySwipe {
+    padding-top: 6px;
+    --van-swipe-indicator-size: 8px;
+
+    .swipeImg {
+      width: 100%;
+      height: 200px;
+      border-radius: 10px;
+      overflow: hidden;
+    }
+  }
+
+  .iconActive {
+    width: 18px;
+    height: 18px;
+  }
+  .iconInactive {
+    width: 12px;
+    height: 12px;
+  }
+}

+ 237 - 0
src/school/orchestra-story/index.tsx

@@ -0,0 +1,237 @@
+import OHeader from '@/components/o-header'
+import {
+  Cell,
+  CellGroup,
+  Icon,
+  Image,
+  List,
+  Picker,
+  Popup,
+  Step,
+  Steps,
+  Swipe,
+  SwipeItem
+} from 'vant'
+import { defineComponent, onMounted, reactive } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import styles from './index.module.less'
+import iconEdit from './images/icon-edit.png'
+import iconStep from './images/icon-step.png'
+import iconStepCalendar from './images/icon-step-calendar.png'
+import request from '@/helpers/request'
+import { state as baseState } from '@/state'
+import OFullRefresh from '@/components/o-full-refresh'
+import OEmpty from '@/components/o-empty'
+import dayjs from 'dayjs'
+import OVideo from '@/components/o-video'
+
+export default defineComponent({
+  name: 'orchestra-story',
+  setup() {
+    const route = useRoute()
+    const router = useRouter()
+    const state = reactive({
+      orchestraStatus: false,
+      orchestraList: [] as any,
+      selectOrchestra: {} as any,
+      isClick: false,
+      list: [] as any,
+      listState: {
+        dataShow: true, // 判断是否有数据
+        loading: false,
+        finished: false,
+        refreshing: false,
+        height: 0 // 页面头部高度,为了处理下拉刷新用的
+      },
+      params: {
+        type: null,
+        page: 1,
+        rows: 20
+      }
+    })
+
+    // 获取乐团列表
+    const getOrchestras = async () => {
+      try {
+        const { data } = await request.post('/api-school/orchestra/page', {
+          data: {
+            page: 1,
+            rows: 100,
+            schoolId: baseState.user.data.school.id
+          }
+        })
+        const temps = data.rows || []
+        const s = [] as any
+        temps.forEach((item: any) => {
+          s.push({
+            text: item.name,
+            value: item.id
+          })
+        })
+        state.orchestraList = [...s]
+
+        // 判断是否有乐团
+        if (s.length > 0) {
+          state.selectOrchestra = s[0]
+
+          getList()
+        }
+      } catch {
+        //
+      }
+    }
+
+    const getList = async () => {
+      try {
+        if (state.isClick) return
+        state.isClick = true
+        const res = await request.post('/api-school/orchestraStory/page', {
+          data: {
+            orchestraId: state.selectOrchestra.value
+          }
+        })
+        state.listState.loading = false
+        state.listState.refreshing = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (state.list.length > 0 && result.current === 1) {
+          return
+        }
+        state.list = state.list.concat(result.rows || [])
+        state.listState.finished = result.current >= result.pages
+        state.params.page = result.current + 1
+        state.listState.dataShow = state.list.length > 0
+        state.isClick = false
+      } catch {
+        state.listState.dataShow = false
+        state.listState.finished = true
+        state.listState.refreshing = false
+        state.isClick = false
+      }
+    }
+
+    const onRefresh = () => {
+      state.params.page = 1
+      state.list = []
+      state.listState.dataShow = true // 判断是否有数据
+      state.listState.loading = false
+      state.listState.finished = false
+      getList()
+    }
+
+    const onEdit = async (item: any) => {
+      router.push({
+        path: '/story-operation',
+        query: {
+          id: item.id
+        }
+      })
+    }
+
+    onMounted(async () => {
+      getOrchestras()
+    })
+    return () => (
+      <div class={styles.orchestraStory}>
+        <OHeader>
+          {{
+            right: () => (
+              <span
+                style={{ color: '#777777' }}
+                onClick={() => {
+                  router.push('/story-operation')
+                }}
+              >
+                添加
+              </span>
+            )
+          }}
+        </OHeader>
+
+        {state.orchestraList.length > 0 && (
+          <CellGroup inset class={styles.cellGroup}>
+            <Cell
+              title={state.selectOrchestra.text || ' '}
+              isLink
+              center
+              onClick={() => (state.orchestraStatus = true)}
+            ></Cell>
+          </CellGroup>
+        )}
+
+        {state.listState.dataShow ? (
+          // <OFullRefresh
+          //   v-model:modelValue={state.listState.refreshing}
+          //   onRefresh={onRefresh}
+          //   style={{
+          //     minHeight: `calc(100vh - ${state.listState.height}px)`
+          //   }}
+          // >
+          <List
+            v-model:loading={state.listState.loading}
+            finished={state.listState.finished}
+            finishedText=" "
+            class={[styles.liveList]}
+            onLoad={getList}
+            immediateCheck={false}
+          >
+            <Steps direction="vertical" class={styles.storySteps}>
+              {state.list.map((item: any) => (
+                <Step
+                  v-slots={{
+                    'inactive-icon': () => <Image src={iconStep} class={styles.iconInactive} />,
+                    'active-icon': () => <Image src={iconStepCalendar} class={styles.iconActive} />
+                  }}
+                >
+                  <div class={styles.stepTimes}>
+                    <div class={styles.stepTime}>
+                      {dayjs(item.createTime).format('YYYY年MM月DD日')}
+                    </div>
+                    <span class={styles.stepEdit} onClick={() => onEdit(item)}>
+                      <Icon name={iconEdit} />
+                      编辑
+                    </span>
+                  </div>
+                  <p class={[styles.content, 'van-multi-ellipsis--l2']}>{item.content}</p>
+
+                  <Swipe class={styles.storySwipe}>
+                    {item.attachments &&
+                      item.attachments.map((child: any) => (
+                        <SwipeItem>
+                          {item.type === 'IMAGE' && (
+                            <Image src={child.url} class={styles.swipeImg} fit="cover" />
+                          )}
+                          {item.type === 'VIDEO' && (
+                            <OVideo
+                              src={child.url}
+                              poster={child.coverImage}
+                              class={styles.swipeImg}
+                            />
+                          )}
+                        </SwipeItem>
+                      ))}
+                  </Swipe>
+                </Step>
+              ))}
+            </Steps>
+          </List>
+        ) : (
+          <OEmpty btnStatus={false} tips="暂无事迹" />
+        )}
+
+        <Popup v-model:show={state.orchestraStatus} position="bottom" round>
+          <Picker
+            columns={state.orchestraList}
+            onCancel={() => (state.orchestraStatus = false)}
+            onConfirm={(val: any) => {
+              state.selectOrchestra = val.selectedOptions[0]
+              state.orchestraStatus = false
+
+              onRefresh()
+            }}
+          />
+        </Popup>
+      </div>
+    )
+  }
+})

+ 76 - 0
src/school/orchestra-story/story-operation/index.module.less

@@ -0,0 +1,76 @@
+.cellGroup {
+  margin: 12px 13px;
+  border-radius: 10px;
+  overflow: hidden;
+  --van-uploader-size: 94px;
+  :global {
+    .van-cell {
+      padding: 18px 15px;
+      font-size: 16px;
+      color: #333333;
+    }
+    .van-cell__right-icon {
+      font-size: 13px;
+      font-weight: bold;
+      color: #d8d8d8;
+    }
+  }
+
+  .title {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    .nums {
+      font-size: 14px;
+      color: #999999;
+    }
+  }
+
+  :global {
+    .van-cell {
+      font-size: 16px;
+      padding: 18px 12px;
+    }
+    .van-cell__value {
+      color: #333;
+    }
+
+    .van-radio-group {
+      justify-content: flex-end;
+    }
+  }
+
+  // .radioSection {
+  //   position: relative;
+  //   min-width: 32px;
+  //   justify-content: center;
+  // }
+
+  // .radioItem {
+  //   position: absolute;
+  //   top: 0;
+  //   left: 0;
+  //   right: 0;
+  //   bottom: 0;
+  //   opacity: 0;
+  // }
+
+  // .radioSection + .radioSection {
+  //   margin-left: 12px;
+  // }
+}
+
+.uploader {
+  :global {
+    .van-uploader__upload {
+      width: 102px;
+      height: 94px;
+      background-color: #fff;
+    }
+    .van-uploader__upload-icon,
+    .van-icon__image {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}

+ 397 - 0
src/school/orchestra-story/story-operation/index.tsx

@@ -0,0 +1,397 @@
+import OSticky from '@/components/o-sticky'
+import request from '@/helpers/request'
+import dayjs from 'dayjs'
+import {
+  Button,
+  Cell,
+  CellGroup,
+  DatePicker,
+  Field,
+  Picker,
+  Popup,
+  Radio,
+  RadioGroup,
+  showSuccessToast,
+  showToast
+} from 'vant'
+import { defineComponent, onMounted, reactive } from 'vue'
+import styles from './index.module.less'
+import iconUpload from '../images/icon-upload.png'
+import iconUploadVideo from '../images/icon-upload-video.png'
+import iconUploadVideoCover from '../images/icon-upload-video-cover.png'
+import OUploadAll from '@/components/o-upload-all'
+import OHeader from '@/components/o-header'
+import ODialog from '@/components/o-dialog'
+import { useRoute, useRouter } from 'vue-router'
+
+export default defineComponent({
+  name: 'story-operation',
+  setup() {
+    const router = useRouter()
+    const route = useRoute()
+    const forms = reactive({
+      id: route.query.id || null,
+      content: '',
+      orchestraStatus: false,
+      orchestraList: [] as any,
+      selectOrchestra: {} as any,
+      createTime: new Date() as any,
+      createTimeStatus: false,
+      currentDate: [dayjs().format('YYYY'), dayjs().format('MM'), dayjs().format('DD')],
+      storyType: 'IMAGE',
+      attachments: [] as any, //群发消息附件
+      video: [] as any,
+      videoCover: [] as any,
+      delStatus: false
+    })
+
+    // 获取乐团列表
+    const getOrchestras = async () => {
+      try {
+        const { data } = await request.post('/api-school/orchestra/page', {
+          data: {
+            page: 1,
+            rows: 100
+          }
+        })
+        const temps = data.rows || []
+        const s = [] as any
+        temps.forEach((item: any) => {
+          s.push({
+            text: item.name,
+            value: item.id
+          })
+        })
+        forms.orchestraList = [...s]
+
+        // 判断是否有乐团
+        if (s.length > 0) {
+          forms.selectOrchestra = s[0]
+        }
+      } catch {
+        //
+      }
+    }
+
+    const getDetails = async () => {
+      try {
+        if (!forms.id) {
+          return
+        }
+        const { data } = await request.get('/api-school/orchestraStory/detail/' + forms.id)
+        console.log(data)
+        forms.content = data.content
+        forms.createTime = data.createTime
+        forms.storyType = data.type
+
+        forms.orchestraList.forEach((item: any) => {
+          if (item.value === item.orchestraId) {
+            forms.selectOrchestra = item
+          }
+        })
+
+        if (data.type === 'IMAGE') {
+          data.attachments &&
+            data.attachments.forEach((item: any) => {
+              forms.attachments.push(item.url)
+            })
+        } else {
+          const temp = data.attachments ? data.attachments[0] : []
+          forms.video.push(temp.url)
+          forms.videoCover.push(temp.coverImage)
+        }
+      } catch {
+        //
+      }
+    }
+
+    const onSumbit = async () => {
+      try {
+        if (!forms.selectOrchestra.value) {
+          showToast('请选择乐团')
+          return
+        }
+        if (!forms.createTime) {
+          showToast('请选择事迹日期')
+          return
+        }
+        if (!forms.content) {
+          showToast('请输入事迹内容')
+          return
+        }
+
+        if (forms.storyType === 'IMAGE' && forms.attachments.length <= 0) {
+          showToast('请上传照片')
+          return
+        }
+        if (forms.storyType === 'VIDEO') {
+          if (forms.video.length <= 0) {
+            showToast('请上传视频')
+            return
+          }
+          if (forms.videoCover.length <= 0) {
+            showToast('请上传视频封面')
+            return
+          }
+        }
+
+        const attachments: any = []
+        if (forms.storyType === 'IMAGE') {
+          forms.attachments.forEach((item: any) => {
+            const temp = {
+              attachmentType: forms.storyType,
+              url: item
+            }
+
+            attachments.push(temp)
+          })
+        } else {
+          attachments.push({
+            attachmentType: forms.storyType,
+            url: forms.video[0],
+            coverImage: forms.videoCover[0]
+          })
+        }
+
+        console.log({
+          createTime: dayjs(forms.createTime).format('YYYY-MM-DD HH:mm:ss'),
+          orchestraId: forms.selectOrchestra.value,
+          content: forms.content,
+          type: forms.storyType,
+          attachments
+        })
+        const params = {
+          createTime: dayjs(forms.createTime).format('YYYY-MM-DD HH:mm:ss'),
+          orchestraId: forms.selectOrchestra.value,
+          content: forms.content,
+          type: forms.storyType,
+          attachments
+        }
+
+        if (forms.id) {
+          await request.post('/api-school/orchestraStory/update', {
+            data: {
+              ...params,
+              id: forms.id
+            }
+          })
+
+          setTimeout(() => {
+            showSuccessToast('修改成功')
+          }, 100)
+        } else {
+          await request.post('/api-school/orchestraStory/save', {
+            data: params
+          })
+
+          setTimeout(() => {
+            showSuccessToast('添加成功')
+          }, 100)
+        }
+
+        setTimeout(() => {
+          router.back()
+        }, 1100)
+      } catch {
+        //
+      }
+    }
+
+    //
+    const onConfirm = async () => {
+      try {
+        await request.post('/api-school/orchestraStory/remove', {
+          requestType: 'form',
+          data: {
+            id: forms.id
+          }
+        })
+
+        setTimeout(() => {
+          showSuccessToast('删除成功')
+        }, 100)
+        setTimeout(() => {
+          router.back()
+        }, 1100)
+      } catch {
+        //
+      }
+    }
+
+    onMounted(async () => {
+      if (forms.id) {
+        document.title = '修改事迹'
+      }
+      await getOrchestras()
+      await getDetails()
+    })
+    return () => (
+      <div class={styles.storyOperation}>
+        <OHeader title={forms.id ? '修改事迹' : '添加事迹'}>
+          {{
+            right: () =>
+              forms.id && (
+                <span
+                  style={{ color: '#777777' }}
+                  onClick={() => {
+                    forms.delStatus = true
+                  }}
+                >
+                  删除
+                </span>
+              )
+          }}
+        </OHeader>
+
+        <CellGroup inset class={styles.cellGroup}>
+          <Field
+            inputAlign="right"
+            label="所属乐团"
+            modelValue={forms.selectOrchestra.text}
+            placeholder="请选择所属乐团"
+            onClick={() => {
+              forms.orchestraStatus = true
+            }}
+            readonly
+            isLink
+          />
+          <Field
+            inputAlign="right"
+            label="事迹日期"
+            modelValue={forms.createTime ? dayjs(forms.createTime).format('YYYY-MM-DD') : ''}
+            placeholder="请选择事迹日期"
+            onClick={() => {
+              forms.createTimeStatus = true
+            }}
+            readonly
+            isLink
+            class={styles.inputForm}
+          />
+        </CellGroup>
+
+        <CellGroup inset class={styles.cellGroup}>
+          <Cell title="事迹内容">
+            {{
+              title: () => (
+                <div class={styles.title}>
+                  <div class={styles.name}>事迹内容</div>
+                  <div class={styles.nums}>{forms.content.length || 0}/200</div>
+                </div>
+              ),
+              label: () => (
+                <Field
+                  style={{ padding: '0', marginTop: '12px' }}
+                  placeholder="请输入乐团事迹内容"
+                  type="textarea"
+                  rows={3}
+                  v-model={forms.content}
+                  maxlength={200}
+                />
+              )
+            }}
+          </Cell>
+        </CellGroup>
+
+        <CellGroup inset class={styles.cellGroup}>
+          <Cell title="事迹资料类型" center>
+            {{
+              value: () => (
+                <RadioGroup
+                  checked-color="#FF8057"
+                  v-model={forms.storyType}
+                  direction="horizontal"
+                >
+                  <Radio class={styles.radioItem} name={'IMAGE'}>
+                    图片
+                  </Radio>
+                  <Radio class={styles.radioItem} name={'VIDEO'}>
+                    视频
+                  </Radio>
+                </RadioGroup>
+              )
+            }}
+          </Cell>
+          {forms.storyType === 'IMAGE' && (
+            <Cell center>
+              {{
+                title: () => (
+                  <OUploadAll
+                    style={{ marginBottom: '12px' }}
+                    v-model:modelValue={forms.attachments}
+                    maxCount={9}
+                    uploadIcon={iconUpload}
+                  />
+                )
+              }}
+            </Cell>
+          )}
+          {forms.storyType === 'VIDEO' && (
+            <Cell center titleStyle={{ display: 'flex' }}>
+              {{
+                title: () => (
+                  <>
+                    <OUploadAll
+                      style={{ marginBottom: '12px' }}
+                      v-model:modelValue={forms.video}
+                      accept="video/*"
+                      uploadType="VIDEO"
+                      uploadIcon={iconUploadVideo}
+                      deletable={false}
+                    />
+
+                    <OUploadAll
+                      deletable={false}
+                      style={{ marginBottom: '12px' }}
+                      v-model:modelValue={forms.videoCover}
+                      uploadIcon={iconUploadVideoCover}
+                    />
+                  </>
+                )
+              }}
+            </Cell>
+          )}
+        </CellGroup>
+
+        <OSticky position="bottom">
+          <div class={'btnGroup'}>
+            <Button round block type="primary" onClick={onSumbit}>
+              保存
+            </Button>
+          </div>
+        </OSticky>
+
+        <Popup v-model:show={forms.createTimeStatus} position="bottom" round>
+          <DatePicker
+            maxDate={new Date()}
+            v-model={forms.currentDate}
+            onConfirm={(val: any) => {
+              const selectedValues = val.selectedValues.join('-')
+              forms.createTime = dayjs(selectedValues).toDate()
+              forms.createTimeStatus = false
+            }}
+          />
+        </Popup>
+
+        <Popup v-model:show={forms.orchestraStatus} position="bottom" round>
+          <Picker
+            columns={forms.orchestraList}
+            onCancel={() => (forms.orchestraStatus = false)}
+            onConfirm={(val: any) => {
+              forms.selectOrchestra = val.selectedOptions[0]
+              forms.orchestraStatus = false
+            }}
+          />
+        </Popup>
+
+        <ODialog
+          v-model:show={forms.delStatus}
+          title="删除事迹"
+          messageAlign="left"
+          message="删除后学生将无法再看到本条事迹确认要删除吗?"
+          showCancelButton
+          onConfirm={onConfirm}
+        ></ODialog>
+      </div>
+    )
+  }
+})

+ 1 - 1
src/school/orchestra/compontent/information.tsx

@@ -439,7 +439,7 @@ export default defineComponent({
                 <div style={{ textAlign: 'center' }}>
                   <span class={styles.codeBtnText}>扫描上方二维码完成资料填写</span>
                 </div>
-                <div class={styles.codeTips}>二维码将在两小时后失效,请及时登记</div>
+                {/* <div class={styles.codeTips}>二维码将在两小时后失效,请及时登记</div> */}
               </div>
             </div>
             <div class={styles.codeBottom}>

+ 2 - 2
src/school/train-planning/modal/timer/index.tsx

@@ -194,10 +194,10 @@ export default defineComponent({
           {state.useTimer.map((item: any) => (
             <Cell
               center
-              title={`${dayjs(item.startTime).format('HH:mm')}~${dayjs(item.endTime).format(
+              value={`${dayjs(item.startTime).format('HH:mm')}~${dayjs(item.endTime).format(
                 'HH:mm'
               )}`}
-              value="可选时间"
+              title="可选时间"
             ></Cell>
           ))}
 

+ 278 - 0
src/student/music-group/goods-detail/index.module.less

@@ -0,0 +1,278 @@
+.swipeItemImg,
+.swipe {
+  width: 100%;
+  height: 375px;
+  vertical-align: middle;
+}
+.custom-indicator {
+  position: absolute;
+  right: 5px;
+  bottom: 5px;
+  padding: 4px 8px;
+  font-size: 14px;
+  color: #fff;
+  background: rgba(0, 0, 0, 0.5);
+  border-radius: 12px;
+}
+
+.goodsHead {
+  padding: 8px 0;
+  :global {
+    .van-cell {
+      padding-top: 5px;
+      padding-bottom: 5px;
+    }
+  }
+}
+
+.priceGroup {
+  display: flex;
+  align-items: center;
+  .price {
+    color: #ff4e19;
+    font-size: 24px;
+    font-weight: bold;
+    i {
+      font-size: 16px;
+      font-style: normal;
+    }
+  }
+  .delPrice {
+    font-size: 14px;
+    color: #999999;
+    margin-left: 12px;
+    line-height: 20px;
+  }
+
+  .stock {
+    font-size: 14px;
+    color: #999999;
+  }
+}
+
+.goodsName {
+  font-size: 16px;
+  font-weight: 500;
+  color: #333333;
+  line-height: 22px;
+}
+
+.row {
+  background-color: #fff;
+  padding: var(--van-cell-vertical-padding) var(--van-cell-horizontal-padding);
+  .col {
+    font-size: 16px;
+    color: #333333;
+  }
+}
+
+.radio-group {
+  display: flex;
+  flex-wrap: wrap;
+  // margin-top: 14px;
+}
+
+.radio {
+  margin-right: 8px;
+  margin-bottom: 8px;
+  min-width: 60px;
+  :global {
+    .van-radio__label--disabled {
+      opacity: 0.5;
+    }
+    .van-radio__icon {
+      display: none;
+    }
+    .van-tag--large {
+      height: 27px;
+      font-size: 13px;
+      text-align: center;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+    .van-tag {
+      box-sizing: border-box;
+    }
+    .van-tag--default {
+      color: #999999;
+    }
+    .van-tag--primary {
+      background-color: #f7f8f9;
+    }
+    .van-radio__label {
+      margin-left: 0;
+    }
+  }
+}
+.badge {
+  :global(.van-badge) {
+    border-radius: 6px 0px 6px 0px;
+    font-size: 12px;
+  }
+}
+
+.section {
+  background: #fff;
+  padding: 12px 0 0;
+}
+
+.detail {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 14px;
+  color: #999999;
+  line-height: 20px;
+  margin-bottom: 12px;
+  span {
+    padding: 0 10px;
+  }
+
+  &::before,
+  &::after {
+    display: inline-block;
+    content: ' ';
+    width: 40px;
+    height: 3px;
+  }
+  &::before {
+    background: linear-gradient(90deg, rgba(238, 238, 238, 0) 0%, #d8d8d8 100%);
+  }
+  &::after {
+    background: linear-gradient(270deg, rgba(238, 238, 238, 0) 0%, #d8d8d8 100%);
+  }
+}
+
+.photoDetail {
+  padding-bottom: 20px;
+  img {
+    width: 100%;
+    vertical-align: middle;
+  }
+}
+
+.goodsDetail {
+  min-height: 100vh;
+  background-color: #f7f8f9;
+  // margin-bottom: var(--van-action-bar-height);
+}
+
+.actionBar {
+  padding-left: 5px;
+  padding-right: 5px;
+  justify-content: space-between;
+  box-shadow: 0px -10px 10px var(--box-shadow-color);
+  box-sizing: border-box;
+  :global {
+    .van-submit-bar__bar {
+      justify-content: space-between;
+      padding: 0;
+    }
+    .van-action-bar-icon {
+      align-items: center;
+    }
+    .van-action-bar-icon__icon {
+      margin-bottom: 0;
+      line-height: 0;
+    }
+    .van-badge {
+      background: #ff4e19;
+    }
+  }
+}
+
+.addCertBtn {
+  background: #fff;
+  color: var(--van-primary);
+}
+
+.buyGroup {
+  flex-basis: 60%;
+  display: flex;
+  justify-content: center;
+  border: var(--van-button-border-width) solid var(--van-button-primary-border-color) !important;
+  border-radius: 20px;
+  background-color: var(--van-primary);
+  overflow: hidden;
+  box-sizing: border-box;
+}
+.selectWrap {
+  padding-top: 1.5px;
+  font-size: 14px;
+  color: #666;
+  padding-bottom: 12px;
+}
+
+.shareBtn {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  color: #666666;
+}
+
+.shareWrap {
+  display: flex;
+  border-radius: 10px;
+  border: 1px solid var(--van-primary);
+  overflow: hidden;
+  padding: 14px;
+  align-items: center;
+  background-color: #fff;
+  margin-top: 16px;
+  .sharePic {
+    width: 100px;
+    height: 100px;
+    border-radius: 8px;
+  }
+  .shareLeft {
+    margin-right: 10px;
+  }
+  .shareRight {
+    flex: 1;
+    overflow: hidden;
+  }
+  .shareShopTitle {
+    font-size: 16px;
+    font-weight: 400;
+    color: #333333;
+    display: -webkit-box;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+  }
+  .shareShopDes {
+    font-size: 14px;
+    font-weight: 400;
+    color: #999999;
+    margin: 10px 0 20px 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .shareShopValue {
+    font-size: 16px;
+    color: #ff4e19;
+  }
+  .shareShopOldPrice {
+    color: #e5e5e5;
+    margin-left: 5px;
+  }
+
+  :global {
+    .van-card {
+      background: transparent;
+    }
+  }
+}
+.imagesOverlayWrap {
+  :global {
+    .van-badge__wrapper {
+      top: 40px;
+    }
+    .van-image-preview__index {
+      top: 40px;
+    }
+  }
+}

+ 219 - 0
src/student/music-group/goods-detail/index.tsx

@@ -0,0 +1,219 @@
+import request from '@/helpers/request'
+import { moneyFormat } from '@/helpers/utils'
+import { Swipe, SwipeItem, Image, CellGroup, Cell, showImagePreview } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'goods-detail',
+  props: {
+    id: {
+      type: String,
+      defualt: ''
+    }
+  },
+  data() {
+    return {
+      albumPics: [] as any[],
+      product: {} as Record<string | number | symbol, any>,
+      radio: 0,
+      skuStockListTemp: [],
+      detailMobileHtml: '',
+      loading: false,
+      addGoodsShow: false,
+      selectGoodsItem: {},
+      cartCount: 0,
+      showType: 'cart',
+      shareShow: false // 分享弹窗
+    }
+  },
+  computed: {
+    skuStockList() {
+      // 处理规格
+      const product = this.product
+      const skuStockList: any =
+        this.skuStockListTemp.length > 0
+          ? this.skuStockListTemp
+          : [
+              {
+                id: -1,
+                price: product.price,
+                pic: product.pic,
+                stock: product.stock,
+                spData: null
+              }
+            ]
+      skuStockList.forEach((item: any) => {
+        if (item.spData) {
+          const spData = JSON.parse(item.spData)
+          item.spDataJson = spData.reduce((spDataJson, value) => {
+            spDataJson += value.value
+            return spDataJson
+          }, '')
+          item.sku = spData
+            .reduce((sku, value) => {
+              sku.push(`${value.key}: ${value.value}`)
+              return sku
+            }, [])
+            .join(',')
+        } else {
+          item.spDataJson = '默认'
+        }
+      })
+      return skuStockList
+    },
+    getPrice() {
+      const item = this.skuStockList.filter((n) => n.id == this.radio) as any
+      if (item && Array.isArray(item) && item.length) {
+        return item[0].price
+      }
+      return 0
+    }
+  },
+  async mounted() {
+    try {
+      this.loading = true
+      const res = await request.get(`/api-student/open/mall/product/detail/${this.id}`)
+      this.loading = false
+      const result = res.data || {}
+      this.albumPics = [result.product.pic]
+        .concat(result.product.albumPics.split(','))
+        .filter((n) => n)
+      this.product = result.product
+      this.skuStockListTemp = result.skuStockList || []
+      if (this.skuStockListTemp.length) {
+        const len = this.skuStockListTemp.length
+        for (let i = 0; i < len; i++) {
+          const item = this.skuStockListTemp[i] as any
+          if (item.stock >= 0) {
+            this.radio = item.id
+            break
+          }
+        }
+      }
+      this.detailMobileHtml = result.product.detailMobileHtml || result.product.detailHtml
+    } catch {
+      //
+    }
+  },
+  methods: {
+    onPreview(index: number) {
+      // 图片预览
+      showImagePreview({
+        images: this.albumPics,
+        startPosition: index,
+        closeable: true,
+        className: styles.imagesOverlayWrap
+      })
+    },
+    onShowImg(target: any) {
+      const { localName } = target.srcElement
+      if (localName !== 'img') {
+        return
+      }
+      let startPosition = 0
+      const domList = document.querySelectorAll('.msgWrap img')
+      const imgList = Array.from(domList).map((item: any, index: number) => {
+        if (target.srcElement == item) {
+          startPosition = index
+        }
+        return item.src
+      })
+
+      showImagePreview({
+        images: imgList,
+        startPosition: startPosition,
+        closeable: true,
+        className: styles.imagesOverlayWrap
+      })
+    },
+    onShowCart(type = 'cart') {
+      this.selectGoodsItem = {
+        price: this.product.pic,
+        stock: this.product.stock,
+        skuStockList: this.skuStockListTemp.length ? this.skuStockListTemp : undefined,
+        brandName: this.product.brandName,
+        productCategoryId: this.product.productCategoryId,
+        name: this.product.name,
+        productSn: this.product.productSn,
+        productSubTitle: this.product.subTitle,
+        id: this.product.id
+      }
+      this.showType = type
+      // 打开购物弹框
+      this.addGoodsShow = true
+    }
+  },
+  render() {
+    const product = this.product
+    const selectSku = this.skuStockList.find((n: any) => n.id === this.radio)
+    return (
+      <div class={styles.goodsDetail}>
+        <Swipe
+          class={styles.swipe}
+          lazyRender
+          v-slots={{
+            indicator: (item: any) =>
+              item.total > 1 && (
+                <div class={styles['custom-indicator']}>
+                  {(item.active || 0) + 1} / {item.total}
+                </div>
+              )
+          }}
+        >
+          {this.albumPics.map((item: string, index: number) => (
+            <SwipeItem>
+              <Image
+                class={styles.swipeItemImg}
+                src={item}
+                onClick={() => this.onPreview(index)}
+                fit="cover"
+              />
+            </SwipeItem>
+          ))}
+        </Swipe>
+
+        <CellGroup border={false} class={[styles.goodsHead, 'mb12']}>
+          <Cell
+            center
+            border={false}
+            v-slots={{
+              title: () => (
+                <div class={styles.priceGroup}>
+                  <span class={styles.price}>
+                    <i>¥</i>
+                    {moneyFormat(this.getPrice)}
+                  </span>
+                  {/* <del class={styles.delPrice}>
+                    ¥{moneyFormat(product.originalPrice)}
+                  </del> */}
+                </div>
+              )
+              // default: () => <div class={styles.stock}>销量4件</div>
+            }}
+          />
+          <Cell
+            center
+            border={false}
+            title={product.name}
+            titleClass={[styles.goodsName, 'van-ellipsis']}
+          />
+        </CellGroup>
+
+        {this.detailMobileHtml && (
+          <div class={[styles.section]}>
+            <div class={styles.detail}>
+              <span>图文详情</span>
+            </div>
+
+            <div
+              class={[styles.photoDetail, 'msgWrap']}
+              onClick={this.onShowImg}
+              v-html={this.detailMobileHtml}
+            ></div>
+          </div>
+        )}
+      </div>
+    )
+  }
+})

+ 10 - 11
src/student/music-group/layout/login.tsx

@@ -24,7 +24,10 @@ export default defineComponent({
       imgCodeStatus: false,
       showPopup: false,
       wxAppId: '', //
-      code: '' // 授权code码
+      code: '', // 授权code码
+
+      // 是否开启微信登录(测试使用)默认为false
+      testIsWeixin: false
     }
   },
   computed: {
@@ -39,7 +42,7 @@ export default defineComponent({
     this.directNext()
 
     // 判断是否是微信,只能微信中打开
-    if (!browser().weixin) {
+    if (!browser().weixin && !this.testIsWeixin) {
       this.showPopup = true
     }
   },
@@ -68,13 +71,6 @@ export default defineComponent({
     directNext() {
       if (state.user.status === 'login' || state.user.status === 'error') {
         const { returnUrl, isRegister, ...rest } = this.$route.query
-        console.log(
-          {
-            ...rest,
-            code: this.code
-          },
-          'jump pre registration'
-        )
 
         const newUrl =
           window.location.origin +
@@ -86,8 +82,11 @@ export default defineComponent({
             ...rest
           })
         // 直接跳转到授权页面
-        this.getAppIdAndCode(newUrl)
-        // this.locationReplace(newUrl)
+        if (this.testIsWeixin) {
+          this.locationReplace(newUrl)
+        } else {
+          this.getAppIdAndCode(newUrl)
+        }
       }
     },
     locationReplace(url: any) {

+ 16 - 0
src/student/music-group/member-bao/index.tsx

@@ -0,0 +1,16 @@
+import { Image } from 'vant'
+import { defineComponent } from 'vue'
+import bg1 from '../pre-apply/images/member_bao-1.png'
+import bg2 from '../pre-apply/images/member_bao-2.png'
+
+export default defineComponent({
+  name: 'member-bao',
+  setup() {
+    return () => (
+      <>
+        <Image src={bg1} />
+        <Image src={bg2} />
+      </>
+    )
+  }
+})

+ 16 - 5
src/student/music-group/pre-apply/component/addres.tsx

@@ -1,10 +1,8 @@
-import { Cell, Icon } from 'vant'
+import { Cell, Icon, Tag } from 'vant'
 import { defineComponent, computed, PropType, reactive, onMounted } from 'vue'
 import styles from './address.module.less'
 import iconAddress from '@common/images/icon-address.png'
-import { postMessage } from '@/helpers/native-message'
 import { useRouter } from 'vue-router'
-import request from '../../request-music'
 
 export default defineComponent({
   name: 'cart-address',
@@ -46,8 +44,8 @@ export default defineComponent({
             class={styles.cell}
             is-link={props.isLink}
             onClick={() => selectAddress()}
+            titleStyle={{ marginLeft: '0' }}
             v-slots={{
-              icon: () => <Icon name={iconAddress} size={19} />,
               title: () => (
                 <div>
                   <span class={styles.userName}>{props.item.name}</span>
@@ -56,6 +54,19 @@ export default defineComponent({
                       props.item.phoneNumber &&
                       props.item.phoneNumber.replace(/^(\d{3})\d{4}(\d+)/, '$1****$2')}
                   </span>
+                  {props.item.defaultStatus && (
+                    <Tag
+                      type="primary"
+                      round
+                      style={{
+                        'vertical-align': 'text-top',
+                        marginLeft: '10px',
+                        padding: '1px 8px'
+                      }}
+                    >
+                      默认
+                    </Tag>
+                  )}
                 </div>
               ),
               label: () => <span class={styles.addressInfo}>{addressInfo.value}</span>
@@ -67,7 +78,7 @@ export default defineComponent({
             is-link={props.isLink}
             onClick={() => selectAddress()}
             v-slots={{
-              icon: () => <Icon name={iconAddress} size={19} />,
+              icon: () => <Icon name={iconAddress} size={28} />,
               title: () => <div class={styles.emtry}>去填写收货地址</div>
             }}
           ></Cell>

+ 23 - 3
src/student/music-group/pre-apply/component/address.module.less

@@ -12,11 +12,29 @@
   }
 }
 .cell {
+  position: relative;
   align-items: center;
+  padding: 13px 12px 18px;
+  &::before {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    content: ' ';
+    display: block;
+    height: 3px;
+    width: 100%;
+    background: url('../images/icon-address-border.png') repeat center left;
+    background-size: 90%;
+  }
   :global {
     .van-cell__title {
       margin-left: 14px;
     }
+    .van-cell__right-icon {
+      font-size: 20px;
+      color: #d8d8d8;
+    }
   }
 }
 .userName {
@@ -26,13 +44,15 @@
 }
 .phone {
   font-size: 16px;
-  color: #333;
+  color: #777;
   margin-left: 14px;
 }
 .addressInfo {
   font-size: 14px;
-  color: #666;
+  color: #777;
+  line-height: 20px;
 }
 .emtry {
-  padding-top: 2px;
+  font-size: 14px;
+  color: #333;
 }

+ 116 - 26
src/student/music-group/pre-apply/component/payment.tsx

@@ -15,10 +15,14 @@ import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue'
 import styles from '../index.module.less'
 import radioCheck from '@/common/images/icon-radio-check.png'
 import radioDefault from '@/common/images/icon-radio-default.png'
+import iconGives from '../images/icon-gives.png'
 import { useRoute, useRouter } from 'vue-router'
 import request from '@/helpers/request'
 import { moneyFormat } from '@/helpers/utils'
 import { CountUp } from 'countup.js'
+import OPopup from '@/components/o-popup'
+import MemberBao from '../../member-bao'
+import GoodsDetail from '../../goods-detail'
 
 export default defineComponent({
   name: 'payment',
@@ -38,7 +42,10 @@ export default defineComponent({
       orderInfo: {
         needPrice: 0,
         originalPrice: 0
-      }
+      },
+      memberBaoStatus: false, // 团练宝详情状态
+      goodsStatus: false, //
+      selectGoodsId: null as any
     })
 
     // 查询未支付订单
@@ -288,7 +295,7 @@ export default defineComponent({
           {/* 判断是否已经购买乐器 */}
           {!state.paymentOrderDetails.includes('INSTRUMENTS') && (
             <>
-              <div class={styles.applyTitle}>乐器</div>
+              {/* <div class={styles.applyTitle}>乐器</div> */}
               <CellGroup
                 inset
                 class={[styles.mlr13, styles.sectionCell]}
@@ -316,31 +323,59 @@ export default defineComponent({
                     ),
                     title: () => (
                       <div class={styles.section}>
-                        <Image class={styles.img} src={state.goodsInfo.goodsUrl} />
+                        <Image
+                          class={styles.img}
+                          src={state.goodsInfo.goodsUrl}
+                          onClick={(e: any) => {
+                            e.stopPropagation()
+                            state.selectGoodsId = state.goodsInfo.goodsId
+                            state.goodsStatus = true
+                          }}
+                        />
                         <div class={styles.sectionContent}>
                           <h2>{state.goodsInfo.goodsName}</h2>
-                          <Tag type="primary">{state.goodsInfo.brandName}</Tag>
+                          <Tag
+                            color="linear-gradient(135deg, #FF8C4A 0%, #FF531C 100%)"
+                            textColor="#fff"
+                            class={styles.brandName}
+                          >
+                            {state.goodsInfo.brandName}
+                          </Tag>
                           <p class={styles.model}>{state.goodsInfo.desciption}</p>
                         </div>
                       </div>
                     )
                   }}
                 </Cell>
-                <Cell>
+                <Cell border={false}>
                   {{
                     title: () => (
                       <div class={styles.extra}>
                         <div class={styles.sectionPrice}>
                           <p class={styles.price}>
-                            新团特惠:<span>¥{moneyFormat(state.goodsInfo.currentPrice)}</span>
+                            新团特惠:
+                            <span class={styles.numFont}>
+                              <span class={styles.numPrefix}>¥</span>
+                              {moneyFormat(state.goodsInfo.currentPrice)}
+                            </span>
                           </p>
                           <p class={styles.originPrice}>
-                            原价:<del>¥{moneyFormat(state.goodsInfo.originalPrice)}</del>
+                            原价:
+                            <del class={styles.numFont}>
+                              ¥{moneyFormat(state.goodsInfo.originalPrice)}
+                            </del>
                           </p>
                         </div>
-                        <div class={styles.sectionTips}>
-                          赠价值{state.repaireInfo.originalPrice}元乐器维保服务一年
-                        </div>
+                      </div>
+                    )
+                  }}
+                </Cell>
+                <Cell center class={styles.gives}>
+                  {{
+                    title: () => (
+                      <div class={styles.sectionTips}>
+                        <Image src={iconGives} class={styles.iconGives} />
+                        赠价值{state.repaireInfo.originalPrice}元乐器维保服务一年
                       </div>
                     )
                   }}
@@ -352,7 +387,7 @@ export default defineComponent({
           {/* 判断是否已经购买教材 */}
           {!state.paymentOrderDetails.includes('TEXTBOOK') && (
             <>
-              <div class={styles.applyTitle}>教材</div>
+              {/* <div class={styles.applyTitle}>教材</div> */}
               <CellGroup
                 inset
                 class={[styles.mlr13, styles.sectionCell]}
@@ -384,10 +419,24 @@ export default defineComponent({
                     ),
                     title: () => (
                       <div class={styles.section}>
-                        <Image class={styles.img} src={state.textBookInfo.goodsUrl} />
+                        <Image
+                          class={styles.img}
+                          src={state.textBookInfo.goodsUrl}
+                          onClick={(e: any) => {
+                            e.stopPropagation()
+                            state.selectGoodsId = state.textBookInfo.goodsId
+                            state.goodsStatus = true
+                          }}
+                        />
                         <div class={styles.sectionContent}>
                           <h2>{state.textBookInfo.goodsName}</h2>
-                          <Tag type="primary">{state.textBookInfo.brandName}</Tag>
+                          <Tag
+                            color="linear-gradient(135deg, #FF8C4A 0%, #FF531C 100%)"
+                            textColor="#fff"
+                            class={styles.brandName}
+                          >
+                            {state.textBookInfo.brandName}
+                          </Tag>
                           <p class={styles.model}>{state.textBookInfo.description}</p>
                         </div>
                       </div>
@@ -401,14 +450,26 @@ export default defineComponent({
                         <div class={styles.sectionPrice}>
                           <p class={styles.price}>
                             新团特惠:
-                            <span class={styles.free}>
-                              {state.textBookInfo.currentPrice > 0
-                                ? moneyFormat(state.textBookInfo.currentPrice)
-                                : '免费赠送'}
+                            <span
+                              class={[
+                                state.textBookInfo.currentPrice > 0 ? styles.numFont : styles.free
+                              ]}
+                            >
+                              {state.textBookInfo.currentPrice > 0 ? (
+                                <>
+                                  <span class={styles.numPrefix}>¥</span>
+                                  {moneyFormat(state.textBookInfo.currentPrice)}
+                                </>
+                              ) : (
+                                '免费'
+                              )}
                             </span>
                           </p>
                           <p class={styles.originPrice}>
-                            原价:<del>¥{moneyFormat(state.textBookInfo.originalPrice)}</del>
+                            原价:
+                            <del class={styles.numFont}>
+                              ¥{moneyFormat(state.textBookInfo.originalPrice)}
+                            </del>
                           </p>
                         </div>
                       </div>
@@ -421,7 +482,7 @@ export default defineComponent({
 
           {!state.paymentOrderDetails.includes('VIP') && (
             <>
-              <div class={styles.applyTitle}>乐团学习系统</div>
+              {/* <div class={styles.applyTitle}>乐团学习系统</div> */}
               <CellGroup
                 inset
                 class={[styles.mlr13, styles.sectionCell]}
@@ -449,10 +510,23 @@ export default defineComponent({
                     ),
                     title: () => (
                       <div class={styles.section}>
-                        <Image class={styles.img} src={state.vipInfo.goodsUrl} />
+                        <Image
+                          class={styles.img}
+                          src={state.vipInfo.goodsUrl}
+                          onClick={(e: any) => {
+                            e.stopPropagation()
+                            state.memberBaoStatus = true
+                          }}
+                        />
                         <div class={styles.sectionContent}>
                           <h2>{state.vipInfo.goodsName}</h2>
-                          <Tag type="primary">6个月</Tag>
+                          <Tag
+                            color="linear-gradient(135deg, #FF8C4A 0%, #FF531C 100%)"
+                            textColor="#fff"
+                            class={styles.brandName}
+                          >
+                            6个月
+                          </Tag>
                           <p class={styles.model}>{state.vipInfo.description}</p>
                         </div>
                       </div>
@@ -465,10 +539,17 @@ export default defineComponent({
                       <div class={styles.extra}>
                         <div class={styles.sectionPrice}>
                           <p class={styles.price}>
-                            新团特惠:<span>¥{moneyFormat(state.vipInfo.currentPrice)}</span>
+                            新团特惠:
+                            <span class={styles.numFont}>
+                              <span class={styles.numPrefix}>¥</span>
+                              {moneyFormat(state.vipInfo.currentPrice)}
+                            </span>
                           </p>
                           <p class={styles.originPrice}>
-                            原价:<del>¥{moneyFormat(state.vipInfo.originalPrice)}</del>
+                            原价:
+                            <del class={styles.numFont}>
+                              ¥{moneyFormat(state.vipInfo.originalPrice)}
+                            </del>
                           </p>
                         </div>
                       </div>
@@ -485,13 +566,14 @@ export default defineComponent({
             <div class={styles.payemntPrice}>
               <p class={styles.needPrice}>
                 支付金额:
-                <span>
-                  ¥<i style="font-style: normal" id="needPrice"></i>
+                <span class={styles.numFont}>
+                  <span>¥</span>
+                  <i style="font-style: normal" id="needPrice"></i>
                 </span>
               </p>
               <p class={styles.allPrice}>
                 总原价:
-                <del>¥{moneyFormat(state.orderInfo.originalPrice)}</del>
+                <del class={styles.numFont}>¥{moneyFormat(state.orderInfo.originalPrice)}</del>
               </p>
             </div>
             <div class={styles.paymentBtn}>
@@ -505,6 +587,14 @@ export default defineComponent({
             </div>
           </div>
         </OSticky>
+
+        <OPopup v-model:modelValue={state.memberBaoStatus} position="right">
+          <MemberBao />
+        </OPopup>
+
+        <OPopup v-model:modelValue={state.goodsStatus} position="right" destroy>
+          {state.goodsStatus && <GoodsDetail id={state.selectGoodsId} />}
+        </OPopup>
       </>
     )
   }

binární
src/student/music-group/pre-apply/images/banner.png


binární
src/student/music-group/pre-apply/images/icon-address-border.png


binární
src/student/music-group/pre-apply/images/icon-gives.png


binární
src/student/music-group/pre-apply/images/member_bao-1.png


binární
src/student/music-group/pre-apply/images/member_bao-2.png


+ 65 - 37
src/student/music-group/pre-apply/index.module.less

@@ -44,41 +44,36 @@
   .banner {
     background: url('./images/banner.png') no-repeat center center;
     background-size: cover;
-    height: 235px;
+    height: 142px;
     width: 100%;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    flex-direction: column;
     .orchestraName {
       display: block;
-      padding: 0 25px;
-      font-size: 24px;
-      font-weight: 600;
+      padding: 86px 18px 0;
+      max-width: 230px;
+      font-size: 16px;
       color: #ffffff;
-      line-height: 38px;
-      letter-spacing: 1px;
-      text-shadow: 2px 2px 0px #d83d00;
       text-align: justify;
-      text-align: center;
-    }
-    .tips {
-      margin-top: 24px;
-      padding: 5px 11px;
-      background-color: #fff;
-      border-radius: 16px 0 16px 0;
-      font-size: 15px;
-      font-weight: 600;
-      color: #e45729;
     }
   }
 }
 
 .applyTitle {
-  padding: 20px 13px 12px;
+  display: flex;
+  align-items: center;
+  padding: 20px 20px 12px;
   font-size: 16px;
   font-weight: 600;
   color: #333333;
+
+  &::before {
+    display: inline-block;
+    content: ' ';
+    margin-right: 6px;
+    width: 4px;
+    height: 14px;
+    background: linear-gradient(180deg, #ffb790 0%, #ff8057 100%);
+    border-radius: 3px;
+  }
 }
 .applyCellGroup {
   margin: 0 13px;
@@ -116,15 +111,11 @@
 .paymentTips {
   background: #ffffff;
   border-radius: 10px;
-  padding: 18px 12px;
-  font-size: 15px;
-  color: #333333;
-  p {
-    padding-bottom: 20px;
-    &:last-child {
-      padding-bottom: 0;
-    }
-  }
+  padding: 12px 14px;
+  font-size: 14px;
+  color: #777;
+  line-height: 20px;
+  text-align: justify;
 }
 
 .paymentContainer {
@@ -142,6 +133,10 @@
       font-size: 22px;
       font-weight: bold;
       color: #ff4e19;
+      span {
+        font-size: 18px;
+        margin-right: 2px;
+      }
     }
   }
   .allPrice {
@@ -158,7 +153,10 @@
 }
 
 .sectionCell {
+  margin-bottom: 12px;
   padding: 15px 12px;
+  border-radius: 10px;
+  overflow: hidden;
   --van-checkbox-border-color: transparent;
   :global {
     .van-cell {
@@ -185,8 +183,9 @@
       font-size: 14px;
       color: #333333;
       padding-right: 12px;
+      font-weight: 400;
       span {
-        font-size: 22px;
+        font-size: 16px;
         font-weight: bold;
         color: #ff4e19;
       }
@@ -194,10 +193,25 @@
         font-size: 14px;
       }
     }
+  }
+
+  .gives {
+    margin-top: 18px;
+    padding-top: 8px;
+    border-top: 1px solid #f2f2f2;
     .sectionTips {
-      padding-top: 8px;
-      font-size: 12px;
+      display: flex;
+      align-items: center;
+      font-size: 14px;
       color: #ff4e19;
+      padding: 5px 6px;
+      background: #ffebdd;
+      border-radius: 6px;
+    }
+    .iconGives {
+      width: 32px;
+      height: 18px;
+      margin-right: 8px;
     }
   }
 
@@ -222,13 +236,20 @@
     }
   }
 }
+.numFont {
+  font-family: 'DINA';
+  .numPrefix {
+    font-size: 14px !important;
+    margin-right: 2px;
+  }
+}
 .section {
   display: flex;
   align-items: center;
 
   .img {
-    width: 90px;
-    height: 90px;
+    width: 88px;
+    height: 88px;
     border-radius: 6px;
     overflow: hidden;
     flex-shrink: 0;
@@ -242,10 +263,17 @@
     line-height: 22px;
   }
 
+  .brandName {
+    line-height: 18px;
+    font-size: 12px;
+    padding: 0 6px;
+    border-radius: 4px;
+  }
+
   .model {
     padding-top: 3px;
     font-size: 13px;
-    color: #333333;
+    color: #777;
     line-height: 18px;
   }
 }

+ 28 - 58
src/student/music-group/pre-apply/index.tsx

@@ -16,7 +16,6 @@ export default defineComponent({
   setup() {
     const route = useRoute()
     const router = useRouter()
-    const bannerRef = ref()
     const state = reactive({
       tabValue: 'apply',
       heightV: 235,
@@ -24,7 +23,10 @@ export default defineComponent({
       purchase: false, // 购买状态
       register: true, // 是否注册
       // showPopup: false,
-      code: '' as any
+      code: '' as any,
+
+      // 是否开启微信登录(测试使用)默认为false
+      testIsWeixin: false
     })
 
     const onNext = async (name: string) => {
@@ -45,20 +47,6 @@ export default defineComponent({
           '/api-student/orchestraRegister/registerStatus/' + route.query.id
         )
         state.registerInfo = data || {}
-        const name = data.orchestraName
-        // const name = '华中科技大学大学同济医学院附'
-        // console.log(name.length)
-        if (name.length > 12 && name.length / 12 > 1 && name.length / 12 <= 2) {
-          const len = name.substring(12, 24)
-          if (len.length < 5) {
-            const splitLen = Math.ceil(name.length / 2)
-            const first = name.substring(0, splitLen)
-            const last = name.substring(splitLen, name.length)
-            state.registerInfo.orchestraName = first + '<br />' + last
-          } else {
-            state.registerInfo.orchestraName = name
-          }
-        }
 
         // 判断是否报名注册过
         state.register = data.register
@@ -179,54 +167,36 @@ export default defineComponent({
     }
 
     // 先请求接口 判断是否有code
-    if (browser().weixin) {
-      // 微信公众号支付
-      //授权
-      const code = getUrlCode()
-      if (!code) {
-        getAppIdAndCode()
+    if (state.testIsWeixin) {
+      getRegisterStatus()
+    } else {
+      if (browser().weixin) {
+        // 微信公众号支付
+        //授权
+        const code = getUrlCode()
+        if (!code) {
+          getAppIdAndCode()
+        } else {
+          state.code = code
+          getRegisterStatus()
+        }
       } else {
-        state.code = code
-        getRegisterStatus()
+        setLogout()
+        const query = {
+          returnUrl: route.path,
+          ...route.query
+        } as any
+        router.replace({
+          path: '/loginMusic',
+          query: query
+        })
       }
-    } else {
-      setLogout()
-      const query = {
-        returnUrl: route.path,
-        ...route.query
-      } as any
-      router.replace({
-        path: '/loginMusic',
-        query: query
-      })
     }
 
-    // onMounted(() => {
-    // state.code = route.query.code || ''
-    // const { height } = useRect(bannerRef.value)
-    // state.heightV = height
-    // 判断是否是微信,只能微信中打开
-    // if (browser().weixin) {
-    //   // 微信公众号支付
-    //   //授权
-    //   const code = getUrlCode()
-    //   if (!code || !state.code) {
-    //     getAppIdAndCode()
-    //   } else {
-    //     state.code = code
-    //   }
-    // }
-    // })
     return () => (
       <div class={styles.preApply}>
-        <div class={styles.banner} ref={bannerRef}>
-          {state.registerInfo.orchestraName && (
-            <>
-              <p class={styles.orchestraName} v-html={state.registerInfo.orchestraName}></p>
-
-              <div class={styles.tips}>快来参加乐团报名吧!</div>
-            </>
-          )}
+        <div class={styles.banner}>
+          <p class={styles.orchestraName}>{state.registerInfo.orchestraName}</p>
         </div>
 
         <Sticky position="top">

+ 65 - 5
src/student/music-group/pre-apply/order-detail.module.less

@@ -12,8 +12,10 @@
 .cartConfirmBox {
   padding: 0;
   margin-bottom: 12px;
-  border-radius: 6px;
+  border-radius: 10px;
   overflow: hidden;
+  // border-bottom: 3px solid;
+  // border-image-source: url('./images/icon-address-border.png');
   .cartItem {
     margin: 0;
     border-radius: 0;
@@ -32,6 +34,17 @@
   margin-left: 13px;
 }
 
+.protocol {
+  box-shadow: none !important;
+  padding: 8px 0 !important;
+  background: #ffebdd;
+  :global {
+    .van-checkbox__label {
+      color: #777;
+    }
+  }
+}
+
 .paymentContainer {
   display: flex;
   align-items: center;
@@ -40,6 +53,7 @@
   padding-left: 12px;
   padding-right: 12px;
   padding-bottom: calc(20px + env(safe-area-inset-bottom));
+  box-shadow: none !important;
   .needPrice {
     display: flex;
     align-items: center;
@@ -83,17 +97,63 @@
     line-height: 24px;
   }
 
+  .numFont {
+    font-family: 'DINA';
+    .numPrefix {
+      font-size: 14px !important;
+      margin-right: 2px;
+    }
+  }
+
   .goodsContent {
+    // h2 {
+    //   font-size: 16px;
+    //   font-weight: 500;
+    //   color: #333333;
+    //   line-height: 22px;
+    // }
+    .goodsNum {
+      font-size: 18px;
+      color: #777777;
+      line-height: 20px;
+      flex-shrink: 0;
+    }
+    .goodsPrice {
+      padding-top: 4px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      .free {
+        font-size: 14px;
+      }
+    }
+    .goodsNums {
+      font-size: 18px;
+      font-weight: bold;
+      color: #fc1a19;
+    }
     h2 {
       font-size: 16px;
       font-weight: 500;
       color: #333333;
       line-height: 22px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
     }
-    .goodsNum {
-      font-size: 14px;
-      color: #777777;
-      line-height: 20px;
+
+    .brandName {
+      line-height: 18px;
+      font-size: 12px;
+      padding: 0 6px;
+      border-radius: 4px;
+    }
+
+    .model {
+      padding-top: 3px;
+      font-size: 13px;
+      color: #777;
+      line-height: 18px;
     }
   }
 }

+ 68 - 13
src/student/music-group/pre-apply/order-detail.tsx

@@ -14,6 +14,8 @@ import OProtocol from '@/components/o-protocol'
 import OPopup from '@/components/o-popup'
 import UserAuth from './component/user-auth'
 import qs from 'query-string'
+import MemberBao from '../member-bao'
+import GoodsDetail from '../goods-detail'
 
 export default defineComponent({
   name: 'order-detail',
@@ -34,7 +36,10 @@ export default defineComponent({
       freight: '', // 运费
       agreeStatus: true, //是否勾选协议
       showHeader: false,
-      authShow: false // 是否进行实名认证
+      authShow: false, // 是否进行实名认证
+      memberBaoStatus: false, // 团练宝详情状态
+      goodsStatus: false, //
+      selectGoodsId: null as any
     })
 
     const orderType = computed(() => {
@@ -381,21 +386,64 @@ export default defineComponent({
           <CellGroup style={{ margin: 0 }}>
             {state.goodsInfos &&
               state.goodsInfos.map((goods: any) => (
-                <Cell class={styles.cellItem} center>
+                <Cell
+                  class={styles.cellItem}
+                  center
+                  onClick={() => {
+                    if (goods.goodsType === 'INSTRUMENTS' || goods.goodsType === 'TEXTBOOK') {
+                      state.selectGoodsId = goods.goodsId
+                      state.goodsStatus = true
+                    } else if (goods.goodsType === 'VIP') {
+                      state.memberBaoStatus = true
+                    }
+                  }}
+                >
                   {{
                     icon: () => <Image class={styles.img} src={goods.goodsUrl} />,
                     title: () => (
                       <div class={styles.goodsContent}>
-                        <h2>{goods.goodsName}</h2>
+                        {/* <h2>{goods.goodsName}</h2>
                         <Tag type="primary">{goods.brandName}</Tag>
-                        <p class={styles.goodsNum}>{goods.goodsType === 'VIP' ? '6个月' : 'x 1'}</p>
+                        <p class={styles.goodsNum}>{goods.goodsType === 'VIP' ? '6个月' : 'x 1'}</p> */}
+
+                        <h2>
+                          <span>{goods.goodsName}</span>
+                          <span class={styles.goodsNum}>
+                            {goods.goodsType === 'VIP' ? '6个月' : 'x 1'}
+                          </span>
+                        </h2>
+                        <div class={styles.goodsPrice}>
+                          <Tag
+                            color="linear-gradient(135deg, #FF8C4A 0%, #FF531C 100%)"
+                            textColor="#fff"
+                            class={styles.brandName}
+                          >
+                            {goods.brandName}
+                          </Tag>
+                          <span
+                            class={[
+                              styles.goodsNums,
+                              goods.paymentCashAmount > 0 ? styles.numFont : styles.free
+                            ]}
+                          >
+                            {goods.paymentCashAmount > 0 ? (
+                              <>
+                                <span class={styles.numPrefix}>¥</span>
+                                {moneyFormat(goods.paymentCashAmount)}
+                              </>
+                            ) : (
+                              '免费'
+                            )}
+                          </span>
+                        </div>
+                        <p class={styles.model}>{goods.description}</p>
                       </div>
-                    ),
-                    value: () => (
-                      <span class={styles.cellPrice}>
-                        {goods.currentPrice > 0 ? '¥' + moneyFormat(goods.currentPrice) : '赠送'}
-                      </span>
                     )
+                    // value: () => (
+                    //   <span class={styles.cellPrice}>
+                    //     {goods.currentPrice > 0 ? '¥' + moneyFormat(goods.currentPrice) : '赠送'}
+                    //   </span>
+                    // )
                   }}
                 </Cell>
               ))}
@@ -404,17 +452,16 @@ export default defineComponent({
           {orderType.value === 'ORCHESTRA' && (
             <Cell class={styles.freight} title="运费" value={state.freight}></Cell>
           )}
+        </div>
 
+        <OSticky position="bottom" background="white">
           <div class={styles.protocol}>
             <OProtocol
               v-model:modelValue={state.agreeStatus}
               showHeader={state.showHeader}
-              style={{ paddingLeft: 0, paddingRight: 0 }}
+              style={{ paddingTop: 0, paddingBottom: 0 }}
             />
           </div>
-        </div>
-
-        <OSticky position="bottom" background="white">
           <div class={styles.paymentContainer}>
             <div class={styles.payemntPrice}>
               <p class={styles.needPrice}>
@@ -499,6 +546,14 @@ export default defineComponent({
         <OPopup v-model:modelValue={state.authShow}>
           <UserAuth onSuccess={onAuthSuccess} hideHeader={!browser().isApp} />
         </OPopup>
+
+        <OPopup v-model:modelValue={state.memberBaoStatus} position="right">
+          <MemberBao />
+        </OPopup>
+
+        <OPopup v-model:modelValue={state.goodsStatus} position="right" destroy>
+          {state.goodsStatus && <GoodsDetail id={state.selectGoodsId} />}
+        </OPopup>
       </>
     )
   }

binární
src/teacher/screen-projection/images/icon-img.png


binární
src/teacher/screen-projection/images/icon-music.png


binární
src/teacher/screen-projection/images/icon-video.png


binární
src/teacher/screen-projection/images/icon_content.png


+ 10 - 0
src/teacher/screen-projection/index.module.less

@@ -14,3 +14,13 @@
   height: 100vh;
   overflow-y: auto;
 }
+.container{
+  padding-bottom: 14px;
+}
+.playBtn{
+  position: fixed;
+  bottom: 30px;
+  left: 50%;
+  width: 60vw;
+  transform: translateX(-50%);
+}

+ 15 - 31
src/teacher/screen-projection/index.tsx

@@ -2,7 +2,7 @@ import OEmpty from '@/components/o-empty'
 import OHeader from '@/components/o-header'
 import request from '@/helpers/request'
 import { state } from '@/state'
-import { Cell, CellGroup, Image, Popup } from 'vant'
+import { Button, Cell, CellGroup, Image, Popup } from 'vant'
 import { defineComponent, onMounted, reactive, ref } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import Guide from './guide'
@@ -13,8 +13,9 @@ export default defineComponent({
   name: 'screen-projection',
   setup(props, { emit }) {
     const icons = {
-      music: getImage('icon-music.png'),
-      video: getImage('icon-video.png')
+      SONG: getImage('icon-music.png'),
+      VIDEO: getImage('icon-video.png'),
+      IMG: getImage('icon-img.png')
     }
     const route = useRoute()
     const router = useRouter()
@@ -47,27 +48,20 @@ export default defineComponent({
     })
 
     // 去课时播放
-    const hanldeOpenPlay = (n: any) => {
+    const hanldeOpenPlay = (n?: any) => {
       router.push({
         path: '/coursewarePlay',
         query: {
           ...query,
           id: query.lessonCoursewareDetailId,
-          kId: n.id
+          kId: n?.id
         }
       })
     }
     return () => (
-      <div>
-        <OHeader
-          desotry={false}
-          rightText="投屏帮助"
-          onClickRight={() => {
-            console.log('打开投屏')
-            data.guideOpen = true
-          }}
-        />
-        <div>
+      <div class={styles.screenProjection}>
+        
+        <div class={styles.container}>
           {data.name && (
             <div class={styles.tips}>
               <div class={styles.line}></div>
@@ -75,20 +69,6 @@ export default defineComponent({
             </div>
           )}
           <CellGroup inset>
-            {/* <Cell
-              title="胜强测试"
-              isLink
-              center
-              onClick={() => {
-                location.href = `http://192.168.3.114:1000/#/coursewarePlay?id=${query.lessonCoursewareDetailId}&courseId=${query.courseId}`
-              }}
-            >
-              {{
-                icon: () => (
-                  <Image style={{ marginRight: '12px' }} width={36} height={36} src={icons.music} />
-                )
-              }}
-            </Cell> */}
             {data.knowledgePointList.map((item: any) => {
               return (
                 <>
@@ -102,7 +82,7 @@ export default defineComponent({
                                 style={{ marginRight: '12px' }}
                                 width={36}
                                 height={36}
-                                src={['VIDEO', 'IMG'].includes(n.type) ? icons.video : icons.music}
+                                src={icons[n.type]}
                               />
                             )
                           }}
@@ -114,8 +94,12 @@ export default defineComponent({
             })}
           </CellGroup>
         </div>
-        {!data.loading && !data.knowledgePointList.length && <OEmpty />}
+        {!data.loading && !data.knowledgePointList.length && <OEmpty tips='暂无内容' />}
+        
 
+        <Button class={styles.playBtn} block type="primary" round onClick={() => hanldeOpenPlay()}>
+          播放课件
+        </Button>
         <Popup
           v-model:show={data.guideOpen}
           position="right"

+ 12 - 0
src/views/accompany/index.module.less

@@ -47,7 +47,19 @@
         top: 0;
         width: 100%;
         z-index: 10;
+        :global{
+          .van-dropdown-menu__bar{
+            box-shadow: none;
+          }
+          .van-dropdown-menu__title:after{
+            border: 0.11rem solid;
+            border-color: transparent transparent var(--van-gray-4) var(--van-gray-4);
+          }
+        }
     }
+    // .cell{
+    //   padding: 16px 10px;
+    // }
     .filter{
         display: flex;
         align-items: center;

+ 1 - 2
src/views/accompany/music-list.tsx

@@ -123,7 +123,6 @@ export default defineComponent({
     }
     // 重置搜索
     const onSearch = () => {
-      console.log(234)
       data.pagenation.page = 1
       data.finished = false
       data.loading = false
@@ -246,7 +245,7 @@ export default defineComponent({
             <CellGroup inset>
               {data.list.map((item: any) => {
                 return (
-                  <Cell center title={item.musicSheetName} isLink onClick={() => openView(item)}>
+                  <Cell size="large" center title={item.musicSheetName} isLink onClick={() => openView(item)}>
                     {{
                       icon: () => (
                         <Icon style={{ marginRight: '12px' }} size={40} name={imgDefault} />

+ 5 - 1
src/views/courseList/index.module.less

@@ -30,8 +30,12 @@
     }
     .title {
       text-align: center;
-      padding: 10px;
+      padding: 14px;
       color: #742c00;
+      font-size: 16px;
+    }
+    .subtitle{
+      font-size: 12px;
     }
     .num {
       position: absolute;

+ 3 - 2
src/views/courseList/index.tsx

@@ -13,6 +13,7 @@ import {
 import iconLook from './image/look.svg'
 import iconCourse from './image/icon-course.png'
 import { browser } from '@/helpers/utils'
+import OEmpty from '@/components/o-empty'
 export default defineComponent({
   name: 'lessonCourseware',
   setup() {
@@ -173,7 +174,7 @@ export default defineComponent({
                   <img src={iconCourse} class={styles.cover} />
                   <div class={styles.title}>
                     <div>{item.coursewareDetailName}</div>
-                    {route.query.code !== 'select' && <div>已使用 {item.useNum} 次</div>}
+                    {route.query.code !== 'select' && <div class={styles.subtitle}>已使用 {item.useNum} 次</div>}
                   </div>
                   {route.query.code !== 'select' ? (
                     <>
@@ -213,7 +214,7 @@ export default defineComponent({
         {/* <Button onClick={() => {
           location.href = 'http://192.168.3.114:1000/teacher.html#/coursewarePlay?id=1613426640725217281'
         }}>胜强测试老师端</Button> */}
-        {!data.loading && !data.list.length && <Empty description="空空如也" />}
+        {!data.loading && !data.list.length && <OEmpty tips='暂无内容' />}
       </div>
     )
   }

+ 1 - 1
src/views/coursewarePlay/component/musicScore.tsx

@@ -15,7 +15,7 @@ export default defineComponent({
     const iframeRef = ref()
     const Authorization = sessionStorage.getItem('Authorization') || ''
     const origin = /(localhost|192)/.test(location.host)
-      ? 'https://ponline.colexiu.com'//'http://192.168.3.114:3000'
+      ? 'https://ponline.colexiu.com' //'http://localhost:3000' ////
       : location.origin
     const query = qs.stringify({
       id: props.music.content,

+ 3 - 1
src/views/coursewarePlay/component/point.module.less

@@ -12,6 +12,7 @@
   align-items: center;
   padding: 13px 10px 15px 15px;
   flex-shrink: 0;
+  font-size: 14px;
   img {
     width: 16px;
     height: 16px;
@@ -30,7 +31,7 @@
   :global {
     .van-cell {
       background: transparent;
-      font-size: 12px;
+      font-size: 14px;
       color: #fff;
       padding: 0 13px 6px 18px;
     }
@@ -63,6 +64,7 @@
   justify-content: space-between;
   padding: 4px 5px;
   border-radius: 6px;
+  font-size: 12px;
   :global {
     .van-icon {
       display: none;

+ 22 - 12
src/views/coursewarePlay/index.module.less

@@ -18,13 +18,11 @@
   left: 0;
   right: 0;
   z-index: 1;
-  padding: 10px;
   display: flex;
   align-items: center;
   background: linear-gradient(180deg, rgba(0, 0, 0, .6), transparent);
 }
 .backBtn {
-  position: absolute;
   color: #fff;
   width: 40px;
   height: 26px;
@@ -32,11 +30,15 @@
   justify-content: space-between;
   align-items: center;
   z-index: 10;
+  padding: 4px 10px 4px 20px;
 }
 .menu {
-  flex: 1;
+  position: absolute;
+  width: 100%;
+  height: 100%;
   display: flex;
   justify-content: center;
+  align-items: center;
   font-size: 12px;
   color: #fff;
 }
@@ -90,7 +92,7 @@
   position: fixed;
   top: 50%;
   transform: translateY(-50%);
-  left: 20px;
+  left: 26px;
   .prePoint {
     margin-bottom: 8px;
   }
@@ -121,23 +123,31 @@
     justify-content: space-between;
     color: #fff;
     font-size: 10px;
-    padding: 4px 10px;
+    padding: 4px 20px;
   }
   .slider {
-    padding: 8px 10px;
+    padding: 8px 20px;
+    :global{
+      .van-slider__button{
+        background: var(--van-primary);
+      }
+    }
   }
   .actions {
     display: flex;
     justify-content: space-between;
     color: #fff;
     font-size: 12px;
-    padding: 8px 10px;
+    padding: 0 10px 0 20px;
     align-items: center;
-    :global {
-      .van-icon {
-        font-size: 20px;
-        margin-right: 14px;
-      }
+    .actionBtn{
+      display: flex;
+    }
+    .actionBtn > img {
+      width: 26px;
+      height: 26px;
+      display: block;
+      padding: 4px 10px 14px 4px;
     }
   }
 }

+ 95 - 89
src/views/coursewarePlay/index.tsx

@@ -2,6 +2,7 @@ import {
   Button,
   closeToast,
   Icon,
+  Loading,
   Popup,
   showToast,
   Slider,
@@ -93,16 +94,6 @@ export default defineComponent({
       timer: null as any,
       item: null as any
     })
-    watch(
-      () => activeData.model,
-      () => {
-        const videoItem = data.itemList.find((n) => n.id === popupData.itemActive)
-        // 阴影切换的时候,具体去切换某个视频的控件
-        if (videoItem && videoItem.type === 'VIDEO') {
-          videoItem.playModel = activeData.model
-        }
-      }
-    )
     // 获取缓存路径
     const getCacheFilePath = async (material: any) => {
       const res = await promisefiyPostMessage({
@@ -135,7 +126,6 @@ export default defineComponent({
     const getItemList = async () => {
       const list: any = []
       const browserInfo = browser()
-      let _item = null
       for (let i = 0; i < data.knowledgePointList.length; i++) {
         const item = data.knowledgePointList[i]
         const itemLength = item.materialList.length - 1
@@ -163,7 +153,8 @@ export default defineComponent({
               timer: null,
               playModel: false,
               isprepare: false,
-              isDrage: false
+              isDrage: false,
+              muted: false // 是否静音
             }
           }
           list.push({
@@ -172,25 +163,26 @@ export default defineComponent({
             iframeRef: null,
             tabName: item.name,
             isLast: j === itemLength, // 当前知识点
-            autoPlay: j === itemLength
+            autoPlay: false //加载完成是否自动播放
           })
         }
       }
 
       let item: any = null
       if (route.query.kId) {
-        item = list.find((n: any) => n.id == route.query.kId)
-        const _firstIndex = list.findIndex((n: any) => n.id == route.query.kId)
+        item = list.find((n: any) => n.materialId == route.query.kId)
+        const _firstIndex = list.findIndex((n: any) => n.materialId == route.query.kId)
         popupData.firstIndex = _firstIndex > -1 ? _firstIndex : 0
-      } else {
-        item = list[0] || {}
       }
+      item = item ? item : list[0] || {}
       if (item) {
         popupData.tabName = item.tabName
         popupData.tabActive = item.knowledgePointId
         popupData.itemActive = item.id
         popupData.itemName = item.name
         popupData.activeIndex = popupData.firstIndex
+        item.autoPlay = true
+        item.muted = true
       }
       console.log('🚀 ~ list', list)
       data.itemList = list
@@ -204,8 +196,18 @@ export default defineComponent({
           data.detail = res.data
         }
         if (Array.isArray(res?.data?.knowledgePointList)) {
+          let index = 0
           data.knowledgePointList = res.data.knowledgePointList.map((n: any) => {
-            n.index = 0
+            if (Array.isArray(n.materialList)) {
+              n.materialList = n.materialList.map((item: any) => {
+                index++
+                return {
+                  ...item,
+                  materialId: item.id,
+                  id: index + ''
+                }
+              })
+            }
             return n
           })
           getItemList()
@@ -216,7 +218,8 @@ export default defineComponent({
     const iframeHandle = (ev: MessageEvent) => {
       // console.log(ev.data)
       if (ev.data?.api === 'headerTogge') {
-        activeData.model = ev.data.show
+        // activeData.model = ev.data.show
+        activeData.model = !activeData.model
       }
     }
     onMounted(() => {
@@ -249,7 +252,11 @@ export default defineComponent({
     // 停止所有的播放
     const handleStopVideo = () => {
       data.itemList.forEach((m: any) => {
-        m.videoEle?.pause()
+        const item = data.itemList[popupData.activeIndex]
+        if (item?.id != m.id) {
+          m.autoPlay = false
+          m.videoEle?.pause()
+        }
       })
     }
     // 停止曲谱的播放
@@ -266,8 +273,17 @@ export default defineComponent({
         swipeRef.value?.swipeTo(index)
       }
     }
+    /** 延迟收起模态框 */
+    const setModelOpen = () => {
+      clearTimeout(activeData.timer)
+      closeToast()
+      activeData.timer = setTimeout(() => {
+        activeData.model = false
+      }, 4000)
+    }
     // 轮播切换
     const handleSwipeChange = (val: any) => {
+      console.log('轮播切换')
       popupData.activeIndex = val
       const item = data.itemList[val]
       if (item) {
@@ -275,9 +291,20 @@ export default defineComponent({
         popupData.itemActive = item.id
         popupData.itemName = item.name
         popupData.tabName = item.tabName
-        activeData.model = true
+        if (item.type == 'SONG') {
+          activeData.model = true
+        }
         if (item.type === 'VIDEO') {
-          item.playModel = true
+          // console.log("🚀 ~ item", item)
+          // 自动播放下一个视频
+          clearTimeout(activeData.timer)
+          closeToast()
+          item.currentTime = 0
+          item.videoEle && (item.videoEle.currentTime = 0)
+          nextTick(() => {
+            item.autoPlay = true
+            item.videoEle?.play()
+          })
         }
       }
     }
@@ -309,7 +336,6 @@ export default defineComponent({
       if (item && item.type === 'VIDEO') {
         const videoEle: HTMLVideoElement = item.videoEle
         if (videoEle) {
-          console.log(videoEle)
           if (videoEle.paused) {
             closeToast()
             videoEle.play()
@@ -318,29 +344,20 @@ export default defineComponent({
             videoEle.pause()
           }
         }
-        item.timer = setTimeout(() => {
-          activeData.model = false
-        }, 3000)
       }
     }
 
     // 暂停播放
     const handlePaused = (e: Event, m: any) => {
       e.stopPropagation()
-      console.log('暂停')
       m.videoEle?.pause()
       m.paused = true
     }
     // 开始播放
     const handlePlay = (e: Event, m: any) => {
       e.stopPropagation()
-      clearTimeout(m.timer)
       closeToast()
       m.videoEle?.play()
-      m.paused = false
-      m.timer = setTimeout(() => {
-        activeData.model = false
-      }, 3000)
     }
 
     // 调整播放进度
@@ -356,38 +373,8 @@ export default defineComponent({
     const handleEnded = (m: any) => {
       // console.log(m)
       if (popupData.activeIndex != data.itemList.length - 1) {
-        popupData.activeIndex++
         swipeRef.value?.next()
-        const nextItem = data.itemList[popupData.activeIndex]
-        if (nextItem.type === 'VIDEO'){
-          nextTick(() => {
-            // 自动播放下一个知识点
-            // if (m.autoPlay) {
-            // }
-            nextItem.videoEle?.play()
-          })
-        }
-        
-        console.log('🚀 ~ nextItem', nextItem)
-      }
-    }
-
-    //加载第一帧
-    const handleFirstFrame = (video: HTMLVideoElement) => {
-      // console.log("🚀 ~ 加载第一帧", video.videoWidth, video.videoHeight)
-      const captureImage = function () {
-        var canvas = document.createElement('canvas')
-        canvas.width = video.videoWidth
-        canvas.height = video.videoHeight
-        canvas?.getContext('2d')?.drawImage(video, 0, 0, canvas.width, canvas.height)
-        canvas.toBlob((blob) => {
-          // console.log("🚀 ~ blob", blob)
-          const imgUrl = URL.createObjectURL(blob as any)
-          // console.log("🚀 ~ imgUrl", imgUrl)
-          video.setAttribute('poster', imgUrl)
-        })
       }
-      captureImage()
     }
 
     return () => (
@@ -399,6 +386,7 @@ export default defineComponent({
           loop={false}
           vertical
           lazyRender={true}
+          touchable={false}
           initialSwipe={popupData.firstIndex}
           onChange={handleSwipeChange}
         >
@@ -410,7 +398,6 @@ export default defineComponent({
                     class={styles.itemDiv}
                     onClick={() => {
                       clearTimeout(activeData.timer)
-                      clearTimeout(m.timer)
                       if (Date.now() - activeData.nowTime < 300) {
                         handleDbClick(m)
                         return
@@ -418,6 +405,7 @@ export default defineComponent({
                       activeData.nowTime = Date.now()
                       activeData.timer = setTimeout(() => {
                         activeData.model = !activeData.model
+                        setModelOpen()
                       }, 300)
                     }}
                   >
@@ -425,23 +413,20 @@ export default defineComponent({
                       <>
                         <video
                           playsinline="false"
+                          muted={m.muted}
                           preload="auto"
                           class="player"
                           poster={iconVideobg}
                           data-vid={m.id}
                           src={m.content}
                           loop={m.loop}
-                          // onLoadeddata={(e: Event) =>
-                          //   handleFirstFrame(e.target as unknown as HTMLVideoElement)
-                          // }
+                          autoplay={m.autoPlay}
                           onLoadedmetadata={(e: Event) => {
                             const videoEle = e.target as unknown as HTMLVideoElement
                             m.currentTime = videoEle.currentTime
                             m.duration = videoEle.duration
                             m.videoEle = videoEle
                             m.isprepare = true
-                            m.playModel = true
-                            console.log('视频准备完成')
                           }}
                           onTimeupdate={(e: Event) => {
                             if (!m.isprepare) return
@@ -450,15 +435,17 @@ export default defineComponent({
                             m.progress = Number(
                               ((videoEle.currentTime / m.duration) * 100).toFixed(1)
                             )
-                            // console.log('播放',videoEle.currentTime,  m.progress)
                           }}
                           onPlay={() => {
                             // 播放
                             m.paused = false
+                            console.log('播放')
+                            setModelOpen()
+                            m.muted = false
                           }}
                           onPause={() => {
                             //暂停
-                            clearTimeout(m.timer)
+                            clearTimeout(activeData.timer)
                             m.paused = true
                           }}
                           onEnded={() => handleEnded(m)}
@@ -466,7 +453,7 @@ export default defineComponent({
                           <source src={m.content} type="video/mp4" />
                         </video>
                         <Transition name="bottom">
-                          {m.playModel && (
+                          {activeData.model && (
                             <div class={[styles.bottomFixedContainer]}>
                               <div class={styles.time}>
                                 <span>{getSecondRPM(m.currentTime)}</span>
@@ -474,6 +461,9 @@ export default defineComponent({
                               </div>
                               <div class={styles.slider}>
                                 <Slider
+                                  onClick={() => {
+                                    setModelOpen()
+                                  }}
                                   style={{ display: m.isprepare ? 'block' : 'none' }}
                                   buttonSize={16}
                                   step={0.1}
@@ -504,29 +494,36 @@ export default defineComponent({
                               </div>
 
                               <div class={styles.actions}>
-                                <div>
-                                  {m.paused ? (
-                                    <Icon
-                                      name={iconplay}
-                                      onClick={(e: Event) => handlePlay(e, m)}
-                                    />
+                                <div class={styles.actionBtn}>
+                                  {m.isprepare ? (
+                                    <>
+                                      {m.paused ? (
+                                        <img
+                                          src={iconplay}
+                                          onClick={(e: Event) => handlePlay(e, m)}
+                                        />
+                                      ) : (
+                                        <img
+                                          src={iconpause}
+                                          onClick={(e: Event) => handlePaused(e, m)}
+                                        />
+                                      )}
+                                    </>
                                   ) : (
-                                    <Icon
-                                      name={iconpause}
-                                      onClick={(e: Event) => handlePaused(e, m)}
-                                    />
+                                    <Loading color="#fff" />
                                   )}
+
                                   {m.loop ? (
-                                    <Icon
-                                      name={iconLoopActive}
+                                    <img
+                                      src={iconLoopActive}
                                       onClick={(e: Event) => {
                                         e.stopPropagation()
                                         m.loop = false
                                       }}
                                     />
                                   ) : (
-                                    <Icon
-                                      name={iconLoop}
+                                    <img
+                                      src={iconLoop}
                                       onClick={(e: Event) => {
                                         e.stopPropagation()
                                         m.loop = true
@@ -551,9 +548,6 @@ export default defineComponent({
                         }}
                       />
                     )}
-                    {/* <Transition name="van-fade">
-                      {activeData.model && <div class={styles.playModel}></div>}
-                    </Transition> */}
                   </div>
                 </>
               </SwipeItem>
@@ -576,7 +570,13 @@ export default defineComponent({
         <Transition name="right">
           {activeData.model && (
             <div class={styles.rightFixedBtns}>
-              <div class={styles.fullBtn} onClick={() => (popupData.open = true)}>
+              <div
+                class={styles.fullBtn}
+                onClick={() => {
+                  clearTimeout(activeData.timer)
+                  popupData.open = true
+                }}
+              >
                 <img src={iconMenu} />
                 <span>知识点</span>
               </div>
@@ -627,6 +627,12 @@ export default defineComponent({
           position="right"
           round
           v-model:show={popupData.open}
+          onClose={() => {
+            const item = data.itemList[popupData.activeIndex]
+            if (item?.type == 'VIDEO') {
+              setModelOpen()
+            }
+          }}
         >
           <Points
             data={data.knowledgePointList}

+ 8 - 2
src/views/exercise-after-class/index.tsx

@@ -72,6 +72,10 @@ export default defineComponent({
     })
 
     const route = useRoute()
+    watch(() => route.query, () => {
+      getDetail()
+      trainingRecord()
+    })
     const router = useRouter()
     const query = route.query
     const browserInfo = browser()
@@ -155,7 +159,7 @@ export default defineComponent({
     const getTrainingTimes = (res: any) => {
       let trainingTimes = 0
       if (Array.isArray(res?.trainings)) {
-        const train = res.trainings.find((n: any) => n.materialId === query.materialId)
+        const train = res.trainings.find((n: any) => n.materialId === route.query?.materialId)
         if (train) {
           trainingTimes = train.trainingTimes
         }
@@ -217,6 +221,7 @@ export default defineComponent({
     // 达到指标,记录
     const addTrainingRecord = async (m: any) => {
       data.recordLoading = true
+      const query = route.query
       const body = {
         materialType: 'VIDEO',
         record: {
@@ -270,7 +275,7 @@ export default defineComponent({
           if (!isLastIndex) {
             const nextItem = data.details[itemIndex + 1]
             if (nextItem?.type === materialType.视频) {
-              // console.log('下一题视频', nextItem)
+              console.log('下一题视频',data.details[itemIndex].materialId, nextItem.materialId)
               router.replace({
                 path: '/exerciseAfterClass',
                 query: {
@@ -314,6 +319,7 @@ export default defineComponent({
           loop={false}
           vertical
           lazyRender={true}
+          touchable={false}
         >
           {data.itemList.map((m: any, mIndex: number) => {
             return (

+ 2 - 1
src/views/lessonCourseware/index.module.less

@@ -31,8 +31,9 @@
     .title {
       position: relative;
       text-align: center;
-      padding: 10px;
+      padding: 14px;
       color:#742C00;
+      font-size: 14px;
     }
     .num {
       position: absolute;

+ 1 - 4
src/views/lessonCourseware/index.tsx

@@ -91,10 +91,7 @@ export default defineComponent({
             )
           })}
         </Grid>
-        {/* <Button onClick={() => {
-          location.href = 'http://192.168.3.114:1000/teacher.html#/courseList?id=1610595624868495362'
-        }}>胜强测试</Button>
-        {!data.loading && !data.list.length && <OEmpty tips="没有课件" />} */}
+        {!data.loading && !data.list.length && <OEmpty tips="没有课件" />} 
       </div>
     )
   }