瀏覽代碼

Merge branch 'colexiu1.3' into jenkins

lex 1 年之前
父節點
當前提交
3e930af251
共有 85 個文件被更改,包括 6503 次插入1742 次删除
  1. 308 309
      src/business-components/subject-list/index.tsx
  2. 二進制
      src/components/col-result/images/empty_tenant.png
  3. 二進制
      src/components/col-result/images/network_tenant.png
  4. 二進制
      src/components/col-result/images/notFond_tenant.png
  5. 49 33
      src/components/col-result/index.module.less
  6. 130 120
      src/components/col-result/index.tsx
  7. 72 71
      src/constant/index.ts
  8. 1 1
      src/helpers/request.ts
  9. 7 0
      src/router/routes-teacher.ts
  10. 66 0
      src/router/routes-tenant.ts
  11. 1 0
      src/state.ts
  12. 二進制
      src/tenant/activation-code/icon-1.png
  13. 139 0
      src/tenant/activation-code/index.module.less
  14. 197 0
      src/tenant/activation-code/index.tsx
  15. 558 0
      src/tenant/exercise-record/echats.ts
  16. 206 58
      src/tenant/exercise-record/exercis-detail.module.less
  17. 223 72
      src/tenant/exercise-record/exercis-detail.tsx
  18. 二進制
      src/tenant/exercise-record/images/icon-arrow.png
  19. 二進制
      src/tenant/exercise-record/images/icon-play.png
  20. 二進制
      src/tenant/exercise-record/images/icon-subject.png
  21. 二進制
      src/tenant/exercise-record/images/train-title.png
  22. 25 9
      src/tenant/exercise-record/modals/detail-item.module.less
  23. 37 29
      src/tenant/exercise-record/modals/detail-item.tsx
  24. 二進制
      src/tenant/images/album-bg.png
  25. 二進制
      src/tenant/images/bg-image-search.png
  26. 二進制
      src/tenant/images/icon-album-cover.png
  27. 二進制
      src/tenant/images/music-bg.png
  28. 115 115
      src/tenant/layout/auth.tsx
  29. 2 2
      src/tenant/main.ts
  30. 1 1
      src/tenant/music/album-detail/index.tsx
  31. 1 1
      src/tenant/music/album/index.module.less
  32. 0 1
      src/tenant/music/album/index.tsx
  33. 1 2
      src/tenant/music/component/song-share/index.tsx
  34. 1 1
      src/tenant/music/component/song/index.module.less
  35. 21 20
      src/tenant/music/component/song/index.tsx
  36. 1 1
      src/tenant/music/music-detail/index.tsx
  37. 28 22
      src/tenant/music/personal/practice.tsx
  38. 53 33
      src/tenant/music/personal/tenant-album.tsx
  39. 1 0
      src/tenant/music/search/all-search.module.less
  40. 64 0
      src/tenant/music/search/all-search.tsx
  41. 135 81
      src/tenant/music/search/header.tsx
  42. 120 12
      src/tenant/music/search/index.module.less
  43. 20 7
      src/tenant/music/search/index.tsx
  44. 1 0
      src/tenant/music/train-list/index.module.less
  45. 196 229
      src/tenant/music/train-list/index.tsx
  46. 二進制
      src/tenant/music/train-tool/images/icon-close.png
  47. 二進制
      src/tenant/music/train-tool/images/icon-menu.png
  48. 二進制
      src/tenant/music/train-tool/images/icon-pian.png
  49. 二進制
      src/tenant/music/train-tool/images/icon-right-top.png
  50. 二進制
      src/tenant/music/train-tool/images/icon-selected.png
  51. 二進制
      src/tenant/music/train-tool/images/price-bg.png
  52. 366 0
      src/tenant/music/train-tool/index.module.less
  53. 441 0
      src/tenant/music/train-tool/index.tsx
  54. 18 0
      src/tenant/ranking-list/index.module.less
  55. 26 0
      src/tenant/ranking-list/index.tsx
  56. 256 0
      src/tenant/trade/index.module.less
  57. 355 0
      src/tenant/trade/index.tsx
  58. 65 0
      src/tenant/trade/list/index.module.less
  59. 336 0
      src/tenant/trade/list/index.tsx
  60. 299 0
      src/tenant/trade/tradeOrder.ts
  61. 34 32
      src/views/404/index.tsx
  62. 13 4
      src/views/adapay/pay-define/index.tsx
  63. 17 5
      src/views/adapay/pay-result/index.tsx
  64. 5 3
      src/views/adapay/payment/index.tsx
  65. 122 119
      src/views/article-center/help-center.tsx
  66. 29 40
      src/views/order-detail/index.tsx
  67. 82 0
      src/views/order-detail/order-tennat-album/index.module.less
  68. 32 0
      src/views/order-detail/order-tennat-album/index.tsx
  69. 48 1
      src/views/order-detail/orderStatus.ts
  70. 155 148
      src/views/order-detail/userAuth/index.tsx
  71. 二進制
      src/views/tenantStudentRejest/images/studentSuccess.png
  72. 126 0
      src/views/tenantStudentRejest/index.module.less
  73. 111 13
      src/views/tenantStudentRejest/index.tsx
  74. 二進制
      src/views/tenantTeacherRejest/images/checkBoxActive.png
  75. 二進制
      src/views/tenantTeacherRejest/images/checkBoxDefault.png
  76. 二進制
      src/views/tenantTeacherRejest/images/chioseOk.png
  77. 二進制
      src/views/tenantTeacherRejest/images/teacherSuccess.png
  78. 64 0
      src/views/tenantTeacherRejest/index.module.less
  79. 119 14
      src/views/tenantTeacherRejest/index.tsx
  80. 110 0
      src/views/tenantTeacherRejest/modals/chioseSuond.module.less
  81. 315 0
      src/views/tenantTeacherRejest/modals/chioseSuond.tsx
  82. 二進制
      src/views/trade/images/icon_success_block_tenant.png
  83. 158 129
      src/views/trade/trade-detail.module.less
  84. 16 3
      src/views/trade/trade-detail.tsx
  85. 5 1
      vite.config.ts

+ 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>
+    )
+  }
+})

二進制
src/components/col-result/images/empty_tenant.png


二進制
src/components/col-result/images/network_tenant.png


二進制
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>
+    )
+  }
+})

+ 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) {

+ 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: '训练工具'
+        }
       }
     ]
   },

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

@@ -53,6 +53,16 @@ const noLoginRouter = [
     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')
   }
 ]
 
@@ -130,6 +140,13 @@ export default [
         }
       },
       {
+        path: '/train-tool',
+        component: () => import('@/tenant/music/train-tool'),
+        meta: {
+          title: '训练工具'
+        }
+      },
+      {
         path: '/look-album-list',
         component: () => import('@/tenant/music/look-album-list'),
         meta: {
@@ -144,6 +161,55 @@ export default [
           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: '/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: '/practiceClass',
       //   name: 'practiceClass',

+ 1 - 0
src/state.ts

@@ -18,6 +18,7 @@ export const state = reactive({
     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

二進制
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('../images/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: 2px 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 '../images/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'
+  ]
+}

+ 206 - 58
src/tenant/exercise-record/exercis-detail.module.less

@@ -20,82 +20,137 @@
   }
 }
 
-.topWrap {
-  :global {
-    .van-sticky {
-      background: url('../images/bg-image.png') no-repeat top center;
-      background-size: 100% 214px;
-    }
+.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%;
+    }
+  }
 
-  .userMember {
-    background-color: transparent;
-    width: auto;
-    padding: 0;
-    // border-radius: 10px;
-    padding: 18px 12px 30px 26px;
+  .userImg {
+    width: 46px;
+    height: 46px;
+    border-radius: 50%;
+    vertical-align: middle;
+    overflow: hidden;
+  }
 
+  .userInfo {
+    display: flex;
+    align-items: center;
+    color: #fff;
+    padding-bottom: 5px;
 
-    .level {
-      width: 44px;
-      height: 17px;
+    .name {
+      padding-right: 5px;
+      max-width: 100px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      font-size: 18px;
+      font-weight: 600;
+      color: #742626;
     }
+  }
 
-    .userImgSection {
-      position: relative;
-      padding: 3px;
-      background: #fff;
-      margin-right: 12px;
-      border-radius: 50%;
+  .timeRemaining {
+    margin-top: 0;
+    font-size: 14px;
+    color: #c0c0c0;
+  }
 
-      &::before {
-        content: ' ';
-        position: absolute;
-        left: 1px;
-        top: 1px;
-        bottom: 1px;
-        right: 1px;
-        background-color: #fff;
-        border-radius: 50%;
-      }
-    }
+  .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;
 
-    .userImg {
-      width: 46px;
-      height: 46px;
-      border-radius: 50%;
-      vertical-align: middle;
-      overflow: hidden;
+    .iconSubject {
+      width: 13px;
+      height: 13px;
+      margin-right: 4px;
     }
+  }
+}
 
-    .userInfo {
-      padding-top: 4px;
-      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;
+.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;
       }
     }
 
-    .timeRemaining {
-      margin-top: 0;
-      font-size: 14px;
-      color: #c0c0c0;
+    .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%;
@@ -162,4 +217,97 @@
     }
   }
 
+}
+
+.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;
 }

+ 223 - 72
src/tenant/exercise-record/exercis-detail.tsx

