Browse Source

添加云酷琴房

lex 2 years ago
parent
commit
90456b398d
42 changed files with 12244 additions and 1137 deletions
  1. 9318 1
      package-lock.json
  2. 2 0
      package.json
  3. BIN
      src/common/images/icon_pinao.png
  4. 60 37
      src/components/col-img-code/index.tsx
  5. 7 0
      src/constant/index.ts
  6. 6 0
      src/helpers/utils.ts
  7. 9 0
      src/router/routes-student.ts
  8. BIN
      src/student/invite-teacher/images/icon_tips.png
  9. BIN
      src/student/invite-teacher/images/success_tips.png
  10. 71 4
      src/student/invite-teacher/index.module.less
  11. 136 57
      src/student/invite-teacher/index.tsx
  12. 46 0
      src/student/invite-teacher/invite-code/index.module.less
  13. 131 0
      src/student/invite-teacher/invite-code/index.tsx
  14. 41 0
      src/student/invite-teacher/invite-success/index.module.less
  15. 65 0
      src/student/invite-teacher/invite-success/index.tsx
  16. 49 0
      src/student/invite-teacher/teacher-change/index.module.less
  17. 86 0
      src/student/invite-teacher/teacher-change/index.tsx
  18. 1 1
      src/teacher/piano-room/account-recharge-timer/index.module.less
  19. 69 19
      src/teacher/piano-room/account-recharge-timer/index.tsx
  20. 7 7
      src/teacher/piano-room/class-arrangement/index.module.less
  21. 11 10
      src/teacher/piano-room/class-arrangement/index.tsx
  22. 4 0
      src/teacher/piano-room/components/course/index.module.less
  23. 84 16
      src/teacher/piano-room/components/course/index.tsx
  24. 18 1
      src/teacher/piano-room/components/student/index.tsx
  25. 118 16
      src/teacher/piano-room/course-record/index.tsx
  26. 306 13
      src/teacher/piano-room/index.tsx
  27. 13 0
      src/teacher/piano-room/model/share/index.module.less
  28. 89 27
      src/teacher/piano-room/model/share/index.tsx
  29. 28 0
      src/teacher/piano-room/model/student-info/index.module.less
  30. 263 0
      src/teacher/piano-room/model/student-info/index.tsx
  31. 38 0
      src/teacher/piano-room/model/student-info/student-confirm.module.less
  32. 105 0
      src/teacher/piano-room/model/student-info/student-confirm.tsx
  33. 55 7
      src/teacher/piano-room/my-student/index.tsx
  34. 20 1
      src/views/order-detail/index.tsx
  35. 48 0
      src/views/order-detail/order-pinao/index.module.less
  36. 62 0
      src/views/order-detail/order-pinao/index.tsx
  37. 9 1
      src/views/order-detail/orderStatus.ts
  38. 5 2
      src/views/order-detail/payment/index.tsx
  39. 246 236
      src/views/protocol/privacy.tsx
  40. 197 187
      src/views/protocol/register.tsx
  41. 4 1
      src/views/trade/trade-detail.tsx
  42. 417 493
      yarn.lock

File diff suppressed because it is too large
+ 9318 - 1
package-lock.json


+ 2 - 0
package.json

@@ -27,11 +27,13 @@
     "classnames": "^2.3.1",
     "clean-deep": "^3.4.0",
     "dayjs": "^1.10.7",
+    "html-to-image": "^1.9.0",
     "loaders.css": "^0.1.2",
     "mitt": "^3.0.0",
     "normalize.css": "^8.0.1",
     "numeral": "^2.0.6",
     "plyr": "^3.6.12",
+    "qrcode.vue": "^3.3.3",
     "query-string": "^7.1.1",
     "umi-request": "^1.4.0",
     "vant": "^3.4.6",

BIN
src/common/images/icon_pinao.png


+ 60 - 37
src/components/col-img-code/index.tsx

@@ -1,10 +1,10 @@
-import { defineComponent } from "vue";
-import { Col, Popup, Row, Image as VanImage, Loading, Field, Toast } from "vant";
+import { defineComponent } from 'vue'
+import { Col, Popup, Row, Image as VanImage, Loading, Field, Toast } from 'vant'
 import styles from './index.module.less'
-import request from "@/helpers/request";
+import request from '@/helpers/request'
 
 export default defineComponent({
-  name: "imgCode",
+  name: 'imgCode',
   props: {
     value: Boolean,
     phone: [String, Number],
@@ -15,84 +15,107 @@ export default defineComponent({
     },
     onSendCode: {
       type: Function,
-      default: () => {},
-    },
+      default: () => {}
+    }
   },
   data() {
     let origin = window.location.origin
     return {
       showStatus: false,
-      identifyingCode: origin + '/api-student/code/getImageCode?phone=' + this.phone,
-      code: null,
+      identifyingCode:
+        origin + '/api-student/code/getImageCode?phone=' + this.phone,
+      code: null
     }
   },
   mounted() {
-    this.showStatus = this.value;
+    this.showStatus = this.value
   },
   watch: {
     value(val: any) {
       this.showStatus = val
     },
     code(val: any) {
-      if(val.length >= 4) {
-        this.checkVerifyLoginImage();
+      if (val.length >= 4) {
+        this.checkVerifyLoginImage()
       }
     }
   },
   methods: {
-    async updateIdentifyingCode() { // 刷新token
+    async updateIdentifyingCode() {
+      // 刷新token
       let origin = window.location.origin
-      this.identifyingCode = `${origin}/api-student/code/getImageCode?phone=${this.phone}&token=${Math.random()}`
+      this.identifyingCode = `${origin}/api-student/code/getImageCode?phone=${
+        this.phone
+      }&token=${Math.random()}`
     },
     async checkVerifyLoginImage() {
       try {
-          if((this as any).code.length < 4) {
-              return
+        if ((this as any).code.length < 4) {
+          return
+        }
+        await request.post('/api-student/code/verifyLoginImage', {
+          data: {
+            phone: this.phone,
+            code: this.code
           }
-          await request.post('/api-student/code/verifyLoginImage', {
-            data: {
-              phone: this.phone,
-              code: this.code
-            }
-          })
-          await request.post('/api-student/code/sendSms', {
-            data: {
-              mobile: this.phone
-            }
-          })
-          Toast('验证码已发送')
-          this.onClose();
-          this.onSendCode();
+        })
+        await request.post('/api-student/code/sendSmsCode', {
+          data: {
+            mobile: this.phone
+          }
+        })
+        Toast('验证码已发送')
+        this.onClose()
+        this.onSendCode()
       } catch {
-          this.updateIdentifyingCode()
+        this.updateIdentifyingCode()
       }
-    },
+    }
   },
   render() {
     return (
       // @ts-ignore
-      <Popup show={this.showStatus} class={styles.imgCodePopup} closeOnClickOverlay={false} onClose={this.onClose} closeable closeIcon="close">
+      <Popup
+        show={this.showStatus}
+        class={styles.imgCodePopup}
+        closeOnClickOverlay={false}
+        onClose={this.onClose}
+        closeable
+        closeIcon="close"
+      >
         <div class={styles.imgCode}>
           <p class={styles.codeTitle}>输入图形验证码</p>
           <Row>
             <Col span="14">
-              <Field placeholder="请输入验证码" v-model={this.code} class={styles.field} />
+              <Field
+                placeholder="请输入验证码"
+                v-model={this.code}
+                class={styles.field}
+              />
             </Col>
             <Col span="10" class={styles.img}>
-              <VanImage src={this.identifyingCode} onClick={() => this.updateIdentifyingCode()}
+              <VanImage
+                src={this.identifyingCode}
+                onClick={() => this.updateIdentifyingCode()}
                 // @ts-ignore
                 vSlots={{
                   loading: () => <Loading type="spinner" size="20" />
-                }} />
+                }}
+              />
             </Col>
           </Row>
           <Row style={{ display: 'flex', justifyContent: 'end' }}>
             <Col span="10">
-              <span class={styles.imgChange} onClick={() => this.updateIdentifyingCode()}>看不清?换一换</span>
+              <span
+                class={styles.imgChange}
+                onClick={() => this.updateIdentifyingCode()}
+              >
+                看不清?换一换
+              </span>
             </Col>
           </Row>
         </div>
       </Popup>
     )
   }
-})
+})

+ 7 - 0
src/constant/index.ts

@@ -32,3 +32,10 @@ export const memberType = {
   YEAR_HALF: '半年会员',
   YEAR: '年度会员'
 }
+
+export const courseType = {
+  NOT_START: '未开始',
+  ING: '进行中',
+  COMPLETE: '已完成',
+  CANCEL: '已取消'
+}

+ 6 - 0
src/helpers/utils.ts

@@ -109,6 +109,12 @@ export const formatterDate = (type: string, val: any) => {
   if (type === 'day') {
     return `${val}日`
   }
+  if (type === 'hour') {
+    return `${val}时`
+  }
+  if (type === 'minute') {
+    return `${val}分`
+  }
   return val
 }
 

+ 9 - 0
src/router/routes-student.ts

