Browse Source

Merge branch 'other-app-home' into online

lex 2 năm trước cách đây
mục cha
commit
19332798eb
71 tập tin đã thay đổi với 3320 bổ sung31 xóa
  1. 11 0
      package-lock.json
  2. 1 0
      package.json
  3. BIN
      src/common/images/icon-student-default.png
  4. 10 2
      src/components/col-sticky/index.tsx
  5. 11 4
      src/helpers/native-message.ts
  6. 4 0
      src/helpers/utils.ts
  7. 39 6
      src/router/routes-student.ts
  8. 7 1
      src/state.ts
  9. 184 0
      src/student/home-layout-orchestra/auth-login.tsx
  10. 141 0
      src/student/home-layout-orchestra/auth.tsx
  11. 75 0
      src/student/home-layout-orchestra/home-layout-center.tsx
  12. 103 0
      src/student/home-layout-orchestra/index.module.less
  13. 46 0
      src/student/home-layout-orchestra/invite-code/index.module.less
  14. 145 0
      src/student/home-layout-orchestra/invite-code/index.tsx
  15. 44 0
      src/student/home-layout-orchestra/login.module.less
  16. 184 0
      src/student/home-layout-orchestra/login.tsx
  17. 122 0
      src/student/home-layout-orchestra/request-home.ts
  18. 54 0
      src/student/home-layout-orchestra/state-orchestra.ts
  19. 22 0
      src/student/home-layout-orchestra/utils.tsx
  20. 38 0
      src/student/home/components/TheHomeHeader/index.module.less
  21. 73 0
      src/student/home/components/TheHomeHeader/index.tsx
  22. 80 0
      src/student/home/components/TheSong/index.module.less
  23. 75 0
      src/student/home/components/TheSong/index.tsx
  24. 85 0
      src/student/home/components/hot-album/index.module.less
  25. 70 0
      src/student/home/components/hot-album/index.tsx
  26. 40 0
      src/student/home/components/info-list/index.module.less
  27. 117 0
      src/student/home/components/info-list/index.tsx
  28. 41 0
      src/student/home/components/menu-list/index.module.less
  29. 58 0
      src/student/home/components/menu-list/index.tsx
  30. 43 0
      src/student/home/components/music/index.module.less
  31. 88 0
      src/student/home/components/music/index.tsx
  32. 91 0
      src/student/home/components/recommend-sage/index.module.less
  33. 111 0
      src/student/home/components/recommend-sage/index.tsx
  34. 106 0
      src/student/home/components/talent-style/index.module.less
  35. 192 0
      src/student/home/components/talent-style/index.tsx
  36. 33 0
      src/student/home/components/the-title/index.module.less
  37. 37 0
      src/student/home/components/the-title/index.tsx
  38. 84 0
      src/student/home/components/video-class/index.module.less
  39. 82 0
      src/student/home/components/video-class/index.tsx
  40. 8 0
      src/student/home/event.ts
  41. 52 0
      src/student/home/images/bars.svg
  42. 52 0
      src/student/home/images/bars2.svg
  43. 15 0
      src/student/home/images/bottom-line.svg
  44. BIN
      src/student/home/images/icon-add-cart.png
  45. BIN
      src/student/home/images/icon-address.png
  46. BIN
      src/student/home/images/icon-cart.png
  47. BIN
      src/student/home/images/icon-filter.png
  48. BIN
      src/student/home/images/icon-fire.png
  49. BIN
      src/student/home/images/icon-hotbg.png
  50. BIN
      src/student/home/images/icon-location.png
  51. BIN
      src/student/home/images/icon-mall.png
  52. BIN
      src/student/home/images/icon-message.png
  53. BIN
      src/student/home/images/icon-more.png
  54. BIN
      src/student/home/images/icon-order.png
  55. BIN
      src/student/home/images/icon-scan.png
  56. BIN
      src/student/home/images/icon-search.png
  57. BIN
      src/student/home/images/icon-sell-out.png
  58. 12 0
      src/student/home/images/icon-share.svg
  59. BIN
      src/student/home/images/icon-shop-cart.png
  60. BIN
      src/student/home/images/icon-wx.png
  61. BIN
      src/student/home/images/icon-zfb.png
  62. BIN
      src/student/home/images/icon_video_play.png
  63. BIN
      src/student/home/images/popup-close.png
  64. BIN
      src/student/home/images/popup-download_bg.png
  65. 51 4
      src/student/home/index.module.less
  66. 289 7
      src/student/home/index.tsx
  67. 30 0
      src/student/home/model/download.tsx
  68. 19 0
      src/student/home/model/index.module.less
  69. 31 3
      src/student/main.ts
  70. 1 1
      src/views/404/index.tsx
  71. 13 3
      yarn.lock

+ 11 - 0
package-lock.json

@@ -16,6 +16,7 @@
         "clean-deep": "^3.4.0",
         "dayjs": "^1.10.7",
         "echarts": "^5.3.3",
+        "eventemitter3": "^5.0.0",
         "html-to-image": "^1.9.0",
         "html2canvas": "^1.4.1",
         "loaders.css": "^0.1.2",
@@ -4747,6 +4748,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/eventemitter3": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.0.tgz",
+      "integrity": "sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg=="
+    },
     "node_modules/execa": {
       "version": "5.1.1",
       "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz",
@@ -13009,6 +13015,11 @@
       "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
       "dev": true
     },
