Browse Source

Merge branch 'colexiu1.3' into ponline

lex 1 year ago
parent
commit
2eba4786e1
100 changed files with 4993 additions and 1452 deletions
  1. 2 1
      package.json
  2. 308 309
      src/business-components/subject-list/index.tsx
  3. BIN
      src/common/images/icon_checkbox-tenant.png
  4. 161 154
      src/components/col-protocol/index.tsx
  5. BIN
      src/components/col-result/images/empty_tenant.png
  6. BIN
      src/components/col-result/images/network_tenant.png
  7. BIN
      src/components/col-result/images/notFond_tenant.png
  8. 49 33
      src/components/col-result/index.module.less
  9. 130 120
      src/components/col-result/index.tsx
  10. 34 1
      src/components/col-search/index.module.less
  11. 35 14
      src/components/col-search/index.tsx
  12. 42 0
      src/components/col-share/index.module.less
  13. 11 1
      src/components/col-share/index.tsx
  14. 214 215
      src/components/col-upload-video/index.tsx
  15. 237 238
      src/components/col-upload/index.tsx
  16. 0 0
      src/components/the-full-refresh/datas/data.json
  17. 21 0
      src/components/the-full-refresh/index.module.less
  18. 77 0
      src/components/the-full-refresh/index.tsx
  19. BIN
      src/components/the-full-refresh/loading.gif
  20. 1 0
      src/components/the-full-refresh/loading.json
  21. 26 0
      src/components/the-qrcode/index.module.less
  22. 65 0
      src/components/the-qrcode/index.tsx
  23. 18 0
      src/components/the-sticky/index.module.less
  24. 121 0
      src/components/the-sticky/index.tsx
  25. 72 71
      src/constant/index.ts
  26. 1 1
      src/helpers/request.ts
  27. 81 0
      src/helpers/utils.ts
  28. 67 0
      src/router/index-tenant.ts
  29. 14 0
      src/router/routes-common.ts
  30. 7 0
      src/router/routes-teacher.ts
  31. 345 0
      src/router/routes-tenant.ts
  32. 70 2
      src/state.ts
  33. 1 0
      src/student/live-class/live-detail.tsx
  34. 271 269
      src/student/trade/tradeOrder.ts
  35. 7 1
      src/styles/index.less
  36. 28 0
      src/styles/tenant.less
  37. 5 3
      src/teacher/share-page/share-album/index.tsx
  38. 90 16
      src/teacher/share-page/share-music/index.module.less
  39. 3 3
      src/teacher/share-page/share-music/index.tsx
  40. 11 0
      src/tenant/App.vue
  41. BIN
      src/tenant/activation-code/bg-image.png
  42. BIN
      src/tenant/activation-code/icon-1.png
  43. 139 0
      src/tenant/activation-code/index.module.less
  44. 197 0
      src/tenant/activation-code/index.tsx
  45. 558 0
      src/tenant/exercise-record/echats.ts
  46. 313 0
      src/tenant/exercise-record/exercis-detail.module.less
  47. 433 0
      src/tenant/exercise-record/exercis-detail.tsx
  48. BIN
      src/tenant/exercise-record/images/Image1.png
  49. BIN
      src/tenant/exercise-record/images/Image2.png
  50. BIN
      src/tenant/exercise-record/images/Image3.png
  51. BIN
      src/tenant/exercise-record/images/Image4.png
  52. BIN
      src/tenant/exercise-record/images/Image5.png
  53. BIN
      src/tenant/exercise-record/images/good-icon.png
  54. BIN
      src/tenant/exercise-record/images/icon-arrow.png
  55. BIN
      src/tenant/exercise-record/images/icon-play.png
  56. BIN
      src/tenant/exercise-record/images/icon-subject.png
  57. BIN
      src/tenant/exercise-record/images/train-title.png
  58. 114 0
      src/tenant/exercise-record/modals/detail-item.module.less
  59. 133 0
      src/tenant/exercise-record/modals/detail-item.tsx
  60. BIN
      src/tenant/images/album-bg.png
  61. BIN
      src/tenant/images/bg-image-search.png
  62. BIN
      src/tenant/images/bg-image.png
  63. BIN
      src/tenant/images/icon-album-cover.png
  64. BIN
      src/tenant/images/icon-search.png
  65. BIN
      src/tenant/images/icon-share.png
  66. BIN
      src/tenant/images/music-bg.png
  67. 32 0
      src/tenant/layout/auth.module.less
  68. 115 0
      src/tenant/layout/auth.tsx
  69. BIN
      src/tenant/layout/images/bottom_bg.png
  70. BIN
      src/tenant/layout/images/top_bg.png
  71. 44 0
      src/tenant/layout/login.module.less
  72. 210 0
      src/tenant/layout/login.tsx
  73. 80 0
      src/tenant/main.ts
  74. BIN
      src/tenant/member-center/images/1.png
  75. BIN
      src/tenant/member-center/images/2.png
  76. BIN
      src/tenant/member-center/images/3.png
  77. BIN
      src/tenant/member-center/images/4.png
  78. BIN
      src/tenant/member-center/images/5.png
  79. BIN
      src/tenant/member-center/images/6.png
  80. BIN
      src/tenant/member-center/images/7.png
  81. BIN
      src/tenant/member-center/images/8.png
  82. BIN
      src/tenant/member-center/images/contnt-bg.png
  83. BIN
      src/tenant/member-center/images/discount_bg.png
  84. BIN
      src/tenant/member-center/images/function-title.png
  85. BIN
      src/tenant/member-center/images/icon-arrow-line.png
  86. BIN
      src/tenant/member-center/images/icon-arrow.png
  87. BIN
      src/tenant/member-center/images/icon-logo-default.png
  88. BIN
      src/tenant/member-center/images/icon-logo.png
  89. BIN
      src/tenant/member-center/images/icon-member-active.png
  90. BIN
      src/tenant/member-center/images/icon-member-s.png
  91. BIN
      src/tenant/member-center/images/icon-selected.png
  92. BIN
      src/tenant/member-center/images/icon_discount.png
  93. BIN
      src/tenant/member-center/images/icon_gift.png
  94. BIN
      src/tenant/member-center/images/icon_video.png
  95. BIN
      src/tenant/member-center/images/info-title.png
  96. BIN
      src/tenant/member-center/images/member-bg.png
  97. BIN
      src/tenant/member-center/images/member_bg.png
  98. BIN
      src/tenant/member-center/images/member_logo.png
  99. BIN
      src/tenant/member-center/images/price-bg.png
  100. BIN
      src/tenant/member-center/images/record_bg.png

+ 2 - 1
package.json

@@ -15,7 +15,8 @@
   "scripts": {
     "dev": "vite",
     "start": "npm run dev",
-    "build": "vue-tsc --noEmit && vite build",
+    "build:prod": "vue-tsc --noEmit && vite build",
+    "build:dev": "vue-tsc --noEmit && vite build --mode development",
     "serve": "vite preview",
     "lint": "eslint --ext .js,.jsx,.vue,.ts,.tsx src",
     "generate": "plop"

+ 308 - 309
src/business-components/subject-list/index.tsx

@@ -1,309 +1,308 @@
-import {
-  Button,
-  Checkbox,
-  CheckboxGroup,
-  Icon,
-  Image,
-  Loading,
-  Radio,
-  RadioGroup,
-  Sticky,
-  Toast
-} from 'vant'
-import { defineComponent, PropType } from 'vue'
-import styles from './index.module.less'
-
-import checkBoxActive from '@/teacher/teacher-cert/images/checkbox_active.png'
-import checkBoxDefault from '@/teacher/teacher-cert/images/checkbox_default.png'
-import ColResult from '@/components/col-result'
-
-export default defineComponent({
-  name: 'SubjectList',
-  props: {
-    onChoice: {
-      type: Function,
-      default: (item: any) => {}
-    },
-    choiceSubjectIds: {
-      type: Array,
-      default: []
-    },
-    subjectList: {
-      type: Array,
-      default: []
-    },
-    max: {
-      // 最多可选数量
-      type: Number,
-      default: 5
-    },
-    selectType: {
-      // 选择类型,Radio:单选,Checkbox:多选
-      type: String as PropType<'Checkbox' | 'Radio'>,
-      default: 'Checkbox'
-    },
-    single: {
-      // 单选模式
-      type: Boolean,
-      default: false
-    }
-  },
-  data() {
-    return {
-      checkBox: [],
-      checkboxRefs: [] as any,
-      radio: null as any // 单选
-    }
-  },
-  async mounted() {
-    if (this.selectType === 'Radio') {
-      this.radio = this.choiceSubjectIds[0]
-    } else {
-      this.checkBox = this.choiceSubjectIds as never[]
-    }
-  },
-  watch: {
-    choiceSubjectIds(val: any, oldVal) {
-      // 同步更新显示数据
-      this.checkBox = [...val] as never[]
-    }
-  },
-  methods: {
-    onSelect(id: number) {
-      if (this.selectType === 'Checkbox') {
-        if (
-          this.max === this.checkBox.length &&
-          !this.checkBox.includes(id as never)
-        ) {
-          Toast(`乐器最多选择${this.max}个`)
-        }
-        this.checkboxRefs[id].toggle()
-      } else if (this.selectType === 'Radio') {
-        this.radio = id
-      }
-    }
-  },
-  render() {
-    return (
-      <div class={styles.subjects}>
-        <div class={styles.subjectContainer}>
-          {this.subjectList.length ? (
-            this.selectType === 'Checkbox' ? (
-              <CheckboxGroup v-model={this.checkBox} max={this.max}>
-                <div class={styles.subjectMaxLength}>
-                  最多可选择{this.max}个乐器
-                </div>
-
-                {!this.single &&
-                  this.subjectList.map((item: any) =>
-                    item.subjects && item.subjects.length > 0 ? (
-                      <>
-                        <div class={styles.title}>{item.name}</div>
-                        <div class={styles['subject-list']}>
-                          {item.subjects &&
-                            item.subjects.map((sub: any) => (
-                              <div
-                                class={styles['subject-item']}
-                                onClick={() => this.onSelect(sub.id)}
-                              >
-                                <Image
-                                  src={sub.img || 'xxx'}
-                                  width="100%"
-                                  height="100%"
-                                  fit="cover"
-                                  v-slots={{
-                                    loading: () => (
-                                      <Loading type="spinner" size={20} />
-                                    )
-                                  }}
-                                />
-                                <div class={styles.topBg}>
-                                  <Checkbox
-                                    name={sub.id}
-                                    class={styles.checkbox}
-                                    disabled
-                                    ref={(el: any) =>
-                                      (this.checkboxRefs[sub.id] = el)
-                                    }
-                                    v-slots={{
-                                      icon: (props: any) => (
-                                        <Icon
-                                          name={
-                                            props.checked
-                                              ? checkBoxActive
-                                              : checkBoxDefault
-                                          }
-                                          size="20"
-                                        />
-                                      )
-                                    }}
-                                  />
-                                  <p class={styles.name}>{sub.name}</p>
-                                </div>
-                              </div>
-                            ))}
-                        </div>
-                      </>
-                    ) : null
-                  )}
-                {this.single ? (
-                  <div class={styles['subject-list']}>
-                    {this.subjectList.map((item: any) => (
-                      <div
-                        class={styles['subject-item']}
-                        onClick={() => this.onSelect(item.id)}
-                      >
-                        <Image
-                          src={item.img || 'xxx'}
-                          width="100%"
-                          height="100%"
-                          fit="cover"
-                          v-slots={{
-                            loading: () => <Loading type="spinner" size={20} />
-                          }}
-                        />
-                        <div class={styles.topBg}>
-                          <Checkbox
-                            name={item.id}
-                            class={styles.checkbox}
-                            disabled
-                            ref={(el: any) => (this.checkboxRefs[item.id] = el)}
-                            v-slots={{
-                              icon: (props: any) => (
-                                <Icon
-                                  name={
-                                    props.checked
-                                      ? checkBoxActive
-                                      : checkBoxDefault
-                                  }
-                                  size="20"
-                                />
-                              )
-                            }}
-                          />
-                          <p class={styles.name}>{item.name}</p>
-                        </div>
-                      </div>
-                    ))}
-                  </div>
-                ) : null}
-              </CheckboxGroup>
-            ) : (
-              <RadioGroup v-model={this.radio}>
-                {!this.single &&
-                  this.subjectList.map((item: any) =>
-                    item.subjects && item.subjects.length > 0 ? (
-                      <>
-                        <div class={styles.title}>{item.name}</div>
-                        <div class={styles['subject-list']}>
-                          {item.subjects &&
-                            item.subjects.map((sub: any) => (
-                              <div
-                                class={styles['subject-item']}
-                                onClick={() => this.onSelect(sub.id)}
-                              >
-                                <Image
-                                  src={sub.img || 'xxx'}
-                                  width="100%"
-                                  height="100%"
-                                  fit="cover"
-                                  v-slots={{
-                                    loading: () => (
-                                      <Loading type="spinner" size={20} />
-                                    )
-                                  }}
-                                />
-                                <div class={styles.topBg}>
-                                  <Radio
-                                    name={sub.id}
-                                    class={styles.checkbox}
-                                    v-slots={{
-                                      icon: (props: any) => (
-                                        <Icon
-                                          name={
-                                            props.checked
-                                              ? checkBoxActive
-                                              : checkBoxDefault
-                                          }
-                                          size="20"
-                                        />
-                                      )
-                                    }}
-                                  />
-                                  <p class={styles.name}>{sub.name}</p>
-                                </div>
-                              </div>
-                            ))}
-                        </div>
-                      </>
-                    ) : null
-                  )}
-                {this.single ? (
-                  <div class={styles['subject-list']}>
-                    {this.subjectList.map((item: any) => (
-                      <div
-                        class={styles['subject-item']}
-                        onClick={() => this.onSelect(item.id)}
-                      >
-                        <Image
-                          src={item.img || 'xxx'}
-                          width="100%"
-                          height="100%"
-                          fit="cover"
-                          v-slots={{
-                            loading: () => <Loading type="spinner" size={20} />
-                          }}
-                        />
-                        <div class={styles.topBg}>
-                          <Radio
-                            name={item.id}
-                            class={styles.checkbox}
-                            v-slots={{
-                              icon: (props: any) => (
-                                <Icon
-                                  name={
-                                    props.checked
-                                      ? checkBoxActive
-                                      : checkBoxDefault
-                                  }
-                                  size="20"
-                                />
-                              )
-                            }}
-                          />
-                          <p class={styles.name}>{item.name}</p>
-                        </div>
-                      </div>
-                    ))}
-                  </div>
-                ) : null}
-              </RadioGroup>
-            )
-          ) : (
-            <ColResult tips="暂无声部数据" btnStatus={false} />
-          )}
-        </div>
-
-        {this.subjectList.length > 0 && (
-          <Sticky offsetBottom={0} position="bottom">
-            <div class={'btnGroup'}>
-              <Button
-                round
-                block
-                type="primary"
-                style={{ width: '96%', margin: '0 auto' }}
-                onClick={() =>
-                  this.onChoice(
-                    this.selectType === 'Checkbox' ? this.checkBox : this.radio
-                  )
-                }
-              >
-                确定
-              </Button>
-            </div>
-          </Sticky>
-        )}
-      </div>
-    )
-  }
-})
+import {
+  Button,
+  Checkbox,
+  CheckboxGroup,
+  Icon,
+  Image,
+  Loading,
+  Radio,
+  RadioGroup,
+  Sticky,
+  Toast
+} from 'vant'
+import { defineComponent, PropType } from 'vue'
+import styles from './index.module.less'
+import checkBoxActive from '@/teacher/teacher-cert/images/checkbox_active.png'
+import checkBoxDefault from '@/teacher/teacher-cert/images/checkbox_default.png'
+import ColResult from '@/components/col-result'
+
+export default defineComponent({
+  name: 'SubjectList',
+  props: {
+    onChoice: {
+      type: Function,
+      default: (item: any) => { }
+    },
+    choiceSubjectIds: {
+      type: Array,
+      default: []
+    },
+    subjectList: {
+      type: Array,
+      default: []
+    },
+    max: {
+      // 最多可选数量
+      type: Number,
+      default: 5
+    },
+    selectType: {
+      // 选择类型,Radio:单选,Checkbox:多选
+      type: String as PropType<'Checkbox' | 'Radio'>,
+      default: 'Checkbox'
+    },
+    single: {
+      // 单选模式
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      checkBox: [],
+      checkboxRefs: [] as any,
+      radio: null as any // 单选
+    }
+  },
+  async mounted() {
+    if (this.selectType === 'Radio') {
+      this.radio = this.choiceSubjectIds[0]
+    } else {
+      this.checkBox = this.choiceSubjectIds as never[]
+    }
+  },
+  watch: {
+    choiceSubjectIds(val: any, oldVal) {
+      // 同步更新显示数据
+      this.checkBox = [...val] as never[]
+    }
+  },
+  methods: {
+    onSelect(id: number) {
+      if (this.selectType === 'Checkbox') {
+        if (
+          this.max === this.checkBox.length &&
+          !this.checkBox.includes(id as never)
+        ) {
+          Toast(`乐器最多选择${this.max}个`)
+        }
+        this.checkboxRefs[id].toggle()
+      } else if (this.selectType === 'Radio') {
+        this.radio = id
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles.subjects}>
+        <div class={styles.subjectContainer}>
+          {this.subjectList.length ? (
+            this.selectType === 'Checkbox' ? (
+              <CheckboxGroup v-model={this.checkBox} max={this.max}>
+                <div class={styles.subjectMaxLength}>
+                  最多可选择{this.max}个乐器
+                </div>
+
+                {!this.single &&
+                  this.subjectList.map((item: any) =>
+                    item.subjects && item.subjects.length > 0 ? (
+                      <>
+                        <div class={styles.title}>{item.name}</div>
+                        <div class={styles['subject-list']}>
+                          {item.subjects &&
+                            item.subjects.map((sub: any) => (
+                              <div
+                                class={styles['subject-item']}
+                                onClick={() => this.onSelect(sub.id)}
+                              >
+                                <Image
+                                  src={sub.img || 'xxx'}
+                                  width="100%"
+                                  height="100%"
+                                  fit="cover"
+                                  v-slots={{
+                                    loading: () => (
+                                      <Loading type="spinner" size={20} />
+                                    )
+                                  }}
+                                />
+                                <div class={styles.topBg}>
+                                  <Checkbox
+                                    name={sub.id}
+                                    class={styles.checkbox}
+                                    disabled
+                                    ref={(el: any) =>
+                                      (this.checkboxRefs[sub.id] = el)
+                                    }
+                                    v-slots={{
+                                      icon: (props: any) => (
+                                        <Icon
+                                          name={
+                                            props.checked
+                                              ? checkBoxActive
+                                              : checkBoxDefault
+                                          }
+                                          size="20"
+                                        />
+                                      )
+                                    }}
+                                  />
+                                  <p class={styles.name}>{sub.name}</p>
+                                </div>
+                              </div>
+                            ))}
+                        </div>
+                      </>
+                    ) : null
+                  )}
+                {this.single ? (
+                  <div class={styles['subject-list']}>
+                    {this.subjectList.map((item: any) => (
+                      <div
+                        class={styles['subject-item']}
+                        onClick={() => this.onSelect(item.id)}
+                      >
+                        <Image
+                          src={item.img || 'xxx'}
+                          width="100%"
+                          height="100%"
+                          fit="cover"
+                          v-slots={{
+                            loading: () => <Loading type="spinner" size={20} />
+                          }}
+                        />
+                        <div class={styles.topBg}>
+                          <Checkbox
+                            name={item.id}
+                            class={styles.checkbox}
+                            disabled
+                            ref={(el: any) => (this.checkboxRefs[item.id] = el)}
+                            v-slots={{
+                              icon: (props: any) => (
+                                <Icon
+                                  name={
+                                    props.checked
+                                      ? checkBoxActive
+                                      : checkBoxDefault
+                                  }
+                                  size="20"
+                                />
+                              )
+                            }}
+                          />
+                          <p class={styles.name}>{item.name}</p>
+                        </div>
+                      </div>
+                    ))}
+                  </div>
+                ) : null}
+              </CheckboxGroup>
+            ) : (
+              <RadioGroup v-model={this.radio}>
+                {!this.single &&
+                  this.subjectList.map((item: any) =>
+                    item.subjects && item.subjects.length > 0 ? (
+                      <>
+                        <div class={styles.title}>{item.name}</div>
+                        <div class={styles['subject-list']}>
+                          {item.subjects &&
+                            item.subjects.map((sub: any) => (
+                              <div
+                                class={styles['subject-item']}
+                                onClick={() => this.onSelect(sub.id)}
+                              >
+                                <Image
+                                  src={sub.img || 'xxx'}
+                                  width="100%"
+                                  height="100%"
+                                  fit="cover"
+                                  v-slots={{
+                                    loading: () => (
+                                      <Loading type="spinner" size={20} />
+                                    )
+                                  }}
+                                />
+                                <div class={styles.topBg}>
+                                  <Radio
+                                    name={sub.id}
+                                    class={styles.checkbox}
+                                    v-slots={{
+                                      icon: (props: any) => (
+                                        <Icon
+                                          name={
+                                            props.checked
+                                              ? checkBoxActive
+                                              : checkBoxDefault
+                                          }
+                                          size="20"
+                                        />
+                                      )
+                                    }}
+                                  />
+                                  <p class={styles.name}>{sub.name}</p>
+                                </div>
+                              </div>
+                            ))}
+                        </div>
+                      </>
+                    ) : null
+                  )}
+                {this.single ? (
+                  <div class={styles['subject-list']}>
+                    {this.subjectList.map((item: any) => (
+                      <div
+                        class={styles['subject-item']}
+                        onClick={() => this.onSelect(item.id)}
+                      >
+                        <Image
+                          src={item.img || 'xxx'}
+                          width="100%"
+                          height="100%"
+                          fit="cover"
+                          v-slots={{
+                            loading: () => <Loading type="spinner" size={20} />
+                          }}
+                        />
+                        <div class={styles.topBg}>
+                          <Radio
+                            name={item.id}
+                            class={styles.checkbox}
+                            v-slots={{
+                              icon: (props: any) => (
+                                <Icon
+                                  name={
+                                    props.checked
+                                      ? checkBoxActive
+                                      : checkBoxDefault
+                                  }
+                                  size="20"
+                                />
+                              )
+                            }}
+                          />
+                          <p class={styles.name}>{item.name}</p>
+                        </div>
+                      </div>
+                    ))}
+                  </div>
+                ) : null}
+              </RadioGroup>
+            )
+          ) : (
+            <ColResult tips="暂无声部数据" btnStatus={false} />
+          )}
+        </div>
+
+        {this.subjectList.length > 0 && (
+          <Sticky offsetBottom={0} position="bottom">
+            <div class={'btnGroup'}>
+              <Button
+                round
+                block
+                type="primary"
+                style={{ width: '96%', margin: '0 auto' }}
+                onClick={() =>
+                  this.onChoice(
+                    this.selectType === 'Checkbox' ? this.checkBox : this.radio
+                  )
+                }
+              >
+                确定
+              </Button>
+            </div>
+          </Sticky>
+        )}
+      </div>
+    )
+  }
+})

BIN
src/common/images/icon_checkbox-tenant.png


+ 161 - 154
src/components/col-protocol/index.tsx

@@ -1,154 +1,161 @@
-import { Checkbox, Icon, Popup } from 'vant'
-import { defineComponent, PropType } from 'vue'
-import styles from './index.module.less'
-import activeButtonIcon from '@common/images/icon_checkbox.png'
-import inactiveButtonIcon from '@common/images/icon_checkbox_default.png'
-import ColHeader from '../col-header'
-import { state } from '@/state'
-import request from '@/helpers/request'
-const protocolText = {
-  BUY_ORDER: '《酷乐秀平台服务协议》',
-  REGISTER: '《酷乐秀平台注册协议》'
-}
-export default defineComponent({
-  name: 'protocol',
-  props: {
-    showHeader: {
-      type: Boolean,
-      default: false
-    },
-    modelValue: {
-      type: Boolean,
-      default: false
-    },
-    prototcolType: {
-      type: String as PropType<'BUY_ORDER' | 'REGISTER'>,
-      default: 'BUY_ORDER'
-    }
-  },
-  data() {
-    return {
-      exists: true,
-      checked: this.modelValue,
-      popupStatus: false,
-      protocolHTML: '',
-      protocolPopup: null as any,
-      baseUrl:
-        state.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
-    }
-  },
-  async mounted() {
-    try {
-      const res = await request.get(
-        this.baseUrl + '/sysUserContractRecord/checkContractSign',
-        {
-          params: {
-            contractType: this.prototcolType
-          }
-        }
-      )
-      // console.log(res)
-      this.exists = res.data
-      this.checked = this.checked || this.exists
-      this.$emit('update:modelValue', this.checked || this.exists)
-    } catch {}
-    this.checked = this.modelValue
-    // this.getContractDetail()
-    window.addEventListener('hashchange', this.onHash, false)
-  },
-  unmounted() {
-    window.removeEventListener('hashchange', this.onHash, false)
-  },
-  watch: {
-    checked(val) {
-      this.$emit('update:modelValue', val)
-    }
-  },
-  methods: {
-    async getContractDetail() {
-      try {
-        console.log('getContractDetail')
-        // 判断是否有协议内容
-        if (!this.protocolHTML) {
-          const res = await request.get(
-            this.baseUrl + '/sysUserContractRecord/queryContract',
-            {
-              params: {
-                contractType: this.prototcolType
-              }
-            }
-          )
-          this.protocolHTML = res.data
-          console.log(res)
-        }
-        this.onPopupClose()
-      } catch {}
-    },
-    onHash() {
-      this.popupStatus = false
-    },
-    onPopupClose() {
-      this.popupStatus = !this.popupStatus
-
-      // 打开弹窗
-      if (this.popupStatus) {
-        const route = this.$route
-        let times = 0
-        for (let i in route.query) {
-          times += 1
-        }
-        const origin = window.location.href
-        const url = times > 0 ? '&pto=' + +new Date() : '?pto=' + +new Date()
-        history.pushState('', '', `${origin}${url}`)
-      } else {
-        window.history.go(-1)
-      }
-      if (this.protocolPopup) {
-        ;(this.protocolPopup as any).scrollTop = 0
-      }
-    }
-  },
-  render() {
-    return (
-      <div class={styles.colProtocol}>
-        {!this.exists && (
-          <Checkbox
-            v-model={this.checked}
-            v-slots={{
-              icon: (props: any) => (
-                <Icon
-                  class={styles.boxStyle}
-                  name={props.checked ? activeButtonIcon : inactiveButtonIcon}
-                  size="15"
-                />
-              )
-            }}
-          >
-            我已阅读并同意
-          </Checkbox>
-        )}
-        {this.exists && <>查看</>}
-        <span onClick={this.getContractDetail} class={styles.protocolText}>
-          {protocolText[this.prototcolType]}
-        </span>
-
-        <Popup
-          ref={this.protocolPopup}
-          show={this.popupStatus}
-          position="bottom"
-          style={{ height: '100%' }}
-        >
-          {this.showHeader && <ColHeader title="酷乐秀平台服务协议" />}
-          {this.popupStatus && (
-            <div class={styles.protocolContent} id="mProtocol">
-              <div
-                class={styles.protocolContent}
-                v-html={this.protocolHTML}
-              ></div>
-            </div>
-          )}
-        </Popup>
-      </div>
-    )
-  }
-})
+import { Checkbox, Icon, Popup } from 'vant'
+import { defineComponent, PropType } from 'vue'
+import styles from './index.module.less'
+import activeButtonIcon from '@common/images/icon_checkbox.png'
+import activeButtonIconTenant from '@common/images/icon_checkbox-tenant.png'
+import inactiveButtonIcon from '@common/images/icon_checkbox_default.png'
+import ColHeader from '../col-header'
+import { state } from '@/state'
+import request from '@/helpers/request'
+const protocolText = {
+  BUY_ORDER: '《酷乐秀平台服务协议》',
+  REGISTER: '《酷乐秀平台注册协议》'
+}
+export default defineComponent({
+  name: 'protocol',
+  props: {
+    showHeader: {
+      type: Boolean,
+      default: false
+    },
+    modelValue: {
+      type: Boolean,
+      default: false
+    },
+    prototcolType: {
+      type: String as PropType<'BUY_ORDER' | 'REGISTER'>,
+      default: 'BUY_ORDER'
+    }
+  },
+  data() {
+    return {
+      exists: true,
+      checked: this.modelValue,
+      popupStatus: false,
+      protocolHTML: '',
+      protocolPopup: null as any,
+      baseUrl:
+        state.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
+    }
+  },
+  async mounted() {
+    try {
+      const res = await request.get(
+        this.baseUrl + '/sysUserContractRecord/checkContractSign',
+        {
+          params: {
+            contractType: this.prototcolType
+          }
+        }
+      )
+      // console.log(res)
+      this.exists = res.data
+      this.checked = this.checked || this.exists
+      this.$emit('update:modelValue', this.checked || this.exists)
+    } catch {}
+    this.checked = this.modelValue
+    // this.getContractDetail()
+    window.addEventListener('hashchange', this.onHash, false)
+  },
+  unmounted() {
+    window.removeEventListener('hashchange', this.onHash, false)
+  },
+  watch: {
+    checked(val) {
+      this.$emit('update:modelValue', val)
+    }
+  },
+  methods: {
+    async getContractDetail() {
+      try {
+        console.log('getContractDetail')
+        // 判断是否有协议内容
+        if (!this.protocolHTML) {
+          const res = await request.get(
+            this.baseUrl + '/sysUserContractRecord/queryContract',
+            {
+              params: {
+                contractType: this.prototcolType
+              }
+            }
+          )
+          this.protocolHTML = res.data
+          console.log(res)
+        }
+        this.onPopupClose()
+      } catch {}
+    },
+    onHash() {
+      this.popupStatus = false
+    },
+    onPopupClose() {
+      this.popupStatus = !this.popupStatus
+
+      // 打开弹窗
+      if (this.popupStatus) {
+        const route = this.$route
+        let times = 0
+        for (const i in route.query) {
+          times += 1
+        }
+        const origin = window.location.href
+        const url = times > 0 ? '&pto=' + +new Date() : '?pto=' + +new Date()
+        history.pushState('', '', `${origin}${url}`)
+      } else {
+        window.history.go(-1)
+      }
+      if (this.protocolPopup) {
+        ;(this.protocolPopup as any).scrollTop = 0
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles.colProtocol}>
+        {!this.exists && (
+          <Checkbox
+            v-model={this.checked}
+            v-slots={{
+              icon: (props: any) => (
+                <Icon
+                  class={styles.boxStyle}
+                  name={
+                    props.checked
+                      ? state.projectType === 'tenant'
+                        ? activeButtonIconTenant
+                        : activeButtonIcon
+                      : inactiveButtonIcon
+                  }
+                  size="15"
+                />
+              )
+            }}
+          >
+            我已阅读并同意
+          </Checkbox>
+        )}
+        {this.exists && <>查看</>}
+        <span onClick={this.getContractDetail} class={styles.protocolText}>
+          {protocolText[this.prototcolType]}
+        </span>
+
+        <Popup
+          ref={this.protocolPopup}
+          show={this.popupStatus}
+          position="bottom"
+          style={{ height: '100%' }}
+        >
+          {this.showHeader && <ColHeader title="酷乐秀平台服务协议" />}
+          {this.popupStatus && (
+            <div class={styles.protocolContent} id="mProtocol">
+              <div
+                class={styles.protocolContent}
+                v-html={this.protocolHTML}
+              ></div>
+            </div>
+          )}
+        </Popup>
+      </div>
+    )
+  }
+})

BIN
src/components/col-result/images/empty_tenant.png


BIN
src/components/col-result/images/network_tenant.png


BIN
src/components/col-result/images/notFond_tenant.png


+ 49 - 33
src/components/col-result/index.module.less

@@ -1,33 +1,49 @@
-.col-result {
-  padding: 30px 14px 14px;
-  text-align: center;
-  margin: 0 auto;
-  .tips {
-    font-size: 14px;
-    color: #333;
-    padding: 20px 0;
-  }
-  .btn {
-    width: 55%;
-    margin: 0 auto;
-  }
-  .SMALL {
-    :global {
-      .van-empty__image {
-        width: 182px;
-        height: 161px;
-      }
-    }
-  }
-  .CERT {
-    :global {
-      .van-empty__image {
-        width: 260px;
-        height: 230px;
-      }
-      .van-empty__description {
-        padding: 0 30px;
-      }
-    }
-  }
-}
+.col-result {
+  padding: 30px 14px 14px;
+  text-align: center;
+  margin: 0 auto;
+
+  .tips {
+    font-size: 14px;
+    color: #333;
+    padding: 20px 0;
+  }
+
+  .btn {
+    width: 55%;
+    margin: 0 auto;
+  }
+
+  :global {
+    .van-empty__image {
+      width: 260px;
+      height: 230px;
+    }
+
+    .van-empty__description {
+      padding: 0 30px;
+    }
+  }
+
+  .SMALL {
+    :global {
+      .van-empty__image {
+        width: 182px;
+        height: 161px;
+      }
+    }
+  }
+
+  .CERT {
+    :global {
+      .van-empty__image {
+        width: 260px;
+        height: 230px;
+      }
+
+      .van-empty__description {
+        padding: 0 30px;
+      }
+    }
+  }
+}

+ 130 - 120
src/components/col-result/index.tsx

@@ -1,120 +1,130 @@
-import { defineComponent, PropType } from 'vue'
-import styles from './index.module.less'
-import empty from '@common/images/icon_nodata.png'
-import { Button, Empty, Image } from 'vant'
-import { postMessage } from '@/helpers/native-message'
-
-export const getAssetsHomeFile = (fileName: string) => {
-  const path = `./images/${fileName}`
-  const modules = import.meta.globEager('./images/*')
-  return modules[path].default
-}
-
-export default defineComponent({
-  name: 'col-result',
-  props: {
-    tips: {
-      type: String
-    },
-    type: {
-      // 空 | 达人认证 | 音乐人认证 | 直播认证
-      type: String as PropType<
-        | 'empty'
-        | 'teacherCert'
-        | 'musicCert'
-        | 'liveCert'
-        | 'error'
-        | 'network'
-        | 'search'
-        | 'emptyContent'
-        | 'notFond'
-      >,
-      default: 'empty'
-    },
-    classImgSize: {
-      type: String as PropType<'CERT' | 'SMALL'>,
-      default: ''
-    },
-    plain: {
-      type: Boolean,
-      default: false
-    },
-    btnStatus: {
-      type: Boolean,
-      default: true
-    },
-    buttonText: {
-      type: String,
-      default: '我知道了'
-    },
-    onClick: Function
-  },
-  methods: {
-    onResult() {
-      if (this.onClick) {
-        this.onClick()
-      } else {
-        postMessage({ api: 'back', content: {} })
-      }
-    }
-  },
-  computed: {
-    image() {
-      let image = null as any
-      switch (this.type) {
-        case 'teacherCert':
-          image = getAssetsHomeFile('teacherCert.png')
-          break
-        case 'musicCert':
-          image = getAssetsHomeFile('musicCert.png')
-          break
-        case 'liveCert':
-          image = getAssetsHomeFile('liveCert.png')
-          break
-        case 'emptyContent':
-          image = getAssetsHomeFile('emptyContent.png')
-          break
-        case 'error':
-          image = 'error'
-          break
-        case 'network':
-          image = getAssetsHomeFile('network.png')
-          break
-        case 'search':
-          image = 'search'
-          break
-        case 'notFond':
-          image = getAssetsHomeFile('notFond.png')
-          break
-        default:
-          image = getAssetsHomeFile('empty.png')
-          break
-      }
-      return image
-    }
-  },
-  render() {
-    return (
-      <div class={[styles['col-result'], 'col-result-container']}>
-        <Empty
-          image={this.image}
-          class={styles[this.classImgSize]}
-          description={this.tips}
-        />
-
-        {this.btnStatus ? (
-          <Button
-            class={styles.btn}
-            round
-            block
-            type="primary"
-            plain={this.plain}
-            onClick={this.onResult}
-          >
-            {this.buttonText}
-          </Button>
-        ) : null}
-      </div>
-    )
-  }
-})
+import { defineComponent, PropType } from 'vue'
+import styles from './index.module.less'
+import empty from '@common/images/icon_nodata.png'
+import { Button, Empty, Image } from 'vant'
+import { postMessage } from '@/helpers/native-message'
+import { state } from '@/state'
+
+export const getAssetsHomeFile = (fileName: string) => {
+  const path = `./images/${fileName}`
+  const modules = import.meta.globEager('./images/*')
+  return modules[path].default
+}
+
+export default defineComponent({
+  name: 'col-result',
+  props: {
+    tips: {
+      type: String
+    },
+    type: {
+      // 空 | 达人认证 | 音乐人认证 | 直播认证
+      type: String as PropType<
+        | 'empty'
+        | 'teacherCert'
+        | 'musicCert'
+        | 'liveCert'
+        | 'error'
+        | 'network'
+        | 'search'
+        | 'emptyContent'
+        | 'notFond'
+      >,
+      default: 'empty'
+    },
+    classImgSize: {
+      type: String as PropType<'CERT' | 'SMALL'>,
+      default: ''
+    },
+    plain: {
+      type: Boolean,
+      default: false
+    },
+    btnStatus: {
+      type: Boolean,
+      default: true
+    },
+    buttonText: {
+      type: String,
+      default: '我知道了'
+    },
+    onClick: Function
+  },
+  methods: {
+    onResult() {
+      if (this.onClick) {
+        this.onClick()
+      } else {
+        postMessage({ api: 'back', content: {} })
+      }
+    }
+  },
+  computed: {
+    image() {
+      let image = null as any
+      switch (this.type) {
+        case 'teacherCert':
+          image = getAssetsHomeFile('teacherCert.png')
+          break
+        case 'musicCert':
+          image = getAssetsHomeFile('musicCert.png')
+          break
+        case 'liveCert':
+          image = getAssetsHomeFile('liveCert.png')
+          break
+        case 'emptyContent':
+          image = getAssetsHomeFile('emptyContent.png')
+          break
+        case 'error':
+          image = 'error'
+          break
+        case 'network':
+          image =
+            state.projectType === 'tenant'
+              ? getAssetsHomeFile('network_tenant.png')
+              : getAssetsHomeFile('network.png')
+          break
+        case 'search':
+          image = 'search'
+          break
+        case 'notFond':
+          image =
+            state.projectType === 'tenant'
+              ? getAssetsHomeFile('notFond_tenant.png')
+              : getAssetsHomeFile('notFond.png')
+          break
+        default:
+          image =
+            state.projectType === 'tenant'
+              ? getAssetsHomeFile('empty_tenant.png')
+              : getAssetsHomeFile('empty.png')
+          break
+      }
+      return image
+    }
+  },
+  render() {
+    return (
+      <div class={[styles['col-result'], 'col-result-container']}>
+        <Empty
+          image={this.image}
+          class={styles[this.classImgSize]}
+          description={this.tips}
+        />
+
+        {this.btnStatus ? (
+          <Button
+            class={styles.btn}
+            round
+            block
+            type="primary"
+            plain={this.plain}
+            onClick={this.onResult}
+          >
+            {this.buttonText}
+          </Button>
+        ) : null}
+      </div>
+    )
+  }
+})

+ 34 - 1
src/components/col-search/index.module.less

@@ -2,18 +2,22 @@
   .van-search {
     padding-left: 14px;
     padding-right: 14px;
+
     input {
       -webkit-user-select: text !important;
       user-select: text !important;
     }
+
     .van-search__field {
       padding: 0.13333rem var(--van-padding-xs) 0.13333rem 0 !important;
       background: transparent !important;
     }
   }
 }
