瀏覽代碼

添加曲谱相关页面

wolyshaw 3 年之前
父節點
當前提交
b90b572f66

+ 33 - 0
package-lock.json

@@ -2835,6 +2835,29 @@
       "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.26.tgz",
       "integrity": "sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA=="
     },
+    "@vueuse/core": {
+      "version": "8.4.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-8.4.1.tgz",
+      "integrity": "sha512-YRM2+wZj/XWzch44sgFaRWtTGEZ8xgTsleaGy6cuULIU1q6o9Z/XHDHqofzNXrEqEhN6EtJqM4m8puURFO0nzg==",
+      "requires": {
+        "@vueuse/metadata": "8.4.1",
+        "@vueuse/shared": "8.4.1",
+        "vue-demi": "*"
+      }
+    },
+    "@vueuse/metadata": {
+      "version": "8.4.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-8.4.1.tgz",
+      "integrity": "sha512-OMwadiewrAHgJAgCh5zrbJMXySECB09cnEnIWRicvTWBiqBm14N1t464oeV1fhskRPEBsLSzxQmDoVBAqQh4rQ=="
+    },
+    "@vueuse/shared": {
+      "version": "8.4.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-8.4.1.tgz",
+      "integrity": "sha512-yzWNzvqaTGkc25fNsGkaF3pnMdXDax3EasYgPKlC4/eXSo0TqwG+xLz0Y8t6KN52x/kIHlpSwtry4LXfw7LSBA==",
+      "requires": {
+        "vue-demi": "*"
+      }
+    },
     "acorn": {
       "version": "8.7.0",
       "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.7.0.tgz",
@@ -3173,6 +3196,11 @@
       "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==",
       "dev": true
     },
+    "classnames": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
+      "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
+    },
     "clean-deep": {
       "version": "3.4.0",
       "resolved": "https://registry.npmmirror.com/clean-deep/-/clean-deep-3.4.0.tgz",
@@ -7013,6 +7041,11 @@
       "resolved": "https://registry.npmmirror.com/vue-cropper/-/vue-cropper-1.0.3.tgz",
       "integrity": "sha512-yDrZkE4H5vOiMA9WQHE+6rmXrZ1S9TMZasEPAZPKg/2I/nySHL4ECD1lNxt7+ofTPKT+9+2sQkCwagPqEqiqJg=="
     },
+    "vue-demi": {
+      "version": "0.12.5",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.12.5.tgz",
+      "integrity": "sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q=="
+    },
     "vue-eslint-parser": {
       "version": "8.0.1",
       "resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-8.0.1.tgz",

+ 2 - 0
package.json

@@ -22,7 +22,9 @@
   },
   "dependencies": {
     "@vant/use": "^1.3.6",
+    "@vueuse/core": "^8.4.1",
     "browserslist": "^4.20.2",
+    "classnames": "^2.3.1",
     "clean-deep": "^3.4.0",
     "dayjs": "^1.10.7",
     "loaders.css": "^0.1.2",

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

@@ -111,6 +111,41 @@ export default [
         meta: {
           title: '老师风采'
         }
+      },
+      {
+        path: '/music-songbook',
+        component: () => import('@/student/music/songbook'),
+        meta: {
+          title: '乐谱库'
+        }
+      },
+      {
+        path: '/music-album',
+        component: () => import('@/student/music/album'),
+        meta: {
+          title: '专辑'
+        }
+      },
+      {
+        path: '/music-album-detail/:id',
+        component: () => import('@/student/music/album-detail'),
+        meta: {
+          title: '专辑详情'
+        }
+      },
+      {
+        path: '/music-list',
+        component: () => import('@/student/music/list'),
+        meta: {
+          title: '曲谱列表'
+        }
+      },
+      {
+        path: '/music-search',
+        component: () => import('@/student/music/search'),
+        meta: {
+          title: '搜索结果'
+        }
       }
     ]
   },

二進制
src/student/music/album-detail/header-bg.png


+ 79 - 0
src/student/music/album-detail/index.module.less