+    "eventemitter3": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.0.tgz",
+      "integrity": "sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg=="
+    },
     "execa": {
       "version": "5.1.1",
       "resolved": "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz",

+ 1 - 0
package.json

@@ -28,6 +28,7 @@
     "clean-deep": "^3.4.0",
     "dayjs": "^1.10.7",
     "echarts": "^5.3.3",
+    "eventemitter3": "^5.0.0",
     "html-to-image": "^1.9.0",
     "html2canvas": "^1.4.1",
     "loaders.css": "^0.1.2",

BIN
src/common/images/icon-student-default.png


+ 10 - 2
src/components/col-sticky/index.tsx

@@ -29,8 +29,16 @@ export default defineComponent({
     } else {
       this.divStyle.bottom = '0px'
     }
-    const { height } = useRect((this as any).$refs.div)
-    this.sectionStyle.height = `${height}px`
+    // const { height } = useRect((this as any).$refs.div)
+    // this.sectionStyle.height = `${height}px`
+
+    this.$nextTick(() => {
+      setTimeout(() => {
+        const { height } = useRect((this as any).$refs.div)
+        this.sectionStyle.height = `${height}px`
+        this.$emit('getHeight', height)
+      }, 100)
+    })
   },
   render() {
     return (

+ 11 - 4
src/helpers/native-message.ts

@@ -27,7 +27,7 @@ window.postMessage = (message: IPostMessage) => {
 type CallBack = (evt?: IPostMessage) => void
 
 // eslint-disable-next-line @typescript-eslint/no-empty-function
-const loop = () => {}
+const loop = () => { }
 
 const calls: { [key: string]: CallBack | CallBack[] } = {}
 
@@ -76,8 +76,15 @@ if (browserInfo.isApp) {
   })
 }
 
-const instance: any =
-  (window as any).COLEXIU || (window as any).webkit?.messageHandlers?.COLEXIU
+// 判断是否是管乐团学生端的app
+console.log(browserInfo.isOrchestraStudent)
+let instance: any
+if (browserInfo.isOrchestraStudent) {
+  instance = (window as any).ORCHESTRA || (window as any).webkit?.messageHandlers?.ORCHESTRA
+} else {
+  instance = (window as any).COLEXIU || (window as any).webkit?.messageHandlers?.COLEXIU
+}
+
 
 export const postMessage = (data: IPostMessage, callback?: CallBack) => {
   if (browserInfo.isApp) {
@@ -111,7 +118,7 @@ export const removeListenerMessage = (api: string, callback: CallBack) => {
     const uuid = api
     if (Array.isArray(calls[uuid])) {
       const indexOf = (calls[uuid] as CallBack[]).indexOf(callback)
-      ;(calls[uuid] as CallBack[]).splice(indexOf, 1)
+        ; (calls[uuid] as CallBack[]).splice(indexOf, 1)
     }
   }
 }

+ 4 - 0
src/helpers/utils.ts

@@ -19,9 +19,13 @@ export const browser = () => {
     isApp:
       u.indexOf('COLEXIUAPPI') > -1 ||
       u.indexOf('COLEXIUAPPA') > -1 ||
+      u.indexOf('ORCHESTRASTUDENT') > -1 ||
       u.indexOf('Adr') > -1,
     isTeacher: u.indexOf('COLEXIUTEACHER') > -1,
     isStudent: u.indexOf('COLEXIUSTUDENT') > -1,
+    isOrchestraStudent: u.indexOf('ORCHESTRASTUDENT') > -1, // 判断是否是管乐团学生端
+    orchestraAndroid: u.indexOf('ORCHESTRAAPPA') > -1 || u.indexOf('Adr') > -1, //android终端
+    orchestraIPhone: u.indexOf('ORCHESTRAAPPI') > -1, //是否为iPhone或者QQHD浏览器
     iPad: u.indexOf('iPad') > -1, //是否iPad
     webApp: u.indexOf('Safari') == -1, //是否web应该程序,没有头部与底部
     weixin: u.indexOf('MicroMessenger') > -1, //是否微信 (2015-01-22新增)

+ 39 - 6
src/router/routes-student.ts

@@ -1,4 +1,5 @@
 import Auth from '@/student/layout/auth'
+import HomeAuth from '@/student/home-layout-orchestra/auth'
 import { router, rootRouter } from './routes-common'
 
 type metaType = {
@@ -21,7 +22,8 @@ const noLoginRouter = [
       title: '曲目挑战排行榜'
       // isExternal: true // 是否外部浏览器可以打开
     }
-  }
+  },
+
 ]
 
 export default [
@@ -38,11 +40,7 @@ export default [
           isRegister: false
         } as metaType
       },
-      {
-        path: '/home',
-        name: 'home',
-        component: () => import('@/student/home/index')
-      },
+
       {
         path: '/practiceClass',
         name: 'practiceClass',
@@ -141,6 +139,41 @@ export default [
       }
     ]
   },
+  {
+    path: '/home-layout',
+    component: HomeAuth,
+    children: [
+      {
+        path: '/home-auth',
+        name: 'home-auth',
+        component: () => import('@/student/home-layout-orchestra/auth-login'),
+        meta: {
+          title: '登录酷乐秀'
+        }
+      },
+      {
+        path: '/home-layout-center',
+        name: 'home-layout-center',
+        component: () => import('@/student/home-layout-orchestra/home-layout-center'),
+        meta: {
+          title: '登录酷乐秀'
+        }
+      },
+      {
+        path: '/home-login',
+        name: 'home-login',
+        component: () => import('@/student/home-layout-orchestra/login'),
+        meta: {
+          title: '登录酷乐秀'
+        }
+      },
+      {
+        path: '/home',
+        name: 'home',
+        component: () => import('@/student/home/index')
+      },
+    ]
+  },
   ...noLoginRouter,
   ...rootRouter,
   {

+ 7 - 1
src/state.ts

@@ -9,6 +9,13 @@ export const state = reactive({
     status: 'init' as status,
     data: {} as any
   },
+  orchestraInfo: {
+    token: '' as any, phone: '' as any,
+    installStatus: 0 as any,
+    nickname: '',
+    avatar: '',
+    unionId: 0 // 是否已关联账号
+  } as any, // 管乐团信息
   platformType: '' as 'STUDENT' | 'TEACHER',
   platformApi: '/api-student' as '/api-student' | '/api-teacher',
   version: '', // 版本号 例如: 1.0.0
@@ -57,5 +64,4 @@ export const openDefaultWebView = (url?: string, callBack?: any) => {
   } else {
     callBack && callBack()
   }
-
 }

+ 184 - 0
src/student/home-layout-orchestra/auth-login.tsx

@@ -0,0 +1,184 @@
+import { defineComponent } from 'vue'
+import { Button, Image, Toast } from 'vant'
+import { removeAuth, setAuth } from './utils'
+import styles from './index.module.less'
+import {
+  listenerMessage,
+  postMessage,
+  promisefiyPostMessage,
+  removeListenerMessage
+} from '@/helpers/native-message'
+import iconStudent from '@common/images/icon-student-default.png'
+import logo from '@common/images/logo.png'
+import ColPopup from '@/components/col-popup'
+import InviteCode from './invite-code'
+import { setLogin, state } from '@/state'
+import request from '@/student/home-layout-orchestra/request-home'
+
+export default defineComponent({
+  name: 'login-music',
+  data() {
+    return {
+      username: '',
+      imgCodeStatus: false
+    }
+  },
+  mounted() {
+    this.username = state.orchestraInfo.phone
+    removeAuth()
+
+    listenerMessage('webViewOnResume', () => {
+      promisefiyPostMessage({ api: 'getUserAccount' }).then(
+        async (res: any) => {
+          const content = res.content
+          state.orchestraInfo.token = content.token.split(' ')[1]
+          state.orchestraInfo.phone = content.phone
+          state.orchestraInfo.nickname = content.nickname
+          state.orchestraInfo.avatar = content.avatar
+          state.orchestraInfo.unionId = content.unionId || 0
+          this.username = state.orchestraInfo.phone
+          if (content.unionId && this.$route.path === '/home-auth') {
+            this.getUserInfo()
+          }
+        }
+      )
+    })
+  },
+  unmounted() {
+    removeListenerMessage('webViewOnResume', () => {
+      //
+    })
+  },
+  methods: {
+    onLoginSuccess() {
+      // 调用原生api去关联账号
+      postMessage({ api: 'bindUserAccount', content: { phone: this.username } })
+      // 登录成功
+      setTimeout(() => {
+        Toast('授权成功')
+      }, 100)
+      setTimeout(() => {
+        this.getUserInfo()
+        // this.$router.push('/home')
+        // window.location.href = location.origin + location.pathname + '#/home'
+        // setTimeout(() => {
+        // window.location.reload()
+        // }, 100)
+      }, 1000)
+    },
+    async getUserInfo() {
+      try {
+        const res = await request.post('/api-auth/smsLogin', {
+          requestType: 'form',
+          data: {
+            clientId: 'student',
+            clientSecret: 'student',
+            phone: state.orchestraInfo.phone,
+            token: state.orchestraInfo.token,
+            isSurportRegister: true
+          }
+        })
+        const { authentication } = res.data
+        setAuth(authentication.token_type + ' ' + authentication.access_token)
+        promisefiyPostMessage({
+          api: 'setCache',
+          content: {
+            key: 'h5-colexiu-token',
+            value: authentication.token_type + ' ' + authentication.access_token
+          }
+        })
+        const userCash = await request.get(
+          '/api-student/student/queryUserInfo',
+          {
+            initRequest: true // 初始化接口
+          }
+        )
+        setLogin(userCash.data)
+        this.$router.push('/home')
+      } catch {
+        //
+      }
+    },
+    async onLogin() {
+      try {
+        const res = await request.post('/api-auth/smsLogin', {
+          requestType: 'form',
+          data: {
+            clientId: 'student',
+            clientSecret: 'student',
+            phone: this.username,
+            token: state.orchestraInfo.token,
+            isSurportRegister: true
+          }
+        })
+
+        const { authentication } = res.data
+        setAuth(authentication.token_type + ' ' + authentication.access_token)
+        promisefiyPostMessage({
+          api: 'setCache',
+          content: {
+            key: 'h5-colexiu-token',
+            value: authentication.token_type + ' ' + authentication.access_token
+          }
+        })
+        this.onLoginSuccess()
+      } catch {
+        //
+      }
+    }
+  },
+  render() {
+    return (
+      <div class={styles.login}>
+        <div class={styles.container}>
+          <div class={[styles.userInfo, styles.loginCenter]}>
+            <Image
+              src={state.orchestraInfo.avatar || iconStudent}
+              class={styles.img}
+              fit="cover"
+            />
+
+            <p class={styles.name}>{state.orchestraInfo.name}</p>
+          </div>
+
+          <div class={[styles.tips, styles.loginCenter]}>
+            <Image src={logo} class={styles.logo} fit="contain" />
+
+            <p class={styles.text}>同意酷乐秀获取您的管乐团信息并登录</p>
+
+            <p class={styles.phone}>{this.username}</p>
+          </div>
+        </div>
+
+        <div class={styles.btnGroup}>
+          <Button
+            round
+            color="#FF8057"
+            class={styles.btn}
+            onClick={this.onLogin}
+          >
+            授权登录
+          </Button>
+
+          <span
+            class={styles.moreBtn}
+            onClick={() => {
+              this.$router.push('/home-login')
+            }}
+          >
+            其他手机号登录
+          </span>
+        </div>
+
+        {/* <ColPopup v-model={this.imgCodeStatus}>
+          {this.imgCodeStatus && (
+            <InviteCode
+              phone={this.username}
+              onLoginSuccess={this.onLoginSuccess}
+            />
+          )}
+        </ColPopup> */}
+      </div>
+    )
+  }
+})

+ 141 - 0
src/student/home-layout-orchestra/auth.tsx

@@ -0,0 +1,141 @@
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import { state, setLogin, setLogout, setLoginError } from '@/state'
+import { browser, removeAuth } from '@/helpers/utils'
+import { setAuth } from './utils'
+import { RouterView } from 'vue-router'
+
+import request from './request-home'
+import ColResult from '@/components/col-result'
+import { promisefiyPostMessage } from '@/helpers/native-message'
+
+const browserInfo = browser()
+export default defineComponent({
+  name: 'Auth-loayout',
+  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 === '/home-auth' ||
+        this.$route.path === '/home-login' ||
+        this.$route.path === '/home-layout-center' ||
+        (this as any).isExternal
+      )
+    }
+  },
+  mounted() {
+    !this.isExternal && this.setAuth()
+    if (!state.orchestraInfo.unionId) {
+      removeAuth()
+    }
+  },
+  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 {
+          // 判断是否已经有关联编号
+          if (state.orchestraInfo.unionId) {
+            const res = await request.post('/api-auth/smsLogin', {
+              requestType: 'form',
+              data: {
+                clientId: 'student',
+                clientSecret: 'student',
+                phone: state.orchestraInfo.phone,
+                token: state.orchestraInfo.token,
+                isSurportRegister: true
+              }
+            })
+            const { authentication } = res.data
+            setAuth(
+              authentication.token_type + ' ' + authentication.access_token
+            )
+            promisefiyPostMessage({
+              api: 'setCache',
+              content: {
+                key: 'h5-colexiu-token',
+                value:
+                  authentication.token_type + ' ' + authentication.access_token
+              }
+            })
+          }
+
+          const res = await request.get('/api-student/student/queryUserInfo', {
+            initRequest: true, // 初始化接口
+            requestType: 'form'
+          })
+          setLogin(res.data)
+        } catch (e: any) {
+          // console.log(e, 'e')
+          const message = e.message
+          if (
+            message.indexOf('5000') === -1 &&
+            message.indexOf('authentication') === -1
+          ) {
+            setLoginError()
+          } else {
+            setLogout()
+          }
+        }
+        this.loading = false
+      }
+      if (state.user.status === 'logout') {
+        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: '/home-auth',
+            query: query
+          })
+        } catch (error) {
+          //
+        }
+      }
+    }
+  },
+  render() {
+    return (
+      <>
+        {state.user.status === 'error' ? (
+          <div class={styles.error}>
+            <ColResult
+              type="notFond"
+              classImgSize="CERT"
+              tips="加载失败,请稍后重试"
+              buttonText="重新加载"
+              plain={true}
+              onClick={this.setAuth}
+            />
+          </div>
+        ) : this.isNeedView ? (
+          <RouterView></RouterView>
+        ) : null}
+      </>
+    )
+  }
+})

+ 75 - 0
src/student/home-layout-orchestra/home-layout-center.tsx

@@ -0,0 +1,75 @@
+import ColResult from '@/components/col-result'
+import { promisefiyPostMessage } from '@/helpers/native-message'
+import { setAuth } from '@/helpers/utils'
+import { setLogin, state } from '@/state'
+import { defineComponent } from 'vue'
+import request from './request-home'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'home-layout-center',
+  data() {
+    return {
+      status: false
+    }
+  },
+  mounted() {
+    this.getUserInfo()
+  },
+  methods: {
+    async getUserInfo() {
+      try {
+        const res = await request.post('/api-auth/smsLogin', {
+          requestType: 'form',
+          data: {
+            clientId: 'student',
+            clientSecret: 'student',
+            phone: state.orchestraInfo.phone,
+            token: state.orchestraInfo.token,
+            isSurportRegister: true
+          }
+        })
+        const { authentication } = res.data
+        setAuth(authentication.token_type + ' ' + authentication.access_token)
+        promisefiyPostMessage({
+          api: 'setCache',
+          content: {
+            key: 'h5-colexiu-token',
+            value: authentication.token_type + ' ' + authentication.access_token
+          }
+        })
+        const userCash = await request.get(
+          '/api-student/student/queryUserInfo',
+          {
+            initRequest: true // 初始化接口
+          }
+        )
+        setLogin(userCash.data)
+        this.$router.push('/home')
+      } catch {
+        //
+        this.status = true
+      }
+    }
+  },
+  render() {
+    return (
+      <>
+        {this.status ? (
+          <div class={styles.error}>
+            <ColResult
+              type="notFond"
+              classImgSize="CERT"
+              tips="加载失败,请稍后重试"
+              buttonText="重新加载"
+              plain={true}
+              onClick={this.getUserInfo}
+            />
+          </div>
+        ) : (
+          <div></div>
+        )}
+      </>
+    )
+  }
+})

+ 103 - 0
src/student/home-layout-orchestra/index.module.less

@@ -0,0 +1,103 @@
+.error {
+  background-color: #fff;
+  display: flex;
+  // padding-top: 20px;
+  flex-direction: column;
+  min-height: calc(100vh);
+  align-items: center;
+  justify-content: center;
+  .info {
+    display: flex;
+    align-items: center;
+    margin-bottom: 30px;
+
+    span {
+      display: inline-block;
+      margin-left: 10px;
+      color: #58727e;
+      font-size: 18px;
+    }
+  }
+
+  :global {
+    .col-result-container,
+    .van-empty {
+      padding-top: 0;
+    }
+
+    .van-button {
+      width: 50%;
+    }
+  }
+}
+.login {
+  min-height: 100vh;
+  background-color: #f6f6f6;
+  background-size: 100%;
+  position: relative;
+  overflow: hidden;
+
+  .loginCenter {
+    margin: 0 auto;
+    text-align: center;
+  }
+  .userInfo {
+    margin-top: 72px;
+    .img {
+      width: 104px;
+      height: 104px;
+      border-radius: 50%;
+      overflow: hidden;
+    }
+    .name {
+      padding-top: 12px;
+      font-size: 20px;
+      font-weight: 500;
+      color: #333333;
+      line-height: 28px;
+    }
+  }
+
+  .tips {
+    padding-top: 50px;
+    .logo {
+      height: 28px;
+    }
+    .text {
+      padding-top: 12px;
+      font-size: 14px;
+      color: #777777;
+      line-height: 20px;
+    }
+    .phone {
+      padding-top: 24px;
+      font-size: 20px;
+      font-weight: 500;
+      color: #333333;
+      line-height: 28px;
+    }
+  }
+
+  .btnGroup {
+    position: absolute;
+    bottom: 70px;
+    width: 100%;
+    text-align: center;
+    display: flex;
+    align-items: center;
+    flex-direction: column;
+
+    .btn {
+      font-size: 16px;
+      font-weight: 500;
+      width: 204px;
+    }
+
+    .moreBtn {
+      padding-top: 24px;
+      font-size: 16px;
+      color: #333333;
+      line-height: 22px;
+    }
+  }
+}

+ 46 - 0
src/student/home-layout-orchestra/invite-code/index.module.less

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

+ 145 - 0
src/student/home-layout-orchestra/invite-code/index.tsx

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

+ 44 - 0
src/student/home-layout-orchestra/login.module.less

@@ -0,0 +1,44 @@
+.login {
+  min-height: 100vh;
+  background: url('../layout/images/top_bg.png') no-repeat top center,
+    url('../layout/images/bottom_bg.png') no-repeat bottom center;
+  background-color: #fff;
+  background-size: 100%;
+
+  .loginTitle {
+    padding-top: 100px;
+    font-size: 26px;
+    padding-left: 35px;
+    padding-bottom: 70px;
+    line-height: 37px;
+    font-weight: 500;
+  }
+
+  .codeText {
+    color: var(--van-primary);
+  }
+
+  .margin34 {
+    margin: 0 34px;
+  }
+
+  .formTitle {
+    font-size: 18px;
+    color: #000;
+    font-weight: 500;
+  }
+
+  :global {
+    .van-cell-group {
+      margin-bottom: 35px;
+    }
+    .van-field {
+      padding-left: 0;
+      padding-right: 0;
+    }
+    .van-button + .van-button {
+      margin-top: 20px;
+      color: #000 !important;
+    }
+  }
+}