+
 .col-search {
   --van-cell-background-color: transparent;
+
   // padding-left: 14px;
   // padding-right: 14px;
   :global {
@@ -21,16 +25,20 @@
       display: flex;
       align-items: center;
     }
+
     .van-field__right-icon {
       font-size: 0;
     }
+
     .van-search__action {
       display: flex;
     }
+
     .van-field__control {
       font-size: 14px;
     }
   }
+
   &.default {
     :global {
       .van-search__content {
@@ -39,6 +47,20 @@
     }
   }
 
+  &.col-tenant-search {
+    &.default {
+      :global {
+        .van-search__content {
+          background: #f5f6f8;
+        }
+
+        .van-field__control {
+          color: #aaa;
+        }
+      }
+    }
+  }
+
   &.white {
     :global {
       .van-search__content {
@@ -51,12 +73,15 @@
     :global {
       .van-search__content {
         background: rgba(255, 255, 255, 0.16);
+
         input::placeholder {
           color: #fff;
         }
+
         input {
           color: #fff;
         }
+
         .van-field__clear {
           color: #fff;
         }
@@ -71,5 +96,13 @@
     font-size: 14px;
     --van-button-mini-height: 28px;
     --van-font-size-xs: 14px;
+    line-height: 1;
+
+    &.searchTenantBtn {
+      background: linear-gradient(270deg, #FF3C81 0%, #FF76A6 100%);
+      border: none;
+
+
+    }
   }
-}
+}

+ 35 - 14
src/components/col-search/index.tsx

@@ -2,6 +2,7 @@ import { Button, Icon, Search } from 'vant'
 import { defineComponent, PropType } from 'vue'
 import styles from './index.module.less'
 import iconSearch from '@common/images/icon_search.png'
+import iconSearchTenant from '@/tenant/images/icon-search.png'
 import iconFilter from '@common/images/icon_filter.png'
 
 type inputBackground = 'default' | 'white' | 'transparent'
@@ -9,6 +10,10 @@ type inputBackground = 'default' | 'white' | 'transparent'
 export default defineComponent({
   name: 'ColSearch',
   props: {
+    type: {
+      type: String as PropType<'person' | 'tenant'>,
+      default: ''
+    },
     modelValue: {
       type: String,
       default: ''
@@ -54,7 +59,7 @@ export default defineComponent({
       default: iconSearch
     }
   },
-  emits: ['click'],
+  emits: ['click', 'input'],
   watch: {
     modelValue() {
       this.search = this.modelValue
@@ -69,7 +74,11 @@ export default defineComponent({
     return (
       <div>
         <Search
-          class={[styles['col-search'], styles[this.inputBackground]]}
+          class={[
+            styles['col-search'],
+            this.type === 'tenant' && styles['col-tenant-search'],
+            styles[this.inputBackground]
+          ]}
           v-model={this.search}
           background={this.background}
           showAction={this.showAction}
@@ -77,6 +86,9 @@ export default defineComponent({
           placeholder={this.placeholder}
           disabled={this.disabled}
           autofocus={this.autofocus}
+          onUpdate:modelValue={(val: any) => {
+            this.$emit('input', val)
+          }}
           onSearch={(val: string) => {
             this.onSearch(val)
           }}
@@ -87,10 +99,18 @@ export default defineComponent({
           onClick={() => this.$emit('click')}
           v-slots={{
             left: () => this.$slots.left && this.$slots.left(),
-            'left-icon': () => <Icon name={this.leftIcon} size={16} />,
+            'left-icon': () => (
+              <Icon
+                name={this.type === 'tenant' ? iconSearchTenant : this.leftIcon}
+                size={16}
+              />
+            ),
             'right-icon': () => (
               <Button
-                class={styles.searchBtn}
+                class={[
+                  styles.searchBtn,
+                  this.type === 'tenant' && styles.searchTenantBtn
+                ]}
                 round
                 type="primary"
                 size="mini"
@@ -101,16 +121,17 @@ export default defineComponent({
                 搜索
               </Button>
             ),
-            action: () => (
-              <Icon
-                name={iconFilter}
-                size={28}
-                dot={this.filterDot}
-                onClick={() => {
-                  this.onFilter()
-                }}
-              />
-            )
+            action: () =>
+              (this.$slots.action && this.$slots.action()) || (
+                <Icon
+                  name={iconFilter}
+                  size={28}
+                  dot={this.filterDot}
+                  onClick={() => {
+                    this.onFilter()
+                  }}
+                />
+              )
           }}
         />
       </div>

+ 42 - 0
src/components/col-share/index.module.less

@@ -4,25 +4,30 @@
   .swipe__indicators {
     color: #fff;
   }
+
   .indicators {
     display: flex;
     justify-content: center;
     align-items: center;
     margin-top: 10px;
   }
+
   .swipe__indicator {
     width: 48px;
     height: 4px;
     background-color: #fff;
     opacity: 0.5;
     border-radius: 0;
+
     &:first-child {
       border-radius: 4px 0 0 4px;
     }
+
     &:last-child {
       border-radius: 0 4px 4px 0;
     }
   }
+
   .swipe__indicator--active {
     opacity: 1;
   }
@@ -38,9 +43,11 @@
     .van-swipe {
       transform: translateZ(0);
     }
+
     .van-swipe-item {
       // margin: 0 5px;
     }
+
     // .van-swipe__indicators {
     //   border-radius: 2px;
     //   overflow: hidden;
@@ -68,6 +75,7 @@
     color: #333333;
     line-height: 35px;
   }
+
   .titleTip {
     padding-top: 5px;
     font-size: 14px;
@@ -82,18 +90,21 @@
   background: linear-gradient(270deg, #baffe7 0%, #c0dcff 100%);
   border-radius: 9px;
   color: #333;
+
   .teacherImg {
     margin-right: 12px;
     position: relative;
     width: 40px;
     text-align: center;
   }
+
   .recommend {
     position: absolute;
     height: 14px;
     left: 0;
     bottom: 3px;
   }
+
   .img {
     width: 33px;
     height: 33px;
@@ -120,49 +131,61 @@
   margin: 0 auto;
   box-sizing: border-box;
   background: #fff;
+
   &.video {
     background: url('./images/video1.png') no-repeat top center #fff;
     background-size: cover;
+
     &.yellow {
       background: url('./images/video2.png') no-repeat top center #fff;
       background-size: cover;
     }
+
     &.pink {
       background: url('./images/video3.png') no-repeat top center #fff;
       background-size: cover;
     }
   }
+
   &.live {
     background: url('./images/live1.png') no-repeat top center #fff;
     background-size: cover;
+
     &.yellow {
       background: url('./images/live2.png') no-repeat top center #fff;
       background-size: cover;
     }
+
     &.pink {
       background: url('./images/live3.png') no-repeat top center #fff;
       background-size: cover;
     }
   }
+
   &.mall {
     background: url('./images/shop1.png') no-repeat top center #fff;
     background-size: cover;
+
     &.yellow {
       background: url('./images/shop2.png') no-repeat top center #fff;
       background-size: cover;
     }
+
     &.pink {
       background: url('./images/shop3.png') no-repeat top center #fff;
       background-size: cover;
     }
   }
+
   &.music {
     background: url('./images/music1.png') no-repeat top center #fff;
     background-size: cover;
+
     &.yellow {
       background: url('./images/music2.png') no-repeat top center #fff;
       background-size: cover;
     }
+
     &.pink {
       background: url('./images/music3.png') no-repeat top center #fff;
       background-size: cover;
@@ -172,11 +195,13 @@
   &.vip {
     background: url('./images/vip1.png') no-repeat top center #fff;
     background-size: cover;
+
     &.yellow {
       background: url('./images/vip2.png') no-repeat top center #fff;
       background-size: cover;
     }
   }
+
   &.album {
     background: url('./images/album1.png') no-repeat top center #fff;
     background-size: contain;
@@ -192,6 +217,7 @@
   display: flex;
   align-items: center;
   justify-content: space-between;
+
   .logo {
     margin-left: 12px;
     padding-left: 14px;
@@ -201,6 +227,7 @@
     flex: 1;
     border-left: 1px solid #ccc;
     font-weight: 500;
+
     img {
       height: 20px;
       vertical-align: middle;
@@ -242,7 +269,22 @@
   align-items: center;
   justify-content: space-between;
   padding-top: 12px;
+
   :global(.van-button) {
     padding: 8px 46px;
   }
+
+  &.shareGroupTenantBtn {
+    :global {
+      .van-button--primary {
+        background: linear-gradient(270deg, #FF204B 0%, #FE5B71 100%);
+        border: none;
+      }
+
+      .van-button--plain.van-button--primary {
+        color: #FF204B;
+        background: #fff !important;
+      }
+    }
+  }
 }

+ 11 - 1
src/components/col-share/index.tsx

@@ -9,6 +9,10 @@ import request from '@/helpers/request'
 export default defineComponent({
   name: 'col-share',
   props: {
+    type: {
+      type: String as PropType<'default' | 'tenant'>,
+      default: 'default'
+    },
     teacherId: {
       type: Number
     },
@@ -254,7 +258,13 @@ export default defineComponent({
               </Swipe>
             </div>
 
-            <div class={['btnGroup', styles.shareGroupBtn]}>
+            <div
+              class={[
+                'btnGroup',
+                styles.shareGroupBtn,
+                this.type === 'tenant' ? styles.shareGroupTenantBtn : ''
+              ]}
+            >
               <Button
                 type="primary"
                 plain

+ 214 - 215
src/components/col-upload-video/index.tsx

@@ -1,215 +1,214 @@
-import request from '@/helpers/request'
-import { Icon, Toast, Uploader, Image } from 'vant'
-import { defineComponent } from 'vue'
-import styles from './index.module.less'
-import { useCustomFieldValue } from '@vant/use'
-import { browser } from '@/helpers/utils'
-import umiRequest from 'umi-request'
-import iconUploader from '@common/images/icon_uploader_video.png'
-import iconUploadPoster from '@common/images/icon_upload_poster.png'
-import { postMessage } from '@/helpers/native-message'
-import { getOssUploadUrl, state } from '@/state'
-
-export default defineComponent({
-  name: 'ColUploadVideo',
-  props: {
-    modelValue: String,
-    posterUrl: String,
-    tips: {
-      type: String,
-      default: '点击上传'
-    },
-    nativeUpload: {
-      // 是否使用原生上传, 且当前环境为app才会生效
-      type: Boolean,
-      default: true
-    },
-    size: {
-      type: Number,
-      default: 30
-    },
-    deletable: {
-      type: Boolean,
-      default: true
-    },
-    bucket: {
-      type: String,
-      default: 'daya'
-    }
-  },
-  methods: {
-    beforeRead(file: any) {
-      const isLt2M = file.size / 1024 / 1024 < this.size
-      // console.log(this.size)
-      if (!isLt2M) {
-        Toast(`上传视频大小不能超过 ${this.size}MB`)
-        return false
-      }
-      return true
-    },
-    beforeDelete(file: any, detail: { index: any }) {
-      // this.dataModel.splice(detail.index, 1)
-      return true
-    },
-    async afterRead(file: any, detail: any) {
-      try {
-        file.status = 'uploading'
-        file.message = '上传中...'
-        // 获取签名
-        const signUrl =
-          state.platformType === 'TEACHER'
-            ? '/api-teacher/getUploadSign'
-            : '/api-student/getUploadSign'
-
-        const fileName = file.file.name.replaceAll(' ', '_')
-        const key = new Date().getTime() + fileName
-        console.log(file)
-
-        const res = await request.post(signUrl, {
-          data: {
-            filename: fileName,
-            bucketName: this.bucket,
-            postData: {
-              filename: fileName,
-              acl: 'public-read',
-              key: key,
-              unknowValueField: []
-            }
-          }
-        })
-        Toast.loading({
-          message: '加载中...',
-          forbidClick: true,
-          loadingType: 'spinner',
-          duration: 0
-        })
-        const obj = {
-          policy: res.data.policy,
-          signature: res.data.signature,
-          key: key,
-          KSSAccessKeyId: res.data.kssAccessKeyId,
-          acl: 'public-read',
-          name: fileName
-        }
-        let formData = new FormData()
-        for (let key in obj) {
-          formData.append(key, obj[key])
-        }
-        formData.append('file', file.file)
-        await umiRequest(getOssUploadUrl(this.bucket), {
-          method: 'POST',
-          data: formData
-        })
-        const uploadUrl = getOssUploadUrl(this.bucket) + key
-        Toast.clear()
-        this.$emit('update:modelValue', uploadUrl)
-        // this.onUploadChange(uploadUrl)
-        // let formData = new FormData()
-        // formData.append('file', file.file)
-        // let res = await request.post('/api-teacher/uploadFile', {
-        //   data: formData
-        // })
-        // const url = res.data.url
-        // this.$emit('update:modelValue', uploadUrl)
-      } catch (error) {
-        //
-        console.log(error)
-      }
-    },
-    onClose(e: any) {
-      this.$emit('update:modelValue', null)
-      e.stopPropagation()
-    },
-    onNativeUpload() {
-      postMessage(
-        { api: 'chooseFile', content: { type: 'video', bucket: this.bucket } },
-        (res: any) => {
-          // this.posterUrlInner = res.firstFrameImg
-          this.$emit('update:modelValue', res.fileUrl)
-          // this.$emit('update:posterUrl', res.firstFrameImg)
-        }
-      )
-    },
-    getVideoBase64(url: string) {
-      return new Promise(function (resolve) {
-        let dataURL = ''
-        const video = document.createElement('video')
-        video.setAttribute('crossOrigin', 'anonymous') // 处理跨域
-        video.setAttribute('src', url)
-        video.setAttribute('preload', 'auto')
-        video.addEventListener('loadeddata', function () {
-          console.log(video, 'video loadeddata')
-          const canvas = document.createElement('canvas')
-          console.log('video.clientWidth', video.videoWidth) // 视频宽
-          console.log('video.clientHeight', video.videoHeight) // 视频高
-          const width = video.videoWidth || 750 // canvas的尺寸和图片一样
-          const height = video.videoHeight || 500 // 设置默认宽高为 750 * 500
-          canvas.width = width
-          canvas.height = height
-          ;(canvas as any)
-            .getContext('2d')
-            .drawImage(video, 0, 0, width, height) // 绘制canvas
-          dataURL = canvas.toDataURL('image/jpeg') // 转换为base64
-          resolve(dataURL)
-        })
-      })
-    }
-  },
-  render() {
-    useCustomFieldValue(() => this.modelValue)
-    return (
-      <div class={styles['uploader-section']}>
-        {this.modelValue && this.deletable ? (
-          <Icon
-            name="cross"
-            onClick={this.onClose}
-            class={styles['img-close']}
-          />
-        ) : null}
-        {browser().isApp && this.nativeUpload ? (
-          <div onClick={this.onNativeUpload} style={{ height: '100%' }}>
-            {this.modelValue ? (
-              <video
-                ref="videoUpload"
-                class={styles.uploadImg}
-                src={this.modelValue + '#t=1,4'}
-                // poster={iconUploadPoster}
-              />
-            ) : (
-              <div class={styles.uploader}>
-                <Icon name={iconUploader} size="32" />
-                <p class={styles.uploaderText}>{this.tips}</p>
-              </div>
-            )}
-          </div>
-        ) : (
-          <>
-            {/* @ts-ignore */}
-            <Uploader
-              accept=".mp4"
-              afterRead={this.afterRead}
-              beforeRead={this.beforeRead}
-              beforeDelete={this.beforeDelete}
-              v-slots={{
-                default: () =>
-                  this.modelValue ? (
-                    <video
-                      ref="videoUpload"
-                      class={styles.uploadImg}
-                      src={this.modelValue + '#t=1,4'}
-                      // poster={this.posterUrl || ''}
-                    />
-                  ) : (
-                    <div class={styles.uploader}>
-                      <Icon name={iconUploader} size="32" />
-                      <p class={styles.uploaderText}>{this.tips}</p>
-                    </div>
-                  )
-              }}
-            />
-          </>
-        )}
-      </div>
-    )
-  }
-})
+import request from '@/helpers/request'
+import { Icon, Toast, Uploader, Image } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import { useCustomFieldValue } from '@vant/use'
+import { browser } from '@/helpers/utils'
+import umiRequest from 'umi-request'
+import iconUploader from '@common/images/icon_uploader_video.png'
+import iconUploadPoster from '@common/images/icon_upload_poster.png'
+import { postMessage } from '@/helpers/native-message'
+import { getOssUploadUrl, state } from '@/state'
+
+export default defineComponent({
+  name: 'ColUploadVideo',
+  props: {
+    modelValue: String,
+    posterUrl: String,
+    tips: {
+      type: String,
+      default: '点击上传'
+    },
+    nativeUpload: {
+      // 是否使用原生上传, 且当前环境为app才会生效
+      type: Boolean,
+      default: true
+    },
+    size: {
+      type: Number,
+      default: 30
+    },
+    deletable: {
+      type: Boolean,
+      default: true
+    },
+    bucket: {
+      type: String,
+      default: 'daya'
+    }
+  },
+  methods: {
+    beforeRead(file: any) {
+      const isLt2M = file.size / 1024 / 1024 < this.size
+      // console.log(this.size)
+      if (!isLt2M) {
+        Toast(`上传视频大小不能超过 ${this.size}MB`)
+        return false
+      }
+      return true
+    },
+    beforeDelete(file: any, detail: { index: any }) {
+      // this.dataModel.splice(detail.index, 1)
+      return true
+    },
+    async afterRead(file: any, detail: any) {
+      try {
+        file.status = 'uploading'
+        file.message = '上传中...'
+        // 获取签名
+        const signUrl =
+          state.platformType === 'TEACHER'
+            ? '/api-teacher/getUploadSign'
+            : '/api-student/getUploadSign'
+
+        const fileName = file.file.name.replaceAll(' ', '_')
+        const key = new Date().getTime() + fileName
+        console.log(file)
+
+        const res = await request.post(signUrl, {
+          data: {
+            filename: fileName,
+            bucketName: this.bucket,
+            postData: {
+              filename: fileName,
+              acl: 'public-read',
+              key: key
+            }
+          }
+        })
+        Toast.loading({
+          message: '加载中...',
+          forbidClick: true,
+          loadingType: 'spinner',
+          duration: 0
+        })
+        const obj = {
+          policy: res.data.policy,
+          signature: res.data.signature,
+          key: key,
+          KSSAccessKeyId: res.data.kssAccessKeyId,
+          acl: 'public-read',
+          name: fileName
+        }
+        const formData = new FormData()
+        for (const key in obj) {
+          formData.append(key, obj[key])
+        }
+        formData.append('file', file.file)
+        await umiRequest(getOssUploadUrl(this.bucket), {
+          method: 'POST',
+          data: formData
+        })
+        const uploadUrl = getOssUploadUrl(this.bucket) + key
+        Toast.clear()
+        this.$emit('update:modelValue', uploadUrl)
+        // this.onUploadChange(uploadUrl)
+        // let formData = new FormData()
+        // formData.append('file', file.file)
+        // let res = await request.post('/api-teacher/uploadFile', {
+        //   data: formData
+        // })
+        // const url = res.data.url
+        // this.$emit('update:modelValue', uploadUrl)
+      } catch (error) {
+        //
+        console.log(error)
+      }
+    },
+    onClose(e: any) {
+      this.$emit('update:modelValue', null)
+      e.stopPropagation()
+    },
+    onNativeUpload() {
+      postMessage(
+        { api: 'chooseFile', content: { type: 'video', bucket: this.bucket } },
+        (res: any) => {
+          // this.posterUrlInner = res.firstFrameImg
+          this.$emit('update:modelValue', res.fileUrl)
+          // this.$emit('update:posterUrl', res.firstFrameImg)
+        }
+      )
+    },
+    getVideoBase64(url: string) {
+      return new Promise(function (resolve) {
+        let dataURL = ''
+        const video = document.createElement('video')
+        video.setAttribute('crossOrigin', 'anonymous') // 处理跨域
+        video.setAttribute('src', url)
+        video.setAttribute('preload', 'auto')
+        video.addEventListener('loadeddata', function () {
+          console.log(video, 'video loadeddata')
+          const canvas = document.createElement('canvas')
+          console.log('video.clientWidth', video.videoWidth) // 视频宽
+          console.log('video.clientHeight', video.videoHeight) // 视频高
+          const width = video.videoWidth || 750 // canvas的尺寸和图片一样
+          const height = video.videoHeight || 500 // 设置默认宽高为 750 * 500
+          canvas.width = width
+          canvas.height = height
+          ;(canvas as any)
+            .getContext('2d')
+            .drawImage(video, 0, 0, width, height) // 绘制canvas
+          dataURL = canvas.toDataURL('image/jpeg') // 转换为base64
+          resolve(dataURL)
+        })
+      })
+    }
+  },
+  render() {
+    useCustomFieldValue(() => this.modelValue)
+    return (
+      <div class={styles['uploader-section']}>
+        {this.modelValue && this.deletable ? (
+          <Icon
+            name="cross"
+            onClick={this.onClose}
+            class={styles['img-close']}
+          />
+        ) : null}
+        {browser().isApp && this.nativeUpload ? (
+          <div onClick={this.onNativeUpload} style={{ height: '100%' }}>
+            {this.modelValue ? (
+              <video
+                ref="videoUpload"
+                class={styles.uploadImg}
+                src={this.modelValue + '#t=1,4'}
+                // poster={iconUploadPoster}
+              />
+            ) : (
+              <div class={styles.uploader}>
+                <Icon name={iconUploader} size="32" />
+                <p class={styles.uploaderText}>{this.tips}</p>
+              </div>
+            )}
+          </div>
+        ) : (
+          <>
+            {/* @ts-ignore */}
+            <Uploader
+              accept=".mp4"
+              afterRead={this.afterRead}
+              beforeRead={this.beforeRead}
+              beforeDelete={this.beforeDelete}
+              v-slots={{
+                default: () =>
+                  this.modelValue ? (
+                    <video
+                      ref="videoUpload"
+                      class={styles.uploadImg}
+                      src={this.modelValue + '#t=1,4'}
+                      // poster={this.posterUrl || ''}
+                    />
+                  ) : (
+                    <div class={styles.uploader}>
+                      <Icon name={iconUploader} size="32" />
+                      <p class={styles.uploaderText}>{this.tips}</p>
+                    </div>
+                  )
+              }}
+            />
+          </>
+        )}
+      </div>
+    )
+  }
+})

+ 237 - 238
src/components/col-upload/index.tsx

@@ -1,238 +1,237 @@
-import { Icon, Image, Toast, Uploader } from 'vant'
-import { defineComponent } from 'vue'
-import styles from './index.module.less'
-import ColCropper from '../col-cropper'
-import { useCustomFieldValue } from '@vant/use'
-import { postMessage } from '@/helpers/native-message'
-import umiRequest from 'umi-request'
-import iconUploader from '@common/images/icon_uploader.png'
-import request from '@/helpers/request'
-import { getOssUploadUrl, state } from '@/state'
-
-export default defineComponent({
-  name: 'col-upload',
-  props: {
-    modelValue: String,
-    tips: {
-      type: String,
-      default: '点击上传'
-    },
-    deletable: {
-      type: Boolean,
-      default: true
-    },
-    native: {
-      // 是否原生上传
-      type: Boolean,
-      default: false
-    },
-    cropper: {
-      // 是否进行裁切
-      type: Boolean,
-      default: false
-    },
-    options: {
-      // 裁切需要参数
-      type: Object,
-      default: {}
-    },
-    uploadSize: {
-      // 上传图片大小
-      type: Number,
-      default: 5
-    },
-    onUploadChange: {
-      type: Function,
-      default: (url: string) => {}
-    },
-    bucket: {
-      type: String,
-      default: 'daya'
-    }
-  },
-  methods: {
-    nativeUpload() {
-      postMessage(
-        {
-          api: 'chooseFile',
-          content: { type: 'img', max: 1, bucket: this.bucket }
-        },
-        (res: any) => {
-          console.log(res, 'fileUrl')
-          this.$emit('update:modelValue', res.fileUrl)
-        }
-      )
-    },
-    beforeRead(file: any) {
-      console.log(file, 'beforeRead')
-      const isLt2M = file.size / 1024 / 1024 < this.uploadSize
-      if (!isLt2M) {
-        Toast(`上传文件大小不能超过 ${this.uploadSize}MB`)
-        return false
-      }
-      return true
-    },
-    beforeDelete(file: any, detail: { index: any }) {
-      // this.dataModel.splice(detail.index, 1)
-      return true
-    },
-    async afterRead(file: any, detail: any) {
-      try {
-        file.status = 'uploading'
-        file.message = '上传中...'
-        await this.uploadFile(file.file)
-      } catch (error) {
-        //
-        console.log(error, '2323')
-        Toast.clear()
-      }
-    },
-    onClose(e: any) {
-      this.$emit('update:modelValue', null)
-      this.onUploadChange()
-      e.stopPropagation()
-    },
-    async getFile(file: any) {
-      try {
-        await this.uploadFile(file)
-      } catch {
-        //
-      }
-    },
-    async uploadFile(file: any) {
-      // 上传文件
-      try {
-        // 获取签名
-        const signUrl =
-          state.platformType === 'TEACHER'
-            ? '/api-teacher/getUploadSign'
-            : '/api-student/getUploadSign'
-        let tempName = file.name || ''
-        const fileName = tempName && tempName.replace(/ /gi, '_')
-        const key = new Date().getTime() + fileName
-        console.log(file)
-
-        const res = await request.post(signUrl, {
-          data: {
-            filename: fileName,
-            bucketName: this.bucket,
-            postData: {
-              filename: fileName,
-              acl: 'public-read',
-              key: key,
-              unknowValueField: []
-            }
-          }
-        })
-        Toast.loading({
-          message: '加载中...',
-          forbidClick: true,
-          loadingType: 'spinner',
-          duration: 0
-        })
-        const obj = {
-          policy: res.data.policy,
-          signature: res.data.signature,
-          key: key,
-          KSSAccessKeyId: res.data.kssAccessKeyId,
-          acl: 'public-read',
-          name: fileName
-        }
-        let formData = new FormData()
-        for (let key in obj) {
-          formData.append(key, obj[key])
-        }
-        formData.append('file', file, fileName)
-        await umiRequest(getOssUploadUrl(this.bucket), {
-          method: 'POST',
-          data: formData
-        })
-        console.log(getOssUploadUrl(this.bucket) + key)
-        const uploadUrl = getOssUploadUrl(this.bucket) + key
-        Toast.clear()
-        this.$emit('update:modelValue', uploadUrl)
-        this.onUploadChange(uploadUrl)
-      } catch (error) {
-        console.log(error, 'uploadFile')
-      }
-    }
-  },
-  render() {
-    useCustomFieldValue(() => this.modelValue)
-    return (
-      <div class={styles['uploader-section']}>
-        {this.modelValue && this.deletable ? (
-          <Icon
-            name="cross"
-            onClick={this.onClose}
-            class={styles['img-close']}
-          />
-        ) : null}
-        {this.cropper && !this.native ? (
-          <div class={styles['col-uploader']}>
-            {this.modelValue ? (
-              <Image
-                fit="cover"
-                position="center"
-                class={styles.uploadImg}
-                src={this.modelValue}
-              />
-            ) : (
-              <div class={styles.uploader}>
-                <Icon name={iconUploader} size="32" />
-                <p class={styles.uploaderText}>{this.tips}</p>
-              </div>
-            )}
-            <ColCropper option={this.options} getFile={this.getFile} />
-          </div>
-        ) : this.native ? (
-          <div
-            style={{
-              display: 'flex',
-              alignItems: 'center',
-              justifyContent: 'center',
-              height: '100%'
-            }}
-            onClick={this.nativeUpload}
-          >
-            {this.modelValue ? (
-              <Image
-                fit="cover"
-                position="center"
-                class={styles.uploadImg}
-                src={this.modelValue}
-              />
-            ) : (
-              <div class={styles.uploader}>
-                <Icon name={iconUploader} size="32" />
-                <p class={styles.uploaderText}>{this.tips}</p>
-              </div>
-            )}
-          </div>
-        ) : (
-          <Uploader
-            afterRead={this.afterRead}
-            beforeRead={this.beforeRead}
-            beforeDelete={this.beforeDelete}
-            v-slots={{
-              default: () =>
-                this.modelValue ? (
-                  <Image
-                    fit="cover"
-                    position="center"
-                    class={styles.uploadImg}
-                    src={this.modelValue}
-                  />
-                ) : (
-                  <div class={styles.uploader}>
-                    <Icon name={iconUploader} size="32" />
-                    <p class={styles.uploaderText}>{this.tips}</p>
-                  </div>
-                )
-            }}
-          />
-        )}
-      </div>
-    )
-  }
-})
+import { Icon, Image, Toast, Uploader } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import ColCropper from '../col-cropper'
+import { useCustomFieldValue } from '@vant/use'
+import { postMessage } from '@/helpers/native-message'
+import umiRequest from 'umi-request'
+import iconUploader from '@common/images/icon_uploader.png'
+import request from '@/helpers/request'
+import { getOssUploadUrl, state } from '@/state'
+
+export default defineComponent({
+  name: 'col-upload',
+  props: {
+    modelValue: String,
+    tips: {
+      type: String,
+      default: '点击上传'
+    },
+    deletable: {
+      type: Boolean,
+      default: true
+    },
+    native: {
+      // 是否原生上传
+      type: Boolean,
+      default: false
+    },
+    cropper: {
+      // 是否进行裁切
+      type: Boolean,
+      default: false
+    },
+    options: {
+      // 裁切需要参数
+      type: Object,
+      default: {}
+    },
+    uploadSize: {
+      // 上传图片大小
+      type: Number,
+      default: 5
+    },
+    onUploadChange: {
+      type: Function,
+      default: (url: string) => {}
+    },
+    bucket: {
+      type: String,
+      default: 'daya'
+    }
+  },
+  methods: {
+    nativeUpload() {
+      postMessage(
+        {
+          api: 'chooseFile',
+          content: { type: 'img', max: 1, bucket: this.bucket }
+        },
+        (res: any) => {
+          console.log(res, 'fileUrl')
+          this.$emit('update:modelValue', res.fileUrl)
+        }
+      )
+    },
+    beforeRead(file: any) {
+      console.log(file, 'beforeRead')
+      const isLt2M = file.size / 1024 / 1024 < this.uploadSize
+      if (!isLt2M) {
+        Toast(`上传文件大小不能超过 ${this.uploadSize}MB`)
+        return false
+      }
+      return true
+    },
+    beforeDelete(file: any, detail: { index: any }) {
+      // this.dataModel.splice(detail.index, 1)
+      return true
+    },
+    async afterRead(file: any, detail: any) {
+      try {
+        file.status = 'uploading'
+        file.message = '上传中...'
+        await this.uploadFile(file.file)
+      } catch (error) {
+        //
+        console.log(error, '2323')
+        Toast.clear()
+      }
+    },
+    onClose(e: any) {
+      this.$emit('update:modelValue', null)
+      this.onUploadChange()
+      e.stopPropagation()
+    },
+    async getFile(file: any) {
+      try {
+        await this.uploadFile(file)
+      } catch {
+        //
+      }
+    },
+    async uploadFile(file: any) {
+      // 上传文件
+      try {
+        // 获取签名
+        const signUrl =
+          state.platformType === 'TEACHER'
+            ? '/api-teacher/getUploadSign'
+            : '/api-student/getUploadSign'
+        const tempName = file.name || ''
+        const fileName = tempName && tempName.replace(/ /gi, '_')
+        const key = new Date().getTime() + fileName
+        console.log(file)
+
+        const res = await request.post(signUrl, {
+          data: {
+            filename: fileName,
+            bucketName: this.bucket,
+            postData: {
+              filename: fileName,
+              acl: 'public-read',
+              key: key
+            }
+          }
+        })
+        Toast.loading({
+          message: '加载中...',
+          forbidClick: true,
+          loadingType: 'spinner',
+          duration: 0
+        })
+        const obj = {
+          policy: res.data.policy,
+          signature: res.data.signature,
+          key: key,
+          KSSAccessKeyId: res.data.kssAccessKeyId,
+          acl: 'public-read',
+          name: fileName
+        }
+        const formData = new FormData()
+        for (const key in obj) {
+          formData.append(key, obj[key])
+        }
+        formData.append('file', file, fileName)
+        await umiRequest(getOssUploadUrl(this.bucket), {
+          method: 'POST',
+          data: formData
+        })
+        console.log(getOssUploadUrl(this.bucket) + key)
+        const uploadUrl = getOssUploadUrl(this.bucket) + key
+        Toast.clear()
+        this.$emit('update:modelValue', uploadUrl)
+        this.onUploadChange(uploadUrl)
+      } catch (error) {
+        console.log(error, 'uploadFile')
+      }
+    }
+  },
+  render() {
+    useCustomFieldValue(() => this.modelValue)
+    return (
+      <div class={styles['uploader-section']}>
+        {this.modelValue && this.deletable ? (
+          <Icon
+            name="cross"
+            onClick={this.onClose}
+            class={styles['img-close']}
+          />
+        ) : null}
+        {this.cropper && !this.native ? (
+          <div class={styles['col-uploader']}>
+            {this.modelValue ? (
+              <Image
+                fit="cover"
+                position="center"
+                class={styles.uploadImg}
+                src={this.modelValue}
+              />
+            ) : (
+              <div class={styles.uploader}>
+                <Icon name={iconUploader} size="32" />
+                <p class={styles.uploaderText}>{this.tips}</p>
+              </div>
+            )}
+            <ColCropper option={this.options} getFile={this.getFile} />
+          </div>
+        ) : this.native ? (
+          <div
+            style={{
+              display: 'flex',
+              alignItems: 'center',
+              justifyContent: 'center',
+              height: '100%'
+            }}
+            onClick={this.nativeUpload}
+          >
+            {this.modelValue ? (
+              <Image
+                fit="cover"
+                position="center"
+                class={styles.uploadImg}
+                src={this.modelValue}
+              />
+            ) : (
+              <div class={styles.uploader}>
+                <Icon name={iconUploader} size="32" />
+                <p class={styles.uploaderText}>{this.tips}</p>
+              </div>
+            )}
+          </div>
+        ) : (
+          <Uploader
+            afterRead={this.afterRead}
+            beforeRead={this.beforeRead}
+            beforeDelete={this.beforeDelete}
+            v-slots={{
+              default: () =>
+                this.modelValue ? (
+                  <Image
+                    fit="cover"
+                    position="center"
+                    class={styles.uploadImg}
+                    src={this.modelValue}
+                  />
+                ) : (
+                  <div class={styles.uploader}>
+                    <Icon name={iconUploader} size="32" />
+                    <p class={styles.uploaderText}>{this.tips}</p>
+                  </div>
+                )
+            }}
+          />
+        )}
+      </div>
+    )
+  }
+})

File diff suppressed because it is too large
+ 0 - 0
src/components/the-full-refresh/datas/data.json


+ 21 - 0
src/components/the-full-refresh/index.module.less

@@ -0,0 +1,21 @@
+.animateWrap {
+  width: 55px !important;
+  height: 55px !important;
+}
+
+.loading {
+  height: 55px !important;
+  img {
+    height: 30px;
+    width: 120px;
+    margin-top: 20px;
+  }
+}
+
+.pullRefresh {
+  :global {
+    .van-pull-refresh__track {
+      min-height: inherit;
+    }
+  }
+}

+ 77 - 0
src/components/the-full-refresh/index.tsx

@@ -0,0 +1,77 @@
+import { Image, PullRefresh } from 'vant';
+import { defineComponent, reactive, watch } from 'vue';
+import styles from './index.module.less';
+import { Vue3Lottie } from 'vue3-lottie';
+import AstronautJSON from './datas/data.json';
+import 'vue3-lottie/dist/style.css';
+import loading from './loading.gif';
+import loadingJSon from './loading.json';
+export default defineComponent({
+  name: 'm-full-refresh',
+  props: {
+    title: String,
+    modelValue: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['refresh', 'update:modelValue'],
+  setup(props, { emit, slots }) {
+    const state = reactive({
+      fullState: false
+    });
+    watch(
+      () => props.modelValue,
+      (val: boolean) => {
+        state.fullState = val;
+      }
+    );
+    watch(
+      () => state.fullState,
+      (val: boolean) => {
+        emit('update:modelValue', val);
+      }
+    );
+    return () => (
+      <PullRefresh
+        v-model:modelValue={state.fullState}
+        onRefresh={() => emit('refresh')}
+        loadingText=" "
+        class={styles.pullRefresh}>
+        {{
+          // loading: () => (
+          //   <div>
+          //     {
+          //       // <Image src={loadingJSon.loading} class={styles.loading} />
+          //       <Vue3Lottie
+          //         class={styles.animateWrap}
+          //         animationData={AstronautJSON}></Vue3Lottie>
+          //     }
+          //   </div>
+          // ),
+          // pulling: () => (
+          //   <div>
+          //     {
+          //       // <Image src={loading} class={styles.loading} />
+          //       <Vue3Lottie
+          //         class={styles.animateWrap}
+          //         animationData={AstronautJSON}></Vue3Lottie>
+          //     }
+          //   </div>
+          // ),
+          // loosing: () => (
+          //   <div>
+          //     {
+          //       // <Image src={loading} class={styles.loading} />
+          //       <Vue3Lottie
+          //         class={styles.animateWrap}
+          //         animationData={AstronautJSON}></Vue3Lottie>
+          //     }
+          //   </div>
+          // ),
+          default: () => <> {slots.default && slots.default()}</>
+        }}
+      </PullRefresh>
+    );
+  }
+});

BIN
src/components/the-full-refresh/loading.gif


File diff suppressed because it is too large
+ 1 - 0
src/components/the-full-refresh/loading.json


+ 26 - 0
src/components/the-qrcode/index.module.less

@@ -0,0 +1,26 @@
+.qrcode {
+  position: relative;
+
+  .qrcodeCanvas {
+    width: 100% !important;
+    height: 100% !important;
+  }
+
+  .qrcodeLogo {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    margin-left: -20px;
+    margin-top: -20px;
+    width: 40px !important;
+    height: 40px !important;
+    border-radius: 4px;
+
+    &.small {
+      margin-left: -10px;
+      margin-top: -10px;
+      width: 20px !important;
+      height: 20px !important;
+    }
+  }
+}

+ 65 - 0
src/components/the-qrcode/index.tsx

@@ -0,0 +1,65 @@
+import { defineComponent, nextTick, onMounted, ref, watch } from 'vue'
+import logo from '@common/images/icon_logo.png'
+//@ts-ignore
+import QRCode from 'qrcode'
+import styles from './index.module.less'
+
+export default defineComponent({
+  props: {
+    text: {
+      type: String,
+      default: ''
+    },
+    size: {
+      type: String,
+      default: '200px'
+    },
+    logoSize: {
+      type: String,
+      default: 'default'
+    }
+  },
+  setup(props) {
+    const canvas = ref()
+
+    const init = () => {
+      QRCode.toCanvas(
+        canvas.value,
+        props.text,
+        {
+          margin: 1
+        },
+        (error: any) => {
+          if (error) console.log(error)
+          console.log('success')
+        }
+      )
+    }
+    watch(
+      () => props.text,
+      () => {
+        init()
+      }
+    )
+    onMounted(() => {
+      nextTick(() => {
+        init()
+      })
+    })
+    return () => (
+      <div
+        class={styles.qrcode}
+        style={{ width: props.size, height: props.size }}
+      >
+        <canvas ref={canvas} class={styles.qrcodeCanvas}></canvas>
+        <img
+          src={logo}
+          class={[
+            styles.qrcodeLogo,
+            props.logoSize === 'small' && styles.small
+          ]}
+        />
+      </div>
+    )
+  }
+})

+ 18 - 0
src/components/the-sticky/index.module.less

@@ -0,0 +1,18 @@
+.sticky {
+  position: sticky;
+  top: 0;
+  z-index: 99;
+}
+
+.white {
+  background-color: #fff;
+
+  >div {
+    padding-top: 15px;
+    box-shadow: 0px 0px 10px 0px rgba(216, 216, 216, 0.5);
+  }
+}
+
+.animationStyle {
+  transition: all .2s;
+}

+ 121 - 0
src/components/the-sticky/index.tsx

@@ -0,0 +1,121 @@
+import {
+  PropType,
+  defineComponent,
+  nextTick,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue'
+import styles from './index.module.less'
+import { useRect } from '@vant/use'
+import { useResizeObserver } from '@vueuse/core'
+
+export default defineComponent({
+  name: 'm-sticky',
+  props: {
+    position: {
+      type: String as PropType<'top' | 'bottom'>,
+      default: 'top'
+    },
+    mode: {
+      type: String as PropType<'fixed' | 'sticky'>,
+      default: 'fixed'
+    },
+    offsetTop: {
+      type: String,
+      default: '0px'
+    },
+    offsetBottom: {
+      default: '0px'
+    },
+    // 变量名
+    varName: {
+      type: String,
+      default: '--header-height'
+    }
+  },
+  emits: ['barHeight'],
+  setup(props, { slots, emit }) {
+    const forms = reactive({
+      divStyle: {} as any,
+      heightV: 0,
+      sectionStyle: {
+        width: '100%',
+        height: 'auto',
+        left: '0'
+      }
+    })
+
+    const __initHeight = (height: any) => {
+      forms.sectionStyle.height = `${height}px`
+      forms.heightV = height
+      // 设置名称
+      document.documentElement.style.setProperty(props.varName, `${height}px`)
+      emit('barHeight', height)
+    }
+
+    const divRef = ref()
+    const div2Ref = ref()
+    onMounted(() => {
+      if (props.position === 'top') {
+        forms.divStyle.top = props.offsetTop || '0px'
+      } else {
+        forms.divStyle.bottom = props.offsetBottom || '0px'
+      }
+      // const resize = new ResizeObserver(() => {
+      //   const { height } = useRect(div2Ref.value);
+      //   __initHeight(height);
+      // });
+      // resize.observe(divRef.value);
+
+      try {
+        useResizeObserver(div2Ref.value, (entries: any) => {
+          const entry = entries[0]
+          // console.log(entry, 'entry')
+          const { height } = entry.contentRect
+          if (Math.abs(height - forms.heightV) > 1) {
+            setTimeout(() => {
+              __initHeight(height)
+            }, 10)
+          }
+        })
+      } catch {
+        //
+      }
+    })
+
+    watch(
+      () => props.offsetTop,
+      () => {
+        forms.divStyle.top = props.offsetTop
+      }
+    )
+    watch(
+      () => props.offsetBottom,
+      () => {
+        forms.divStyle.bottom = props.offsetBottom
+      }
+    )
+    return () => (
+      <div
+        style={[forms.sectionStyle]}
+        class={props.mode === 'sticky' && styles.sticky}
+      >
+        <div
+          ref={divRef}
+          class={[
+            'van-sticky',
+            props.mode === 'fixed' ? 'van-sticky--fixed' : '',
+            styles.animationStyle
+          ]}
+          style={[forms.divStyle, forms.sectionStyle]}
+        >
+          <div ref={div2Ref} style={{ position: 'relative' }}>
+            {slots.default && slots.default()}
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

+ 72 - 71
src/constant/index.ts

@@ -1,71 +1,72 @@
-export const goodsType = {
-  LIVE: '直播课',
-  PRACTICE: '陪练课',
-  VIDEO: '视频课',
-  VIP: '开通会员',
-  MUSIC: '单曲点播',
-  ALBUM: '专辑购买',
-  PIANO_ROOM: '琴房时长充值',
-  ACTI_REGIST: '活动报名'
-}
-
-export const orderType = {
-  WAIT_PAY: '待支付',
-  PAYING: '支付中',
-  PAID: '已付款',
-  CLOSE: '已关闭',
-  FAIL: '支付失败'
-}
-
-export const returnType = {
-  DOING: '审核中',
-  PASS: '通过',
-  UNPASS: '不通过'
-}
-
-export const levelMember = {
-  BEGINNER: '入门级',
-  ADVANCED: '进阶级',
-  PERFORMER: '大师级'
-}
-
-export const memberType = {
-  MONTH: '月度会员',
-  QUARTERLY: '季度会员',
-  YEAR_HALF: '半年会员',
-  YEAR: '年度会员'
-}
-
-export const courseType = {
-  NOT_START: '未开始',
-  ING: '进行中',
-  COMPLETE: '已完成',
-  CANCEL: '已取消'
-}
-
-export const bizStatus = {
-  PRACTICE: '陪练课',
-  LIVE: '直播课',
-  VIDEO: '视频课',
-  MUSIC: '乐谱',
-  WITHDRAWAL: '提现',
-  LIVE_SHARE: '直播课分润',
-  VIDEO_SHARE: '视频课分润',
-  MUSIC_SHARE: '乐谱分润',
-  VIP_SHARE: '会员分润',
-  MALL_SHARE: '商品分润'
-}
-
-export const postStatus = {
-  WAIT: '待入账',
-  FROZEN: '冻结入账 ',
-  RECORDED: '已入账 ',
-  CANCEL: '取消'
-}
-
-// 评测难度
-export const difficulty = {
-  BEGINNER: '入门级',
-  ADVANCED: '进阶级',
-  PERFORMER: '大师级'
-}
+export const goodsType = {
+  LIVE: '直播课',
+  PRACTICE: '陪练课',
+  VIDEO: '视频课',
+  VIP: '开通会员',
+  MUSIC: '单曲点播',
+  ALBUM: '专辑购买',
+  PIANO_ROOM: '琴房时长充值',
+  ACTI_REGIST: '活动报名',
+  TENANT_ALBUM: '机构专辑'
+}
+
+export const orderType = {
+  WAIT_PAY: '待支付',
+  PAYING: '支付中',
+  PAID: '已付款',
+  CLOSE: '已关闭',
+  FAIL: '支付失败'
+}
+
+export const returnType = {
+  DOING: '审核中',
+  PASS: '通过',
+  UNPASS: '不通过'
+}
+
+export const levelMember = {
+  BEGINNER: '入门级',
+  ADVANCED: '进阶级',
+  PERFORMER: '大师级'
+}
+
+export const memberType = {
+  MONTH: '月度会员',
+  QUARTERLY: '季度会员',
+  YEAR_HALF: '半年会员',
+  YEAR: '年度会员'
+}
+
+export const courseType = {
+  NOT_START: '未开始',
+  ING: '进行中',
+  COMPLETE: '已完成',
+  CANCEL: '已取消'
+}
+
+export const bizStatus = {
+  PRACTICE: '陪练课',
+  LIVE: '直播课',
+  VIDEO: '视频课',
+  MUSIC: '乐谱',
+  WITHDRAWAL: '提现',
+  LIVE_SHARE: '直播课分润',
+  VIDEO_SHARE: '视频课分润',
+  MUSIC_SHARE: '乐谱分润',
+  VIP_SHARE: '会员分润',
+  MALL_SHARE: '商品分润'
+}
+
+export const postStatus = {
+  WAIT: '待入账',
+  FROZEN: '冻结入账 ',
+  RECORDED: '已入账 ',
+  CANCEL: '取消'
+}
+
+// 评测难度
+export const difficulty = {
+  BEGINNER: '入门级',
+  ADVANCED: '进阶级',
+  PERFORMER: '大师级'
+}

+ 1 - 1
src/helpers/request.ts

@@ -85,7 +85,7 @@ request.interceptors.response.use(
       throw new Error(msg)
     }
     const data = await res.clone().json()
-    if (data.code !== 200 && data.errCode !== 0) {
+    if (data.code !== 200 && data.errCode !== 0 && data.code !== 5004) {
       let msg = data.msg || data.message || '处理失败,请重试'
       if (initRequest) {
         if (data.code === 403 || data.code === 401) {

+ 81 - 0
src/helpers/utils.ts

@@ -2,6 +2,7 @@ import dayjs from 'dayjs'
 import numeral from 'numeral'
 import { Toast } from 'vant'
 import { state as helpState } from './helpState'
+import qs from 'query-string'
 
 export const browser = () => {
   const u = navigator.userAgent
@@ -41,11 +42,77 @@ export const browser = () => {
     iPad: u.indexOf('iPad') > -1, //是否iPad
     webApp: u.indexOf('Safari') == -1, //是否web应该程序,没有头部与底部
     weixin: u.indexOf('MicroMessenger') > -1, //是否微信 (2015-01-22新增)
+    alipay: u.indexOf('AlipayClient') > -1, //是否支付宝
     huawei: !!u.match(/huawei/i) || !!u.match(/honor/i),
     xiaomi: !!u.match(/mi\s/i) || !!u.match(/redmi/i) || !!u.match(/mix/i)
   }
 }
 
+/**
+ * @description 格式化日期控件显示内容
+ * @param type
+ * @param option
+ * @returns OBJECT
+ */
+export const formatterDatePicker = (type: any, option: any) => {
+  if (type === 'year') {
+    option.text += '年'
+  }
+  if (type === 'month') {
+    option.text += '月'
+  }
+  if (type === 'day') {
+    option.text += '日'
+  }
+  return option
+}
+
+/**
+ * 数字转成汉字
+ * @params num === 要转换的数字
+ * @return 汉字
+ * */
+export const toChinesNum = (num: any) => {
+  const changeNum = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
+  const unit = ['', '十', '百', '千', '万']
+  num = parseInt(num)
+  const getWan = (temp: any) => {
+    const strArr = temp.toString().split('').reverse()
+    let newNum = ''
+    const newArr: string[] = []
+    strArr.forEach((item: any, index: any) => {
+      newArr.unshift(
+        item === '0' ? changeNum[item] : changeNum[item] + unit[index]
+      )
+    })
+    const numArr: number[] = []
+    newArr.forEach((m, n) => {
+      if (m !== '零') numArr.push(n)
+    })
+    if (newArr.length > 1) {
+      newArr.forEach((m, n) => {
+        if (newArr[newArr.length - 1] === '零') {
+          if (n <= numArr[numArr.length - 1]) {
+            newNum += m
+          }
+        } else {
+          newNum += m
+        }
+      })
+    } else {
+      newNum = newArr[0]
+    }
+
+    return newNum
+  }
+  const overWan = Math.floor(num / 10000)
+  let noWan: any = num % 10000
+  if (noWan.toString().length < 4) {
+    noWan = '0' + noWan
+  }
+  return overWan ? getWan(overWan) + '万' + getWan(noWan) : getWan(num)
+}
+
 export const getRandomKey = () => {
   const key = '' + new Date().getTime() + Math.floor(Math.random() * 1000000)
   return key
@@ -152,3 +219,17 @@ export const dateFormat = (
 ) => {
   return dayjs(value).format(format)
 }
+
+// 获取授权的code码
+export const getUrlCode = (name = 'code') => {
+  let search: any = {}
+  try {
+    search = {
+      ...qs.parse(location.search),
+      ...qs.parse(location.hash.split('?')[1])
+    }
+  } catch (error) {
+    //
+  }
+  return search[name]
+}

+ 67 - 0
src/router/index-tenant.ts

@@ -0,0 +1,67 @@
+import { browser } from '@/helpers/utils'
+import { state } from '@/state'
+import { Dialog } from 'vant'
+import { createRouter, createWebHashHistory, Router } from 'vue-router'
+import { postMessage } from '@/helpers/native-message'
+import routes from './routes-tenant'
+const router: Router = createRouter({
+  history: createWebHashHistory(),
+  routes
+})
+
+router.beforeEach((to, from, next) => {
+  const title = to.meta.title
+  document.title = (title || '酷乐秀') as any
+  next()
+  // if (browser().iPhone && !state.version) {
+  // try {
+  //   postMessage(
+  //     {
+  //       api: 'getVersion'
+  //     },
+  //     (res: any) => {
+  //       state.version = res.version
+  //       console.log(res, 'version')
+  //       setTimeout(() => {
+  //         next()
+  //       }, 50)
+  //     }
+  //   )
+  // } catch {}
+  // // 为了处理上面方法的没有返回
+  // setTimeout(() => {
+  // if (!state.version) {
+  //       next()
+  //     // }
+  //   // }, 5000)
+  // } else {
+  //   console.log(222)
+  //   next()
+  // }
+})
+
+let isOpen = false
+router.onError(error => {
+  if (error instanceof Error) {
+    const isChunkLoadFailed = error.name.indexOf('chunk')
+    const targetPath = router.currentRoute.value.fullPath
+    if (isChunkLoadFailed && !isOpen) {
+      isOpen = true
+      Dialog.alert({
+        title: '更新提示',
+        message: 'APP有更新请点击确定刷新页面?',
+        confirmButtonColor: 'var(--van-primary)'
+      }).then(() => {
+        // on close
+        if (browser().isApp) {
+          postMessage({ api: 'back' })
+        } else {
+          location.hash = targetPath
+          window.location.reload()
+        }
+      })
+    }
+  }
+})
+
+export default router

+ 14 - 0
src/router/routes-common.ts

@@ -328,5 +328,19 @@ export const rootRouter = [
     meta: {
       title: '活动声部曲目'
     }
+  },
+  {
+    path: '/tenantStudentRejest',
+    component: () => import('@/views/tenantStudentRejest/index'),
+    meta: {
+      title: '机构学员注册'
+    }
+  },
+  {
+    path: '/tenantTeacherRejest',
+    component: () => import('@/views/tenantTeacherRejest/index'),
+    meta: {
+      title: '机构老师注册'
+    }
   }
 ]

+ 7 - 0
src/router/routes-teacher.ts

@@ -318,6 +318,13 @@ export default [
         meta: {
           title: '老师主页'
         }
+      },
+      {
+        path: '/train-tool',
+        component: () => import('@/tenant/music/train-tool'),
+        meta: {
+          title: '训练工具'
+        }
       }
     ]
   },

+ 345 - 0
src/router/routes-tenant.ts

@@ -0,0 +1,345 @@
+import Auth from '@/student/layout/auth'
+import { router, rootRouter } from './routes-common'
+
+type metaType = {
+  isRegister: boolean
+}
+
+const noLoginRouter = [
+  {
+    path: '/share-music-sheet',
+    name: 'share-music-sheet',
+    component: () => import('@/teacher/share-page/share-music-sheet/index'),
+    meta: {
+      title: '分享乐曲'
+    }
+  },
+  {
+    path: '/leaderboard',
+    component: () => import('@/student/leaderboard/index'),
+    meta: {
+      title: '曲目挑战排行榜'
+      // isExternal: true // 是否外部浏览器可以打开
+    }
+  },
+  {
+    path: '/payCenter',
+    name: 'payCenter',
+    component: () => import('@/views/adapay/pay-center'),
+    meta: {
+      title: '支付'
+    }
+  },
+  {
+    path: '/payDefine',
+    name: 'payDefine',
+    component: () => import('@/views/adapay/pay-define'),
+    meta: {
+      title: '支付'
+    }
+  },
+  {
+    path: '/payResult',
+    name: 'payResult',
+    component: () => import('@/views/adapay/pay-result'),
+    meta: {
+      title: '支付'
+    }
+  },
+  {
+    path: '/tradeDetail',
+    name: 'tradeDetail',
+    component: () => import('@/views/trade/trade-detail'),
+    meta: {
+      title: '交易详情'
+    }
+  },
+  {
+    path: '/helpCenter',
+    name: 'helpCenter',
+    component: () => import('@/views/article-center/help-center')
+  },
+  {
+    path: '/helpCenterDetail',
+    name: 'helpCenterDetail',
+    component: () => import('@/views/article-center/help-center-detail')
+  }
+]
+
+export default [
+  {
+    path: '/',
+    component: Auth,
+    children: [
+      // ...router,
+      {
+        path: '/login',
+        name: 'login',
+        component: () => import('@/tenant/layout/login'),
+        meta: {
+          isRegister: false
+        } as metaType
+      },
+      {
+        path: '/music-album',
+        component: () => import('@/tenant/music/album/index'),
+        meta: {
+          title: '专辑'
+        }
+      },
+      {
+        path: '/music-album-detail/:id',
+        name: 'music-album-detail',
+        component: () => import('@/tenant/music/album-detail'),
+        meta: {
+          title: '专辑详情'
+        }
+      },
+      {
+        path: '/music-list',
+        component: () => import('@/tenant/music/list'),
+        meta: {
+          title: '曲谱列表'
+        }
+      },
+      {
+        path: '/train-list',
+        component: () => import('@/tenant/music/train-list'),
+        meta: {
+          title: '声部训练'
+        }
+      },
+      {
+        path: '/music-detail',
+        component: () => import('@/tenant/music/music-detail/new-index'),
+        meta: {
+          title: '曲谱详情'
+        }
+      },
+      {
+        path: '/memberCenter',
+        name: 'memberCenter',
+        component: () => import('@/tenant/member-center/index'),
+        meta: {
+          title: '会员中心'
+        }
+      },
+      {
+        path: '/orderDetail',
+        name: 'orderDetail',
+        component: () => import('@/views/order-detail/index'),
+        meta: {
+          title: '订单详情'
+        }
+      },
+      {
+        path: '/music-personal',
+        component: () => import('@/tenant/music/personal'),
+        meta: {
+          title: '我的曲库'
+        }
+      },
+      {
+        path: '/train-tool',
+        component: () => import('@/tenant/music/train-tool'),
+        meta: {
+          title: '训练工具'
+        }
+      },
+      {
+        path: '/look-album-list',
+        component: () => import('@/tenant/music/look-album-list'),
+        meta: {
+          title: '查看专辑'
+        }
+      },
+      {
+        path: '/exercise-record',
+        name: 'exercise-record',
+        component: () => import('@/tenant/exercise-record/exercis-detail'),
+        meta: {
+          title: '练习统计'
+        }
+      },
+      {
+        path: '/activation-code',
+        name: 'activation-code',
+        component: () => import('@/tenant/activation-code'),
+        meta: {
+          title: '激活码'
+        }
+      },
+      {
+        path: '/ranking-list',
+        name: 'ranking-list',
+        component: () => import('@/tenant/ranking-list'),
+        meta: {
+          title: '排行榜'
+        }
+      },
+      {
+        path: '/goodsOrder',
+        name: 'goodsOrder',
+        component: () => import('@/tenant/trade/index'),
+        meta: {
+          title: '订单信息'
+        }
+      },
+      {
+        path: '/goodsDetail',
+        name: 'goodsDetail',
+        component: () => import('@/tenant/trade/detail'),
+        meta: {
+          title: '订单详情'
+        }
+      },
+      {
+        path: '/music-songbook',
+        component: () => import('@/tenant/music/search/header'),
+        meta: {
+          title: '搜索顶部'
+        },
+        children: [
+          {
+            path: '/music-songbook/search',
+            name: 'musicSearch',
+            component: () => import('@/tenant/music/search'),
+            meta: {
+              title: '搜索结果'
+            }
+          },
+          {
+            path: '/music-songbook/musicSongbook',
+            name: 'musicSongbook',
+            component: () => import('@/tenant/music/songbook'),
+            meta: {
+              title: '乐谱库'
+            }
+          },
+          {
+            path: '/music-songbook/result',
+            name: 'musicSongresult',
+            component: () => import('@/tenant/music/search/search-result'),
+            meta: {
+              title: '搜索结果'
+            }
+          }
+        ]
+      },
+      // {
+      //   path: '/practiceClass',
+      //   name: 'practiceClass',
+      //   component: () => import('@/student/practice-class/index'),
+      //   meta: {
+      //     title: '陪练课'
+      //   }
+      // },
+      // {
+      //   path: '/videoDetail',
+      //   name: 'videoDetail',
+      //   component: () => import('@/student/video-class/video-detail'),
+      //   meta: {
+      //     title: '视频课'
+      //   }
+      // },
+      // {
+      //   path: '/videoClassDetail',
+      //   name: 'videoClassDetail',
+      //   component: () => import('@/student/video-class/video-class-detail'),
+      //   meta: {
+      //     title: '视频课详情'
+      //   }
+      // },
+      // {
+      //   path: '/liveDetail',
+      //   name: 'liveDetail',
+      //   component: () => import('@/student/live-class/live-detail'),
+      //   meta: {
+      //     title: '直播课详情'
+      //   }
+      // },
+      // {
+      //   path: '/memberActive',
+      //   name: 'memberActive',
+      //   component: () => import('@/student/member-center/member-active'),
+      //   meta: {
+      //     title: '小酷Ai会员大放价'
+      //   }
+      // },
+      {
+        path: '/memberRecord',
+        name: 'memberRecord',
+        component: () => import('@/tenant/exercise-record/exercis-detail'),
+        meta: {
+          title: '训练统计'
+        }
+      }
+      // {
+      //   path: '/tradeRecord',
+      //   name: 'tradeRecord',
+      //   component: () => import('@/student/trade/index'),
+      //   meta: {
+      //     title: '交易记录'
+      //   }
+      // },
+      // {
+      //   path: '/teacherHome',
+      //   name: 'teacherHome',
+      //   component: () => import('@/student/teacher-dependent/teacher-home'),
+      //   meta: {
+      //     title: '老师主页'
+      //   }
+      // },
+      // {
+      //   path: '/teacherElegant',
+      //   name: 'teacherElegant',
+      //   component: () => import('@/student/teacher-dependent/teacher-elegant'),
+      //   meta: {
+      //     title: '老师风采'
+      //   }
+      // },
+      // {
+      //   path: '/music-upload',
+      //   component: () => import('@/teacher/music/upload'),
+      //   meta: {
+      //     title: '上传曲谱'
+      //   }
+      // },
+      // {
+      //   path: '/teacherFollow',
+      //   component: () => import('@/student/teacher-dependent/teacher-follow'),
+      //   meta: {
+      //     title: '我的关注'
+      //   }
+      // },
+      // {
+      //   path: '/track-review-activity',
+      //   component: () =>
+      //     import('@/student/share-active/track-review-activity/index'),
+      //   meta: {
+      //     title: '曲目评测活动',
+      //     isExternal: true // 是否外部浏览器可以打开
+      //   }
+      // },
+
+      // {
+      //   path: '/track-song',
+      //   component: () =>
+      //     import('@/student/share-active/track-review-activity/track-song'),
+      //   meta: {
+      //     title: '评测曲目'
+      //   }
+      // }
+    ]
+  },
+  ...noLoginRouter,
+  // ...rootRouter,
+  {
+    path: '/:pathMatch(.*)*',
+    component: () => import('@/views/404'),
+    meta: {
+      title: '404 Not Fund',
+      platform: 'STUDENT'
+    }
+  }
+]

+ 70 - 2
src/state.ts

@@ -10,12 +10,15 @@ export const state = reactive({
     data: {} as any
   },
   orchestraInfo: {
-    token: '' as any, phone: '' as any,
+    token: '' as any,
+    phone: '' as any,
     installStatus: 0 as any,
     nickname: '',
     avatar: '',
     unionId: 0 // 是否已关联账号
   } as any, // 管乐团信息
+  projectType: 'default' as 'default' | 'tenant', // 机构端,还是默认
+  payBackPath: '/tenant.html',
   platformType: '' as 'STUDENT' | 'TEACHER',
   platformApi: '/api-student' as '/api-student' | '/api-teacher',
   version: '', // 版本号 例如: 1.0.0
@@ -64,4 +67,69 @@ export const openDefaultWebView = (url?: string, callBack?: any) => {
   } else {
     callBack && callBack()
   }
-}
+}
+
+/**
+ * @description 微信授权-会根据环境去判断
+ * @param wxAppId
+ * @param urlString 回调链接【默认当前页面地址】
+ * @returns void
+ */
+export const goWechatAuth = (wxAppId: string, urlString?: string) => {
+  // 开发环境
+  if (import.meta.env.DEV) {
+    const replaceUrl =
+      `https://kt.colexiu.com/getWxCode?appid=${
+        wxAppId || 'wx8654c671631cfade'
+      }&state=STATE&redirect_uri=` +
+      encodeURIComponent(urlString || window.location.href)
+    window.location.replace(replaceUrl)
+  }
+
+  // 生产环境
+  if (import.meta.env.PROD) {
+    goAuth(wxAppId, urlString)
+  }
+}
+
+const goAuth = (wxAppId: string, urlString?: string) => {
+  // 用户授权
+  const urlNow = encodeURIComponent(urlString || window.location.href)
+  // console.log(urlNow, 'urlNow');
+  const scope = 'snsapi_base' //snsapi_userinfo   //静默授权 用户无感知
+  const appid = wxAppId || 'wx8654c671631cfade'
+  const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${urlNow}&response_type=code&scope=${scope}&state=STATE&connect_redirect=1#wechat_redirect`
+  window.location.replace(url)
+}
+
+/**
+ * @description 支付宝授权-会根据环境去判断
+ * @param wxAppId
+ * @param urlString 回调链接【默认当前页面地址】
+ * @returns void
+ */
+export const goAliAuth = (alipayAppId: string, urlString?: string) => {
+  // 支付宝授权
+  const urlNow = encodeURIComponent(urlString || window.location.href)
+  const appid = alipayAppId || '2021004100630808'
+  // 开发环境
+  if (import.meta.env.DEV) {
+    const url = `https://kt.colexiu.com/getAliCode?app_id=${appid}&state=STATE&redirect_uri=${urlNow}`
+    window.location.replace(url)
+  }
+
+  // 生产环境
+  if (import.meta.env.PROD) {
+    alipayAuth(alipayAppId, urlString)
+  }
+}
+
+const alipayAuth = (alipayAppId: string, urlString?: string) => {
+  // 用户授权
+  const urlNow = encodeURIComponent(urlString || window.location.href)
+  const scope = 'auth_base' //snsapi_userinfo   //静默授权 用户无感知
+  const appid = alipayAppId || '2021004100630808'
+  // 判断是否是线上
+  const url = `https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=${appid}&redirect_uri=${urlNow}&response_type=auth_code&scope=${scope}&state=STATE`
+  window.location.replace(url)
+}

+ 1 - 0
src/student/live-class/live-detail.tsx

@@ -226,6 +226,7 @@ export default defineComponent({
               orderStatus.orderObject.orderNo = result.orderNo
               orderStatus.orderObject.actualPrice = result.actualPrice
               orderStatus.orderObject.discountPrice = result.discountPrice
+              orderStatus.orderObject.paymentConfig = result.paymentConfig
               this.routerTo()
             })
             .catch(() => {

+ 271 - 269
src/student/trade/tradeOrder.ts

@@ -1,269 +1,271 @@
-import { memberType } from '@/constant'
-import request from '@/helpers/request'
-import { state } from '@/state'
-import { orderStatus } from '@/views/order-detail/orderStatus'
-import dayjs from 'dayjs'
-
-const apiSuffix =
-  state.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
-// LIVE: '直播课',
-// PRACTICE: '陪练课',
-// VIDEO: '视频课',
-// VIP: '开通会员',
-// MUSIC: '单曲点播'
-interface IAmount {
-  couponAmount: number
-  discountPrice: number
-}
-export const formatOrderDetail = async (item: any, amount?: IAmount) => {
-  const type = item.goodType
-  let tempList: any = {}
-
-  switch (type) {
-    case 'LIVE':
-      {
-        try {
-          const live = await getLiveDetail(item.bizId)
-          const courseInfo: any[] = []
-          const coursePlanList = live.planList || []
-          coursePlanList.forEach((item: any) => {
-            const startTime = item.startTime || new Date()
-            const endTime = item.endTime || new Date()
-            courseInfo.push({
-              courseTime: `${dayjs(startTime).format('YYYY-MM-DD')} ${dayjs(
-                startTime
-              ).format('HH:mm')}~${dayjs(endTime).format('HH:mm')}`,
-              coursePlan: item.plan,
-              id: item.courseId
-            })
-          })
-          tempList = {
-            orderType: item.goodType,
-            goodName: item.goodName,
-            courseGroupId: live.courseGroupId,
-            courseGroupName: live.courseGroupName,
-            coursePrice: live.coursePrice,
-            teacherName: live.userName || `游客${live.teacherId || ''}`,
-            teacherId: live.teacherId,
-            avatar: live.avatar,
-            courseInfo
-          }
-        } catch (e: any) {
-          throw new Error(e.message)
-        }
-      }
-      break
-    case 'PRACTICE': {
-      const bizContent: any = JSON.parse(item.bizContent)
-      tempList = {
-        ...bizContent,
-        teacherName: item.username,
-        starGrade: item.starGrade,
-        avatar: item.avatar
-      }
-      break
-    }
-    case 'VIDEO': {
-      try {
-        const res = await getVideoDetail(item.bizId)
-        const { lessonGroup, detailList } = res
-        tempList = {
-          orderType: item.goodType,
-          goodName: item.goodName,
-          courseGroupId: lessonGroup.id,
-          courseGroupName: lessonGroup.lessonName,
-          coursePrice: lessonGroup.lessonPrice,
-          teacherName: lessonGroup.username,
-          teacherId: lessonGroup.teacherId,
-          avatar: lessonGroup.avatar,
-          courseInfo: detailList
-        }
-      } catch (e: any) {
-        throw new Error(e.message)
-      }
-      break
-    }
-    case 'VIP':
-      {
-        try {
-          const res = await getVipDetail(item.id)
-          tempList = {
-            orderType: item.goodType,
-            goodName: item.goodName,
-            id: item.id,
-            title: memberType[res.period] || '',
-            // 判断是否有优惠金额
-            price: amount?.couponAmount
-              ? Number(
-                (
-                  res.salePrice -
-                  amount.couponAmount +
-                  amount.discountPrice
-                ).toFixed(2)
-              )
-              : res.salePrice || item.actualPrice,
-            startTime: dayjs(res.startTime).format('YYYY-MM-DD'),
-            endTime: dayjs(res.endTime).format('YYYY-MM-DD')
-          }
-        } catch (e: any) {
-          throw new Error(e.message)
-        }
-      }
-      break
-    case 'MUSIC':
-      {
-        try {
-          const res = await getMusicDetail(item.bizId)
-          tempList = {
-            orderType: item.goodType,
-            goodName: item.goodName,
-            ...res
-          }
-        } catch (e: any) {
-          throw new Error(e.message)
-        }
-      }
-      break
-    case 'ALBUM':
-      {
-        console.log(item)
-        try {
-          const res = await getAlbumDetail(item.bizId)
-          tempList = {
-            orderType: item.goodType,
-            goodName: item.goodName,
-            ...res
-          }
-        } catch (e: any) {
-          throw new Error(e.message)
-        }
-      }
-      break
-    case 'ACTI_REGIST':
-      {
-        try {
-          const res = await getMusicActiveTrack(item.bizId)
-          tempList = {
-            orderType: item.goodType,
-            goodsName: res.activityName,
-            activityId: res.id,
-            actualPrice: res.registrationPrice
-          }
-        } catch (e: any) {
-          throw new Error(e.message)
-        }
-      }
-      break
-  }
-  tempList.orderType = type
-  tempList.goodName = item.goodName
-  orderStatus.orderObject.orderList.push(tempList)
-}
-// 获取视频课详情
-export const getVideoDetail = async (groupId: any) => {
-  try {
-    const res = await request.get(
-      `${apiSuffix}/videoLesson/selectVideoLesson`,
-      {
-        params: {
-          groupId
-        }
-      }
-    )
-    return res.data
-  } catch {
-    throw new Error('获取视频课详情失败')
-  }
-}
-
-// 获取直播课详情
-export const getLiveDetail = async (groupId: any) => {
-  try {
-    const res = await request.get(
-      `${apiSuffix}/courseGroup/queryLiveCourseInfo`,
-      {
-        params: {
-          groupId
-        }
-      }
-    )
-    return res.data
-  } catch {
-    throw new Error('获取直播课详情失败')
-  }
-}
-
-// 获取会员详情
-export const getVipDetail = async (id: any) => {
-  try {
-    const setting = await request.get(`${apiSuffix}/vipCardRecord/detail/` + id)
-    return setting.data || []
-  } catch {
-    throw new Error('获取会员详情失败')
-  }
-}
-
-// 获取曲目详情
-export const getMusicDetail = async (id: any) => {
-  try {
-    const res = await request.get(`${apiSuffix}/music/sheet/detail/${id}`)
-    return res.data
-  } catch {
-    throw new Error('获取曲目详情失败')
-  }
-}
-
-// 活动列表
-// 获取曲目详情
-export const getMusicActiveTrack = async (id: any) => {
-  try {
-    const res = await request.post(`${apiSuffix}/open/activity/info/${id}`)
-    return res.data
-  } catch {
-    throw new Error('获取曲目详情失败')
-  }
-}
-
-// 获取专辑详情
-export const getAlbumDetail = async (id: any) => {
-  try {
-    const res = await request.post(`${apiSuffix}/music/album/detail`, {
-      data: { id }
-    })
-    return res.data
-  } catch {
-    throw new Error('获取专辑详情失败')
-  }
-}
-
-// 为了处理继续支付逻辑
-export const tradeOrder = (result: any, callBack?: any) => {
-  const {
-    orderNo,
-    actualPrice,
-    orderDesc,
-    orderName,
-    orderType,
-    orderDetailList,
-    couponAmount, // 优惠金额
-    discountPrice
-  } = result
-  orderStatus.orderObject.orderType = orderType
-  orderStatus.orderObject.orderName = orderName
-  orderStatus.orderObject.orderDesc = orderDesc
-  orderStatus.orderObject.actualPrice = actualPrice
-  orderStatus.orderObject.orderNo = orderNo
-  orderStatus.orderObject.discountPrice = discountPrice
-  orderStatus.orderObject.orderList = []
-  try {
-    orderDetailList.forEach(async (item: any) => {
-      await formatOrderDetail(item, {
-        couponAmount,
-        discountPrice
-      })
-    })
-    callBack && callBack()
-  } catch {
-    //
-  }
-}
+import { memberType } from '@/constant'
+import request from '@/helpers/request'
+import { state } from '@/state'
+import { orderStatus } from '@/views/order-detail/orderStatus'
+import dayjs from 'dayjs'
+
+const apiSuffix =
+  state.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
+// LIVE: '直播课',
+// PRACTICE: '陪练课',
+// VIDEO: '视频课',
+// VIP: '开通会员',
+// MUSIC: '单曲点播'
+interface IAmount {
+  couponAmount: number
+  discountPrice: number
+}
+export const formatOrderDetail = async (item: any, amount?: IAmount) => {
+  const type = item.goodType
+  let tempList: any = {}
+
+  switch (type) {
+    case 'LIVE':
+      {
+        try {
+          const live = await getLiveDetail(item.bizId)
+          const courseInfo: any[] = []
+          const coursePlanList = live.planList || []
+          coursePlanList.forEach((item: any) => {
+            const startTime = item.startTime || new Date()
+            const endTime = item.endTime || new Date()
+            courseInfo.push({
+              courseTime: `${dayjs(startTime).format('YYYY-MM-DD')} ${dayjs(
+                startTime
+              ).format('HH:mm')}~${dayjs(endTime).format('HH:mm')}`,
+              coursePlan: item.plan,
+              id: item.courseId
+            })
+          })
+          tempList = {
+            orderType: item.goodType,
+            goodName: item.goodName,
+            courseGroupId: live.courseGroupId,
+            courseGroupName: live.courseGroupName,
+            coursePrice: live.coursePrice,
+            teacherName: live.userName || `游客${live.teacherId || ''}`,
+            teacherId: live.teacherId,
+            avatar: live.avatar,
+            courseInfo
+          }
+        } catch (e: any) {
+          throw new Error(e.message)
+        }
+      }
+      break
+    case 'PRACTICE': {
+      const bizContent: any = JSON.parse(item.bizContent)
+      tempList = {
+        ...bizContent,
+        teacherName: item.username,
+        starGrade: item.starGrade,
+        avatar: item.avatar
+      }
+      break
+    }
+    case 'VIDEO': {
+      try {
+        const res = await getVideoDetail(item.bizId)
+        const { lessonGroup, detailList } = res
+        tempList = {
+          orderType: item.goodType,
+          goodName: item.goodName,
+          courseGroupId: lessonGroup.id,
+          courseGroupName: lessonGroup.lessonName,
+          coursePrice: lessonGroup.lessonPrice,
+          teacherName: lessonGroup.username,
+          teacherId: lessonGroup.teacherId,
+          avatar: lessonGroup.avatar,
+          courseInfo: detailList
+        }
+      } catch (e: any) {
+        throw new Error(e.message)
+      }
+      break
+    }
+    case 'VIP':
+      {
+        try {
+          const res = await getVipDetail(item.id)
+          tempList = {
+            orderType: item.goodType,
+            goodName: item.goodName,
+            id: item.id,
+            title: memberType[res.period] || '',
+            // 判断是否有优惠金额
+            price: amount?.couponAmount
+              ? Number(
+                  (
+                    res.salePrice -
+                    amount.couponAmount +
+                    amount.discountPrice
+                  ).toFixed(2)
+                )
+              : res.salePrice || item.actualPrice,
+            startTime: dayjs(res.startTime).format('YYYY-MM-DD'),
+            endTime: dayjs(res.endTime).format('YYYY-MM-DD')
+          }
+        } catch (e: any) {
+          throw new Error(e.message)
+        }
+      }
+      break
+    case 'MUSIC':
+      {
+        try {
+          const res = await getMusicDetail(item.bizId)
+          tempList = {
+            orderType: item.goodType,
+            goodName: item.goodName,
+            ...res
+          }
+        } catch (e: any) {
+          throw new Error(e.message)
+        }
+      }
+      break
+    case 'ALBUM':
+      {
+        console.log(item)
+        try {
+          const res = await getAlbumDetail(item.bizId)
+          tempList = {
+            orderType: item.goodType,
+            goodName: item.goodName,
+            ...res
+          }
+        } catch (e: any) {
+          throw new Error(e.message)
+        }
+      }
+      break
+    case 'ACTI_REGIST':
+      {
+        try {
+          const res = await getMusicActiveTrack(item.bizId)
+          tempList = {
+            orderType: item.goodType,
+            goodsName: res.activityName,
+            activityId: res.id,
+            actualPrice: res.registrationPrice
+          }
+        } catch (e: any) {
+          throw new Error(e.message)
+        }
+      }
+      break
+  }
+  tempList.orderType = type
+  tempList.goodName = item.goodName
+  orderStatus.orderObject.orderList.push(tempList)
+}
+// 获取视频课详情
+export const getVideoDetail = async (groupId: any) => {
+  try {
+    const res = await request.get(
+      `${apiSuffix}/videoLesson/selectVideoLesson`,
+      {
+        params: {
+          groupId
+        }
+      }
+    )
+    return res.data
+  } catch {
+    throw new Error('获取视频课详情失败')
+  }
+}
+
+// 获取直播课详情
+export const getLiveDetail = async (groupId: any) => {
+  try {
+    const res = await request.get(
+      `${apiSuffix}/courseGroup/queryLiveCourseInfo`,
+      {
+        params: {
+          groupId
+        }
+      }
+    )
+    return res.data
+  } catch {
+    throw new Error('获取直播课详情失败')
+  }
+}
+
+// 获取会员详情
+export const getVipDetail = async (id: any) => {
+  try {
+    const setting = await request.get(`${apiSuffix}/vipCardRecord/detail/` + id)
+    return setting.data || []
+  } catch {
+    throw new Error('获取会员详情失败')
+  }
+}
+
+// 获取曲目详情
+export const getMusicDetail = async (id: any) => {
+  try {
+    const res = await request.get(`${apiSuffix}/music/sheet/detail/${id}`)
+    return res.data
+  } catch {
+    throw new Error('获取曲目详情失败')
+  }
+}
+
+// 活动列表
+// 获取曲目详情
+export const getMusicActiveTrack = async (id: any) => {
+  try {
+    const res = await request.post(`${apiSuffix}/open/activity/info/${id}`)
+    return res.data
+  } catch {
+    throw new Error('获取曲目详情失败')
+  }
+}
+
+// 获取专辑详情
+export const getAlbumDetail = async (id: any) => {
+  try {
+    const res = await request.post(`${apiSuffix}/music/album/detail`, {
+      data: { id }
+    })
+    return res.data
+  } catch {
+    throw new Error('获取专辑详情失败')
+  }
+}
+
+// 为了处理继续支付逻辑
+export const tradeOrder = (result: any, callBack?: any) => {
+  const {
+    orderNo,
+    actualPrice,
+    orderDesc,
+    orderName,
+    orderType,
+    orderDetailList,
+    couponAmount, // 优惠金额
+    discountPrice,
+    paymentConfig
+  } = result
+  orderStatus.orderObject.orderType = orderType
+  orderStatus.orderObject.orderName = orderName
+  orderStatus.orderObject.orderDesc = orderDesc
+  orderStatus.orderObject.actualPrice = actualPrice
+  orderStatus.orderObject.orderNo = orderNo
+  orderStatus.orderObject.discountPrice = discountPrice
+  orderStatus.orderObject.paymentConfig = paymentConfig
+  orderStatus.orderObject.orderList = []
+  try {
+    orderDetailList.forEach(async (item: any) => {
+      await formatOrderDetail(item, {
+        couponAmount,
+        discountPrice
+      })
+    })
+    callBack && callBack()
+  } catch {
+    //
+  }
+}

+ 7 - 1
src/styles/index.less

@@ -122,16 +122,20 @@ body {
   padding: 0 28px;
   padding-bottom: 15px;
 }
+
 .btnMore {
   display: flex !important;
   justify-content: center !important;
+
   // :global {
   .van-button {
     width: 48% !important;
   }
-  .van-button + .van-button {
+
+  .van-button+.van-button {
     margin-left: 6px;
   }
+
   // }
 }
 
@@ -148,12 +152,14 @@ body {
 
 .sticky {
   position: relative;
+
   .van-sticky {
     height: inherit !important;
     top: var(--van-sticky-z-index) !important;
     position: fixed;
     width: 100%;
   }
+
   :global(.van-sticky--fixed) {
     box-shadow: 10px 10px 10px var(--box-shadow-color);
   }

+ 28 - 0
src/styles/tenant.less

@@ -0,0 +1,28 @@
+:root:root {
+  --van-primary: #FE2451 !important;
+  --van-picker-confirm-action-color: #FE2451 !important;
+  --van-primary-color: var(--van-primary) !important;
+  --tag-border-color: #FE2451;
+  --tag-color: #FE2451;
+}
+
+body {
+  background-color: #fafafa;
+  user-select: none;
+  margin-top: 0 !important;
+}
+
+.btnMore {
+  justify-content: space-between !important;
+}
+
+
+/* 以下是使用颜色关键字的代码 */
+progress::-webkit-progress-value {
+  background-color: #FE2451;
+}
+
+input,
+textarea {
+  caret-color: #FE2451;
+}

+ 5 - 3
src/teacher/share-page/share-album/index.tsx

@@ -46,9 +46,11 @@ export default defineComponent({
     const color = ref<string>('#fff')
     const heightInfo = ref<any>('auto')
 
-    const tmpUrl = `${location.origin}/student/#/music-album-detail/${
-      route.query.id
-    }?${qs.stringify(route.query)}`
+    const tmpUrl = `${location.origin}/${
+      route.query.p == 'tenant' ? 'tenant.html' : 'student/'
+    }#/music-album-detail/${route.query.id}?${qs.stringify(route.query)}`
+
+    console.log(tmpUrl, 'tmpUrl')
     const jumpUrl = ref<string>(tmpUrl)
 
     const FetchList = async (id?: any) => {

+ 90 - 16
src/teacher/share-page/share-music/index.module.less

@@ -1,7 +1,8 @@
-.base > div {
+.base>div {
   background: url(./header-bg.png) no-repeat top center;
   // background-attachment: fixed;
 }
+
 .detail {
   overflow: hidden;
   --van-nav-bar-background-color: transparent;
@@ -20,18 +21,36 @@
       border-radius: 50%;
     }
   }
+
+  .wxpopup {
+    width: 100%;
+    height: 100vh;
+    position: fixed;
+    top: 0;
+    left: 0;
+    background: rgba(0, 0, 0, 0.5);
+    z-index: 9999;
+
+    img {
+      width: 88%;
+      margin: 0 6%;
+    }
+  }
 }
+
 .base {
   :global(.van-sticky--fixed) {
     box-shadow: 10px 10px 10px var(--box-shadow-color);
   }
 }
+
 .shareBtn {
   display: flex;
   align-items: flex-start;
   color: #fff;
   font-size: 14px;
   line-height: 20px !important;
+
   :global(.van-image) {
     width: 18px;
     height: 18px;
@@ -48,6 +67,7 @@
   object-fit: cover;
   filter: blur(10px);
 }
+
 .bgContent {
   position: absolute;
   top: 0;
@@ -57,44 +77,50 @@
   backdrop-filter: blur(20px);
   -webkit-backdrop-filter: blur(20px);
 }
+
 .musicContent {
   position: relative;
   width: 100%;
   height: 500px;
   overflow: hidden;
+
   &::after {
     content: ' ';
     position: absolute;
     bottom: 0;
     left: 0;
     right: 0;
-    background: linear-gradient(
-      180deg,
-      rgba(255, 255, 255, 0) 0%,
-      #ffffff 100%
-    );
+    background: linear-gradient(180deg,
+        rgba(255, 255, 255, 0) 0%,
+        #ffffff 100%);
     height: 287px;
   }
+
   .musicTitle {
     text-align: center;
     font-size: 16px;
   }
+
   .musicImg {
     width: 100%;
   }
+
   .finch {
     width: 150px;
     margin: 80px auto 0;
   }
+
   .finchLoad {
     text-align: center;
     color: #333;
     font-size: 15px;
     margin-top: 4px;
   }
+
   :global {
     iframe {
       visibility: hidden;
+
       body {
         ::-webkit-scrollbar-thumb {
           background-color: #efeff0;
@@ -114,6 +140,7 @@
   box-shadow: 0px 0px 6px 0px rgba(229, 229, 229, 0.7);
   overflow: hidden;
 }
+
 .videoOperation {
   position: absolute;
   left: 0;
@@ -121,6 +148,7 @@
   bottom: 5px;
   z-index: 1;
 }
+
 .audition {
   display: flex;
   align-items: center;
@@ -132,6 +160,7 @@
   font-weight: 600;
   color: #ff731d;
   height: 18px;
+
   img {
     margin-top: -2px;
     width: 21px;
@@ -139,6 +168,7 @@
     margin-right: 11px;
   }
 }
+
 .collect {
   display: flex;
   align-items: center;
@@ -146,17 +176,20 @@
   padding: 11px;
   font-size: 14px;
   color: #666666;
+
   .userInfo {
     display: flex;
     align-items: center;
     padding: 3px 6px;
     background: #d5f3ee;
     border-radius: 16px;
+
     img {
       width: 26px;
       height: 26px;
       border-radius: 50%;
     }
+
     span {
       padding-left: 8px;
       max-width: 60px;
@@ -166,15 +199,18 @@
       color: #2dc7aa;
     }
   }
+
   .collectSection {
     display: flex;
     align-items: center;
+
     img {
       margin-top: -2px;
       margin-left: 5px;
       width: 18px;
       height: 18px;
     }
+
     :global {
       .van-icon {
         font-size: 20px;
@@ -199,10 +235,12 @@
   display: flex;
   align-items: center;
   justify-content: space-between;
-  & > div {
+
+  &>div {
     display: flex;
     align-items: center;
     line-height: 1;
+
     img {
       width: 20px;
       height: 20px;
@@ -217,6 +255,7 @@
   padding: 16px;
   z-index: 11;
 }
+
 .musicContainer {
   position: relative;
   // padding: 16px 0 0;
@@ -235,24 +274,28 @@
   padding: 11px 12px;
   background: #ffffff;
   border-radius: 10px;
+
   // border: 1px solid #2dc7aa;
   .icon {
     width: 36px;
     height: 36px;
     border-radius: 10px;
   }
+
   .info {
     margin-left: 14px;
     flex: 1;
     margin-right: 14px;
     word-break: break-all;
-    > h4 {
+
+    >h4 {
       color: var(--music-list-item-title-color);
       font-size: 14px;
       font-weight: 600;
       width: 200px;
     }
-    > p {
+
+    >p {
       color: var(--music-list-item-mate-color);
       line-height: 17px;
     }
@@ -288,16 +331,19 @@
     height: 72px;
     border-radius: 10px;
   }
+
   .info {
     margin-left: 6px;
     flex: 1;
     word-break: break-all;
-    > h4 {
+
+    >h4 {
       color: var(--music-list-item-title-color);
       font-size: 16px;
       font-weight: 600;
     }
-    > p {
+
+    >p {
       color: var(--music-list-item-mate-color);
       line-height: 17px;
     }
@@ -317,6 +363,7 @@
   color: #ffffff;
   line-height: 24px;
 }
+
 .buttonDiscount {
   position: absolute;
   top: -18px;
@@ -338,10 +385,12 @@
   overflow: hidden;
   flex-shrink: 0;
 }
+
 .musicInfo {
   padding-top: 23px !important;
   padding-bottom: 23px !important;
   margin-bottom: 10px;
+
   .coomposer {
     padding-top: 2px;
     padding-left: 6px;
@@ -350,11 +399,13 @@
     overflow: hidden;
     text-overflow: ellipsis;
   }
+
   .tag {
     flex-shrink: 0;
     padding: 2px 4px 0;
     border-radius: 4px;
-    & + .tag {
+
+    &+.tag {
       margin-left: 5px;
     }
   }
@@ -364,14 +415,16 @@
     flex: 1;
     margin-right: 14px;
     word-break: break-all;
-    > h4 {
+
+    >h4 {
       font-size: 16px;
       font-weight: bold;
       color: #1a1a1a;
       width: 175px;
       padding-bottom: 3px;
     }
-    > p {
+
+    >p {
       font-size: 12px;
       color: #999;
       line-height: 17px;
@@ -384,6 +437,7 @@
     justify-content: center;
     align-items: center;
     font-size: 12px;
+
     img {
       height: 24px;
       width: 24px;
@@ -395,6 +449,7 @@
     margin-left: 5px;
     flex-shrink: 0;
   }
+
   .songAlbum {
     width: 15px;
     height: 15px;
@@ -414,15 +469,18 @@
   display: flex;
   align-items: center;
   justify-content: space-between;
+
   // height: 45px;
   .priceSection {
     font-size: 14px;
     font-weight: 400;
     color: #999999;
+
     .price {
       font-size: 22px;
       font-weight: bold;
       color: #ff4e19;
+
       i {
         font-style: normal;
         font-size: 16px;
@@ -437,19 +495,22 @@
   display: flex;
   align-items: center;
   justify-content: flex-end;
+
   :global {
     .van-button {
       padding: 0 22px;
       font-weight: 600;
 
-      & + .van-button {
+      &+.van-button {
         margin-left: 12px;
       }
     }
   }
+
   .primry {
     box-shadow: 0px 2px 7px 0px rgba(45, 199, 170, 0.25);
   }
+
   .member {
     box-shadow: 0px 2px 7px 0px rgba(187, 156, 83, 0.25);
   }
@@ -466,6 +527,7 @@
   line-height: 20px;
   text-align: center;
   padding-top: 60px;
+
   .emptyImg {
     width: 172px;
   }
@@ -474,6 +536,7 @@
 .staffContainer {
   // text-align: center;
   padding: 15px 15px 24px;
+
   .staffTitle {
     padding-bottom: 25px;
     font-size: 16px;
@@ -485,34 +548,41 @@
     width: 32px;
     height: 20px;
   }
+
   .name {
     padding-left: 17px;
     font-size: 13px;
     font-weight: 500;
     color: #333333;
   }
+
   .boxStyle {
     background: transparent !important;
     width: 15px;
     height: 15px;
     border: transparent !important;
   }
+
   .active {
     background: #f7f8f9;
     border-radius: 8px;
+
     .name {
       color: #2dc7aa;
     }
   }
+
   :global {
     .van-cell {
       padding: 9px 16px 9px;
       margin-bottom: 6px;
+
       &:hover,
       &:active,
       &.active {
         background: #f7f8f9;
         border-radius: 8px;
+
         .name {
           color: #2dc7aa;
         }
@@ -522,10 +592,12 @@
         margin-bottom: 0;
       }
     }
+
     .van-cell__value {
       display: flex;
       justify-content: flex-end;
     }
+
     .van-checkbox {
       overflow: inherit;
       height: 18px;
@@ -533,14 +605,16 @@
       align-items: center;
       justify-content: flex-end;
     }
+
     .van-checkbox__icon {
       height: 15px;
       line-height: 15px;
       display: inline-block;
       vertical-align: middle;
     }
+
     .van-checkbox__label {
       line-height: 15px;
     }
   }
-}
+}

+ 3 - 3
src/teacher/share-page/share-music/index.tsx

@@ -94,9 +94,9 @@ export default defineComponent({
       radio: 'staff' // staff first fixed
     })
 
-    const tmpUrl = `${location.origin}/student/#/music-detail?${qs.stringify(
-      route.query
-    )}`
+    const tmpUrl = `${location.origin}/${
+      route.query.p == 'tenant' ? 'tenant' : 'student'
+    }/#/music-detail?${qs.stringify(route.query)}`
     const jumpUrl = ref<string>(tmpUrl)
 
     const colors: any = {

+ 11 - 0
src/tenant/App.vue

@@ -0,0 +1,11 @@
+<template>
+  <router-view></router-view>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+
+export default defineComponent({
+  name: 'App'
+});
+</script>

BIN
src/tenant/activation-code/bg-image.png


BIN
src/tenant/activation-code/icon-1.png


+ 139 - 0
src/tenant/activation-code/index.module.less

@@ -0,0 +1,139 @@
+.bgImg {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 214px;
+  // object-fit: cover;
+  z-index: -1;
+}
+
+.sticky {
+  :global {
+    .van-sticky {
+      background: url('./bg-image.png') no-repeat top center;
+      background-size: 100% 214px;
+    }
+  }
+}
+
+.codeContainer {
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  padding: 20px 40px 20px;
+
+  .codeInput {
+    background: #FFFFFF;
+    border-radius: 10px;
+    border: 1px solid rgba(255, 60, 129, 1);
+    // border-image: linear-gradient(270deg, rgba(255, 60, 129, 1), rgba(255, 118, 166, 1)) 2 2;
+    height: 48px;
+    width: 100%;
+    text-align: center;
+    font-size: 16px;
+    font-weight: 400;
+    color: #333;
+
+    &::placeholder {
+      color: #AAAAAA
+    }
+  }
+
+  .codeBtn {
+    margin-top: 25px;
+    background: linear-gradient(270deg, #FF7B57 0%, #FF3460 100%);
+    border-radius: 22px;
+    border: none;
+    height: 44px;
+    font-size: 16px;
+    font-weight: 500;
+    color: #FFFFFF;
+  }
+}
+
+.colGroup {
+  margin: 20px 13px;
+  background: #FFFFFF;
+  border-radius: 10px;
+  padding: 0 12px;
+
+  .title {
+    display: flex;
+    align-items: center;
+    padding: 16px 0;
+    font-size: 16px;
+    font-weight: 600;
+    color: #131415;
+    line-height: 1;
+
+    img {
+      margin-top: -1px;
+      width: 18px;
+      height: 18px;
+      margin-right: 4px;
+    }
+  }
+
+
+  .colRow {
+    :global {
+      .van-col {
+        font-size: 14px;
+        font-weight: 500;
+        color: #333333;
+      }
+
+      .van-col--9 {
+        text-align: center;
+      }
+    }
+  }
+
+  .codeList {
+    min-height: 40vh;
+
+    :global {
+      .van-row {
+        padding: 16px 0;
+        font-size: 12px;
+        font-weight: 400;
+        color: #444444;
+        border-bottom: 1px solid #F2F2F2;
+
+        &:last-child {
+          border: none;
+        }
+      }
+
+      .van-col {
+        display: flex;
+        align-items: center;
+      }
+
+      .van-col--9 {
+        text-align: center;
+        justify-content: center;
+      }
+    }
+
+    .c1 {
+      color: #248FFE;
+    }
+
+    .c2 {
+      color: #FE2451;
+    }
+
+    .c3 {
+      color: #AAAAAA;
+    }
+
+    .liveBtn {
+      background: #FE2451;
+      border-radius: 6px;
+      padding: 0 20px;
+      height: 25px;
+    }
+  }
+}

+ 197 - 0
src/tenant/activation-code/index.tsx

@@ -0,0 +1,197 @@
+import TheSticky from '@/components/the-sticky'
+import { defineComponent, onMounted, reactive } from 'vue'
+import styles from './index.module.less'
+import ColHeader from '@/components/col-header'
+import bgImg from './bg-image.png'
+import { Button, List, Row, Col, Toast } from 'vant'
+import icon1 from './icon-1.png'
+import request from '@/helpers/request'
+import ColResult from '@/components/col-result'
+
+export default defineComponent({
+  name: 'activation-code',
+  setup() {
+    // tenantActivationCode/page
+    const state = reactive({
+      refreshing: false,
+      height: 0, // 页面头部高度,为了处理下拉刷新用的
+      list: [],
+      dataShow: true, // 判断是否有数据
+      loading: false,
+      finished: false,
+      params: {
+        page: 1,
+        rows: 10
+      },
+      isClick: false,
+      code: ''
+    })
+    const getList = async (hideLoading = false) => {
+      try {
+        if (state.isClick) return
+        state.isClick = true
+        const res = await request.post(
+          '/api-student/tenantActivationCode/page',
+          {
+            data: {
+              ...state.params
+            },
+            hideLoading: hideLoading
+          }
+        )
+        state.isClick = false
+        state.loading = false
+        state.refreshing = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (state.list.length > 0 && result.current === 1) {
+          return
+        }
+        state.list = state.list.concat(result.rows || [])
+        state.finished = result.current >= result.pages
+        state.params.page = result.current + 1
+        state.dataShow = state.list.length > 0
+      } catch {
+        state.isClick = false
+        state.dataShow = false
+        state.refreshing = false
+        state.finished = true
+      }
+    }
+
+    const onSubmit = async () => {
+      try {
+        if (!state.code) {
+          Toast('请输入激活码')
+          return
+        }
+
+        await request.post(
+          '/api-student/tenantActivationCode/active?activationCode=' +
+            state.code
+        )
+        Toast.success('激活成功')
+        state.params.page = 1
+        state.list = []
+        getList(true)
+      } catch {
+        //
+      }
+    }
+
+    //
+    const onActiveation = async (item: any) => {
+      try {
+        await request.post(
+          '/api-student/tenantActivationCode/activeById?id=' + item.id
+        )
+        Toast.success('激活成功')
+        state.params.page = 1
+        state.list = []
+        getList(true)
+      } catch {
+        //
+      }
+    }
+
+    onMounted(() => {
+      getList()
+    })
+    return () => (
+      <div class={styles.activationCode}>
+        <div class={styles.sticky}>
+          <TheSticky position="top">
+            <ColHeader
+              background="transparent"
+              isFixed={false}
+              border={false}
+              color="#131415"
+            />
+            <div class={styles.codeContainer}>
+              <input
+                v-model={state.code}
+                placeholder="请输入激活码"
+                class={styles.codeInput}
+                maxlength={7}
+              />
+
+              <Button
+                type="primary"
+                round
+                class={styles.codeBtn}
+                block
+                onClick={onSubmit}
+              >
+                立即兑换
+              </Button>
+            </div>
+          </TheSticky>
+          <img class={styles.bgImg} src={bgImg} />
+
+          <List
+            loading-text=" "
+            finished={state.finished}
+            finished-text=" "
+            onLoad={getList}
+            style={{ overflow: 'hidden' }}
+          >
+            <div class={styles.colGroup}>
+              <div class={styles.title}>
+                <img src={icon1} />
+                激活记录
+              </div>
+              <Row class={styles.colRow}>
+                <Col span={5}>激活码</Col>
+                <Col span={4}>周期</Col>
+                <Col span={6}>激活状态</Col>
+                <Col span={9}>激活时间</Col>
+              </Row>
+
+              <div class={styles.codeList}>
+                {state.list.map((item: any) => (
+                  <Row>
+                    <Col span={5}>{item.activationCode}</Col>
+                    <Col span={4}>{item.purchaseCycle}个月</Col>
+                    <Col
+                      span={6}
+                      class={item.activationStatus ? styles.c1 : styles.c3}
+                    >
+                      {item.activationStatus ? '已激活' : '待激活'}
+                    </Col>
+                    <Col span={9}>
+                      {item.activationStatus ? (
+                        item.activationTime
+                      ) : (
+                        <Button
+                          class={styles.liveBtn}
+                          color="#FE2451"
+                          onClick={() => onActiveation(item)}
+                        >
+                          激活
+                        </Button>
+                      )}
+
+                      {/* 2023-07-22 12:00:02 */}
+                    </Col>
+                  </Row>
+                ))}
+                {!state.dataShow && (
+                  <div class={styles.emptyContainer}>
+                    <ColResult tips="暂无激活码" btnStatus={false} />
+                  </div>
+                )}
+              </div>
+            </div>
+          </List>
+          {/* ) : (
+            <div
+              class={styles.emptyContainer}
+            >
+              <ColResult tips="暂无学练统计" btnStatus={false} />
+            </div>
+          )} */}
+        </div>
+      </div>
+    )
+  }
+})

+ 558 - 0
src/tenant/exercise-record/echats.ts

@@ -0,0 +1,558 @@
+export const lineChartOption = {
+  tooltip: {
+    trigger: 'axis',
+    axisPointer: {
+      lineStyle: {
+        width: 2,
+        color: '#FFA3C2'
+      }
+    }
+  },
+  legend: {
+    show: false,
+    selected: {
+      //在这里设置默认展示就ok了
+      训练时长: true,
+      使用次数: true
+    }
+  },
+  xAxis: {
+    type: 'category',
+    boundaryGap: true,
+    axisLabel: {
+      show: true,
+      interval: 0
+    },
+    data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
+    // splitLine: {
+    //   show: true,
+    //   lineStyle: {
+    //     width: 2,
+    //     type: 'solid',
+    //     color: 'rgba(226,226,226,0.5)'
+    //   }
+    // }
+    // axisTick: {
+    //   show: false
+    // }
+  },
+  yAxis: [
+    {
+      type: 'value',
+      axisLabel: {
+        formatter: '{value}'
+      },
+      axisTick: {
+        show: false
+      },
+      splitArea: {
+        show: false,
+        areaStyle: {
+          color: ['rgba(255,255,255,0.2)']
+        }
+      },
+      minInterval: 1,
+      splitNumber: 5
+    }
+  ],
+  grid: {
+    left: '1%',
+    right: '1%',
+    top: '2  %',
+    bottom: 0,
+    containLabel: true
+  },
+  series: [
+    {
+      // smooth: true,
+      data: ['0.00', '0.00', '0.00', '0.00', '0.00', '0.00', '0.00'],
+      symbolSize: 10,
+      type: 'line',
+      name: '训练时长',
+      symbol: 'circle',
+      smooth: true,
+      itemStyle: {
+        color: '#FF7AA7',
+        borderColor: '#fff',
+        borderWidth: 3
+      },
+      lineStyle: {
+        width: 3 //设置线条粗细
+      },
+      areaStyle: {
+        color: {
+          type: 'linear',
+          x: 0,
+          y: 0,
+          x2: 0,
+          y2: 1,
+          colorStops: [
+            {
+              offset: 0,
+              color: 'rgba(255, 243, 246, 1)'
+              // 0% 处的颜色
+            },
+            {
+              offset: 1,
+              // 100% 处的颜色
+              color: 'rgba(255, 246, 248, 0)'
+            }
+          ]
+        }
+      },
+      emphasis: {
+        disabled: true
+      }
+    },
+    {
+      data: ['0.00', '0.00', '0.00', '0.00', '0.00', '0.00', '0.00'],
+      type: 'line',
+      name: '使用次数',
+      symbolSize: 10,
+      symbol: 'circle',
+      smooth: true,
+      itemStyle: {
+        color: '#3583FA',
+        borderColor: '#fff',
+        borderWidth: 3
+      },
+      lineStyle: {
+        width: 2 //设置线条粗细
+      },
+      areaStyle: {
+        color: {
+          type: 'linear',
+          x: 0,
+          y: 0,
+          x2: 0,
+          y2: 1,
+          colorStops: [
+            {
+              offset: 0,
+              color: 'rgba(212, 231, 255, 1)'
+              // 0% 处的颜色
+            },
+            {
+              offset: 1,
+              color: 'rgba(221, 235, 254, 0)' // 100% 处的颜色
+            }
+          ]
+        }
+      },
+      emphasis: {
+        disabled: true
+      }
+    }
+  ],
+  formatter: (item: any) => {
+    if (Array.isArray(item)) {
+      return [
+        item[0].axisValueLabel,
+        ...item.map(
+          (d: any, index: number) =>
+            `<br/>${
+              d.marker
+            }<span style="margin-top:10px;margin-left:5px;font-size: 13px;font-weight: 500;
+                  color: #333333;
+                  line-height: 18px;">${d.seriesName}: ${d.value}${
+              index === 0 ? '分钟' : '次'
+            } </span>`
+        )
+      ].join('')
+    } else {
+      return item
+    }
+  }
+  // dataZoom: [
+  //   {
+  //     type: 'slider',
+  //     start: 5,
+  //     end: 100,
+  //     filterMode: 'empty'
+  //   }
+  // ]
+}
+
+// export const lineChartOption = {
+//   legend: { show: false },
+//   emphasis: { lineStyle: { width: 2 } },
+//   xAxis: {
+//     boundaryGap: false,
+//     data: [
+//       '01月',
+//       '02月',
+//       '03月',
+//       '04月',
+//       '05月',
+//       '06月',
+//       '07月',
+//       '08月',
+//       '09月',
+//       '10月',
+//       '11月',
+//       '12月'
+//     ],
+//     type: 'category',
+//     axisLine: { lineStyle: { color: '#8C8C8C' } }
+//   },
+//   color: [
+//     '#5B8FF9',
+//     '#2DC7AA',
+//     '#91DD1C',
+//     '#FFA92C',
+//     '#BE7E2E',
+//     '#1C96DD',
+//     '#D22CFF',
+//     '#FF3C3C',
+//     '#1AEE3E',
+//     '#00c9ff',
+//     '#7c47ff'
+//   ],
+//   series: [
+//     {
+//       lineStyle: { width: 1 },
+//       data: [
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00'
+//       ],
+//       symbol: 'circle',
+//       name: '陪练课',
+//       type: 'line',
+//       emphasis: { lineStyle: { width: 1 } }
+//     },
+//     {
+//       lineStyle: { width: 1 },
+//       data: [
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00'
+//       ],
+//       symbol: 'circle',
+//       name: '直播课',
+//       type: 'line',
+//       emphasis: { lineStyle: { width: 1 } }
+//     },
+//     {
+//       lineStyle: { width: 1 },
+//       data: [
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00'
+//       ],
+//       symbol: 'circle',
+//       name: '视频课',
+//       type: 'line',
+//       emphasis: { lineStyle: { width: 1 } }
+//     },
+//     {
+//       lineStyle: { width: 1 },
+//       data: [
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00'
+//       ],
+//       symbol: 'circle',
+//       name: '乐谱',
+//       type: 'line',
+//       emphasis: { lineStyle: { width: 1 } }
+//     },
+//     {
+//       lineStyle: { width: 1 },
+//       data: [
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00'
+//       ],
+//       symbol: 'circle',
+//       name: '小酷Ai推广',
+//       type: 'line',
+//       emphasis: { lineStyle: { width: 1 } }
+//     },
+//     {
+//       lineStyle: { width: 1 },
+//       data: [
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00'
+//       ],
+//       symbol: 'circle',
+//       name: '直播课推荐',
+//       type: 'line',
+//       emphasis: { lineStyle: { width: 1 } }
+//     },
+//     {
+//       lineStyle: { width: 1 },
+//       data: [
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00'
+//       ],
+//       symbol: 'circle',
+//       name: '视频课推荐',
+//       type: 'line',
+//       emphasis: { lineStyle: { width: 1 } }
+//     },
+//     {
+//       lineStyle: { width: 1 },
+//       data: [
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00'
+//       ],
+//       symbol: 'circle',
+//       name: '商品推荐',
+//       type: 'line',
+//       emphasis: { lineStyle: { width: 1 } }
+//     },
+//     {
+//       lineStyle: { width: 1 },
+//       data: [
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00'
+//       ],
+//       symbol: 'circle',
+//       name: '乐谱推荐',
+//       type: 'line',
+//       emphasis: { lineStyle: { width: 1 } }
+//     },
+//     {
+//       lineStyle: { width: 1 },
+//       data: [
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00'
+//       ],
+//       symbol: 'circle',
+//       name: '专辑推荐',
+//       type: 'line',
+//       emphasis: { lineStyle: { width: 1 } }
+//     },
+//     {
+//       lineStyle: { width: 1 },
+//       data: [
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00',
+//         '0.00'
+//       ],
+//       symbol: 'circle',
+//       name: '活动报名',
+//       type: 'line',
+//       emphasis: { lineStyle: { width: 1 } }
+//     }
+//   ],
+//   title: { show: false },
+//   grid: {
+//     bottom: '3%',
+//     containLabel: true,
+//     left: '3%',
+//     right: '5%',
+//     top: '7%'
+//   },
+//   tooltip: {
+//     trigger: 'axis',
+//     confine: true
+//     // position: function (point, params, dom, rect, size) {
+//     //   // 鼠标坐标和提示框位置的参考坐标系是:以外层div的左上角那一点为原点,x轴向右,y轴向下
+//     //   // 提示框位置
+//     //   var x = 0 // x坐标位置
+//     //   var y = 0 // y坐标位置
+
+//     //   // 当前鼠标位置
+//     //   var pointX = point[0]
+//     //   var pointY = point[1]
+
+//     //   // 外层div大小
+//     //   // var viewWidth = size.viewSize[0];
+//     //   // var viewHeight = size.viewSize[1];
+
+//     //   // 提示框大小
+//     //   var boxWidth = size.contentSize[0]
+//     //   var boxHeight = size.contentSize[1]
+
+//     //   // boxWidth > pointX 说明鼠标左边放不下提示框 --- 情况
+//     //   if (boxWidth > pointX) {
+//     //     x = 5 // 自己定个x坐标值,以防出屏
+//     //     y -= 15 // 防止点被覆盖住,可根据情况自行调节
+//     //   } else {
+//     //     // 左边放的下
+//     //     x = pointX - boxWidth - 15
+//     //   }
+
+//     //   // boxHeight > pointY 说明鼠标上边放不下提示框 --- 情况
+//     //   if (boxHeight + 20 > pointY) {
+//     //     y = pointY + 15
+//     //   } else if (boxHeight > pointY) {
+//     //     y = 5
+//     //   } else {
+//     //     // 上边放得下
+//     //     y += pointY - boxHeight
+//     //   }
+//     //   //return [x, y]
+//     //   return [x, '20%'] //这里采用固定y轴 x轴随鼠标位置变化
+//     //   // return [point[0], '10%']
+//     // }
+//   },
+//   yAxis: {
+//     type: 'value',
+//     splitLine: {
+//       axisLine: { lineStyle: { color: '#8C8C8C' } },
+//       lineStyle: { color: ['#E2E2E2'] }
+//     }
+//   },
+//   dataZoom: [{ type: 'inside', throttle: 100 }],
+//   toolbox: { feature: { saveAsImage: { show: false } } }
+// }
+
+export const pieChartOption = {
+  tooltip: {
+    position: ['30%', '30%'],
+    trigger: 'item',
+    padding: 3,
+    textStyle: { fontSize: 12 },
+    borderWidth: 0,
+    formatter: '{b} : {c} ({d}%)'
+  },
+  series: [
+    {
+      avoidLabelOverlap: false,
+      label: { show: false },
+      data: [
+        { name: '陪练课', value: '0.00' },
+        { name: '直播课', value: '0.00' },
+        { name: '视频课', value: '0.00' },
+        { name: '乐谱', value: '0.00' },
+        { name: '小酷Ai推广', value: '0.00' },
+        { name: '直播课推荐', value: '0.00' },
+        { name: '视频课推荐', value: '0.00' },
+        { name: '商品推荐', value: '0.00' },
+        { name: '乐谱推荐', value: '0.00' },
+        { name: '专辑推荐', value: '0.00' },
+        { name: '活动报名', value: '0.00' }
+      ],
+      type: 'pie',
+      radius: ['50%', '80%']
+    }
+  ],
+  grid: {
+    bottom: '0%',
+    containLabel: true,
+    left: '0%',
+    right: '0%',
+    top: '0%'
+  },
+  toolbox: { feature: { saveAsImage: { show: false } } },
+  color: [
+    '#5B8FF9',
+    '#2DC7AA',
+    '#91DD1C',
+    '#FFA92C',
+    '#BE7E2E',
+    '#1C96DD',
+    '#D22CFF',
+    '#FF3C3C',
+    '#1AEE3E',
+    '#00c9ff',
+    '#7c47ff'
+  ]
+}

+ 313 - 0
src/tenant/exercise-record/exercis-detail.module.less

@@ -0,0 +1,313 @@
+.exercisContainer {
+  :global {
+
+    .van-calendar__day--end,
+    .van-calendar__day--start,
+    .van-calendar__day--start-end,
+    .van-calendar__day--multiple-middle,
+    .van-calendar__day--multiple-selected {
+      background: #FF5074;
+    }
+  }
+
+  .bgImg {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 214px;
+    z-index: -1;
+  }
+}
+
+.userMember {
+  background-color: transparent !important;
+  width: auto;
+  padding: 0;
+  // border-radius: 10px;
+  padding: 18px;
+
+
+  .level {
+    width: 44px;
+    height: 17px;
+  }
+
+  .userImgSection {
+    position: relative;
+    padding: 3px;
+    background: #fff;
+    margin-right: 12px;
+    border-radius: 50%;
+
+    &::before {
+      content: ' ';
+      position: absolute;
+      left: 1px;
+      top: 1px;
+      bottom: 1px;
+      right: 1px;
+      background-color: #fff;
+      border-radius: 50%;
+    }
+  }
+
+  .userImg {
+    width: 46px;
+    height: 46px;
+    border-radius: 50%;
+    vertical-align: middle;
+    overflow: hidden;
+  }
+
+  .userInfo {
+    display: flex;
+    align-items: center;
+    color: #fff;
+    padding-bottom: 5px;
+
+    .name {
+      padding-right: 5px;
+      max-width: 100px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      font-size: 18px;
+      font-weight: 600;
+      color: #742626;
+    }
+  }
+
+  .timeRemaining {
+    margin-top: 0;
+    font-size: 14px;
+    color: #c0c0c0;
+  }
+
+  .subjectTag {
+    font-size: 12px;
+    font-weight: 500;
+    color: #FE2451;
+    padding: 1px 7px;
+    background: rgba(255, 255, 255, 0.3);
+    border-radius: 20px;
+    border: 1px solid #FFFFFF;
+    display: inline-flex;
+    align-items: center;
+    line-height: 1;
+
+    .iconSubject {
+      width: 13px;
+      height: 13px;
+      margin-right: 4px;
+    }
+  }
+}
+
+.itemBottom {
+  margin: 0 13px 15px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-around;
+  text-align: center;
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 20px 0;
+
+  .itemBottomDot {
+    width: 25%;
+
+    .dotMain {
+      font-size: 24px;
+      color: #333333;
+      line-height: 30px;
+      margin-bottom: 4px;
+      font-family: DINAlternate-Bold, DINAlternate;
+      font-weight: bold;
+
+      span {
+        margin-left: 1px;
+        font-size: 12px;
+        font-weight: 400;
+        color: #333333;
+        line-height: 17px;
+      }
+    }
+
+    .dotSub {
+      font-size: 12px;
+      font-weight: 400;
+      color: #777777;
+      line-height: 17px;
+    }
+  }
+}
+
+.topWrap {
+  :global {
+    .van-sticky {
+      background: url('../images/bg-image.png') no-repeat top center;
+      background-size: 100% 214px;
+    }
+  }
+
+  .topInfoRight {
+    // width: 50%;
+    margin-bottom: 18px;
+    padding: 0 10%;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: center;
+
+    .infoDay,
+    .infoTime {
+      width: 50%;
+    }
+
+    .infoDay {
+      position: relative;
+      margin-right: 20px;
+
+      &::after {
+        position: absolute;
+        content: ' ';
+        right: -12px;
+        top: 12px;
+        width: 1px;
+        height: 35px;
+        background-color: #E0E5E8;
+      }
+    }
+
+    .infoDayMain {
+      font-size: 24px;
+      color: #333333;
+      line-height: 28px;
+      margin-bottom: 7px;
+      font-family: DINAlternate-Bold, DINAlternate;
+      font-weight: 600;
+      text-align: center;
+
+      span {
+        margin-left: 2px;
+        font-size: 12px;
+        font-weight: 400;
+        color: #333333;
+        line-height: 17px;
+      }
+    }
+
+    .infoDaysub {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 12px;
+      font-weight: 400;
+      color: #333333;
+      line-height: 17px;
+      text-align: center;
+
+      img {
+        width: 14px;
+        height: 14px;
+        margin-right: 3px;
+      }
+    }
+  }
+
+}
+
+.trainWeek {
+  margin: 15px 13px 12px;
+  min-height: 267px;
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 20px 15px;
+
+  .exerciseWeek {
+    height: 200px;
+  }
+
+  .TrainDataTopRight {
+    display: flex;
+    align-items: center;
+    margin-bottom: 18px;
+  }
+
+  .DataTopRightItem {
+    display: flex;
+    align-items: center;
+    font-size: 13px;
+    color: #777777;
+    line-height: 1;
+
+    &:first-child {
+      margin-right: 20px;
+
+      .DataTopRightDot {
+        background-color: #FF7AA7;
+      }
+    }
+
+    &:last-child {
+      .DataTopRightDot {
+        background-color: #3583FA;
+      }
+    }
+
+    &.DataTopRightItemDis {
+      .DataTopRightDot {
+        background-color: #e8e8e8 !important
+      }
+    }
+
+    .DataTopRightDot {
+      width: 12px;
+      height: 12px;
+      border-radius: 2px;
+      background-color: #ccc;
+      margin-right: 4px;
+    }
+  }
+
+  .trainTitle {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-bottom: 16px;
+
+    img {
+      width: 64px;
+      height: 15px;
+    }
+
+    .timeRange {
+      display: flex;
+      align-items: center;
+      font-size: 14px;
+      color: #131415;
+
+
+      .iconArrow {
+        display: inline-block;
+        width: 9px;
+        height: 5px;
+        margin-left: 3px;
+        background: url('./images/icon-arrow.png') no-repeat center;
+        background-size: contain;
+      }
+    }
+  }
+}
+
+.fullRefresh {
+  min-height: calc(100vh - var(--header-height) - 275px)
+}
+
+.emptyContainer {
+  height: calc(100vh - var(--header-height) - 275px);
+  display: flex;
+  align-items: center;
+}

+ 433 - 0
src/tenant/exercise-record/exercis-detail.tsx

@@ -0,0 +1,433 @@
+import dayjs from 'dayjs'
+import isBetween from 'dayjs/plugin/isBetween'
+dayjs.extend(isBetween)
+import { List, Image, Calendar, Cell } from 'vant'
+import OFullRefresh from '@/components/the-full-refresh'
+import DetailItem from './modals/detail-item'
+import {
+  defineComponent,
+  onMounted,
+  reactive,
+  ref,
+  computed,
+  markRaw,
+  nextTick
+} from 'vue'
+import { useRoute } from 'vue-router'
+import styles from './exercis-detail.module.less'
+import request from '@/helpers/request'
+import { state as baseState } from '@/state'
+import TheSticky from '@/components/the-sticky'
+import ColHeader from '@/components/col-header'
+import ColResult from '@/components/col-result'
+import bgImg from '../images/bg-image.png'
+import iconStudent from '@common/images/icon_student.png'
+import iconLogo from '../member-center/images/icon-logo-default.png'
+import iconLogoActive from '../member-center/images/icon-logo.png'
+import iconSubject from './images/icon-subject.png'
+import iconTrainTitle from './images/train-title.png'
+
+import * as echarts from 'echarts/core'
+import {
+  BarChart,
+  // 系列类型的定义后缀都为 SeriesOption
+  BarSeriesOption,
+  LineChart,
+  LineSeriesOption
+} from 'echarts/charts'
+import { PieChart } from 'echarts/charts'
+import {
+  TitleComponent,
+  // 组件类型的定义后缀都为 ComponentOption
+  TitleComponentOption,
+  TooltipComponent,
+  TooltipComponentOption,
+  GridComponent,
+  GridComponentOption,
+  // 数据集组件
+  DatasetComponent,
+  DatasetComponentOption,
+  // 内置数据转换器组件 (filter, sort)
+  TransformComponent,
+  LegendComponent,
+  ToolboxComponent,
+  DataZoomComponent
+} from 'echarts/components'
+import { LabelLayout, UniversalTransition } from 'echarts/features'
+import { CanvasRenderer } from 'echarts/renderers'
+import { lineChartOption } from './echats'
+
+// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
+type ECOption = echarts.ComposeOption<
+  | BarSeriesOption
+  | LineSeriesOption
+  | TitleComponentOption
+  | TooltipComponentOption
+  | GridComponentOption
+  | DatasetComponentOption
+>
+
+// 注册必须的组件
+echarts.use([
+  TitleComponent,
+  TooltipComponent,
+  GridComponent,
+  DatasetComponent,
+  TransformComponent,
+  BarChart,
+  LabelLayout,
+  UniversalTransition,
+  CanvasRenderer,
+  PieChart,
+  ToolboxComponent,
+  LegendComponent,
+  DataZoomComponent,
+  LineChart
+])
+
+export default defineComponent({
+  name: 'exercis-detail',
+  setup() {
+    const route = useRoute()
+    const state = reactive({
+      showPopoverTime: false,
+      currentDate: [dayjs().format('YYYY'), dayjs().format('MM')],
+      isClick: false,
+      practiceMonthName: route.query.practiceMonthName
+        ? route.query.practiceMonthName
+        : dayjs().format('YYYY') + '年' + dayjs().format('MM') + '月',
+      userTrainOverView: {
+        trainDays: 0,
+        trainNum: 0,
+        trainTime: 0,
+        avgTrainTime: 0
+      },
+      userTrainChartData: [] as any,
+      myChart: null as any
+    })
+    const qualifiedFlag = ref(true)
+    const unqualifiedFlag = ref(true)
+
+    // const chartRef = ref<HTMLDivElement | null>(null)
+    // const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
+
+    const userInfo = computed(() => {
+      const users = baseState.user.data
+      return {
+        username: users?.username,
+        phone: users?.phone,
+        avatar: users?.heardUrl,
+        id: users?.userId,
+        subjectName: users?.subjectName,
+        memberRankSettingId: users?.memberRankSettingId,
+        isVip: users?.isVip,
+        membershipDays: users?.membershipDays,
+        membershipEndTime: users?.membershipEndTime
+      }
+    })
+    const forms = reactive({
+      startTime: dayjs().day(1).format('YYYY-MM-DD'),
+      endTime: dayjs().day(7).format('YYYY-MM-DD'),
+      page: 1,
+      rows: 20
+    })
+    const refreshing = ref(false)
+    const loading = ref(false)
+    const finished = ref(false)
+    const showContact = ref(false)
+    const list = ref([])
+    const getList = async () => {
+      if (state.isClick) {
+        return
+      }
+      state.isClick = true
+      if (refreshing.value) {
+        list.value = []
+        forms.page = 1
+        refreshing.value = false
+      }
+      try {
+        const { data } = await request.get(
+          `/api-student/sysMusicRecord/studentTrainDataByWeek`,
+          {
+            params: { ...forms }
+          }
+        )
+
+        // 在第一页的时候才处理数据显示
+        if (data.detail.pageNo === 1) {
+          state.userTrainOverView = data.userTrainOverView
+        }
+        if (list.value.length > 0 && data.detail.pageNo === 1) {
+          return
+        }
+        state.userTrainChartData = data.userTrainChartData || []
+        list.value = list.value.concat(data.detail.rows || [])
+        forms.page = data.detail.pageNo + 1
+        showContact.value = list.value.length > 0
+        loading.value = false
+        finished.value = data.detail.pageNo >= data.detail.totalPage
+      } catch {
+        showContact.value = false
+        finished.value = true
+      }
+      state.isClick = false
+
+      // if (showContact.value) {
+      nextTick(() => {
+        if (document.getElementById('exerciseWeek')) {
+          state.myChart = markRaw(
+            echarts.init(
+              document.getElementById('exerciseWeek') as HTMLDivElement
+            )
+          )
+
+          const cloudTime: any = []
+          const cloudNum: any = []
+          state.userTrainChartData.forEach((data: any) => {
+            const indexData = data.indexMonthData || []
+            if (data.dataType === 'CLOUD_STUDY_TRAIN_TIME') {
+              indexData.forEach((d: any) => {
+                cloudTime.push(d.totalNum)
+              })
+            } else if (data.dataType === 'CLOUD_STUDY_TRAIN_NUM') {
+              indexData.forEach((d: any) => {
+                cloudNum.push(d.totalNum)
+              })
+            }
+          })
+          lineChartOption.series[0].data = cloudTime
+          lineChartOption.series[1].data = cloudNum
+
+          state.myChart.clear()
+          state.myChart.setOption(lineChartOption)
+        }
+      })
+      // }
+    }
+    const onRefresh = () => {
+      finished.value = false
+      // 重新加载数据
+      // 将 loading 设置为 true,表示处于加载状态
+      loading.value = true
+      getList()
+    }
+
+    onMounted(async () => {
+      await getList()
+    })
+
+    return () => (
+      <div class={[styles.exercisContainer]}>
+        <div class={styles.topWrap}>
+          <TheSticky position="top">
+            <ColHeader
+              border={false}
+              background={'transparent'}
+              color={'#333333'}
+            />
+            <Cell
+              class={styles.userMember}
+              labelClass={styles.timeRemaining}
+              border={false}
+              v-slots={{
+                icon: () => (
+                  <div class={styles.userImgSection}>
+                    <Image
+                      class={styles.userImg}
+                      src={userInfo.value.avatar || iconStudent}
+                      fit="cover"
+                    />
+                  </div>
+                ),
+                title: () => (
+                  <div class={styles.userInfo}>
+                    <span class={styles.name}>{userInfo.value.username}</span>
+                    <Image
+                      class={styles.level}
+                      src={userInfo.value.isVip ? iconLogoActive : iconLogo}
+                    />
+                  </div>
+                ),
+                label: () => (
+                  <div class={styles.subjectName}>
+                    <span class={styles.subjectTag}>
+                      <img src={iconSubject} class={styles.iconSubject} />
+                      {userInfo.value.subjectName}
+                    </span>
+                  </div>
+                )
+              }}
+            ></Cell>
+
+            <div class={styles.itemBottom}>
+              <div class={styles.itemBottomDot}>
+                <p class={styles.dotMain}>
+                  {state.userTrainOverView.trainTime || 0}
+                  <span>分钟</span>{' '}
+                </p>
+                <p class={styles.dotSub}> 累计练习时长</p>
+              </div>
+              <div class={styles.itemBottomDot}>
+                <p class={styles.dotMain}>
+                  {state.userTrainOverView.trainDays || 0}
+                  <span>天</span>{' '}
+                </p>
+                <p class={styles.dotSub}>累计练习天数 </p>
+              </div>
+              <div class={styles.itemBottomDot}>
+                <p class={styles.dotMain}>
+                  {state.userTrainOverView.avgTrainTime || 0}
+                  <span>分钟</span>{' '}
+                </p>
+                <p class={styles.dotSub}>平均训练时长 </p>
+              </div>
+              <div class={styles.itemBottomDot}>
+                <p class={styles.dotMain}>
+                  {state.userTrainOverView.trainNum || 0}
+                  <span>次</span>{' '}
+                </p>
+                <p class={styles.dotSub}>累计训练次数 </p>
+              </div>
+            </div>
+          </TheSticky>
+        </div>
+        <img class={styles.bgImg} src={bgImg} />
+
+        <div class={styles.trainWeek}>
+          <div class={styles.trainTitle}>
+            <img src={iconTrainTitle} />
+
+            <span
+              class={styles.timeRange}
+              onClick={() => (state.showPopoverTime = true)}
+            >
+              {dayjs(forms.startTime).format('YYYY-MM-DD')}至
+              {dayjs(forms.endTime).format('YYYY-MM-DD')}
+              <i class={styles.iconArrow}></i>
+            </span>
+          </div>
+          <div class={styles.TrainDataTopRight}>
+            <div
+              onClick={() => {
+                qualifiedFlag.value = !qualifiedFlag.value
+                lineChartOption.legend.selected['训练时长'] =
+                  qualifiedFlag.value
+                state.myChart.setOption(lineChartOption)
+              }}
+              class={[
+                styles.DataTopRightItem,
+                qualifiedFlag.value ? '' : styles.DataTopRightItemDis
+              ]}
+            >
+              <div class={styles.DataTopRightDot}></div>
+              <p>训练时长</p>
+            </div>
+            <div
+              onClick={() => {
+                unqualifiedFlag.value = !unqualifiedFlag.value
+                lineChartOption.legend.selected['使用次数'] =
+                  unqualifiedFlag.value
+                state.myChart.setOption(lineChartOption)
+              }}
+              class={[
+                styles.DataTopRightItem,
+                unqualifiedFlag.value ? '' : styles.DataTopRightItemDis
+              ]}
+            >
+              <div class={[styles.DataTopRightDot, styles.red]}></div>
+              <p>使用次数</p>
+            </div>
+          </div>
+          <div id="exerciseWeek" class={styles.exerciseWeek}></div>
+        </div>
+        {showContact.value ? (
+          <OFullRefresh
+            v-model:modelValue={refreshing.value}
+            onRefresh={onRefresh}
+            class={styles.fullRefresh}
+          >
+            <List
+              loading-text=" "
+              finished={finished.value}
+              finished-text=" "
+              onLoad={getList}
+              style={{ overflow: 'hidden' }}
+            >
+              {list.value.map((item: any) => (
+                <DetailItem item={item} />
+              ))}
+            </List>
+          </OFullRefresh>
+        ) : (
+          <div
+            // style={{
+            //   height: `calc(100vh - var(--header-height))`,
+            //   display: 'flex',
+            //   alignItems: 'center'
+            // }}
+            class={styles.emptyContainer}
+          >
+            <ColResult
+              tips="暂无学练统计"
+              classImgSize="SMALL"
+              btnStatus={false}
+            />
+          </div>
+        )}
+
+        <Calendar
+          v-model:show={state.showPopoverTime}
+          firstDayOfWeek={1}
+          showConfirm={false}
+          type="range"
+          title="周期选择"
+          maxRange={7}
+          minDate={new Date('2023-02-27')}
+          defaultDate={[
+            dayjs(forms.startTime).toDate(),
+            dayjs(forms.endTime).toDate()
+          ]}
+          style={{
+            height: '70%'
+          }}
+          onSelect={(item: any) => {
+            forms.startTime = ''
+            forms.endTime = ''
+            if (
+              !dayjs(item[0]).isBetween(
+                dayjs(forms.startTime),
+                dayjs(forms.endTime)
+              )
+            ) {
+              const week = dayjs(item[0]).day()
+              if (week === 0) {
+                // 星期天
+                forms.startTime = dayjs(item[0])
+                  .subtract(6, 'day')
+                  .format('YYYY-MM-DD')
+                forms.endTime = dayjs(item[0]).format('YYYY-MM-DD')
+              } else if (week === 1) {
+                // 星期一
+                forms.startTime = dayjs(item[0]).format('YYYY-MM-DD')
+                forms.endTime = dayjs(item[0])
+                  .add(6, 'day')
+                  .format('YYYY-MM-DD')
+              } else {
+                forms.startTime = dayjs(item[0])
+                  .subtract(week - 1, 'day')
+                  .format('YYYY-MM-DD')
+                forms.endTime = dayjs(item[0])
+                  .add(7 - week, 'day')
+                  .format('YYYY-MM-DD')
+              }
+            }
+            state.showPopoverTime = false
+            refreshing.value = true
+            getList()
+          }}
+        />
+      </div>
+    )
+  }
+})

BIN
src/tenant/exercise-record/images/Image1.png


BIN
src/tenant/exercise-record/images/Image2.png


BIN
src/tenant/exercise-record/images/Image3.png


BIN
src/tenant/exercise-record/images/Image4.png


BIN
src/tenant/exercise-record/images/Image5.png


BIN
src/tenant/exercise-record/images/good-icon.png


BIN
src/tenant/exercise-record/images/icon-arrow.png


BIN
src/tenant/exercise-record/images/icon-play.png


BIN
src/tenant/exercise-record/images/icon-subject.png


BIN
src/tenant/exercise-record/images/train-title.png


+ 114 - 0
src/tenant/exercise-record/modals/detail-item.module.less

@@ -0,0 +1,114 @@
+.itemWrap {
+  background: #ffffff;
+  border-radius: 10px;
+  padding: 12px 15px 20px;
+  margin: 0 13px 13px;
+
+  .itemTop {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    border-bottom: 1px solid #f2f2f2;
+    padding-bottom: 12px;
+
+    .itemTopLeft {
+      .itemTopMain {
+        display: flex;
+        align-items: center;
+        margin-bottom: 6px;
+
+        span {
+          display: inline-block;
+          height: 22px;
+          font-size: 16px;
+          font-weight: 500;
+          color: #333333;
+          line-height: 22px;
+
+          max-width: 150px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+
+        .iconPlay {
+          margin-top: -1px;
+          font-size: 20px;
+          width: 20px;
+          height: 20px;
+          margin-left: 6px;
+        }
+      }
+
+
+      .itemTopSub {
+        font-size: 12px !important;
+        font-weight: 400;
+        color: #777777;
+        line-height: 17px;
+      }
+    }
+
+    .itemTopRight {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+
+      .imgWrap {
+        width: 100px;
+        height: 33px;
+        background: #e9e3ff;
+        border-radius: 19px;
+
+        img {
+          width: 100%;
+          height: 100%;
+        }
+      }
+
+      .imgIcon {
+        font-size: 16px;
+        color: #d8d8d8;
+        margin-left: 6px;
+      }
+    }
+  }
+
+  .itemBottom {
+    margin-top: 15px;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-around;
+    text-align: center;
+
+    .itemBottomDot {
+      width: 25%;
+
+      .dotMain {
+        font-size: 26px;
+        color: #333333;
+        line-height: 30px;
+        margin-bottom: 4px;
+        font-family: DINAlternate-Bold, DINAlternate;
+        font-weight: bold;
+
+        span {
+          margin-left: 1px;
+          font-size: 12px;
+          font-weight: 400;
+          color: #333333;
+          line-height: 17px;
+        }
+      }
+
+      .dotSub {
+        font-size: 12px;
+        font-weight: 400;
+        color: #777777;
+        line-height: 17px;
+      }
+    }
+  }
+}

+ 133 - 0
src/tenant/exercise-record/modals/detail-item.tsx

@@ -0,0 +1,133 @@
+import { defineComponent } from 'vue'
+import styles from './detail-item.module.less'
+import { postMessage } from '@/helpers/native-message'
+import { Icon } from 'vant'
+import Image1 from '../images/Image1.png'
+import Image2 from '../images/Image2.png'
+import Image3 from '../images/Image3.png'
+import Image4 from '../images/Image4.png'
+import Image5 from '../images/Image5.png'
+import iconPlay from '../images/icon-play.png'
+
+const scoreInfos: any = {
+  1: {
+    img: Image1,
+    tips: '你的演奏不太好,音准和完整性还需加强,再练一练吧~',
+    mome: '敢于尝试'
+  },
+  2: {
+    img: Image2,
+    tips: '你的演奏还不熟练,音准和完整性还需加强,加紧训练才能有好成绩哦~',
+    mome: '还要加油哦~'
+  },
+  3: {
+    img: Image3,
+    tips: '你的演奏还不流畅,音准和节奏还需加强,科学的练习才能更完美哦~',
+    mome: '突破自我'
+  },
+  4: {
+    img: Image4,
+    tips: '你的演奏还不错,继续加油吧,加强音准,离完美就差一步啦~',
+    mome: '崭露头角'
+  },
+  5: {
+    img: Image5,
+    tips: '你的演奏非常不错,音准的把握和节奏稍有瑕疵,完整性把握的很好~',
+    mome: '你很棒'
+  }
+}
+export default defineComponent({
+  props: ['item'],
+  name: 'detail-item',
+
+  setup(props) {
+    const getLeveByScoreId = (score?: number) => {
+      if (!score && typeof score !== 'number') {
+        return {}
+      }
+      let leve: any = 1
+      if (score > 20 && score <= 40) {
+        leve = 2
+      } else if (score > 40 && score <= 60) {
+        leve = 3
+      } else if (score > 60 && score <= 80) {
+        leve = 4
+      } else if (score > 80) {
+        leve = 5
+      }
+      return leve
+    }
+    const gotoDetail = () => {
+      const behaviorId = +new Date()
+      postMessage({
+        api: 'openAccompanyWebView',
+        content: {
+          url:
+            location.origin +
+            '/accompany/colexiu-report.html?id=' +
+            props.item.id +
+            '&behaviorId=' +
+            behaviorId,
+          orientation: 0,
+          isHideTitle: true,
+          statusBarTextColor: false,
+          isOpenLight: true
+        }
+      })
+    }
+    return () => (
+      <div class={styles.itemWrap} onClick={gotoDetail}>
+        <div class={styles.itemTop}>
+          <div class={styles.itemTopLeft}>
+            <p class={styles.itemTopMain}>
+              <span>{props.item.sysMusicScoreName}</span>
+              {props.item.videoFilePath && (
+                <Icon class={styles.iconPlay} name={iconPlay} />
+              )}
+            </p>
+            <p class={styles.itemTopSub}>{props.item.createTime}</p>
+          </div>
+          <div class={styles.itemTopRight}>
+            <div class={styles.imgWrap}>
+              <img
+                src={scoreInfos[getLeveByScoreId(props.item.score || 0)].img}
+                alt=""
+              />
+            </div>
+            <Icon name="arrow" class={styles.imgIcon} />
+          </div>
+        </div>
+        <div class={styles.itemBottom}>
+          <div class={styles.itemBottomDot}>
+            <p class={styles.dotMain} style={{ color: '#ff5a56' }}>
+              {props.item.score || 0}
+              <span>分</span>{' '}
+            </p>
+            <p class={styles.dotSub}> 综合得分</p>
+          </div>
+          <div class={styles.itemBottomDot}>
+            <p class={styles.dotMain}>
+              {props.item.intonation || 0}
+              <span>分</span>{' '}
+            </p>
+            <p class={styles.dotSub}>音准 </p>
+          </div>
+          <div class={styles.itemBottomDot}>
+            <p class={styles.dotMain}>
+              {props.item.cadence || 0}
+              <span>分</span>{' '}
+            </p>
+            <p class={styles.dotSub}>节奏 </p>
+          </div>
+          <div class={styles.itemBottomDot}>
+            <p class={styles.dotMain}>
+              {props.item.integrity || 0}
+              <span>分</span>{' '}
+            </p>
+            <p class={styles.dotSub}>完成度 </p>
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

BIN
src/tenant/images/album-bg.png


BIN
src/tenant/images/bg-image-search.png


BIN
src/tenant/images/bg-image.png


BIN
src/tenant/images/icon-album-cover.png


BIN
src/tenant/images/icon-search.png


BIN
src/tenant/images/icon-share.png


BIN
src/tenant/images/music-bg.png


+ 32 - 0
src/tenant/layout/auth.module.less

@@ -0,0 +1,32 @@
+.error {
+  background-color: #fff;
+  display: flex;
+  // padding-top: 20px;
+  flex-direction: column;
+  min-height: calc(100vh);
+  align-items: center;
+  justify-content: center;
+  .info {
+    display: flex;
+    align-items: center;
+    margin-bottom: 30px;
+
+    span {
+      display: inline-block;
+      margin-left: 10px;
+      color: #58727e;
+      font-size: 18px;
+    }
+  }
+
+  :global {
+    .col-result-container,
+    .van-empty {
+      padding-top: 0;
+    }
+
+    .van-button {
+      width: 50%;
+    }
+  }
+}

+ 115 - 0
src/tenant/layout/auth.tsx

@@ -0,0 +1,115 @@
+import { defineComponent } from 'vue'
+import styles from './auth.module.less'
+import { state, setLogin, setLogout, setLoginError } from '@/state'
+import { browser, setAuth } from '@/helpers/utils'
+import { postMessage } from '@/helpers/native-message'
+import { RouterView } from 'vue-router'
+import { Button, Icon } from 'vant'
+import request from '@/helpers/request'
+import ColResult from '@/components/col-result'
+
+const browserInfo = browser()
+export default defineComponent({
+  name: 'Auth',
+  data() {
+    return {
+      loading: false as boolean
+    }
+  },
+  computed: {
+    isExternal() {
+      // 该路由在外部连接打开是否需要登录
+      // 只判断是否在学生端打开
+      return (this.$route.meta.isExternal && !browserInfo.isStudent) || false
+    },
+    isNeedView() {
+      return (
+        state.user.status === 'login' ||
+        this.$route.path === '/login' ||
+        (this as any).isExternal
+      )
+    }
+  },
+  mounted() {
+    !this.isExternal && this.setAuth()
+  },
+  methods: {
+    async setAuth() {
+      const { query } = this.$route
+      const token = query.userInfo || query.Authorization
+      if (token) {
+        setAuth(token)
+      }
+      if (this.loading) {
+        return
+      }
+      if (state.user.status === 'init' || state.user.status === 'error') {
+        this.loading = true
+        try {
+          const res = await request.get('/api-student/student/queryUserInfo', {
+            initRequest: true // 初始化接口
+          })
+          setLogin(res.data)
+        } catch (e: any) {
+          const message = e.message
+          if (
+            message.indexOf('403') === -1 &&
+            message.indexOf('authentication') === -1
+          ) {
+            setLoginError()
+          } else {
+            setLogout()
+          }
+        }
+        this.loading = false
+      }
+      if (state.user.status === 'logout') {
+        if (browser().isApp) {
+          postMessage({ api: 'login' })
+        } else {
+          try {
+            const route = this.$route
+            const query = {
+              returnUrl: this.$route.path,
+              ...this.$route.query
+            } as any
+            if (route.meta.isRegister) {
+              query.isRegister = route.meta.isRegister
+            }
+            this.$router.replace({
+              path: '/login',
+              query: query
+            })
+          } catch (error) {}
+        }
+      }
+    }
+  },
+  render() {
+    return (
+      <>
+        {state.user.status === 'error' ? (
+          <div class={styles.error}>
+            {/* <div class={styles.info}>
+              <Icon name="clear" size="36" color="#ee0a24" />
+              <span>加载失败,请重新尝试</span>
+            </div>
+            <Button type="primary" round onClick={this.setAuth}>
+              重新加载
+            </Button> */}
+            <ColResult
+              type="notFond"
+              classImgSize="CERT"
+              tips="加载失败,请稍后重试"
+              buttonText="重新加载"
+              plain={true}
+              onClick={this.setAuth}
+            />
+          </div>
+        ) : this.isNeedView ? (
+          <RouterView></RouterView>
+        ) : null}
+      </>
+    )
+  }
+})

BIN
src/tenant/layout/images/bottom_bg.png


BIN
src/tenant/layout/images/top_bg.png


+ 44 - 0
src/tenant/layout/login.module.less

@@ -0,0 +1,44 @@
+.login {
+  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%;
+
+  .loginTitle {
+    padding-top: 100px;
+    font-size: 26px;
+    padding-left: 35px;
+    padding-bottom: 70px;
+    line-height: 37px;
+    font-weight: 500;
+  }
+
+  .codeText {
+    color: var(--van-primary);
+  }
+
+  .margin34 {
+    margin: 0 34px;
+  }
+
+  .formTitle {
+    font-size: 18px;
+    color: #000;
+    font-weight: 500;
+  }
+
+  :global {
+    .van-cell-group {
+      margin-bottom: 35px;
+    }
+    .van-field {
+      padding-left: 0;
+      padding-right: 0;
+    }
+    .van-button + .van-button {
+      margin-top: 20px;
+      color: #000 !important;
+    }
+  }
+}

+ 210 - 0
src/tenant/layout/login.tsx

@@ -0,0 +1,210 @@
+import { defineComponent } from 'vue'
+import { CellGroup, Field, Button, CountDown, Row, Col, Toast } from 'vant'
+import ImgCode from '@/components/col-img-code'
+import { checkPhone } from '@/helpers/validate'
+import request from '@/helpers/request'
+import { setLogin, state } from '@/state'
+import { removeAuth, setAuth } from '@/helpers/utils'
+import styles from './login.module.less'
+
+type loginType = 'PWD' | 'SMS'
+export default defineComponent({
+  name: 'login',
+  data() {
+    return {
+      loginType: 'SMS' as loginType,
+      username: '',
+      password: '',
+      smsCode: '',
+      countDownStatus: true, // 是否发送验证码
+      countDownTime: 1000 * 120, // 倒计时时间
+      countDownRef: null as any, // 倒计时实例
+      imgCodeStatus: false
+    }
+  },
+  computed: {
+    codeDisable() {
+      let status = true
+      if (this.loginType === 'PWD') {
+        this.username && this.password && (status = false)
+      } else {
+        this.username && this.smsCode && (status = false)
+      }
+      return status
+    }
+  },
+  mounted() {
+    removeAuth()
+    this.directNext()
+  },
+  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,
+          query: {
+            ...rest
+          }
+        })
+      }
+    },
+    async onLogin() {
+      try {
+        let res: any
+        if (this.loginType === 'PWD') {
+          res = await request.post('/api-auth/usernameLogin', {
+            requestType: 'form',
+            data: {
+              username: this.username,
+              password: this.password,
+              clientId: 'student',
+              clientSecret: 'student'
+            }
+          })
+        } else {
+          res = await request.post('/api-auth/smsLogin', {
+            requestType: 'form',
+            data: {
+              clientId: 'student',
+              clientSecret: 'student',
+              phone: this.username,
+              smsCode: this.smsCode
+            }
+          })
+        }
+
+        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()
+      } 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() {
+    return (
+      <div class={styles.login}>
+        <div class={styles.loginTitle}>
+          您好,
+          <br /> 欢迎使用酷乐秀
+        </div>
+        <CellGroup class={styles.margin34} border={false}>
+          <Row style={{ marginBottom: '16px' }}>
+            <Col span={24} class={styles.formTitle}>
+              手机号
+            </Col>
+            <Col span={24} class="van-hairline--bottom">
+              <Field
+                v-model={this.username}
+                name="手机号"
+                placeholder="请输入您的手机号"
+                type="tel"
+                maxlength={11}
+              />
+            </Col>
+          </Row>
+
+          {this.loginType === 'PWD' ? (
+            <Row>
+              <Col span={24} class={styles.formTitle}>
+                密码
+              </Col>
+              <Col span={24} class="van-hairline--bottom">
+                <Field
+                  v-model={this.password}
+                  type="password"
+                  name="密码"
+                  placeholder="请输入密码"
+                />
+              </Col>
+            </Row>
+          ) : (
+            <Row>
+              <Col span={24} class={styles.formTitle}>
+                验证码
+              </Col>
+              <Col span={24} class="van-hairline--bottom">
+                <Field
+                  v-model={this.smsCode}
+                  name="验证码"
+                  placeholder="请输入验证码"
+                  type="tel"
+                  maxlength={6}
+                  // @ts-ignore
+                  vSlots={{
+                    button: () =>
+                      this.countDownStatus ? (
+                        <span class={styles.codeText} onClick={this.onSendCode}>
+                          获取验证码
+                        </span>
+                      ) : (
+                        <CountDown
+                          ref={this.countDownRef}
+                          auto-start={false}
+                          time={this.countDownTime}
+                          onFinish={this.onFinished}
+                          format="ss秒"
+                        />
+                      )
+                  }}
+                />
+              </Col>
+            </Row>
+          )}
+        </CellGroup>
+        <div class={styles.margin34}>
+          <Button
+            round
+            block
+            type="primary"
+            disabled={this.codeDisable}
+            onClick={this.onLogin}
+          >
+            提交
+          </Button>
+          <Button block round color="#F5F7FB" onClick={this.onChange}>
+            {this.loginType === 'PWD' ? '验证码登录' : '密码登录'}
+          </Button>
+        </div>
+
+        {this.imgCodeStatus ? (
+          <ImgCode
+            v-model:value={this.imgCodeStatus}
+            phone={this.username}
+            onClose={() => {
+              this.imgCodeStatus = false
+            }}
+            onSendCode={this.onCodeSend}
+          />
+        ) : null}
+      </div>
+    )
+  }
+})

+ 80 - 0
src/tenant/main.ts

@@ -0,0 +1,80 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import dayjs from 'dayjs'
+import 'dayjs/locale/zh-cn'
+import router from '../router/index-tenant'
+import vueFilter from '@/helpers/vueFilter'
+import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
+
+import 'normalize.css'
+
+import '../styles/index.less'
+import '../styles/tenant.less'
+import { state } from '@/state'
+import { browser, setAuth } from '@/helpers/utils'
+
+const app = createApp(App)
+
+// import Vconsole from 'vconsole'
+// const vconsole = new Vconsole()
+postMessage(
+  {
+    api: 'getVersion'
+  },
+  (res: any) => {
+    state.version = res.content.version
+  }
+)
+
+// 判断是否是管乐团学生端,用来获取基础数据
+if (browser().isOrchestraStudent) {
+  // await promisefiyPostMessage({
+  //   api: 'setCache',
+  //   content: {
+  //     key: 'h5-colexiu-token',
+  //     value: ''
+  //   }
+  // })
+
+  // 获取管乐团token
+  promisefiyPostMessage({ api: 'getUserAccount' }).then((res: any) => {
+    const content = res.content
+    state.orchestraInfo.token = content.token.split(' ')[1]
+    state.orchestraInfo.phone = content.phone
+    state.orchestraInfo.nickname = content.nickname
+    state.orchestraInfo.avatar = content.avatar
+    state.orchestraInfo.unionId = content.unionId || 0
+  })
+
+  // 从缓存里面获取token
+  promisefiyPostMessage({
+    api: 'getCache',
+    content: { key: 'h5-colexiu-token' }
+  }).then((res: any) => {
+    const content = res.content
+    if (content.value) {
+      setAuth(content.value)
+    }
+  })
+}
+
+if (browser().isTeacher) {
+  state.platformType = 'TEACHER'
+} else if (browser().isStudent) {
+  state.platformType = 'STUDENT'
+} else {
+  state.platformType = 'STUDENT'
+}
+if (state.platformType === 'TEACHER') {
+  state.platformApi = '/api-teacher'
+} else {
+  state.platformApi = '/api-student'
+}
+state.projectType = 'tenant'
+
+dayjs.locale('zh-ch')
+app.config.globalProperties.$dayjs = dayjs
+app.config.globalProperties.$filters = vueFilter
+app.use(router)
+
+app.mount('#app')

BIN
src/tenant/member-center/images/1.png


BIN
src/tenant/member-center/images/2.png


BIN
src/tenant/member-center/images/3.png


BIN
src/tenant/member-center/images/4.png


BIN
src/tenant/member-center/images/5.png


BIN
src/tenant/member-center/images/6.png


BIN
src/tenant/member-center/images/7.png


BIN
src/tenant/member-center/images/8.png


BIN
src/tenant/member-center/images/contnt-bg.png


BIN
src/tenant/member-center/images/discount_bg.png


BIN
src/tenant/member-center/images/function-title.png


BIN
src/tenant/member-center/images/icon-arrow-line.png


BIN
src/tenant/member-center/images/icon-arrow.png


BIN
src/tenant/member-center/images/icon-logo-default.png


BIN
src/tenant/member-center/images/icon-logo.png


BIN
src/tenant/member-center/images/icon-member-active.png


BIN
src/tenant/member-center/images/icon-member-s.png


BIN
src/tenant/member-center/images/icon-selected.png


BIN
src/tenant/member-center/images/icon_discount.png


BIN
src/tenant/member-center/images/icon_gift.png


BIN
src/tenant/member-center/images/icon_video.png


BIN
src/tenant/member-center/images/info-title.png


BIN
src/tenant/member-center/images/member-bg.png


BIN
src/tenant/member-center/images/member_bg.png


BIN
src/tenant/member-center/images/member_logo.png


BIN
src/tenant/member-center/images/price-bg.png


BIN
src/tenant/member-center/images/record_bg.png


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