@@ -0,0 +1,79 @@
+.detail {
+  background: url(./header-bg.png) no-repeat top center;
+  background-attachment: fixed;
+}
+.img {
+  width: 94px;
+  height: 94px;
+  margin-right: 18px;
+  position: relative;
+  > img,
+  > div {
+    position: absolute;
+    border-radius: 10px;
+    overflow: hidden;
+  }
+  &::before {
+    content: '';
+    width: 80px;
+    height: 80px;
+    border-radius: 9px;
+    background-color: var(--music-list-item-background-color);
+    box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.08);
+    position: absolute;
+    right: -6px;
+    top: 8px;
+  }
+}
+.detailContent {
+  background-color: white;
+  padding: 0 14px;
+  border-radius: 17px 17px 0px 0px;
+  .main {
+    padding-top: 24px;
+    padding-bottom: 28px;
+    display: flex;
+  }
+  .favoriteContaineer {
+    border: none;
+    color: var(--music-list-item-mate-color);
+    :global(.van-button__text) {
+      display: flex;
+      align-items: center;
+    }
+    > span {
+      display: inline-block;
+      line-height: 16px;
+      margin-top: 1px;
+    }
+  }
+  .favorite {
+    font-size: 16px;
+    margin-right: 5px;
+  }
+  .content {
+    flex: 1;
+    > h4 {
+      color: var(--music-list-item-title-color);
+      font-size: 14px;
+      height: 20px;
+      line-height: 20px;
+      margin-top: 7px;
+    }
+    > p {
+      margin-top: 6px;
+      font-size: 12px;
+      color: var(--music-list-item-desc-color);
+      line-height: 17px;
+      height: 51px;
+    }
+  }
+}
+.footerBar {
+  padding: 13px 0;
+  display: flex;
+  justify-content: space-between;
+  > footer {
+    margin-top: 0;
+  }
+}

+ 101 - 0
src/student/music/album-detail/index.tsx

@@ -0,0 +1,101 @@
+import { defineComponent, reactive, ref } from 'vue'
+import { useRoute } from 'vue-router'
+import request from '@/helpers/request'
+import ColHeader from '@/components/col-header'
+import { Button, Icon, Image, List, Sticky } from 'vant'
+import classNames from 'classnames'
+import Footer from '../album/footer'
+import FavoriteIcon from '../album/favorite.svg'
+import FavoritedIcon from '../album/favorited.svg'
+import styles from './index.module.less'
+import Item from '../list/item'
+
+export default defineComponent({
+  name: 'AlbumDetail',
+  setup() {
+    const params = reactive({
+      search: '',
+      page: 1
+    })
+    const albumDetail = ref<any>(null)
+    const data = ref<any>(null)
+    const loading = ref(false)
+    const finished = ref(false)
+    const isError = ref(false)
+    const route = useRoute()
+    const FetchList = async () => {
+      if (loading.value) {
+        return
+      }
+      loading.value = true
+      isError.value = false
+      try {
+        const res = await request.post('/api-student/music/album/detail', {
+          data: { id: route.params.id, ...params }
+        })
+        const { musicSheetList, ...rest } = res.data
+        console.log(rest)
+        albumDetail.value = rest
+        data.value = musicSheetList
+        params.page = musicSheetList.pageNo + 1
+        finished.value = musicSheetList.pageNo >= musicSheetList.totalPage
+      } catch (error) {
+        isError.value = true
+      }
+      loading.value = false
+    }
+    return () => {
+      const isFavorite = albumDetail.value?.favorite
+      return (
+        <div class={styles.detail}>
+          <Sticky class={styles.header}>
+            <ColHeader
+              class={styles.header}
+              background="transparent"
+              color="#fff"
+              title="专辑详情"
+              backIconColor="white"
+              border={false}
+              isFixed={false}
+            />
+            <div class={styles.detailContent}>
+              <div class={classNames(styles.main, 'van-hairline--bottom')}>
+                <Image
+                  class={styles.img}
+                  src={albumDetail.value?.albumCoverUrl}
+                />
+                <div class={styles.content}>
+                  <h4>{albumDetail.value?.albumName}</h4>
+                  <p>{albumDetail.value?.albumDesc}</p>
+                </div>
+              </div>
+              <div class={styles.footerBar}>
+                <Footer
+                  musicSheetCount={albumDetail.value?.musicSheetCount}
+                  albumFavoriteCount={albumDetail.value?.albumFavoriteCount}
+                />
+                <Button class={styles.favoriteContaineer}>
+                  <Icon
+                    class={styles.favorite}
+                    name={isFavorite ? FavoritedIcon : FavoriteIcon}
+                  />{' '}
+                  <span>{isFavorite ? '已' : ''}收藏</span>
+                </Button>
+              </div>
+            </div>
+          </Sticky>
+          <List
+            loading={loading.value}
+            finished={finished.value}
+            finished-text="没有更多了"
+            onLoad={FetchList}
+          >
+            {data.value && data.value.rows.length
+              ? data.value.rows.map(item => <Item data={item} />)
+              : null}
+          </List>
+        </div>
+      )
+    }
+  }
+})