+ 184 - 0
src/student/home-layout-orchestra/login.tsx

@@ -0,0 +1,184 @@
+import { defineComponent } from 'vue'
+import { CellGroup, Field, Button, CountDown, Row, Col, Toast } from 'vant'
+import ImgCode from '@/components/col-img-code'
+import { checkPhone } from '@/helpers/validate'
+import request from '@/student/home-layout-orchestra/request-home'
+import { setLogin, state } from '@/state'
+import { removeAuth, setAuth } from '@/helpers/utils'
+import styles from './login.module.less'
+import ColHeader from '@/components/col-header'
+
+export default defineComponent({
+  name: 'login',
+  data() {
+    return {
+      username: '',
+      password: '',
+      smsCode: '',
+      countDownStatus: true, // 是否发送验证码
+      countDownTime: 1000 * 120, // 倒计时时间
+      countDownRef: null as any, // 倒计时实例
+      imgCodeStatus: false
+    }
+  },
+  computed: {
+    codeDisable() {
+      let status = true
+
+      this.username && this.smsCode && (status = false)
+      return status
+    }
+  },
+  mounted() {
+    removeAuth()
+    this.directNext()
+  },
+  methods: {
+    directNext() {
+      if (state.user.status === 'login' || state.user.status === 'error') {
+        const { returnUrl, isRegister, ...rest } = this.$route.query
+        this.$router.replace({
+          path: '/home',
+          query: {
+            ...rest
+          }
+        })
+      }
+    },
+    async onLogin() {
+      try {
+        const res = await request.post('/api-auth/smsLogin', {
+          requestType: 'form',
+          data: {
+            clientId: 'student',
+            clientSecret: 'student',
+            phone: this.username,
+            smsCode: this.smsCode,
+            isSurportRegister: true
+          }
+        })
+
+        const { authentication } = res.data
+        setAuth(authentication.token_type + ' ' + authentication.access_token)
+        postMessage({
+          api: 'setCache',
+          content: {
+            key: 'h5-colexiu-token',
+            value: authentication.token_type + ' ' + authentication.access_token
+          }
+        })
+        const userCash = await request.get(
+          '/api-student/student/queryUserInfo',
+          {
+            initRequest: true // 初始化接口
+          }
+        )
+        setLogin(userCash.data)
+        // 调用原生api去关联账号
+        postMessage({
+          api: 'bindUserAccount',
+          content: { phone: this.username }
+        })
+
+        this.directNext()
+      } catch {
+        //
+      }
+    },
+    async onSendCode() {
+      // 发送验证码
+      if (!checkPhone(this.username)) {
+        return Toast('请输入正确的手机号码')
+      }
+      this.imgCodeStatus = true
+    },
+    onCodeSend() {
+      this.countDownStatus = false
+      this.countDownRef.start()
+    },
+    onFinished() {
+      this.countDownStatus = true
+      this.countDownRef.reset()
+    }
+  },
+  render() {
+    return (
+      <div class={styles.login}>
+        <ColHeader border={false} background="transparent" title=" " isBack />
+        <div class={styles.loginTitle}>
+          您好,
+          <br /> 欢迎使用酷乐秀
+        </div>
+        <CellGroup class={styles.margin34} border={false}>
+          <Row style={{ marginBottom: '16px' }}>
+            <Col span={24} class={styles.formTitle}>
+              手机号
+            </Col>
+            <Col span={24} class="van-hairline--bottom">
+              <Field
+                v-model={this.username}
+                name="手机号"
+                placeholder="请输入您的手机号"
+                type="tel"
+                maxlength={11}
+              />
+            </Col>
+          </Row>
+
+          <Row>
+            <Col span={24} class={styles.formTitle}>
+              验证码
+            </Col>
+            <Col span={24} class="van-hairline--bottom">
+              <Field
+                v-model={this.smsCode}
+                name="验证码"
+                placeholder="请输入验证码"
+                type="tel"
+                maxlength={6}
+                v-slots={{
+                  button: () =>
+                    this.countDownStatus ? (
+                      <span class={styles.codeText} onClick={this.onSendCode}>
+                        获取验证码
+                      </span>
+                    ) : (
+                      <CountDown
+                        ref={this.countDownRef}
+                        auto-start={false}
+                        time={this.countDownTime}
+                        onFinish={this.onFinished}
+                        format="ss秒"
+                      />
+                    )
+                }}
+              />
+            </Col>
+          </Row>
+        </CellGroup>
+        <div class={styles.margin34}>
+          <Button
+            round
+            block
+            type="primary"
+            disabled={this.codeDisable}
+            onClick={this.onLogin}
+          >
+            登录
+          </Button>
+        </div>
+
+        {this.imgCodeStatus ? (
+          <ImgCode
+            v-model:value={this.imgCodeStatus}
+            phone={this.username}
+            onClose={() => {
+              this.imgCodeStatus = false
+            }}
+            onSendCode={this.onCodeSend}
+          />
+        ) : null}
+      </div>
+    )
+  }
+})

+ 122 - 0
src/student/home-layout-orchestra/request-home.ts

@@ -0,0 +1,122 @@
+import { extend } from 'umi-request'
+import cleanDeep from 'clean-deep'
+import { browser, openLoading, closeLoading } from '@/helpers/utils'
+import { setLogout, setLoginError } from '@/state'
+import { postMessage } from '@/helpers/native-message'
+import { Toast } from 'vant'
+
+export interface SearchInitParams {
+  rows?: string | number
+  page?: string | number
+}
+
+const request = extend({
+  // requestType: 'form',
+  timeout: 20000,
+  timeoutMessage: '请求超时'
+})
+
+// request.use(async (ctx, next) => {
+//   const { url, options } = ctx.req
+//   const prefix = options.prefix || '';
+//   const baseUrl: string = url.replace(prefix, '') || '';
+//   const linkUrl: string = (ApiRouter as any)[baseUrl];
+//   if (linkUrl) {
+//     ctx.req.url = prefix + linkUrl;
+//   }
+//   await next();
+// })
+
+// 是否是初始化接口
+let initRequest = false
+let toast: ReturnType<typeof setTimeout>
+
+request.interceptors.request.use(
+  (url, options: any) => {
+    // openLoading();
+    if (!options.hideLoading) {
+      clearTimeout(toast)
+      Toast.loading({
+        message: '加载中...',
+        forbidClick: true,
+        loadingType: 'spinner',
+        duration: 0
+      })
+    }
+
+    initRequest = options.initRequest || false
+    const Authorization = sessionStorage.getItem('Authorization') || ''
+    const authHeaders: any = {}
+    if (
+      Authorization &&
+      ![
+        '/api-auth/usernameLogin',
+        '/api-auth/smsLogin',
+        '/api-auth/code/sendSms'
+      ].includes(url)
+    ) {
+      authHeaders.Authorization = Authorization
+    }
+    return {
+      url,
+      options: {
+        ...options,
+        params: cleanDeep(options.params),
+        headers: {
+          ...options.headers,
+          ...authHeaders
+        }
+      }
+    }
+  },
+  { global: false }
+)
+
+request.interceptors.response.use(
+  async res => {
+    toast = setTimeout(() => {
+      Toast.clear()
+    }, 100)
+
+    if (res.status > 299 || res.status < 200) {
+      clearTimeout(toast)
+      const msg = '服务器错误,状态码' + res.status
+      Toast(msg)
+      throw new Error(msg)
+    }
+    const data = await res.clone().json()
+    if (data.code !== 200 && data.errCode !== 0) {
+      let msg = data.msg || data.message || '处理失败,请重试'
+      if (initRequest) {
+        if (data.code === 403 || data.code === 401) {
+          setLogout()
+        } else {
+          setLoginError()
+        }
+      }
+      if (!(data.code === 403 || data.code === 401)) {
+        clearTimeout(toast)
+        Toast(msg)
+      }
+      const browserInfo = browser()
+      if (data.code === 403) {
+        msg += '403'
+        setLogout()
+        window.location.href = window.location.origin + window.location.pathname + '#/home-layout-center'
+        // window.location.reload()
+        // if (browserInfo.isApp) {
+        //   postMessage({
+        //     api: 'login'
+        //   })
+        // } else {
+        //   setLogout()
+        // }
+      }
+      throw new Error(msg)
+    }
+    return res
+  },
+  { global: false }
+)
+
+export default request

+ 54 - 0
src/student/home-layout-orchestra/state-orchestra.ts

@@ -0,0 +1,54 @@
+import { browser } from '@/helpers/utils'
+import { reactive } from 'vue'
+
+type status = 'init' | 'login' | 'logout' | 'error'
+
+export const state = reactive({
+  user: {
+    status: 'init' as status,
+    data: {} as any
+  }
+})
+
+// 预览上传到oss的地址
+export const getOssUploadUrl = (bucket: string) => {
+  const tmpBucket = bucket || 'daya'
+  return `https://${tmpBucket}.ks3-cn-beijing.ksyuncs.com/`
+}
+
+export const setLoginInit = () => {
+  state.user.status = 'init'
+  state.user.data = null
+}
+
+export const setLogin = (data: any) => {
+  state.user.status = 'login'
+  state.user.data = data
+}
+
+export const setLogout = () => {
+  state.user.status = 'logout'
+  state.user.data = null
+}
+
+export const setLoginError = () => {
+  state.user.status = 'error'
+  state.user.data = null
+}
+
+
+// 用于处理跳转地址,如果是在app内,则打开一个新的webview, 否则跳转连接
+export const openDefaultWebView = (url?: string, callBack?: any) => {
+  if (browser().isApp) {
+    postMessage({
+      api: 'openWebView',
+      content: {
+        url,
+        orientation: 1,
+        isHideTitle: false
+      }
+    })
+  } else {
+    callBack && callBack()
+  }
+}

+ 22 - 0
src/student/home-layout-orchestra/utils.tsx

@@ -0,0 +1,22 @@
+/**
+ * 删除token
+ */
+export const removeAuth = () => {
+  sessionStorage.removeItem('Authorization')
+}
+
+/**
+ * 设置token
+ * @param token
+ * @returns {void}
+ */
+export const setAuth = (token: any) => {
+  sessionStorage.setItem('Authorization', token)
+}
+
+/**
+ * 获取token
+ */
+export const getAuth = () => {
+  sessionStorage.getItem('Authorization')
+}

+ 38 - 0
src/student/home/components/TheHomeHeader/index.module.less

@@ -0,0 +1,38 @@
+.theHomeHeader {
+  // position: sticky;
+  // top: 0;
+  background-color: #fff;
+  padding: 10px 14px;
+  z-index: 100;
+  .content {
+    display: flex;
+    align-items: center;
+    .mall {
+      width: 62px;
+      height: 35px;
+      flex-shrink: 0;
+    }
+    .cart {
+      width: 22px;
+      height: 22px;
+      flex-shrink: 0;
+      margin-left: 16px;
+    }
+  }
+  .searchBox {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    padding: 0 16px;
+    background-color: #f8f8f8;
+    border-radius: 20px;
+    color: #ccc;
+    height: 30px;
+    margin-left: 4px;
+    .iconSearch {
+      width: 16px;
+      height: 16px;
+      margin-right: 16px;
+    }
+  }
+}

+ 73 - 0
src/student/home/components/TheHomeHeader/index.tsx