@@ -173,12 +173,21 @@ export default [
   ...rootRouter,
   {
     path: '/inviteTeacher',
+    name: 'inviteTeacher',
     component: () => import('@/student/invite-teacher/index'),
     meta: {
       title: '邀请学员'
     }
   },
   {
+    path: '/inviteSuccess',
+    name: 'inviteSuccess',
+    component: () => import('@/student/invite-teacher/invite-success'),
+    meta: {
+      title: '注册成功'
+    }
+  },
+  {
     path: '/transfer',
     component: () => import('@/student/down-load/transfer'),
     meta: {

BIN
src/student/invite-teacher/images/icon_tips.png


BIN
src/student/invite-teacher/images/success_tips.png


+ 71 - 4
src/student/invite-teacher/index.module.less

@@ -10,7 +10,7 @@
     align-items: center;
     padding-top: 100px;
     padding-left: 20px;
-    padding-bottom: 70px;
+    padding-bottom: 23px;
     color: #1a1a1a;
 
     img {
@@ -52,9 +52,76 @@
       padding-left: 0;
       padding-right: 0;
     }
-    .van-button + .van-button {
-      margin-top: 20px;
-      color: #000 !important;
+
+    .van-checkbox {
+      display: inline-block;
+      align-items: inherit;
+      overflow: inherit;
+    }
+    .van-checkbox__icon {
+      height: 15px;
+      line-height: 15px;
+      display: inline-block;
+      vertical-align: middle;
+    }
+    .van-checkbox__label {
+      line-height: 15px;
+      color: #999;
+    }
+  }
+
+  .teacherInfo {
+    margin: 0 18px 55px;
+    background: url('./images//teacher_card.png') no-repeat top center;
+    background-size: 100%;
+    padding: 14px 14px 27px;
+    display: flex;
+    align-items: center;
+
+    .teacherLogo {
+      width: 54px;
+      height: 54px;
+      border-radius: 50%;
+      img {
+        width: 54px;
+        height: 54px;
+        border-radius: 50%;
+      }
+    }
+
+    .teacherName {
+      font-size: 14px;
+      color: #333;
+      line-height: 20px;
+      padding-left: 16px;
+      span {
+        font-weight: 500;
+        font-size: 18px;
+        color: #1a1a1a;
+      }
+    }
+  }
+
+  .itips {
+    font-size: 12px;
+    color: #666666;
+  }
+
+  .protocol {
+    font-size: 12px;
+    padding: 30px 0 15px;
+    text-align: center;
+    color: #999;
+    .protocolText {
+      color: var(--van-primary);
+      line-height: 15px;
+    }
+
+    .boxStyle {
+      background: transparent !important;
+      width: 15px;
+      height: 15px;
+      border: transparent !important;
     }
   }
 }

+ 136 - 57
src/student/invite-teacher/index.tsx

@@ -1,91 +1,105 @@
 import { defineComponent } from 'vue'
-import { CellGroup, Field, Button, CountDown, Row, Col, Toast } from 'vant'
+import {
+  CellGroup,
+  Field,
+  Button,
+  Row,
+  Col,
+  Toast,
+  Checkbox,
+  Icon,
+  Popup
+} from 'vant'
 import { checkPhone } from '@/helpers/validate'
 import request from '@/helpers/request'
 import { setLogin, state } from '@/state'
 import { removeAuth, setAuth } from '@/helpers/utils'
 import styles from './index.module.less'
 import logo from './images/logo.png'
+import iconTeacher from '@/common/images/icon_teacher.png'
+import activeButtonIcon from '@common/images/icon_checkbox.png'
+import inactiveButtonIcon from '@common/images/icon_checkbox_default.png'
+import ColPopup from '@/components/col-popup'
+import InviteCode from './invite-code'
+import TeacherChange from './teacher-change'
 
-type loginType = 'PWD' | 'SMS'
 export default defineComponent({
   name: 'login',
   data() {
+    const query = this.$route.query
     return {
-      loginType: 'SMS' as loginType,
+      id: query.id,
       username: '',
-      smsCode: '',
-      countDownStatus: true, // 是否发送验证码
-      countDownTime: 1000 * 120, // 倒计时时间
-      countDownRef: null as any, // 倒计时实例
-      imgCodeStatus: false
+      teacherInfo: {} as any, // 当前老师信息
+      imgCodeStatus: false,
+      checked: false,
+      teacherChangeStatus: false,
+      changeInfo: {} as any // 更换老师信息
     }
   },
   computed: {
     codeDisable() {
+      console.log(this.username ? true : false)
       return (this as any).username ? true : false
     }
   },
-  mounted() {
+  async mounted() {
     removeAuth()
-    this.directNext()
+    try {
+      const res = await request.get('/api-student/open/getTeacher', {
+        params: {
+          userId: this.id
+        }
+      })
+      console.log(res)
+      this.teacherInfo = res.data || {}
+    } catch {}
   },
   methods: {
-    directNext() {
-      if (state.user.status === 'login' || state.user.status === 'error') {
-        const { returnUrl, isRegister, ...rest } = this.$route.query
-        this.$router.replace({
-          path: returnUrl as any,
+    async onSendCode() {
+      // 发送验证码
+      if (!checkPhone(this.username)) {
+        return Toast('请输入正确的手机号码')
+      }
+      if (!this.checked) {
+        return Toast('请阅读并同意协议')
+      }
+      this.imgCodeStatus = true
+    },
+    previewProtocol(type: string) {
+      if (type === 'user') {
+        this.$router.push({
+          path: '/registerProtocol',
           query: {
-            ...rest
+            showHeader: 1
+          }
+        })
+      } else if (type === 'privacy') {
+        this.$router.push({
+          path: '/privacyProtocol',
+          query: {
+            showHeader: 1
           }
         })
       }
     },
-    async onLogin() {
+    async onLoginSuccess(isUpdate: boolean = false) {
       try {
-        let res = await request.post('/api-auth/smsLogin', {
-          requestType: 'form',
-          data: {
-            clientId: 'student',
-            clientSecret: 'student',
+        const res = await request.get('/api-student/open/bindTeacher', {
+          params: {
             phone: this.username,
-            smsCode: this.smsCode
+            userId: this.id,
+            isUpdate
           }
         })
-
-        const { authentication } = res.data
-        setAuth(authentication.token_type + ' ' + authentication.access_token)
-
-        let userCash = await request.get('/api-student/student/queryUserInfo', {
-          initRequest: true // 初始化接口
-        })
-        setLogin(userCash.data)
-
-        this.directNext()
+        this.changeInfo = res.data || {}
+        const { old, now } = res.data
+        if (!isUpdate && now && old && now.userId !== old.userId) {
+          this.teacherChangeStatus = true
+        } else {
+          this.$router.push('/inviteSuccess')
+        }
       } catch {}
-    },
-    async onSendCode() {
-      // 发送验证码
-      if (!checkPhone(this.username)) {
-        return Toast('请输入正确的手机号码')
-      }
-      this.imgCodeStatus = true
-    },
-    onCodeSend() {
-      this.countDownStatus = false
-      this.countDownRef.start()
-    },
-    onFinished() {
-      this.countDownStatus = true
-      this.countDownRef.reset()
-    },
-    onChange() {
-      if (this.loginType === 'PWD') {
-        this.loginType = 'SMS'
-      } else if (this.loginType === 'SMS') {
-        this.loginType = 'PWD'
-      }
     }
   },
   render() {
@@ -98,6 +112,15 @@ export default defineComponent({
             <span>你的器乐学习好帮手</span>
           </p>
         </div>
+        <div class={styles.teacherInfo}>
+          <div class={styles.teacherLogo}>
+            <img src={this.teacherInfo.avatar || iconTeacher} alt="" />
+          </div>
+          <div class={styles.teacherName}>
+            <span>{this.teacherInfo.realName}</span>{' '}
+            老师邀请您注册酷乐秀一起体验器乐学习的乐趣吧!
+          </div>
+        </div>
         <CellGroup class={styles.margin34} border={false}>
           <Row style={{ marginBottom: '16px' }}>
             <Col span={24} class={styles.formTitle}>
@@ -113,18 +136,74 @@ export default defineComponent({
               />
             </Col>
           </Row>
+          <div class={styles.itips}>未注册的手机号码将自动注册酷乐秀账号</div>
         </CellGroup>
         <div class={styles.margin34}>
           <Button
             round
             block
             type="primary"
-            disabled={this.codeDisable}
-            onClick={this.onLogin}
+            disabled={!this.codeDisable}
+            onClick={this.onSendCode}
           >
             获取短信验证码
           </Button>
         </div>
+        <div class={styles.protocol}>
+          <Checkbox
+            v-model={this.checked}
+            v-slots={{
+              icon: (props: any) => (
+                <Icon
+                  class={styles.boxStyle}
+                  name={props.checked ? activeButtonIcon : inactiveButtonIcon}
+                  size="15"
+                />
+              )
+            }}
+          >
+            我已阅读并同意
+          </Checkbox>
+          <span
+            class={styles.protocolText}
+            onClick={() => {
+              this.previewProtocol('user')
+            }}
+          >
+            《用户注册协议》
+          </span>
+          和
+          <span
+            class={styles.protocolText}
+            onClick={() => {
+              this.previewProtocol('privacy')
+            }}
+          >
+            《隐私政策》
+          </span>
+        </div>
+
+        <ColPopup v-model={this.imgCodeStatus}>
+          {this.imgCodeStatus && (
+            <InviteCode
+              phone={this.username}
+              onLoginSuccess={this.onLoginSuccess}
+            />
+          )}
+        </ColPopup>
+
+        <Popup show={this.teacherChangeStatus} round>
+          <TeacherChange
+            changeInfo={this.changeInfo}
+            onClose={() => {
+              this.teacherChangeStatus = false
+              this.$router.push('/inviteSuccess')
+            }}
+            onChangeTeacher={(isUpdate: boolean) => {
+              this.onLoginSuccess(isUpdate)
+            }}
+          />
+        </Popup>
       </div>
     )
   }

+ 46 - 0
src/student/invite-teacher/invite-code/index.module.less

@@ -0,0 +1,46 @@
+.loginCode {
+  min-height: 100vh;
+  background: url('../images/top_bg.png') no-repeat top center,
+    url('../images/bottom_bg.png') no-repeat bottom center;
+  background-color: #fff;
+  background-size: 100%;
+
+  :global {
+    .van-password-input {
+      margin: 0 42px;
+    }
+    .van-password-input__security li {
+      background-color: #f3f3f3;
+      width: 38px;
+      height: 40px;
+      border-radius: 6px;
+    }
+    .van-button {
+      padding: 14px 32px;
+    }
+    .van-count-down {
+      display: inline;
+      color: #fff;
+    }
+  }
+}
+
+.codeTips {
+  padding: 85px 35px 40px;
+  font-size: 26px;
+  font-weight: 500;
+
+  .codeTxt {
+    padding-top: 20px;
+    font-size: 16px;
+    line-height: 22px;
+    span {
+      color: #999999;
+    }
+  }
+}
+
+.btnWrap {
+  padding-top: 50px;
+  text-align: center;
+}

+ 131 - 0
src/student/invite-teacher/invite-code/index.tsx

@@ -0,0 +1,131 @@
+import ColHeader from '@/components/col-header'
+import request from '@/helpers/request'
+import { setAuth } from '@/helpers/utils'
+import { Button, NumberKeyboard, PasswordInput, Toast } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'invteCode',
+  props: {
+    phone: String,
+    onLoginSuccess: {
+      type: Function,
+      default: () => {}
+    }
+  },
+  data() {
+    return {
+      smsCode: '',
+      showKeyboard: true,
+      countDownStatus: true,
+      countDownTime: 120, // 倒计时时间
+      countTimer: null as any
+    }
+  },
+  watch: {
+    smsCode(val: any) {
+      if (val && val.length === 6) {
+        this.onLogin()
+      }
+    }
+  },
+  async mounted() {
+    this.$nextTick(async () => {
+      await this.onSendSms()
+    })
+  },
+  unmounted() {
+    clearInterval(this.countTimer)
+  },
+  methods: {
+    async onSendSms() {
+      try {
+        await request.post('/api-teacher/code/sendSmsCode', {
+          data: {
+            mobile: this.phone
+          }
+        })
+        this.onCountDown()
+        Toast('验证码已发送')
+      } catch {
+        this.countDownStatus = true
+      }
+    },
+    onCountDown() {
+      this.countDownStatus = false
+      this.countTimer = setInterval(() => {
+        if (this.countDownTime > 0) {
+          this.countDownTime--
+        } else {
+          this.countDownStatus = true
+          clearInterval(this.countTimer)
+        }
+      }, 1000)
+    },
+    async onLogin() {
+      try {
+        let res = await request.post('/api-auth/smsLogin', {
+          requestType: 'form',
+          data: {
+            clientId: 'student',
+            clientSecret: 'student',
+            phone: this.phone,
+            smsCode: this.smsCode
+          }
+        })
+
+        const { authentication } = res.data
+        setAuth(authentication.token_type + ' ' + authentication.access_token)
+
+        window.history.go(-1)
+        this.onLoginSuccess()
+      } catch {}
+    }
+  },
+  render() {
+    return (
+      <>
+        <div class={styles.loginCode}>
+          <ColHeader border={false} background="transparent" title=" " isBack />
+
+          <div class={styles.codeTips}>
+            <p class={styles.txt}>输入验证码</p>
+            <p class={styles.codeTxt}>
+              已发送6位验证码至 <span>{this.phone}</span>
+            </p>
+          </div>
+
+          <PasswordInput
+            value={this.smsCode}
+            focused={this.showKeyboard}
+            length={6}
+            onFocus={() => {
+              this.showKeyboard = true
+            }}
+            gutter={10}
+          ></PasswordInput>
+          <NumberKeyboard
+            v-model={this.smsCode}
+            show={this.showKeyboard}
+            maxlength={6}
+            onBlur={() => {
+              this.showKeyboard = false
+            }}
+          ></NumberKeyboard>
+
+          <div class={styles.btnWrap}>
+            <Button
+              type="primary"
+              round
+              onClick={this.onSendSms}
+              disabled={!this.countDownStatus}
+            >
+              重新发送 {!this.countDownStatus && <>({this.countDownTime})</>}
+            </Button>
+          </div>
+        </div>
+      </>
+    )
+  }
+})

+ 41 - 0
src/student/invite-teacher/invite-success/index.module.less

@@ -0,0 +1,41 @@
+.inviteSuccess {
+  min-height: 100vh;
+  background-color: #fff;
+  text-align: center;
+  img {
+    padding-top: 100px;
+    width: 273px;
+  }
+
+  .h2 {
+    font-size: 20px;
+    font-weight: 500;
+    color: #333333;
+    line-height: 28px;
+  }
+
+  .tips {
+    padding-top: 10px;
+    font-size: 12px;
+    color: #333333;
+    line-height: 17px;
+  }
+
+  .btnDownload {
+    margin-top: 35px;
+    padding: 14px 45px;
+  }
+
+  .wxpopup {
+    width: 100%;
+    height: 100vh;
+    position: absolute;
+    top: 0;
+    left: 0;
+    background: rgba(0, 0, 0, 0.5);
+    img {
+      width: 88%;
+      margin: 0 6%;
+    }
+  }
+}

+ 65 - 0
src/student/invite-teacher/invite-success/index.tsx

@@ -0,0 +1,65 @@
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import successTips from '../images/success_tips.png'
+import { Button } from 'vant'
+import { browser } from '@/helpers/utils'
+
+import wxBg from '../../down-load/images/wx_bg.png'
+
+export default defineComponent({
+  name: 'inviteSuccess',
+  data() {
+    return {
+      wxStatus: false
+    }
+  },
+  methods: {
+    onDownload() {
+      if (browser().weixin) {
+        this.wxStatus = true
+        return
+      }
+      if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
+        window.location.href =
+          'https://apps.apple.com/cn/app/%E7%AE%A1%E4%B9%90%E8%BF%B7/id1487054260'
+      } else if (/(Android)/i.test(navigator.userAgent)) {
+        window.location.href =
+          'https://sj.qq.com/myapp/detail.htm?apkName=com.daya.studaya_android'
+      } else {
+        this.$toast('请用手机或移动设备打开')
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles.inviteSuccess}>
+        <img src={successTips} />
+
+        <p class={styles.h2}>恭喜你注册成功</p>
+        <p class={styles.tips}>下载酷乐秀APP开启器乐学习之旅!</p>
+
+        <div class={'btnGroup'}>
+          <Button
+            type="primary"
+            round
+            class={styles.btnDownload}
+            onClick={this.onDownload}
+          >
+            下载酷乐秀
+          </Button>
+        </div>
+
+        {this.wxStatus && (
+          <div
+            class={styles.wxpopup}
+            onClick={() => {
+              this.wxStatus = false
+            }}
+          >
+            <img src={wxBg} alt="" />
+          </div>
+        )}
+      </div>
+    )
+  }
+})

+ 49 - 0
src/student/invite-teacher/teacher-change/index.module.less

@@ -0,0 +1,49 @@
+.teacherChange {
+  width: 311px;
+  border-radius: 15px;
+  overflow: hidden;
+}
+
+.changeInfo {
+  display: flex;
+  flex-direction: column;
+  justify-content: space-evenly;
+  height: 146px;
+  background: linear-gradient(to right, #ffefe7, #eafffd);
+  padding: 10px 34px 0;
+  font-size: 16px;
+  color: #333333;
+  line-height: 20px;
+  img {
+    width: 38px;
+    height: 38px;
+    border-radius: 50%;
+    overflow: hidden;
+  }
+
+  .txt,
+  .name {
+    display: flex;
+    align-items: center;
+  }
+  .txt {
+    font-weight: 500;
+  }
+  span {
+    padding-left: 12px;
+  }
+}
+
+.changeTips {
+  display: flex;
+  align-items: center;
+  padding: 16px 30px 35px;
+  font-size: 14px;
+  color: #666666;
+  line-height: 20px;
+  img {
+    width: 24px;
+    height: 24px;
+    margin-right: 10px;
+  }
+}

+ 86 - 0
src/student/invite-teacher/teacher-change/index.tsx

@@ -0,0 +1,86 @@
+import { Button, Col, Row } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import iconTeacher from '@/common/images/icon_teacher.png'
+import iconTips from '../images/icon_tips.png'
+
+export default defineComponent({
+  name: 'teacherChange',
+  props: {
+    changeInfo: {
+      type: Object,
+      default: () => ({})
+    },
+    onClose: {
+      type: Function,
+      default: () => {}
+    },
+    onChangeTeacher: {
+      type: Function,
+      default: (isUpdate: boolean) => {}
+    }
+  },
+  computed: {
+    teacherInfo() {
+      const { old, now } = this.changeInfo
+      return {
+        oldName: old.realName,
+        oldAvatar: old.avatar,
+        nowName: now.realName,
+        nowAvatar: now.avatar
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles.teacherChange}>
+        <div class={styles.changeInfo}>
+          <Row>
+            <Col span={12} class={styles.txt}>
+              您已绑定
+            </Col>
+            <Col span={12} class={styles.name}>
+              <img src={this.teacherInfo.oldAvatar || iconTeacher} />
+              <span>{this.teacherInfo.oldName}</span>
+            </Col>
+          </Row>
+          <Row>
+            <Col span={12} class={styles.txt}>
+              是否更换为
+            </Col>
+            <Col span={12} class={styles.name}>
+              <img src={this.teacherInfo.nowAvatar || iconTeacher} />
+              <span>{this.teacherInfo.nowName}</span>
+            </Col>
+          </Row>
+        </div>
+        <div class={styles.changeTips}>
+          <img src={iconTips} />
+          <span>更换后原老师已排课程课继续上课</span>
+        </div>
+
+        <div class={['btnGroup', 'btnMore']}>
+          <Button
+            type="primary"
+            round
+            plain
+            onClick={() => {
+              this.onClose()
+            }}
+          >
+            不更换
+          </Button>
+          <Button
+            type="primary"
+            round
+            onClick={() => {
+              this.onChangeTeacher(true)
+            }}
+          >
+            更换
+          </Button>
+        </div>
+      </div>
+    )
+  }
+})

+ 1 - 1
src/teacher/piano-room/account-recharge-timer/index.module.less

@@ -132,7 +132,7 @@
 .reminder {
   margin: 0 14px;
   // margin-bottom: calc(var(--van-button-default-line-height) + 38px);
-  margin-bottom: calc(var(--van-button-default-height) + 38px);
+  margin-bottom: calc(80px);
   padding: 12px;
   background: #ffffff;
   border-radius: 10px;

+ 69 - 19
src/teacher/piano-room/account-recharge-timer/index.tsx

@@ -1,4 +1,6 @@
 import ColHeader from '@/components/col-header'
+import request from '@/helpers/request'
+import { orderStatus } from '@/views/order-detail/orderStatus'
 import { Button } from 'vant'
 import { defineComponent } from 'vue'
 import styles from './index.module.less'
@@ -11,15 +13,62 @@ export const getAssetsHomeFile = (fileName: string) => {
 
 export default defineComponent({
   name: 'accountRechargeTimer',
+  data() {
+    const query = this.$route.query
+    return {
+      remainTime: query.remainTime || 0,
+      list: [],
+      selectItem: {} as any
+    }
+  },
+  async mounted() {
+    try {
+      const res = await request.post('/api-teacher/pianoRoomSettings/list', {
+        data: {}
+      })
+      this.list = res.data || []
+      if (this.list.length > 0) {
+        const item: any = this.list[0]
+        this.selectItem = item
+      }
+
+      console.log(this.selectItem)
+    } catch {}
+  },
+  methods: {
+    async onSubmit() {
+      const selectItem = this.selectItem
+      orderStatus.orderObject.orderType = 'PINAO_ROOM'
+      orderStatus.orderObject.orderName = '琴房时长充值'
+      orderStatus.orderObject.orderDesc = '琴房时长充值'
+      orderStatus.orderObject.actualPrice = selectItem.salePrice
+      orderStatus.orderObject.orderNo = ''
+      orderStatus.orderObject.orderList = [
+        {
+          orderType: 'PINAO_ROOM',
+          goodsName: '琴房时长充值',
+          ...selectItem
+        }
+      ]
+
+      this.$router.push({
+        path: '/orderDetail',
+        query: {
+          orderType: 'PINAO_ROOM',
+          pinaoId: selectItem.id
+        }
+      })
+    }
+  },
   render() {
     return (
-      <>
+      <div style={{ overflow: 'hidden' }}>
         <ColHeader />
 
         <div class={styles.lastMin}>
           <img src={getAssetsHomeFile('icon_music_account.png')} />
           <p>
-            琴房剩余时长<span>900</span>分钟
+            琴房剩余时长<span>{this.remainTime}</span>分钟
           </p>
         </div>
 
@@ -29,23 +78,23 @@ export default defineComponent({
           </div>
 
           <div class={styles['system-list']}>
-            <div class={[styles['system-item'], styles.active]}>
-              <p class={styles.title}>900分钟琴房时长</p>
-              <p class={styles.tips}>约10节45分钟1v1课程</p>
-              <p class={styles.price}>
-                <span>¥</span>199
-              </p>
-              <del class={styles.originalPrice}>¥199</del>
-            </div>
-
-            {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(item => (
-              <div class={[styles['system-item']]}>
-                <p class={styles.title}>900分钟琴房时长</p>
-                <p class={styles.tips}>约10节45分钟1v1课程</p>
+            {this.list.map((item: any) => (
+              <div
+                class={[
+                  styles['system-item'],
+                  item.id === this.selectItem.id && styles.active
+                ]}
+                onClick={() => {
+                  this.selectItem = item
+                }}
+              >
+                <p class={styles.title}>{item.times}分钟琴房时长</p>
+                <p class={styles.tips}>{item.description}</p>
                 <p class={styles.price}>
-                  <span>¥</span>199
+                  <span>¥</span>
+                  {item.salePrice}
                 </p>
-                <del class={styles.originalPrice}>¥199</del>
+                <del class={styles.originalPrice}>¥{item.originalPrice}</del>
               </div>
             ))}
           </div>
@@ -75,18 +124,19 @@ export default defineComponent({
             支付金额:
             <div class={styles.price}>
               <span class={styles.priceUnit}>¥</span>
-              <span class={styles.priceNum}>0</span>
+              <span class={styles.priceNum}>{this.selectItem.salePrice}</span>
             </div>
           </div>
           <Button
             color="linear-gradient(360deg, #FF0909 0%, #FF4D4D 69%, #FF7B7B 100%)"
             round
             class={styles.btn}
+            onClick={this.onSubmit}
           >
             立即支付
           </Button>
         </div>
-      </>
+      </div>
     )
   }
 })

+ 7 - 7
src/teacher/piano-room/class-arrangement/index.module.less

@@ -138,10 +138,10 @@
   font-weight: 500;
   text-align: center;
 }
-.singleClssTime {
-  :global {
-    .van-field__control {
-      text-align: center;
-    }
-  }
-}
+// .singleClssTime {
+//   :global {
+//     .van-field__control {
+//       text-align: center;
+//     }
+//   }
+// }

+ 11 - 10
src/teacher/piano-room/class-arrangement/index.tsx

@@ -205,7 +205,9 @@ export default defineComponent({
           {
             data: {
               classNum: params.classNum, //课时数
-              consumeTime: Math.ceil(students.value.length * params.classNum * params.singleClssTime), //消耗时长
+              consumeTime: Math.ceil(
+                students.value.length * params.classNum * params.singleClssTime
+              ), //消耗时长
               courseName: params.courseName, //课程名称
               singleClssTime: params.singleClssTime, //单课时长
               studentIds: students.value.map(n => n.userId), //学员id集合
@@ -214,11 +216,10 @@ export default defineComponent({
             }
           }
         )
-        if(code === 200){
-            confirmShow.value = false
+        if (code === 200) {
+          confirmShow.value = false
         }
       } catch (error) {}
-      
     }
     const week = {
       周一: 1,
@@ -278,12 +279,12 @@ export default defineComponent({
             label="单课时时长"
             placeholder="请输入课程时长"
             modelValue={params.singleClssTime}
-            onUpdate:modelValue={(t) => {
-                if (Math.abs(t) > 60){
-                    Toast('时长不能大于60分钟')
-                    return
-                }
-                params.singleClssTime = Math.abs(t)
+            onUpdate:modelValue={t => {
+              if (Math.abs(t) > 60) {
+                Toast('时长不能大于60分钟')
+                return
+              }
+              params.singleClssTime = Math.abs(t)
             }}
             // v-model={params.singleClssTime}
             v-slots={{

+ 4 - 0
src/teacher/piano-room/components/course/index.module.less

@@ -58,6 +58,10 @@
   height: 42px;
   background: linear-gradient(180deg, #ffbc90 0%, #ff7021 100%);
   border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
 }
 
 .courseName {

+ 84 - 16
src/teacher/piano-room/components/course/index.tsx

@@ -2,6 +2,8 @@ import { Button, Cell, CellGroup, Col, Row } from 'vant'
 import { defineComponent } from 'vue'
 import styles from './index.module.less'
 import iconTimer from '@/common/images/icon_timer2.png'
+import dayjs from 'dayjs'
+import { courseType } from '@/constant'
 
 export const getAssetsHomeFile = (fileName: string) => {
   const path = `../../images/${fileName}`
@@ -12,12 +14,54 @@ export const getAssetsHomeFile = (fileName: string) => {
 export default defineComponent({
   name: 'course',
   props: {
+    item: {
+      type: Object
+    },
     operation: {
       type: Boolean,
       default: true
+    },
+    // 学员调整
+    onStudentAdjust: {
+      type: Function,
+      default: (item: any) => {}
+    },
+    // 时间调整
+    onTimeAdjust: {
+      type: Function,
+      default: (item: any) => {}
+    },
+    // 删除课程
+    onCourseDelete: {
+      type: Function,
+      default: (item: any) => {}
+    }
+  },
+  computed: {
+    timer() {
+      const item: any = this.item
+      return (
+        dayjs(item.startTime).format('YYYY/MM/DD HH:mm') +
+        ' ~ ' +
+        dayjs(item.endTime).format('HH:mm')
+      )
+    }
+  },
+  methods: {
+    onJoinChat() {
+      const item: any = this.item
+      // 进入聊天
+      postMessage({
+        api: 'joinChatGroup',
+        content: {
+          type: 'multi', // single 单人 multi 多人
+          id: item.id
+        }
+      })
     }
   },
   render() {
+    const item: any = this.item
     return (
       <CellGroup class={styles.course} border={false}>
         <Cell
@@ -27,50 +71,74 @@ export default defineComponent({
             title: () => (
               <div class={styles.courseTimer}>
                 <img src={iconTimer} />
-                <span>2021/09/17 14:00~14:25</span>
+                <span>{this.timer}</span>
               </div>
             ),
             value: () => (
               <div class={styles.courseNum}>
-                <span>6人</span>
+                <span>{item.studentCount}人</span>
                 <img
                   src={getAssetsHomeFile('icon_num.png')}
                   style={{ marginRight: '14px' }}
                 />
-                {/* <img src={getAssetsHomeFile('icon_message.png')} /> */}
-
-                <span class={styles.munis}>-45分钟</span>
+                {this.operation ? (
+                  <img
+                    onClick={this.onJoinChat}
+                    src={getAssetsHomeFile('icon_message.png')}
+                  />
+                ) : (
+                  <span class={styles.munis}>-{item.singleCourseTime}分钟</span>
+                )}
               </div>
             )
           }}
         />
         <Cell>
           <div class={styles.courseInfo}>
-            <div class={styles.courseImg}></div>
+            <div class={styles.courseImg}>小课</div>
             <div class={styles.courseName}>
-              <p class={styles.name}>单簧管第二期</p>
+              <p class={styles.name}>{item.groupName}</p>
               <p>
-                <span class={styles.subject}>长笛</span>
+                <span class={styles.subject}>{item.subjectName}</span>
               </p>
             </div>
-            {/* <Button type="primary" round size="small">
-                  未开始
-                </Button> */}
-            <Button type="default" round size="small">
-              已结束
+            <Button
+              type={item.status !== 'NOT_START' ? 'default' : 'primary'}
+              round
+              size="small"
+            >
+              {courseType[item.status]}
             </Button>
           </div>
           {this.operation}
           {this.operation && (
             <Row class={styles.courseOperation}>
               <Col span={8} class="van-hairline--right">
-                <span>学员调整</span>
+                <span
+                  onClick={() => {
+                    this.onStudentAdjust(this.item)
+                  }}
+                >
+                  学员调整
+                </span>
               </Col>
               <Col span={8} class="van-hairline--right">
-                <span>时间调整</span>
+                <span
+                  onClick={() => {
+                    this.onTimeAdjust(this.item)
+                  }}
+                >
+                  时间调整
+                </span>
               </Col>
               <Col span={8}>
-                <span>删除课程</span>
+                <span
+                  onClick={() => {
+                    this.onCourseDelete(this.item)
+                  }}
+                >
+                  删除课程
+                </span>
               </Col>
             </Row>
           )}

+ 18 - 1
src/teacher/piano-room/components/student/index.tsx

@@ -10,6 +10,14 @@ export default defineComponent({
     item: {
       type: Object,
       default: {}
+    },
+    onClick: {
+      type: Function,
+      default: (item: any) => {}
+    },
+    border: {
+      type: Boolean,
+      default: true
     }
   },
   render() {
@@ -17,8 +25,17 @@ export default defineComponent({
       <Cell
         center
         class={styles.student}
+        border={this.border}
+        onClick={() => {
+          this.onClick(this.item)
+        }}
         v-slots={{
-          icon: () => <img src={this.item.avatar || iconStudent} class={styles.studentImg} />,
+          icon: () => (
+            <img
+              src={this.item.avatar || iconStudent}
+              class={styles.studentImg}
+            />
+          ),
           title: () => (
             <div class={styles.info}>
               <p class={styles.studentName}>{this.item.userName}</p>

+ 118 - 16
src/teacher/piano-room/course-record/index.tsx

@@ -1,11 +1,86 @@
 import ColHeader from '@/components/col-header'
-import { Col, Icon, Row } from 'vant'
+import ColResult from '@/components/col-result'
+import request from '@/helpers/request'
+import { formatterDate } from '@/helpers/utils'
+import dayjs from 'dayjs'
+import { Col, DatetimePicker, Icon, List, Popup, Row } from 'vant'
 import { defineComponent } from 'vue'
 import Course from '../components/course'
 import styles from './index.module.less'
 
 export default defineComponent({
   name: 'courseRecord',
+  data() {
+    return {
+      list: [],
+      dataShow: true, // 判断是否有数据
+      loading: false,
+      finished: false,
+      monthTxt: dayjs().format('YYYY年MM月'),
+      month: new Date(),
+      params: {
+        status: 'COMPLETE',
+        month: dayjs().format('YYYY-MM'),
+        page: 1,
+        rows: 20
+      },
+      timeShow: false,
+      studentStatus: false,
+      consumeTime: 0
+    }
+  },
+  async mounted() {
+    this.getConsumeTime()
+    this.getList()
+  },
+  methods: {
+    onSearch() {
+      this.dataShow = true
+      this.loading = false
+      this.finished = false
+      this.list = []
+      this.params.page = 1
+      this.getConsumeTime()
+      this.getList()
+    },
+    async getConsumeTime() {
+      try {
+        const res = await await request.get(
+          '/api-teacher/courseSchedule/selectConsumeTime',
+          {
+            params: {
+              month: this.params.month
+            }
+          }
+        )
+        console.log(res)
+        this.consumeTime = res.data.consumeTime || 0
+      } catch {}
+    },
+    async getList() {
+      try {
+        const res = await request.post(
+          '/api-teacher/courseSchedule/selectConsumeTimeList',
+          {
+            data: this.params
+          }
+        )
+        this.loading = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (this.list.length > 0 && result.pageNo === 1) {
+          return
+        }
+        this.list = this.list.concat(result.rows || [])
+        this.finished = result.pageNo >= result.totalPage
+        this.params.page = result.pageNo + 1
+        this.dataShow = this.list.length > 0
+      } catch {
+        this.dataShow = false
+        this.finished = true
+      }
+    }
+  },
   render() {
     return (
       <>
@@ -13,16 +88,22 @@ export default defineComponent({
         <Row class={[styles.countTimer, 'van-hairline--bottom']}>
           <Col span={9}>
             <p class={styles.timer}>
-              900<span>分钟</span>
+              {this.consumeTime}
+              <span>分钟</span>
             </p>
             <p class={styles.title}>
-              <span>琴房剩余时长</span>
+              <span>已消耗时长</span>
             </p>
           </Col>
         </Row>
         <div class={styles.searchList}>
-          <div class={styles.dataItem}>
-            2021年9月
+          <div
+            class={styles.dataItem}
+            onClick={() => {
+              this.timeShow = true
+            }}
+          >
+            {this.monthTxt}
             <Icon
               classPrefix="iconfont"
               name="down"
@@ -31,20 +112,41 @@ export default defineComponent({
               color="#CCCCCC"
             />
           </div>
+        </div>
 
-          <div class={styles.dataItem}>
-            所有课程
-            <Icon
-              classPrefix="iconfont"
-              name="down"
-              size={8}
-              style={{ marginLeft: '4px' }}
-              color="#CCCCCC"
+        {this.dataShow ? (
+          <List
+            v-model:loading={this.loading}
+            finished={this.finished}
+            finishedText=" "
+            class={[styles.liveList, 'mb12']}
+            immediateCheck={false}
+            onLoad={this.getList}
+          >
+            {this.list.map((item: any) => (
+              <Course item={item} operation={false} />
+            ))}
+          </List>
+        ) : (
+          <ColResult btnStatus={false} classImgSize="SMALL" tips="暂无课程" />
+        )}
+
+        <Popup position="bottom" v-model:show={this.timeShow} round>
+          <div class={styles.picker}>
+            <DatetimePicker
+              v-model={this.month}
+              type="year-month"
+              formatter={formatterDate}
+              onConfirm={(item: any) => {
+                this.monthTxt = dayjs(item).format('YYYY年MM月')
+                this.params.month = dayjs(item).format('YYYY-MM')
+                this.timeShow = false
+                this.onSearch()
+              }}
+              onCancel={() => (this.timeShow = false)}
             />
           </div>
-        </div>
-
-        <Course operation={false} />
+        </Popup>
       </>
     )
   }

+ 306 - 13
src/teacher/piano-room/index.tsx

@@ -1,9 +1,29 @@
 import ColHeader from '@/components/col-header'
-import { Row, Col, Button, CellGroup, Cell, Icon, Popup } from 'vant'
+import ColResult from '@/components/col-result'
+import request from '@/helpers/request'
+import { formatterDate } from '@/helpers/utils'
+import {
+  Row,
+  Col,
+  Button,
+  CellGroup,
+  Cell,
+  Icon,
+  Popup,
+  List,
+  DatetimePicker,
+  ActionSheet,
+  Toast,
+  Dialog
+} from 'vant'
 import { defineComponent } from 'vue'
+import dayjs from 'dayjs'
 import Course from './components/course'
 import styles from './index.module.less'
 import Share from './model/share'
+import ColPopup from '@/components/col-popup'
+import StudentInfo from './model/student-info'
+import StudentConfirm from './model/student-info/student-confirm'
 
 export const getAssetsHomeFile = (fileName: string) => {
   const path = `./images/${fileName}`
@@ -15,7 +35,159 @@ export default defineComponent({
   name: 'PianoRoom',
   data() {
     return {
-      shareStatus: true
+      shareStatus: false,
+      teacherId: 0,
+      typeStatus: false,
+      courseTxt: '所有课程',
+      actions: [
+        { name: '所有课程' },
+        { name: '未开始', status: 'NOT_START' },
+        { name: '进行中', status: 'ING' },
+        { name: '已完成', status: 'COMPLETE' },
+        { name: '已取消', status: 'CANCEL' }
+      ],
+      remain: {
+        frozenTime: 0,
+        remainTime: 0,
+        studentCount: 0
+      },
+      list: [],
+      dataShow: true, // 判断是否有数据
+      loading: false,
+      finished: false,
+      monthTxt: dayjs().format('YYYY年MM月'),
+      month: new Date(),
+      params: {
+        status: '',
+        month: dayjs().format('YYYY-MM'),
+        page: 1,
+        rows: 20
+      },
+      timeShow: false,
+      timeUpdateStatus: false,
+      timeUpdateInfo: {} as any,
+      timeUpdateTimer: new Date(),
+      studentStatus: false,
+      studentConfirm: false,
+      studentChangeObject: {} as any
+    }
+  },
+  async mounted() {
+    await this._init()
+    await this.getList()
+  },
+  methods: {
+    async _init() {
+      try {
+        const res = await request.get(
+          '/api-teacher/courseSchedule/selectRemainTime'
+        )
+        // console.log(res)
+        const { frozenTime, remainTime, studentCount, teacherId } =
+          res.data || {}
+        this.remain = {
+          frozenTime: frozenTime || 0,
+          remainTime: remainTime || 0,
+          studentCount: studentCount || 0
+        }
+        this.teacherId = teacherId
+      } catch {}
+    },
+    onSearch() {
+      this.dataShow = true
+      this.loading = false
+      this.finished = false
+      this.list = []
+      this.params.page = 1
+      this.getList()
+    },
+    onSelect(item: any) {
+      this.courseTxt = item.name
+      this.params.status = item.status || ''
+      this.onSearch()
+    },
+    async getList() {
+      try {
+        const res = await request.post(
+          '/api-teacher/courseSchedule/selectCourseList',
+          {
+            data: this.params
+          }
+        )
+        this.loading = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (this.list.length > 0 && result.pageNo === 1) {
+          return
+        }
+        this.list = this.list.concat(result.rows || [])
+        this.finished = result.pageNo >= result.totalPage
+        this.params.page = result.pageNo + 1
+        this.dataShow = this.list.length > 0
+      } catch {
+        this.dataShow = false
+        this.finished = true
+      }
+    },
+    async onCourseDelete(item: any) {
+      try {
+        Dialog.confirm({
+          title: '提示',
+          message: '确定删除该课程吗?',
+          confirmButtonColor: 'var(--van-primary)'
+        }).then(async () => {
+          await request.post('/api-teacher/courseSchedule/deleteCourse', {
+            data: {
+              courseId: item.courseId
+            }
+          })
+          console.log('删除成功')
+          Toast('课程删除成功')
+          this.onSearch()
+        })
+      } catch {}
+    },
+    onTimeUpdateChange(item: any) {
+      try {
+        Dialog.confirm({
+          title: '提示',
+          message: '确定调整该课程吗?',
+          confirmButtonColor: 'var(--van-primary)'
+        }).then(async () => {
+          const { courseId, singleCourseTime } = this.timeUpdateInfo
+          const startTime = dayjs(item).format('YYYY-MM-DD HH:mm:ss')
+          const endTime = dayjs(item)
+            .add(singleCourseTime, 'minute')
+            .format('YYYY-MM-DD HH:mm:ss')
+          await request.post('/api-teacher/courseSchedule/updateCourseTime', {
+            data: {
+              courseId,
+              startTime,
+              endTime
+            }
+          })
+          this.timeUpdateStatus = false
+          Toast('课程调整成功')
+          this.onSearch()
+        })
+      } catch {}
+    },
+    async onStudentChange(item: any) {
+      try {
+        const { courseId } = this.timeUpdateInfo
+        await request.post('/api-teacher/courseSchedule/updateCourseStudent', {
+          data: {
+            studentIds: item,
+            courseId
+          }
+        })
+        this.studentConfirm = false
+        Toast('学生调整成功')
+        setTimeout(async () => {
+          await this._init()
+          this.onSearch()
+        }, 1000)
+      } catch {}
     }
   },
   render() {
@@ -33,7 +205,8 @@ export default defineComponent({
           <Row class={styles.countTimer}>
             <Col span={9}>
               <p class={styles.timer}>
-                900<span>分钟</span>
+                {this.remain.remainTime}
+                <span>分钟</span>
               </p>
               <p class={styles.title}>
                 <img src={getAssetsHomeFile('icon_last_timer.png')} />
@@ -42,7 +215,8 @@ export default defineComponent({
             </Col>
             <Col span={9}>
               <p class={styles.timer}>
-                900<span>分钟</span>
+                {this.remain.frozenTime}
+                <span>分钟</span>
               </p>
               <p class={styles.title}>
                 <img src={getAssetsHomeFile('icon_freeze_timer.png')} />
@@ -56,7 +230,12 @@ export default defineComponent({
                 size="small"
                 class={styles.chargeTimer}
                 onClick={() => {
-                  this.$router.push('/accountRechargeTimer')
+                  this.$router.push({
+                    path: '/accountRechargeTimer',
+                    query: {
+                      remainTime: this.remain.remainTime
+                    }
+                  })
                 }}
               >
                 时长充值
@@ -67,7 +246,7 @@ export default defineComponent({
 
           <CellGroup class={styles.studentList} border={false}>
             <Cell
-              title={'我的学员 19 人'}
+              title={`我的学员 ${this.remain.studentCount} 人`}
               titleClass={styles.studentCount}
               isLink
               to={'/myStudent'}
@@ -85,7 +264,14 @@ export default defineComponent({
                 <img src={getAssetsHomeFile('icon_invite_student.png')} />
                 邀请学员
               </Button>
-              <Button color="#E0F7F3" round block>
+              <Button
+                color="#E0F7F3"
+                round
+                block
+                onClick={() => {
+                  this.$router.push('/classArrangement')
+                }}
+              >
                 <img src={getAssetsHomeFile('icon_class_plan.png')} />
                 排课
               </Button>
@@ -93,8 +279,13 @@ export default defineComponent({
           </CellGroup>
         </div>
         <div class={styles.searchList}>
-          <div class={styles.dataItem}>
-            2021年9月
+          <div
+            class={styles.dataItem}
+            onClick={() => {
+              this.timeShow = true
+            }}
+          >
+            {this.monthTxt}
             <Icon
               classPrefix="iconfont"
               name="down"
@@ -104,8 +295,13 @@ export default defineComponent({
             />
           </div>
 
-          <div class={styles.dataItem}>
-            所有课程
+          <div
+            class={styles.dataItem}
+            onClick={() => {
+              this.typeStatus = true
+            }}
+          >
+            {this.courseTxt}
             <Icon
               classPrefix="iconfont"
               name="down"
@@ -116,10 +312,107 @@ export default defineComponent({
           </div>
         </div>
 
-        <Course />
+        {this.dataShow ? (
+          <List
+            v-model:loading={this.loading}
+            finished={this.finished}
+            finishedText=" "
+            class={[styles.liveList, 'mb12']}
+            immediateCheck={false}
+            onLoad={this.getList}
+          >
+            {this.list.map((item: any) => (
+              <Course
+                item={item}
+                onCourseDelete={this.onCourseDelete}
+                onStudentAdjust={(item: any) => {
+                  console.log(item)
+                  this.timeUpdateInfo = item
+                  this.studentStatus = true
+                }}
+                onTimeAdjust={(item: any) => {
+                  this.timeUpdateInfo = item
+                  this.timeUpdateTimer = dayjs(item.startTime).toDate()
+                  this.timeUpdateStatus = true
+                }}
+              />
+            ))}
+          </List>
+        ) : (
+          <ColResult btnStatus={false} classImgSize="SMALL" tips="暂无课程" />
+        )}
 
+        <Popup position="bottom" v-model:show={this.timeShow} round>
+          <div class={styles.picker}>
+            <DatetimePicker
+              v-model={this.month}
+              type="year-month"
+              formatter={formatterDate}
+              onConfirm={(item: any) => {
+                this.monthTxt = dayjs(item).format('YYYY年MM月')
+                this.params.month = dayjs(item).format('YYYY-MM')
+                this.timeShow = false
+                this.onSearch()
+              }}
+              onCancel={() => (this.timeShow = false)}
+            />
+          </div>
+        </Popup>
+
+        <Popup position="bottom" v-model:show={this.timeUpdateStatus} round>
+          <div class={styles.picker}>
+            <DatetimePicker
+              v-model={this.timeUpdateTimer}
+              type="datetime"
+              formatter={formatterDate}
+              onConfirm={(item: any) => {
+                this.onTimeUpdateChange(item)
+              }}
+              onCancel={() => (this.timeUpdateStatus = false)}
+            />
+          </div>
+        </Popup>
+
+        <ActionSheet
+          v-model:show={this.typeStatus}
+          actions={this.actions}
+          closeOnClickAction
+          cancelText="取消"
+          onSelect={this.onSelect}
+          onCancel={() => {
+            this.typeStatus = false
+          }}
+        />
         <Popup v-model:show={this.shareStatus} round>
-          <Share />
+          <Share teacherId={this.teacherId} />
+        </Popup>
+
+        <Popup v-model:show={this.studentStatus} position="bottom" round>
+          {this.studentStatus && (
+            <StudentInfo
+              courseId={this.timeUpdateInfo.courseId}
+              onSubmit={(item: any) => {
+                this.studentChangeObject = item
+                this.studentStatus = false
+                this.studentConfirm = true
+              }}
+            />
+          )}
+        </Popup>
+
+        <Popup
+          v-model:show={this.studentConfirm}
+          position="bottom"
+          round
+          closeable
+        >
+          <StudentConfirm
+            courseInfo={this.timeUpdateInfo}
+            studentObject={this.studentChangeObject}
+            onSubmit={(item: any) => {
+              this.onStudentChange(item)
+            }}
+          />
         </Popup>
       </>
     )

+ 13 - 0
src/teacher/piano-room/model/share/index.module.less

@@ -72,3 +72,16 @@
     height: 76px;
   }
 }
+
+.continue {
+  position: absolute;
+  right: 25px;
+  top: 20px;
+  z-index: 2;
+  background: rgba(0, 0, 0, 0.23);
+  border-radius: 19px;
+  font-size: 14px;
+  color: #fff;
+  background: linear-gradient(0deg, #2dc7aa 0%, #32e8c6 100%);
+  padding: 6px 18px;
+}

+ 89 - 27
src/teacher/piano-room/model/share/index.tsx

@@ -1,44 +1,106 @@
-import { Cell } from 'vant'
+import { Cell, Toast } from 'vant'
 import { defineComponent } from 'vue'
 import styles from './index.module.less'
+import { postMessage } from '@/helpers/native-message'
+import QrCodeVue from 'qrcode.vue'
+import { toPng } from 'html-to-image'
 
 import iconTeacher from '@/common/images/icon_teacher.png'
 import logo from '../../images/logo.png'
 
 export default defineComponent({
   name: 'share',
+  props: {
+    teacherId: {
+      type: Number
+    }
+  },
+  data() {
+    return {
+      qrCode: '',
+      image: null as any
+    }
+  },
+  mounted() {
+    this.qrCode =
+      location.origin + '/student/#/inviteTeacher?id=' + this.teacherId
+
+    this.$nextTick(async () => {
+      const container: any = document.getElementById('share-preview-container')
+      let image = await toPng(container)
+      image = await toPng(container)
+      this.image = image
+    })
+  },
+  methods: {
+    async shareShow() {
+      let image = this.image
+      if (image) {
+        postMessage(
+          {
+            api: 'shareAchievements',
+            content: {
+              title: '我在管乐迷使用AI智能云教练练习乐器',
+              desc: '管乐迷AI智能云教练帮助我自主练习乐器,真的太好用啦!每天都要坚持练习哦~',
+              image,
+              video: '',
+              type: 'image'
+            }
+          },
+          (res: any) => {
+            if (res && res.content) {
+              Toast(
+                res.content.message ||
+                  (res.content.status ? '分享成功' : '分享失败')
+              )
+            }
+          }
+        )
+      }
+    }
+  },
   render() {
     return (
-      <div class={styles.shareSection}>
-        <div class={styles.section}>
-          <Cell
-            center
-            border={false}
-            style={{ padding: 0 }}
-            v-slots={{
-              icon: () => <img src={iconTeacher} class={styles.img} />,
-              title: () => (
-                <div>
-                  <p class={styles.name}>分享</p>
-                  <p class={styles.titleTips}>酷乐秀入驻老师</p>
-                </div>
-              )
-            }}
-          />
-          <p class={[styles.txt, styles.teacherName]}>
-            <span>李老师</span>邀请您加入酷乐秀
-          </p>
-          <p class={styles.txt}>来与我一起踏入音</p>
+      <>
+        <div class={styles.continue} onClick={() => {}}>
+          分享
         </div>
+        <div class={styles.shareSection} id="share-preview-container">
+          <div class={styles.section}>
+            <Cell
+              center
+              border={false}
+              style={{ padding: 0 }}
+              v-slots={{
+                icon: () => <img src={iconTeacher} class={styles.img} />,
+                title: () => (
+                  <div>
+                    <p class={styles.name}>分享</p>
+                    <p class={styles.titleTips}>酷乐秀入驻老师</p>
+                  </div>
+                )
+              }}
+            />
+            <p class={[styles.txt, styles.teacherName]}>
+              <span>李老师</span>邀请您加入酷乐秀
+            </p>
+            <p class={styles.txt}>来与我一起踏入音</p>
+          </div>
 
-        <div class={[styles.section, styles.download]}>
-          <div class={styles.logo}>
-            <img src={logo} />
-            <p>扫码下载酷乐秀开启教学之旅</p>
+          <div class={[styles.section, styles.download]}>
+            <div class={styles.logo}>
+              <img src={logo} />
+              <p>扫码下载酷乐秀开启教学之旅</p>
+            </div>
+            <div class={styles.qrcode}>
+              <QrCodeVue
+                value={this.qrCode}
+                style={{ width: '100%', height: '100%' }}
+              />
+            </div>
           </div>
-          <div class={styles.qrcode}></div>
         </div>
-      </div>
+      </>
     )
   }
 })

+ 28 - 0
src/teacher/piano-room/model/student-info/index.module.less

@@ -0,0 +1,28 @@
+.label {
+  margin-right: 8px;
+  font-size: 14px;
+  :global {
+    .van-list__loading,
+    .van-list__finished-text,
+    .van-list__error-text {
+      width: 100%;
+    }
+    .iconfont-down {
+      margin-left: 4px;
+    }
+  }
+}
+
+.studentList {
+  height: 55vh;
+  overflow: auto;
+  :global {
+    .van-checkbox {
+      justify-content: flex-end;
+    }
+  }
+}
+
+.btnGroup {
+  padding: 0 20px 14px;
+}

+ 263 - 0
src/teacher/piano-room/model/student-info/index.tsx

@@ -0,0 +1,263 @@
+import ColHeader from '@/components/col-header'
+import ColResult from '@/components/col-result'
+import ColSearch from '@/components/col-search'
+import request from '@/helpers/request'
+import OrganSearch from '@/student/practice-class/model/organ-search'
+import { Button, Checkbox, Icon, List, Popup, Toast } from 'vant'
+import { defineComponent } from 'vue'
+import Student from '../../components/student'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'myStudent',
+  props: {
+    courseId: Number,
+    onSubmit: {
+      type: Function,
+      default: (item: any) => {}
+    }
+  },
+  data() {
+    return {
+      searchStatus: false,
+      openStatus: false,
+      subjectList: [],
+      list: [],
+      dataShow: true, // 判断是否有数据
+      loading: false,
+      finished: false,
+      params: {
+        userName: '',
+        subjectName: '全部声部',
+        subjectId: null as any,
+        page: 1,
+        rows: 20
+      },
+      interfaceIds: [] as any,
+      userIdList: [] as any
+    }
+  },
+  async mounted() {
+    try {
+      const res = await request.get('/api-teacher/subject/subjectSelect')
+      this.subjectList = res.data || []
+    } catch {}
+    await this.getExistList()
+    await this.getList()
+  },
+  methods: {
+    onSort() {
+      this.params.page = 1
+      this.list = []
+      this.dataShow = true // 判断是否有数据
+      this.loading = false
+      this.finished = false
+      this.searchStatus = false
+      this.getList()
+    },
+    onSearch(val: string) {
+      this.params.userName = val
+      this.onSort()
+    },
+    async getExistList() {
+      try {
+        const res = await request.post(
+          '/api-teacher/courseSchedule/selectStudent',
+          {
+            data: {
+              courseId: this.courseId,
+              page: 1,
+              rows: 20
+            }
+          }
+        )
+        const rows = res.data.rows || []
+        const userIdList = rows.map((item: any) => {
+          return item.userId
+        })
+        this.interfaceIds = [...userIdList]
+        this.userIdList = [...userIdList]
+      } catch {}
+    },
+    async getList() {
+      try {
+        const res = await request.post(
+          '/api-teacher/courseSchedule/selectStudent',
+          {
+            data: this.params
+          }
+        )
+        this.loading = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (this.list.length > 0 && result.pageNo === 1) {
+          return
+        }
+        const rows = result.rows || []
+        rows.forEach((n: any) => {
+          if (this.userIdList.includes(n.userId)) {
+            n.checked = true
+          } else {
+            n.checked = false
+          }
+        })
+        this.list = this.list.concat(rows || [])
+        this.finished = result.pageNo >= result.totalPage
+        this.params.page = result.pageNo + 1
+        this.dataShow = this.list.length > 0
+      } catch {
+        this.dataShow = false
+        this.finished = true
+      }
+    },
+    equar(a, b) {
+      // 判断数组的长度
+      if (a.length !== b.length) {
+        return false
+      } else {
+        // 循环遍历数组的值进行比较
+        for (let i = 0; i < a.length; i++) {
+          if (a[i] !== b[i]) {
+            return false
+          }
+        }
+        return true
+      }
+    },
+    nextSubmit() {
+      if (this.userIdList.length <= 0) {
+        return Toast('请至少选择一个学员')
+      }
+      if (this.equar(this.interfaceIds, this.userIdList)) {
+        return Toast('您未调整学员')
+      }
+      const addStudentIds = this.userIdList.filter((item: any) => {
+        return !this.interfaceIds.includes(item) && item
+      })
+      const removeStudentIds = this.interfaceIds.filter((item: any) => {
+        return !this.userIdList.includes(item) && item
+      })
+      console.log(addStudentIds, removeStudentIds)
+      const addStudents: any = []
+      const removeStudents: any = []
+      this.list.forEach((item: any) => {
+        if (addStudentIds.includes(item.userId)) {
+          addStudents.push(item)
+        }
+
+        if (removeStudentIds.includes(item.userId)) {
+          removeStudents.push(item)
+        }
+      })
+      console.log(addStudents, removeStudents)
+      this.onSubmit({
+        addStudents,
+        removeStudents,
+        userIdList: this.userIdList
+      })
+    },
+    onCheckbox(item: any) {
+      if (!item.checked && this.userIdList.length >= 7) {
+        return Toast('学生已达上限')
+      }
+      item.checked = !item.checked
+      const isExist = this.userIdList.includes(item.userId)
+      if (item.checked) {
+        !isExist && this.userIdList.push(item.userId)
+      } else {
+        isExist &&
+          this.userIdList.splice(this.userIdList.indexOf(item.userId), 1)
+      }
+    }
+  },
+  render() {
+    return (
+      <>
+        <ColSearch
+          placeholder="请输入学员名称"
+          onSearch={this.onSearch}
+          v-slots={{
+            left: () => (
+              <div
+                class={styles.label}
+                onClick={() => {
+                  this.searchStatus = !this.searchStatus
+                  this.openStatus = !this.openStatus
+                }}
+              >
+                {this.params.subjectName}
+                <Icon
+                  classPrefix="iconfont"
+                  name="down"
+                  size={12}
+                  color="#333"
+                />
+              </div>
+            )
+          }}
+        />
+
+        <div class={styles.studentList}>
+          {this.dataShow ? (
+            <List
+              v-model:loading={this.loading}
+              finished={this.finished}
+              finishedText=" "
+              class={['mb12']}
+              immediateCheck={false}
+              onLoad={this.getList}
+            >
+              {this.list.map((item: any) => (
+                <Student
+                  item={{
+                    userName: item.userName,
+                    subjectName: item.subjectName,
+                    avatar: item.avatar
+                  }}
+                  onClick={() => {
+                    this.onCheckbox(item)
+                  }}
+                >
+                  <Checkbox
+                    v-model={item.checked}
+                    name={item.userId}
+                    onClick={() => {
+                      this.onCheckbox(item)
+                    }}
+                  ></Checkbox>
+                </Student>
+              ))}
+            </List>
+          ) : (
+            <ColResult btnStatus={false} classImgSize="SMALL" tips="暂无学员" />
+          )}
+        </div>
+        <Popup
+          show={this.searchStatus}
+          position="bottom"
+          round
+          closeable
+          safe-area-inset-bottom
+          onClose={() => (this.searchStatus = false)}
+          onClosed={() => (this.openStatus = false)}
+        >
+          {this.openStatus && (
+            <OrganSearch
+              subjectList={this.subjectList}
+              onSort={this.onSort}
+              isReset
+              v-model={this.params.subjectId}
+              v-model:subjectName={this.params.subjectName}
+            />
+          )}
+        </Popup>
+
+        <div class={styles.btnGroup}>
+          <Button type="primary" round block onClick={this.nextSubmit}>
+            下一步
+          </Button>
+        </div>
+      </>
+    )
+  }
+})

+ 38 - 0
src/teacher/piano-room/model/student-info/student-confirm.module.less

@@ -0,0 +1,38 @@
+.studentConfirm {
+}
+.confirmTitle {
+  font-size: 16px;
+  font-weight: 500;
+  color: #333333;
+  line-height: 22px;
+  padding: 22px 22px 12px;
+  .timer {
+    color: #2dc7aa;
+  }
+}
+
+.addTitle,
+.calc {
+  font-size: 16px;
+  color: #666666;
+  line-height: 22px;
+  padding: 12px 22px 0;
+  span {
+    color: var(--van-primary);
+  }
+
+  :global {
+    .student {
+      margin: 0;
+    }
+  }
+}
+
+.studentList {
+  max-height: 45vh;
+  overflow: auto;
+}
+
+.btnGroup {
+  padding: 14px 20px;
+}

+ 105 - 0
src/teacher/piano-room/model/student-info/student-confirm.tsx

@@ -0,0 +1,105 @@
+import dayjs from 'dayjs'
+import { Button } from 'vant'
+import { defineComponent } from 'vue'
+import Student from '../../components/student'
+import styles from './student-confirm.module.less'
+
+export default defineComponent({
+  name: 'studentConfirm',
+  props: {
+    courseInfo: {
+      type: Object,
+      default: {}
+    },
+    studentObject: {
+      type: Object,
+      default: {}
+    },
+    onSubmit: {
+      type: Function,
+      default: (item: any) => {}
+    }
+  },
+  computed: {
+    timer() {
+      const item: any = this.courseInfo
+      return (
+        dayjs(item.startTime).format('YYYY/MM/DD HH:mm') +
+        ' ~ ' +
+        dayjs(item.endTime).format('HH:mm')
+      )
+    },
+    addStudents() {
+      const { addStudents } = this.studentObject
+      return addStudents || []
+    },
+    removeStudents() {
+      const { removeStudents } = this.studentObject
+      return removeStudents || []
+    },
+    calcTimer() {
+      const { addStudents, removeStudents } = this.studentObject
+      const { singleCourseTime } = this.courseInfo
+      const suffix: number = addStudents.length - removeStudents.length
+      console.log(suffix, singleCourseTime)
+      const type = suffix >= 0 ? 'add' : 'remove'
+      return {
+        type,
+        mins: Math.abs(suffix * singleCourseTime)
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles.studentConfirm}>
+        <div class={[styles.confirmTitle, 'van-hairline--bottom']}>
+          <p>您将为{this.courseInfo.groupName}</p>
+          <p class={styles.timer}>{this.timer}</p>
+        </div>
+
+        <div class={styles.studentList}>
+          {this.addStudents.length > 0 && (
+            <>
+              <p class={styles.addTitle}>
+                添加学员 <span>{this.addStudents.length}</span> 名
+              </p>
+              {this.addStudents.map((item: any) => (
+                <Student border={false} item={item} />
+              ))}
+            </>
+          )}
+
+          {this.removeStudents.length > 0 && (
+            <>
+              <p class={styles.addTitle}>
+                移除学员 <span>{this.removeStudents.length}</span> 名
+              </p>
+              {this.removeStudents.map((item: any) => (
+                <Student border={false} item={item} />
+              ))}
+            </>
+          )}
+        </div>
+
+        <p class={styles.calc}>
+          调整后将{this.calcTimer.type === 'remove' ? '释放' : '冻结'}{' '}
+          <span>{this.calcTimer.mins}</span> 分钟
+        </p>
+
+        <div class={styles.btnGroup}>
+          <Button
+            type="primary"
+            round
+            block
+            onClick={() => {
+              const { userIdList } = this.studentObject
+              this.onSubmit(userIdList)
+            }}
+          >
+            确认调整
+          </Button>
+        </div>
+      </div>
+    )
+  }
+})

+ 55 - 7
src/teacher/piano-room/my-student/index.tsx

@@ -1,9 +1,10 @@
 import ColHeader from '@/components/col-header'
+import ColResult from '@/components/col-result'
 import ColSearch from '@/components/col-search'
 import request from '@/helpers/request'
 import OrganSearch from '@/student/practice-class/model/organ-search'
 import { useRect } from '@vant/use'
-import { Icon, Popup, Sticky } from 'vant'
+import { Icon, List, Popup, Sticky } from 'vant'
 import { defineComponent } from 'vue'
 import Student from '../components/student'
 import styles from './index.module.less'
@@ -20,7 +21,7 @@ export default defineComponent({
       loading: false,
       finished: false,
       params: {
-        search: '',
+        userName: '',
         subjectName: '全部声部',
         subjectId: null as any,
         page: 1,
@@ -34,7 +35,7 @@ export default defineComponent({
       const res = await request.get('/api-teacher/subject/subjectSelect')
       this.subjectList = res.data || []
     } catch {}
-    // this.getList()
+    this.getList()
 
     const { height } = useRect((this as any).$refs.headers)
     this.height = height
@@ -50,10 +51,32 @@ export default defineComponent({
       this.getList()
     },
     onSearch(val: string) {
-      this.params.search = val
+      this.params.userName = val
       this.onSort()
     },
-    async getList() {}
+    async getList() {
+      try {
+        const res = await request.post(
+          '/api-teacher/courseSchedule/selectStudent',
+          {
+            data: this.params
+          }
+        )
+        this.loading = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (this.list.length > 0 && result.pageNo === 1) {
+          return
+        }
+        this.list = this.list.concat(result.rows || [])
+        this.finished = result.pageNo >= result.totalPage
+        this.params.page = result.pageNo + 1
+        this.dataShow = this.list.length > 0
+      } catch {
+        this.dataShow = false
+        this.finished = true
+      }
+    }
   },
   render() {
     return (
@@ -62,7 +85,7 @@ export default defineComponent({
           <div ref="headers">
             <ColHeader isFixed={false} border={false} />
             <ColSearch
-              placeholder="请输入老师名称"
+              placeholder="请输入学员名称"
               onSearch={this.onSearch}
               v-slots={{
                 left: () => (
@@ -87,7 +110,32 @@ export default defineComponent({
           </div>
         </Sticky>
 
-        <Student>上次课程 1 天前</Student>
+        {this.dataShow ? (
+          <List
+            v-model:loading={this.loading}
+            finished={this.finished}
+            finishedText=" "
+            class={[styles.liveList, 'mb12']}
+            immediateCheck={false}
+            onLoad={this.getList}
+          >
+            {this.list.map((item: any) => (
+              <Student
+                item={{
+                  userName: item.userName,
+                  subjectName: item.subjectName,
+                  avatar: item.avatar
+                }}
+              >
+                {item.lastEndClass !== 0 && (
+                  <>上次课程 {item.lastEndClass} 天前</>
+                )}
+              </Student>
+            ))}
+          </List>
+        ) : (
+          <ColResult btnStatus={false} classImgSize="SMALL" tips="暂无学员" />
+        )}
 
         <Popup
           show={this.searchStatus}

+ 20 - 1
src/views/order-detail/index.tsx

@@ -24,6 +24,7 @@ import OrderPractice from './order-practice'
 import OrderVip from './order-vip'
 import OrderMusic from './order-music'
 import { moneyFormat } from '@/helpers/utils'
+import OrderPinao from './order-pinao'
 
 export default defineComponent({
   name: 'order-detail',
@@ -96,6 +97,8 @@ export default defineComponent({
             musicSheetId: item.id,
             actualPrice: item.actualPrice || 0
           }
+        } else if (item.orderType === 'PINAO_ROOM') {
+          params.bizContent = item.id
         }
         return params
       })
@@ -132,7 +135,11 @@ export default defineComponent({
       // 正常支付
       try {
         const orderObject = orderStatus.orderObject
-        const res = await request.post('/api-student/userOrder/executeOrder', {
+        const url =
+          state.platformType === 'TEACHER'
+            ? '/api-teacher/userOrder/executeOrder'
+            : '/api-student/userOrder/executeOrder'
+        const res = await request.post(url, {
           data: {
             orderName: orderObject.orderName,
             orderDesc: orderObject.orderDesc,
@@ -142,6 +149,16 @@ export default defineComponent({
           }
         })
         const result = res.data || {}
+
+        if (result.status == 'PAID') {
+          this.$router.push({
+            path: '/tradeDetail',
+            query: {
+              orderNo: result.orderNo
+            }
+          })
+          return
+        }
         orderStatus.orderObject.orderNo = result.orderNo
         orderStatus.orderObject.actualPrice = result.actualPrice
 
@@ -176,6 +193,8 @@ export default defineComponent({
             return <OrderVip item={item} />
           } else if (item.orderType === 'MUSIC') {
             return <OrderMusic item={item} />
+          } else if (item.orderType === 'PINAO_ROOM') {
+            return <OrderPinao item={item} />
           }
         })}
 

+ 48 - 0
src/views/order-detail/order-pinao/index.module.less

@@ -0,0 +1,48 @@
+.memberLogo {
+  width: 53px;
+  height: 50px;
+}
+
+.titleClass {
+  padding-left: 20px;
+}
+
+.title {
+  font-size: 16px;
+  font-weight: 500;
+  color: #333333;
+  line-height: 22px;
+}
+
+.price {
+  padding-top: 3px;
+  font-size: 16px;
+  font-weight: 500;
+  color: #ff4e19;
+  line-height: 20px;
+  i {
+    font-style: normal;
+    font-size: 14px;
+  }
+}
+
+.timerTitle {
+  display: flex;
+  align-items: center;
+  font-size: 14px;
+  font-weight: 500;
+  color: #333333;
+  line-height: 20px;
+  padding-right: 12px;
+}
+
+.timer {
+  font-size: 13px;
+  color: #999999;
+  line-height: 18px;
+}
+
+.timerCell {
+  display: flex;
+  align-items: center;
+}

+ 62 - 0
src/views/order-detail/order-pinao/index.tsx

@@ -0,0 +1,62 @@
+import { Cell, CellGroup, Icon, Image } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import iconPinao from '@common/images/icon_pinao.png'
+
+export default defineComponent({
+  name: 'OrderPinao',
+  props: {
+    item: {
+      type: Object,
+      default: {}
+    }
+  },
+  render() {
+    const item = this.item
+    console.log(item)
+    return (
+      <div class={styles.videoOrder}>
+        <CellGroup
+          class={'mb12'}
+          border={false}
+          style={{ borderRadius: '8px' }}
+        >
+          <Cell
+            center
+            titleClass={styles.titleClass}
+            v-slots={{
+              icon: () => <Image class={styles.memberLogo} src={iconPinao} />,
+              title: () => (
+                <div class={styles.container}>
+                  <div class={styles.title}>{item.times}分钟琴房时长</div>
+                  <div class={styles.price}>
+                    <i>¥</i>
+                    {(this as any).$filters.moneyFormat(item.salePrice)}
+                  </div>
+                </div>
+              )
+            }}
+          />
+
+          {/* <Cell
+            center
+            v-slots={{
+              title: () => (
+                <div class={styles.timerCell}>
+                  <div class={styles.timerTitle}>
+                    <Icon name={iconTimer} size={18} />
+                    <span style={{ paddingLeft: '5px' }}>生效时间</span>
+                  </div>
+                  <div class={styles.timer}>
+                    {item.startTime} 至 {item.endTime}
+                  </div>
+                </div>
+              )
+            }}
+          /> */}
+        </CellGroup>
+      </div>
+      // 视频课
+    )
+  }
+})

+ 9 - 1
src/views/order-detail/orderStatus.ts

@@ -1,6 +1,14 @@
 import { orderType } from './../../constant/index'
 import { reactive } from 'vue'
-type orderType = 'VIDEO' | 'LIVE' | 'PRACTICE' | 'GOODS' | 'VIP' | 'MUSIC' | ''
+type orderType =
+  | 'VIDEO'
+  | 'LIVE'
+  | 'PRACTICE'
+  | 'GOODS'
+  | 'VIP'
+  | 'MUSIC'
+  | 'PINAO_ROOM'
+  | ''
 export const orderStatus = reactive({
   orderType: '' as orderType, // 购买类型
   liveInfo: {} as any, // 直播购买信息

+ 5 - 2
src/views/order-detail/payment/index.tsx

@@ -23,14 +23,17 @@ interface IOrderInfo {
   orderNo: string | number
   actualPrice: string | number
 }
+
+const urlFix =
+  state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student'
 const urlType = {
   goodsPay: {
     cancelUrl: '/api-mall-portal/order/cancelUserOrder',
     payUrl: '/api-mall-portal/payment/orderPay'
   },
   orderPay: {
-    cancelUrl: '/api-student/userOrder/orderCancel',
-    payUrl: '/api-student/userOrder/orderPay'
+    cancelUrl: urlFix + '/userOrder/orderCancel',
+    payUrl: urlFix + '/userOrder/orderPay'
   }
 }
 

+ 246 - 236
src/views/protocol/privacy.tsx

@@ -1,246 +1,256 @@
+import ColHeader from '@/components/col-header'
 import { defineComponent } from 'vue'
 import styles from './index.module.less'
 
 export default defineComponent({
   name: 'register',
+  data() {
+    const query = this.$route.query
+    return {
+      showHeader: query.showHeader || '0'
+    }
+  },
   render() {
     return (
-      <div class={styles.container}>
-        版本更新时间
-        <br />
-        <strong>更新日期:2022年4月18日</strong>
-        <br />
-        <strong>生效日期:2022年4月18日</strong>
-        <br />
-        版本更新提示
-        <br />
-        我们可能适时修订本《隐私政策》的条款,该修订构成本《隐私政策》的一部分。如该修订造成您在本《隐私政策》下权利的实质减少,我们将在修订生效前通过在主页上显著位置提示或向您发送电子邮件或以其他方式通知您。在该种情况下,若您继续使用我们的服务,即表示同意受经修订的本《隐私政策》的约束。
-        <br />
-        引言
-        <br />
-        酷乐秀是由武汉酷乐秀科技有限公司(以下简称“酷乐秀”)为您提供的一款找谱,智能练琴,社交学习平台,问答于一体的综合音乐服务产品。酷乐秀十分重视用户的个人信息和数据。您在使用我们的服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明,在使用我们的服务时,我们如何收集、使用、储存和分享这些信息,以及我们为您提供的访问、更新、控制和保护这些信息的方式。本《隐私政策》与您所使用的酷乐秀服务息息相关,希望您仔细阅读,在需要时,按照本《隐私政策》的指引,作出您认为适当的选择。本《隐私政策》中涉及的相关技术词汇,我们尽量以简明扼要的表述,并提供进一步说明的链接,以便您的理解。
-        <br />
-        您使用或继续使用我们的服务,即意味着同意我们按照本《隐私政策》收集、使用、储存和分享您的相关信息。
-        <br />
-        我们如何收集的信息
-        <br />
-        您在使用我们的产品与/或服务时,我们需要/可能需要收集和使用您的一些个人信息,我们收集和使用的您的个人信息类型包括两种: 第一种:我们产品与/或服务的核心业务功能所必需的信息:此类信息为产品与/或服务正常运行的必备信息,您须授权我们收集。如您拒绝提供,您将无法正常使用我们的产品与/或服务 第二种:我们产品与/或服务的附加业务功能可能需要收集的信息:此信息为非核心业务功能所需的信息,您可以选择是否授权我们收集。如您拒绝提供,将导致附加业务功能无法实现或无法达到我们拟达到的效果,但不影响您对核心业务功能的正常使用。
-        <br />
-        <h2>(一)账号注册/登录功能</h2>
-        个人信息是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。
-        <br />
-        1. 用户账户信息:
-        当您使用账号注册功能时,我们会收集由您主动提供给我们的一些单独或者结合识别您实名身份的信息,包括:手机号码、验证码匹配结果,并创建密码。您的密码将以加密形式进行自动存储、传输、验证,我们不会以明文方式存储、传输、验证您的密码。您在保管、输入、使用您的密码时,应当对物理环境、电子环境审慎评估,以防密码外泄。我们收集这些信息是用以完成注册程序、为您持续稳定提供专属于注册用户的产品与/或服务,并保护您的账号安全。您应知悉,手机号码和验证码匹配结果属于您的个人敏感信息,我们收集该类信息是为了满足相关法律法规的要求,如您拒绝提供可能导致您无法使用我们的此功能,请您谨慎考虑后再提供。
-        <br />
-        需要说明的是,我们的一些产品支持您使用第三方平台的账号(例如:微信)进行登录,如您使用第三方平台的账号登录的,我们将根据您的授权获取该第三方账号下的相关信息(包括:昵称、头像,具体以您的授权内容为准)以及身份验证信息(个人敏感信息)。我们收集这些信息是用于为您提供账号登录服务以及保障您的账号安全,防范安全风险。如您拒绝授权此类信息的,您将无法使用第三方平台的账号登录我们平台,但不影响我们为您提供的其他产品和服务的正常使用。
-        <br />
-        <h2>(二)服务内容展示/浏览/播放/下载/上传功能</h2>
-        我们的产品与/或服务为您提供曲谱、视频、帖子服务内容的展示、浏览、播放、下载和上传功能,在此过程中,我们会根据您使用我们产品与/或服务的具体操作收集您的一些信息,包括如下个人信息:
-        <br />
-        设备信息:包括设备MAC地址、唯一设备识别码、登录IP地址、设备型号、设备名称、设备标识、浏览器类型和设置、语言设置、操作系统和应用程序版本、接入网络的方式、网络质量数据、移动网络信息(包括运营商名称)、产品版本号、设备所在位置相关信息(包括您授权我们获取的地理位置信息)。为了收集上述基本的个人设备信息,我们将会申请访问您的设备信息的权限并根据您的授权获取相关信息。
-        <br />
-        日志信息:当您使用我们的产品与/或服务时,我们会自动收集您的个人上网记录,并作为有关操作日志、服务日志保存,包括您的浏览记录、点赞/分享/评论/互动记录、收藏/关注记录、播放记录、播放时长、访问日期和时间。
-        <br />
-        获取运行中的进程:当您使用我们的产品与/或服务时,我们会不定期获取当前设备正在运行中的进程,以用来判定本App是否在前台运行,从而使App可以正常开展业务逻辑。获取的进程信息,仅用来做App是否在前台运行的判定,不会对该信息进行存储、上传、转发操作。
-        <br />
-        位置信息:附近的人,您通过具有定位功能的移动设备使用我们的服务时,通过GPS或WiFi方式收集的您的地理位置信息;您或其他用户提供的包含您所处地理位置的实时信息,例如您提供的账户信息中包含的您所在地区信息,您或其他人上传的显示您当前或曾经所处地理位置的共享信息,您或其他人共享的照片包含的地理标记信息;您可以通过关闭定位功能,停止对您的地理位置信息的收集。
-        <br />
-        录音信息:当前App的核心功能是通过录音信息分析来获取频率信息,所以我们需要获取您的设备录音(麦克风)的相关权限,以用来完成当前声音频率的识别。如果您不允许App使用录音权限,该App将无法正常使用。App获取录音信息后仅用于声音分析,不会对录音信息进行存储、上传操作。
-        <br />
-        软件安装列表:近期出现不法分子通过反编译、套壳一些非法手段生成盗版的“酷乐秀”App,可能会导致您的个人信息泄露,个人权益受损。获取软件安装列表是为了从安装列表中获取到“酷乐秀”App的安装包名,包体大小,以此来判定app是否已被盗版攻击,以更好的保护您的个人权益和隐私。
-        <br />
-        我们收集这些信息是为了向您提供我们最核心的服务内容展示/播放/下载服务,如您拒绝提供上述信息和/或权限将可能导致您无法使用我们的产品与服务。请您知悉,单独的设备信息、日志信息无法识别您的身份信息。
-        <br />
-        <h2>(三)信息统计、消息推送、分享、支付</h2>
-        为了完成支付、统计、推送、地图,分享功能,我们还集成了其他的SDK,如您在我们平台上使用这类由第三方提供的服务时,您同意将由其直接收集和处理您的信息(如以嵌入代码、插件形式)。目前我们产品中包含的第三方SDK服务以及隐私政策详情请参阅《酷乐秀第三方SDK目录及其隐私政策》。
-        <br />
-        <h2>(四)信息制作、发布、上传、交流互动功能</h2>
-        当您在我们的部分产品与/或服务中使用视频剪辑创作、上传、发布、社区发帖、平台内交流互动、点赞、评论、分享服务/功能时,除注册登录账户之外,您可能会主动提供相关图文/视频内容、互动信息(包括但不限于帖子、点赞/评论/分享/交流互动信息)。我们会自动收集您的上述信息,并展示您的昵称、头像、发布的信息内容。请您知悉,您发布的信息中可能包含他人的个人信息,请您务必取得他人的合法授权,避免非法泄露他人的个人信息。如您不同意我们收集上述信息,您将无法使用我们的信息发布功能,但不影响您使用我们为您提供的其他产品和服务。
-        <br />
-        摄像头权限:当您需要上传个人中心头像跟背景图片或者上传帖子内容里面的图片、视频的时候,拍照功能需要调取您的摄像头权限,如果您拒绝授权,可能会影响您上传个人中心头像以及背景图片,发布帖子,由此带来的不便请您理解。
-        <br />
-        <h2>(五)下单与交付</h2>
-        当您在我们的产品与/或服务中购买商品或服务的,我们需要根据商品或服务类型收集如下部分或全部的个人信息,包括:交易商品或服务信息、收货人信息(收货人姓名、收货地址及其联系电话)(个人敏感信息)、交易金额、下单时间、订单商户、订单编号、订单状态、支付方式、支付账号、支付状态(个人敏感信息),我们收集这些信息是为了帮助您顺利完成交易、保障您的交易安全、查询订单信息、提供客户服务。
-        <br />
-        <h2>(六)客服、其他用户响应功能</h2>
-        当您与我们的客服互动时或使用其他用户响应功能时(包括:在线提交意见反馈、与在线/人工客服沟通、提出我们的产品与/或服务的售后申请、行使您的相关个人信息控制权、其他客户投诉和需求),为了您的账号与系统安全,我们可能需要您先行提供账号信息,并与您之前的个人信息相匹配以验证您的用户身份。在您使用客服或其他用户响应功能时,我们可能还会需要收集您的如下个人敏感信息:联系方式(您与我们联系时使用的电话号码/电子邮箱或您向我们主动提供的其他联系方式)、您与我们的沟通信息(包括文字/图片/音视频/通话记录形式)、与您需求相关联的其他必要信息。我们收集这些信息是为了调查事实与帮助您解决问题,如您拒绝提供可能导致您无法使用我们的客服用户响应机制。
-        <br />
-        <h2>(七)产品安全保障功能</h2>
-        我们需要收集您的一些信息来保障您使用我们的产品与/或服务时的账号与系统安全,并协助提升我们的产品与/服务的安全性和可靠性,以防产生任何危害用户、酷乐秀、社会的行为,包括您的如下个人信息:账号登录地、个人常用设备信息(例如:硬件型号、设备MAC地址、IMEI、IMSI)、登录IP地址、产品版本号、语言模式、浏览记录、网络使用习惯、服务故障信息,以及个人敏感信息:交易信息、会员实名认证信息。我们会根据上述信息来综合判断您账号、账户及交易风险、进行身份验证、客户服务、检测及防范安全事件、诈骗监测、存档和备份用途,并依法采取必要的记录、审计、分析、处置措施,一旦我们检测出存在或疑似存在账号安全风险时,我们会使用相关信息进行安全验证与风险排除,确保我们向您提供的产品和服务的安全性,以用来保障您的权益不受侵害。同时,当发生账号或系统安全问题时,我们会收集这些信息来优化我们的产品和服务。
-        <br />
-        此外,为确保您设备操作环境的安全以及提供我们的产品与/或服务所必需,防止恶意程序和反作弊,我们会在您同意本《隐私政策》后获取您设备上已安装或正在运行的必要的应用/软件列表信息(包括应用/软件来源、应用/软件总体运行情况、崩溃情况、使用频率)。请您知悉,单独的应用/软件列表信息无法识别您的特定身份。
-        <br />
-        例外情形,另外,您充分理解并同意,我们在以下情况下收集、使用您的个人信息无需您的授权同意:
-        <br />
-        与我们履行法律法规规定的义务相关的;
-        <br />
-        与国家安全、国防安全直接相关的;
-        <br />
-        与公共安全、公共卫生、重大公共利益直接相关的;
-        <br />
-        与犯罪侦查、起诉、审判和判决执行直接相关的;
-        <br />
-        出于维护您或其他个人的生命、财产重大合法权益但又很难得到您本人同意的;
-        <br />
-        所收集的信息是您自行向社会公开的或者是从合法公开的渠道(如合法的新闻报道、政府信息公开渠道)中收集到的;
-        <br />
-        根据与您签订和履行相关协议或其他书面文件所必需的;
-        <br />
-        用于维护我们的产品与/或服务的安全稳定运行所必需的,例如发现、处置产品与/或服务的故障
-        <br />
-        有权机关的要求、法律法规规定的其他情形。
-        <br />
-        我们如何使用Cookie和同类技术
-        <br />
-        (一)关于Cookie和同类技术
-        <br />
-        Cookie是包含字符串的小文件,在您登入和使用网站或其他网络内容时发送、存放在您的计算机、移动设备或其他装置内(通常经过加密)。Cookie同类技术是可用于与Cookie类似用途的其他技术,例如:Web
-        Beacon、Proxy、嵌入式脚本。
-        <br />
-        目前,我们主要使用Cookie收集您的个人信息。您知悉并同意,随着技术的发展和我们产品和服务的进一步完善,我们也可能会使用Cookie同类技术
-        <br />
-        (二)我们如何使用Cookie和同类技术
-        <br />
-        在您使用我们的产品与/或服务时,我们可能会使用Cookie和同类技术收集您的一些个人信息,包括:您访问网站的习惯、您的浏览信息、您的登录信息
-        <br />
-        如果您的浏览器允许,您可以通过您的浏览器的设置以管理、(部分/全部)拒绝Cookie与/或同类技术;或删除已经储存在您的计算机、移动设备或其他装置内的Cookie与/或同类技术,从而实现我们无法全部或部分追踪您的个人信息。您如需详细了解如何更改浏览器设置,请具体查看您使用的浏览器的相关设置页面。您理解并知悉:我们的某些产品/服务只能通过使用Cookie或同类技术才可得到实现,如您拒绝使用或删除的,您可能将无法正常使用我们的相关产品与/或服务或无法通过我们的产品与/或服务获得最佳的服务体验,同时也可能会对您的信息保护和账号安全性造成一定的影响。
-        <br />
-        我们如何共享、转让、公开披露您的个人信息
-        <br />
-        除以下情形外,未经您同意,我们以及我们的关联公司不会与任何第三方分享您的个人信息:
-        <br />
-        我们以及我们的关联公司,可能将您的个人信息与我们的关联公司、合作伙伴及第三方服务供应商、承包商及代理(例如代表我们发出电子邮件或推送通知的通讯服务提供商、为我们提供位置数据的地图服务供应商)分享(他们可能并非位于您所在的法域),用作下列用途:
-        <br />
-        向您提供我们的服务;
-        <br />
-        实现“我们可能如何使用信息”部分所述目的;
-        <br />
-        履行我们在《酷乐秀服务协议》或本《隐私政策》中的义务和行使我们的权利;
-        <br />
-        理解、维护和改善我们的服务。
-        <br />
-        如我们或我们的关联公司与任何上述第三方分享您的个人信息,我们将努力确保该第三方在使用您的个人信息时遵守本《隐私政策》及我们要求其遵守的其他适当的保密和安全措施。
-        <br />
-        随着我们业务的持续发展,我们以及我们的关联公司有可能进行合并、收购、资产转让或类似的交易,您的个人信息有可能作为此类交易的一部分而被转移。我们将在转移前通知您。
-        <br />
-        我们或我们的关联公司还可能为以下需要而保留、保存或披露您的个人信息:
-        <br />
-        遵守适用的法律法规;
-        <br />
-        遵守法院命令或其他法律程序的规定;
-        <br />
-        遵守相关政府机关的要求;
-        <br />
-        为遵守适用的法律法规、维护社会公共利益,或保护我们的客户、我们或我们的集团公司、其他用户或雇员的人身和财产安全或合法权益所合理必需的用途。
-        <br />
-        您对个人信息享有的控制权
-        <br />
-        您对我们产品与/或服务中的您的个人信息享有多种方式的控制权,包括:您可以访问、更正/修改、删除您的个人信息,也可以撤回之前作出的对您个人信息的同意,同时您也可以注销您的账号。为便于您行使您的上述控制权,我们在产品的相关功能页面为您提供了操作指引和操作设置,您可以自行进行操作,如您在操作过程中有疑惑或困难的可以通过文末的方式联系我们来进行控制,我们会及时为您处理。
-        <br />
-        (一)访问权
-        <br />
-        您可以在我们的产品与/或服务中查询或访问您的相关个人信息,包括:
-        <br />
-        账号信息:您可以通过相关产品页面随时登录您的个人账号,随时查询或访问您的账号中的个人资料信息,包括:头像、昵称、性别、个性签名。
-        <br />
-        使用信息:您可以通过相关产品页面随时访问您的使用信息,包括:收藏记录、观看历史、离线下载记录、搜索记录、上传内容、订单信息。
-        <br />
-        其他信息:如您在此访问过程中遇到操作问题的或如需获取其他前述无法获知的个人信息内容,您可通过文末提供的方式联系我们,我们将在核实您的身份后在合理期限内向您提供,但法律法规另有规定的或本政策另有约定的除外。
-        <br />
-        (二)更正/修改权
-        <br />
-        您可以在我们的产品与/或服务中更正/修改您的相关个人信息。为便于您行使您的上述权利,我们为您提供了在线自行更正/修改和向我们提出更正/修改申请两种方式。
-        <br />
-        对于您的部分个人信息,我们在产品的相关功能页面为您提供了操作指引和操作设置,您可以直接进行更正/修改,例如:“头像/昵称/性别/个性签名”信息在“手机端APP”中的更正/修改路径为:我的—设置;
-        <br />
-        对于您在行使上述权利过程中遇到的困难,或其他可能未/无法向您提供在线自行更正/修改权限的,
-        经对您的身份进行验证,且更正不影响信息的客观性和准确性的情况下,您有权对错误或不完整的信息作出更正或修改,或在特定情况下,尤其是数据错误时,通过我们公布的反馈与报错措施将您的更正/修改申请提交给我们,要求我们更正或修改您的数据,但法律法规另有规定的除外。但出于安全性和身份识别的考虑,您可能无法修改注册时提交的某些初始注册信息。
-        <br />
-        (三)注销权
-        <br />
-        我们为您提供账号注销的多种途径,您可以通过在线申请注销或联系我们的客服或通过其他我们公示的方式申请注销您的账号。在您注销账号后,您将无法再以此账号登录和使用酷乐秀旗下的相关产品与服务;该账号在酷乐秀的产品与服务使用期间已产生的但未消耗完毕的权益及未来的预期利益全部权益将被清除;该账号下的内容、信息、数据、记录将会被删除或匿名化处理(但法律法规另有规定或监管部门另有要求的除外);同时,账号一旦注销超过一定时间,将无法恢复。
-        <br />
-        (四)提前获知产品与/或服务停止运营权
-        <br />
-        我们将持续为您提供优质服务,若因特殊原因导致我们的部分或全部产品与/或服务被迫停止运营,我们将提前在显著位置或向您发送推送消息或以其他方式通知您,并将停止对您个人信息的收集,同时在超出法律法规规定的必需且最短期限后,我们将会对所持有的您的个人信息进行删除或匿名化处理。
-        <br />
-        (五)帮助反馈权
-        <br />
-        我们为您提供了多种反馈渠道,联系客服,联系电话,帮助反馈。
-        <br />
-        我们如何存储和保护您的个人信息
-        <br />
-        (一)个人信息的存储
-        <br />
-        存储地点:我们依照法律法规的规定,将您的个人信息存储于中华人民共和国境内。目前我们不存在跨境存储您的个人信息或向境外提供个人信息的场景。如需跨境存储或向境外提供个人信息的,我们会单独向您明确告知(包括出境的目的、接收方、使用方式与范围、使用内容、安全保障措施、安全风险)并再次征得您的授权同意,并严格要求接收方按照本《隐私政策》以及法律法规相关要求来处理您的个人信息;
-        <br />
-        存储期限:我们在为提供我们的产品和服务之目的所必需且最短的期间内保留您的个人信息,例如:当您使用我们的注册登录及会员功能时,我们需要收集您的手机号码,且在您提供后并在您使用该功能期间,我们需要持续为您保留您的手机号码,以向您正常提供该功能、保障您的账号和系统安全。在超出上述存储期限后,我们会对您的个人信息进行删除或匿名化处理。但您行使删除权、注销账号的或法律法规另有规定的除外(例如:《电子商务法》规定:商品和服务信息、交易信息保存时间自交易完成之日起不少于三年)。
-        <br />
-        (二)个人信息的保护措施
-        <br />
-        我们一直都极为重视保护用户的个人信息安全,为此我们采用了符合行业标准的安全技术措施及组织和管理措施保护措施以最大程度降低您的信息被泄露、毁损、误用、非授权访问、非授权披露和更改的风险。
-        <br />
-        未成年人保护
-        <br />
-        酷乐秀一直非常注重对未成年人的保护,致力于践行我们的企业社会责任。
-        <br />
-        酷乐秀的绝大部分产品与/或服务主要面向成年人提供,针对这部分产品与/或服务,我们不会主动直接向未成年人收集其个人信息,如未成年人需要使用的,应首先取得其监护人的同意(包括本政策),在监护人同意后和指导下进行使用、提交个人信息;我们希望监护人亦能积极的教育和引导未成年人增强个人信息保护意识和能力,保护未成年人个人信息安全。酷乐秀会严格履行法律规定的未成年人保护义务与责任,我们只会在法律允许、监护人同意或保护未成年人所必要的情况下收集、使用、共享、转让或披露未成年人个人信息,如果我们发现未成年人在未事先获得其监护人同意的情况下使用了我们的产品与/或服务的,我们会尽最大努力与监护人取得联系,并在监护人要求下尽快删除相关未成年人个人信息。
-        <br />
-        本《隐私政策》的更新
-        <br />
-        我们鼓励您在每次使用我们的产品或服务时都查阅我们的《隐私政策》。为了给您提供更好的服务,我们会根据产品的更新情况及法律法规的相关要求更新本《隐私政策》的条款,该更新构成本《隐私政策》的一部分。如该更新造成您在本《隐私政策》下权利的实质减少或重大变更,我们将在本政策生效前通过在显著位置提示或向您发送推送消息或以其他方式通知您,若您继续使用我们的服务,即表示您充分阅读、理解并同意受经修订的《隐私政策》的约束。为保障您的合法权益,我们建议您可以定期在我们平台的设置页面中查看本政策。
-        <br />
-        上述的“重大变更”包括但不限于:
-        <br />
-        我们的服务模式发生重大变化。如处理个人信息的目的、处理的个人信息的类型、个人信息的使用方式;
-        <br />
-        我们在所有权结构、组织架构方面发生重大变化。如业务调整、破产并购引起的所有者变更;
-        <br />
-        个人信息共享、转让或公开披露的主要对象发生变化;
-        <br />
-        您参与个人信息处理方面的权利及其行使方式发生重大变化;
-        <br />
-        我们负责处理个人信息安全的责任部门、联络方式及投诉渠道发生变化时;
-        <br />
-        个人信息安全影响评估报告表明存在高风险时;
-        <br />
-        其他重要的或可能严重影响您的个人权益的情况发生时。
-        <br />
-        如何联系我们
-        <br />
-        (一)如您对本《隐私政策》的执行或使用我们的服务时遇到的与隐私保护相关的事宜有任何问题(包括问题咨询、投诉),我们专门为您提供了多种反馈通道,希望为您提供满意的解决方案:
-        <br />
-        在线客服/其他在线意见反馈通道:您可与我们平台上产品功能页面的在线客服联系或者在线提交意见反馈;
-        <br />
-        人工客服通道:您可以拨打我们的任何一部客服电话与我们联系(400-018-5077);
-        <br />
-        (二)我们会在收到您的意见及建议后,并在验证您的用户身份后的15个工作日内或法律法规规定的期限内尽快向您回复,一般情况下,我们不会因此对您收取服务费。但是,在以下情形下,您理解并知悉,我们将无法响应您的请求:
-        <br />
-        与我们履行法律法规规定的义务相关的;
-        <br />
-        与国家安全、国防安全直接相关的
-        <br />
-        与公共安全、公共卫生、重大公共利益直接相关的
-        <br />
-        与犯罪侦查、起诉和审判有关的;
-        <br />
-        有充分证据表明您存在主观恶意或滥用权利的;
-        <br />
-        出于维护您或其他个人的生命、财产重大权益但又难得到本人授权同意的;
-        <br />
-        响应您的请求将导致您或其他个人、组织的合法权益受到严重损害的;
-        <br />
-        涉及商业秘密的;
-        <br />
-        法律法规规定的其他情形。
-        <br />
-        其他
-        <br />
-        (一)本《隐私政策》的解释及争议解决均应适用中华人民共和国大陆地区法律。如就本政策的签订、履行发生任何争议的,双方应尽量友好协商解决;协商不成时,任何一方均可向被告住所地享有管辖权的人民法院提起诉讼。
-        <br />
-        (二)本《隐私政策》的标题仅为方便及阅读而设,并不影响正文其中任何规定的含义或解释。
-        <br />
-      </div>
+      <>
+        {this.showHeader === '1' && <ColHeader isBack />}
+        <div class={styles.container}>
+          版本更新时间
+          <br />
+          <strong>更新日期:2022年4月18日</strong>
+          <br />
+          <strong>生效日期:2022年4月18日</strong>
+          <br />
+          版本更新提示
+          <br />
+          我们可能适时修订本《隐私政策》的条款,该修订构成本《隐私政策》的一部分。如该修订造成您在本《隐私政策》下权利的实质减少,我们将在修订生效前通过在主页上显著位置提示或向您发送电子邮件或以其他方式通知您。在该种情况下,若您继续使用我们的服务,即表示同意受经修订的本《隐私政策》的约束。
+          <br />
+          引言
+          <br />
+          酷乐秀是由武汉酷乐秀科技有限公司(以下简称“酷乐秀”)为您提供的一款找谱,智能练琴,社交学习平台,问答于一体的综合音乐服务产品。酷乐秀十分重视用户的个人信息和数据。您在使用我们的服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明,在使用我们的服务时,我们如何收集、使用、储存和分享这些信息,以及我们为您提供的访问、更新、控制和保护这些信息的方式。本《隐私政策》与您所使用的酷乐秀服务息息相关,希望您仔细阅读,在需要时,按照本《隐私政策》的指引,作出您认为适当的选择。本《隐私政策》中涉及的相关技术词汇,我们尽量以简明扼要的表述,并提供进一步说明的链接,以便您的理解。
+          <br />
+          您使用或继续使用我们的服务,即意味着同意我们按照本《隐私政策》收集、使用、储存和分享您的相关信息。
+          <br />
+          我们如何收集的信息
+          <br />
+          您在使用我们的产品与/或服务时,我们需要/可能需要收集和使用您的一些个人信息,我们收集和使用的您的个人信息类型包括两种: 第一种:我们产品与/或服务的核心业务功能所必需的信息:此类信息为产品与/或服务正常运行的必备信息,您须授权我们收集。如您拒绝提供,您将无法正常使用我们的产品与/或服务 第二种:我们产品与/或服务的附加业务功能可能需要收集的信息:此信息为非核心业务功能所需的信息,您可以选择是否授权我们收集。如您拒绝提供,将导致附加业务功能无法实现或无法达到我们拟达到的效果,但不影响您对核心业务功能的正常使用。
+          <br />
+          <h2>(一)账号注册/登录功能</h2>
+          个人信息是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。
+          <br />
+          1. 用户账户信息:
+          当您使用账号注册功能时,我们会收集由您主动提供给我们的一些单独或者结合识别您实名身份的信息,包括:手机号码、验证码匹配结果,并创建密码。您的密码将以加密形式进行自动存储、传输、验证,我们不会以明文方式存储、传输、验证您的密码。您在保管、输入、使用您的密码时,应当对物理环境、电子环境审慎评估,以防密码外泄。我们收集这些信息是用以完成注册程序、为您持续稳定提供专属于注册用户的产品与/或服务,并保护您的账号安全。您应知悉,手机号码和验证码匹配结果属于您的个人敏感信息,我们收集该类信息是为了满足相关法律法规的要求,如您拒绝提供可能导致您无法使用我们的此功能,请您谨慎考虑后再提供。
+          <br />
+          需要说明的是,我们的一些产品支持您使用第三方平台的账号(例如:微信)进行登录,如您使用第三方平台的账号登录的,我们将根据您的授权获取该第三方账号下的相关信息(包括:昵称、头像,具体以您的授权内容为准)以及身份验证信息(个人敏感信息)。我们收集这些信息是用于为您提供账号登录服务以及保障您的账号安全,防范安全风险。如您拒绝授权此类信息的,您将无法使用第三方平台的账号登录我们平台,但不影响我们为您提供的其他产品和服务的正常使用。
+          <br />
+          <h2>(二)服务内容展示/浏览/播放/下载/上传功能</h2>
+          我们的产品与/或服务为您提供曲谱、视频、帖子服务内容的展示、浏览、播放、下载和上传功能,在此过程中,我们会根据您使用我们产品与/或服务的具体操作收集您的一些信息,包括如下个人信息:
+          <br />
+          设备信息:包括设备MAC地址、唯一设备识别码、登录IP地址、设备型号、设备名称、设备标识、浏览器类型和设置、语言设置、操作系统和应用程序版本、接入网络的方式、网络质量数据、移动网络信息(包括运营商名称)、产品版本号、设备所在位置相关信息(包括您授权我们获取的地理位置信息)。为了收集上述基本的个人设备信息,我们将会申请访问您的设备信息的权限并根据您的授权获取相关信息。
+          <br />
+          日志信息:当您使用我们的产品与/或服务时,我们会自动收集您的个人上网记录,并作为有关操作日志、服务日志保存,包括您的浏览记录、点赞/分享/评论/互动记录、收藏/关注记录、播放记录、播放时长、访问日期和时间。
+          <br />
+          获取运行中的进程:当您使用我们的产品与/或服务时,我们会不定期获取当前设备正在运行中的进程,以用来判定本App是否在前台运行,从而使App可以正常开展业务逻辑。获取的进程信息,仅用来做App是否在前台运行的判定,不会对该信息进行存储、上传、转发操作。
+          <br />
+          位置信息:附近的人,您通过具有定位功能的移动设备使用我们的服务时,通过GPS或WiFi方式收集的您的地理位置信息;您或其他用户提供的包含您所处地理位置的实时信息,例如您提供的账户信息中包含的您所在地区信息,您或其他人上传的显示您当前或曾经所处地理位置的共享信息,您或其他人共享的照片包含的地理标记信息;您可以通过关闭定位功能,停止对您的地理位置信息的收集。
+          <br />
+          录音信息:当前App的核心功能是通过录音信息分析来获取频率信息,所以我们需要获取您的设备录音(麦克风)的相关权限,以用来完成当前声音频率的识别。如果您不允许App使用录音权限,该App将无法正常使用。App获取录音信息后仅用于声音分析,不会对录音信息进行存储、上传操作。
+          <br />
+          软件安装列表:近期出现不法分子通过反编译、套壳一些非法手段生成盗版的“酷乐秀”App,可能会导致您的个人信息泄露,个人权益受损。获取软件安装列表是为了从安装列表中获取到“酷乐秀”App的安装包名,包体大小,以此来判定app是否已被盗版攻击,以更好的保护您的个人权益和隐私。
+          <br />
+          我们收集这些信息是为了向您提供我们最核心的服务内容展示/播放/下载服务,如您拒绝提供上述信息和/或权限将可能导致您无法使用我们的产品与服务。请您知悉,单独的设备信息、日志信息无法识别您的身份信息。
+          <br />
+          <h2>(三)信息统计、消息推送、分享、支付</h2>
+          为了完成支付、统计、推送、地图,分享功能,我们还集成了其他的SDK,如您在我们平台上使用这类由第三方提供的服务时,您同意将由其直接收集和处理您的信息(如以嵌入代码、插件形式)。目前我们产品中包含的第三方SDK服务以及隐私政策详情请参阅《酷乐秀第三方SDK目录及其隐私政策》。
+          <br />
+          <h2>(四)信息制作、发布、上传、交流互动功能</h2>
+          当您在我们的部分产品与/或服务中使用视频剪辑创作、上传、发布、社区发帖、平台内交流互动、点赞、评论、分享服务/功能时,除注册登录账户之外,您可能会主动提供相关图文/视频内容、互动信息(包括但不限于帖子、点赞/评论/分享/交流互动信息)。我们会自动收集您的上述信息,并展示您的昵称、头像、发布的信息内容。请您知悉,您发布的信息中可能包含他人的个人信息,请您务必取得他人的合法授权,避免非法泄露他人的个人信息。如您不同意我们收集上述信息,您将无法使用我们的信息发布功能,但不影响您使用我们为您提供的其他产品和服务。
+          <br />
+          摄像头权限:当您需要上传个人中心头像跟背景图片或者上传帖子内容里面的图片、视频的时候,拍照功能需要调取您的摄像头权限,如果您拒绝授权,可能会影响您上传个人中心头像以及背景图片,发布帖子,由此带来的不便请您理解。
+          <br />
+          <h2>(五)下单与交付</h2>
+          当您在我们的产品与/或服务中购买商品或服务的,我们需要根据商品或服务类型收集如下部分或全部的个人信息,包括:交易商品或服务信息、收货人信息(收货人姓名、收货地址及其联系电话)(个人敏感信息)、交易金额、下单时间、订单商户、订单编号、订单状态、支付方式、支付账号、支付状态(个人敏感信息),我们收集这些信息是为了帮助您顺利完成交易、保障您的交易安全、查询订单信息、提供客户服务。
+          <br />
+          <h2>(六)客服、其他用户响应功能</h2>
+          当您与我们的客服互动时或使用其他用户响应功能时(包括:在线提交意见反馈、与在线/人工客服沟通、提出我们的产品与/或服务的售后申请、行使您的相关个人信息控制权、其他客户投诉和需求),为了您的账号与系统安全,我们可能需要您先行提供账号信息,并与您之前的个人信息相匹配以验证您的用户身份。在您使用客服或其他用户响应功能时,我们可能还会需要收集您的如下个人敏感信息:联系方式(您与我们联系时使用的电话号码/电子邮箱或您向我们主动提供的其他联系方式)、您与我们的沟通信息(包括文字/图片/音视频/通话记录形式)、与您需求相关联的其他必要信息。我们收集这些信息是为了调查事实与帮助您解决问题,如您拒绝提供可能导致您无法使用我们的客服用户响应机制。
+          <br />
+          <h2>(七)产品安全保障功能</h2>
+          我们需要收集您的一些信息来保障您使用我们的产品与/或服务时的账号与系统安全,并协助提升我们的产品与/服务的安全性和可靠性,以防产生任何危害用户、酷乐秀、社会的行为,包括您的如下个人信息:账号登录地、个人常用设备信息(例如:硬件型号、设备MAC地址、IMEI、IMSI)、登录IP地址、产品版本号、语言模式、浏览记录、网络使用习惯、服务故障信息,以及个人敏感信息:交易信息、会员实名认证信息。我们会根据上述信息来综合判断您账号、账户及交易风险、进行身份验证、客户服务、检测及防范安全事件、诈骗监测、存档和备份用途,并依法采取必要的记录、审计、分析、处置措施,一旦我们检测出存在或疑似存在账号安全风险时,我们会使用相关信息进行安全验证与风险排除,确保我们向您提供的产品和服务的安全性,以用来保障您的权益不受侵害。同时,当发生账号或系统安全问题时,我们会收集这些信息来优化我们的产品和服务。
+          <br />
+          此外,为确保您设备操作环境的安全以及提供我们的产品与/或服务所必需,防止恶意程序和反作弊,我们会在您同意本《隐私政策》后获取您设备上已安装或正在运行的必要的应用/软件列表信息(包括应用/软件来源、应用/软件总体运行情况、崩溃情况、使用频率)。请您知悉,单独的应用/软件列表信息无法识别您的特定身份。
+          <br />
+          例外情形,另外,您充分理解并同意,我们在以下情况下收集、使用您的个人信息无需您的授权同意:
+          <br />
+          与我们履行法律法规规定的义务相关的;
+          <br />
+          与国家安全、国防安全直接相关的;
+          <br />
+          与公共安全、公共卫生、重大公共利益直接相关的;
+          <br />
+          与犯罪侦查、起诉、审判和判决执行直接相关的;
+          <br />
+          出于维护您或其他个人的生命、财产重大合法权益但又很难得到您本人同意的;
+          <br />
+          所收集的信息是您自行向社会公开的或者是从合法公开的渠道(如合法的新闻报道、政府信息公开渠道)中收集到的;
+          <br />
+          根据与您签订和履行相关协议或其他书面文件所必需的;
+          <br />
+          用于维护我们的产品与/或服务的安全稳定运行所必需的,例如发现、处置产品与/或服务的故障
+          <br />
+          有权机关的要求、法律法规规定的其他情形。
+          <br />
+          我们如何使用Cookie和同类技术
+          <br />
+          (一)关于Cookie和同类技术
+          <br />
+          Cookie是包含字符串的小文件,在您登入和使用网站或其他网络内容时发送、存放在您的计算机、移动设备或其他装置内(通常经过加密)。Cookie同类技术是可用于与Cookie类似用途的其他技术,例如:Web
+          Beacon、Proxy、嵌入式脚本。
+          <br />
+          目前,我们主要使用Cookie收集您的个人信息。您知悉并同意,随着技术的发展和我们产品和服务的进一步完善,我们也可能会使用Cookie同类技术
+          <br />
+          (二)我们如何使用Cookie和同类技术
+          <br />
+          在您使用我们的产品与/或服务时,我们可能会使用Cookie和同类技术收集您的一些个人信息,包括:您访问网站的习惯、您的浏览信息、您的登录信息
+          <br />
+          如果您的浏览器允许,您可以通过您的浏览器的设置以管理、(部分/全部)拒绝Cookie与/或同类技术;或删除已经储存在您的计算机、移动设备或其他装置内的Cookie与/或同类技术,从而实现我们无法全部或部分追踪您的个人信息。您如需详细了解如何更改浏览器设置,请具体查看您使用的浏览器的相关设置页面。您理解并知悉:我们的某些产品/服务只能通过使用Cookie或同类技术才可得到实现,如您拒绝使用或删除的,您可能将无法正常使用我们的相关产品与/或服务或无法通过我们的产品与/或服务获得最佳的服务体验,同时也可能会对您的信息保护和账号安全性造成一定的影响。
+          <br />
+          我们如何共享、转让、公开披露您的个人信息
+          <br />
+          除以下情形外,未经您同意,我们以及我们的关联公司不会与任何第三方分享您的个人信息:
+          <br />
+          我们以及我们的关联公司,可能将您的个人信息与我们的关联公司、合作伙伴及第三方服务供应商、承包商及代理(例如代表我们发出电子邮件或推送通知的通讯服务提供商、为我们提供位置数据的地图服务供应商)分享(他们可能并非位于您所在的法域),用作下列用途:
+          <br />
+          向您提供我们的服务;
+          <br />
+          实现“我们可能如何使用信息”部分所述目的;
+          <br />
+          履行我们在《酷乐秀服务协议》或本《隐私政策》中的义务和行使我们的权利;
+          <br />
+          理解、维护和改善我们的服务。
+          <br />
+          如我们或我们的关联公司与任何上述第三方分享您的个人信息,我们将努力确保该第三方在使用您的个人信息时遵守本《隐私政策》及我们要求其遵守的其他适当的保密和安全措施。
+          <br />
+          随着我们业务的持续发展,我们以及我们的关联公司有可能进行合并、收购、资产转让或类似的交易,您的个人信息有可能作为此类交易的一部分而被转移。我们将在转移前通知您。
+          <br />
+          我们或我们的关联公司还可能为以下需要而保留、保存或披露您的个人信息:
+          <br />
+          遵守适用的法律法规;
+          <br />
+          遵守法院命令或其他法律程序的规定;
+          <br />
+          遵守相关政府机关的要求;
+          <br />
+          为遵守适用的法律法规、维护社会公共利益,或保护我们的客户、我们或我们的集团公司、其他用户或雇员的人身和财产安全或合法权益所合理必需的用途。
+          <br />
+          您对个人信息享有的控制权
+          <br />
+          您对我们产品与/或服务中的您的个人信息享有多种方式的控制权,包括:您可以访问、更正/修改、删除您的个人信息,也可以撤回之前作出的对您个人信息的同意,同时您也可以注销您的账号。为便于您行使您的上述控制权,我们在产品的相关功能页面为您提供了操作指引和操作设置,您可以自行进行操作,如您在操作过程中有疑惑或困难的可以通过文末的方式联系我们来进行控制,我们会及时为您处理。
+          <br />
+          (一)访问权
+          <br />
+          您可以在我们的产品与/或服务中查询或访问您的相关个人信息,包括:
+          <br />
+          账号信息:您可以通过相关产品页面随时登录您的个人账号,随时查询或访问您的账号中的个人资料信息,包括:头像、昵称、性别、个性签名。
+          <br />
+          使用信息:您可以通过相关产品页面随时访问您的使用信息,包括:收藏记录、观看历史、离线下载记录、搜索记录、上传内容、订单信息。
+          <br />
+          其他信息:如您在此访问过程中遇到操作问题的或如需获取其他前述无法获知的个人信息内容,您可通过文末提供的方式联系我们,我们将在核实您的身份后在合理期限内向您提供,但法律法规另有规定的或本政策另有约定的除外。
+          <br />
+          (二)更正/修改权
+          <br />
+          您可以在我们的产品与/或服务中更正/修改您的相关个人信息。为便于您行使您的上述权利,我们为您提供了在线自行更正/修改和向我们提出更正/修改申请两种方式。
+          <br />
+          对于您的部分个人信息,我们在产品的相关功能页面为您提供了操作指引和操作设置,您可以直接进行更正/修改,例如:“头像/昵称/性别/个性签名”信息在“手机端APP”中的更正/修改路径为:我的—设置;
+          <br />
+          对于您在行使上述权利过程中遇到的困难,或其他可能未/无法向您提供在线自行更正/修改权限的,
+          经对您的身份进行验证,且更正不影响信息的客观性和准确性的情况下,您有权对错误或不完整的信息作出更正或修改,或在特定情况下,尤其是数据错误时,通过我们公布的反馈与报错措施将您的更正/修改申请提交给我们,要求我们更正或修改您的数据,但法律法规另有规定的除外。但出于安全性和身份识别的考虑,您可能无法修改注册时提交的某些初始注册信息。
+          <br />
+          (三)注销权
+          <br />
+          我们为您提供账号注销的多种途径,您可以通过在线申请注销或联系我们的客服或通过其他我们公示的方式申请注销您的账号。在您注销账号后,您将无法再以此账号登录和使用酷乐秀旗下的相关产品与服务;该账号在酷乐秀的产品与服务使用期间已产生的但未消耗完毕的权益及未来的预期利益全部权益将被清除;该账号下的内容、信息、数据、记录将会被删除或匿名化处理(但法律法规另有规定或监管部门另有要求的除外);同时,账号一旦注销超过一定时间,将无法恢复。
+          <br />
+          (四)提前获知产品与/或服务停止运营权
+          <br />
+          我们将持续为您提供优质服务,若因特殊原因导致我们的部分或全部产品与/或服务被迫停止运营,我们将提前在显著位置或向您发送推送消息或以其他方式通知您,并将停止对您个人信息的收集,同时在超出法律法规规定的必需且最短期限后,我们将会对所持有的您的个人信息进行删除或匿名化处理。
+          <br />
+          (五)帮助反馈权
+          <br />
+          我们为您提供了多种反馈渠道,联系客服,联系电话,帮助反馈。
+          <br />
+          我们如何存储和保护您的个人信息
+          <br />
+          (一)个人信息的存储
+          <br />
+          存储地点:我们依照法律法规的规定,将您的个人信息存储于中华人民共和国境内。目前我们不存在跨境存储您的个人信息或向境外提供个人信息的场景。如需跨境存储或向境外提供个人信息的,我们会单独向您明确告知(包括出境的目的、接收方、使用方式与范围、使用内容、安全保障措施、安全风险)并再次征得您的授权同意,并严格要求接收方按照本《隐私政策》以及法律法规相关要求来处理您的个人信息;
+          <br />
+          存储期限:我们在为提供我们的产品和服务之目的所必需且最短的期间内保留您的个人信息,例如:当您使用我们的注册登录及会员功能时,我们需要收集您的手机号码,且在您提供后并在您使用该功能期间,我们需要持续为您保留您的手机号码,以向您正常提供该功能、保障您的账号和系统安全。在超出上述存储期限后,我们会对您的个人信息进行删除或匿名化处理。但您行使删除权、注销账号的或法律法规另有规定的除外(例如:《电子商务法》规定:商品和服务信息、交易信息保存时间自交易完成之日起不少于三年)。
+          <br />
+          (二)个人信息的保护措施
+          <br />
+          我们一直都极为重视保护用户的个人信息安全,为此我们采用了符合行业标准的安全技术措施及组织和管理措施保护措施以最大程度降低您的信息被泄露、毁损、误用、非授权访问、非授权披露和更改的风险。
+          <br />
+          未成年人保护
+          <br />
+          酷乐秀一直非常注重对未成年人的保护,致力于践行我们的企业社会责任。
+          <br />
+          酷乐秀的绝大部分产品与/或服务主要面向成年人提供,针对这部分产品与/或服务,我们不会主动直接向未成年人收集其个人信息,如未成年人需要使用的,应首先取得其监护人的同意(包括本政策),在监护人同意后和指导下进行使用、提交个人信息;我们希望监护人亦能积极的教育和引导未成年人增强个人信息保护意识和能力,保护未成年人个人信息安全。酷乐秀会严格履行法律规定的未成年人保护义务与责任,我们只会在法律允许、监护人同意或保护未成年人所必要的情况下收集、使用、共享、转让或披露未成年人个人信息,如果我们发现未成年人在未事先获得其监护人同意的情况下使用了我们的产品与/或服务的,我们会尽最大努力与监护人取得联系,并在监护人要求下尽快删除相关未成年人个人信息。
+          <br />
+          本《隐私政策》的更新
+          <br />
+          我们鼓励您在每次使用我们的产品或服务时都查阅我们的《隐私政策》。为了给您提供更好的服务,我们会根据产品的更新情况及法律法规的相关要求更新本《隐私政策》的条款,该更新构成本《隐私政策》的一部分。如该更新造成您在本《隐私政策》下权利的实质减少或重大变更,我们将在本政策生效前通过在显著位置提示或向您发送推送消息或以其他方式通知您,若您继续使用我们的服务,即表示您充分阅读、理解并同意受经修订的《隐私政策》的约束。为保障您的合法权益,我们建议您可以定期在我们平台的设置页面中查看本政策。
+          <br />
+          上述的“重大变更”包括但不限于:
+          <br />
+          我们的服务模式发生重大变化。如处理个人信息的目的、处理的个人信息的类型、个人信息的使用方式;
+          <br />
+          我们在所有权结构、组织架构方面发生重大变化。如业务调整、破产并购引起的所有者变更;
+          <br />
+          个人信息共享、转让或公开披露的主要对象发生变化;
+          <br />
+          您参与个人信息处理方面的权利及其行使方式发生重大变化;
+          <br />
+          我们负责处理个人信息安全的责任部门、联络方式及投诉渠道发生变化时;
+          <br />
+          个人信息安全影响评估报告表明存在高风险时;
+          <br />
+          其他重要的或可能严重影响您的个人权益的情况发生时。
+          <br />
+          如何联系我们
+          <br />
+          (一)如您对本《隐私政策》的执行或使用我们的服务时遇到的与隐私保护相关的事宜有任何问题(包括问题咨询、投诉),我们专门为您提供了多种反馈通道,希望为您提供满意的解决方案:
+          <br />
+          在线客服/其他在线意见反馈通道:您可与我们平台上产品功能页面的在线客服联系或者在线提交意见反馈;
+          <br />
+          人工客服通道:您可以拨打我们的任何一部客服电话与我们联系(400-018-5077);
+          <br />
+          (二)我们会在收到您的意见及建议后,并在验证您的用户身份后的15个工作日内或法律法规规定的期限内尽快向您回复,一般情况下,我们不会因此对您收取服务费。但是,在以下情形下,您理解并知悉,我们将无法响应您的请求:
+          <br />
+          与我们履行法律法规规定的义务相关的;
+          <br />
+          与国家安全、国防安全直接相关的
+          <br />
+          与公共安全、公共卫生、重大公共利益直接相关的
+          <br />
+          与犯罪侦查、起诉和审判有关的;
+          <br />
+          有充分证据表明您存在主观恶意或滥用权利的;
+          <br />
+          出于维护您或其他个人的生命、财产重大权益但又难得到本人授权同意的;
+          <br />
+          响应您的请求将导致您或其他个人、组织的合法权益受到严重损害的;
+          <br />
+          涉及商业秘密的;
+          <br />
+          法律法规规定的其他情形。
+          <br />
+          其他
+          <br />
+          (一)本《隐私政策》的解释及争议解决均应适用中华人民共和国大陆地区法律。如就本政策的签订、履行发生任何争议的,双方应尽量友好协商解决;协商不成时,任何一方均可向被告住所地享有管辖权的人民法院提起诉讼。
+          <br />
+          (二)本《隐私政策》的标题仅为方便及阅读而设,并不影响正文其中任何规定的含义或解释。
+          <br />
+        </div>
+      </>
     )
   }
 })

+ 197 - 187
src/views/protocol/register.tsx

@@ -1,197 +1,207 @@
+import ColHeader from '@/components/col-header'
 import { defineComponent } from 'vue'
 import styles from './index.module.less'
 
 export default defineComponent({
   name: 'register',
+  data() {
+    const query = this.$route.query
+    return {
+      showHeader: query.showHeader || '0'
+    }
+  },
   render() {
     return (
-      <div class={styles.container}>
-        欢迎来到酷乐秀,酷乐秀 隶属于武汉酷乐秀科技有限公司。
-        <br />
-        请您仔细阅读以下条款,如果您对本协议的任何条款表示异议,您可以选择不进入酷乐秀。当您注册成功,无论是进入酷乐秀,还是在酷乐秀上发布任何内容(即「内容」),均意味着您(即「用户」)完全接受本协议项下的全部条款。
-        <br />
-        使用协议
-        <br />
-        用户注册成功后,酷乐秀将给予每个用户一个用户帐号及相应的密码,该用户帐号和密码由用户负责保管;用户应当对以其用户帐号进行的所有活动和事件负法律责任。
-        <br />
-        用户须对在酷乐秀的注册信息的真实性、合法性、有效性承担全部责任,用户不得冒充他人;不得利用他人的名义发布任何信息;不得恶意使用注册帐号导致其他用户误认;否则酷乐秀有权立即停止提供服务,收回其帐号并由用户独自承担由此而产生的一切法律责任。
-        <br />
-        用户直接或通过各类方式(如 RSS 源和站外 API
-        引用等)间接使用酷乐秀服务和数据的行为,都将被视作已无条件接受本协议全部内容;若用户对本协议的任何条款存在异议,请停止使用酷乐秀所提供的全部服务。
-        <br />
-        酷乐秀是一个信息分享、传播及获取的平台,用户通过酷乐秀发表的信息为公开的信息,其他第三方均可以通过酷乐秀获取用户发表的信息,用户对任何信息的发表即认可该信息为公开的信息,并单独对此行为承担法律责任;任何用户不愿被其他第三人获知的信息都不应该在酷乐秀上进行发表。
-        <br />
-        为了更好地维护乐谱,酷乐秀会选择性地将用户上传的高质量乐谱设置成VIP。
-        <br />
-        您的上传行为代表您同意您上传的作品在本站内的公开发布与传播并授权本站使用您上传的作品。任何第三方的转载行为与本站无关。
-        <br />
-        酷乐秀如果认为您上传的内容不适当,有权进行删除或修改,甚至对情节严重者进行封号处理。
-        <br />
-        酷乐秀会员账号、曲谱、教程视频等资源均不能二次转售,否则将对实施以上行为的账号终身封号,且将通过法律途径追究其责任,追偿酷乐秀的损失。用户账号被封后,酷乐秀会提供1次人工申诉机会,请按照要求填写相关的信息,完成备案后,如符合条件可以解封;如不符合条件,我们将会通过邮件告知您。
-        <br />
-        酷乐秀VIP会员每天的最大的打印/保存pdf数量为15,不同天打印的总数不设上限。请正常下载使用本站乐谱,如非正常下载(不符合正常用户的行为),潜在有可能用于不正当途径,您的账号可能会被封号。酷乐秀的乐谱中均包含有账户信息,请仅供自己使用,如发布于互联网,或被用于转售曲谱,您的账号同样存在封号的风险;如对酷乐秀造成损害,酷乐秀同样会利用法律武器进行维权。
-        <br />
-        酷乐秀账号每年可累计在5台设备上登录使用,每天不得超过其中的4台。如果违规将由系统判定,给予警告和处罚。提醒用户账号切勿共享,因共享一定会违规。首次违规锁定7天VIP,第2次违规锁定1个月,依次3个月,6个月,12个月,永久。(2021年7月8日新增)
-        <br />
-        用户承诺不得以任何方式利用酷乐秀直接或间接从事违反中国法律、以及社会公德的行为,酷乐秀有权对违反上述承诺的内容予以删除。用户账号被封后,酷乐秀会提供1次人工申诉机会,请按照要求填写相关的信息,完成备案后,如符合条件可以解封;如不符合条件,我们将会通过邮件告知您。
-        <br />
-        用户不得利用酷乐秀服务制作、上载、复制、发布、传播或者转载如下内容:
-        <br />
-        反对宪法所确定的基本原则的;
-        <br />
-        危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;
-        <br />
-        损害国家荣誉和利益的;
-        <br />
-        煽动民族仇恨、民族歧视,破坏民族团结的;
-        <br />
-        破坏国家宗教政策,宣扬邪教和封建迷信的;
-        <br />
-        散布谣言,扰乱社会秩序,破坏社会稳定的;
-        <br />
-        散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;
-        <br />
-        侮辱或者诽谤他人,侵害他人合法权益的;
-        <br />
-        含有法律、行政法规禁止的其他内容的信息。
-        <br />
-        禁止使用国家领导人、亲属名字作为昵称,含隐晦名称等。
-        <br />
-        包含以上情况的内容不得出现在:用户名、昵称、个人签名、帖子中、评论中、回复中,以及任何形式可能出现在酷乐秀的内容中。
-        当您在酷乐秀发表内容前,您已做过实名认证,如您的行为包含违规的内容,我们将及时向国家相关部门上报您的违规行为。
-        当您出现任何违规行为,我们将有权删除您的账号和相关的评论内容,并且终身不可恢复。
-        <br />
-        本站不是知识产权专业机构,不具备审查、判断作品是否侵权的能力,也不具备对用户所发布内容的合法性、真实性及其品质等进行审查的能力。任何用户应当为自己上传文件的行为独立完全承担法律责任。因您的个人行为所产生的一切争议和纠纷以及诉讼,与本站无关。
-        <br />
-        终身VIP时效指的是产品的生命周期结束,停止运营为止,含主动和被动原因。主动原因为:公司因故主动宣布停止运营;被动原因为:公司倒闭、破产,APP程序下架,市场政策调整,不可抗力等原因,造成无法继续运营。当停止运营时,终身VIP服务自动终止,公司将按照酷乐秀有权对用户使用酷乐秀的情况进行审查和监督,如用户在使用酷乐秀时违反任何上述规定,酷乐秀或其授权的人有权要求用户改正或直接采取一切必要的措施(包括但不限于更改或删除用户张贴的内容、暂停或终止用户使用酷乐秀的权利)以减轻用户不当行为造成的影响。
-        <br />
-        在您充值酷乐秀会员、购买课程等服务前,请您充分确认后再充值,商品一旦售出后,除重复支付、7天内质量问题(需有证据证明)可退全款外,其他情况支持退残值,计算方法,见第14条。
-        <br />
-        因本平台app需通过不断升级来适配苹果、安卓、微软等操作系统的升级,可能会导致较低版本的设备不再支持安装、运行,属于正常合理的情况。如果出现这种情况,表示您的硬件需要升级了。此种情况下,充值的VIP仍然可用,同时支持残值部分办理退款。(残值计算方法,见第14条)
-        <br />
-        侵权举报
-        <br />
-        处理原则
-        <br />
-        酷乐秀作为知识讨论社区,高度重视自由表达和企业正当权利的平衡。依照法律规定删除违法信息是酷乐秀社区的法定义务,酷乐秀社区亦未与任何中介机构合作开展此项业务。
-        <br />
-        受理范围
-        <br />
-        受理酷乐秀社区内侵犯企业或个人合法权益的侵权举报,包括但不限于涉及个人隐私、造谣与诽谤、商业侵权。
-        <br />
-        涉及个人隐私:发布内容中直接涉及身份信息,如个人姓名、家庭住址、身份证号码、工作单位、私人电话等详细个人隐私;
-        <br />
-        造谣、诽谤:发布内容中指名道姓(包括自然人和企业)的直接谩骂、侮辱、虚构中伤、恶意诽谤等;
-        <br />
-        商业侵权:泄露企业商业机密及其他根据保密协议不能公开讨论的内容。
-        <br />
-        权利人
-        <br />
-        权利人对涉嫌侵权内容拥有商标权、著作权或其他依法可以行使权利的权属证明;如果举报人非权利人,请举报人提供代表企业进行举报的书面授权证明。
-        <br />
-        充分、明确地描述侵犯了权利人合法权益的内容,提供涉嫌侵权内容在酷乐秀上的具体页面地址,指明涉嫌侵权内容中的哪些内容侵犯了上述列明的权利人的合法权益;
-        <br />
-        权利人具体的联络信息,包括姓名、身份证或护照复印件(对自然人)、单位登记证明复印件(对单位)、通信地址、电话号码、传真和电子邮件;
-        <br />
-        在侵权举报中加入如下关于举报内容真实性的声明:
-        <br />
-        我本人为所举报内容的合法权利人;
-        <br />
-        我举报的发布在酷乐秀社区中的内容侵犯了本人相应的合法权益;
-        <br />
-        如果本侵权举报内容不完全属实,本人将承担由此产生的一切法律责任。
-        <br />
-        处理流程
-        <br />
-        出于网络社区的监督属性,并非所有申请都必须受理。酷乐秀自收到举报邮件七个工作日内处理完毕并给出回复。处理期间,不提供任何电话、邮件及其他方式的查询服务。
-        <br />
-        出现酷乐秀已经删除或处理的内容,但是百度、谷歌等搜索引擎依然可以搜索到的现象,是因为百度、谷歌等搜索引擎自带缓存,此类问题酷乐秀无权也无法处理,因此相关申请不予受理。
-        <br />
-        此为酷乐秀社区唯一的官方的侵权投诉渠道,暂不提供其他方式处理此业务。
-        <br />
-        用户在酷乐秀中的商业行为引发的法律纠纷,由交易双方自行处理,与酷乐秀无关。
-        <br />
-        知识产权
-        <br />
-        酷乐秀是一个信息获取、分享及传播的平台,我们尊重和鼓励酷乐秀用户创作的内容,认识到保护知识产权对酷乐秀生存与发展的重要性,承诺将保护知识产权作为酷乐秀运营的基本原则之一。
-        <br />
-        用户在酷乐秀上发布的原创视频,帖子,发表的原创回答和评论,著作权均归用户本人所有。用户可授权第三方以任何方式使用,不需要得到酷乐秀的同意。
-        <br />
-        用户不得修改、改编、翻译酷乐秀服务所使用的软件、技术、材料等,或者创作与之相关的派生作品,不得通过反向工程、反编译、反汇编或其他类似行为获得其的源代码,否则由此引起的一切法律后果由用户负责,酷乐秀将依法追究违约方的法律责任。
-        <br />
-        用户不得恶意修改、复制、传播酷乐秀服务所使用的软件、技术、材料等。否则,用户自行承担因此而造成对其他人的损害,或者造成对酷乐秀公司形象损害,要承担相应的法律责任。
-        <br />
-        用户不得擅自删除、掩盖或更改酷乐秀的版权声明、商标或其它权利声明。酷乐秀平台所有设计图样以及其他图样、产品及服务名称,均为酷乐秀所享有的商标、标识。任何人不得使用、复制或用作其他用途。
-        <br />
-        为了促进知识的分享和传播,用户将其在酷乐秀上发表的全部内容,授予酷乐秀免费的、不可撤销的、非精编使用许可,酷乐秀有权将该内容用于酷乐秀各种形态的产品和服务上,包括但不限于网站以及发表的应用或其他互联网产品。
-        <br />
-        在酷乐秀上传或发表的内容,用户应保证其为著作权人或已取得合法授权,并且该内容不会侵犯任何第三方的合法权益。如果第三方提出关于著作权的异议,酷乐秀有权根据实际情况删除相关的内容有权追究用户的法律责任,给酷乐秀或任何第三方造成损失的,用户应负责全额赔偿。
-        <br />
-        如果任何第三方侵犯了酷乐秀用户相关的权利,用户同意授权酷乐秀或其指定的代理人代表酷乐秀自身或用户对该第三方提出警告、投诉、发起行政执法、诉讼、进行上诉,或谈判和解,并且用户同意在酷乐秀认为必要的情况下参与共同维权。
-        <br />
-        酷乐秀有权但无义务对用户发布的内容进行审核,有权根据相关证据结合《侵权责任法》、《信息网络传播权保护条例》等法律法规及酷乐秀社区指导原则对侵权信息进行处理。
-        <br />
-        酷乐秀对其自制内容和其他通过授权取得的独占内容享有完全知识产权,未经酷乐秀许可,任何单位和个人不得私自转载、传播和提供观看服务或者有其他侵犯酷乐秀知识产权的行为。否则,酷乐秀将追究侵权行为人的法律责任。
-        <br />
-        酷乐秀所有和享有的知识产权,不因用户的任何使用行为而发生权利转移。
-        <br />
-        免责申明
-        <br />
-        酷乐秀对于任何包含、经由或连接、下载或从任何与有关本网络服务所获得的任何内容、信息不声明或保证其正确性或可靠性;
-        <br />
-        用户在酷乐秀发表的内容仅表明其个人的立场和观点,并不代表酷乐秀的立场或观点。作为内容的发表者,需自行对所发表内容负责,因所发表内容引发的一切纠纷,由该内容的发表者承担全部法律及连带责任。酷乐秀不承担任何法律及连带责任。
-        <br />
-        酷乐秀对如下事项不做担保(包括但不限于):
-        <br />
-        酷乐秀提供的网站、客户端等软件虽然均已经过酷乐秀测试,但由于技术本身的局限性,酷乐秀不能保证其与其他软硬件、系统完全兼容。如果出现不兼容的情况,用户可将情况报告酷乐秀,以获得技术支持。如果无法解决问题,用户可以选择卸载、停止使用酷乐秀服务。
-        <br />
-        用酷乐秀服务涉及到Internet服务,可能会受到各个环节不稳定因素的影响。因不可抗力、黑客攻击、系统不稳定、网络中断、用户关机、通信线路等原因,均可能造成酷乐秀服务中断或不能满足用户要求的情况。酷乐秀不保证酷乐秀服务适合用户的使用要求。
-        <br />
-        由于酷乐秀提供的客户端等软件可以通过网络途径下载、传播,因此对于从非酷乐秀指定官方站点下载、非酷乐秀指定途径获得的酷乐秀服务相关软件,酷乐秀无法保证其是否感染计算机病毒、是否隐藏有伪装的木马程序等黑客软件,也不承担用户由此遭受的一切直接或间接损害赔偿等法律责任。
-        <br />
-        酷乐秀不做任何与酷乐秀服务、产品的安全性、可靠性、及时性和性能有关的担保。
-        <br />
-        酷乐秀不保证其提供的任何产品、服务或其他材料符合用户的期望。
-        <br />
-        用户使用经由酷乐秀服务下载或取得的任何资料,其风险由用户自行负担,因该使用而导致用户电脑系统损坏或资料流失,用户应负完全责任
-        <br />
-        基于以下原因而造成的利润、商业信誉、资料损失或其他有形或无形损失,酷乐秀不承担任何直接、间接、附带、衍生或惩罚性的赔偿:
-        <br />
-        酷乐秀服务全部或部分无法使用;
-        <br />
-        经由酷乐秀服务购买或取得的任何产品、资料或服务;
-        <br />
-        用户资料遭到未授权的使用或修改;
-        <br />
-        其他与酷乐秀服务相关的事宜
-        <br />
-        用户应妥善保管自己的账号和密码,加强密码安全性,谨防账号泄露或被盗。因用户账号被泄露或被盗而造成的任何损失,酷乐秀不承担补偿责任。用户因电信和网通部门的通讯线路故障、网络或电脑故障、系统不稳定、不可抗力(如服务器宕机)等非酷乐秀原因造成账号、账号内财产等丢失、减少的,酷乐秀不承担补偿等责任。
-        <br />
-        用户理解并同意自主选择免费下载和使用酷乐秀服务,风险自负,包括但不限于用户使用酷乐秀服务过程中的行为,以及因使用酷乐秀服务产生的一切后果。如因下载或使用酷乐秀服务而对计算机系统造成的损坏或数据的丢失等,用户须自行承担全部责任。
-        <br />
-        本站所有乐谱均为用户自行制作和上传,其所上传数字内容的版权问题,由上传者自行负责,本站不对用户上传的数字内容承担任何责任。因版权原因导致乐谱下架,本站不承担因此带来的任何责任,不支持以版权下架原因导致不能看谱、下载曲谱而申请退费。
-        <br />
-        本站十分重视网络版权及其他知识产权以及用户权益的保护,如果您发现有用户上传的内容侵犯了您相关权益,请即以电话(4008851569)或邮件(753761527@qq.com)的方式告知本站,并提供以下材料:1、著作权人的身份证明,包括身份证、法人执照、营业执照等有效身份证件;2、著作权权属证明,如著作权登记证书等;3、侵权情况证明,包括被控侵权信息的内容等。本站工作人员会在收到相关材料并经相关机构核实后,在一个工作日内将所涉及的侵权内容删除。
-        <br />
-        隐私政策
-        <br />
-        酷乐秀注重保护用户的个人信息及个人隐私。个人信息是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。您在下载、安装、启动、浏览、注册、登录、使用酷乐秀的产品与/或服务时,酷乐秀将按照平台公布的《酷乐秀隐私政策》的约定处理和保护您的个人信息,因此希望您能够仔细阅读、充分理解《酷乐秀隐私政策》的全文,并在需要时,按照《酷乐秀隐私政策》的指引,作出您认为适当的选择。
-        <br />
-        您应当在仔细阅读、充分理解《酷乐秀隐私政策》后使用酷乐秀的产品与/或服务,如果您不同意政策的内容,将可能导致酷乐秀的产品与/或服务无法正常运行,或者无法达到酷乐秀拟达到的服务效果。您使用或继续使用酷乐秀提供的产品与/或服务的行为,都表示您充分理解和同意《酷乐秀隐私政策》(包括更新版本)的全部内容。
-        <br />
-        如您对《酷乐秀隐私政策》或对您的个人信息相关内容有任何问题(包括问题咨询、投诉等),请即以电话(4008851569)或邮件(753761527@qq.com)的方式告知本站。
-        <br />
-        协议修改
-        <br />
-        根据互联网的发展和有关法律、法规及规范性文件的变化,或者因业务发展需要,酷乐秀有权对本协议的条款作出修改或变更,一旦本协议的内容发生变动,酷乐秀将会直接在酷乐秀网站上公布修改之后的协议内容,该公布行为视为酷乐秀已经通知用户修改内容。酷乐秀也可采用通知的传送方式,提示用户协议条款的修改、服务变更、或其它重要事项。
-        <br />
-        如果不同意酷乐秀对本协议相关条款所做的修改,用户有权并应当停止使用酷乐秀。如果用户继续使用酷乐秀,则视为用户接受酷乐秀对本协议相关条款所做的修改。
-        <br />
-        上次版本更新时间:2022年4月18日
-        <br />
-        本次版本更新时间:2022年4月18日
-        <br />
-      </div>
+      <>
+        {this.showHeader === '1' && <ColHeader isBack />}
+        <div class={styles.container}>
+          欢迎来到酷乐秀,酷乐秀 隶属于武汉酷乐秀科技有限公司。
+          <br />
+          请您仔细阅读以下条款,如果您对本协议的任何条款表示异议,您可以选择不进入酷乐秀。当您注册成功,无论是进入酷乐秀,还是在酷乐秀上发布任何内容(即「内容」),均意味着您(即「用户」)完全接受本协议项下的全部条款。
+          <br />
+          使用协议
+          <br />
+          用户注册成功后,酷乐秀将给予每个用户一个用户帐号及相应的密码,该用户帐号和密码由用户负责保管;用户应当对以其用户帐号进行的所有活动和事件负法律责任。
+          <br />
+          用户须对在酷乐秀的注册信息的真实性、合法性、有效性承担全部责任,用户不得冒充他人;不得利用他人的名义发布任何信息;不得恶意使用注册帐号导致其他用户误认;否则酷乐秀有权立即停止提供服务,收回其帐号并由用户独自承担由此而产生的一切法律责任。
+          <br />
+          用户直接或通过各类方式(如 RSS 源和站外 API
+          引用等)间接使用酷乐秀服务和数据的行为,都将被视作已无条件接受本协议全部内容;若用户对本协议的任何条款存在异议,请停止使用酷乐秀所提供的全部服务。
+          <br />
+          酷乐秀是一个信息分享、传播及获取的平台,用户通过酷乐秀发表的信息为公开的信息,其他第三方均可以通过酷乐秀获取用户发表的信息,用户对任何信息的发表即认可该信息为公开的信息,并单独对此行为承担法律责任;任何用户不愿被其他第三人获知的信息都不应该在酷乐秀上进行发表。
+          <br />
+          为了更好地维护乐谱,酷乐秀会选择性地将用户上传的高质量乐谱设置成VIP。
+          <br />
+          您的上传行为代表您同意您上传的作品在本站内的公开发布与传播并授权本站使用您上传的作品。任何第三方的转载行为与本站无关。
+          <br />
+          酷乐秀如果认为您上传的内容不适当,有权进行删除或修改,甚至对情节严重者进行封号处理。
+          <br />
+          酷乐秀会员账号、曲谱、教程视频等资源均不能二次转售,否则将对实施以上行为的账号终身封号,且将通过法律途径追究其责任,追偿酷乐秀的损失。用户账号被封后,酷乐秀会提供1次人工申诉机会,请按照要求填写相关的信息,完成备案后,如符合条件可以解封;如不符合条件,我们将会通过邮件告知您。
+          <br />
+          酷乐秀VIP会员每天的最大的打印/保存pdf数量为15,不同天打印的总数不设上限。请正常下载使用本站乐谱,如非正常下载(不符合正常用户的行为),潜在有可能用于不正当途径,您的账号可能会被封号。酷乐秀的乐谱中均包含有账户信息,请仅供自己使用,如发布于互联网,或被用于转售曲谱,您的账号同样存在封号的风险;如对酷乐秀造成损害,酷乐秀同样会利用法律武器进行维权。
+          <br />
+          酷乐秀账号每年可累计在5台设备上登录使用,每天不得超过其中的4台。如果违规将由系统判定,给予警告和处罚。提醒用户账号切勿共享,因共享一定会违规。首次违规锁定7天VIP,第2次违规锁定1个月,依次3个月,6个月,12个月,永久。(2021年7月8日新增)
+          <br />
+          用户承诺不得以任何方式利用酷乐秀直接或间接从事违反中国法律、以及社会公德的行为,酷乐秀有权对违反上述承诺的内容予以删除。用户账号被封后,酷乐秀会提供1次人工申诉机会,请按照要求填写相关的信息,完成备案后,如符合条件可以解封;如不符合条件,我们将会通过邮件告知您。
+          <br />
+          用户不得利用酷乐秀服务制作、上载、复制、发布、传播或者转载如下内容:
+          <br />
+          反对宪法所确定的基本原则的;
+          <br />
+          危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;
+          <br />
+          损害国家荣誉和利益的;
+          <br />
+          煽动民族仇恨、民族歧视,破坏民族团结的;
+          <br />
+          破坏国家宗教政策,宣扬邪教和封建迷信的;
+          <br />
+          散布谣言,扰乱社会秩序,破坏社会稳定的;
+          <br />
+          散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的;
+          <br />
+          侮辱或者诽谤他人,侵害他人合法权益的;
+          <br />
+          含有法律、行政法规禁止的其他内容的信息。
+          <br />
+          禁止使用国家领导人、亲属名字作为昵称,含隐晦名称等。
+          <br />
+          包含以上情况的内容不得出现在:用户名、昵称、个人签名、帖子中、评论中、回复中,以及任何形式可能出现在酷乐秀的内容中。
+          当您在酷乐秀发表内容前,您已做过实名认证,如您的行为包含违规的内容,我们将及时向国家相关部门上报您的违规行为。
+          当您出现任何违规行为,我们将有权删除您的账号和相关的评论内容,并且终身不可恢复。
+          <br />
+          本站不是知识产权专业机构,不具备审查、判断作品是否侵权的能力,也不具备对用户所发布内容的合法性、真实性及其品质等进行审查的能力。任何用户应当为自己上传文件的行为独立完全承担法律责任。因您的个人行为所产生的一切争议和纠纷以及诉讼,与本站无关。
+          <br />
+          终身VIP时效指的是产品的生命周期结束,停止运营为止,含主动和被动原因。主动原因为:公司因故主动宣布停止运营;被动原因为:公司倒闭、破产,APP程序下架,市场政策调整,不可抗力等原因,造成无法继续运营。当停止运营时,终身VIP服务自动终止,公司将按照酷乐秀有权对用户使用酷乐秀的情况进行审查和监督,如用户在使用酷乐秀时违反任何上述规定,酷乐秀或其授权的人有权要求用户改正或直接采取一切必要的措施(包括但不限于更改或删除用户张贴的内容、暂停或终止用户使用酷乐秀的权利)以减轻用户不当行为造成的影响。
+          <br />
+          在您充值酷乐秀会员、购买课程等服务前,请您充分确认后再充值,商品一旦售出后,除重复支付、7天内质量问题(需有证据证明)可退全款外,其他情况支持退残值,计算方法,见第14条。
+          <br />
+          因本平台app需通过不断升级来适配苹果、安卓、微软等操作系统的升级,可能会导致较低版本的设备不再支持安装、运行,属于正常合理的情况。如果出现这种情况,表示您的硬件需要升级了。此种情况下,充值的VIP仍然可用,同时支持残值部分办理退款。(残值计算方法,见第14条)
+          <br />
+          侵权举报
+          <br />
+          处理原则
+          <br />
+          酷乐秀作为知识讨论社区,高度重视自由表达和企业正当权利的平衡。依照法律规定删除违法信息是酷乐秀社区的法定义务,酷乐秀社区亦未与任何中介机构合作开展此项业务。
+          <br />
+          受理范围
+          <br />
+          受理酷乐秀社区内侵犯企业或个人合法权益的侵权举报,包括但不限于涉及个人隐私、造谣与诽谤、商业侵权。
+          <br />
+          涉及个人隐私:发布内容中直接涉及身份信息,如个人姓名、家庭住址、身份证号码、工作单位、私人电话等详细个人隐私;
+          <br />
+          造谣、诽谤:发布内容中指名道姓(包括自然人和企业)的直接谩骂、侮辱、虚构中伤、恶意诽谤等;
+          <br />
+          商业侵权:泄露企业商业机密及其他根据保密协议不能公开讨论的内容。
+          <br />
+          权利人
+          <br />
+          权利人对涉嫌侵权内容拥有商标权、著作权或其他依法可以行使权利的权属证明;如果举报人非权利人,请举报人提供代表企业进行举报的书面授权证明。
+          <br />
+          充分、明确地描述侵犯了权利人合法权益的内容,提供涉嫌侵权内容在酷乐秀上的具体页面地址,指明涉嫌侵权内容中的哪些内容侵犯了上述列明的权利人的合法权益;
+          <br />
+          权利人具体的联络信息,包括姓名、身份证或护照复印件(对自然人)、单位登记证明复印件(对单位)、通信地址、电话号码、传真和电子邮件;
+          <br />
+          在侵权举报中加入如下关于举报内容真实性的声明:
+          <br />
+          我本人为所举报内容的合法权利人;
+          <br />
+          我举报的发布在酷乐秀社区中的内容侵犯了本人相应的合法权益;
+          <br />
+          如果本侵权举报内容不完全属实,本人将承担由此产生的一切法律责任。
+          <br />
+          处理流程
+          <br />
+          出于网络社区的监督属性,并非所有申请都必须受理。酷乐秀自收到举报邮件七个工作日内处理完毕并给出回复。处理期间,不提供任何电话、邮件及其他方式的查询服务。
+          <br />
+          出现酷乐秀已经删除或处理的内容,但是百度、谷歌等搜索引擎依然可以搜索到的现象,是因为百度、谷歌等搜索引擎自带缓存,此类问题酷乐秀无权也无法处理,因此相关申请不予受理。
+          <br />
+          此为酷乐秀社区唯一的官方的侵权投诉渠道,暂不提供其他方式处理此业务。
+          <br />
+          用户在酷乐秀中的商业行为引发的法律纠纷,由交易双方自行处理,与酷乐秀无关。
+          <br />
+          知识产权
+          <br />
+          酷乐秀是一个信息获取、分享及传播的平台,我们尊重和鼓励酷乐秀用户创作的内容,认识到保护知识产权对酷乐秀生存与发展的重要性,承诺将保护知识产权作为酷乐秀运营的基本原则之一。
+          <br />
+          用户在酷乐秀上发布的原创视频,帖子,发表的原创回答和评论,著作权均归用户本人所有。用户可授权第三方以任何方式使用,不需要得到酷乐秀的同意。
+          <br />
+          用户不得修改、改编、翻译酷乐秀服务所使用的软件、技术、材料等,或者创作与之相关的派生作品,不得通过反向工程、反编译、反汇编或其他类似行为获得其的源代码,否则由此引起的一切法律后果由用户负责,酷乐秀将依法追究违约方的法律责任。
+          <br />
+          用户不得恶意修改、复制、传播酷乐秀服务所使用的软件、技术、材料等。否则,用户自行承担因此而造成对其他人的损害,或者造成对酷乐秀公司形象损害,要承担相应的法律责任。
+          <br />
+          用户不得擅自删除、掩盖或更改酷乐秀的版权声明、商标或其它权利声明。酷乐秀平台所有设计图样以及其他图样、产品及服务名称,均为酷乐秀所享有的商标、标识。任何人不得使用、复制或用作其他用途。
+          <br />
+          为了促进知识的分享和传播,用户将其在酷乐秀上发表的全部内容,授予酷乐秀免费的、不可撤销的、非精编使用许可,酷乐秀有权将该内容用于酷乐秀各种形态的产品和服务上,包括但不限于网站以及发表的应用或其他互联网产品。
+          <br />
+          在酷乐秀上传或发表的内容,用户应保证其为著作权人或已取得合法授权,并且该内容不会侵犯任何第三方的合法权益。如果第三方提出关于著作权的异议,酷乐秀有权根据实际情况删除相关的内容有权追究用户的法律责任,给酷乐秀或任何第三方造成损失的,用户应负责全额赔偿。
+          <br />
+          如果任何第三方侵犯了酷乐秀用户相关的权利,用户同意授权酷乐秀或其指定的代理人代表酷乐秀自身或用户对该第三方提出警告、投诉、发起行政执法、诉讼、进行上诉,或谈判和解,并且用户同意在酷乐秀认为必要的情况下参与共同维权。
+          <br />
+          酷乐秀有权但无义务对用户发布的内容进行审核,有权根据相关证据结合《侵权责任法》、《信息网络传播权保护条例》等法律法规及酷乐秀社区指导原则对侵权信息进行处理。
+          <br />
+          酷乐秀对其自制内容和其他通过授权取得的独占内容享有完全知识产权,未经酷乐秀许可,任何单位和个人不得私自转载、传播和提供观看服务或者有其他侵犯酷乐秀知识产权的行为。否则,酷乐秀将追究侵权行为人的法律责任。
+          <br />
+          酷乐秀所有和享有的知识产权,不因用户的任何使用行为而发生权利转移。
+          <br />
+          免责申明
+          <br />
+          酷乐秀对于任何包含、经由或连接、下载或从任何与有关本网络服务所获得的任何内容、信息不声明或保证其正确性或可靠性;
+          <br />
+          用户在酷乐秀发表的内容仅表明其个人的立场和观点,并不代表酷乐秀的立场或观点。作为内容的发表者,需自行对所发表内容负责,因所发表内容引发的一切纠纷,由该内容的发表者承担全部法律及连带责任。酷乐秀不承担任何法律及连带责任。
+          <br />
+          酷乐秀对如下事项不做担保(包括但不限于):
+          <br />
+          酷乐秀提供的网站、客户端等软件虽然均已经过酷乐秀测试,但由于技术本身的局限性,酷乐秀不能保证其与其他软硬件、系统完全兼容。如果出现不兼容的情况,用户可将情况报告酷乐秀,以获得技术支持。如果无法解决问题,用户可以选择卸载、停止使用酷乐秀服务。
+          <br />
+          用酷乐秀服务涉及到Internet服务,可能会受到各个环节不稳定因素的影响。因不可抗力、黑客攻击、系统不稳定、网络中断、用户关机、通信线路等原因,均可能造成酷乐秀服务中断或不能满足用户要求的情况。酷乐秀不保证酷乐秀服务适合用户的使用要求。
+          <br />
+          由于酷乐秀提供的客户端等软件可以通过网络途径下载、传播,因此对于从非酷乐秀指定官方站点下载、非酷乐秀指定途径获得的酷乐秀服务相关软件,酷乐秀无法保证其是否感染计算机病毒、是否隐藏有伪装的木马程序等黑客软件,也不承担用户由此遭受的一切直接或间接损害赔偿等法律责任。
+          <br />
+          酷乐秀不做任何与酷乐秀服务、产品的安全性、可靠性、及时性和性能有关的担保。
+          <br />
+          酷乐秀不保证其提供的任何产品、服务或其他材料符合用户的期望。
+          <br />
+          用户使用经由酷乐秀服务下载或取得的任何资料,其风险由用户自行负担,因该使用而导致用户电脑系统损坏或资料流失,用户应负完全责任
+          <br />
+          基于以下原因而造成的利润、商业信誉、资料损失或其他有形或无形损失,酷乐秀不承担任何直接、间接、附带、衍生或惩罚性的赔偿:
+          <br />
+          酷乐秀服务全部或部分无法使用;
+          <br />
+          经由酷乐秀服务购买或取得的任何产品、资料或服务;
+          <br />
+          用户资料遭到未授权的使用或修改;
+          <br />
+          其他与酷乐秀服务相关的事宜
+          <br />
+          用户应妥善保管自己的账号和密码,加强密码安全性,谨防账号泄露或被盗。因用户账号被泄露或被盗而造成的任何损失,酷乐秀不承担补偿责任。用户因电信和网通部门的通讯线路故障、网络或电脑故障、系统不稳定、不可抗力(如服务器宕机)等非酷乐秀原因造成账号、账号内财产等丢失、减少的,酷乐秀不承担补偿等责任。
+          <br />
+          用户理解并同意自主选择免费下载和使用酷乐秀服务,风险自负,包括但不限于用户使用酷乐秀服务过程中的行为,以及因使用酷乐秀服务产生的一切后果。如因下载或使用酷乐秀服务而对计算机系统造成的损坏或数据的丢失等,用户须自行承担全部责任。
+          <br />
+          本站所有乐谱均为用户自行制作和上传,其所上传数字内容的版权问题,由上传者自行负责,本站不对用户上传的数字内容承担任何责任。因版权原因导致乐谱下架,本站不承担因此带来的任何责任,不支持以版权下架原因导致不能看谱、下载曲谱而申请退费。
+          <br />
+          本站十分重视网络版权及其他知识产权以及用户权益的保护,如果您发现有用户上传的内容侵犯了您相关权益,请即以电话(4008851569)或邮件(753761527@qq.com)的方式告知本站,并提供以下材料:1、著作权人的身份证明,包括身份证、法人执照、营业执照等有效身份证件;2、著作权权属证明,如著作权登记证书等;3、侵权情况证明,包括被控侵权信息的内容等。本站工作人员会在收到相关材料并经相关机构核实后,在一个工作日内将所涉及的侵权内容删除。
+          <br />
+          隐私政策
+          <br />
+          酷乐秀注重保护用户的个人信息及个人隐私。个人信息是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。您在下载、安装、启动、浏览、注册、登录、使用酷乐秀的产品与/或服务时,酷乐秀将按照平台公布的《酷乐秀隐私政策》的约定处理和保护您的个人信息,因此希望您能够仔细阅读、充分理解《酷乐秀隐私政策》的全文,并在需要时,按照《酷乐秀隐私政策》的指引,作出您认为适当的选择。
+          <br />
+          您应当在仔细阅读、充分理解《酷乐秀隐私政策》后使用酷乐秀的产品与/或服务,如果您不同意政策的内容,将可能导致酷乐秀的产品与/或服务无法正常运行,或者无法达到酷乐秀拟达到的服务效果。您使用或继续使用酷乐秀提供的产品与/或服务的行为,都表示您充分理解和同意《酷乐秀隐私政策》(包括更新版本)的全部内容。
+          <br />
+          如您对《酷乐秀隐私政策》或对您的个人信息相关内容有任何问题(包括问题咨询、投诉等),请即以电话(4008851569)或邮件(753761527@qq.com)的方式告知本站。
+          <br />
+          协议修改
+          <br />
+          根据互联网的发展和有关法律、法规及规范性文件的变化,或者因业务发展需要,酷乐秀有权对本协议的条款作出修改或变更,一旦本协议的内容发生变动,酷乐秀将会直接在酷乐秀网站上公布修改之后的协议内容,该公布行为视为酷乐秀已经通知用户修改内容。酷乐秀也可采用通知的传送方式,提示用户协议条款的修改、服务变更、或其它重要事项。
+          <br />
+          如果不同意酷乐秀对本协议相关条款所做的修改,用户有权并应当停止使用酷乐秀。如果用户继续使用酷乐秀,则视为用户接受酷乐秀对本协议相关条款所做的修改。
+          <br />
+          上次版本更新时间:2022年4月18日
+          <br />
+          本次版本更新时间:2022年4月18日
+          <br />
+        </div>
+      </>
     )
   }
 })

+ 4 - 1
src/views/trade/trade-detail.tsx

@@ -8,6 +8,7 @@ import iconTeacher from '@common/images/icon_teacher.png'
 import request from '@/helpers/request'
 import Payment from '../order-detail/payment'
 import { orderStatus } from '../order-detail/orderStatus'
+import { state } from '@/state'
 
 export const getAssetsHomeFile = (fileName: string) => {
   const path = `./images/${fileName}`
@@ -93,8 +94,10 @@ export default defineComponent({
     },
     async getOrder(status?: true) {
       try {
+        const urlFix =
+          state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student'
         const res = await request.get(
-          `/api-student/userOrder/detailByOrderNo/${this.orderNo}`,
+          `${urlFix}/userOrder/detailByOrderNo/${this.orderNo}`,
           {
             hideLoading: status
           }

File diff suppressed because it is too large
+ 417 - 493
yarn.lock


Some files were not shown because too many files changed in this diff