+ 19 - 0
src/student/music/album/count.svg

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="15px" height="15px" viewBox="0 0 15 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>曲目数量</title>
+    <g id="页面" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="专辑" transform="translate(-136.000000, -236.000000)" fill="#DADADA" fill-rule="nonzero">
+            <g id="编组-18" transform="translate(14.000000, 151.000000)">
+                <g id="编组-9" transform="translate(10.000000, 10.000000)">
+                    <g id="编组-2" transform="translate(112.000000, 4.000000)">
+                        <g id="编组-8" transform="translate(0.000000, 70.000000)">
+                            <g id="曲目数量" transform="translate(0.000000, 1.000000)">
+                                <path d="M7.5,13.7325 C4.0600867,13.7276789 1.27288113,10.9399142 1.26875,7.5 C1.27356953,4.06057395 4.06057395,1.27356953 7.5,1.26875 C10.9399963,1.27219516 13.7278048,4.06000369 13.73125,7.5 C13.7264305,10.9394261 10.9394261,13.7264305 7.5,13.73125 L7.5,13.7325 Z M7.5,0 C3.35957804,0.00413422372 0.00413422372,3.35957804 0,7.5 C0.00482172375,11.6401368 3.35986324,14.9951783 7.5,15 C11.6401368,14.9951783 14.9951783,11.6401368 15,7.5 C15,3.36375 11.635,0 7.5,0 Z M5.65,2.3825 C4.01426316,2.97269069 2.75969986,4.30996224 2.275,5.98 C2.25875001,6.04 2.05625001,6.6825 2.72375,6.81375 C3.39,6.94375 3.51375,6.26875 3.56125,6.13 C3.97801767,4.92223188 4.92787577,3.97375706 6.13625,3.55875 C6.245,3.52125 6.85374999,3.22875 6.64125,2.6725 C6.42875,2.11625001 5.75125,2.3475 5.6525,2.38375 L5.6525,2.3825 L5.65,2.3825 Z M7.5,8.695 C7.0636448,8.70956018 6.65401361,8.48523833 6.43126245,8.10973895 C6.20851129,7.73423958 6.20802302,7.26720878 6.42998854,6.89124446 C6.65195405,6.51528014 7.0611153,6.29010226 7.4975,6.30375 C7.92487896,6.30330342 8.32003165,6.53089472 8.53410789,6.90079247 C8.74818412,7.27069021 8.74866062,7.72669814 8.53535789,8.09704247 C8.32205516,8.4673868 7.92737896,8.69580342 7.5,8.69625 L7.5,8.695 Z M7.5,5.0325 C6.13809049,5.0325 5.03398032,6.13642406 5.03375031,7.49833355 C5.03352037,8.86024304 6.13725766,9.96453975 7.49916709,9.96500001 C8.86107653,9.96545999 9.96555973,8.86190934 9.96625,7.5 C9.96418334,6.13898713 8.86101375,5.03637667 7.5,5.035 L7.5,5.0325 Z" id="形状"></path>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

文件差異過大導致無法顯示
+ 11 - 0
src/student/music/album/favorite.svg


