瀏覽代碼

添加首页功能

lex 2 年之前
父節點
當前提交
824aaff494
共有 37 個文件被更改,包括 1149 次插入11 次删除
  1. 38 0
      src/student/home/components/TheHomeHeader/index.module.less
  2. 58 0
      src/student/home/components/TheHomeHeader/index.tsx
  3. 80 0
      src/student/home/components/TheSong/index.module.less
  4. 79 0
      src/student/home/components/TheSong/index.tsx
  5. 85 0
      src/student/home/components/hot-album/index.module.less
  6. 41 0
      src/student/home/components/hot-album/index.tsx
  7. 41 0
      src/student/home/components/menu-list/index.module.less
  8. 56 0
      src/student/home/components/menu-list/index.tsx
  9. 43 0
      src/student/home/components/music/index.module.less
  10. 53 0
      src/student/home/components/music/index.tsx
  11. 91 0
      src/student/home/components/recommend-sage/index.module.less
  12. 74 0
      src/student/home/components/recommend-sage/index.tsx
  13. 33 0
      src/student/home/components/the-title/index.module.less
  14. 37 0
      src/student/home/components/the-title/index.tsx
  15. 84 0
      src/student/home/components/video-class/index.module.less
  16. 50 0
      src/student/home/components/video-class/index.tsx
  17. 15 0
      src/student/home/images/bottom-line.svg
  18. 二進制
      src/student/home/images/icon-add-cart.png
  19. 二進制
      src/student/home/images/icon-address.png
  20. 二進制
      src/student/home/images/icon-cart.png
  21. 二進制
      src/student/home/images/icon-filter.png
  22. 二進制
      src/student/home/images/icon-fire.png
  23. 二進制
      src/student/home/images/icon-hotbg.png
  24. 二進制
      src/student/home/images/icon-location.png
  25. 二進制
      src/student/home/images/icon-mall.png
  26. 二進制
      src/student/home/images/icon-message.png
  27. 二進制
      src/student/home/images/icon-more.png
  28. 二進制
      src/student/home/images/icon-order.png
  29. 二進制
      src/student/home/images/icon-scan.png
  30. 二進制
      src/student/home/images/icon-search.png
  31. 二進制
      src/student/home/images/icon-sell-out.png
  32. 12 0
      src/student/home/images/icon-share.svg
  33. 二進制
      src/student/home/images/icon-shop-cart.png
  34. 二進制
      src/student/home/images/icon-wx.png
  35. 二進制
      src/student/home/images/icon-zfb.png
  36. 28 4
      src/student/home/index.module.less
  37. 151 7
      src/student/home/index.tsx

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

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