@@ -0,0 +1,73 @@
+import { defineComponent, nextTick, onMounted, ref } from 'vue'
+import styles from './index.module.less'
+
+import IconMall from '../../images/icon-mall.png'
+import IconSearch from '../../images/icon-search.png'
+import IconScan from '../../images/icon-scan.png'
+import IconMessage from '../../images/icon-message.png'
+import { postMessage } from '@/helpers/native-message'
+import TheHomeHeader from '@/views/shop-mall/components/TheHomeHeader'
+import { useRect } from '@vant/use'
+import ColSticky from '@/components/col-sticky'
+
+export default defineComponent({
+  name: 'TheHomeHeader',
+  emits: ['cart', 'more', 'search', 'headerDom'],
+  setup(props, { emit, expose }) {
+    const navBarHeight = ref(sessionStorage.getItem('navHeight'))
+    const init = () => {
+      // 设置是否显示导航栏 0 显示 1 不显示
+      postMessage({ api: 'setBarStatus', content: { status: 0 } })
+      if (navBarHeight.value) return
+      postMessage({ api: 'getNavHeight' }, res => {
+        const { content } = res as any
+        const dpi = content.dpi || 2
+        if (content.navHeight) {
+          const navHeight = content.navHeight / dpi + ''
+          sessionStorage.setItem('navHeight', navHeight)
+          navBarHeight.value = navHeight
+        }
+      })
+    }
+    init()
+    const homeHeaderDom = ref(null)
+
+    onMounted(() => {
+      nextTick(() => {
+        setTimeout(() => {
+          const { height } = useRect(homeHeaderDom as any)
+          emit('headerDom', height)
+        }, 300)
+      })
+    })
+
+    return () => (
+      <ColSticky position="top">
+        <div class={styles.theHomeHeader} ref={homeHeaderDom}>
+          <div
+            style={{ height: navBarHeight.value + 'px', background: '#fff' }}
+          ></div>
+          <div class={styles.content}>
+            <img class={styles.mall} src={IconMall} />
+            <div class={styles.searchBox} onClick={() => emit('search')}>
+              <img class={styles.iconSearch} src={IconSearch} />
+              <span>搜索你喜欢的内容</span>
+            </div>
+
+            <img
+              class={styles.cart}
+              src={IconScan}
+              onClick={() => emit('cart')}
+            />
+
+            <img
+              class={styles.cart}
+              src={IconMessage}
+              onClick={() => emit('more')}
+            />
+          </div>
+        </div>
+      </ColSticky>
+    )
+  }
+})

+ 80 - 0
src/student/home/components/TheSong/index.module.less

@@ -0,0 +1,80 @@
+.theSong {
+  padding: 0 10px;
+  background-color: #fff;
+  border-radius: 8px;
+  box-shadow: 0px 2px 10px 0px rgba(229, 229, 229, 0.1);
+  .item {
+    display: flex;
+    align-items: center;
+    border-bottom: 1px solid #e8e8e8;
+    padding: 16px 0;
+  }
+  .item:last-child {
+    border: none;
+  }
+  .play {
+    flex-shrink: 0;
+  }
+  .iconFine {
+    width: 16px;
+    height: 20px;
+    margin-right: 6px;
+    flex-shrink: 0;
+  }
+  .iconAlbum {
+    width: 15px;
+    height: 15px;
+    margin-right: 6px;
+    flex-shrink: 0;
+  }
+  .content {
+    flex: 1;
+    .top {
+      display: flex;
+      align-items: center;
+      margin-bottom: 10px;
+    }
+    .tag {
+      flex-shrink: 0;
+      padding: 2px 4px;
+      border-radius: 4px;
+    }
+    .user {
+      display: flex;
+      align-items: center;
+      .name {
+        font-size: 14px;
+        color: #999;
+        line-height: 16px;
+        margin-right: 12px;
+        max-width: 150px;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        overflow: hidden;
+      }
+      .tags {
+        font-size: 12px;
+        & > span {
+          display: inline-block;
+          background: #effbf9;
+          border-radius: 20px;
+          color: var(--van-primary-color);
+          padding: 4px 8px;
+          margin-right: 4px;
+        }
+      }
+    }
+    .title {
+      max-width: 145px;
+      font-size: 16px;
+      font-weight: bold;
+      color: #1a1a1a;
+      margin: 0 6px 0 0;
+    }
+    .singer {
+      max-width: 50px;
+      font-size: 12px;
+      color: #999;
+    }
+  }
+}

+ 75 - 0
src/student/home/components/TheSong/index.tsx

@@ -0,0 +1,75 @@
+import { Icon, NoticeBar, Tag, Image } from 'vant'
+import { defineComponent, PropType } from 'vue'
+import styles from './index.module.less'
+import IconPlay from '@/common/images/icon-play.png'
+import IconFine from '@/views/music/component/images/icon_exquisite.png'
+import IconAlbum from '@/views/music/component/images/icon_album_active.png'
+import { useRouter } from 'vue-router'
+export default defineComponent({
+  name: 'TheSong',
+  props: {
+    list: {
+      type: Array as PropType<any[]>,
+      default: () => []
+    }
+  },
+  emits: ['detail'],
+  setup(props, { emit }) {
+    const router = useRouter()
+    const colors: any = {
+      FREE: {
+        color: '#01B84F',
+        text: '免费'
+      },
+      VIP: {
+        color: '#CD863E',
+        text: '会员'
+      },
+      CHARGE: {
+        color: '#3591CE',
+        text: '点播'
+      }
+    }
+    return () => (
+      <div class={styles.theSong}>
+        {props.list.map((n: any) => (
+          <div class={styles.item} onClick={() => emit('detail', n)}>
+            <div class={styles.content}>
+              <div class={styles.top}>
+                {n.exquisiteFlag === 1 && (
+                  <Image src={IconFine} class={styles.iconFine} />
+                )}
+
+                {n.albumNums > 0 && (
+                  <Image src={IconAlbum} class={styles.iconAlbum} />
+                )}
+
+                <span class={[styles.title, 'van-ellipsis']}>
+                  {n.musicSheetName}
+                </span>
+                <span class={[styles.singer, 'van-ellipsis']}>
+                  -{n.composer}
+                </span>
+              </div>
+              <div class={styles.user}>
+                {n.addName ? (
+                  <div class={styles.name}>上传者:{n.addName}</div>
+                ) : (
+                  <div class={styles.name}>作曲:{n.composer}</div>
+                )}
+                <div class={styles.tags}>
+                  {n?.subjectNames.split(',').map((name: any) => (
+                    <span>{name}</span>
+                  ))}
+                </div>
+              </div>
+            </div>
+            <div style={styles.play}>
+              <Icon name={IconPlay} size={28} />
+            </div>
+          </div>
+        ))}
+      </div>
+    )
+  }
+})

+ 85 - 0
src/student/home/components/hot-album/index.module.less

@@ -0,0 +1,85 @@
+.albumContainer::-webkit-scrollbar {
+  display: none; /* Chrome Safari */
+}
+.albumContainer {
+  width: 100%;
+  overflow: hidden;
+  overflow-x: auto;
+  display: flex;
+  padding: 0 16px;
+  box-sizing: border-box;
+  flex-wrap: nowrap;
+  .album {
+    margin-right: 20px;
+    // overflow: hidden;
+    width: 94px;
+    flex-shrink: 0;
+    .albumType {
+      position: absolute;
+      left: 0;
+      top: 0;
+      background: linear-gradient(180deg, #ff8900 0%, #ff5100 100%);
+      box-shadow: 0px 1px 2px 0px rgba(150, 13, 0, 0.11);
+      border-radius: 10px 0px 10px 0px;
+      font-size: 12px;
+      padding: 0 6px;
+      line-height: 20px;
+      color: #ffffff;
+      z-index: 9;
+    }
+    .main {
+      position: relative;
+      .favorite {
+        position: absolute;
+        bottom: 8px;
+        text-align: center;
+        transform: translateX(50%);
+        background-color: var(--favorite-bg);
+        color: var(--favorite-text-color);
+        padding: 2px 7px;
+        border-radius: 10px;
+        font-size: 11px;
+        > span {
+          color: var(--favorite-color);
+        }
+      }
+    }
+    .img {
+      width: 94px;
+      height: 94px;
+      position: relative;
+      > img,
+      > div {
+        position: absolute;
+        border-radius: 10px;
+        overflow: hidden;
+      }
+    }
+    .title {
+      padding-top: 8px;
+      font-size: 14px;
+      color: #333;
+      font-weight: 400;
+      width: 94px;
+      text-align: center;
+    }
+    .model {
+      position: absolute;
+      left: 4px;
+      bottom: 4px;
+      background: rgba(67, 67, 67, 0.6);
+      backdrop-filter: blur(20px);
+      -webkit-backdrop-filter: blur(20px);
+      display: flex;
+      align-items: center;
+      padding: 4px 6px;
+      border-radius: 20px;
+      font-size: 12px;
+      color: #fff;
+      transform: scale(0.9);
+    }
+    .num {
+      margin-left: 3px;
+    }
+  }
+}

+ 70 - 0
src/student/home/components/hot-album/index.tsx

@@ -0,0 +1,70 @@
+import { Cell, Icon, Image } from 'vant'
+import styles from './index.module.less'
+import { defineComponent } from 'vue'
+import IconXin from '@/common/images/icon-xin.png'
+import IconXinActive from '@/common/images/icon-xin-active.png'
+import TheTitle from '../the-title'
+import { openDefaultWebView } from '@/student/home-layout-orchestra/state-orchestra'
+import { useRouter } from 'vue-router'
+import event from '../../event'
+
+export default defineComponent({
+  name: 'hot-album',
+  props: {
+    album: {
+      type: Array,
+      default: []
+    }
+  },
+  setup(props) {
+    const router = useRouter()
+    return () => (
+      <>
+        {props.album.length > 0 && (
+          <TheTitle
+            title="热门专辑"
+            onDetail={() => {
+              // const url = location.origin + location.pathname + '#/music-album'
+              // openDefaultWebView(url, () => {
+              //   router.push('/music-album')
+              // })
+              event.emit('downloadApp')
+            }}
+          />
+        )}
+
+        <div class={styles.albumContainer}>
+          {props.album.map((item: any) => (
+            <div
+              class={styles.album}
+              key={item.id}
+              onClick={() => {
+                // const url =
+                //   location.origin +
+                //   location.pathname +
+                //   '#/music-album-detail/' +
+                //   item.id
+                // openDefaultWebView(url, () => {
+                //   router.push('/music-album-detail/' + item.id)
+                // })
+                event.emit('downloadApp')
+              }}
+            >
+              <div class={styles.main}>
+                {item.paymentType === 'CHARGE' && (
+                  <span class={styles.albumType}>付费</span>
+                )}
+                <Image class={styles.img} src={item.albumCoverUrl} />
+                <div class={styles.model}>
+                  <Icon name={item.favorite ? IconXinActive : IconXin} />
+                  <span class={styles.num}>{item.albumFavoriteCount}人</span>
+                </div>
+              </div>
+              <h4 class={[styles.title, 'van-ellipsis']}>{item.albumName}</h4>
+            </div>
+          ))}
+        </div>
+      </>
+    )
+  }
+})

+ 40 - 0
src/student/home/components/info-list/index.module.less

@@ -0,0 +1,40 @@
+.cellGroup {
+  padding: 10px 8px;
+  margin-top: 8px;
+  margin-bottom: 16px;
+
+  :global {
+    .van-cell {
+      padding: 0 0 16px;
+      &:last-child {
+        padding: 0;
+      }
+    }
+  }
+}
+
+.infoImg {
+  width: 104px;
+  height: 72px;
+  border-radius: 6px;
+  overflow: hidden;
+}
+
+.valueContent {
+  margin-left: 10px;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+
+  h2 {
+    font-size: 14px;
+    font-weight: 400;
+    color: #333333;
+    line-height: 20px;
+  }
+  p {
+    font-size: 11px;
+    color: #999999;
+    line-height: 16px;
+  }
+}

+ 117 - 0
src/student/home/components/info-list/index.tsx

@@ -0,0 +1,117 @@
+import ColResult from '@/components/col-result'
+import request from '@/student/home-layout-orchestra/request-home'
+import { verifyUrl } from '@/helpers/toolsValidate'
+import { dateFormat } from '@/helpers/utils'
+import { openDefaultWebView } from '@/student/home-layout-orchestra/state-orchestra'
+import { Cell, CellGroup, Image, List } from 'vant'
+import { defineComponent, reactive } from 'vue'
+import { useRouter } from 'vue-router'
+import styles from './index.module.less'
+import event from '../../event'
+
+export default defineComponent({
+  name: 'info-list',
+  setup() {
+    const router = useRouter()
+    const params = reactive({
+      platformType: 'STUDENT',
+      type: '1',
+      clientType: 'STUDENT',
+      page: 1,
+      rows: 20
+    })
+    const state = reactive({
+      list: [],
+      dataShow: true, // 判断是否有数据
+      loading: false,
+      finished: false
+    })
+    const getList = async () => {
+      try {
+        const res = await request.post('/api-cms/news/page', {
+          data: {
+            ...params
+          }
+        })
+        state.loading = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (state.list.length > 0 && result.pageNo === 1) {
+          return
+        }
+        state.list = state.list.concat(result.rows || [])
+        state.finished = result.pageNo >= result.totalPage
+        params.page = result.pageNo + 1
+        state.dataShow = state.list.length > 0
+      } catch {
+        state.dataShow = false
+        state.finished = true
+      }
+    }
+
+    getList()
+    return () => (
+      <>
+        {state.dataShow ? (
+          <List
+            v-model:loading={state.loading}
+            finished={state.finished}
+            finishedText=" "
+            immediateCheck={false}
+            class={[styles.liveList]}
+            onLoad={getList}
+          >
+            <CellGroup inset class={state.list.length > 0 && styles.cellGroup}>
+              {state.list.map((item: any) => (
+                <Cell
+                  valueClass={styles.valueContent}
+                  onClick={() => {
+                    // let url =
+                    //   location.origin +
+                    //   location.pathname +
+                    //   '#/specialDetail?id=' +
+                    //   item.id
+
+                    // if (verifyUrl(item.linkUrl)) {
+                    //   openDefaultWebView(item.linkUrl, () => {
+                    //     window.location.href = item.linkUrl
+                    //   })
+                    // } else {
+                    //   openDefaultWebView(url, () => {
+                    //     router.push({
+                    //       path: '/specialDetail',
+                    //       query: {
+                    //         id: item.id
+                    //       }
+                    //     })
+                    //   })
+                    // }
+                    event.emit('downloadApp')
+                  }}
+                >
+                  {{
+                    icon: () => (
+                      <Image src={item.coverImage} class={styles.infoImg} />
+                    ),
+                    value: () => (
+                      <>
+                        <h2 class={'van-multi-ellipsis--l2'}>{item.title}</h2>
+                        <p>{dateFormat(item.createTime, 'YYYY年MM月DD日')}</p>
+                      </>
+                    )
+                  }}
+                </Cell>
+              ))}
+            </CellGroup>
+          </List>
+        ) : (
+          <ColResult
+            btnStatus={false}
+            classImgSize="SMALL"
+            tips="暂无热门资讯"
+          />
+        )}
+      </>
+    )
+  }
+})

+ 41 - 0
src/student/home/components/menu-list/index.module.less

@@ -0,0 +1,41 @@
+.swipeType {
+  padding-top: 20px;
+  padding-bottom: 20px;
+  :global {
+    .van-swipe__indicators {
+      bottom: 12px;
+    }
+    .van-swipe__indicator:not(:last-child) {
+      margin-right: 0;
+    }
+    .van-swipe__indicator {
+      width: 20px;
+      height: 4px;
+      background: #ebebeb;
+      border-radius: 4px;
+    }
+  }
+}
+.swipeTypeShow {
+  padding-bottom: 30px;
+}
+
+.typeSection {
+  display: flex;
+  align-items: center;
+}
+
+.typeItem {
+  width: 20%;
+  text-align: center;
+  .swipeTypeImg {
+    width: 50px;
+    height: 50px;
+    border-radius: 50%;
+    overflow: hidden;
+  }
+  .typeName {
+    font-size: 12px;
+    color: #333333;
+  }
+}

+ 58 - 0
src/student/home/components/menu-list/index.tsx

@@ -0,0 +1,58 @@
+import { verifyUrl } from '@/helpers/toolsValidate'
+import { openDefaultWebView } from '@/student/home-layout-orchestra/state-orchestra'
+import { Swipe, SwipeItem, Image } from 'vant'
+import { defineComponent } from 'vue'
+import event from '../../event'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'menu-list',
+  props: {
+    productList: {
+      type: Array,
+      default: []
+    },
+    onOpenWebView: {
+      type: Function,
+      default: (a: any) => {}
+    }
+  },
+  render() {
+    return (
+      <Swipe
+        class={[
+          styles.swipeType,
+          this.productList.length > 1 && styles.swipeTypeShow
+        ]}
+        indicator-color="var(--van-primary)"
+        loop={false}
+        showIndicators={this.productList.length > 1}
+      >
+        {this.productList.map((product: any) => (
+          <SwipeItem class={styles.typeSection}>
+            {product.map((item: any) => (
+              <div
+                class={styles.typeItem}
+                onClick={() => {
+                  // if (verifyUrl(item.linkUrl)) {
+                  //   openDefaultWebView(item.linkUrl, () => {
+                  //     window.location.href = item.linkUrl
+                  //   })
+                  // }
+                  event.emit('downloadApp')
+                }}
+              >
+                <Image
+                  class={styles.swipeTypeImg}
+                  src={item.coverImage}
+                  fit="cover"
+                />
+                <p class={styles.typeName}>{item.title}</p>
+              </div>
+            ))}
+          </SwipeItem>
+        ))}
+      </Swipe>
+    )
+  }
+})