+ 13 - 0
src/student/music/album/favorited.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="专辑详情" transform="translate(-299.000000, -245.000000)" fill="#FFC459" fill-rule="nonzero">
+            <g id="编组-9" transform="translate(276.000000, 245.000000)">
+                <g id="已收藏" transform="translate(23.000000, 0.000000)">
+                    <path d="M13.0588435,16 C12.8351907,16 12.5806892,15.9398798 12.3493243,15.8196393 L9.00224436,14.1888778 L5.63202796,15.8196393 C5.41608732,15.9323647 5.17701019,15.992485 4.92250872,15.992485 C4.59859776,15.992485 4.28239897,15.8947896 4.0278975,15.7069138 C3.55745539,15.3612224 3.31837826,14.7675351 3.42634858,14.2189379 L4.11273132,10.739479 L1.4674585,8.34218437 C1.05871372,7.93637275 0.904470402,7.35771543 1.05871372,6.80911824 L1.06642588,6.79408818 C1.25923002,6.23046092 1.73738429,5.83967936 2.31579672,5.76452906 L5.98678758,5.11072144 L7.62947887,1.8491984 C7.89169251,1.33066132 8.4315441,1 9.00224436,1 C9.59608112,1 10.151357,1.34569138 10.382722,1.85671343 L12.0254133,5.11072144 L15.6964042,5.73446894 C16.2748166,5.81713427 16.760683,6.22294589 16.9226385,6.77905812 C17.1077305,7.32014028 16.9534872,7.91382766 16.5370302,8.31963928 L16.5293181,8.32715431 L13.8994696,10.746994 L14.5627158,14.2339679 C14.6706861,14.7900802 14.4393212,15.3537074 13.9688791,15.7069138 C13.6989533,15.8947896 13.3827545,16 13.0588435,16 Z" id="路径"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 33 - 0
src/student/music/album/footer.tsx

@@ -0,0 +1,33 @@
+import { Icon } from 'vant'
+import { defineComponent } from 'vue'
+import CountIcon from './count.svg'
+import FavoriteIcon from './favorite.svg'
+import styles from './item.module.less'
+
+export default defineComponent({
+  name: 'AlbumFooter',
+  props: {
+    musicSheetCount: {
+      type: Number,
+      default: 0
+    },
+    albumFavoriteCount: {
+      type: Number,
+      default: 0
+    }
+  },
+  setup({ musicSheetCount, albumFavoriteCount }) {
+    return () => (
+      <footer class={styles.footer}>
+        <div>
+          <Icon class={styles.icon} name={CountIcon} />
+          <span>{musicSheetCount}首</span>
+        </div>
+        <div>
+          <Icon class={styles.icon} name={FavoriteIcon} />
+          <span>{albumFavoriteCount}人收藏</span>
+        </div>
+      </footer>
+    )
+  }
+})

+ 59 - 0
src/student/music/album/index.tsx

@@ -0,0 +1,59 @@
+import { defineComponent, reactive, ref } from 'vue'
+import { Sticky, List } from 'vant'
+import Search from '@/components/col-search'
+import request from '@/helpers/request'
+import Item from './item'
+
+export default defineComponent({
+  name: 'Album',
+  setup() {
+    const params = reactive({
+      search: '',
+      page: 1
+    })
+    const data = ref<any>(null)
+    const loading = ref(false)
+    const finished = ref(false)
+    const isError = ref(false)
+
+    const onSearch = (value: string) => {
+      params.page = 1
+      params.search = value
+      FetchList()
+    }
+
+    const FetchList = async () => {
+      if (loading.value) {
+        return
+      }
+      loading.value = true
+      isError.value = false
+      try {
+        const res = await request.post('/api-student/music/album/list', {
+          data: params
+        })
+        data.value = res.data
+        params.page = res.data.pageNo + 1
+        finished.value = res.data.pageNo >= res.data.totalPage
+      } catch (error) {
+        isError.value = true
+      }
+      loading.value = false
+    }
+    return () => (
+      <List
+        loading={loading.value}
+        finished={finished.value}
+        finished-text="没有更多了"
+        onLoad={FetchList}
+      >
+        <Sticky>
+          <Search showAction onSearch={onSearch} />
+        </Sticky>
+        {data.value && data.value.rows.length
+          ? data.value.rows.map(item => <Item data={item} />)
+          : null}
+      </List>
+    )
+  }
+})

+ 66 - 0
src/student/music/album/item.module.less