@@ -4,7 +4,15 @@ 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 } from 'vue'
+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'
@@ -16,6 +24,66 @@ 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',
@@ -31,9 +99,17 @@ export default defineComponent({
       userTrainOverView: {
         trainDays: 0,
         trainNum: 0,
-        trainTime: 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
@@ -49,8 +125,7 @@ export default defineComponent({
       }
     })
     const forms = reactive({
-      practiceMonth: dayjs().day(1).format('YYYYMMDD'),
-      startTime: '2023-09',
+      startTime: dayjs().day(1).format('YYYY-MM-DD'),
       endTime: dayjs().day(7).format('YYYY-MM-DD'),
       page: 1,
       rows: 20
@@ -72,7 +147,7 @@ export default defineComponent({
       }
       try {
         const { data } = await request.get(
-          `/api-student/sysMusicRecord/studentTrainData`,
+          `/api-student/sysMusicRecord/studentTrainDataByWeek`,
           {
             params: { ...forms }
           }
@@ -85,7 +160,7 @@ export default defineComponent({
         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
@@ -96,21 +171,39 @@ export default defineComponent({
         finished.value = true
       }
       state.isClick = false
-    }
 
-    onMounted(async () => {
-      await getList()
-    })
+      // if (showContact.value) {
+      nextTick(() => {
+        if (document.getElementById('exerciseWeek')) {
+          state.myChart = markRaw(
+            echarts.init(
+              document.getElementById('exerciseWeek') as HTMLDivElement
+            )
+          )
 
-    const checkTimer = (val: any) => {
-      // forms.practiceMonth = val.selectedValues[0] + val.selectedValues[1]
-      // state.practiceMonthName =
-      //   val.selectedValues[0] + '年' + val.selectedValues[1] + '月'
-      state.showPopoverTime = false
-      refreshing.value = true
-      getList()
-    }
+          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
       // 重新加载数据
@@ -119,6 +212,10 @@ export default defineComponent({
       getList()
     }
 
+    onMounted(async () => {
+      await getList()
+    })
+
     return () => (
       <div class={[styles.exercisContainer]}>
         <div class={styles.topWrap}>
@@ -131,6 +228,7 @@ export default defineComponent({
             <Cell
               class={styles.userMember}
               labelClass={styles.timeRemaining}
+              border={false}
               v-slots={{
                 icon: () => (
                   <div class={styles.userImgSection}>
@@ -150,64 +248,110 @@ export default defineComponent({
                     />
                   </div>
                 ),
-                label: () => <div class={styles.subjectName}></div>
+                label: () => (
+                  <div class={styles.subjectName}>
+                    <span class={styles.subjectTag}>
+                      <img src={iconSubject} class={styles.iconSubject} />
+                      口风琴
+                    </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.topInfoRight}>
-            <div class={styles.infoDay}>
-              <p class={styles.infoDayMain}>
-                {infoDetail.value.practiceDays
-                  ? infoDetail.value.practiceDays
-                  : 0}
-              </p>
-              <p class={styles.infoDaysub}>
-                <img src={iconDays} />
-                练习天数(天)
-              </p>
-            </div>
-            <div class={styles.infoTime}>
-              <p class={styles.infoDayMain}>
-                {infoDetail.value.practiceTimes
-                  ? Math.floor(infoDetail.value.practiceTimes / 60)
-                  : 0}
 
-              </p>
-              <p class={styles.infoDaysub}>
-                <img src={iconClock} />
-                练习时长(分钟)
-              </p>
-            </div>
-          </div> */}
-        {/* <CellGroup inset>
-            <Cell
-              class={styles.select}
-              center
-              isLink
+        <div class={styles.trainWeek}>
+          <div class={styles.trainTitle}>
+            <img src={iconTrainTitle} />
+
+            <span
+              class={styles.timeRange}
               onClick={() => (state.showPopoverTime = true)}
             >
-              {{
-                // icon: () => <img class={styles.icon} src={iconData} />,
-                title: () => (
-                  <div class="van-ellipsis">{state.practiceMonthName}</div>
-                )
+              {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)
               }}
-            </Cell>
-          </CellGroup>
-         */}
+              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}
-            style={{ minHeight: `calc(100vh - var(--header-height))` }}
+            class={styles.fullRefresh}
           >
             <List
               loading-text=" "
               finished={finished.value}
               finished-text=" "
               onLoad={getList}
+              style={{ overflow: 'hidden' }}
             >
               {list.value.map((item: any) => (
                 <DetailItem item={item} />
@@ -216,13 +360,18 @@ export default defineComponent({
           </OFullRefresh>
         ) : (
           <div
-            style={{
-              height: `calc(100vh - var(--header-height))`,
-              display: 'flex',
-              alignItems: 'center'
-            }}
+            // style={{
+            //   height: `calc(100vh - var(--header-height))`,
+            //   display: 'flex',
+            //   alignItems: 'center'
+            // }}
+            class={styles.emptyContainer}
           >
-            <ColResult tips="暂无学练统计" btnStatus={false} />
+            <ColResult
+              tips="暂无学练统计"
+              classImgSize="SMALL"
+              btnStatus={false}
+            />
           </div>
         )}
 
@@ -235,44 +384,46 @@ export default defineComponent({
           maxRange={7}
           minDate={new Date('2023-02-27')}
           defaultDate={[
-            dayjs(forms.practiceMonth).toDate(),
+            dayjs(forms.startTime).toDate(),
             dayjs(forms.endTime).toDate()
           ]}
           style={{
             height: '70%'
           }}
           onSelect={(item: any) => {
-            forms.practiceMonth = ''
+            forms.startTime = ''
             forms.endTime = ''
             if (
               !dayjs(item[0]).isBetween(
-                dayjs(forms.practiceMonth),
+                dayjs(forms.startTime),
                 dayjs(forms.endTime)
               )
             ) {
               const week = dayjs(item[0]).day()
               if (week === 0) {
                 // 星期天
-                forms.practiceMonth = dayjs(item[0])
+                forms.startTime = dayjs(item[0])
                   .subtract(6, 'day')
-                  .format('YYYYMMDD')
+                  .format('YYYY-MM-DD')
                 forms.endTime = dayjs(item[0]).format('YYYY-MM-DD')
               } else if (week === 1) {
                 // 星期一
-                forms.practiceMonth = dayjs(item[0]).format('YYYYMMDD')
+                forms.startTime = dayjs(item[0]).format('YYYY-MM-DD')
                 forms.endTime = dayjs(item[0])
                   .add(6, 'day')
                   .format('YYYY-MM-DD')
               } else {
-                forms.practiceMonth = dayjs(item[0])
+                forms.startTime = dayjs(item[0])
                   .subtract(week - 1, 'day')
-                  .format('YYYYMMDD')
+                  .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>

二進制
src/tenant/exercise-record/images/icon-arrow.png


二進制
src/tenant/exercise-record/images/icon-play.png


二進制
src/tenant/exercise-record/images/icon-subject.png


二進制
src/tenant/exercise-record/images/train-title.png


+ 25 - 9
src/tenant/exercise-record/modals/detail-item.module.less

@@ -14,18 +14,34 @@
 
     .itemTopLeft {
       .itemTopMain {
-        height: 22px;
-        font-size: 16px;
-        font-weight: 500;
-        color: #333333;
-        line-height: 22px;
+        display: flex;
+        align-items: center;
         margin-bottom: 6px;
-        max-width: 160px;
-        overflow: hidden;
-        text-overflow: ellipsis;
-        white-space: nowrap;
+
+        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;

+ 37 - 29
src/tenant/exercise-record/modals/detail-item.tsx

@@ -1,12 +1,13 @@
-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 { 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: {
@@ -34,7 +35,7 @@ const scoreInfos: any = {
     tips: '你的演奏非常不错,音准的把握和节奏稍有瑕疵,完整性把握的很好~',
     mome: '你很棒'
   }
-};
+}
 export default defineComponent({
   props: ['item'],
   name: 'detail-item',
@@ -42,41 +43,48 @@ export default defineComponent({
   setup(props) {
     const getLeveByScoreId = (score?: number) => {
       if (!score && typeof score !== 'number') {
-        return {};
+        return {}
       }
-      let leve: any = 1;
+      let leve: any = 1
       if (score > 20 && score <= 40) {
-        leve = 2;
+        leve = 2
       } else if (score > 40 && score <= 60) {
-        leve = 3;
+        leve = 3
       } else if (score > 60 && score <= 80) {
-        leve = 4;
+        leve = 4
       } else if (score > 80) {
-        leve = 5;
+        leve = 5
       }
-      return leve;
-    };
+      return leve
+    }
     const gotoDetail = () => {
-      const url =
-        window.location.origin +
-        `/instrument/#/evaluat-report?id=${props.item.id}`;
-      // const url = `https://test.lexiaoya.cn/instrument/#/evaluat-report?id=${props.item.id}`;
+      const behaviorId = +new Date()
       postMessage({
-        api: 'openWebView',
+        api: 'openAccompanyWebView',
         content: {
-          url: url,
+          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}>{props.item.musicSheetName}</p>
+            <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}>
@@ -120,6 +128,6 @@ export default defineComponent({
           </div>
         </div>
       </div>
-    );
+    )
   }
-});
+})

二進制
src/tenant/images/album-bg.png


二進制
src/tenant/images/bg-image-search.png


二進制
src/tenant/images/icon-album-cover.png


二進制
src/tenant/images/music-bg.png


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

@@ -1,115 +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 {
-          let 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}
-      </>
-    )
-  }
-})
+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}
+      </>
+    )
+  }
+})

+ 2 - 2
src/tenant/main.ts

@@ -15,8 +15,8 @@ import { browser, setAuth } from '@/helpers/utils'
 
 const app = createApp(App)
 
-import Vconsole from 'vconsole'
-const vconsole = new Vconsole()
+// import Vconsole from 'vconsole'
+// const vconsole = new Vconsole()
 postMessage(
   {
     api: 'getVersion'

+ 1 - 1
src/tenant/music/album-detail/index.tsx

@@ -233,7 +233,7 @@ export default defineComponent({
         }
       }
       shareUrl.value = `${location.origin}/teacher#/shareAblum?id=${id}&recomUserId=${userId}&activityId=${activityId}&userType=${state.platformType}`
-      console.log(shareUrl.value, 'shareUrl')
+      // console.log(shareUrl.value, 'shareUrl')
       shareStatus.value = true
     }
 

+ 1 - 1
src/tenant/music/album/index.module.less

@@ -6,7 +6,7 @@
 }
 
 .headerImg {
-  position: fixed;
+  position: fixed !important;
   left: 0;
   top: 0;
   width: 100%;

+ 0 - 1
src/tenant/music/album/index.tsx

@@ -220,7 +220,6 @@ export default defineComponent({
                   <ColHeader
                     class={styles.memberHeader}
                     background="transparent"
-                    backIconColor="white"
                     border={false}
                     isFixed={false}
                     color="#131415"

+ 1 - 2
src/tenant/music/component/song-share/index.tsx

@@ -28,7 +28,6 @@ export default defineComponent({
   },
   emits: ['detail'],
   setup(props, { emit }) {
-
     const list = computed(() => {
       return props.list.map(n => {
         if (typeof n.paymentType === 'string')
@@ -70,7 +69,7 @@ export default defineComponent({
                   </span>
 
                   <div class={styles.tags}>
-                    {n?.subjectNames.split(',').map((name: any) => (
+                    {n?.subjectNames?.split(',').map((name: any) => (
                       <span>{name}</span>
                     ))}
                   </div>

+ 1 - 1
src/tenant/music/component/song/index.module.less

@@ -8,7 +8,7 @@
   .item {
     display: flex;
     align-items: center;
-    border-bottom: 1px solid #f2f2f2;
+    // border-bottom: 1px solid #f2f2f2;
     padding: 16px 0;
 
     &:last-child {

+ 21 - 20
src/tenant/music/component/song/index.tsx

@@ -42,28 +42,29 @@ export default defineComponent({
   },
   emits: ['detail'],
   setup(props, { emit }) {
-    const isMore = ref<boolean>(false)
-    const moreData = ref<any>({})
-    const router = useRouter()
-    const colors: any = {
-      FREE: {
-        color: '#01B84F',
-        text: '免费'
-      },
-      VIP: {
-        color: '#CD863E',
-        text: '会员'
-      },
-      CHARGE: {
-        color: '#3591CE',
-        text: '点播'
-      }
-    }
+    // const isMore = ref<boolean>(false)
+    // const moreData = ref<any>({})
+    // const router = useRouter()
+    // const colors: any = {
+    //   FREE: {
+    //     color: '#01B84F',
+    //     text: '免费'
+    //   },
+    //   VIP: {
+    //     color: '#CD863E',
+    //     text: '会员'
+    //   },
+    //   CHARGE: {
+    //     color: '#3591CE',
+    //     text: '点播'
+    //   }
+    // }
 
     const list = computed(() => {
       return props.list.map(n => {
-        if (typeof n.paymentType === 'string')
-          n.paymentType = n.paymentType.split(',')
+        // if (typeof n.paymentType === 'string')
+        //   n.paymentType = n.paymentType.split(',')
+        n.subjectNames = n.musicSubjectName
         return { ...n }
       })
     })
@@ -108,7 +109,7 @@ export default defineComponent({
                   )}
 
                   <div class={styles.tags}>
-                    {n?.subjectNames.split(',').map((name: any) => (
+                    {n.subjectNames?.split(',').map((name: any) => (
                       <span>{name}</span>
                     ))}
                   </div>

+ 1 - 1
src/tenant/music/music-detail/index.tsx

@@ -961,7 +961,7 @@ export default defineComponent({
                   <h4 class="van-multi-ellipsis--l2">
                     {musicDetail.value?.musicSheetName}
                   </h4>
-                  <p>作曲人:{musicDetail.value?.composer}</p>
+                  <p>作曲人:{musicDetail.value?.composer || ''}</p>
                 </div>
               </div>
             </ColShare>

+ 28 - 22
src/tenant/music/personal/practice.tsx

@@ -43,29 +43,35 @@ export default defineComponent({
       return (
         <>
           {prevNum.value > 0 && (
-            <Cell titleClass={styles.pTitle} title="最近练习" border={false} />
+            <>
+              <Cell
+                titleClass={styles.pTitle}
+                title="最近练习"
+                border={false}
+              />
+              <div class={styles.practice}>
+                <Song
+                  showTitleImg
+                  list={list}
+                  onDetail={(item: any) => {
+                    const url =
+                      location.origin +
+                      location.pathname +
+                      '#/music-detail?id=' +
+                      item.id
+                    openDefaultWebView(url, () => {
+                      router.push({
+                        path: '/music-detail',
+                        query: {
+                          id: item.id
+                        }
+                      })
+                    })
+                  }}
+                />
+              </div>
+            </>
           )}
-          <div class={styles.practice}>
-            <Song
-              showTitleImg
-              list={list}
-              onDetail={(item: any) => {
-                const url =
-                  location.origin +
-                  location.pathname +
-                  '#/music-detail?id=' +
-                  item.id
-                openDefaultWebView(url, () => {
-                  router.push({
-                    path: '/music-detail',
-                    query: {
-                      id: item.id
-                    }
-                  })
-                })
-              }}
-            />
-          </div>
         </>
       )
     }

+ 53 - 33
src/tenant/music/personal/tenant-album.tsx

@@ -35,6 +35,7 @@ export default defineComponent({
         })
         rows.value = [...rows.value, ...res.data.rows]
         data.value = res.data
+        console.log(data.value)
         params.page = res.data.pageNo + 1
         finished.value = res.data.pageNo >= res.data.totalPage
       } catch (error) {
@@ -43,47 +44,66 @@ export default defineComponent({
       loading.value = false
     }
 
+    const onDetail = (item: any) => {
+      router.push({
+        path: '/train-tool',
+        query: {
+          albumId: item.id
+        }
+      })
+    }
+
     return () => {
       return (
         <List
           loading={loading.value}
           finished={finished.value}
-          finished-text={rows.value.length ? '没有更多了' : ''}
+          finished-text={rows.value.length ? ' ' : ''}
           onLoad={FetchList}
           error={isError.value}
         >
-          {rows.value.length ? (
-            <CellGroup class={styles.tennatCellGroup} border={false}>
-              <Cell isLink>
-                {{
-                  icon: () => <img class={styles.tenantLogo} />,
-                  title: () => <div class={styles.tenantName}>测试机构</div>
-                }}
-              </Cell>
-              <Cell>
-                {{
-                  icon: () => <Image class={styles.tenantCoverImg} />,
-                  title: () => (
-                    <div class={styles.tenantContent}>
-                      <h2>巴赫旧约 | 四号勃兰登堡长笛协奏曲</h2>
-                      <p class="van-multi-ellipsis--l2">
-                        巴赫经典曲目巴赫经典曲目巴赫经典曲目巴赫经典曲目巴赫经典曲目
-                        巴赫经典曲目 巴赫经典曲目 巴赫经典曲目 巴赫经典曲目
-                      </p>
-                    </div>
-                  )
-                }}
-              </Cell>
-            </CellGroup>
-          ) : (
-            !loading.value && (
-              <ColResult
-                tips="暂无训练教程"
-                classImgSize="SMALL"
-                btnStatus={false}
-              />
-            )
-          )}
+          {rows.value.length
+            ? rows.value.map((item: any) => (
+                <CellGroup
+                  class={styles.tennatCellGroup}
+                  border={false}
+                  onClick={() => onDetail(item)}
+                >
+                  <Cell isLink clickable={false}>
+                    {{
+                      icon: () => (
+                        <img src={item.tenantImg} class={styles.tenantLogo} />
+                      ),
+                      title: () => (
+                        <div class={styles.tenantName}>{item.tenantName}</div>
+                      )
+                    }}
+                  </Cell>
+                  <Cell>
+                    {{
+                      icon: () => (
+                        <Image
+                          src={item.coverImg}
+                          class={styles.tenantCoverImg}
+                        />
+                      ),
+                      title: () => (
+                        <div class={styles.tenantContent}>
+                          <h2>{item.name}</h2>
+                          <p class="van-multi-ellipsis--l2">{item.describe}</p>
+                        </div>
+                      )
+                    }}
+                  </Cell>
+                </CellGroup>
+              ))
+            : !loading.value && (
+                <ColResult
+                  tips="暂无训练教程"
+                  classImgSize="SMALL"
+                  btnStatus={false}
+                />
+              )}
         </List>
       )
     }

+ 1 - 0
src/tenant/music/search/all-search.module.less

@@ -0,0 +1 @@
+.albumSection {}

+ 64 - 0
src/tenant/music/search/all-search.tsx

@@ -0,0 +1,64 @@
+import { defineComponent, onMounted, onUnmounted, reactive, ref } from 'vue'
+import styles from './all-search.module.less'
+import { useRoute, useRouter } from 'vue-router'
+import MusicGrid from '../component/music-grid'
+import request from '@/helpers/request'
+
+export default defineComponent({
+  name: 'MusicSearch',
+  props: {
+    defauleParams: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  emits: ['confirm'],
+  setup(props) {
+    const route = useRoute()
+    const router = useRouter()
+    const state = reactive({
+      albumList: [] as any
+    })
+
+    const getAlbumList = async () => {
+      try {
+        const { data } = await request.post('/api-student/music/album/list', {
+          data: {
+            ...props.defauleParams,
+            page: 1,
+            rows: 3
+          }
+        })
+        console.log(data)
+        state.albumList = data.rows || []
+      } catch {
+        //
+      }
+    }
+
+    // music-songbook/search
+    onMounted(() => {
+      getAlbumList()
+    })
+
+    return () => (
+      <div class={styles.allSearch}>
+        <div class={styles.albumSection}>
+          <div class={styles.musicGrid}>
+            <MusicGrid
+              list={state.albumList}
+              onGoto={(n: any) => {
+                router.push({
+                  name: 'music-album-detail',
+                  params: {
+                    id: n.id
+                  }
+                })
+              }}
+            />
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

+ 135 - 81
src/tenant/music/search/header.tsx

@@ -1,21 +1,22 @@
-import { Sticky, Cell, Tag, Icon, Popup, Tabs, Tab, Dialog } from 'vant'
+import { Sticky, Cell, Tag, Icon, Popup, Tabs, Tab, Dialog, Button } from 'vant'
 import {
   RouterView,
   useRouter,
   useRoute,
   onBeforeRouteUpdate
 } from 'vue-router'
-import { defineComponent, onMounted, reactive, ref, watch } from 'vue'
+import { defineComponent, nextTick, onMounted, reactive, ref, watch } from 'vue'
 import mitt from 'mitt'
 import Search from '@/components/col-search'
 import { useLocalStorage } from '@vueuse/core'
 import styles from './index.module.less'
 import classNames from 'classnames'
-import SelectTag from './select-tag'
 import { getRandomKey } from '../music'
 import SelectSubject from './select-subject'
 import { SubjectEnum, useSubjectId } from '@/helpers/hooks'
 import { state } from '@/state'
+import TheSticky from '@/components/the-sticky'
+import bgImg from '../../images/bg-image-search.png'
 
 export const mitter = mitt()
 
@@ -52,9 +53,8 @@ export default defineComponent({
     const route = useRoute()
     const keyword = ref('')
     const tagids = ref('')
-    const tagVisibility = ref(false)
     const words = useLocalStorage<string[]>('music-search', [])
-    const activeTab = ref('songe')
+    const activeTab = ref('all')
 
     onBeforeRouteUpdate(() => {
       const getSubject: any = useSubjectId(SubjectEnum.SEARCH)
@@ -63,7 +63,7 @@ export default defineComponent({
       if (route.path === '/music-songbook/search') {
         keyword.value = ''
         tagids.value = ''
-        activeTab.value = 'songe'
+        activeTab.value = 'all'
         try {
           selectTagRef.value?.resetTags?.()
         } catch (error) {
@@ -85,18 +85,12 @@ export default defineComponent({
       }
       if (val) {
         words.value.unshift(val)
-        words.value.length = Math.min(words.value.length, 5)
+        console.log(words.value.length, 'words.value.length')
+        words.value.length = Math.min(words.value.length, 10)
       }
       mitter.emit('search', val)
     }
 
-    const onComfirm = (tags, name = '') => {
-      const data = Object.values(tags).flat().filter(Boolean).join(',')
-      tagids.value = data
-      mitter.emit('confirm', tags)
-      tagVisibility.value = false
-    }
-
     const onComfirmSubject = (item: any) => {
       // console.log('onSort', item)
       subject.name = item.name
@@ -121,42 +115,81 @@ export default defineComponent({
       name: getSubject.name || '全部声部',
       id: getSubject.id || ''
     })
+
+    const tagRef = ref<any>([])
+    const collapse = reactive({
+      line: 0,
+      arrowStatus: false
+    })
+
+    // 历史搜索默认收起
+    const defaultClose = () => {
+      nextTick(() => {
+        if (!words.value || !words.value.length) {
+          return
+        }
+        let offsetLeft = -1
+        collapse.line = 0
+        const tags = tagRef.value
+        tags.forEach((item: any, index: number) => {
+          try {
+            item.$el.style.display = 'block'
+            if (index === 0) {
+              collapse.line = 1
+              offsetLeft = item.$el.offsetLeft
+            } else if (item.$el.offsetLeft === offsetLeft && index != 0) {
+              // 如果某个标签的offsetLeft和第一个标签的offsetLeft相等  说明增加了一行
+              collapse.line += 1
+            }
+
+            if (!collapse.arrowStatus) {
+              if (collapse.line > 2) {
+                //从第3行开始 隐藏标签
+                item.$el.style.display = 'none'
+              } else {
+                item.$el.style.display = 'block'
+              }
+            } else {
+              item.$el.style.display = 'block'
+            }
+          } catch (e: any) {
+            console.log(e, 'Error')
+          }
+        })
+      })
+    }
+    // 首先调用默认收起的方法
+    defaultClose()
+
     return () => {
       return (
         <div class={styles.search}>
-          <Sticky class={styles.sticky}>
-            <Search
-              modelValue={keyword.value}
-              // showAction
-              ref={searchInputRef}
-              onSearch={onSearch}
-              // onFilter={() => (tagVisibility.value = true)}
-              // filterDot={!!tagids.value}
-              onClick={() => {
-                if (route.path === '/music-songbook') {
-                  router.push({
-                    path: '/music-songbook/search'
-                  })
-                }
-              }}
-              v-slots={{
-                left: () => (
-                  <div
-                    class={styles.label}
-                    onClick={() => (subject.show = true)}
-                  >
-                    {subject.name}
-                    <Icon
-                      classPrefix="iconfont"
-                      name="down"
-                      size={12}
-                      color="#333"
-                    />
-                  </div>
-                )
-              }}
-            />
-            {route.path === '/music-songbook/search' && (
+          <div class={styles.sticky}>
+            <TheSticky position="top">
+              <Search
+                modelValue={keyword.value}
+                background="transparent"
+                ref={searchInputRef}
+                onSearch={onSearch}
+                type="tenant"
+                v-slots={{
+                  left: () => (
+                    <div
+                      class={styles.label}
+                      onClick={() => (subject.show = true)}
+                    >
+                      {subject.name}
+                      <Icon
+                        classPrefix="iconfont"
+                        name="down"
+                        size={12}
+                        color="#333"
+                      />
+                    </div>
+                  )
+                }}
+              />
+              {/* {route.path === '/music-songbook/search' && (
               <Tabs
                 color="var(--van-primary)"
                 background="transparent"
@@ -168,45 +201,66 @@ export default defineComponent({
                 <Tab title="单曲" name="songe"></Tab>
                 <Tab title="专辑" name="album"></Tab>
               </Tabs>
-            )}
-          </Sticky>
+            )} */}
+            </TheSticky>
+            <img class={styles.bgImg} src={bgImg} />
+          </div>
           {words.value.length > 0 && route.path === '/music-songbook/search' && (
-            <div class={classNames(styles.keywords, 'van-hairline--bottom')}>
-              <div class={styles.content}>
-                {words.value.map(item => (
-                  <Tag
-                    round
-                    class={styles.searchKeyword}
-                    key={item}
-                    onClick={() => onSearch(item)}
-                  >
-                    {item}
-                  </Tag>
-                ))}
+            <div class={styles.keywordSection}>
+              <div class={styles.keywordTitle}>
+                <span class={styles.t}>搜索历史</span>
+                <Icon
+                  class={styles.remove}
+                  name="delete-o"
+                  onClick={() => (words.value = [])}
+                />
+              </div>
+              <div class={classNames(styles.keywords)}>
+                <div class={styles.content}>
+                  {words.value.map((item: any, index: number) => (
+                    <Tag
+                      ref={(el: any) => (tagRef.value[index] = el)}
+                      round
+                      class={[styles.searchKeyword, 'van-ellipsis']}
+                      key={item}
+                      onClick={() => onSearch(item)}
+                    >
+                      {item}
+                    </Tag>
+                  ))}
+                  {collapse.line > 2 && (
+                    <span
+                      class={[styles.arrowMore]}
+                      onClick={() => {
+                        collapse.arrowStatus = !collapse.arrowStatus
+                        defaultClose()
+                      }}
+                    >
+                      <Icon
+                        name={collapse.arrowStatus ? 'arrow-up' : 'arrow-down'}
+                      />
+                    </span>
+                  )}
+                </div>
               </div>
-              <Icon
-                class={styles.remove}
-                name="delete-o"
-                onClick={() => (words.value = [])}
-              />
             </div>
           )}
+          {route.path === '/music-songbook/search' && (
+            <Tabs
+              color="var(--van-primary)"
+              background="transparent"
+              lineWidth={20}
+              shrink
+              class={styles.tagTabs}
+              v-model:active={activeTab.value}
+              onChange={val => (activeTab.value = val)}
+            >
+              <Tab title="综合" name="all"></Tab>
+              <Tab title="单曲" name="songe"></Tab>
+              <Tab title="专辑" name="album"></Tab>
+            </Tabs>
+          )}
           <RouterView />
-          <Popup
-            show={tagVisibility.value}
-            round
-            closeable
-            position="bottom"
-            style={{ height: '60%' }}
-            teleport="body"
-            onUpdate:show={val => (tagVisibility.value = val)}
-          >
-            <SelectTag
-              ref={selectTagRef}
-              onConfirm={onComfirm}
-              onCancel={() => {}}
-            />
-          </Popup>
 
           {/* 声部弹框 */}
           <Popup

+ 120 - 12
src/tenant/music/search/index.module.less

@@ -11,56 +11,164 @@
     :global(.van-sticky--fixed) {
       box-shadow: 10px 10px 10px var(--box-shadow-color);
     }
-    > div {
+
+    >div {
       background-color: var(--base-bg);
     }
   }
+
   .title {
     padding-top: 16px;
+
     :global(.van-cell__value) {
       font-size: 12px;
     }
   }
+
+
+  .keywordTitle {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 8px 9px;
+
+    .t {
+      font-size: 14px;
+      font-weight: 500;
+      color: #000000;
+      line-height: 20px;
+    }
+
+    .remove {
+      font-size: 16px;
+    }
+  }
+
   .keywords {
     margin-top: 10px;
     padding: 0 14px;
-    padding-bottom: 10px;
     display: flex;
     align-items: center;
-    .content::-webkit-scrollbar {
-      display: none; /* Chrome Safari */
-    }
+
+    // .content::-webkit-scrollbar {
+    //   display: none;
+    // }
+
     .content {
       flex: 1;
-      overflow: hidden;
-      overflow-x: auto;
+      // overflow: hidden;
+      // overflow-x: auto;
       display: flex;
+      flex-wrap: wrap;
+
       .searchKeyword {
         --van-tag-default-color: white;
-        --van-tag-text-color: #333;
+        --van-tag-text-color: #313443;
         font-size: 14px;
-        padding: 4px 10px;
+        padding: 5px 14px;
         margin-right: 5px;
+        margin-bottom: 10px;
+        max-width: 100px;
+        display: block;
         word-break: keep-all;
       }
-    }
 
-    .remove {
-      font-size: 16px;
+      .arrowMore {
+        width: 27px;
+        height: 27px;
+        border-radius: 50%;
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        background-color: #fff;
+        font-size: 12px;
+        color: #93959F;
+      }
     }
   }
+
   .label {
     margin-right: 8px;
     font-size: 14px;
+
     :global {
+
       .van-list__loading,
       .van-list__finished-text,
       .van-list__error-text {
         width: 100%;
       }
+
       .iconfont-down {
         margin-left: 4px;
       }
     }
   }
 }
+
+
+.sticky {
+  :global {
+    .van-sticky {
+      background: url('../../images/bg-image-search.png') no-repeat top center;
+      background-size: 100% 214px;
+      box-shadow: none !important;
+    }
+
+    .van-search__content {
+      background: rgba(255, 255, 255, 0.5) !important;
+
+      input::placeholder {
+        color: rgba(0, 0, 0, 0.4) !important;
+      }
+
+      input {
+        color: rgba(0, 0, 0, 0.4) !important;
+      }
+
+      .van-field__clear {
+        color: rgba(0, 0, 0, 0.4) !important;
+      }
+    }
+
+  }
+}
+
+.bgImg {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 214px;
+  // object-fit: cover;
+  z-index: -1;
+}
+
+.tagTabs {
+  --van-cell-background-color: transparent;
+  --van-cell-font-size: 16px;
+  --van-cell-text-color: #333;
+  --van-cell-value-color: #999;
+  --van-cell-icon-size: 10px;
+
+  :global {
+    .van-tab {
+      font-size: 16px !important;
+
+      color: #999999;
+    }
+
+    .van-tab--active {
+      font-size: 16px !important;
+      color: #131415;
+    }
+
+    .van-tabs__line {
+      width: 24px;
+      height: 4px;
+      background: linear-gradient(90deg, #FF3C81 0%, rgba(255, 118, 166, 0.5) 100%) !important;
+      border-radius: 36px 36px 0px 0px;
+    }
+
+  }
+}

+ 20 - 7
src/tenant/music/search/index.tsx

@@ -7,6 +7,7 @@ import { useRoute, useRouter } from 'vue-router'
 import { getRandomKey } from '../music'
 import { mitter } from './header'
 import { SubjectEnum, useSubjectId } from '@/helpers/hooks'
+import AllSearch from './all-search'
 
 export default defineComponent({
   name: 'MusicSearch',
@@ -20,7 +21,7 @@ export default defineComponent({
     const subject = ref()
     const tagVisibility = ref(false)
     const words = useLocalStorage<string[]>('music-search', [])
-    const activeTab = ref('songe')
+    const activeTab = ref('all')
 
     const getSubject: any = useSubjectId(SubjectEnum.SEARCH)
     subject.value = getSubject.id
@@ -57,6 +58,7 @@ export default defineComponent({
     const musicList = ref(null)
 
     const changeTab = (val: any) => {
+      console.log(val, 'val')
       activeTab.value = val
     }
 
@@ -65,6 +67,8 @@ export default defineComponent({
       mitter.on('search', onSearch)
       mitter.on('confirm', onComfirm)
       mitter.on('confirmSubject', onConfirmSubject)
+
+      console.log(activeTab.value, 'activeTab.value')
     })
 
     onUnmounted(() => {
@@ -77,18 +81,27 @@ export default defineComponent({
     return () => {
       return (
         <div class={styles.search}>
-          {activeTab.value === 'album' ? (
+          {activeTab.value === 'all' && (
+            <AllSearch
+              defauleParams={{
+                albumTagIds: tagids.value,
+                subjectIds: subject.value
+              }}
+            />
+          )}
+          {activeTab.value === 'album' && (
             <AlbumList
               hideSearch
               ref={albumList}
               defauleParams={{
-                search: keyword.value,
-                tagids: tagids.value,
+                // search: keyword.value,
+                // tagids: tagids.value,
                 albumTagIds: tagids.value,
                 subjectIds: subject.value
               }}
             />
-          ) : (
+          )}
+          {activeTab.value === 'songe' && (
             <MusicList
               hideSearch
               ref={musicList}
@@ -102,8 +115,8 @@ export default defineComponent({
                 })
               }}
               defauleParams={{
-                search: keyword.value,
-                tagids: tagids.value,
+                // search: keyword.value,
+                // tagids: tagids.value,
                 musicTagIds: tagids.value,
                 subjectIds: subject.value
               }}

+ 1 - 0
src/tenant/music/train-list/index.module.less

@@ -89,6 +89,7 @@
   border-radius: 18px;
   background-color: #fff;
   margin: 6px;
+  min-height: 40vh;
 }
 
 .bgImg {

+ 196 - 229
src/tenant/music/train-list/index.tsx

@@ -1,35 +1,16 @@
 import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue'
-import {
-  Sticky,
-  List,
-  Popup,
-  Icon,
-  Switch,
-  Tabs,
-  Tab,
-  DropdownMenu,
-  DropdownItem,
-  Tag
-} from 'vant'
+import { List, DropdownMenu, DropdownItem, Tag, Sticky, Button } from 'vant'
 import Search from '@/components/col-search'
 import request from '@/helpers/request'
-// import Item from './item'
-import SelectTag from '../search/select-tag'
 import { useRoute, useRouter } from 'vue-router'
 import ColResult from '@/components/col-result'
 import styles from './index.module.less'
 import { getRandomKey } from '../music'
 import { openDefaultWebView, state as baseState } from '@/state'
-import SelectSubject from '../search/select-subject'
 import { SubjectEnum, useSubjectId } from '@/helpers/hooks'
 import Song from '../component/song'
 import ColHeader from '@/components/col-header'
-import { useRect } from '@vant/use'
-import { useAsyncState } from '@vueuse/core'
 import bgImg from '../../images/bg-image.png'
-import iconSearch from './icons/icon_search.png'
-import iconFree from './icons/icon-free.png'
-import { browser } from '@/helpers/utils'
 import TheSticky from '@/components/the-sticky'
 
 const noop = () => {
@@ -39,18 +20,6 @@ const noop = () => {
 export default defineComponent({
   name: 'MusicList',
   props: {
-    hideSearch: {
-      type: Boolean,
-      default: false
-    },
-    defauleParams: {
-      type: Object,
-      default: () => ({})
-    },
-    onItemClick: {
-      type: Function,
-      default: noop
-    },
     teacherId: {
       type: String || Number,
       default: ''
@@ -60,77 +29,24 @@ export default defineComponent({
       default: false
     }
   },
-  setup(
-    { hideSearch, defauleParams, onItemClick, teacherId, myself },
-    { expose }
-  ) {
-    const teacherDetaultSubject = ref({
-      id: '',
-      name: ''
-    })
-    if (baseState.platformType === 'TEACHER') {
-      // defaultSubject
-      const users = baseState.user.data
-      teacherDetaultSubject.value = {
-        name: users.defaultSubjectName || '全部声部',
-        id: users.defaultSubject || ''
-      }
-    } else {
-      const subjects: any = useSubjectId(SubjectEnum.SEARCH)
-      // 判断是否已有数据
-      if (!subjects.id) {
-        const users = baseState.user.data
-        const subjectId = users.subjectId
-          ? Number(users.subjectId.split(',')[0])
-          : ''
-        const subjectName = users.subjectName
-          ? users.subjectName.split(',')[0]
-          : ''
-        if (subjectId) {
-          useSubjectId(
-            SubjectEnum.SEARCH,
-            JSON.stringify({
-              id: subjectId,
-              name: subjectName
-            }),
-            'set'
-          )
-        }
-      }
-    }
-
+  setup({ onItemClick }, { expose }) {
     localStorage.setItem('behaviorId', getRandomKey())
     const route = useRoute()
     const router = useRouter()
-    const tempParams: any = {}
-    if (baseState.version) {
-      tempParams.version = baseState.version || '' // 处理ios审核版本
-      tempParams.platform =
-        baseState.platformType === 'STUDENT' ? 'ios-student' : 'ios-teacher'
-    }
-    // 判断是否在搜索页面用过
-    if (!hideSearch) {
-      if (baseState.platformType === 'TEACHER') {
-        tempParams.subjectIds = teacherDetaultSubject.value.id
-      } else {
-        const getSubject: any = useSubjectId(SubjectEnum.SEARCH)
-        tempParams.subjectIds = getSubject.id
-      }
-
-      // const getMusic: any = useSubjectId(SubjectEnum.MUSIC_FREE)
-    }
     //
     const params = reactive({
       search: (route.query.search as string) || '',
       subjectType: (route.query.subjectType as string) || '',
       page: 1,
-      ...tempParams
+      subjectId: null,
+      level: '',
+      type: ''
     })
-    const subjectList = ref<any>([])
     const data = ref<any>(null)
     const loading = ref(false)
     const finished = ref(false)
     const isError = ref(false)
+    const searchObj = ref<any>({})
 
     const apiSuffix = ref(
       baseState.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
@@ -144,9 +60,6 @@ export default defineComponent({
     }
 
     const FetchList = async () => {
-      if (loading.value) {
-        return
-      }
       loading.value = true
       isError.value = false
       const tempParams = {
@@ -173,159 +86,213 @@ export default defineComponent({
       loading.value = false
     }
 
-    // 设置默认声部
-    const setDefaultSubject = async (subjectId: any) => {
-      try {
-        await request.post('/api-teacher/teacher/defaultSubject', {
-          params: {
-            subjectId
-          }
-        })
-      } catch {
-        //
-      }
-    }
-
-    const onComfirmSubject = item => {
-      params.page = 1
-      params.subjectIds = item.id
-      data.value = null
-      if (baseState.platformType === 'TEACHER') {
-        teacherDetaultSubject.value = {
-          name: item.name,
-          id: item.id
-        }
-        setDefaultSubject(item.id)
-      } else {
-        subject.id = item.id
-        subject.name = item.name
-        useSubjectId(
-          SubjectEnum.SEARCH,
-          JSON.stringify({
-            id: item.id,
-            name: item.name
-          }),
-          'set'
-        )
-      }
-
-      FetchList()
-      subject.show = false
-    }
-
-    const getSubject: any = useSubjectId(SubjectEnum.SEARCH)
-    const subject = reactive({
-      show: false,
-      name: getSubject.id ? getSubject.name : '全部声部',
-      id: getSubject.id || ''
-    })
-
-    const getSubjectList = async () => {
-      const { data } = await request.get(
-        `${apiSuffix.value}/subject/subjectSelect?type=MUSIC`
-      )
-      if (Array.isArray(data)) {
-        const subject: any = []
-        data.forEach((item: any) => {
-          if (item.subjects && item.subjects.length) {
-            item.subjects.forEach(s => {
-              subject.push(s)
-            })
-          }
-        })
-        subjectList.value = subject || []
-      }
-    }
-
     const getSelectCondition = async () => {
       const { data } = await request.post(
-        `${apiSuffix.value}/tenantAlbumMusic/selectCondition?subjectType=${params.subjectType}`
+        `${apiSuffix.value}/tenantAlbumMusic/selectCondition`,
+        {
+          data: {
+            subjectType: params.subjectType
+          }
+        }
       )
-      console.log(data)
+      searchObj.value = data || {}
     }
 
     onMounted(async () => {
-      getSubjectList()
-      getSelectCondition()
-    })
-
-    expose({
-      onSearch,
-      onComfirmSubject
+      // SUBJECT: '声部练习',
+      // MUSIC: '独奏曲目',
+      // ENSEMBLE: '合奏练习'
+      if (params.subjectType === 'SUBJECT') {
+        document.title = '声部练习'
+      } else if (params.subjectType === 'MUSIC') {
+        document.title = '独奏曲目'
+      } else if (params.subjectType === 'ENSEMBLE') {
+        document.title = '合奏练习'
+      }
+      loading.value = true
+      await getSelectCondition()
+      await FetchList()
     })
 
     return () => {
       return (
         <>
-          {!hideSearch && (
-            <div class={styles.sticky}>
-              <TheSticky>
-                <ColHeader
-                  background="transparent"
-                  isFixed={false}
-                  border={false}
-                  color="#131415"
-                />
-                <Search
-                  onSearch={onSearch}
-                  type="tenant"
-                  background="transparent"
-                  inputBackground="transparent"
-                  // leftIcon={iconSearch}
-                  v-slots={{
-                    left: () => (
-                      <DropdownMenu>
-                        <DropdownItem title="筛选">
-                          <div
-                            class={styles.searchResult}
-                            style={{ maxHeight: '45vh', overflowY: 'auto' }}
-                          >
-                            <div class={styles.searchTitle}>声部</div>
-                            <div
-                              class={[
-                                styles['radio-group'],
-                                styles.radio,
-                                styles['organ-radio']
-                              ]}
-                            >
-                              {subjectList.value.map((subject: any) => {
-                                const isActive =
-                                  subject.id ===
-                                  Number(params.subjectIds || null)
-                                const type = isActive ? 'primary' : 'default'
-                                return (
-                                  <Tag
-                                    size="large"
-                                    plain={isActive}
-                                    type={type}
-                                    round
-                                    onClick={() => {
-                                      console.log(subject, '1212')
-                                      // this.subject = { ...subject }
-                                    }}
-                                  >
-                                    {subject.name}
-                                  </Tag>
-                                )
-                              })}
+          <div class={styles.sticky}>
+            <TheSticky>
+              <ColHeader
+                background="transparent"
+                isFixed={false}
+                border={false}
+                color="#131415"
+              />
+              <Search
+                onSearch={onSearch}
+                type="tenant"
+                background="transparent"
+                inputBackground="transparent"
+                // leftIcon={iconSearch}
+                v-slots={{
+                  left: () => (
+                    <DropdownMenu>
+                      <DropdownItem title="筛选">
+                        <div
+                          class={styles.searchResult}
+                          style={{ maxHeight: '45vh', overflowY: 'auto' }}
+                        >
+                          {searchObj.value.subjects &&
+                            searchObj.value.subjects.length > 0 && (
+                              <>
+                                <div class={styles.searchTitle}>声部</div>
+                                <div
+                                  class={[
+                                    styles['radio-group'],
+                                    styles.radio,
+                                    styles['organ-radio']
+                                  ]}
+                                >
+                                  {searchObj.value.subjects.map(
+                                    (subject: any) => {
+                                      const isActive =
+                                        subject.id ===
+                                        Number(params.subjectId || null)
+                                      const type = isActive
+                                        ? 'primary'
+                                        : 'default'
+                                      return (
+                                        <Tag
+                                          size="large"
+                                          plain={isActive}
+                                          type={type}
+                                          round
+                                          onClick={() => {
+                                            console.log(subject, '1212')
+                                            // this.subject = { ...subject }
+                                          }}
+                                        >
+                                          {subject.name}
+                                        </Tag>
+                                      )
+                                    }
+                                  )}
+                                </div>
+                              </>
+                            )}
+                          {searchObj.value.levels &&
+                            searchObj.value.levels.length > 0 && (
+                              <>
+                                <div class={styles.searchTitle}>级别</div>
+                                <div
+                                  class={[
+                                    styles['radio-group'],
+                                    styles.radio,
+                                    styles['organ-radio']
+                                  ]}
+                                >
+                                  {searchObj.value.levels.map(
+                                    (subject: any) => {
+                                      const isActive = subject === params.level
+                                      const type = isActive
+                                        ? 'primary'
+                                        : 'default'
+                                      return (
+                                        <Tag
+                                          size="large"
+                                          plain={isActive}
+                                          type={type}
+                                          round
+                                          onClick={() => {
+                                            console.log(subject, '1212')
+                                            // this.subject = { ...subject }
+                                          }}
+                                        >
+                                          {subject}
+                                        </Tag>
+                                      )
+                                    }
+                                  )}
+                                </div>
+                              </>
+                            )}
+                          {searchObj.value.types &&
+                            searchObj.value.types.length > 0 && (
+                              <>
+                                <div class={styles.searchTitle}>类型</div>
+                                <div
+                                  class={[
+                                    styles['radio-group'],
+                                    styles.radio,
+                                    styles['organ-radio']
+                                  ]}
+                                >
+                                  {searchObj.value.types.map((subject: any) => {
+                                    const isActive = subject === params.type
+                                    const type = isActive
+                                      ? 'primary'
+                                      : 'default'
+                                    return (
+                                      <Tag
+                                        size="large"
+                                        plain={isActive}
+                                        type={type}
+                                        round
+                                        onClick={() => {
+                                          console.log(subject, '1212')
+                                          // this.subject = { ...subject }
+                                        }}
+                                      >
+                                        {subject}
+                                      </Tag>
+                                    )
+                                  })}
+                                </div>
+                              </>
+                            )}
+
+                          <Sticky position="bottom" offsetBottom={0}>
+                            <div class={['btnGroup', 'btnMore']}>
+                              <Button
+                                type="primary"
+                                plain
+                                round
+                                onClick={() => {
+                                  params.subjectId = null
+                                  params.level = ''
+                                  params.type = ''
+                                }}
+                              >
+                                重 置
+                              </Button>
+
+                              <Button
+                                type="primary"
+                                round
+                                block
+                                onClick={() => {
+                                  // this.onComfirm({ ...this.subject })
+                                }}
+                              >
+                                确 认
+                              </Button>
                             </div>
-                          </div>
-                        </DropdownItem>
-                      </DropdownMenu>
-                    )
-                  }}
-                />
-              </TheSticky>
-              <img class={styles.bgImg} src={bgImg} />
-            </div>
-          )}
+                          </Sticky>
+                        </div>
+                      </DropdownItem>
+                    </DropdownMenu>
+                  )
+                }}
+              />
+            </TheSticky>
+            <img class={styles.bgImg} src={bgImg} />
+          </div>
+
           <div class={styles.alumnList}>
             <List
-              loading={loading.value}
+              // loading={loading.value}
               finished={finished.value}
               finished-text={data.value && data.value.rows.length ? '' : ''}
               onLoad={FetchList}
               error={isError.value}
+              immediateCheck={false}
             >
               {data.value && data.value.rows.length ? (
                 <Song
@@ -354,7 +321,7 @@ export default defineComponent({
               ) : (
                 !loading.value && (
                   <ColResult
-                    tips="暂无声部训练"
+                    tips="暂无曲目"
                     classImgSize="SMALL"
                     btnStatus={false}
                   />

二進制
src/tenant/music/train-tool/images/icon-close.png


二進制
src/tenant/music/train-tool/images/icon-menu.png


二進制
src/tenant/music/train-tool/images/icon-pian.png


二進制
src/tenant/music/train-tool/images/icon-right-top.png


二進制
src/tenant/music/train-tool/images/icon-selected.png


二進制
src/tenant/music/train-tool/images/price-bg.png


+ 366 - 0
src/tenant/music/train-tool/index.module.less

@@ -0,0 +1,366 @@
+.musicContent {
+  position: absolute;
+  top: 0;
+  height: 265px;
+  width: 100%;
+  padding-top: 55px;
+  z-index: 10;
+  background-color: rgba(0, 0, 0, 0.2);
+  backdrop-filter: blur(20px);
+  -webkit-backdrop-filter: blur(20px);
+}
+
+
+.bgImg {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 265px;
+  object-fit: cover;
+  filter: blur(20px);
+  backdrop-filter: blur(22px);
+  -webkit-backdrop-filter: blur(20px);
+}
+
+.bg {
+  position: relative;
+  height: 100%;
+  padding: 16px 16px 12px;
+  z-index: 11;
+}
+
+.alumWrap {
+  position: relative;
+  display: flex;
+  align-items: center;
+  flex-direction: column;
+  margin-bottom: 76px;
+
+  .img {
+    width: 118px;
+    height: 118px;
+    flex-shrink: 0;
+    border-radius: 6px;
+    position: relative;
+    z-index: 9;
+
+    --van-image-error-icon-size: 118px;
+  }
+
+  .iconPian {
+    position: absolute;
+    right: -40px;
+    top: 8px;
+    z-index: -1;
+    width: 110px;
+    height: 110px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: url('./images/icon-pian.png') no-repeat center;
+    background-size: contain;
+
+    :global {
+      .van-image {
+        width: 60px !important;
+        height: 60px !important;
+        border-radius: 50%;
+        overflow: hidden;
+      }
+    }
+  }
+
+  .numContent {
+    position: absolute;
+    right: 4px;
+    bottom: 4px;
+    display: flex;
+    align-items: center;
+    background: rgba(255, 255, 255, 0.8);
+    border-radius: 4px;
+    font-size: 12px;
+    font-weight: 500;
+    color: #000000;
+    padding: 2px 3px;
+
+    .iconMenu {
+      margin-top: -2px;
+      width: 10px;
+      height: 12px;
+      margin-right: 4px;
+    }
+  }
+
+  .alumTitle {
+    font-size: 16px;
+    font-weight: 600;
+    color: #fff;
+    line-height: 24px;
+    padding-bottom: 8px;
+  }
+
+  .alumDes {
+    position: absolute;
+    bottom: -80px;
+    z-index: 99;
+    width: calc(100% - 96px);
+    padding-top: 16px;
+    text-align: center;
+
+    .des {
+
+      font-size: 12px;
+      color: #fff;
+    }
+  }
+}
+
+.musicList {
+  position: relative;
+  z-index: 12;
+  background-color: #fff;
+  border-radius: 16px;
+  min-height: 40vh; //calc(100vh - 210px - var(--header-height));
+
+  --van-cell-background-color: transparent;
+  --van-cell-font-size: 16px;
+  --van-cell-text-color: #333;
+  --van-cell-value-color: #999;
+  --van-cell-icon-size: 10px;
+
+  :global {
+    .van-tab {
+      font-size: 16px !important;
+      margin-top: 15px;
+      color: #999999;
+    }
+
+    .van-tab--active {
+      font-size: 16px !important;
+      color: #131415;
+    }
+
+    .van-tabs__line {
+      width: 24px;
+      height: 4px;
+      background: linear-gradient(90deg, #FF3C81 0%, rgba(255, 118, 166, 0.5) 100%) !important;
+      border-radius: 36px 36px 0px 0px;
+    }
+
+    .van-button--plain.van-button--primary {
+      background-color: transparent;
+    }
+  }
+
+  .alumnList {
+    padding: 0 15px;
+  }
+}
+
+.btnGroup {
+  // background-color: #fff;
+  padding: 12px;
+
+  :global {
+    .van-button {
+      font-size: 18px;
+      font-weight: 500;
+    }
+  }
+}
+
+
+.system-list::-webkit-scrollbar {
+  display: none;
+  /* Chrome Safari */
+}
+
+.system-list {
+  width: 100%;
+  overflow-x: auto;
+  overflow-y: hidden;
+  display: flex;
+  position: relative;
+  user-select: none;
+  box-sizing: content-box;
+  margin-bottom: 25px;
+  height: auto;
+  transition: all .2s;
+
+  &.systemHide {
+    height: 0;
+    transition: all .2s;
+    margin-bottom: 0px;
+  }
+}
+
+.system-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  flex: 1 0 auto;
+  width: 96px;
+  min-height: 120px;
+  box-sizing: border-box;
+  background: #ffffff;
+  border-radius: 12px;
+  border: 1px solid #e5e5e5;
+  margin-left: 10px;
+
+  &:last-child {
+    margin-right: 10px;
+  }
+
+  .title {
+    font-size: 14px;
+    font-weight: 500;
+    color: #333333;
+    line-height: 20px;
+  }
+
+  .price {
+    color: #EF2F56;
+    font-size: 25px;
+    line-height: 1.5;
+    font-family: DINAlternate-Bold, DINAlternate;
+    font-weight: bold;
+
+    span {
+      font-size: 16px;
+    }
+  }
+
+  .originalPrice {
+    color: #999999;
+    font-size: 13px;
+  }
+
+  &.active {
+    background: linear-gradient(223deg, #FEECE3 0%, #FEE4E3 52%, #FFDCE6 100%);
+    border: 1px solid #FF4264;
+    position: relative;
+
+    .title {
+      color: #EF2F56;
+    }
+
+
+    .originalPrice {
+      color: #EF2F56;
+    }
+
+    &::before {
+      content: ' ';
+      font: 14px/1 'vant-icon';
+      width: 27px;
+      height: 18px;
+
+      position: absolute;
+      top: -1px;
+      right: -1px;
+      background: url('./images/icon-selected.png') no-repeat center;
+      background-size: contain;
+    }
+  }
+}
+
+.bottom_function {
+  background: url('./images/price-bg.png') no-repeat top center #fff;
+  background-size: contain;
+  box-shadow: inset 0px 1px 0px 0px #FFFFFF;
+  border-radius: 16px 16px 0px 0px;
+  // border: none;
+
+  .iconRightTop {
+    position: absolute;
+    top: -51px;
+    right: 9px;
+    width: 144px;
+    height: 154px;
+  }
+
+  .iconClose {
+    position: absolute;
+    top: -51px;
+    right: 9px;
+    width: 32px;
+    height: 32px;
+    background: url('./images/icon-close.png') no-repeat center;
+    background-size: contain;
+    display: inline-block;
+    z-index: 99;
+  }
+}
+
+
+.popupStatus {
+  overflow-y: initial;
+}
+
+.memberMeal {
+  .titleMeal {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 16px;
+    font-weight: 600;
+    color: #131415;
+    line-height: 22px;
+    padding: 26px 24px;
+
+    .iconArrowLine {
+      display: inline-block;
+      width: 14px;
+      height: 14px;
+      background: url('./images/icon-arrow-line.png') no-repeat center;
+      background-size: contain;
+    }
+  }
+}
+
+.btnGroup {
+  // position: fixed;
+  // bottom: 0;
+  // left: 0;
+  // right: 0;
+  // z-index: 100;
+  // background-color: #fff;
+  display: flex;
+  align-items: center;
+  padding: 0 16px 12px;
+  justify-content: space-between;
+
+  .btn {
+    padding: 0 22px;
+    height: 44px;
+    color: #fff !important;
+    background: linear-gradient(270deg, #FF204B 0%, #FE5B71 100%);
+    border-radius: 39px;
+    font-size: 18px;
+    font-weight: bold;
+    border: none;
+
+    .unit {
+      font-size: 14px;
+    }
+  }
+
+  .priceSection {
+    display: flex;
+    align-items: center;
+    font-size: 16px;
+    color: #1a1a1a;
+
+    .price {
+      font-size: 18px;
+      font-weight: bold;
+      color: #ff3535;
+
+      .priceUnit {
+        font-size: 14px;
+      }
+    }
+  }
+}

+ 441 - 0
src/tenant/music/train-tool/index.tsx

@@ -0,0 +1,441 @@
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { Image, Tabs, Tab, List, Button, Popup, Dialog } from 'vant'
+import styles from './index.module.less'
+import TheSticky from '@/components/the-sticky'
+import ColHeader from '@/components/col-header'
+import { useWindowScroll, useEventListener } from '@vueuse/core'
+import request from '@/helpers/request'
+import iconMenu from './images/icon-menu.png'
+import iconRightTop from './images/icon-right-top.png'
+import iconAlbumCover from '../../images/icon-album-cover.png'
+import { state as baseState } from '@/state'
+import Song from '../component/song'
+import { useRoute, useRouter } from 'vue-router'
+import ColResult from '@/components/col-result'
+import { moneyFormat } from '@/helpers/utils'
+import { orderStatus } from '@/views/order-detail/orderStatus'
+import { postMessage } from '@/helpers/native-message'
+
+export default defineComponent({
+  name: 'train-tool',
+  setup() {
+    const route = useRoute()
+    const router = useRouter()
+    const background = ref<string>('rgba(55, 205, 177, 0)')
+    const color = ref<string>('#fff')
+    const state = reactive({
+      details: {} as any,
+      albumId: route.query.albumId || null,
+      activeTab: 'SUBJECT',
+      loading: false,
+      finished: false,
+      isError: false,
+      list: [] as any,
+      popupStatus: false,
+      selectMember: {} as any, // 购买的月份
+      buyList: [
+        {
+          purchaseCycle: 6,
+          salePrice: 0,
+          costPrice: 0,
+          status: true
+        },
+        {
+          purchaseCycle: 12,
+          salePrice: 0,
+          costPrice: 0,
+          status: false
+        },
+        {
+          purchaseCycle: 18,
+          salePrice: 0,
+          costPrice: 0,
+          status: false
+        },
+        {
+          purchaseCycle: 24,
+          salePrice: 0,
+          costPrice: 0,
+          status: false
+        },
+        {
+          purchaseCycle: 30,
+          salePrice: 0,
+          costPrice: 0,
+          status: false
+        },
+        {
+          purchaseCycle: 36,
+          salePrice: 0,
+          costPrice: 0,
+          status: false
+        }
+      ],
+      ensembleCounts: false,
+      musicCounts: false,
+      subjectCounts: false
+    })
+    const params = reactive({
+      page: 1,
+      rows: 20
+    })
+    const apiSuffix = ref(
+      baseState.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
+    )
+
+    const getDetails = async () => {
+      try {
+        const { data } = await request.post(
+          apiSuffix.value +
+            '/userTenantAlbumRecord/detail?albumId=' +
+            state.albumId
+        )
+        state.details = data || {}
+        console.log(state.details, 'details')
+        state.buyList.forEach((item: any, index: number) => {
+          item.salePrice = (index + 1) * data.salePrice
+          item.costPrice = (index + 1) * data.costPrice
+        })
+
+        state.selectMember = {
+          ...state.buyList[0]
+        }
+
+        state.ensembleCounts = data.ensembleCounts <= 0 ? false : true
+        state.subjectCounts = data.subjectCounts <= 0 ? false : true
+        state.musicCounts = data.musicCounts <= 0 ? false : true
+
+        if (state.subjectCounts) {
+          state.activeTab = 'SUBJECT'
+        } else if (state.ensembleCounts) {
+          state.activeTab = 'ENSEMBLE'
+        } else if (state.musicCounts) {
+          state.activeTab = 'MUSIC'
+        }
+      } catch {
+        //
+      }
+    }
+
+    const FetchList = async () => {
+      if (state.loading) {
+        return
+      }
+      console.log(state.details, 'state.details')
+      state.loading = true
+      state.isError = false
+      const tempParams = {
+        albumId: state.details.id || null,
+        subjectType: state.activeTab,
+        ...params
+      }
+
+      try {
+        const { data } = await request.post(
+          `${apiSuffix.value}/tenantAlbumMusic/page`,
+          {
+            data: tempParams
+          }
+        )
+        if (state.list.length > 0 && data.pageNo === 1) {
+          return
+        }
+        state.list = state.list.concat(data.rows || [])
+        params.page = data.pageNo + 1
+        // showContact.value = state.list.length > 0
+        state.loading = false
+        state.finished = data.pageNo >= data.totalPage
+        params.page = data.pageNo + 1
+      } catch (error) {
+        state.isError = true
+      }
+      state.loading = false
+    }
+
+    onMounted(async () => {
+      useEventListener(document, 'scroll', evt => {
+        const { y } = useWindowScroll()
+        if (y.value > 20) {
+          background.value = `rgba(255, 255, 255)`
+          color.value = 'black'
+          postMessage({
+            api: 'backIconChange',
+            content: { iconStyle: 'black' }
+          })
+        } else {
+          background.value = 'transparent'
+          color.value = '#fff'
+          postMessage({
+            api: 'backIconChange',
+            content: { iconStyle: 'white' }
+          })
+        }
+      })
+
+      await getDetails()
+      await FetchList()
+    })
+
+    const onSubmit = async () => {
+      const album = state.selectMember
+      const details = state.details
+      orderStatus.orderObject.orderType = 'TENANT_ALBUM'
+      orderStatus.orderObject.orderName = details.name
+      orderStatus.orderObject.orderDesc = details.name
+      orderStatus.orderObject.actualPrice = album.salePrice
+      // orderStatus.orderObject.recomUserId = route.query.recomUserId || 0
+      // orderStatus.orderObject.activityId = route.query.activityId || 0
+      orderStatus.orderObject.orderNo = ''
+      orderStatus.orderObject.orderList = [
+        {
+          orderType: 'TENANT_ALBUM',
+          goodsName: details.name,
+          actualPrice: album.salePrice,
+          // recomUserId: route.query.recomUserId || 0,
+          price: album.salePrice,
+          ...details,
+          ...album
+        }
+      ]
+
+      const res = await request.post('/api-student/userOrder/getPendingOrder', {
+        data: {
+          goodType: 'TENANT_ALBUM',
+          bizId: details.id
+        }
+      })
+
+      const result = res.data
+      if (result) {
+        state.popupStatus = false
+        Dialog.confirm({
+          title: '提示',
+          message: '您有一个未支付的订单,是否继续支付?',
+          confirmButtonColor: '#269a93',
+          cancelButtonText: '取消订单',
+          confirmButtonText: '继续支付'
+        })
+          .then(async () => {
+            orderStatus.orderObject.orderNo = result.orderNo
+            orderStatus.orderObject.actualPrice = result.actualPrice
+            orderStatus.orderObject.discountPrice = result.discountPrice
+            orderStatus.orderObject.paymentConfig = result.paymentConfig
+            routerTo()
+          })
+          .catch(() => {
+            Dialog.close()
+            // 只用取消订单,不用做其它处理
+            cancelPayment(result.orderNo)
+          })
+      } else {
+        routerTo()
+      }
+    }
+    const routerTo = () => {
+      const album = state.details
+      router.push({
+        path: '/orderDetail',
+        query: {
+          orderType: 'ALBUM',
+          album: album.id
+        }
+      })
+    }
+
+    const cancelPayment = async (orderNo: string) => {
+      try {
+        await request.post('/api-student/userOrder/orderCancel/v2', {
+          data: {
+            orderNo
+          }
+        })
+      } catch {
+        //
+      }
+    }
+
+    return () => (
+      <div class={styles.trainTool}>
+        <TheSticky position="top">
+          <ColHeader
+            background={background.value}
+            border={false}
+            isFixed={false}
+            color={color.value}
+            backIconColor="white"
+          />
+        </TheSticky>
+        <img class={styles.bgImg} src={state.details?.coverImg} />
+        <div class={styles.musicContent}></div>
+        <div class={styles.bg}>
+          <div class={styles.alumWrap}>
+            <div class={styles.img}>
+              <Image
+                class={styles.image}
+                width="100%"
+                height="100%"
+                fit="cover"
+                src={state.details?.coverImg || iconAlbumCover}
+                errorIcon={iconAlbumCover}
+              />
+              <span class={styles.numContent}>
+                <img src={iconMenu} class={styles.iconMenu} />共
+                {state.details?.musicNum}首
+              </span>
+
+              <div class={styles.iconPian}>
+                <Image
+                  class={styles.image}
+                  width="100%"
+                  height="100%"
+                  fit="cover"
+                  src={state.details?.coverImg}
+                />
+              </div>
+            </div>
+            <div class={styles.alumDes}>
+              <div class={[styles.alumTitle, 'van-ellipsis']}>
+                {state.details?.name}
+              </div>
+              <div
+                class={[styles.des, 'van-multi-ellipsis--l2']}
+                style={{
+                  height: '32px',
+                  lineHeight: '16px'
+                }}
+              >
+                {state.details?.describe}
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div class={styles.musicList}>
+          <Tabs
+            color="var(--van-primary)"
+            background="transparent"
+            lineWidth={20}
+            shrink
+            v-model:active={state.activeTab}
+            onChange={val => {
+              state.activeTab = val
+
+              params.page = 1
+              state.list = []
+              FetchList()
+            }}
+          >
+            {state.subjectCounts && <Tab title="声部练习" name="SUBJECT"></Tab>}
+            {state.musicCounts && <Tab title="合奏练习" name="ENSEMBLE"></Tab>}
+            {state.ensembleCounts && <Tab title="独奏曲目" name="MUSIC"></Tab>}
+          </Tabs>
+
+          <div class={styles.alumnList}>
+            <List
+              loading={state.loading}
+              finished={state.finished}
+              finished-text={' '}
+              onLoad={FetchList}
+              immediateCheck={false}
+              error={state.isError}
+            >
+              {state.list && state.list.length ? (
+                <Song
+                  showNumber
+                  list={state.list}
+                  onDetail={(item: any) => {
+                    router.push({
+                      path: '/music-detail',
+                      query: {
+                        id: item.id
+                        // albumId: route.params.id
+                      }
+                    })
+                  }}
+                />
+              ) : (
+                !state.loading && (
+                  <ColResult
+                    tips="暂无曲目"
+                    classImgSize="SMALL"
+                    btnStatus={false}
+                  />
+                )
+              )}
+            </List>
+          </div>
+        </div>
+        {!state.loading && !state.details.ifBuy && (
+          <TheSticky position="bottom">
+            <div class={styles.btnGroup}>
+              <Button
+                round
+                block
+                color="#FE2451"
+                onClick={() => (state.popupStatus = true)}
+              >
+                购买教程
+              </Button>
+            </div>
+          </TheSticky>
+        )}
+        <Popup
+          v-model:show={state.popupStatus}
+          position="bottom"
+          round
+          zIndex={9999}
+          safe-area-inset-bottom
+          closeable={false}
+          class={styles.popupStatus}
+          onClose={() => (state.popupStatus = false)}
+          // onClosed={() => (this.openStatus = false)}
+        >
+          <div class={styles.bottom_function}>
+            <i
+              class={styles.iconClose}
+              onClick={() => (state.popupStatus = false)}
+            ></i>
+            <img src={iconRightTop} class={styles.iconRightTop} />
+            <div class={styles.memberMeal}>
+              <div class={styles.titleMeal}>
+                <span>请选择教程购买周期</span>
+              </div>
+
+              <div class={styles['system-list']}>
+                {state.buyList.map((item: any) => (
+                  <div
+                    class={[
+                      styles['system-item'],
+                      item.status && styles.active
+                    ]}
+                    onClick={() => {
+                      state.buyList.forEach((item: any) => {
+                        item.status = false
+                      })
+                      item.status = true
+                      state.selectMember = item
+                    }}
+                  >
+                    <p class={styles.title}>{item.purchaseCycle}个月</p>
+                    <p class={styles.price}>
+                      <span>¥</span>
+                      {moneyFormat(item.salePrice, '0,0[.]00')}
+                    </p>
+                    <del class={styles.originalPrice}>
+                      ¥{moneyFormat(item.costPrice, '0,0[.]00')}
+                    </del>
+                  </div>
+                ))}
+              </div>
+            </div>
+            <div class={styles.btnGroup}>
+              <Button round block class={styles.btn} onClick={onSubmit}>
+                点击购买
+              </Button>
+            </div>
+          </div>
+        </Popup>
+      </div>
+    )
+  }
+})

+ 18 - 0
src/tenant/ranking-list/index.module.less

@@ -0,0 +1,18 @@
+.bgImg {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 214px;
+  // object-fit: cover;
+  z-index: -1;
+}
+
+.sticky {
+  :global {
+    .van-sticky {
+      background: url('../images/bg-image.png') no-repeat top center;
+      background-size: 100% 214px;
+    }
+  }
+}

+ 26 - 0
src/tenant/ranking-list/index.tsx

@@ -0,0 +1,26 @@
+import TheSticky from '@/components/the-sticky'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import ColHeader from '@/components/col-header'
+import bgImg from '../images/bg-image.png'
+
+export default defineComponent({
+  name: 'ranking-list',
+  setup() {
+    return () => (
+      <div class={styles.activationCode}>
+        <div class={styles.sticky}>
+          <TheSticky position="top">
+            <ColHeader
+              background="transparent"
+              isFixed={false}
+              border={false}
+              color="#131415"
+            />
+          </TheSticky>
+          <img class={styles.bgImg} src={bgImg} />
+        </div>
+      </div>
+    )
+  }
+})

+ 256 - 0
src/tenant/trade/index.module.less

@@ -0,0 +1,256 @@
+.sticky {
+  :global {
+    .van-sticky {
+      background: url('../images/bg-image.png') no-repeat top center;
+      background-size: 100% 214px;
+    }
+
+    .van-search__content {
+      background: rgba(255, 255, 255, 0.5) !important;
+
+      input::placeholder {
+        color: rgba(0, 0, 0, 0.4) !important;
+      }
+
+      input {
+        color: rgba(0, 0, 0, 0.4) !important;
+      }
+
+      .van-field__clear {
+        color: rgba(0, 0, 0, 0.4) !important;
+      }
+    }
+
+  }
+}
+
+.label {
+  margin-right: 8px;
+  font-size: 14px;
+  color: #131415;
+
+  :global {
+
+    .van-list__loading,
+    .van-list__finished-text,
+    .van-list__error-text {
+      width: 100%;
+    }
+
+    .iconfont-down {
+      margin-left: 4px;
+    }
+  }
+}
+
+
+.bgImg {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 214px;
+  // object-fit: cover;
+  z-index: -1;
+}
+
+.tagTabs {
+  --van-cell-background-color: transparent;
+  --van-cell-font-size: 16px;
+  --van-cell-text-color: #333;
+  --van-cell-value-color: #999;
+  --van-cell-icon-size: 10px;
+
+  :global {
+    .van-tab {
+      font-size: 16px !important;
+      margin-top: 15px;
+      color: #999999;
+    }
+
+    .van-tab--active {
+      font-size: 16px !important;
+      color: #131415;
+    }
+
+    .van-tabs__line {
+      width: 24px;
+      height: 4px;
+      background: linear-gradient(90deg, #FF3C81 0%, rgba(255, 118, 166, 0.5) 100%) !important;
+      border-radius: 36px 36px 0px 0px;
+    }
+
+    .van-button--plain.van-button--primary {
+      background-color: transparent;
+    }
+  }
+
+  // :global {
+  // .van-tabs__nav {
+  //   background-color: transparent;
+  //   padding: 0;
+  //   margin: 0 15px;
+  // }
+
+  // .van-tab {
+  //   font-size: 16px;
+  //   font-weight: bold;
+  // }
+
+  // .van-tab--shrink {
+  //   padding: 0;
+  //   margin: 10px 0;
+  //   display: inline-block;
+  //   font-size: 14px;
+  //   background: transparent;
+  //   border-radius: 14px;
+  //   line-height: 26px;
+  //   padding: 0 12px;
+  //   color: rgba(0, 0, 0, 0.4);
+  // }
+
+  // .van-tab--active {
+  //   background: #FF699E;
+
+  //   color: #FFFFFF;
+
+  //   .van-tab__text {
+  //     z-index: 1;
+  //   }
+  // }
+
+  // .van-tabs__line {
+  //   height: 0;
+  // }
+  // }
+}
+
+
+.tradeList {
+  padding-top: 12px;
+
+  :global {
+    .van-cell-group {
+      overflow: hidden;
+      border-radius: 10px;
+      margin-bottom: 12px;
+      padding-bottom: 18px;
+    }
+
+    .van-cell {
+      padding: 16px 12px 12px;
+    }
+  }
+
+  .orderSection {
+    padding-top: 0;
+  }
+
+  .list {
+    padding: 0 14px;
+  }
+
+  .tradeLogo {
+    width: 80px;
+    height: 80px;
+    border-radius: 12px;
+    margin-right: 10px;
+    overflow: hidden;
+  }
+
+  .tradeType {
+    color: var(--van-primary);
+  }
+
+  .title {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 14px;
+    font-weight: 500;
+    color: #131415;
+    line-height: 24px;
+
+
+    .name {
+      max-width: 160px;
+    }
+
+    .desc {
+      font-size: 14px;
+      font-family: DINAlternate-Bold, DINAlternate;
+      font-weight: bold;
+      color: #131415;
+    }
+  }
+
+  .description {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 12px;
+    color: #777777;
+
+    .d {
+      max-width: 160px;
+    }
+
+  }
+
+  .paymentPrice {
+    text-align: right;
+    font-size: 14px;
+    font-weight: 500;
+    color: #131415;
+    padding-right: 12px;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+
+    span {
+      font-family: DINAlternate-Bold, DINAlternate;
+      font-weight: bold;
+      color: #FE2451;
+      font-size: 20px;
+      padding-left: 4px;
+
+      i {
+        padding-right: 2px;
+        font-size: 14px;
+        font-style: normal;
+        vertical-align: middle;
+      }
+    }
+  }
+
+  .songLength {
+    border-radius: 4px;
+    border: 1px solid #FE2451;
+    font-size: 12px;
+    font-family: PingFangSC-Regular, PingFang SC;
+    font-weight: 400;
+    color: #FE2451;
+    line-height: 16px;
+    padding: 1px 6px;
+  }
+}
+
+.btnList {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  padding-top: 12px;
+  padding-right: 12px;
+
+  :global {
+    .van-button {
+      font-size: 14px;
+      font-weight: 500;
+      padding: 7px 16px;
+    }
+
+    .van-button+.van-button {
+      margin-left: 10px;
+    }
+  }
+}

+ 355 - 0
src/tenant/trade/index.tsx

@@ -0,0 +1,355 @@
+import { defineComponent, onMounted, reactive } from 'vue'
+import {
+  List,
+  Popup,
+  Icon,
+  Tabs,
+  Tab,
+  DatetimePicker,
+  CellGroup,
+  Button,
+  Dialog,
+  Cell,
+  Image
+} from 'vant'
+import Search from '@/components/col-search'
+import request from '@/helpers/request'
+import iconMember from '@common/images/icon_member.png'
+// import Item from './item'
+
+import { useRouter } from 'vue-router'
+import ColResult from '@/components/col-result'
+import styles from './index.module.less'
+import ColHeader from '@/components/col-header'
+import bgImg from '../images/bg-image.png'
+import { dateFormat, formatterDate, moneyFormat } from '@/helpers/utils'
+import TheSticky from '@/components/the-sticky'
+import { tradeOrder } from './tradeOrder'
+import dayjs from 'dayjs'
+import { goodsType, orderType } from '@/constant'
+
+export default defineComponent({
+  name: 'MusicList',
+  setup() {
+    const router = useRouter()
+    const state = reactive({
+      actions: [
+        { name: '待支付', status: 'WAIT_PAY' },
+        { name: '支付中', status: 'PAYING' },
+        { name: '已付款', status: 'PAID' },
+        { name: '已关闭', status: 'CLOSE' },
+        // { name: '已退费', status: 'REFUND' },
+        { name: '支付失败', status: 'FAIL' }
+      ],
+      timeStatus: false,
+      currentDate: new Date(),
+      list: [],
+      dataShow: true, // 判断是否有数据
+      loading: false,
+      finished: false,
+      searchName: '全部',
+      params: {
+        status: '',
+        page: 1,
+        rows: 20
+      },
+      type: 'buy'
+    })
+    const getList = async () => {
+      if (state.loading) return
+      state.loading = true
+      try {
+        const params = {
+          ...state.params,
+          searchDate: dayjs(state.currentDate).format('YYYY-MM')
+        }
+
+        const url =
+          state.type === 'buy'
+            ? '/api-student/userOrder/page'
+            : '/api-student/userOrderRefunds/page'
+        const { code, data } = await request.post(url, {
+          data: {
+            ...params,
+            dateTime: state.type === 'refund' ? params.searchDate : undefined,
+            timeType: state.type === 'refund' ? 'MONTH' : undefined
+          }
+        })
+        if (code === 200) {
+          const result = data || {}
+          state.list = state.list.concat(result.rows || [])
+          state.finished = result.pageNo >= result.totalPage
+          state.params.page = result.pageNo + 1
+          state.dataShow = state.list.length > 0
+        }
+      } catch {
+        state.dataShow = false
+        state.finished = true
+      }
+      state.loading = false
+    }
+    const onDetail = (item: any) => {
+      if (state.type === 'refund') return
+      router.push({
+        path: '/tradeDetail',
+        query: {
+          orderNo: item.orderNo,
+          path: 'tradeRecord'
+        }
+      })
+    }
+    const onConfirm = (date: Date) => {
+      state.currentDate = date
+      state.timeStatus = false
+      onSearch()
+    }
+    const onSearch = () => {
+      state.dataShow = true
+      state.loading = false
+      state.finished = false
+      state.list = []
+      state.params.page = 1
+      getList()
+    }
+    const onCancelPay = async (item: any) => {
+      Dialog.confirm({
+        message: '是否取消订单?',
+        confirmButtonText: '确定',
+        confirmButtonColor: 'var(--van-primary)',
+        cancelButtonText: '取消'
+      }).then(async () => {
+        try {
+          await request.post('/api-student/userOrder/orderCancel', {
+            data: {
+              orderNo: item.orderNo
+            }
+          })
+          // Toast('取消成功')
+          onSearch()
+        } catch {
+          //
+        }
+      })
+    }
+    const onPay = async (item: any) => {
+      try {
+        const res = await request.get(
+          `/api-student/userOrder/detailByOrderNo/${item.orderNo}`
+        )
+        const result = res.data
+        tradeOrder({ ...result, paymentConfig: item.paymentConfig }, () => {
+          router.push({
+            path: '/orderDetail',
+            query: {
+              orderType: result.orderType
+            }
+          })
+        })
+      } catch {
+        //
+      }
+    }
+
+    onMounted(() => {
+      getList()
+    })
+    return () => (
+      <>
+        <div class={styles.sticky}>
+          <TheSticky>
+            <ColHeader
+              background="transparent"
+              isFixed={false}
+              border={false}
+              color="#131415"
+            />
+            <Search
+              onSearch={onSearch}
+              type="tenant"
+              background="transparent"
+              inputBackground="transparent"
+              v-slots={{
+                left: () => (
+                  <div
+                    class={styles.label}
+                    onClick={() => (state.timeStatus = true)}
+                  >
+                    {dateFormat(state.currentDate, 'YYYY-MM')}
+
+                    <Icon
+                      classPrefix="iconfont"
+                      name="down"
+                      size={12}
+                      color="#131415"
+                    />
+                  </div>
+                )
+              }}
+            />
+            <Tabs
+              color="var(--van-primary)"
+              background="transparent"
+              lineWidth={20}
+              shrink
+              class={styles.tagTabs}
+              onClick-tab={(obj: any) => {
+                state.type = obj.name === 'REFUND' ? 'refund' : 'buy'
+                state.params.status = obj.name
+                onSearch()
+              }}
+            >
+              <Tab title="全部" name=""></Tab>
+              {state.actions.map((tag: any) => (
+                <Tab title={tag.name} name={tag.status}></Tab>
+              ))}
+            </Tabs>
+          </TheSticky>
+          <img class={styles.bgImg} src={bgImg} />
+        </div>
+        <div class={styles.tradeList}>
+          {state.dataShow ? (
+            <List
+              loading={state.loading}
+              finished={state.finished}
+              finishedText=" "
+              class={[styles.list]}
+              onLoad={getList}
+            >
+              {state.list.map((item: any) => (
+                <CellGroup
+                  border={false}
+                  onClick={() => {
+                    onDetail(item)
+                  }}
+                >
+                  <Cell
+                    border={false}
+                    title={dayjs(item.createTime).format('YYYY-MM-DD HH:mm')}
+                    value={
+                      state.type === 'buy'
+                        ? orderType[item.status]
+                        : item.operateReason
+                    }
+                    valueClass={styles.tradeType}
+                  />
+                  {item.orderDetailList &&
+                    item.orderDetailList.map((orderDetail: any) => (
+                      <Cell
+                        border={false}
+                        class={styles.orderSection}
+                        v-slots={{
+                          icon: () => (
+                            <Image
+                              src={
+                                orderDetail.goodType === 'VIP'
+                                  ? iconMember
+                                  : orderDetail.bizInfo?.bizCover
+                              }
+                              class={styles.tradeLogo}
+                            />
+                          ),
+                          title: () => (
+                            <div class={styles.goodsSection}>
+                              <div class={[styles.title]}>
+                                <span class={[styles.name, 'van-ellipsis']}>
+                                  {orderDetail.bizInfo?.bizName}
+                                </span>
+                                <span class={styles.desc}>
+                                  ¥{moneyFormat(orderDetail.actualPrice)}
+                                </span>
+                              </div>
+
+                              <div class={styles.description}>
+                                <span class={[styles.d, 'van-ellipsis']}>
+                                  {orderDetail.bizInfo?.bizDesc}
+                                </span>
+                                {orderDetail.goodType !== 'VIP' && (
+                                  <>
+                                    {orderDetail.goodType === 'TENANT_ALBUM' ? (
+                                      <span class={styles.t}>
+                                        x{orderDetail.bizInfo?.bizValidTime}个月
+                                      </span>
+                                    ) : (
+                                      <span class={styles.t}>永久</span>
+                                    )}
+                                  </>
+                                )}
+                              </div>
+
+                              {orderDetail.bizInfo?.bizMusicCount && (
+                                <span class={styles.songLength}>
+                                  共{orderDetail.bizInfo?.bizMusicCount}首
+                                </span>
+                              )}
+                            </div>
+                          )
+                        }}
+                      />
+                    ))}
+
+                  <div class={styles.paymentPrice}>
+                    {['PAYING', 'WAIT_PAY'].includes(item.status)
+                      ? '需付款'
+                      : '实付款'}
+                    <span>
+                      <i>¥</i>
+                      {moneyFormat(item.actualPrice)}
+                    </span>
+                  </div>
+                  {item.status === 'PAYING' || item.status === 'WAIT_PAY' ? (
+                    <div class={styles.btnList}>
+                      <Button
+                        size="small"
+                        round
+                        onClick={(e: any) => {
+                          e.stopPropagation()
+                          onCancelPay(item)
+                        }}
+                      >
+                        取消订单
+                      </Button>
+                      <Button
+                        size="small"
+                        round
+                        type="primary"
+                        onClick={(e: any) => {
+                          e.stopPropagation()
+                          onPay(item)
+                        }}
+                      >
+                        继续付款
+                      </Button>
+                    </div>
+                  ) : null}
+                </CellGroup>
+              ))}
+            </List>
+          ) : (
+            <ColResult
+              btnStatus={false}
+              classImgSize="SMALL"
+              tips={state.type === 'buy' ? '暂无购买记录' : '暂无退款记录'}
+            />
+          )}
+        </div>
+
+        <Popup
+          v-model:show={state.timeStatus}
+          position="bottom"
+          round
+          closeOnPopstate
+        >
+          <DatetimePicker
+            type="year-month"
+            v-model={state.currentDate}
+            formatter={formatterDate}
+            onCancel={() => {
+              state.timeStatus = false
+            }}
+            onConfirm={onConfirm}
+          />
+        </Popup>
+      </>
+    )
+  }
+})

+ 65 - 0
src/tenant/trade/list/index.module.less

@@ -0,0 +1,65 @@
+.tradeList {
+  .searchTime,
+  .searchType {
+    color: #1a1a1a;
+    font-size: 14px;
+  }
+  :global {
+    .iconfont-down {
+      margin-left: 4px;
+      // transform: scale(0.8);
+    }
+
+    .van-cell-group {
+      overflow: hidden;
+      border-radius: 10px;
+      margin-bottom: 12px;
+    }
+  }
+
+  .list {
+    padding: 0 14px;
+  }
+
+  .tradeLogo {
+    width: 35px;
+    height: 35px;
+    border-radius: 50%;
+    margin-right: 10px;
+    overflow: hidden;
+  }
+
+  .tradeType {
+    color: var(--van-primary);
+  }
+
+  .title,
+  .content {
+    padding-top: 1px;
+    display: flex;
+    justify-content: space-between;
+    flex-direction: column;
+    line-height: 18px;
+    color: #333333;
+    font-size: 14px;
+  }
+  .desc,
+  .num {
+    padding-top: 3px;
+    font-size: 13px;
+    color: #999999;
+  }
+}
+
+.btnList {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  padding-bottom: var(--van-cell-vertical-padding);
+  padding-right: var(--van-cell-horizontal-padding);
+  :global {
+    .van-button + .van-button {
+      margin-left: 10px;
+    }
+  }
+}

+ 336 - 0
src/tenant/trade/list/index.tsx

@@ -0,0 +1,336 @@
+import { defineComponent, PropType } from 'vue'
+import styles from './index.module.less'
+import {
+  ActionSheet,
+  Cell,
+  CellGroup,
+  DatetimePicker,
+  Icon,
+  Popup,
+  Sticky,
+  Image,
+  List,
+  Button,
+  Dialog,
+  Toast
+} from 'vant'
+import { formatterDate } from '@/helpers/utils'
+import { goodsType, orderType, returnType } from '@/constant'
+
+import iconTeacher from '@common/images/icon_teacher.png'
+import request from '@/helpers/request'
+import dayjs from 'dayjs'
+import ColResult from '@/components/col-result'
+import { orderStatus } from '@/views/order-detail/orderStatus'
+import { tradeOrder } from '../tradeOrder'
+
+export default defineComponent({
+  name: 'list',
+  props: {
+    type: {
+      type: String as PropType<'buy' | 'refund'>,
+      default: 'buy'
+    },
+    height: {
+      type: Number,
+      default: 44
+    }
+  },
+  data() {
+    return {
+      timeStatus: false,
+      currentDate: new Date(),
+      typeStatus: false,
+      // 订单状态 WAIT_PAY 待支付 PAYING 支付中 PAID 已付款 CLOSE 已关闭 FAIL 支付失败 (多选用,分割)
+      actions: [
+        { name: '全部' },
+        { name: '待支付', status: 'WAIT_PAY' },
+        { name: '支付中', status: 'PAYING' },
+        { name: '已付款', status: 'PAID' },
+        { name: '已关闭', status: 'CLOSE' },
+        { name: '支付失败', status: 'FAIL' }
+      ],
+      list: [],
+      dataShow: true, // 判断是否有数据
+      loading: false,
+      finished: false,
+      searchName: '全部',
+      params: {
+        status: '',
+        page: 1,
+        rows: 20
+      }
+    }
+  },
+  methods: {
+    async getList() {
+      if (this.loading) return
+      this.loading = true
+      try {
+        const params = {
+          ...this.params,
+          searchDate: dayjs(this.currentDate).format('YYYY-MM')
+        }
+
+        const url =
+          this.type === 'buy'
+            ? '/api-student/userOrder/page'
+            : '/api-student/userOrderRefunds/page'
+        const { code, data } = await request.post(url, {
+          data: {
+            ...params,
+            dateTime: this.type === 'refund' ? params.searchDate : undefined,
+            timeType: this.type === 'refund' ? 'MONTH' : undefined
+          }
+        })
+        if (code === 200) {
+          const result = data || {}
+          this.list = this.list.concat(result.rows || [])
+          this.finished = result.pageNo >= result.totalPage
+          this.params.page = result.pageNo + 1
+          this.dataShow = this.list.length > 0
+        }
+      } catch {
+        this.dataShow = false
+        this.finished = true
+      }
+      this.loading = false
+    },
+    onDetail(item: any) {
+      if (this.type === 'refund') return
+      this.$router.push({
+        path: '/tradeDetail',
+        query: {
+          orderNo: item.orderNo,
+          path: 'tradeRecord'
+        }
+      })
+    },
+    onConfirm(date: Date) {
+      this.currentDate = date
+      this.timeStatus = false
+      this.onSearch()
+    },
+    onSelect(item: any) {
+      this.params.status = item.status
+      this.searchName = item.name
+      this.onSearch()
+    },
+    onSearch() {
+      this.dataShow = true
+      this.loading = false
+      this.finished = false
+      this.list = []
+      this.params.page = 1
+      this.getList()
+    },
+    async onCancelPay(item: any) {
+      // orderPay: {
+      //   cancelUrl: '/api-student/userOrder/orderCancel',
+      //   payUrl: '/api-student/userOrder/orderPay'
+      // }
+      Dialog.confirm({
+        message: '是否取消订单?',
+        confirmButtonText: '确定',
+        confirmButtonColor: 'var(--van-primary)',
+        cancelButtonText: '取消'
+      }).then(async () => {
+        try {
+          await request.post('/api-student/userOrder/orderCancel', {
+            data: {
+              orderNo: item.orderNo
+            }
+          })
+          // Toast('取消成功')
+          this.onSearch()
+        } catch {}
+      })
+    },
+    async onPay(item: any) {
+      try {
+        const res = await request.get(
+          `/api-student/userOrder/detailByOrderNo/${item.orderNo}`
+        )
+        const result = res.data
+        tradeOrder(result, () => {
+          this.$router.push({
+            path: '/orderDetail',
+            query: {
+              orderType: result.orderType
+            }
+          })
+        })
+      } catch {}
+    }
+  },
+  render() {
+    return (
+      <div class={styles.tradeList}>
+        <Sticky position="top" offsetTop={this.height}>
+          <Cell
+            center
+            style={{ backgroundColor: '#F7F8F9' }}
+            v-slots={{
+              title: () => (
+                <div
+                  class={styles.searchTime}
+                  onClick={() => {
+                    this.timeStatus = true
+                  }}
+                >
+                  <span>
+                    {(this as any).$filters.dateFormat(
+                      this.currentDate,
+                      'YYYY-MM'
+                    )}
+                  </span>
+                  <Icon
+                    classPrefix="iconfont"
+                    name="down"
+                    size={12}
+                    color="var(--van-primary)"
+                  />
+                </div>
+              ),
+              value: () => {
+                if (this.type === 'buy') {
+                  return (
+                    <div
+                      class={styles.searchType}
+                      onClick={() => {
+                        this.typeStatus = true
+                      }}
+                    >
+                      <span>{this.searchName}</span>
+                      <Icon
+                        classPrefix="iconfont"
+                        name="down"
+                        size={12}
+                        color="var(--van-primary)"
+                      />
+                    </div>
+                  )
+                }
+                return null
+              }
+            }}
+          ></Cell>
+        </Sticky>
+        {this.dataShow ? (
+          <List
+            loading={this.loading}
+            finished={this.finished}
+            finishedText=" "
+            class={[styles.list]}
+            onLoad={this.getList}
+          >
+            {this.list.map((item: any) => (
+              <CellGroup
+                border={false}
+                onClick={() => {
+                  this.onDetail(item)
+                }}
+              >
+                <Cell
+                  title={dayjs(item.createTime).format('YYYY-MM-DD HH:mm')}
+                  value={
+                    this.type === 'buy'
+                      ? orderType[item.status]
+                      : item.operateReason
+                  }
+                  valueClass={styles.tradeType}
+                />
+                <Cell
+                  border={false}
+                  v-slots={{
+                    title: () => (
+                      <div class={styles.title}>
+                        <span>{item.orderName}</span>
+                        <span class={styles.desc}>
+                          {goodsType[item.orderType]}
+                        </span>
+                      </div>
+                    ),
+                    default: () => (
+                      <div class={styles.content}>
+                        <span class={styles.price}>
+                          ¥
+                          {this.type === 'buy'
+                            ? (this as any).$filters.moneyFormat(
+                                item.actualPrice
+                              )
+                            : (this as any).$filters.moneyFormat(
+                                item.actualAmount
+                              )}
+                        </span>
+                      </div>
+                    )
+                  }}
+                />
+                {item.status === 'PAYING' || item.status === 'WAIT_PAY' ? (
+                  <div class={styles.btnList}>
+                    <Button
+                      size="small"
+                      round
+                      onClick={(e: any) => {
+                        e.stopPropagation()
+                        this.onCancelPay(item)
+                      }}
+                    >
+                      取消订单
+                    </Button>
+                    <Button
+                      size="small"
+                      round
+                      type="primary"
+                      onClick={(e: any) => {
+                        e.stopPropagation()
+                        this.onPay(item)
+                      }}
+                    >
+                      继续支付
+                    </Button>
+                  </div>
+                ) : null}
+              </CellGroup>
+            ))}
+          </List>
+        ) : (
+          <ColResult
+            btnStatus={false}
+            classImgSize="SMALL"
+            tips={this.type === 'buy' ? '暂无购买记录' : '暂无退款记录'}
+          />
+        )}
+
+        <Popup
+          v-model:show={this.timeStatus}
+          position="bottom"
+          round
+          closeOnPopstate
+        >
+          <DatetimePicker
+            type="year-month"
+            v-model={this.currentDate}
+            formatter={formatterDate}
+            onCancel={() => {
+              this.timeStatus = false
+            }}
+            onConfirm={this.onConfirm}
+          />
+        </Popup>
+
+        <ActionSheet
+          v-model:show={this.typeStatus}
+          actions={this.actions}
+          closeOnClickAction
+          cancelText="取消"
+          onSelect={this.onSelect}
+          onCancel={() => {
+            this.typeStatus = false
+          }}
+        />
+      </div>
+    )
+  }
+})

+ 299 - 0
src/tenant/trade/tradeOrder.ts

@@ -0,0 +1,299 @@
+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':
+      {
+        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 'TENANT_ALBUM':
+      {
+        try {
+          const res = await getTenantAlbumDetail(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 getTenantAlbumDetail = async (id: any) => {
+  try {
+    const res = await request.post(
+      `${apiSuffix}/userTenantAlbumRecord/detail`,
+      {
+        data: { albumId: 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 // v2 类型的订单才会用
+  } = 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 = []
+  orderStatus.orderObject.paymentConfig = paymentConfig
+  try {
+    orderDetailList.forEach(async (item: any) => {
+      await formatOrderDetail(item, {
+        couponAmount,
+        discountPrice
+      })
+    })
+    callBack && callBack()
+  } catch {
+    //
+  }
+}

+ 34 - 32
src/views/404/index.tsx

@@ -1,32 +1,34 @@
-import { defineComponent } from 'vue'
-import styles from './index.module.less'
-import img404 from '@/common/images/404.png'
-import { Button, Image } from 'vant'
-import { postMessage } from '@/helpers/native-message'
-import { browser } from '@/helpers/utils'
-
-export default defineComponent({
-  name: 'NotFound',
-  render() {
-    return (
-      <div class={styles.f404}>
-        <Image src={img404} />
-        <p>页面找不到了</p>
-        <Button
-          type="primary"
-          plain
-          round
-          onClick={() => {
-            if (browser().isApp) {
-              postMessage({ api: 'back' })
-            } else {
-              this.$router.back()
-            }
-          }}
-        >
-          返回
-        </Button>
-      </div>
-    )
-  }
-})
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import img404 from '@/common/images/404.png'
+import img404Tenant from '@/components/col-result/images/notFond_tenant.png'
+import { Button, Image } from 'vant'
+import { postMessage } from '@/helpers/native-message'
+import { browser } from '@/helpers/utils'
+import { state } from '@/state'
+
+export default defineComponent({
+  name: 'NotFound',
+  render() {
+    return (
+      <div class={styles.f404}>
+        <Image src={state.projectType === 'tenant' ? img404Tenant : img404} />
+        <p>页面找不到了</p>
+        <Button
+          type="primary"
+          plain
+          round
+          onClick={() => {
+            if (browser().isApp) {
+              postMessage({ api: 'back' })
+            } else {
+              this.$router.back()
+            }
+          }}
+        >
+          返回
+        </Button>
+      </div>
+    )
+  }
+})

+ 13 - 4
src/views/adapay/pay-define/index.tsx

@@ -5,6 +5,7 @@ import { Button, Cell, CellGroup, Icon, Toast } from 'vant'
 import { defineComponent, onMounted, reactive } from 'vue'
 import { useRoute } from 'vue-router'
 import styles from './index.module.less'
+import { state as baseState } from '@/state'
 
 export default defineComponent({
   name: 'pay-define',
@@ -126,13 +127,15 @@ export default defineComponent({
             if ('9000' == data.resultCode) {
               window.location.replace(
                 location.origin +
-                  '/tenant/#/tradeDetail?orderNo=' +
+                  +baseState.payBackPath +
+                  '#/tradeDetail?orderNo=' +
                   state.orderNo
               )
             } else {
               window.location.replace(
                 location.origin +
-                  '/tenant/#/tradeDetail?orderNo=' +
+                  +baseState.payBackPath +
+                  '#/tradeDetail?orderNo=' +
                   state.orderNo
               )
             }
@@ -165,14 +168,20 @@ export default defineComponent({
             res.err_msg == 'get_brand_wcpay_request:fail'
           ) {
             window.location.replace(
-              location.origin + '/tenant/#/tradeDetail?orderNo=' + state.orderNo
+              location.origin +
+                baseState.payBackPath +
+                '#/tradeDetail?orderNo=' +
+                state.orderNo
             )
           } else {
             // 使用以上方式判断前端返回,微信团队郑重提示:
             //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
             // alert('支付成功')
             window.location.replace(
-              location.origin + '/tenant/#/tradeDetail?orderNo=' + state.orderNo
+              location.origin +
+                baseState.payBackPath +
+                '#/tradeDetail?orderNo=' +
+                state.orderNo
             )
           }
         }

+ 17 - 5
src/views/adapay/pay-result/index.tsx

@@ -5,6 +5,7 @@ import { Cell, CellGroup, Dialog, Icon, Loading, Toast } from 'vant'
 import { defineComponent, onMounted, reactive } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
 import styles from './index.module.less'
+import { state as baseState } from '@/state'
 
 export default defineComponent({
   name: 'pay-result',
@@ -97,7 +98,10 @@ export default defineComponent({
         console.log(e)
         // 接口报错也跳转到支付回调页
         window.location.replace(
-          location.origin + '/tenant/#/tradeDetail?orderNo=' + state.orderNo
+          location.origin +
+            baseState.payBackPath +
+            '#/tradeDetail?orderNo=' +
+            state.orderNo
         )
       }
     }
@@ -122,13 +126,15 @@ export default defineComponent({
             if ('9000' == data.resultCode) {
               window.location.replace(
                 location.origin +
-                  '/tenant/#/tradeDetail?orderNo=' +
+                  baseState.payBackPath +
+                  '#/tradeDetail?orderNo=' +
                   state.orderNo
               )
             } else {
               window.location.replace(
                 location.origin +
-                  '/tenant/#/tradeDetail?orderNo=' +
+                  baseState.payBackPath +
+                  '#/tradeDetail?orderNo=' +
                   state.orderNo
               )
             }
@@ -208,14 +214,20 @@ export default defineComponent({
             res.err_msg == 'get_brand_wcpay_request:fail'
           ) {
             window.location.replace(
-              location.origin + '/tenant/#/tradeDetail?orderNo=' + state.orderNo
+              location.origin +
+                baseState.payBackPath +
+                '#/tradeDetail?orderNo=' +
+                state.orderNo
             )
           } else {
             // 使用以上方式判断前端返回,微信团队郑重提示:
             //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
             // alert('支付成功')
             window.location.replace(
-              location.origin + '/tenant/#/tradeDetail?orderNo=' + state.orderNo
+              location.origin +
+                baseState.payBackPath +
+                '#/tradeDetail?orderNo=' +
+                state.orderNo
             )
           }
         }

+ 5 - 3
src/views/adapay/payment/index.tsx

@@ -22,7 +22,7 @@ export default defineComponent({
     }
   },
   emits: ['backOut', 'close', 'confirm', 'update:modelValue'],
-  setup(props, { slots, attrs, emit }) {
+  setup(props, { emit }) {
     const router = useRouter()
     const state = reactive({
       payType: 'wx',
@@ -35,10 +35,12 @@ export default defineComponent({
         confirmButtonText: '继续付款',
         cancelButtonText: '放弃'
       })
-        .then(() => {})
+        .then(() => {
+          //
+        })
         .catch(async () => {
           // this.onCancel()
-          // useEventTracking('取消支付')
+          useEventTracking('取消支付')
           await onCancel()
           emit('close')
           useEventTracking('取消支付')

+ 122 - 119
src/views/article-center/help-center.tsx

@@ -1,119 +1,122 @@
-import { state } from '@/state'
-import request from '@/helpers/request'
-import { Cell, List, Sticky } from 'vant'
-import { defineComponent } from 'vue'
-import ColSearch from '@/components/col-search'
-import ColResult from '@/components/col-result'
-import { useEventTracking } from '@/helpers/hooks'
-
-export default defineComponent({
-  name: 'help-center',
-  data() {
-    const query = this.$route.query
-    const title = query.catalogType == '2' ? '公告列表' : '帮助中心'
-    document.title = title
-
-    // 处理兼容性问题
-    if (query.mode === 'accompany') {
-      query.platformType = 'ANALYSIS'
-    }
-    return {
-      list: [],
-      dataShow: true, // 判断是否有数据
-      dataLoading: false,
-      loading: false,
-      finished: false,
-      // 1:帮助中心,2:公告管理
-      params: {
-        catalogIds: query.catalogType || 1,
-        title: '',
-        status: 1,
-        // STUDENT:学生 TEACHER:老师 ANALYSIS:智能陪练
-        catalogType: query.platformType || state.platformType,
-        page: 1,
-        rows: 20
-      }
-    }
-  },
-  mounted(){
-    useEventTracking('帮助中心')
-  },
-  methods: {
-    async getList() {
-      try {
-        if (this.dataLoading) {
-          return
-        }
-        this.dataLoading = true
-        let params = this.params
-        const res = await request.post('/api-cms/helpCenterContent/list', {
-          data: {
-            ...params
-          }
-        })
-        this.dataLoading = false
-        this.loading = false
-        const result = res.data || {}
-        // 处理重复请求数据
-        if (this.list.length > 0 && result.pageNo === 1) {
-          return
-        }
-        this.list = this.list.concat(result.rows || [])
-        this.finished = result.pageNo >= result.totalPage
-        this.params.page = result.pageNo + 1
-        this.dataShow = this.list.length > 0
-      } catch {
-        this.dataShow = false
-        this.finished = true
-      }
-    },
-    onSearch(val: string) {
-      this.params.title = val
-      this.params.page = 1
-      this.list = []
-      this.dataShow = true // 判断是否有数据
-      this.loading = false
-      this.finished = false
-      this.getList()
-    },
-    onDetail(item: any) {
-      this.$router.push({
-        path: 'helpCenterDetail',
-        query: {
-          id: item.id,
-          catalogType: this.params.catalogType
-        }
-      })
-    }
-  },
-  render() {
-    return (
-      <div>
-        <Sticky offsetTop={0} position="top" class={'mb12'}>
-          <ColSearch onSearch={this.onSearch} />
-        </Sticky>
-        {this.dataShow ? (
-          <List
-            v-model:loading={this.loading}
-            finished={this.finished}
-            finishedText="没有更多了"
-            onLoad={this.getList}
-          >
-            {this.list.map((item: any) => (
-              <Cell
-                title={item.title}
-                titleClass={'van-ellipsis'}
-                isLink
-                onClick={() => {
-                  this.onDetail(item)
-                }}
-              />
-            ))}
-          </List>
-        ) : (
-          <ColResult btnStatus={false} classImgSize="SMALL" tips="暂无内容" />
-        )}
-      </div>
-    )
-  }
-})
+import { state } from '@/state'
+import request from '@/helpers/request'
+import { Cell, List, Sticky } from 'vant'
+import { defineComponent } from 'vue'
+import ColSearch from '@/components/col-search'
+import ColResult from '@/components/col-result'
+import { useEventTracking } from '@/helpers/hooks'
+
+export default defineComponent({
+  name: 'help-center',
+  data() {
+    const query = this.$route.query
+    const title = query.catalogType == '2' ? '公告列表' : '帮助中心'
+    document.title = title
+
+    // 处理兼容性问题
+    if (query.mode === 'accompany') {
+      query.platformType = 'ANALYSIS'
+    }
+    return {
+      list: [],
+      dataShow: true, // 判断是否有数据
+      dataLoading: false,
+      loading: false,
+      finished: false,
+      // 1:帮助中心,2:公告管理
+      params: {
+        catalogIds: query.catalogType || 1,
+        title: '',
+        status: 1,
+        // STUDENT:学生 TEACHER:老师 ANALYSIS:智能陪练
+        catalogType: query.platformType || state.platformType,
+        page: 1,
+        rows: 20
+      }
+    }
+  },
+  mounted() {
+    useEventTracking('帮助中心')
+  },
+  methods: {
+    async getList() {
+      try {
+        if (this.dataLoading) {
+          return
+        }
+        this.dataLoading = true
+        const params = this.params
+        const res = await request.post('/api-cms/helpCenterContent/list', {
+          data: {
+            ...params
+          }
+        })
+        this.dataLoading = false
+        this.loading = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (this.list.length > 0 && result.pageNo === 1) {
+          return
+        }
+        this.list = this.list.concat(result.rows || [])
+        this.finished = result.pageNo >= result.totalPage
+        this.params.page = result.pageNo + 1
+        this.dataShow = this.list.length > 0
+      } catch {
+        this.dataShow = false
+        this.finished = true
+      }
+    },
+    onSearch(val: string) {
+      this.params.title = val
+      this.params.page = 1
+      this.list = []
+      this.dataShow = true // 判断是否有数据
+      this.loading = false
+      this.finished = false
+      this.getList()
+    },
+    onDetail(item: any) {
+      this.$router.push({
+        path: 'helpCenterDetail',
+        query: {
+          id: item.id,
+          catalogType: this.params.catalogType
+        }
+      })
+    }
+  },
+  render() {
+    return (
+      <div>
+        <Sticky offsetTop={0} position="top" class={'mb12'}>
+          <ColSearch
+            type={state.projectType === 'tenant' ? 'tenant' : 'person'}
+            onSearch={this.onSearch}
+          />
+        </Sticky>
+        {this.dataShow ? (
+          <List
+            v-model:loading={this.loading}
+            finished={this.finished}
+            finishedText="没有更多了"
+            onLoad={this.getList}
+          >
+            {this.list.map((item: any) => (
+              <Cell
+                title={item.title}
+                titleClass={'van-ellipsis'}
+                isLink
+                onClick={() => {
+                  this.onDetail(item)
+                }}
+              />
+            ))}
+          </List>
+        ) : (
+          <ColResult btnStatus={false} classImgSize="SMALL" tips="暂无内容" />
+        )}
+      </div>
+    )
+  }
+})

+ 29 - 40
src/views/order-detail/index.tsx

@@ -19,7 +19,12 @@ import Payment from './payment'
 import UrlPayment from '../adapay/payment'
 import ColHeader from '@/components/col-header'
 import { state } from '@/state'
-import { orderInfos, orderStatus, resestState } from './orderStatus'
+import {
+  orderInfos,
+  orderStatus,
+  orderTenantInfos,
+  resestState
+} from './orderStatus'
 import OrderVideo from './order-video'
 import OrderLive from './order-live'
 import OrderPractice from './order-practice'
@@ -33,6 +38,7 @@ import UseCoupon from './use-coupons'
 import OrderAlbum from './order-album'
 import { useRect } from '@vant/use'
 import QrcodePayment from './qrcode-payment'
+import OrderTennatAlbum from './order-tennat-album'
 
 export default defineComponent({
   name: 'order-detail',
@@ -53,10 +59,10 @@ export default defineComponent({
       exists: false, // 是否签署过用户注册协议
       bottomHeight: 0,
       paymentVendor: '', //支付厂商
-      paymentVersion: '', // 支付版本,可用值:V1 老版,V2 新版
+      paymentVersion: 'V1', // 支付版本,可用值:V1 老版,V2 新版
       showQrcode: false,
       orderTimer: null as any,
-      qrCodeUrl: 'https://m.baidu.com',
+      qrCodeUrl: '',
       pay_channel: '',
       orderInfo: {} as any,
       config: {} as any, // 支付信息
@@ -149,8 +155,14 @@ export default defineComponent({
     async getOrderPayType() {
       try {
         const orderObject = orderStatus.orderObject
-        const bizId =
+        let bizId =
           orderObject.orderList.length > 0 ? orderObject.orderList[0].id : ''
+        if (orderObject.orderType === 'PRACTICE') {
+          bizId =
+            orderObject.orderList.length > 0
+              ? orderObject.orderList[0].teacherId
+              : ''
+        }
         const { data } = await request.post(
           state.platformApi + '/userOrder/orderPayType',
           {
@@ -163,7 +175,6 @@ export default defineComponent({
             }
           }
         )
-        console.log(data, 'data')
         this.paymentVersion = data.paymentVersion || 'V1'
         this.paymentVendor = data.paymentVendor
       } catch {
@@ -193,6 +204,8 @@ export default defineComponent({
       // 判断是否有订单号
       if (orderStatus.orderObject.orderNo) {
         this.paymentStatus = true
+        this.orderInfo = orderStatus.orderObject.paymentConfig || {}
+        this.orderNo = orderStatus.orderObject.paymentConfig.orderNo
         return
       }
 
@@ -200,8 +213,7 @@ export default defineComponent({
       try {
         const orderObject = orderStatus.orderObject
 
-        console.log(this.paymentVendor, '12')
-        if (this.paymentVersion === 'V2') {
+        if (this.paymentVersion === 'V1') {
           const url =
             state.platformType === 'TEACHER'
               ? '/api-teacher/userOrder/executeOrder'
@@ -237,39 +249,13 @@ export default defineComponent({
             state.platformType === 'TEACHER'
               ? '/api-teacher/userOrder/executeOrder/v2'
               : '/api-student/userOrder/executeOrder/v2'
-          const orders: any = []
-          this.orderList.forEach((item: any) => {
-            const params = {
-              goodType: item.orderType,
-              goodName: item.goodsName,
-              goodNum: 1,
-              bizContent: {}
-            }
-            if (item.orderType === 'VIP') {
-              params.bizContent = item.id
-            } else if (item.orderType === 'MUSIC') {
-              params.bizContent = {
-                musicSheetId: item.id,
-                actualPrice: item.actualPrice || 0,
-                clientType: state.platformType
-              }
-            } else if (item.orderType === 'ALBUM') {
-              params.bizContent = {
-                musicSheetId: item.id,
-                actualPrice: item.actualPrice || 0,
-                clientType: state.platformType
-              }
-            }
-            orders.push(params)
-          })
-
           const res = await request.post(url, {
             data: {
               activityId: orderObject.activityId || null,
               // bizId: '',
               couponIds: orderObject.couponId,
               // currentPrice: 0,
-              goodsInfos: [...orders],
+              goodsInfos: [...orderTenantInfos()],
               orderDesc: orderObject.orderDesc,
               orderName: orderObject.orderName,
               orderType: orderObject.orderType,
@@ -334,10 +320,10 @@ export default defineComponent({
       })
       if (val.payCode === 'payResult') {
         window.location.href =
-          window.location.origin + '/tenant.html#/payResult?' + params
+          window.location.origin + state.payBackPath + '#/payResult?' + params
       } else {
         this.qrCodeUrl =
-          window.location.origin + '/tenant.html#/payDefine?' + params
+          window.location.origin + state.payBackPath + '#/payDefine?' + params
         this.showQrcode = true
         this.paymentStatus = false
 
@@ -417,6 +403,8 @@ export default defineComponent({
                 return <OrderActive item={item} />
               } else if (item.orderType === 'ALBUM') {
                 return <OrderAlbum item={item} />
+              } else if (item.orderType === 'TENANT_ALBUM') {
+                return <OrderTennatAlbum item={item} />
               }
             })}
 
@@ -486,7 +474,7 @@ export default defineComponent({
           style={{ minHeight: '30%' }}
         >
           {/* 判断类型使用什么去支付 */}
-          {this.paymentVersion === 'V2' ? (
+          {this.paymentVersion === 'V1' ? (
             <Payment
               v-model={this.paymentStatus}
               orderInfo={orderStatus.orderObject}
@@ -494,9 +482,10 @@ export default defineComponent({
             />
           ) : (
             <UrlPayment
-              // v-model={this.paymentStatus}
-              paymentConfig={orderStatus.orderObject}
-              // onBackOut={this.onBackOut}
+              paymentConfig={{
+                ...orderStatus.orderObject,
+                orderNo: this.orderNo
+              }}
               onClose={() => (this.paymentStatus = false)}
               onBackOut={this.onBackOut}
               onConfirm={(val: any) => this.onConfirm(val)}

+ 82 - 0
src/views/order-detail/order-tennat-album/index.module.less

@@ -0,0 +1,82 @@
+.album {
+  margin-bottom: 12px;
+  padding: 10px;
+  background-color: var(--music-list-item-background-color);
+  border-radius: 10px;
+  display: flex;
+  position: relative;
+
+  .albumType {
+    position: absolute;
+    left: 10px;
+    top: 10px;
+    background: #FE2451;
+    border-radius: 10px 0px 10px 0px;
+    font-size: 12px;
+    padding: 0 6px;
+    line-height: 20px;
+    color: #ffffff;
+  }
+
+  .img {
+    width: 94px;
+    height: 94px;
+    margin-right: 18px;
+    position: relative;
+
+    >img,
+    >div {
+      position: absolute;
+      border-radius: 10px;
+      overflow: hidden;
+    }
+  }
+
+  .content {
+    flex: 1;
+
+    >h4 {
+      font-size: 14px;
+      color: #131415;
+      line-height: 24px;
+    }
+
+    >p {
+      margin: 4px 0 6px;
+      font-size: 12px;
+      color: #777777;
+      line-height: 18px;
+    }
+
+    .musicNum {
+      font-size: 12px;
+      color: #FE2451;
+      border-radius: 4px;
+      border: 1px solid #FE2451;
+      padding: 1px 6px;
+    }
+  }
+}
+
+.footer {
+  margin-top: 11px;
+  display: flex;
+
+  >div {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 12px;
+    color: var(--music-list-item-mate-color);
+    margin-right: 18px;
+
+    .icon {
+      margin-right: 5px;
+    }
+
+    span {
+      display: block;
+      margin-top: 1px;
+    }
+  }
+}

+ 32 - 0
src/views/order-detail/order-tennat-album/index.tsx

@@ -0,0 +1,32 @@
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import { Image } from 'vant'
+import { useRouter } from 'vue-router'
+
+export default defineComponent({
+  name: 'OrderMusic',
+  props: {
+    item: {
+      type: Object,
+      default: {}
+    }
+  },
+  render() {
+    const item = this.item
+    console.log(item)
+    return (
+      <div class={styles.album}>
+        <Image class={styles.img} src={item.coverImg} />
+
+        <span class={styles.albumType}>{item.purchaseCycle}个月</span>
+
+        <div class={styles.content}>
+          <h4 class="van-ellipsis">{item.name}</h4>
+          <p class="van-multi-ellipsis--l2">{item.describe}</p>
+
+          <span class={styles.musicNum}>共{item.musicNum}首</span>
+        </div>
+      </div>
+    )
+  }
+})

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

@@ -13,6 +13,7 @@ type orderType =
   | 'PIANO_ROOM'
   | 'ACTI_REGIST'
   | 'ALBUM'
+  | 'TENANT_ALBUM'
   | ''
 
 const original = () => {
@@ -36,7 +37,7 @@ const original = () => {
       activityId: '' as any, // 活动编号
       couponId: '' as string, // 优惠券编号
       discountPrice: 0 as number // 优惠
-    }
+    } as any
     // orderObject: {
     //   orderNo: '',
     //   actualPrice: 28,
@@ -126,6 +127,52 @@ export const orderInfos = () => {
       params.bizContent = {
         activityId: item.activityId
       }
+    } else if (item.orderType === 'TENANT_ALBUM') {
+      params.bizContent = {
+        musicSheetId: item.id,
+        actualPrice: item.actualPrice || 0,
+        clientType: state.platformType
+      }
+    }
+    return params
+  })
+}
+
+export const orderTenantInfos = () => {
+  // 商品列表
+  const orderList = orderStatus.orderObject.orderList || []
+  return orderList.map((item: any) => {
+    const params: any = {
+      goodType: item.orderType,
+      goodName: item.goodsName,
+      goodNum: 1,
+      bizContent: {}
+    }
+    if (item.orderType === 'VIP') {
+      params.bizContent = item.id
+    } else if (item.orderType === 'MUSIC') {
+      params.bizContent = {
+        musicSheetId: item.id,
+        actualPrice: item.actualPrice || 0,
+        clientType: state.platformType
+      }
+    } else if (item.orderType === 'ALBUM') {
+      params.bizContent = {
+        musicSheetId: item.id,
+        actualPrice: item.actualPrice || 0,
+        clientType: state.platformType
+      }
+    } else if (item.orderType === 'TENANT_ALBUM') {
+      params.bizContent = {
+        tenantAlbumId: item.id,
+        actualPrice: item.actualPrice || 0,
+        buyNumber: 1,
+        buyMultiple: item.purchaseCycle / 6,
+        clientType: state.platformType
+      }
+      params.bizId = item.id
+      params.buyNumber = 1
+      params.buyMultiple = item.purchaseCycle / 6
     }
     return params
   })

+ 155 - 148
src/views/order-detail/userAuth/index.tsx

@@ -1,148 +1,155 @@
-import ColField from '@/components/col-field'
-import ColFieldGroup from '@/components/col-field-group'
-import ColHeader from '@/components/col-header'
-import request from '@/helpers/request'
-import { verifyIdCard } from '@/helpers/toolsValidate'
-import { postMessage } from '@/helpers/native-message'
-import { state } from '@/state'
-import { Button, CellGroup, Checkbox, Field, Form, Icon, Toast } from 'vant'
-import { defineComponent } from 'vue'
-import activeButtonIcon from '@common/images/icon_checkbox.png'
-import inactiveButtonIcon from '@common/images/icon_checkbox_default.png'
-import styles from './index.module.less'
-
-export default defineComponent({
-  name: 'UserAuth',
-  props: {
-    onSuccess: {
-      // 实名成功
-      type: Function,
-      default: () => {}
-    },
-    exists: {
-      type: Boolean,
-      default: false
-    },
-    hideHeader: {
-      type: Boolean,
-      default: false
-    }
-  },
-  data() {
-    return {
-      form: {
-        realName: '',
-        idCardNo: ''
-      },
-      checked: false
-    }
-  },
-  mounted() {
-    // exists
-    this.checked = this.checked || this.exists
-    // 初始化数据
-    const users = state.user.data
-    this.form.realName = users?.realName
-    // this.form.idCardNo = users?.idCardNo
-  },
-  methods: {
-    async onSubmit() {
-      try {
-        if (!this.checked) {
-          Toast('请先阅读并同意《用户注册协议》')
-          return
-        }
-        const url =
-          state.platformType === 'STUDENT'
-            ? '/api-student/student/realNameAuth'
-            : '/api-teacher/teacher/realNameAuth'
-        await request.post(url, {
-          data: {
-            ...this.form,
-            contract: true,
-            save: true
-          }
-        })
-        Toast('实名成功')
-        state.user.data.realName = this.form.realName
-        state.user.data.idCardNo = this.form.idCardNo
-        setTimeout(() => {
-          this.onSuccess()
-        }, 500)
-      } catch {}
-    },
-    getContractDetail() {
-      // 查看协议
-      const client = state.platformType === 'STUDENT' ? 'student' : 'teacher'
-      postMessage({
-        api: 'openWebView',
-        content: {
-          url: `${location.origin}/${client}/#/previewProtocol`,
-          orientation: 1,
-          isHideTitle: false
-        }
-      })
-    }
-  },
-  render() {
-    return (
-      <Form class={styles.userAuth} onSubmit={this.onSubmit}>
-        {!this.hideHeader && <ColHeader title="实名认证" />}
-
-        <ColFieldGroup style={{ marginTop: '15px' }}>
-          <ColField title="姓名" required>
-            <Field
-              name="lessonName"
-              maxlength={20}
-              v-model={this.form.realName}
-              placeholder="请输入真实姓名"
-              rules={[{ required: true, message: '请输入真实姓名' }]}
-            />
-          </ColField>
-          <ColField title="证件号码" required>
-            <Field
-              name="lessonSubjectName"
-              v-model={this.form.idCardNo}
-              rules={[
-                { required: true, message: '请输入身份证号' },
-                {
-                  pattern:
-                    /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
-                  message: '请输入正确的身份证号'
-                }
-              ]}
-              placeholder="请输入身份证号"
-            />
-          </ColField>
-        </ColFieldGroup>
-        <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}>
-            《用户注册协议》
-          </span>
-        </div>
-        <div class={['btnGroup']}>
-          <Button block round type="primary" native-type="submit">
-            确定
-          </Button>
-        </div>
-      </Form>
-    )
-  }
-})
+import ColField from '@/components/col-field'
+import ColFieldGroup from '@/components/col-field-group'
+import ColHeader from '@/components/col-header'
+import request from '@/helpers/request'
+import { verifyIdCard } from '@/helpers/toolsValidate'
+import { postMessage } from '@/helpers/native-message'
+import { state } from '@/state'
+import { Button, CellGroup, Checkbox, Field, Form, Icon, Toast } from 'vant'
+import { defineComponent } from 'vue'
+import activeButtonIcon from '@common/images/icon_checkbox.png'
+import inactiveButtonIcon from '@common/images/icon_checkbox_default.png'
+import activeButtonIconTenant from '@common/images/icon_checkbox-tenant.png'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'UserAuth',
+  props: {
+    onSuccess: {
+      // 实名成功
+      type: Function,
+      default: () => {}
+    },
+    exists: {
+      type: Boolean,
+      default: false
+    },
+    hideHeader: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      form: {
+        realName: '',
+        idCardNo: ''
+      },
+      checked: false
+    }
+  },
+  mounted() {
+    // exists
+    this.checked = this.checked || this.exists
+    // 初始化数据
+    const users = state.user.data
+    this.form.realName = users?.realName
+    // this.form.idCardNo = users?.idCardNo
+  },
+  methods: {
+    async onSubmit() {
+      try {
+        if (!this.checked) {
+          Toast('请先阅读并同意《用户注册协议》')
+          return
+        }
+        const url =
+          state.platformType === 'STUDENT'
+            ? '/api-student/student/realNameAuth'
+            : '/api-teacher/teacher/realNameAuth'
+        await request.post(url, {
+          data: {
+            ...this.form,
+            contract: true,
+            save: true
+          }
+        })
+        Toast('实名成功')
+        state.user.data.realName = this.form.realName
+        state.user.data.idCardNo = this.form.idCardNo
+        setTimeout(() => {
+          this.onSuccess()
+        }, 500)
+      } catch {}
+    },
+    getContractDetail() {
+      // 查看协议
+      const client = state.platformType === 'STUDENT' ? 'student' : 'teacher'
+      postMessage({
+        api: 'openWebView',
+        content: {
+          url: `${location.origin}/${client}/#/previewProtocol`,
+          orientation: 1,
+          isHideTitle: false
+        }
+      })
+    }
+  },
+  render() {
+    return (
+      <Form class={styles.userAuth} onSubmit={this.onSubmit}>
+        {!this.hideHeader && <ColHeader title="实名认证" />}
+
+        <ColFieldGroup style={{ marginTop: '15px' }}>
+          <ColField title="姓名" required>
+            <Field
+              name="lessonName"
+              maxlength={20}
+              v-model={this.form.realName}
+              placeholder="请输入真实姓名"
+              rules={[{ required: true, message: '请输入真实姓名' }]}
+            />
+          </ColField>
+          <ColField title="证件号码" required>
+            <Field
+              name="lessonSubjectName"
+              v-model={this.form.idCardNo}
+              rules={[
+                { required: true, message: '请输入身份证号' },
+                {
+                  pattern:
+                    /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
+                  message: '请输入正确的身份证号'
+                }
+              ]}
+              placeholder="请输入身份证号"
+            />
+          </ColField>
+        </ColFieldGroup>
+        <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}>
+            《用户注册协议》
+          </span>
+        </div>
+        <div class={['btnGroup']}>
+          <Button block round type="primary" native-type="submit">
+            确定
+          </Button>
+        </div>
+      </Form>
+    )
+  }
+})

二進制
src/views/tenantStudentRejest/images/studentSuccess.png


+ 126 - 0
src/views/tenantStudentRejest/index.module.less

@@ -189,3 +189,129 @@
   margin-left: -151px;
   margin-top: 24px;
 }
+
+.showWrap {
+  width: 264px;
+  height: 326px;
+  position: absolute;
+  top: 212px;
+  left: 50%;
+  margin-left: -132px;
+  background-color: #fff;
+  z-index: 1001;
+  box-shadow: 1px 1px 9px 0px rgba(149, 145, 145, 0.07);
+  border-radius: 12px 12px;
+  position: relative;
+  .showWrapTop {
+    width: 264px;
+    height: 130px;
+    position: relative;
+    top: -23px;
+  }
+  h2 {
+    margin-top: 3px;
+    height: 25px;
+    font-size: 18px;
+    font-weight: 500;
+    color: #333333;
+    line-height: 25px;
+    text-align: center;
+    margin-bottom: 11px;
+  }
+  h4 {
+    font-size: 15px;
+    font-weight: 500;
+    color: #333333;
+    text-align: center;
+    margin-bottom: 11px;
+    span {
+      color: #fe2451;
+      font-weight: bold;
+    }
+  }
+  p {
+    font-size: 14px;
+    font-weight: 400;
+    color: #777777;
+    line-height: 20px;
+    text-align: center;
+    margin-bottom: 22px;
+  }
+  .downApp {
+    width: 200px;
+    height: 40px;
+    background: #fe2451;
+    border-radius: 39px;
+    font-size: 18px;
+    font-family: PingFangSC-Medium, PingFang SC;
+    font-weight: 500;
+    color: #ffffff;
+    text-align: center;
+    line-height: 40px;
+    position: relative;
+    left: 50%;
+    margin-left: -100px;
+  }
+}
+.secondWrap {
+  width: 294px;
+  height: 182px;
+  background: #ffffff;
+  border-radius: 12px;
+  padding: 15px 21px 21px;
+  h2 {
+    height: 24px;
+    font-size: 17px;
+    font-weight: 600;
+    color: #333333;
+    line-height: 24px;
+    margin-bottom: 10px;
+    text-align: center;
+  }
+  p {
+    font-size: 15px;
+    font-weight: 400;
+    color: #666666;
+    line-height: 24px;
+    text-align: center;
+    margin-bottom: 3px;
+    span {
+      color: #fe2451;
+      font-weight: bold;
+    }
+  }
+  .buttonWrap {
+    margin-top: 24px;
+    box-sizing: border-box;
+    width: 100%;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: center;
+    .closeBtn {
+      width: 120px;
+      height: 40px;
+      background: #ffffff;
+      border-radius: 20px;
+      border: 1px solid #dbdbdb;
+      line-height: 40px;
+      font-size: 14px;
+      font-weight: 400;
+      color: #333333;
+      text-align: center;
+      margin-right: 12px;
+    }
+    .submitBtn {
+      width: 120px;
+      height: 40px;
+      background: #fe2451;
+      border-radius: 20px;
+      border: 1px solid #dbdbdb;
+      line-height: 40px;
+      font-size: 14px;
+      font-weight: 400;
+      color: #fff;
+      text-align: center;
+    }
+  }
+}

+ 111 - 13
src/views/tenantStudentRejest/index.tsx

@@ -1,6 +1,6 @@
 import ColHeader from '@/components/col-header'
 import ColSearch from '@/components/col-search'
-import { Sticky, Image, List, Popup, Icon, Area, Field, Form, CellGroup, Button, Toast, Picker, DatetimePicker  } from 'vant'
+import { Sticky, Image, List, Popup, Icon, Area, Field, Form, CellGroup, Button, Toast, Picker, DatetimePicker,Overlay,Dialog } from 'vant'
 import { defineComponent, onMounted, reactive } from 'vue'
 import styles from './index.module.less'
 import bg from './images/bg.png'
@@ -12,6 +12,7 @@ import studentText from './images/studentText.png'
 import { useRoute } from 'vue-router'
 import icon_arrow from './images/icon_arrow.png'
 import rejectBtn from './images/rejectBtn.png'
+import studentSuccess from './images/studentSuccess.png'
 import request from '@/helpers/request'
 import dayjs from 'dayjs'
 export default defineComponent({
@@ -23,14 +24,16 @@ export default defineComponent({
       name: '',
       phone: '',
       subjectId: '',
-      tenantId: '',
+      subjectName:'',
       birthdate: '',
       code: '',
-      genderName:''
+      genderName:'',
+      tenantId:route.query.tenantId,
     });
 
     const data = reactive({
-      schoolName: route.query.schoolName || '',
+      schoolName: route.query.name || '',
+      id:route.query.tenantId,
       cityName: '', // 所属城市
       showArea: false,
       checked: true,
@@ -43,9 +46,11 @@ export default defineComponent({
       openStatus: false,
       dateState: false,
       genderState:false,
-      genderList:[{text:'男',value:'1'},{text:'女',value:'0'}]
+      genderList:[{text:'男',value:'1'},{text:'女',value:'0'}],
+      showSuccess:false,
+      secondConfirm:false,
     });
-    const handleSubmit = () => {
+    const handleSubmit = async() => {
       console.log(forms, 'forms')
       if (!forms.name) {
         Toast('请输入姓名')
@@ -65,22 +70,44 @@ export default defineComponent({
       if (!forms.subjectId) {
         Toast('请选择声部')
       }
+
+        const res = await request.post('/api-tenant/open/student/save',{ data: { ... forms}})
+        console.log(res)
+      if(res.code == 200){
+        data.showSuccess = true
+      }
+
+      if(res.code == 5004){
+        data.secondConfirm = true
+      }
+
+
+
+
     }
     const getSubjectList = async () => {
       try {
-        const res = await request.get('/api-student/open/subject/subjectSelect')
-        data.subjectList = res.data || []
+        const res = await request.get('/api-tenant/open/subject/queryPage',{ data: { page: 1, rows: 9999 }})
+        data.subjectList = res.data.rows.map((item:any)=>{
+          return {
+            text:item.name,
+            value:item.id
+          }
+        }) || []
       } catch (e) {
         console.log(e)
       }
     }
     const confirmSubject = (val: any) => {
+      forms.subjectName = val.text;
+      forms.subjectId = val.value;
+      data.searchStatus = false
       console.log(val, 'confirmSubject')
     }
 
     const confirmDate = (val:any)=>{
       forms.birthdate = dayjs(val).format('YYYY-MM-DD')
-     data.genderState = false
+     data.dateState = false
     }
     onMounted(() => {
       getSubjectList()
@@ -96,7 +123,7 @@ export default defineComponent({
       data.genderState = false
     }
     /** 发送验证码 */
-    const onSendSms = () => {
+    const onSendSms = async () => {
       if (!forms.phone) {
         Toast('请输入手机号码');
         return;
@@ -105,8 +132,49 @@ export default defineComponent({
         Toast('手机号码格式不正确');
         return;
       }
-      data.imgCodeStatus = true;
+      await request.post('/api-student/code/sendSmsCode', {
+        requestType: 'form',
+        data: {
+          mobile: forms.phone,
+          type: 'LOGIN'
+        }
+      })
+      onCountDown()
+      setTimeout(() => {
+        Toast('验证码已发送')
+      }, 100)
     };
+
+    const onCountDown = ()=>{
+        data.sendMsg='60s'
+        let count = 60;
+        const timer = setInterval(() => {
+          count--;
+          data.sendMsg= `${count}s`
+          if (count <= 0) {
+           data.sendMsg='获取验证码'
+            clearInterval(timer);
+          }
+        }, 1000);
+
+    }
+
+    const downApp = ()=>{
+      window.open(location.origin + '/student/#/download')
+      data.showSuccess = false
+    }
+
+    const submitSecond = async()=>{
+      try{
+        const res = await request.post('/api-tenant/open/student/save',{ data: { ... forms,updateTenant:true}})
+        data.showSuccess=true
+        data.secondConfirm=false
+
+      }catch(e){
+        console.log(e)
+      }
+
+    }
     return () =>
       <>< div class={styles.videoClass} >
         <ColHeader
@@ -121,7 +189,7 @@ export default defineComponent({
           <img src={bg} class={styles.bgWrap} alt="" />
           <div class={styles.schoolNameWrap}>
             <img src={rejectSchool} class={styles.rejectSchool} alt="" />
-            <p>武汉星星小学</p>
+            <p>{data.schoolName}</p>
 
           </div>
           <img class={styles.centerLogo} src={centerLogo} alt="" />
@@ -233,7 +301,7 @@ export default defineComponent({
                     label="声部"
                     placeholder="请选择声部"
                     readonly
-                    v-model={data.cityName}
+                    v-model={forms.subjectName}
                     onClick={() => (data.searchStatus = true)}>
                     {{
                       button: () => (
@@ -289,6 +357,36 @@ export default defineComponent({
         >
           <Picker columns={data.genderList} onCancel={() => { data.genderState = false }} onConfirm={confirmGender}></Picker>
         </Popup>
+
+        <Overlay show={data.showSuccess}  z-index={1000}>
+          <div class={styles.showWrap}>
+           <img class={styles.showWrapTop} src={studentSuccess} alt="" />
+           <h2>恭喜您已成功登记为</h2>
+           <h4>{data.schoolName} <span>【学员】</span> </h4>
+           <p>请下载酷乐秀机构版APP进行学习</p>
+           <div class={styles.downApp} onClick={downApp}>立即下载</div>
+          </div>
+        </Overlay>
+
+
+          <Popup
+          show={data.secondConfirm}
+          position="center"
+          round
+          onClose={() => (data.secondConfirm = false)}
+          onClosed={() => (data.secondConfirm = false)}
+        >
+        <div class={styles.secondWrap}>
+          <h2>提示</h2>
+          <p>当前账号已存在 <span>【机构名称】</span> ,是否</p>
+          <p>确认更换到 <span>【机构名称】</span>吗? </p>
+          <div class={styles.buttonWrap}>
+            <div class={styles.closeBtn} onClick={()=>{data.secondConfirm = false}}> 取消</div>
+            <div  class={styles.submitBtn} onClick={submitSecond}>确定</div>
+          </div>
+        </div>
+
+        </Popup>
       </div ></>
 
   }

二進制
src/views/tenantTeacherRejest/images/checkBoxActive.png


二進制
src/views/tenantTeacherRejest/images/checkBoxDefault.png


二進制
src/views/tenantTeacherRejest/images/chioseOk.png


二進制
src/views/tenantTeacherRejest/images/teacherSuccess.png


+ 64 - 0
src/views/tenantTeacherRejest/index.module.less

@@ -189,3 +189,67 @@
   margin-left: -151px;
   margin-top: 24px;
 }
+
+.showWrap {
+  width: 264px;
+  height: 326px;
+  position: absolute;
+  top: 212px;
+  left: 50%;
+  margin-left: -132px;
+  background-color: #fff;
+  z-index: 1001;
+  box-shadow: 1px 1px 9px 0px rgba(149, 145, 145, 0.07);
+  border-radius: 12px 12px;
+  position: relative;
+  .showWrapTop {
+    width: 264px;
+    height: 130px;
+    position: relative;
+    top: -25px;
+  }
+  h2 {
+    margin-top: 3px;
+    height: 25px;
+    font-size: 18px;
+    font-weight: 500;
+    color: #333333;
+    line-height: 25px;
+    text-align: center;
+    margin-bottom: 11px;
+  }
+  h4 {
+    font-size: 15px;
+    font-weight: 500;
+    color: #333333;
+    text-align: center;
+    margin-bottom: 11px;
+    span {
+      color: #fe2451;
+      font-weight: bold;
+    }
+  }
+  p {
+    font-size: 14px;
+    font-weight: 400;
+    color: #777777;
+    line-height: 20px;
+    text-align: center;
+    margin-bottom: 22px;
+  }
+  .downApp {
+    width: 200px;
+    height: 40px;
+    background: #fe2451;
+    border-radius: 39px;
+    font-size: 18px;
+    font-family: PingFangSC-Medium, PingFang SC;
+    font-weight: 500;
+    color: #ffffff;
+    text-align: center;
+    line-height: 40px;
+    position: relative;
+    left: 50%;
+    margin-left: -100px;
+  }
+}

+ 119 - 14
src/views/tenantTeacherRejest/index.tsx

@@ -1,6 +1,6 @@
 import ColHeader from '@/components/col-header'
 import ColSearch from '@/components/col-search'
-import { Sticky, Image, List, Popup, Icon, Area, Field, Form, CellGroup, Button, Toast, Picker, DatetimePicker } from 'vant'
+import { Sticky, Image, List, Popup, Icon, Area, Field, Form, CellGroup, Button, Toast, Picker, DatetimePicker, Overlay } from 'vant'
 import { defineComponent, onMounted, reactive } from 'vue'
 import styles from './index.module.less'
 import bg from './images/teacherBg.png'
@@ -12,6 +12,8 @@ import studentText from './images/studentText.png'
 import { useRoute } from 'vue-router'
 import icon_arrow from './images/icon_arrow.png'
 import rejectBtn from './images/rejectBtn.png'
+import teacherSuccess from './images/teacherSuccess.png'
+import SubjectModel from './modals/chioseSuond'
 import request from '@/helpers/request'
 import dayjs from 'dayjs'
 export default defineComponent({
@@ -24,13 +26,13 @@ export default defineComponent({
       realName: '',
       phone: '',
       subjectId: '',
-      tenantId: '',
+      tenantId: route.query.tenantId,
       birthdate: '',
       code: ''
     });
 
     const data = reactive({
-      schoolName: route.query.schoolName || '',
+      schoolName: route.query.name || '',
       cityName: '', // 所属城市
       showArea: false,
       checked: true,
@@ -41,9 +43,13 @@ export default defineComponent({
       subjectList: [],
       searchStatus: false,
       openStatus: false,
-      dateState: false
+      dateState: false,
+      showSuccess: false,
+      // selectedSubjectList: [] as any,
+      choiceSubjectIds: [] as any,
+      choiceSubjectNames: [] as any
     });
-    const handleSubmit = () => {
+    const handleSubmit = async () => {
       console.log(forms, 'forms')
       if (!forms.username) {
         Toast('请输入老师昵称')
@@ -60,14 +66,33 @@ export default defineComponent({
       if (!forms.idCardNo) {
         Toast('请输入身份证号')
       }
-      if (!forms.subjectId) {
+      if (data.choiceSubjectIds.length < 1) {
         Toast('请选择声部')
       }
+      try {
+        const res = await request.post('/api-tenant/open/teacher/submit', { data: { ...forms, subjectId: data.choiceSubjectIds.join(',') } })
+        data.showSuccess = true
+      } catch (e) {
+        console.log(e)
+      }
     }
     const getSubjectList = async () => {
       try {
-        const res = await request.get('/api-student/open/subject/subjectSelect')
-        data.subjectList = res.data || []
+        const res = await request.get('/api-tenant/open/subject/queryPage', { data: { page: 1, rows: 9999 } })
+        // const res = await request.post('/api-tenant/open/subject/queryPageTree', { data: { page: 1, rows: 9999 } })
+        data.subjectList = res.data.rows || []
+
+
+
+        /**
+         * .map((item: any) => {
+          return {
+            text: item.name,
+            value: item.id
+          }
+        })
+         *
+         */
       } catch (e) {
         console.log(e)
       }
@@ -80,12 +105,17 @@ export default defineComponent({
       forms.birthdate = dayjs(val).format('YYYY-MM-DD')
       data.dateState = false
     }
+
+
     onMounted(() => {
+      console.log(
+        route.query
+      )
       getSubjectList()
     })
 
     /** 发送验证码 */
-    const onSendSms = () => {
+    const onSendSms = async () => {
       if (!forms.phone) {
         Toast('请输入手机号码');
         return;
@@ -94,8 +124,51 @@ export default defineComponent({
         Toast('手机号码格式不正确');
         return;
       }
-      data.imgCodeStatus = true;
+      await request.post('/api-student/code/sendSmsCode', {
+        requestType: 'form',
+        data: {
+          mobile: forms.phone,
+          type: 'LOGIN'
+        }
+      })
+      onCountDown()
+      setTimeout(() => {
+        Toast('验证码已发送')
+      }, 100)
     };
+    const onCountDown = () => {
+      data.sendMsg = '60s'
+      let count = 60;
+      const timer = setInterval(() => {
+        count--;
+        data.sendMsg = `${count}s`
+        if (count <= 0) {
+          data.sendMsg = '获取验证码'
+          clearInterval(timer);
+        }
+      }, 1000);
+
+    }
+
+    const downApp = () => {
+      window.open(location.origin + '/student/#/download?type=teacher')
+      data.showSuccess = false
+    }
+
+    const onChoice = (val: any) => {
+
+      data.searchStatus = false
+      data.choiceSubjectIds = [...val]
+      const chioseSound = [] as any
+      data.subjectList.forEach((item: any) => {
+        if (data.choiceSubjectIds.indexOf(item.id) != -1) {
+          chioseSound.push(item.name)
+        }
+      })
+      data.choiceSubjectNames = [...chioseSound]
+      // data.choiceSubjectNames =
+    }
+
     return () =>
       <>< div class={styles.videoClass} >
         <ColHeader
@@ -110,7 +183,7 @@ export default defineComponent({
           <img src={bg} class={styles.bgWrap} alt="" />
           <div class={styles.schoolNameWrap}>
             <img src={rejectSchool} class={styles.rejectSchool} alt="" />
-            <p>武汉星星小学</p>
+            <p>{data.schoolName}</p>
 
           </div>
           <img class={styles.centerLogo} src={centerLogo} alt="" />
@@ -196,7 +269,7 @@ export default defineComponent({
                     label="声部"
                     placeholder="请选择声部"
                     readonly
-                    v-model={data.cityName}
+                    v-model={data.choiceSubjectNames}
                     onClick={() => (data.searchStatus = true)}>
                     {{
                       button: () => (
@@ -218,7 +291,7 @@ export default defineComponent({
             <img src={rejectBtn} onClick={() => { handleSubmit() }} class={styles.rejectBtn} alt="" />
           </div>
         </div>
-        <Popup
+        {/* <Popup
           show={data.searchStatus}
           position="bottom"
           round
@@ -228,8 +301,40 @@ export default defineComponent({
           onClosed={() => (data.openStatus = false)}
         >
           <Picker columns={data.subjectList} onCancel={() => { data.searchStatus = false }} onConfirm={confirmSubject}></Picker>
+        </Popup> */}
+
+
+        <Popup
+          show={data.searchStatus}
+          round
+          closeable
+          position="bottom"
+          style={{ height: '60%' }}
+          teleport="body"
+          onUpdate: show={val => (data.searchStatus = val)}
+        >
+          <SubjectModel
+            subjectList={data.subjectList}
+            choiceSubjectIds={data.choiceSubjectIds}
+            onChoice={onChoice}
+            single={true}
+            selectType="Checkbox"
+          />
         </Popup>
-      </div ></>
+      </div >
+
+        <Overlay show={data.showSuccess} z-index={1000}>
+          <div class={styles.showWrap}>
+            <img class={styles.showWrapTop} src={teacherSuccess} alt="" />
+            <h2>恭喜您已成功登记为</h2>
+            <h4>{data.schoolName} <span>【音乐老师】</span> </h4>
+            <p>请下载酷乐秀机构版APP进行学习</p>
+            <div class={styles.downApp} onClick={downApp}>立即下载</div>
+          </div>
+        </Overlay>
+
+
+      </>
 
   }
 

+ 110 - 0
src/views/tenantTeacherRejest/modals/chioseSuond.module.less

@@ -0,0 +1,110 @@
+.subjects {
+  .subjectName {
+    text-align: center;
+    margin-bottom: 15px;
+  }
+  padding: 15px 0 0;
+  background: #f6f8f9;
+  min-height: calc(100vh - 15px);
+  .subjectContainer {
+    min-height: calc(100vh - 95px);
+  }
+
+  .subjectMaxLength {
+    margin: 0 14px 10px;
+    background: linear-gradient(139deg, #fff6ee 0%, #ffecdd 100%) #ffffff;
+    border-radius: 10px;
+    padding: 7px 11px;
+    background: #ffffff;
+    font-size: 14px;
+    color: #ff9e5a;
+    line-height: 22px;
+  }
+  .title {
+    padding: 12px 0;
+    margin: 0 15px;
+    color: #333;
+    font-size: 16px;
+    display: flex;
+    align-items: center;
+    &::before {
+      content: ' ';
+      display: inline-block;
+      width: 3px;
+      height: 16px;
+      background: #2dc7aa;
+      border-radius: 3px;
+      margin-right: 8px;
+      vertical-align: text-bottom;
+    }
+  }
+
+  .subject-list {
+    display: flex;
+    align-items: center;
+    // justify-content: space-between;
+    // justify-content: center;
+    flex-wrap: wrap;
+    padding: 0 10px;
+
+    .subject-item {
+      position: relative;
+      width: 108px;
+      height: 108px;
+      margin-right: 5px;
+      margin-left: 5px;
+      margin-bottom: 10px;
+      border-radius: 7px;
+      overflow: hidden;
+    }
+
+    .topBg {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background: linear-gradient(
+        180deg,
+        rgba(0, 0, 0, 0) 0%,
+        rgba(0, 0, 0, 0.54) 100%
+      );
+    }
+
+    .checkbox {
+      position: absolute;
+      right: 7px;
+      top: 7px;
+    }
+
+    .name {
+      position: absolute;
+      bottom: 7px;
+      left: 7px;
+      font-size: 16px;
+      font-weight: 500;
+      color: #ffffff;
+      line-height: 22px;
+    }
+
+    :global {
+      .van-checkbox__icon,
+      .van-radio__icon {
+        height: 22px;
+        .van-icon {
+          border: 0;
+          background-color: transparent;
+        }
+      }
+      .van-checkbox__icon--checked .van-icon,
+      .van-radio__icon--checked .van-icon {
+        background-color: transparent;
+        border: transparent;
+      }
+    }
+  }
+}
+.chioseBtn {
+  width: 303px;
+  height: 50px;
+}

+ 315 - 0
src/views/tenantTeacherRejest/modals/chioseSuond.tsx

@@ -0,0 +1,315 @@
+import {
+  Button,
+  Checkbox,
+  CheckboxGroup,
+  Icon,
+  Image,
+  Loading,
+  Radio,
+  RadioGroup,
+  Sticky,
+  Toast
+} from 'vant'
+import { defineComponent, PropType } from 'vue'
+import styles from './chioseSuond.module.less'
+import checkBoxActive from '../images/checkBoxActive.png'
+import checkBoxDefault from '../images/checkBoxDefault.png'
+import ColResult from '@/components/col-result'
+import chioseOk from '../images/chioseOk.png'
+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() {
+    console.log('mounted=====>', this.selectType)
+    if (this.selectType === 'Radio') {
+      this.radio = this.choiceSubjectIds[0]
+    } else {
+
+      this.checkBox = [...this.choiceSubjectIds] as never[]
+    }
+  },
+  watch: {
+    choiceSubjectIds(val: any, oldVal) {
+      // 同步更新显示数据
+      console.log(this.choiceSubjectIds, this.checkBox, 'choiceSubjectIds')
+      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()
+        console.log(this.checkBox, 'onSelect====>')
+      } else if (this.selectType === 'Radio') {
+        this.radio = id
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles.subjects}>
+        <h2 class={styles.subjectName}>选择声部</h2>
+        <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'}>
+
+              <img src={chioseOk} class={styles.chioseBtn} alt="" onClick={() =>
+                this.onChoice(
+                  this.selectType === 'Checkbox' ? this.checkBox : this.radio
+                )
+              } />
+              {/* <Button
+                round
+                block
+                type="primary"
+                style={{ width: '96%', margin: '0 auto' }}
+
+              >
+                确定
+              </Button> */}
+            </div>
+          </Sticky>
+        )}
+      </div>
+    )
+  }
+})

二進制
src/views/trade/images/icon_success_block_tenant.png


+ 158 - 129
src/views/trade/trade-detail.module.less

@@ -1,129 +1,158 @@
-.tradeDetail {
-  .orderType {
-    padding: 30px 0 72px;
-    background: var(--van-primary);
-    text-align: center;
-    color: #fff;
-    font-size: 24px;
-
-    & > p {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      span {
-        padding-top: 8px;
-        padding-left: 13px;
-      }
-    }
-  }
-  .orderImg {
-    width: 42px;
-    height: 47px;
-  }
-
-  &.FAILED,
-  &.CLOSE {
-    .orderType {
-      background-color: #bdbdbd;
-    }
-    .orderContent::before {
-      background: url('./images/icon_close_block.png') no-repeat center;
-      background-size: contain;
-    }
-  }
-
-  &.ING {
-    .orderType {
-      background-color: #ff802c;
-    }
-    .orderContent::before {
-      background: url('./images/icon_paying_block.png') no-repeat center;
-      background-size: contain;
-    }
-  }
-
-  .orderContent {
-    border-radius: 0px 0px 10px 10px;
-    margin: -30px 16px 0;
-    position: relative;
-    &::before {
-      content: ' ';
-      position: absolute;
-      top: -6px;
-      left: -8px;
-      right: -8px;
-      height: 14px;
-      background: url('./images/icon_success_block.png') no-repeat center;
-      background-size: contain;
-    }
-
-    .payPrice {
-      color: #333;
-      font-size: 32px;
-      text-align: center;
-      font-weight: 500;
-      padding: 10px 0 5px;
-      & > span {
-        font-size: 12px;
-      }
-    }
-  }
-
-  .tradeLogo {
-    width: 35px;
-    height: 35px;
-    border-radius: 50%;
-    margin-right: 10px;
-    overflow: hidden;
-  }
-
-  .title,
-  .content {
-    padding-top: 1px;
-    display: flex;
-    justify-content: space-between;
-    flex-direction: column;
-    line-height: 18px;
-    color: #333333;
-    font-size: 14px;
-  }
-  .desc,
-  .num {
-    padding-top: 3px;
-    font-size: 13px;
-    color: #999999;
-  }
-
-  .optionRow {
-    line-height: 26px;
-    display: flex;
-    position: relative;
-    padding-bottom: 15px;
-    box-sizing: border-box;
-    color: #333333;
-    font-size: 14px;
-
-    :global {
-      .van-col {
-        display: flex;
-        line-height: 1.5;
-      }
-      .van-col--8 {
-        color: #999999;
-        justify-content: flex-start;
-      }
-      .van-col--14 {
-        justify-content: flex-end;
-        word-break: break-word; /* 文本行的任意字内断开 */
-        word-wrap: break-word; /* IE */
-        white-space: -moz-pre-wrap; /* Mozilla */
-        white-space: -hp-pre-wrap; /* HP printers */
-        white-space: -o-pre-wrap; /* Opera 7 */
-        white-space: -pre-wrap; /* Opera 4-6 */
-        white-space: pre; /* CSS2 */
-        white-space: pre-wrap; /* CSS 2.1 */
-        white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */
-      }
-    }
-  }
-}
+.tradeDetail {
+  .orderType {
+    padding: 30px 0 72px;
+    background: var(--van-primary);
+    text-align: center;
+    color: #fff;
+    font-size: 24px;
+
+    &>p {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      span {
+        padding-top: 8px;
+        padding-left: 13px;
+      }
+    }
+  }
+
+  .orderTypeTenant {
+    background: #FF5461;
+  }
+
+  .orderImg {
+    width: 42px;
+    height: 47px;
+  }
+
+  &.FAILED,
+  &.CLOSE {
+    .orderType {
+      background-color: #bdbdbd;
+    }
+
+    .orderContent::before {
+      background: url('./images/icon_close_block.png') no-repeat center !important;
+      background-size: contain;
+    }
+  }
+
+  &.ING {
+    .orderType {
+      background-color: #ff802c;
+    }
+
+    .orderContent::before {
+      background: url('./images/icon_paying_block.png') no-repeat center !important;
+      background-size: contain;
+    }
+  }
+
+  .orderContent {
+    border-radius: 0px 0px 10px 10px;
+    margin: -30px 16px 0;
+    position: relative;
+
+    &::before {
+      content: ' ';
+      position: absolute;
+      top: -6px;
+      left: -8px;
+      right: -8px;
+      height: 14px;
+      background: url('./images/icon_success_block.png') no-repeat center;
+      background-size: contain;
+    }
+
+    &.orderContentTenant {
+      &::before {
+        background: url('./images/icon_success_block_tenant.png') no-repeat center;
+        background-size: contain;
+      }
+    }
+
+    .payPrice {
+      color: #333;
+      font-size: 32px;
+      text-align: center;
+      font-weight: 500;
+      padding: 10px 0 5px;
+
+      &>span {
+        font-size: 12px;
+      }
+    }
+  }
+
+  .tradeLogo {
+    width: 35px;
+    height: 35px;
+    border-radius: 50%;
+    margin-right: 10px;
+    overflow: hidden;
+  }
+
+  .title,
+  .content {
+    padding-top: 1px;
+    display: flex;
+    justify-content: space-between;
+    flex-direction: column;
+    line-height: 18px;
+    color: #333333;
+    font-size: 14px;
+  }
+
+  .desc,
+  .num {
+    padding-top: 3px;
+    font-size: 13px;
+    color: #999999;
+  }
+
+  .optionRow {
+    line-height: 26px;
+    display: flex;
+    position: relative;
+    padding-bottom: 15px;
+    box-sizing: border-box;
+    color: #333333;
+    font-size: 14px;
+
+    :global {
+      .van-col {
+        display: flex;
+        line-height: 1.5;
+      }
+
+      .van-col--8 {
+        color: #999999;
+        justify-content: flex-start;
+      }
+
+      .van-col--14 {
+        justify-content: flex-end;
+        word-break: break-word;
+        /* 文本行的任意字内断开 */
+        word-wrap: break-word;
+        /* IE */
+        white-space: -moz-pre-wrap;
+        /* Mozilla */
+        white-space: -hp-pre-wrap;
+        /* HP printers */
+        white-space: -o-pre-wrap;
+        /* Opera 7 */
+        white-space: -pre-wrap;
+        /* Opera 4-6 */
+        white-space: pre;
+        /* CSS2 */
+        white-space: pre-wrap;
+        /* CSS 2.1 */
+        white-space: pre-line;
+        /* CSS 3 (and 2.1 as well, actually) */
+      }
+    }
+  }
+}

+ 16 - 3
src/views/trade/trade-detail.tsx

@@ -36,7 +36,8 @@ export default defineComponent({
         },
         SUCCESS: {
           img: getAssetsHomeFile('icon_success.png'),
-          background: 'var(--van-primary)',
+          background:
+            state.projectType === 'tenant' ? '#FF5461' : 'var(--van-primary)',
           title: '支付成功'
         },
         FAILED: {
@@ -144,7 +145,12 @@ export default defineComponent({
               border={false}
             />
 
-            <div class={styles.orderType}>
+            <div
+              class={[
+                styles.orderType,
+                state.projectType === 'tenant' && styles.orderTypeTenant
+              ]}
+            >
               <p>
                 <Image
                   class={styles.orderImg}
@@ -155,7 +161,14 @@ export default defineComponent({
               </p>
             </div>
 
-            <CellGroup border={false} class={[styles.orderContent, 'mb12']}>
+            <CellGroup
+              border={false}
+              class={[
+                styles.orderContent,
+                state.projectType === 'tenant' && styles.orderContentTenant,
+                'mb12'
+              ]}
+            >
               <Cell
                 v-slots={{
                   title: () => (

+ 5 - 1
vite.config.ts

@@ -13,7 +13,7 @@ function resolve(dir: string) {
 // https://vitejs.dev/config/
 // https://github.com/vitejs/vite/issues/1930 .env
 // const proxyUrl = 'https://online.colexiu.com/';
-const proxyUrl = 'https://dev.colexiu.com/'
+const proxyUrl = 'https://test.colexiu.com/'
 // const proxyUrl = 'http://192.168.3.143:8000/'
 export default defineConfig({
   base: './',
@@ -102,6 +102,10 @@ export default defineConfig({
       '/api-mall-portal': {
         target: proxyUrl,
         changeOrigin: true
+      },
+      '/api-tenant': {
+        target: proxyUrl,
+        changeOrigin: true
       }
     }
   },