+ 43 - 0
src/student/home/components/music/index.module.less

@@ -0,0 +1,43 @@
+.title {
+  padding: 20px 14px 12px;
+  background: transparent;
+  :global(.van-cell__value) {
+    font-size: 12px;
+  }
+}
+.cellTitle {
+  font-size: 17px;
+  color: #333;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  &::before {
+    width: 4px;
+    height: 14px;
+    border-radius: 4px;
+    margin-top: -2px;
+    content: ' ';
+    background: linear-gradient(to bottom, #59e5d5, #2dc7aa);
+    display: inline-block;
+    margin-right: 6px;
+  }
+}
+.cellValue {
+  line-height: 20px;
+  height: 20px;
+  padding: 2px 5px 0 7px;
+  background: #dfefec;
+  display: inline-block;
+  border-radius: 20px;
+  color: var(--van-primary);
+}
+
+.hotMusic {
+  // padding-bottom: 20px;
+  .swipeItem .swipeChild {
+    padding-left: 14px;
+  }
+  .swipeItem:last-child .swipeChild {
+    padding-right: 14px;
+  }
+}

+ 88 - 0
src/student/home/components/music/index.tsx

@@ -0,0 +1,88 @@
+import { Cell, Icon, Swipe, SwipeItem } from 'vant'
+import styles from './index.module.less'
+import { defineComponent, onMounted, ref } from 'vue'
+import { useRouter } from 'vue-router'
+import TheSong from '../TheSong'
+import TheTitle from '../the-title'
+import { openDefaultWebView } from '@/student/home-layout-orchestra/state-orchestra'
+import item from '@/views/coupons/item'
+import event from '../../event'
+
+export default defineComponent({
+  name: 'music-list',
+  props: {
+    title: {
+      type: String,
+      default: '最热曲目'
+    },
+    music: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    }
+  },
+  setup(props) {
+    const router = useRouter()
+    onMounted(() => {
+      getWidth()
+    })
+
+    const swipeWidth = ref(312)
+    const swipeShow = ref(false)
+    const getWidth = () => {
+      swipeShow.value = false
+      const clientWidth =
+        document.body.clientWidth > 750 ? 750 : document.body.clientWidth
+      swipeWidth.value = clientWidth - 63
+      swipeShow.value = true
+    }
+    return () => (
+      <>
+        {props.music.length > 0 && (
+          <TheTitle
+            title={props.title}
+            onDetail={() => {
+              // const url = location.origin + location.pathname + '#/music-list'
+              // openDefaultWebView(url, () => {
+              //   router.push('/music-list')
+              // })
+              event.emit('downloadApp')
+            }}
+          />
+        )}
+        <div class={styles.hotMusic}>
+          {swipeShow.value && (
+            <Swipe showIndicators={false} loop={false} width={swipeWidth.value}>
+              {props.music.map((n: any) => (
+                <SwipeItem class={styles.swipeItem}>
+                  <div class={styles.swipeChild}>
+                    <TheSong
+                      list={n}
+                      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
+                        //     }
+                        //   })
+                        // })
+                        event.emit('downloadApp')
+                      }}
+                    />
+                  </div>
+                </SwipeItem>
+              ))}
+            </Swipe>
+          )}
+        </div>
+      </>
+    )
+  }
+})

+ 91 - 0
src/student/home/components/recommend-sage/index.module.less

@@ -0,0 +1,91 @@
+.sageContainer::-webkit-scrollbar {
+  display: none; /* Chrome Safari */
+}
+.sageContainer {
+  width: 100%;
+  overflow: hidden;
+  overflow-x: auto;
+  display: flex;
+  padding: 0 16px;
+  box-sizing: border-box;
+  flex-wrap: nowrap;
+  .sage {
+    position: relative;
+    margin-right: 12px;
+    width: 110px;
+    padding: 15px 0;
+    flex-shrink: 0;
+    background-color: #fff;
+    box-shadow: 0px 2px 10px 0px rgba(229, 229, 229, 0.1);
+    border-radius: 10px;
+    text-align: center;
+    .animation {
+      position: absolute;
+      width: 13px;
+      height: 12px;
+      top: 9px;
+      right: 8px;
+    }
+    .header {
+      position: relative;
+      border: 2px solid var(--van-primary);
+      width: 58px;
+      height: 58px;
+      border-radius: 50%;
+      margin: 0 auto;
+
+      .living {
+        position: absolute;
+        bottom: -6px;
+        left: 5px;
+        background: linear-gradient(216deg, #ff8b39 0%, #ff4046 100%);
+        border-radius: 7px;
+        border: 1px solid #ffffff;
+        font-size: 12px;
+        padding: 2px 0;
+        width: 48px;
+        color: #ffffff;
+        border-radius: 10px;
+      }
+    }
+    .img {
+      width: 58px;
+      height: 58px;
+      position: relative;
+
+      > img,
+      > div {
+        position: absolute;
+        border-radius: 50%;
+        overflow: hidden;
+      }
+    }
+
+    .username {
+      font-size: 14px;
+      font-weight: 600;
+      padding-top: 12px;
+      color: #333333;
+      line-height: 20px;
+      max-width: 88px;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      margin: 0 auto;
+    }
+    .cert {
+      font-size: 12px;
+      color: #999999;
+      line-height: 17px;
+      padding: 4px 0 8px;
+    }
+    .btn {
+      padding: 12px 0;
+      width: 60px;
+      font-size: 13px;
+      &.van-button--disabled {
+        opacity: 1;
+      }
+    }
+  }
+}

+ 111 - 0
src/student/home/components/recommend-sage/index.tsx

@@ -0,0 +1,111 @@
+import { Button, Cell, Icon, Image, Toast } from 'vant'
+import styles from './index.module.less'
+import { defineComponent } from 'vue'
+import request from '@/student/home-layout-orchestra/request-home'
+import bars from '../../images/bars.svg'
+import TheTitle from '../the-title'
+import { useRouter } from 'vue-router'
+import { openDefaultWebView } from '@/student/home-layout-orchestra/state-orchestra'
+import event from '../../event'
+
+export default defineComponent({
+  name: 'recommend-sage',
+  props: {
+    title: {
+      type: String,
+      default: '推荐达人'
+    },
+    sage: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    }
+  },
+  setup(props) {
+    const router = useRouter()
+    const onStart = async (item: any) => {
+      // 关注与取消关注
+      try {
+        await request.get('/api-student/teacher/starOrUnStar', {
+          params: {
+            userId: item.userId,
+            starStatus: 1
+          }
+        })
+        setTimeout(() => {
+          Toast('关注成功')
+          item.watch = true
+        }, 100)
+      } catch {
+        //
+      }
+    }
+    return () => (
+      <>
+        {props.sage.length > 0 && (
+          <TheTitle
+            title={props.title}
+            onDetail={() => {
+              // const url =
+              //   location.origin + location.pathname + '#/teacherElegant'
+              // openDefaultWebView(url, () => {
+              //   router.push('/teacherElegant')
+              // })
+              event.emit('downloadApp')
+            }}
+          />
+        )}
+
+        <div class={styles.sageContainer}>
+          {props.sage.map((item: any) => (
+            <div
+              class={styles.sage}
+              key={item.id}
+              onClick={() => {
+                // 是否要跳转到直播间
+                // const url =
+                //   location.origin +
+                //   location.pathname +
+                //   '#/teacherHome?teacherId=' +
+                //   item.userId
+                // openDefaultWebView(url, () => {
+                //   router.push({
+                //     path: '/teacherHome',
+                //     query: {
+                //       teacherId: item.userId
+                //     }
+                //   })
+                // })
+                event.emit('downloadApp')
+              }}
+            >
+              {item.living && <Image src={bars} class={styles.animation} />}
+
+              <div class={styles.header}>
+                <Image class={styles.img} src={item.avatar} fit="cover" />
+                {item.living && <span class={styles.living}>直播中</span>}
+              </div>
+              <p class={styles.username}>{item.username}</p>
+              <p class={styles.cert}>认证达人</p>
+
+              <Button
+                round
+                size="mini"
+                class={styles.btn}
+                type={item.watch ? 'default' : 'primary'}
+                disabled={item.watch}
+                onClick={() => {
+                  // onStart(item)
+                  event.emit('downloadApp')
+                }}
+              >
+                {item.watch ? '已关注' : '关注'}
+              </Button>
+            </div>
+          ))}
+        </div>
+      </>
+    )
+  }
+})