@@ -0,0 +1,66 @@
+.album {
+  margin: 12px 14px;
+  padding: 10px;
+  background-color: var(--music-list-item-background-color);
+  border-radius: 10px;
+  display: flex;
+  .img {
+    width: 94px;
+    height: 94px;
+    margin-right: 18px;
+    position: relative;
+    > img,
+    > div {
+      position: absolute;
+      border-radius: 10px;
+      overflow: hidden;
+    }
+    &::before {
+      content: '';
+      width: 80px;
+      height: 80px;
+      border-radius: 9px;
+      background-color: var(--music-list-item-background-color);
+      box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.08);
+      position: absolute;
+      right: -6px;
+      top: 8px;
+    }
+  }
+  .content {
+    flex: 1;
+    > h4 {
+      color: var(--music-list-item-title-color);
+      font-size: 14px;
+      height: 20px;
+      line-height: 20px;
+    }
+    > p {
+      margin-top: 6px;
+      font-size: 12px;
+      color: var(--music-list-item-desc-color);
+      line-height: 17px;
+      height: 34px;
+    }
+  }
+}
+
+.footer {
+  margin-top: 11px;
+  display: flex;
+  > div {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 12px;
+    color: var(--music-list-item-mate-color);
+    margin-right: 18px;
+    .icon {
+      margin-right: 5px;
+    }
+    span {
+      display: block;
+      margin-top: 1px;
+    }
+  }
+}

+ 29 - 0
src/student/music/album/item.tsx

@@ -0,0 +1,29 @@
+import { Image } from 'vant'
+import { defineComponent } from 'vue'
+import Footer from './footer'
+import styles from './item.module.less'
+
+export default defineComponent({
+  name: 'AlbumItem',
+  props: {
+    data: {
+      type: Object,
+      default: {}
+    }
+  },
+  setup({ data }) {
+    return () => (
+      <div class={styles.album}>
+        <Image class={styles.img} src={data.albumCoverUrl} />
+        <div class={styles.content}>
+          <h4 class="van-ellipsis">{data.albumName}</h4>
+          <p class="van-multi-ellipsis--l2">{data.albumDesc}</p>
+          <Footer
+            musicSheetCount={data.musicSheetCount}
+            albumFavoriteCount={data.albumFavoriteCount}
+          />
+        </div>
+      </div>
+    )
+  }
+})

二進制
src/student/music/list/icons/init-user-icon.png


二進制
src/student/music/list/icons/music-icon.png


+ 59 - 0
src/student/music/list/index.tsx

@@ -0,0 +1,59 @@
+import { defineComponent, reactive, ref } from 'vue'
+import { Sticky, List } from 'vant'
+import Search from '@/components/col-search'
+import request from '@/helpers/request'
+import Item from './item'
+
+export default defineComponent({
+  name: 'MusicList',
+  setup() {
+    const params = reactive({
+      search: '',
+      page: 1
+    })
+    const data = ref<any>(null)
+    const loading = ref(false)
+    const finished = ref(false)
+    const isError = ref(false)
+
+    const onSearch = (value: string) => {
+      params.page = 1
+      params.search = value
+      FetchList()
+    }
+
+    const FetchList = async () => {
+      if (loading.value) {
+        return
+      }
+      loading.value = true
+      isError.value = false
+      try {
+        const res = await request.post('/api-student/music/sheet/list', {
+          data: params
+        })
+        data.value = res.data
+        params.page = res.data.pageNo + 1
+        finished.value = res.data.pageNo >= res.data.totalPage
+      } catch (error) {
+        isError.value = true
+      }
+      loading.value = false
+    }
+    return () => (
+      <List
+        loading={loading.value}
+        finished={finished.value}
+        finished-text="没有更多了"
+        onLoad={FetchList}
+      >
+        <Sticky>
+          <Search showAction onSearch={onSearch} />
+        </Sticky>
+        {data.value && data.value.rows.length
+          ? data.value.rows.map(item => <Item data={item} />)
+          : null}
+      </List>
+    )
+  }
+})

+ 78 - 0
src/student/music/list/item.module.less