@@ -0,0 +1,58 @@
+import { defineComponent, 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'
+
+export default defineComponent({
+  name: 'TheHomeHeader',
+  emits: ['cart', 'more', 'search'],
+  setup(props, { emit }) {
+    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()
+
+    return () => (
+      <div class={styles.theHomeHeader}>
+        <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>
+    )
+  }
+})

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

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

@@ -0,0 +1,79 @@
+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/images/component/icon_album_active.png'
+import { useRouter } from 'vue-router'
+export default defineComponent({
+  name: 'TheSong',
+  props: {
+    list: {
+      type: Array as PropType<any[]>,
+      default: () => []
+    }
+  },
+  setup(props) {
+    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={() =>
+              router.push({ path: '/musicDetail', query: { id: n.id } })
+            }
+          >
+            <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;
+    }
+  }
+}

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

@@ -0,0 +1,41 @@
+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'
+
+export default defineComponent({
+  name: 'hot-album',
+  props: {
+    album: {
+      type: Array,
+      default: []
+    }
+  },
+  setup(props) {
+    return () => (
+      <>
+        {props.album.length > 0 && <TheTitle title="热门曲谱" />}
+
+        <div class={styles.albumContainer}>
+          {props.album.map((item: any) => (
+            <div class={styles.album} key={item.id} onClick={() => {}}>
+              <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>
+      </>
+    )
+  }
+})

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

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

@@ -0,0 +1,56 @@
+import { Swipe, SwipeItem, Image } from 'vant'
+import { defineComponent } from 'vue'
+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={() => {
+                  // this.onOpenWebView(
+                  //   '/goodsList?' +
+                  //     'id=' +
+                  //     item.id +
+                  //     '&tag=' +
+                  //     encodeURIComponent(item.name)
+                  // )
+                }}
+              >
+                <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 {
+    padding-left: 14px;
+  }
+  .swipeItem:last-child {
+    padding-right: 14px;
+  }
+}

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

@@ -0,0 +1,53 @@
+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'
+
+export default defineComponent({
+  name: 'music-list',
+  props: {
+    title: {
+      type: String,
+      default: '最热曲目'
+    },
+    music: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    }
+  },
+  setup(props) {
+    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} />}
+        <div class={styles.hotMusic}>
+          {swipeShow.value && (
+            <Swipe showIndicators={false} loop={false} width={swipeWidth.value}>
+              {props.music.map((n: any) => (
+                <SwipeItem class={styles.swipeItem}>
+                  <TheSong list={n} />
+                </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;
+      }
+    }
+  }
+}

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

@@ -0,0 +1,74 @@
+import { Button, Cell, Icon, Image, Toast } from 'vant'
+import styles from './index.module.less'
+import { defineComponent } from 'vue'
+import request from '@/helpers/request'
+import bars from '@common/svg/bars.svg'
+import TheTitle from '../the-title'
+
+export default defineComponent({
+  name: 'recommend-sage',
+  props: {
+    title: {
+      type: String,
+      default: '推荐达人'
+    },
+    sage: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    }
+  },
+  setup(props) {
+    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} />}
+
+        <div class={styles.sageContainer}>
+          {props.sage.map((item: any) => (
+            <div class={styles.sage} key={item.id} onClick={() => {}}>
+              {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)
+                }}
+              >
+                {item.watch ? '已关注' : '关注'}
+              </Button>
+            </div>
+          ))}
+        </div>
+      </>
+    )
+  }
+})

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

@@ -0,0 +1,33 @@
+.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);
+}

+ 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: 168px;
+  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;
+      }
+    }
+  }
+}

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

@@ -0,0 +1,50 @@
+import { Image } from 'vant'
+import { defineComponent } from 'vue'
+import TheTitle from '../the-title'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'video-class',
+  props: {
+    video: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    }
+  },
+  setup(props) {
+    console.log(props.video)
+    return () => (
+      <>
+        {props.video.length > 0 && <TheTitle title="精品视频课" />}
+
+        <div class={styles.videoContainer}>
+          {props.video.map((item: any) => (
+            <div class={styles.videoItem}>
+              <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>
+      </>
+    )
+  }
+})

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

二進制
src/student/home/images/icon-add-cart.png


二進制
src/student/home/images/icon-address.png


二進制
src/student/home/images/icon-cart.png


二進制
src/student/home/images/icon-filter.png


二進制
src/student/home/images/icon-fire.png


二進制
src/student/home/images/icon-hotbg.png


二進制
src/student/home/images/icon-location.png


二進制
src/student/home/images/icon-mall.png


二進制
src/student/home/images/icon-message.png


二進制
src/student/home/images/icon-more.png


二進制
src/student/home/images/icon-order.png


二進制
src/student/home/images/icon-scan.png


二進制
src/student/home/images/icon-search.png


二進制
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>

二進制
src/student/home/images/icon-shop-cart.png


二進制
src/student/home/images/icon-wx.png


二進制
src/student/home/images/icon-zfb.png


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

@@ -1,4 +1,28 @@
-.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;
+    }
+  }
+}

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

@@ -1,15 +1,159 @@
-import { Button } from 'vant'
+import request from '@/helpers/request'
+import { PullRefresh, Swipe, SwipeItem, Image } from 'vant'
 import { defineComponent } from 'vue'
+import HotAlbum from './components/hot-album'
+import MenuList from './components/menu-list'
+import Music from './components/music'
+import RecommendSage from './components/recommend-sage'
+import TheHomeHeader from './components/TheHomeHeader'
+import VideoClass from './components/video-class'
 import styles from './index.module.less'
 
 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: [] // 视频课
+    }
+  },
+  mounted() {
+    this.init()
+  },
+  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'
+        )
+        console.log(video.data.videoList)
+        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
+          onCart={() => {
+            //
+          }}
+          onSearch={() => {
+            //
+          }}
+          onMore={() => {
+            //
+          }}
+        />
+
+        <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>
+                  <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} />
+        </PullRefresh>
+      </div>
     )
   }
-})
+})