+ 106 - 0
src/student/home/components/talent-style/index.module.less

@@ -0,0 +1,106 @@
+.style {
+  padding: 0 14px;
+}
+.lineContainer {
+  display: flex;
+  flex-flow: wrap;
+  justify-content: space-between;
+}
+.line {
+  width: 168px;
+}
+
+.styleItem {
+  position: relative;
+  width: 168px;
+  overflow: hidden;
+  box-shadow: 0px 2px 10px 0px rgba(229, 229, 229, 0.1);
+  border-radius: 8px;
+  background-color: #fff;
+  margin-bottom: 12px;
+
+  .styleImg {
+    position: relative;
+  }
+  .img {
+    width: 168px;
+    height: 168px;
+
+    &.first {
+      height: 222px;
+    }
+  }
+
+  .iconVideoPlay {
+    position: absolute;
+    top: 8px;
+    right: 8px;
+    width: 24px;
+    height: 24px;
+  }
+
+  .living {
+    position: absolute;
+    top: 15px;
+    right: 0;
+    line-height: 24px;
+    background: rgba(0, 0, 0, 0.2);
+    border-radius: 13px 0px 0px 13px;
+    display: flex;
+    align-items: center;
+    .animationContainer {
+      width: 24px;
+      height: 24px;
+      background: linear-gradient(180deg, #ff6e6e 0%, #ff3b3b 100%);
+      border-radius: 50%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+    .animation {
+      width: 11px;
+      height: 10px;
+    }
+    span {
+      font-size: 12px;
+      color: #ffffff;
+      line-height: 17px;
+      padding: 0 7px 0 5px;
+    }
+  }
+
+  .userInfo {
+    padding: 14px 10px;
+  }
+
+  .userInfo,
+  .user {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+  .user {
+    .userImg {
+      width: 16px;
+      height: 16px;
+      border-radius: 50%;
+      overflow: hidden;
+      margin-right: 5px;
+    }
+    span {
+      font-size: 12px;
+      color: #333333;
+      line-height: 17px;
+      max-width: 70px;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+    }
+  }
+
+  .lookNum {
+    font-size: 12px;
+    color: #999999;
+    line-height: 17px;
+  }
+}

+ 192 - 0
src/student/home/components/talent-style/index.tsx

@@ -0,0 +1,192 @@
+import ColResult from '@/components/col-result'
+import request from '@/student/home-layout-orchestra/request-home'
+import { Image, List } from 'vant'
+import { defineComponent, reactive } from 'vue'
+import styles from './index.module.less'
+import bars from '../../images/bars2.svg'
+import iconVideoPlay from '../../images/icon_video_play.png'
+import { openDefaultWebView } from '@/student/home-layout-orchestra/state-orchestra'
+import { useRouter } from 'vue-router'
+import event from '../../event'
+
+export default defineComponent({
+  name: 'talent-list',
+  setup() {
+    const router = useRouter()
+    const params = reactive({
+      platformType: 'STUDENT',
+      page: 1,
+      rows: 20
+    })
+    const state = reactive({
+      list: [],
+      dataShow: true, // 判断是否有数据
+      loading: false,
+      finished: false
+    })
+    const getList = async () => {
+      try {
+        const res = await request.post('/api-student/teacher/stylePage', {
+          data: {
+            ...params
+          }
+        })
+        state.loading = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (state.list.length > 0 && result.pageNo === 1) {
+          return
+        }
+        state.list = state.list.concat(result.rows || [])
+        state.finished = result.pageNo >= result.totalPage
+        params.page = result.pageNo + 1
+        state.dataShow = state.list.length > 0
+      } catch {
+        state.dataShow = false
+        state.finished = true
+      }
+    }
+
+    getList()
+    return () => (
+      <>
+        {state.dataShow ? (
+          <List
+            v-model:loading={state.loading}
+            finished={state.finished}
+            finishedText=" "
+            immediateCheck={false}
+            class={styles.style}
+            onLoad={getList}
+          >
+            <div class={styles.lineContainer}>
+              <div class={styles.line}>
+                {state.list.map(
+                  (item: any, index: number) =>
+                    index % 2 === 0 && (
+                      <div
+                        class={styles.styleItem}
+                        onClick={() => {
+                          // const url =
+                          //   location.origin +
+                          //   location.pathname +
+                          //   '#/teacherHome?teacherId=' +
+                          //   item.userId
+                          // openDefaultWebView(url, () => {
+                          //   router.push({
+                          //     path: '/teacherHome',
+                          //     query: {
+                          //       teacherId: item.userId
+                          //     }
+                          //   })
+                          // })
+                          event.emit('downloadApp')
+                        }}
+                      >
+                        <div class={styles.styleImg}>
+                          <Image
+                            src={item.cover}
+                            fit="cover"
+                            class={[styles.img, index === 0 && styles.first]}
+                          />
+
+                          {item.living === 1 ? (
+                            <div class={styles.living}>
+                              <div class={styles.animationContainer}>
+                                <Image src={bars} class={styles.animation} />
+                              </div>
+                              <span>直播中</span>
+                            </div>
+                          ) : (
+                            <Image
+                              src={iconVideoPlay}
+                              class={styles.iconVideoPlay}
+                            />
+                          )}
+                        </div>
+                        <div class={styles.userInfo}>
+                          <p class={styles.user}>
+                            <Image
+                              src={item.avatar}
+                              fit="cover"
+                              class={styles.userImg}
+                            />
+                            <span>{item.username}</span>
+                          </p>
+                          <p class={styles.lookNum}>{item.browse}人观看</p>
+                        </div>
+                      </div>
+                    )
+                )}
+              </div>
+              <div class={styles.line}>
+                {state.list.map(
+                  (item: any, index: number) =>
+                    index % 2 === 1 && (
+                      <div
+                        class={styles.styleItem}
+                        onClick={() => {
+                          // const url =
+                          //   location.origin +
+                          //   location.pathname +
+                          //   '#/teacherHome?teacherId=' +
+                          //   item.userId
+                          // openDefaultWebView(url, () => {
+                          //   router.push({
+                          //     path: '/teacherHome',
+                          //     query: {
+                          //       teacherId: item.userId
+                          //     }
+                          //   })
+                          // })
+                          event.emit('downloadApp')
+                        }}
+                      >
+                        <div class={styles.styleImg}>
+                          <Image
+                            src={item.cover}
+                            fit="cover"
+                            class={[styles.img, index === 0 && styles.first]}
+                          />
+                          {item.living === 1 ? (
+                            <div class={styles.living}>
+                              <div class={styles.animationContainer}>
+                                <Image src={bars} class={styles.animation} />
+                              </div>
+                              <span>直播中</span>
+                            </div>
+                          ) : (
+                            <Image
+                              src={iconVideoPlay}
+                              class={styles.iconVideoPlay}
+                            />
+                          )}
+                        </div>
+                        <div class={styles.userInfo}>
+                          <p class={styles.user}>
+                            <Image
+                              src={item.avatar}
+                              fit="cover"
+                              class={styles.userImg}
+                            />
+                            <span>{item.username}</span>
+                          </p>
+                          <p class={styles.lookNum}>{item.browse}人观看</p>
+                        </div>
+                      </div>
+                    )
+                )}
+              </div>
+            </div>
+          </List>
+        ) : (
+          <ColResult
+            btnStatus={false}
+            classImgSize="SMALL"
+            tips="暂无达人风采"
+          />
+        )}
+      </>
+    )
+  }
+})

+ 33 - 0
src/student/home/components/the-title/index.module.less

@@ -0,0 +1,33 @@
+.title {
+  padding: 20px 14px 12px;
+  background: transparent !important;
+  :global(.van-cell__value) {
+    font-size: 12px;
+  }
+}
+.cellTitle {
+  font-size: 17px;
+  color: #333;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  &::before {
+    width: 4px;
+    height: 14px;
+    border-radius: 4px;
+    margin-top: -2px;
+    content: ' ';
+    background: linear-gradient(to bottom, #59e5d5, #2dc7aa);
+    display: inline-block;
+    margin-right: 6px;
+  }
+}
+.cellValue {
+  line-height: 20px;
+  height: 20px;
+  padding: 2px 5px 0 7px;
+  background: #dfefec;
+  display: inline-block;
+  border-radius: 20px;
+  color: var(--van-primary);
+}

+ 37 - 0
src/student/home/components/the-title/index.tsx

@@ -0,0 +1,37 @@
+import { Cell, Icon } from 'vant'
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'the-music',
+  props: {
+    title: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['detail'],
+  setup(props, { emit }) {
+    return () => (
+      <Cell
+        class={styles.title}
+        titleClass={styles.cellTitle}
+        title={props.title}
+        border={false}
+        v-slots={{
+          value: () => (
+            <span
+              class={styles.cellValue}
+              onClick={() => {
+                emit('detail')
+              }}
+            >
+              更多
+              <Icon name="arrow" />
+            </span>
+          )
+        }}
+      />
+    )
+  }
+})

+ 84 - 0
src/student/home/components/video-class/index.module.less

@@ -0,0 +1,84 @@
+.videoContainer::-webkit-scrollbar {
+  display: none; /* Chrome Safari */
+}
+.videoContainer {
+  width: 100%;
+  overflow: hidden;
+  overflow-x: auto;
+  display: flex;
+  padding: 0 16px;
+  box-sizing: border-box;
+  flex-wrap: nowrap;
+}
+
+.videoItem {
+  flex-shrink: 0;
+  border-radius: 8px;
+  background-color: #fff;
+  overflow: hidden;
+  width: 148px;
+  margin-right: 12px;
+
+  .viCover {
+    height: 94px;
+    width: 100%;
+    vertical-align: middle;
+  }
+
+  .viSection {
+    padding: 8px 12px 13px;
+  }
+
+  .viTitle {
+    font-size: 14px;
+    color: #1a1a1a;
+    line-height: 20px;
+  }
+
+  .viUserNum {
+    color: #ff802c;
+    font-size: 12px;
+  }
+
+  .subjectName {
+    position: absolute;
+    bottom: 6px;
+    right: 6px;
+    font-size: 10px;
+    padding: 3px 5px;
+    color: #ffffff;
+    line-height: 1;
+    border-radius: 2px;
+    background: rgba(0, 0, 0, 0.29);
+  }
+
+  .viUser {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-top: 6px;
+    .viUserName {
+      font-size: 12px;
+      color: #999999;
+      line-height: 17px;
+      max-width: 70px;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      overflow: hidden;
+    }
+
+    .viUserNum {
+      display: flex;
+      align-items: center;
+      &::before {
+        content: ' ';
+        display: inline-block;
+        width: 4px;
+        height: 4px;
+        background: #ff8901;
+        border-radius: 50%;
+        margin-right: 8px;
+      }
+    }
+  }
+}

+ 82 - 0
src/student/home/components/video-class/index.tsx

@@ -0,0 +1,82 @@
+import { openDefaultWebView } from '@/student/home-layout-orchestra/state-orchestra'
+import { Image } from 'vant'
+import { defineComponent } from 'vue'
+import { useRouter } from 'vue-router'
+import TheTitle from '../the-title'
+import styles from './index.module.less'
+import event from '../../event'
+
+export default defineComponent({
+  name: 'video-class',
+  props: {
+    video: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    }
+  },
+  setup(props) {
+    const router = useRouter()
+    return () => (
+      <>
+        {props.video.length > 0 && (
+          <TheTitle
+            title="精品视频课"
+            onDetail={() => {
+              // const url = location.origin + location.pathname + '#/videoClass'
+              // openDefaultWebView(url, () => {
+              //   router.push('/videoClass')
+              // })
+              event.emit('downloadApp')
+            }}
+          />
+        )}
+
+        <div class={styles.videoContainer}>
+          {props.video.map((item: any) => (
+            <div
+              class={styles.videoItem}
+              onClick={() => {
+                // const url =
+                //   location.origin +
+                //   location.pathname +
+                //   '#/videoDetail?groupId=' +
+                //   item.videoGroupId
+                // openDefaultWebView(url, () => {
+                //   router.push({
+                //     path: '/videoDetail',
+                //     query: {
+                //       groupId: item.videoGroupId
+                //     }
+                //   })
+                // })
+                event.emit('downloadApp')
+              }}
+            >
+              <div style={{ position: 'relative' }}>
+                <Image
+                  class={styles.viCover}
+                  fit="cover"
+                  src={item?.lessonCoverUrl}
+                />
+                <span class={styles.subjectName}>{item?.subjectName}</span>
+              </div>
+
+              <div class={styles.viSection}>
+                <div class={[styles.viTitle, 'van-ellipsis']}>
+                  {item?.videoGroupName}
+                </div>
+
+                <div class={styles.viUser}>
+                  <div class={styles.viUserName}>{item.teacherName}</div>
+                  <div class={styles.viUserNum}>{item?.buyCount}人在学</div>
+                </div>
+              </div>
+            </div>
+          ))}
+        </div>
+      </>
+    )
+  }
+})

+ 8 - 0
src/student/home/event.ts

@@ -0,0 +1,8 @@
+import EventEmitter from "eventemitter3"
+
+export default new EventEmitter()
+
+/**
+ * 事件列表
+ * downloadApp 下载app
+ */

+ 52 - 0
src/student/home/images/bars.svg

@@ -0,0 +1,52 @@
+<svg width="135" height="140" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" fill="#2dc7aa">
+    <!-- <rect y="10" width="15" height="120" rx="6">
+        <animate attributeName="height"
+             begin="0.5s" dur="1s"
+             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
+             repeatCount="indefinite" />
+        <animate attributeName="y"
+             begin="0.5s" dur="1s"
+             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
+             repeatCount="indefinite" />
+    </rect> -->
+    <rect x="20" y="10" width="15" height="120" rx="6">
+        <animate attributeName="height"
+             begin="0.25s" dur="1s"
+             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
+             repeatCount="indefinite" />
+        <animate attributeName="y"
+             begin="0.25s" dur="1s"
+             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
+             repeatCount="indefinite" />
+    </rect>
+    <rect x="60" width="15" height="140" rx="6">
+        <animate attributeName="height"
+             begin="0s" dur="1s"
+             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
+             repeatCount="indefinite" />
+        <animate attributeName="y"
+             begin="0s" dur="1s"
+             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
+             repeatCount="indefinite" />
+    </rect>
+    <rect x="100" y="10" width="15" height="120" rx="6">
+        <animate attributeName="height"
+             begin="0.5s" dur="1s"
+             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
+             repeatCount="indefinite" />
+        <animate attributeName="y"
+             begin="0.5s" dur="1s"
+             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
+             repeatCount="indefinite" />
+    </rect>
+    <!-- <rect x="120" y="10" width="15" height="120" rx="6">
+        <animate attributeName="height"
+             begin="0.5s" dur="1s"
+             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
+             repeatCount="indefinite" />
+        <animate attributeName="y"
+             begin="0.5s" dur="1s"
+             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
+             repeatCount="indefinite" />
+    </rect> -->
+</svg>

+ 52 - 0
src/student/home/images/bars2.svg

@@ -0,0 +1,52 @@
+<svg width="135" height="140" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" fill="#fff">
+    <!-- <rect y="10" width="15" height="120" rx="6">
+        <animate attributeName="height"
+             begin="0.5s" dur="1s"
+             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
+             repeatCount="indefinite" />
+        <animate attributeName="y"
+             begin="0.5s" dur="1s"
+             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
+             repeatCount="indefinite" />
+    </rect> -->
+    <rect x="10" y="10" width="20" height="140" rx="6">
+        <animate attributeName="height"
+             begin="0.25s" dur="1s"
+             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
+             repeatCount="indefinite" />
+        <animate attributeName="y"
+             begin="0.25s" dur="1s"
+             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
+             repeatCount="indefinite" />
+    </rect>
+    <rect x="60" width="20" height="160" rx="6">
+        <animate attributeName="height"
+             begin="0s" dur="1s"
+             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
+             repeatCount="indefinite" />
+        <animate attributeName="y"
+             begin="0s" dur="1s"
+             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
+             repeatCount="indefinite" />
+    </rect>
+    <rect x="110" y="10" width="20" height="140" rx="6">
+        <animate attributeName="height"
+             begin="0.5s" dur="1s"
+             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
+             repeatCount="indefinite" />
+        <animate attributeName="y"
+             begin="0.5s" dur="1s"
+             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
+             repeatCount="indefinite" />
+    </rect>
+    <!-- <rect x="120" y="10" width="15" height="120" rx="6">
+        <animate attributeName="height"
+             begin="0.5s" dur="1s"
+             values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
+             repeatCount="indefinite" />
+        <animate attributeName="y"
+             begin="0.5s" dur="1s"
+             values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
+             repeatCount="indefinite" />
+    </rect> -->
+</svg>

+ 15 - 0
src/student/home/images/bottom-line.svg

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="20px" height="7px" viewBox="0 0 20 7" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>选中(弧形)备份</title>
+    <defs>
+        <linearGradient x1="0%" y1="-8.18438733%" x2="106.381755%" y2="100%" id="linearGradient-1">
+            <stop stop-color="#2DC7AA" offset="0%"></stop>
+            <stop stop-color="#59E5D5" offset="100%"></stop>
+        </linearGradient>
+    </defs>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="商城" transform="translate(-34.000000, -595.000000)" fill="url(#linearGradient-1)" fill-rule="nonzero">
+            <path d="M38.6153846,590 C39.5075369,590 40.2307692,590.723232 40.2307692,591.615385 C40.2307692,596.670914 44.3290858,600.769231 49.3846154,600.769231 C50.2767677,600.769231 51,601.492463 51,602.384615 C51,603.276768 50.2767677,604 49.3846154,604 C42.5447812,604 37,598.455219 37,591.615385 C37,590.723232 37.7232323,590 38.6153846,590 Z" id="选中(弧形)备份" transform="translate(44.000000, 597.000000) rotate(-45.000000) translate(-44.000000, -597.000000) "></path>
+        </g>
+    </g>
+</svg>

BIN
src/student/home/images/icon-add-cart.png


BIN
src/student/home/images/icon-address.png


BIN
src/student/home/images/icon-cart.png


BIN
src/student/home/images/icon-filter.png


BIN
src/student/home/images/icon-fire.png


BIN
src/student/home/images/icon-hotbg.png


BIN
src/student/home/images/icon-location.png


BIN
src/student/home/images/icon-mall.png


BIN
src/student/home/images/icon-message.png


BIN
src/student/home/images/icon-more.png


BIN
src/student/home/images/icon-order.png


BIN
src/student/home/images/icon-scan.png


BIN
src/student/home/images/icon-search.png


BIN
src/student/home/images/icon-sell-out.png


+ 12 - 0
src/student/home/images/icon-share.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="20px" height="19px" viewBox="0 0 20 19" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>编组 12</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="我的乐谱-审核中" transform="translate(-337.000000, -57.000000)" fill="#000000" fill-rule="nonzero" stroke="#000000" stroke-width="0.4">
+            <g id="编组备份" transform="translate(338.000000, 58.000000)">
+                <path d="M16.2470898,9.95964844 C15.8204687,9.95964844 15.4739063,10.3062109 15.4739063,10.7331641 L15.4739063,13.322207 C15.4739063,14.5087305 14.5086328,15.4740625 13.3220898,15.4740625 L3.70880859,15.4740625 C2.52246094,15.4740625 1.55714844,14.5087305 1.55714844,13.322207 L1.55714844,3.70892578 C1.55714844,2.52259766 2.52246094,1.55728516 3.70880859,1.55728516 L6.24816406,1.55728516 C6.67533203,1.55728516 7.02169922,1.21089844 7.02169922,0.784316406 C7.02169922,0.356972656 6.6753125,0.0105859375 6.24816406,0.0105859375 L3.70880859,0.0105859375 C1.66607422,0.0105859375 0.0106640625,1.66619141 0.0106640625,3.70892578 L0.0106640625,13.322207 C0.0106640625,15.3649609 1.66607422,17.0207227 3.70880859,17.0207227 L13.3220898,17.0207227 C15.3648047,17.0207227 17.0206055,15.3649609 17.0206055,13.322207 L17.0206055,10.7331641 C17.0206055,10.3062109 16.674043,9.95964844 16.2470898,9.95964844 Z" id="路径"></path>
+                <path d="M10.8350195,1.56066406 L14.4217969,1.56066406 C14.3497037,1.60870774 14.2825522,1.66378187 14.2213281,1.72507812 L6.35746094,9.58894531 C5.96238281,9.98341797 5.88708984,10.5485742 6.18929688,10.8502148 C6.49111328,11.1525977 7.05607422,11.0770703 7.45056641,10.6818555 L15.3149805,2.81818359 C15.3739557,2.75927938 15.4271724,2.69487912 15.4739063,2.62585937 L15.4739063,6.19996094 C15.4739063,6.62693359 15.8200977,6.97310547 16.2470898,6.97310547 C16.6743945,6.97310547 17.0202734,6.62693359 17.0202734,6.19996094 L17.0202734,0.787519531 C17.0202734,0.360546875 16.6743945,0.014375 16.2470898,0.014375 L10.8350195,0.014375 C10.4076758,0.014375 10.0618359,0.360566406 10.0618359,0.787519531 C10.0618359,1.21447266 10.4076758,1.56066406 10.8350195,1.56066406 Z" id="路径"></path>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/student/home/images/icon-shop-cart.png


BIN
src/student/home/images/icon-wx.png


BIN
src/student/home/images/icon-zfb.png


BIN
src/student/home/images/icon_video_play.png


BIN
src/student/home/images/popup-close.png


BIN
src/student/home/images/popup-download_bg.png


+ 51 - 4
src/student/home/index.module.less

@@ -1,4 +1,51 @@
-.title {
-  font-size: 20px;
-  color: red;
-}
+.hotContent {
+  padding: 11px 14px 0 14px;
+  background-color: #fff;
+  border-radius: 0 0 18px 18px;
+}
+
+.swipe {
+  height: 35.2vmin;
+  border-radius: 10px;
+  overflow: hidden;
+  .swipeItemImg {
+    height: 100%;
+    width: 100%;
+  }
+  :global {
+    .van-swipe__indicators {
+      right: 10px;
+      left: initial;
+    }
+    .van-swipe__indicator {
+      transition: width 0.3s;
+    }
+    .van-swipe__indicator--active {
+      width: 12px;
+      border-radius: 4px;
+    }
+  }
+}
+
+.homeTabs {
+  margin-top: 18px;
+  :global {
+    .van-tab {
+      font-size: 18px;
+      color: #999;
+      font-weight: 600;
+      position: relative;
+      z-index: 2;
+    }
+    .van-tab--active {
+      color: #333333;
+    }
+    .van-tabs__line {
+      width: 56px;
+      height: 10px;
+      background: rgba(45, 199, 170, 0.5);
+      border-radius: 6px;
+      bottom: 25px;
+    }
+  }
+}

+ 289 - 7
src/student/home/index.tsx

@@ -1,15 +1,297 @@
-import { Button } from 'vant'
+import request from '@/student/home-layout-orchestra/request-home'
+import { verifyUrl } from '@/helpers/toolsValidate'
+import { openDefaultWebView } from '@/student/home-layout-orchestra/state-orchestra'
+import { PullRefresh, Swipe, SwipeItem, Image, Tabs, Tab, Popup } from 'vant'
 import { defineComponent } from 'vue'
+import HotAlbum from './components/hot-album'
+import InfoList from './components/info-list'
+import MenuList from './components/menu-list'
+import Music from './components/music'
+import RecommendSage from './components/recommend-sage'
+import TalentStyle from './components/talent-style'
+import TheHomeHeader from './components/TheHomeHeader'
+import VideoClass from './components/video-class'
 import styles from './index.module.less'
+import Download from './model/download'
+import popupClose from './images/popup-close.png'
+import {
+  listenerMessage,
+  postMessage,
+  promisefiyPostMessage,
+  removeListenerMessage
+} from '@/helpers/native-message'
+import { browser } from '@/helpers/utils'
+import { setLogout, state } from '@/state'
+import event from './event'
 
 export default defineComponent({
-  name: 'home',
+  name: 'home-index',
+  props: {
+    album: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    }
+  },
+  data() {
+    return {
+      loading: false,
+      height: 'auto' as any,
+      banner: [], // BANNER列表
+      appMenu: [], // 按钮列表
+      albumList: [], // 热门专辑
+      musicList: {
+        topMusicSheet: [] as any,
+        newMusicSheet: [] as any,
+        hotMusicSheet: [] as any
+      },
+      sageList: [], // 推荐达人
+      videoList: [], // 视频课
+      headerHeight: 0,
+      downloadStatus: false
+    }
+  },
+  async mounted() {
+    await this.init()
+
+    event.on('downloadApp', async () => {
+      // 管乐团里面,获取是否已安装酷乐秀
+      const type = browser().orchestraAndroid
+        ? 'com.cooleshow.student'
+        : 'ColexiuStudent://'
+      await promisefiyPostMessage({
+        api: 'isInstall',
+        content: { type: type }
+      }).then((res: any) => {
+        const content = res.content
+        state.orchestraInfo.installStatus = content.installStatus
+        this.downloadStatus = true
+      })
+    })
+
+    listenerMessage('webViewOnResume', () => {
+      promisefiyPostMessage({ api: 'getUserAccount' }).then((res: any) => {
+        const content = res.content
+        // console.log(state.orchestraInfo, 'state.orchestraInfo')
+        // console.log(content, 'content')
+        if (content.phone !== state.orchestraInfo.phone) {
+          // 判断是否已经有关联编号
+          if (content.unionId) {
+            window.location.reload()
+          } else {
+            state.orchestraInfo.token = content.token.split(' ')[1]
+            state.orchestraInfo.phone = content.phone
+            state.orchestraInfo.nickname = content.nickname
+            state.orchestraInfo.avatar = content.avatar
+            state.orchestraInfo.unionId = content.unionId || 0
+            // 13310106048
+            // 15088666723
+            setLogout()
+            // window.location.reload()
+            this.$router.push({
+              path: '/home-auth'
+            })
+          }
+        }
+      })
+    })
+
+    postMessage({ api: 'setBarStatus', content: { status: 0 } })
+  },
+  unmounted() {
+    removeListenerMessage('webViewOnResume', () => {
+      //
+    })
+  },
+  methods: {
+    async init() {
+      try {
+        const res = await request.post('/api-cms/news/app/home', {
+          data: {
+            clientType: 'STUDENT'
+          }
+        })
+        const result = res.data || {}
+        this.banner = result.banner || []
+        this.appMenu = this.arrChange(5, result.appMenu || [])
+
+        // 热门专辑
+        const album = await request.post('/api-student/music/album/list', {
+          data: {
+            albumStatus: 1,
+            clientId: 'STUDENT'
+          }
+        })
+        this.albumList = album.data.rows || []
+
+        // 曲谱
+        const music = await request.post(
+          '/api-student/music/sheet/appMusicSheet',
+          {
+            data: {}
+          }
+        )
+        const musicData = music.data || []
+        this.musicList = {
+          topMusicSheet: this.arrChange(4, musicData.topMusicSheet || []),
+          newMusicSheet: this.arrChange(4, musicData.newMusicSheet || []),
+          hotMusicSheet: this.arrChange(4, musicData.hotMusicSheet || [])
+        }
+
+        const sage = await request.get(
+          '/api-student/teacher/queryHotTeacherList'
+        )
+        this.sageList = sage.data || []
+
+        const video = await request.get(
+          '/api-student/courseSchedule/queryLiveAndVideo'
+        )
+        this.videoList = video.data.videoList || []
+      } catch {
+        //
+      }
+      setTimeout(() => {
+        this.loading = false
+      }, 500)
+    },
+    arrChange(num: number, arr: any) {
+      const newArr = [] as any
+      while (arr.length > 0) {
+        newArr.push(arr.splice(0, num))
+      }
+      return newArr
+    },
+    async onRefresh() {
+      await this.init()
+      setTimeout(() => {
+        this.loading = false
+      }, 500)
+    }
+  },
   render() {
     return (
-      <>
-        <div class={styles.title}>标题</div>
-        <Button>首页</Button>
-      </>
+      <div class={styles.home}>
+        <TheHomeHeader
+          ref="header"
+          onHeaderDom={(height: number) => {
+            console.log(height, 'height')
+            this.headerHeight = height
+          }}
+          onCart={() => {
+            event.emit('downloadApp')
+          }}
+          onSearch={() => {
+            // 搜索详情
+            event.emit('downloadApp')
+          }}
+          onMore={() => {
+            event.emit('downloadApp')
+          }}
+        />
+
+        <PullRefresh
+          v-model={this.loading}
+          loading-text="正在刷新..."
+          success-text="刷新完成"
+          onRefresh={() => this.onRefresh()}
+        >
+          <div class={styles.hotContent}>
+            <Swipe class={styles.swipe} autoplay={3000}>
+              {this.banner.map((item: any) => (
+                <SwipeItem
+                  onClick={() => {
+                    // 判断url是否正常
+                    // if (verifyUrl(item.linkUrl)) {
+                    //   openDefaultWebView(item.linkUrl, () => {
+                    //     window.location.href = item.linkUrl
+                    //   })
+                    // }
+                    event.emit('downloadApp')
+                  }}
+                >
+                  <Image
+                    class={styles.swipeItemImg}
+                    src={item.coverImage}
+                    fit="fill"
+                  />
+                </SwipeItem>
+              ))}
+            </Swipe>
+
+            <MenuList productList={this.appMenu} />
+          </div>
+
+          <HotAlbum album={this.albumList} />
+
+          <Music title="推荐曲目" music={this.musicList?.topMusicSheet || []} />
+          <Music title="最新曲目" music={this.musicList?.newMusicSheet || []} />
+          <Music title="最热曲目" music={this.musicList?.hotMusicSheet || []} />
+
+          {/* 推荐达人 */}
+          <RecommendSage sage={this.sageList} />
+
+          {/* 精品视频课 */}
+          <VideoClass video={this.videoList} />
+
+          <Tabs
+            shrink
+            background="#f6f8f9"
+            class={styles.homeTabs}
+            sticky
+            offsetTop={this.headerHeight + 'px'}
+          >
+            <Tab title="达人风采" name="sage">
+              <TalentStyle />
+            </Tab>
+            <Tab title="热门资讯" name="info">
+              <InfoList />
+            </Tab>
+          </Tabs>
+        </PullRefresh>
+
+        <Popup
+          v-model:show={this.downloadStatus}
+          round
+          style={{ width: '86%', background: 'transparent' }}
+          closeable
+          closeIcon={popupClose}
+        >
+          <Download
+            buttonText={
+              state.orchestraInfo.installStatus ? '打开酷乐秀' : '下载酷乐秀'
+            }
+            onConfirm={() => {
+              const type = browser().orchestraAndroid
+                ? 'com.cooleshow.student'
+                : 'ColexiuStudent://'
+              if (state.orchestraInfo.installStatus) {
+                postMessage({ api: 'openApp', content: { type: type } })
+              } else {
+                // const urlIos =
+                //   'https://itunes.apple.com/cn/app/id1626971695?mt=8'
+                // const urlAndroid =
+                //   'https://appstore.ks3-cn-beijing.ksyuncs.com/clx-student-domain.apk'
+
+                const url = browser().orchestraAndroid
+                  ? window.location.origin +
+                    window.location.pathname +
+                    '#/download'
+                  : 'https://itunes.apple.com/cn/app/id1626971695?mt=8'
+                postMessage({
+                  api: 'downloadApp',
+                  content: { type: type, url: url }
+                })
+              }
+            }}
+          />
+        </Popup>
+        {/* 
+          ColexiuStudent IOS 学生端
+          com.cooleshow.student ADNROID 学生端
+          com.cooleshow.teacher ADNROID 老师端
+         */}
+      </div>
     )
   }
-})
+})