@@ -0,0 +1,78 @@
+.item {
+  background-color: var(--music-list-item-background-color);
+  margin: 10px 14px;
+  padding: 10px;
+  border-radius: 9px;
+  .header {
+    display: flex;
+    align-items: center;
+    border-bottom: 1px solid var(--music-list-item-border-color);
+    padding-bottom: 12px;
+    .mate {
+      display: flex;
+      flex: 1;
+      align-items: center;
+      .icon {
+        width: 40px;
+        height: 40px;
+      }
+      .info {
+        margin-left: 14px;
+        > h4 {
+          color: var(--music-list-item-title-color);
+          font-size: 14px;
+          font-weight: 600;
+        }
+        > p {
+          color: var(--music-list-item-mate-color);
+          line-height: 17px;
+        }
+      }
+    }
+    .btn {
+      width: 54px;
+      height: 22px;
+      font-size: 12px;
+      border-radius: 11px;
+      padding: 0;
+      border: none;
+      &.vip {
+        background-color: var(--music-list-item-vip-bg);
+        color: var(--music-list-item-vip-color);
+      }
+      &.free {
+        background-color: var(--music-list-item-free-bg);
+        color: var(--music-list-item-free-color);
+      }
+      &.charge {
+        background-color: var(--music-list-item-charge-bg);
+        color: var(--music-list-item-charge-color);
+      }
+    }
+  }
+  .footer {
+    display: flex;
+    padding-top: 8px;
+    align-items: center;
+    justify-content: space-between;
+    .user {
+      display: flex;
+      align-items: center;
+      padding: 0 10px;
+      .userIcon {
+        width: 20px;
+        height: 20px;
+        margin-right: 8px;
+      }
+    }
+    .favorite {
+      font-size: 16px;
+    }
+    .tags {
+      display: flex;
+      align-items: center;
+      --van-tag-default-color: #fff1de;
+      --van-tag-text-color: #ff8c00;
+    }
+  }
+}

+ 67 - 0
src/student/music/list/item.tsx

@@ -0,0 +1,67 @@
+import { defineComponent } from 'vue'
+import { Button, Icon, Image, Tag } from 'vant'
+import classNames from 'classnames'
+import MusicIcon from './icons/music-icon.png'
+import InitUserIcon from './icons/init-user-icon.png'
+import FavoriteIcon from '../album/favorite.svg'
+import styles from './item.module.less'
+
+const chargeTypes = {
+  CHARGE: '点播',
+  FREE: '免费',
+  VIP: 'VIP'
+}
+
+export default defineComponent({
+  name: 'MusicItem',
+  props: {
+    data: {
+      type: Object,
+      default: {}
+    }
+  },
+  setup({ data }) {
+    return () => (
+      <div class={styles.item}>
+        <header class={styles.header}>
+          <div class={styles.mate}>
+            <Image src={MusicIcon} round class={styles.icon} />
+            <div class={styles.info}>
+              <h4>{data.musicSheetName}</h4>
+              <p>{data.composer}</p>
+            </div>
+          </div>
+          <div class={styles.buttons}>
+            <Button
+              class={classNames(
+                styles.btn,
+                styles[data.chargeType.toLocaleLowerCase()]
+              )}
+            >
+              {chargeTypes[data.chargeType]}
+              <Icon name="arrow" />
+            </Button>
+          </div>
+        </header>
+        <footer class={styles.footer}>
+          <div class={styles.user}>
+            <Image
+              round
+              src={data.addUserAvatar || InitUserIcon}
+              class={styles.userIcon}
+            />
+            <p>{data.addName}</p>
+            <div class={styles.tags}>
+              {(data.subjectNames || '').split(',').map(item => (
+                <Tag>{item}</Tag>
+              ))}
+            </div>
+          </div>
+          <div class={styles.icons}>
+            <Icon class={styles.favorite} name={FavoriteIcon} />
+          </div>
+        </footer>
+      </div>
+    )
+  }
+})

+ 37 - 0
src/student/music/search/index.module.less

@@ -0,0 +1,37 @@
+.search {
+  --van-cell-background-color: transparent;
+  --van-cell-font-size: 16px;
+  --van-cell-text-color: #333;
+  --van-cell-value-color: #999;
+  --van-cell-icon-size: 10px;
+  .title {
+    padding-top: 16px;
+    :global(.van-cell__value) {
+      font-size: 12px;
+    }
+  }
+  .keywords {
+    margin-top: 10px;
+    padding: 0 14px;
+    padding-bottom: 10px;
+    display: flex;
+    align-items: center;
+    .content {
+      flex: 1;
+      overflow: hidden;
+      overflow-x: auto;
+      display: flex;
+      .searchKeyword {
+        --van-tag-default-color: white;
+        --van-tag-text-color: #333;
+        font-size: 14px;
+        padding: 4px 10px;
+        margin-right: 5px;
+      }
+    }
+
+    .remove {
+      font-size: 16px;
+    }
+  }
+}

+ 83 - 0
src/student/music/search/index.tsx

@@ -0,0 +1,83 @@
+import { Sticky, Cell, Tag, Icon } from 'vant'
+import { defineComponent, ref } from 'vue'
+import Search from '@/components/col-search'
+import { useLocalStorage } from '@vueuse/core'
+import AlbumItem from '../album/item'
+import MusicItem from '../list/item'
+import styles from './index.module.less'
+import classNames from 'classnames'
+import request from '@/helpers/request'
+
+export default defineComponent({
+  name: 'MusicSearch',
+  setup() {
+    const loading = ref(false)
+    const keyword = ref('')
+    const tagVisibility = ref(false)
+    const words = useLocalStorage<string[]>('music-search', [])
+    const FetchList = async () => {
+      loading.value = true
+      await request.post('/api-student/music/sheet/albumAndSheetList', {})
+    }
+    const onSearch = val => {
+      keyword.value = val
+      const indexOf = words.value.indexOf(val)
+      if (indexOf > -1) {
+        words.value.splice(indexOf, 1)
+      }
+      if (val) {
+        words.value.unshift(val)
+        words.value.length = Math.min(words.value.length, 5)
+      }
+    }
+    return () => {
+      return (
+        <div class={styles.search}>
+          <Sticky>
+            <Search
+              modelValue={keyword.value}
+              showAction
+              onSearch={onSearch}
+              onFilter={() => (tagVisibility.value = true)}
+            />
+          </Sticky>
+          {words.value.length > 0 && (
+            <div class={classNames(styles.keywords, 'van-hairline--bottom')}>
+              <div class={styles.content}>
+                {words.value.map(item => (
+                  <Tag
+                    round
+                    class={styles.searchKeyword}
+                    key={item}
+                    onClick={() => onSearch(item)}
+                  >
+                    {item}
+                  </Tag>
+                ))}
+              </div>
+              <Icon
+                class={styles.remove}
+                name="delete-o"
+                onClick={() => (words.value = [])}
+              />
+            </div>
+          )}
+          <Cell
+            class={styles.title}
+            title="专辑"
+            is-link
+            to="/music-album"
+            value="更多"
+          />
+          <Cell
+            class={styles.title}
+            title="曲谱"
+            is-link
+            to="/music-list"
+            value="更多"
+          />
+        </div>
+      )
+    }
+  }
+})

+ 16 - 0
src/student/music/search/select-tag.tsx

@@ -0,0 +1,16 @@
+import request from '@/helpers/request'
+import { useAsyncState } from '@vueuse/core'
+import { defineComponent } from 'vue'
+
+export default defineComponent({
+  name: 'SelectTag',
+  setup() {
+    const { isLoading, state } = useAsyncState(
+      request.post('/api-student/MusicTag/tree', {
+        data: {}
+      }),
+      null
+    )
+    return () => {}
+  }
+})

+ 53 - 0
src/student/music/songbook/album.tsx

@@ -0,0 +1,53 @@
+import { defineComponent } from 'vue'
+import { Image, Skeleton } from 'vant'
+import request from '@/helpers/request'
+import { useAsyncState } from '@vueuse/core'
+import styles from './index.module.less'
+import classNames from 'classnames'
+
+export default defineComponent({
+  name: 'Songbook',
+  setup() {
+    const { isLoading, state } = useAsyncState(
+      request.post('/api-student/music/album/list', {
+        data: {}
+      }),
+      null
+    )
+    return () => (
+      <>
+        {state.value && state.value.data.rows.length ? (
+          <div class={styles.albumContainer}>
+            {state.value.data.rows.map(item => (
+              <div class={styles.album} key={item.id}>
+                <div class={styles.main}>
+                  <Image class={styles.img} src={item.albumCoverUrl} />
+                  <p class={styles.favorite}>
+                    <span>{item.albumFavoriteCount}</span>人收藏
+                  </p>
+                </div>
+                <h4 class={classNames(styles.title, 'van-ellipsis')}>
+                  {item.albumName}
+                </h4>
+              </div>
+            ))}
+          </div>
+        ) : null}
+        {isLoading.value && (
+          <div style={{ display: 'flex' }}>
+            {Array.from({ length: 4 }).map(() => (
+              <Skeleton
+                style={{ width: '96px', flexDirection: 'column' }}
+                avatar
+                avatar-shape="square"
+                avatar-size="96px"
+                title
+                titleWidth="100%"
+              />
+            ))}
+          </div>
+        )}
+      </>
+    )
+  }
+})

+ 75 - 0
src/student/music/songbook/index.module.less

@@ -0,0 +1,75 @@
+.songbook {
+  --van-cell-background-color: transparent;
+  --van-cell-font-size: 16px;
+  --van-cell-text-color: #333;
+  --van-cell-value-color: #999;
+  --van-cell-icon-size: 10px;
+
+  --favorite-bg: rgba(67, 67, 67, 0.3);
+  --favorite-color: #63ffe1;
+  --favorite-text-color: white;
+}
+.title {
+  padding-top: 16px;
+  :global(.van-cell__value) {
+    font-size: 12px;
+  }
+}
+.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;
+    .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;
+      }
+      &::before {
+        content: '';
+        width: 80px;
+        height: 80px;
+        border-radius: 9px;
+        background-color: #fff;
+        box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.08);
+        position: absolute;
+        right: -6px;
+        top: 8px;
+      }
+    }
+    .title {
+      width: 94px;
+      text-align: center;
+    }
+  }
+}

+ 35 - 0
src/student/music/songbook/index.tsx

@@ -0,0 +1,35 @@
+import { defineComponent } from 'vue'
+import { Cell, Sticky } from 'vant'
+import Search from '@/components/col-search'
+import Album from './album'
+import List from './list'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'Songbook',
+  setup() {
+    return () => (
+      <div class={styles.songbook}>
+        <Sticky>
+          <Search showAction />
+        </Sticky>
+        <Cell
+          class={styles.title}
+          title="专辑"
+          is-link
+          to="/music-album"
+          value="更多"
+        />
+        <Album />
+        <Cell
+          class={styles.title}
+          title="曲谱"
+          is-link
+          to="/music-list"
+          value="更多"
+        />
+        <List />
+      </div>
+    )
+  }
+})

+ 26 - 0
src/student/music/songbook/list.tsx

@@ -0,0 +1,26 @@
+import { defineComponent } from 'vue'
+import { Image } from 'vant'
+import request from '@/helpers/request'
+import { useAsyncState } from '@vueuse/core'
+import Item from '../list/item'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'Songbook',
+  setup() {
+    const { isLoading, state } = useAsyncState(
+      request.post('/api-student/music/sheet/list', {
+        data: {}
+      }),
+      null
+    )
+    return () =>
+      state.value && state.value.data.rows.length ? (
+        <div class={styles.listContainer}>
+          {state.value.data.rows.map(item => (
+            <Item data={item} />
+          ))}
+        </div>
+      ) : null
+  }
+})

+ 14 - 0
src/styles/index.less

@@ -135,3 +135,17 @@ body {
   transform: translateZ(0);
   -webkit-transform: translateZ(0);
 }
+
+:root {
+  --music-list-item-background-color: #fff;
+  --music-list-item-title-color: #333;
+  --music-list-item-desc-color: #333;
+  --music-list-item-mate-color: #6a6a6a;
+  --music-list-item-border-color: #f1f1f1;
+  --music-list-item-vip-bg: #fff1cd;
+  --music-list-item-vip-color: #ff6c00;
+  --music-list-item-free-bg: #fff1e7;
+  --music-list-item-free-color: #ff4700;
+  --music-list-item-charge-bg: #e1f0ff;
+  --music-list-item-charge-color: #0086ff;
+}

部分文件因文件數量過多而無法顯示