+ 30 - 0
src/student/home/model/download.tsx

@@ -0,0 +1,30 @@
+import { defineComponent } from 'vue'
+import { Image, Button } from 'vant'
+import downloadBg from '../images/popup-download_bg.png'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'download-popup',
+  props: {
+    buttonText: {
+      type: String,
+      default: '下载酷乐秀'
+    }
+  },
+  emits: ['confirm'],
+  setup(props, { emit }) {
+    return () => (
+      <div class={styles.download}>
+        <Image src={downloadBg} class={styles.img} />
+
+        <Button
+          round
+          class={styles.downloadBtn}
+          onClick={() => emit('confirm')}
+        >
+          {props.buttonText}
+        </Button>
+      </div>
+    )
+  }
+})

+ 19 - 0
src/student/home/model/index.module.less

@@ -0,0 +1,19 @@
+.download {
+  background-color: #fff;
+  border-radius: 26px;
+  overflow: hidden;
+  text-align: center;
+  .img {
+  }
+
+  .downloadBtn {
+    background: linear-gradient(180deg, #59e5d5 0%, #2dc7aa 100%);
+    border-radius: 39px;
+    color: #fff;
+    font-size: 18px;
+    line-height: 25px;
+    width: 194px;
+    margin-top: 28px;
+    margin-bottom: 34px;
+  }
+}

+ 31 - 3
src/student/main.ts

@@ -4,13 +4,13 @@ import dayjs from 'dayjs'
 import 'dayjs/locale/zh-cn'
 import router from '../router/index-student'
 import vueFilter from '@/helpers/vueFilter'
-import { postMessage } from '@/helpers/native-message'
+import { listenerMessage, postMessage, promisefiyPostMessage } from '@/helpers/native-message'
 
 import 'normalize.css'
 
 import '../styles/index.less'
 import { state } from '@/state'
-import { browser } from '@/helpers/utils'
+import { browser, setAuth } from '@/helpers/utils'
 
 const app = createApp(App)
 
@@ -22,10 +22,38 @@ postMessage(
   },
   (res: any) => {
     state.version = res.content.version
-    console.log(res, 'version')
   }
 )
 
+// 判断是否是管乐团学生端,用来获取基础数据
+if (browser().isOrchestraStudent) {
+  // await promisefiyPostMessage({
+  //   api: 'setCache',
+  //   content: {
+  //     key: 'h5-colexiu-token',
+  //     value: ''
+  //   }
+  // })
+
+  // 获取管乐团token
+  promisefiyPostMessage({ api: 'getUserAccount' }).then((res: any) => {
+    const content = res.content
+    state.orchestraInfo.token = content.token.split(' ')[1]
+    state.orchestraInfo.phone = content.phone
+    state.orchestraInfo.nickname = content.nickname
+    state.orchestraInfo.avatar = content.avatar
+    state.orchestraInfo.unionId = content.unionId || 0
+  })
+
+  // 从缓存里面获取token
+  promisefiyPostMessage({ api: 'getCache', content: { key: 'h5-colexiu-token' } }).then((res: any) => {
+    const content = res.content
+    if (content.value) {
+      setAuth(content.value)
+    }
+  })
+}
+
 if (browser().isTeacher) {
   state.platformType = 'TEACHER'
 } else if (browser().isStudent) {

+ 1 - 1
src/views/404/index.tsx

@@ -17,7 +17,7 @@ export default defineComponent({
           plain
           round
           onClick={() => {
-            if (browser().iPhone) {
+            if (browser().isApp) {
               postMessage({ api: 'back' })
             } else {
               this.$router.back()

+ 13 - 3
yarn.lock

@@ -2193,9 +2193,9 @@
   "resolved" "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz"
   "version" "0.9.3"
 
-"esbuild-windows-64@0.14.54":
-  "integrity" "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ=="
-  "resolved" "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz"
+"esbuild-darwin-64@0.14.54":
+  "integrity" "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug=="
+  "resolved" "https://registry.npmmirror.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz"
   "version" "0.14.54"
 
 "esbuild@^0.14.27":
@@ -2397,6 +2397,11 @@
   "resolved" "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz"
   "version" "2.0.3"
 
+"eventemitter3@^5.0.0":
+  "integrity" "sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg=="
+  "resolved" "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.0.tgz"
+  "version" "5.0.0"
+
 "execa@^0.8.0":
   "integrity" "sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA=="
   "resolved" "https://registry.npmmirror.com/execa/-/execa-0.8.0.tgz"
@@ -2588,6 +2593,11 @@
   "resolved" "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz"
   "version" "1.0.0"
 
+"fsevents@~2.3.2":
+  "integrity" "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="
+  "resolved" "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz"
+  "version" "2.3.2"
+
 "function-bind@^1.1.1":
   "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
   "resolved" "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz"