Преглед на файлове

✨ feat: 添加曲目中心修改

wolyshaw преди 3 години
родител
ревизия
75ef07bac0

+ 101 - 86
src/components/col-search/index.tsx

@@ -1,86 +1,101 @@
-import { Button, Icon, Search } from 'vant'
-import { defineComponent, PropType } from 'vue'
-import styles from './index.module.less'
-import iconSearch from '@common/images/icon_search.png'
-import iconFilter from '@common/images/icon_filter.png'
-
-type inputBackground = 'default' | 'white'
-
-export default defineComponent({
-  name: 'ColSearch',
-  props: {
-    modelValue: {
-      type: String,
-      default: ''
-    },
-    showAction: {
-      type: Boolean,
-      default: false
-    },
-    placeholder: {
-      type: String,
-      default: '请输入搜索关键词'
-    },
-    background: {
-      type: String,
-      default: '#fff'
-    },
-    inputBackground: {
-      type: String as PropType<inputBackground>,
-      default: 'default'
-    },
-    onSearch: {
-      type: Function,
-      default: (val: string) => {}
-    },
-    onFilter: {
-      type: Function,
-      default: () => {}
-    }
-  },
-  data() {
-    return {
-      search: ''
-    }
-  },
-  render() {
-    return (
-      <Search
-        class={[styles['col-search'], styles[this.inputBackground]]}
-        v-model={this.search}
-        background={this.background}
-        showAction={this.showAction}
-        shape="round"
-        placeholder={this.placeholder}
-        onSearch={(val: string) => {
-          this.onSearch(val)
-        }}
-        v-slots={{
-          'left-icon': () => <Icon name={iconSearch} size={16} />,
-          'right-icon': () => (
-            <Button
-              class={styles.searchBtn}
-              round
-              type="primary"
-              size="mini"
-              onClick={() => {
-                this.onSearch(this.search)
-              }}
-            >
-              搜索
-            </Button>
-          ),
-          action: () => (
-            <Icon
-              name={iconFilter}
-              size={28}
-              onClick={() => {
-                this.onFilter()
-              }}
-            />
-          )
-        }}
-      />
-    )
-  }
-})
+import { Button, Icon, Search } from 'vant'
+import { defineComponent, PropType } from 'vue'
+import styles from './index.module.less'
+import iconSearch from '@common/images/icon_search.png'
+import iconFilter from '@common/images/icon_filter.png'
+
+type inputBackground = 'default' | 'white'
+
+export default defineComponent({
+  name: 'ColSearch',
+  props: {
+    modelValue: {
+      type: String,
+      default: ''
+    },
+    showAction: {
+      type: Boolean,
+      default: false
+    },
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+    autofocus: {
+      type: Boolean,
+      default: false
+    },
+    placeholder: {
+      type: String,
+      default: '请输入搜索关键词'
+    },
+    background: {
+      type: String,
+      default: '#fff'
+    },
+    inputBackground: {
+      type: String as PropType<inputBackground>,
+      default: 'default'
+    },
+    onSearch: {
+      type: Function,
+      default: (val: string) => {}
+    },
+    onFilter: {
+      type: Function,
+      default: () => {}
+    }
+  },
+  watch: {
+    modelValue() {
+      this.search = this.modelValue
+    }
+  },
+  data() {
+    return {
+      search: this.modelValue || ''
+    }
+  },
+  render() {
+    return (
+      <Search
+        class={[styles['col-search'], styles[this.inputBackground]]}
+        v-model={this.search}
+        background={this.background}
+        showAction={this.showAction}
+        shape="round"
+        placeholder={this.placeholder}
+        disabled={this.disabled}
+        autofocus={this.autofocus}
+        onSearch={(val: string) => {
+          this.onSearch(val)
+        }}
+        v-slots={{
+          'left-icon': () => <Icon name={iconSearch} size={16} />,
+          'right-icon': () => (
+            <Button
+              class={styles.searchBtn}
+              round
+              type="primary"
+              size="mini"
+              onClick={() => {
+                this.onSearch(this.search)
+              }}
+            >
+              搜索
+            </Button>
+          ),
+          action: () => (
+            <Icon
+              name={iconFilter}
+              size={28}
+              onClick={() => {
+                this.onFilter()
+              }}
+            />
+          )
+        }}
+      />
+    )
+  }
+})

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

@@ -146,6 +146,13 @@ export default [
         meta: {
           title: '搜索结果'
         }
+      },
+      {
+        path: '/music-personal',
+        component: () => import('@/student/music/personal'),
+        meta: {
+          title: '我的乐谱'
+        }
       }
     ]
   },

+ 27 - 6
src/student/music/album-detail/index.tsx

@@ -22,6 +22,8 @@ export default defineComponent({
     const loading = ref(false)
     const finished = ref(false)
     const isError = ref(false)
+    const favorited = ref(0)
+    const albumFavoriteCount = ref(0)
     const route = useRoute()
     const FetchList = async () => {
       if (loading.value) {
@@ -34,18 +36,32 @@ export default defineComponent({
           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
+        favorited.value = rest.favorite
+        albumFavoriteCount.value = rest.albumFavoriteCount
       } catch (error) {
         isError.value = true
       }
       loading.value = false
     }
+
+    const favoriteLoading = ref(false)
+
+    const toggleFavorite = async (id: number) => {
+      favoriteLoading.value = true
+      try {
+        await request.post('/api-student/music/album/favorite/' + id)
+        favorited.value = favorited.value === 1 ? 0 : 1
+        albumFavoriteCount.value += favorited.value ? 1 : -1
+      } catch (error) {}
+      favoriteLoading.value = false
+    }
+
     return () => {
-      const isFavorite = albumDetail.value?.favorite
+      console.log(albumFavoriteCount.value)
       return (
         <div class={styles.detail}>
           <Sticky class={styles.header}>
@@ -72,14 +88,19 @@ export default defineComponent({
               <div class={styles.footerBar}>
                 <Footer
                   musicSheetCount={albumDetail.value?.musicSheetCount}
-                  albumFavoriteCount={albumDetail.value?.albumFavoriteCount}
+                  albumFavoriteCount={albumFavoriteCount.value}
                 />
-                <Button class={styles.favoriteContaineer}>
+                <Button
+                  class={styles.favoriteContaineer}
+                  loading={favoriteLoading.value}
+                  onClick={() => toggleFavorite(albumDetail.value?.id)}
+                >
                   <Icon
+                    key={favorited.value}
                     class={styles.favorite}
-                    name={isFavorite ? FavoritedIcon : FavoriteIcon}
+                    name={favorited.value ? FavoritedIcon : FavoriteIcon}
                   />{' '}
-                  <span>{isFavorite ? '已' : ''}收藏</span>
+                  <span>{favorited.value ? '已' : ''}收藏</span>
                 </Button>
               </div>
             </div>

+ 17 - 14
src/student/music/album/footer.tsx

@@ -1,5 +1,5 @@
 import { Icon } from 'vant'
-import { defineComponent } from 'vue'
+import { defineComponent, toRefs } from 'vue'
 import CountIcon from './count.svg'
 import FavoriteIcon from './favorite.svg'
 import styles from './item.module.less'
@@ -16,18 +16,21 @@ export default defineComponent({
       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>
-    )
+  setup(props) {
+    const { musicSheetCount, albumFavoriteCount } = toRefs(props)
+    return () => {
+      return (
+        <footer class={styles.footer}>
+          <div>
+            <Icon class={styles.icon} name={CountIcon} />
+            <span>{musicSheetCount.value}首</span>
+          </div>
+          <div>
+            <Icon class={styles.icon} name={FavoriteIcon} />
+            <span>{albumFavoriteCount.value}人收藏</span>
+          </div>
+        </footer>
+      )
+    }
   }
 })

+ 56 - 15
src/student/music/album/index.tsx

@@ -1,20 +1,25 @@
 import { defineComponent, reactive, ref } from 'vue'
-import { Sticky, List } from 'vant'
+import { Sticky, List, Popup } from 'vant'
 import Search from '@/components/col-search'
 import request from '@/helpers/request'
 import Item from './item'
+import SelectTag from '../search/select-tag'
+import { useRoute } from 'vue-router'
 
 export default defineComponent({
   name: 'Album',
   setup() {
+    const route = useRoute()
     const params = reactive({
-      search: '',
+      search: (route.query.search as string) || '',
+      musicTagIds: route.query.tagids || '',
       page: 1
     })
     const data = ref<any>(null)
     const loading = ref(false)
     const finished = ref(false)
     const isError = ref(false)
+    const tagVisibility = ref(false)
 
     const onSearch = (value: string) => {
       params.page = 1
@@ -40,20 +45,56 @@ export default defineComponent({
       }
       loading.value = false
     }
+
+    const onComfirm = tags => {
+      const data = Object.values(tags)
+        .map((items: any) => items.join(','))
+        .filter(Boolean)
+        .join(',')
+      params.musicTagIds = data
+      params.page = 1
+      FetchList()
+      tagVisibility.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>
+      <>
+        <List
+          loading={loading.value}
+          finished={finished.value}
+          finished-text="没有更多了"
+          onLoad={FetchList}
+          error={isError.value}
+        >
+          <Sticky>
+            <Search
+              modelValue={params.search}
+              showAction
+              onSearch={onSearch}
+              onFilter={() => (tagVisibility.value = true)}
+            />
+          </Sticky>
+          {data.value && data.value.rows.length
+            ? data.value.rows.map(item => <Item data={item} />)
+            : null}
+        </List>
+
+        <Popup
+          show={tagVisibility.value}
+          round
+          closeable
+          position="bottom"
+          style={{ height: '60%' }}
+          teleport="body"
+          onUpdate:show={val => (tagVisibility.value = val)}
+        >
+          <SelectTag
+            defaultValue={route.query.tagids as string}
+            onComfirm={onComfirm}
+            onCancel={() => {}}
+          />
+        </Popup>
+      </>
     )
   }
 })

+ 6 - 1
src/student/music/album/item.tsx

@@ -1,5 +1,6 @@
 import { Image } from 'vant'
 import { defineComponent } from 'vue'
+import { useRouter } from 'vue-router'
 import Footer from './footer'
 import styles from './item.module.less'
 
@@ -12,8 +13,12 @@ export default defineComponent({
     }
   },
   setup({ data }) {
+    const router = useRouter()
     return () => (
-      <div class={styles.album}>
+      <div
+        class={styles.album}
+        onClick={() => router.push('/music-album-detail/' + data.id)}
+      >
         <Image class={styles.img} src={data.albumCoverUrl} />
         <div class={styles.content}>
           <h4 class="van-ellipsis">{data.albumName}</h4>

+ 54 - 15
src/student/music/list/index.tsx

@@ -1,20 +1,25 @@
 import { defineComponent, reactive, ref } from 'vue'
-import { Sticky, List } from 'vant'
+import { Sticky, List, Popup } from 'vant'
 import Search from '@/components/col-search'
 import request from '@/helpers/request'
 import Item from './item'
+import SelectTag from '../search/select-tag'
+import { useRoute } from 'vue-router'
 
 export default defineComponent({
   name: 'MusicList',
   setup() {
+    const route = useRoute()
     const params = reactive({
-      search: '',
+      search: (route.query.search as string) || '',
+      musicTagIds: route.query.tagids || '',
       page: 1
     })
     const data = ref<any>(null)
     const loading = ref(false)
     const finished = ref(false)
     const isError = ref(false)
+    const tagVisibility = ref(false)
 
     const onSearch = (value: string) => {
       params.page = 1
@@ -40,20 +45,54 @@ export default defineComponent({
       }
       loading.value = false
     }
+
+    const onComfirm = tags => {
+      const data = Object.values(tags)
+        .map((items: any) => items.join(','))
+        .filter(Boolean)
+        .join(',')
+      params.musicTagIds = data
+      params.page = 1
+      FetchList()
+      tagVisibility.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>
+      <>
+        <List
+          loading={loading.value}
+          finished={finished.value}
+          finished-text="没有更多了"
+          onLoad={FetchList}
+          error={isError.value}
+        >
+          <Sticky>
+            <Search
+              showAction
+              onSearch={onSearch}
+              onFilter={() => (tagVisibility.value = true)}
+            />
+          </Sticky>
+          {data.value && data.value.rows.length
+            ? data.value.rows.map(item => <Item data={item} />)
+            : null}
+        </List>
+        <Popup
+          show={tagVisibility.value}
+          round
+          closeable
+          position="bottom"
+          style={{ height: '60%' }}
+          teleport="body"
+          onUpdate:show={val => (tagVisibility.value = val)}
+        >
+          <SelectTag
+            onComfirm={onComfirm}
+            onCancel={() => {}}
+            defaultValue={route.query.tagids as string}
+          />
+        </Popup>
+      </>
     )
   }
 })

+ 61 - 0
src/student/music/personal/album.tsx

@@ -0,0 +1,61 @@
+import { defineComponent, reactive, ref } from 'vue'
+import { List } from 'vant'
+import request from '@/helpers/request'
+import Item from '../album/item'
+import { useRoute } from 'vue-router'
+import ColResult from '@/components/col-result'
+
+export default defineComponent({
+  name: 'MusicList',
+  setup() {
+    const route = useRoute()
+    const params = reactive({
+      search: (route.query.search as string) || '',
+      musicTagIds: route.query.tagids || '',
+      page: 1
+    })
+    const data = ref<any>(null)
+    const loading = ref(false)
+    const finished = ref(false)
+    const isError = ref(false)
+
+    const FetchList = async () => {
+      if (loading.value) {
+        return
+      }
+      loading.value = true
+      isError.value = false
+      try {
+        const res = await request('/api-student/music/album/favorite', {
+          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}
+        error={isError.value}
+      >
+        {data.value && data.value.rows.length ? (
+          data.value.rows.map(item => <Item data={item} />)
+        ) : (
+          <ColResult
+            tips="暂无收藏专辑"
+            classImgSize="SMALL"
+            btnStatus={false}
+          />
+        )}
+      </List>
+    )
+  }
+})

+ 61 - 0
src/student/music/personal/collection.tsx

@@ -0,0 +1,61 @@
+import { defineComponent, reactive, ref } from 'vue'
+import { List } from 'vant'
+import request from '@/helpers/request'
+import Item from '../list/item'
+import { useRoute } from 'vue-router'
+import ColResult from '@/components/col-result'
+
+export default defineComponent({
+  name: 'MusicList',
+  setup() {
+    const route = useRoute()
+    const params = reactive({
+      search: (route.query.search as string) || '',
+      musicTagIds: route.query.tagids || '',
+      page: 1
+    })
+    const data = ref<any>(null)
+    const loading = ref(false)
+    const finished = ref(false)
+    const isError = ref(false)
+
+    const FetchList = async () => {
+      if (loading.value) {
+        return
+      }
+      loading.value = true
+      isError.value = false
+      try {
+        const res = await request('/api-student/music/sheet/favorite', {
+          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}
+        error={isError.value}
+      >
+        {data.value && data.value.rows.length ? (
+          data.value.rows.map(item => <Item data={item} />)
+        ) : (
+          <ColResult
+            tips="暂无收藏曲目"
+            classImgSize="SMALL"
+            btnStatus={false}
+          />
+        )}
+      </List>
+    )
+  }
+})

+ 16 - 0
src/student/music/personal/index.module.less

@@ -0,0 +1,16 @@
+.personal {
+  :global {
+    .van-tab {
+      font-size: 16px !important;
+      margin-top: 15px;
+      color: #999999;
+    }
+    .van-tab--active {
+      font-size: 16px !important;
+      color: var(--van-primary);
+    }
+    .van-button--plain.van-button--primary {
+      background-color: transparent;
+    }
+  }
+}

+ 37 - 0
src/student/music/personal/index.tsx

@@ -0,0 +1,37 @@
+import { Tab, Tabs } from 'vant'
+import { defineComponent, ref } from 'vue'
+import Practice from './practice'
+import Personal from './personal'
+import Collection from './collection'
+import Album from './album'
+
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'MusicPersonal',
+  setup() {
+    const activeTab = ref('personal')
+    return () => {
+      return (
+        <div class={styles.personal}>
+          <Practice />
+          <Tabs
+            color="var(--van-primary)"
+            background="transparent"
+            lineWidth={20}
+            shrink
+            v-model:active={activeTab.value}
+            onChange={val => (activeTab.value = val)}
+          >
+            <Tab title="我的单曲" name="personal"></Tab>
+            <Tab title="收藏单曲" name="collection"></Tab>
+            <Tab title="收藏专辑" name="album"></Tab>
+          </Tabs>
+          {activeTab.value === 'personal' && <Personal />}
+          {activeTab.value === 'collection' && <Collection />}
+          {activeTab.value === 'album' && <Album />}
+        </div>
+      )
+    }
+  }
+})

+ 61 - 0
src/student/music/personal/personal.tsx

@@ -0,0 +1,61 @@
+import { defineComponent, reactive, ref } from 'vue'
+import { List } from 'vant'
+import request from '@/helpers/request'
+import Item from '../list/item'
+import { useRoute } from 'vue-router'
+import ColResult from '@/components/col-result'
+
+export default defineComponent({
+  name: 'MusicList',
+  setup() {
+    const route = useRoute()
+    const params = reactive({
+      search: (route.query.search as string) || '',
+      musicTagIds: route.query.tagids || '',
+      page: 1
+    })
+    const data = ref<any>(null)
+    const loading = ref(false)
+    const finished = ref(false)
+    const isError = ref(false)
+
+    const FetchList = async () => {
+      if (loading.value) {
+        return
+      }
+      loading.value = true
+      isError.value = false
+      try {
+        const res = await request('/api-student/music/sheet/my', {
+          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}
+        error={isError.value}
+      >
+        {data.value && data.value.rows.length ? (
+          data.value.rows.map(item => <Item data={item} />)
+        ) : (
+          <ColResult
+            tips="暂无个人曲目"
+            classImgSize="SMALL"
+            btnStatus={false}
+          />
+        )}
+      </List>
+    )
+  }
+})

+ 30 - 0
src/student/music/personal/practice.tsx

@@ -0,0 +1,30 @@
+import request from '@/helpers/request'
+import { useAsyncState } from '@vueuse/core'
+import { defineComponent } from 'vue'
+import { Cell } from 'vant'
+import Item from '../list/item'
+
+export default defineComponent({
+  name: 'Practice',
+  setup() {
+    const { isLoading, state } = useAsyncState(
+      request.get('/api-student/music/sheet/practice', {
+        params: {
+          rows: 3
+        }
+      }),
+      null
+    )
+    return () => {
+      const list: any[] = state.value?.data.rows || []
+      return (
+        <>
+          {list.length > 0 && <Cell title="最近练习" />}
+          {list.map(item => (
+            <Item data={item} />
+          ))}
+        </>
+      )
+    }
+  }
+})

+ 67 - 5
src/student/music/search/index.tsx

@@ -1,5 +1,5 @@
-import { Sticky, Cell, Tag, Icon } from 'vant'
-import { defineComponent, ref } from 'vue'
+import { Sticky, Cell, Tag, Icon, Popup } from 'vant'
+import { defineComponent, onMounted, ref } from 'vue'
 import Search from '@/components/col-search'
 import { useLocalStorage } from '@vueuse/core'
 import AlbumItem from '../album/item'
@@ -7,17 +7,36 @@ import MusicItem from '../list/item'
 import styles from './index.module.less'
 import classNames from 'classnames'
 import request from '@/helpers/request'
+import SelectTag from './select-tag'
 
 export default defineComponent({
   name: 'MusicSearch',
   setup() {
     const loading = ref(false)
     const keyword = ref('')
+    const tagids = ref('')
+    const albumRows = ref([])
+    const sheetRows = 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', {})
+      try {
+        const res = await request.post(
+          '/api-student/music/sheet/albumAndSheetList',
+          {
+            data: {
+              albumRow: 3,
+              sheetRow: 10,
+              search: keyword.value,
+              musicTagIds: tagids.value
+            }
+          }
+        )
+        albumRows.value = res.data.musicAlbumList.rows
+        sheetRows.value = res.data.musicSheetList.rows
+      } catch (error) {}
+      loading.value = false
     }
     const onSearch = val => {
       keyword.value = val
@@ -29,7 +48,20 @@ export default defineComponent({
         words.value.unshift(val)
         words.value.length = Math.min(words.value.length, 5)
       }
+      FetchList()
     }
+
+    const onComfirm = tags => {
+      const data = Object.values(tags)
+        .map((items: any) => items.join(','))
+        .filter(Boolean)
+        .join(',')
+      tagids.value = data
+      FetchList()
+      tagVisibility.value = false
+    }
+
+    onMounted(() => onSearch(''))
     return () => {
       return (
         <div class={styles.search}>
@@ -37,6 +69,7 @@ export default defineComponent({
             <Search
               modelValue={keyword.value}
               showAction
+              autofocus
               onSearch={onSearch}
               onFilter={() => (tagVisibility.value = true)}
             />
@@ -66,16 +99,45 @@ export default defineComponent({
             class={styles.title}
             title="专辑"
             is-link
-            to="/music-album"
+            to={{
+              path: '/music-album',
+              query: {
+                search: keyword.value,
+                tagids: tagids.value
+              }
+            }}
             value="更多"
           />
+          {albumRows.value.map(item => (
+            <AlbumItem data={item} />
+          ))}
           <Cell
             class={styles.title}
             title="曲谱"
             is-link
-            to="/music-list"
+            to={{
+              path: '/music-list',
+              query: {
+                search: keyword.value,
+                tagids: tagids.value
+              }
+            }}
             value="更多"
           />
+          {sheetRows.value.map(item => (
+            <MusicItem data={item} />
+          ))}
+          <Popup
+            show={tagVisibility.value}
+            round
+            closeable
+            position="bottom"
+            style={{ height: '60%' }}
+            teleport="body"
+            onUpdate:show={val => (tagVisibility.value = val)}
+          >
+            <SelectTag onComfirm={onComfirm} onCancel={() => {}} />
+          </Popup>
         </div>
       )
     }

+ 81 - 6
src/student/music/search/select-tag.tsx

@@ -1,16 +1,91 @@
 import request from '@/helpers/request'
 import { useAsyncState } from '@vueuse/core'
-import { defineComponent } from 'vue'
+import { defineComponent, reactive, watch } from 'vue'
+import { Button } from 'vant'
+import SelectTagsChild from './select-tags-child'
+import styles from './select.module.less'
 
 export default defineComponent({
   name: 'SelectTag',
-  setup() {
+  props: {
+    onComfirm: {
+      type: Function,
+      default: () => {}
+    },
+    onCancel: {
+      type: Function,
+      default: () => {}
+    },
+    defaultValue: {
+      type: String,
+      default: ''
+    }
+  },
+  setup({ onCancel, onComfirm, defaultValue }) {
     const { isLoading, state } = useAsyncState(
-      request.post('/api-student/MusicTag/tree', {
-        data: {}
-      }),
+      request('/api-student/MusicTag/tree'),
       null
     )
-    return () => {}
+    const resetTags = () => {
+      for (const key in tags) {
+        if (Object.prototype.hasOwnProperty.call(tags, key)) {
+          delete tags[key]
+        }
+      }
+      onCancel()
+    }
+
+    const defaultTags = (defaultValue || '').split(',').map(id => Number(id))
+    const tags: { [key in string]: number[] } = reactive({})
+    watch(state, () => {
+      if (state.value) {
+        const list = (state.value && state.value.data) || []
+        for (const item of list) {
+          const allids = item.children.map(c => c.id)
+          tags[item.id] = defaultTags.filter(id => {
+            return allids.includes(Number(id))
+          })
+        }
+      }
+    })
+    return () => {
+      const list = (state.value && state.value.data) || []
+      return (
+        <div class={styles.select}>
+          <h4 class={styles.title}>全部标签</h4>
+          <div class={styles.content}>
+            {list.map(item => {
+              return (
+                <div class={styles.list} key={item.id}>
+                  <div class={styles.tit}>{item.name}</div>
+                  <SelectTagsChild
+                    key={item.id}
+                    // @ts-ignore
+                    selected={tags[item.id] || []}
+                    child={item.children || []}
+                    onSelect={val => {
+                      tags[item.id] = val
+                    }}
+                  />
+                </div>
+              )
+            })}
+          </div>
+          <footer class="van-safe-area-bottom van-hairline--top">
+            <Button class={styles.btn} round onClick={resetTags}>
+              重置
+            </Button>
+            <Button
+              class={styles.btn}
+              type="primary"
+              round
+              onClick={() => onComfirm(tags)}
+            >
+              确认
+            </Button>
+          </footer>
+        </div>
+      )
+    }
   }
 })

+ 57 - 0
src/student/music/search/select-tags-child.tsx

@@ -0,0 +1,57 @@
+import { defineComponent } from 'vue'
+import { Tag, CheckboxGroup, Checkbox } from 'vant'
+import styles from './select.module.less'
+import classNames from 'classnames'
+
+export default defineComponent({
+  name: 'SelectTagChild',
+  props: {
+    child: {
+      type: Array,
+      default: () => []
+    },
+    onSelect: {
+      type: Function,
+      default: () => {}
+    }
+  },
+  setup({ child, onSelect }, { attrs }) {
+    return () => {
+      const selected = attrs.selected as number[]
+      return (
+        <CheckboxGroup
+          class={classNames(styles.childContent, styles['radio-group'])}
+          modelValue={selected}
+          onUpdate:modelValue={val => {
+            onSelect(val)
+          }}
+        >
+          <Checkbox name={0} class={styles.radio} onClick={() => onSelect([])}>
+            <Tag
+              class={classNames(styles.item, 'van-ellipsis')}
+              type="primary"
+              plain={selected.length !== 0}
+              round
+              size="large"
+            >
+              全部
+            </Tag>
+          </Checkbox>
+          {child.map((item: any) => (
+            <Checkbox key={item.id} name={item.id} class={styles.radio}>
+              <Tag
+                class={classNames(styles.item, 'van-ellipsis')}
+                plain={!selected.includes(item.id)}
+                type="primary"
+                round
+                size="large"
+              >
+                {item.name}
+              </Tag>
+            </Checkbox>
+          ))}
+        </CheckboxGroup>
+      )
+    }
+  }
+})

+ 89 - 0
src/student/music/search/select.module.less

@@ -0,0 +1,89 @@
+.select {
+  padding: 0 16px;
+
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  > div {
+    flex: 1;
+    overflow: hidden;
+    overflow-y: auto;
+  }
+  > footer {
+    padding: 10px 0;
+    display: flex;
+    justify-content: space-between;
+  }
+}
+.title {
+  font-size: 18px;
+  font-weight: bold;
+  color: black;
+  padding-top: 19px;
+  text-align: center;
+}
+
+.tit {
+  color: #333;
+  line-height: 22px;
+  font-size: 16px;
+  margin: 20px 0;
+  margin-top: 10px;
+}
+
+.childContent {
+  display: flex;
+  flex-wrap: wrap;
+  text-align: center;
+  .item {
+    display: block;
+    margin-right: 5px;
+    margin-bottom: 10px;
+    width: 80px;
+    height: 32px;
+    &:nth-child(4n + 0) {
+      margin-right: 0;
+    }
+  }
+}
+
+.radio-group {
+  display: flex;
+  margin-top: 14px;
+  .radio:first-child {
+    :global {
+      .van-radio__label {
+        margin-left: 0;
+      }
+    }
+  }
+}
+
+.radio {
+  box-sizing: border-box;
+  :global {
+    .van-radio__icon,
+    .van-checkbox__icon {
+      display: none;
+    }
+    .van-checkbox__label {
+      margin-left: 0;
+    }
+    .van-tag--large {
+      // width: 94px;
+      // height: 30px;
+      font-size: 14px;
+      text-align: center;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+    .van-tag {
+      box-sizing: border-box;
+    }
+  }
+}
+
+.btn {
+  width: 164px;
+}

+ 7 - 1
src/student/music/songbook/album.tsx

@@ -4,6 +4,7 @@ import request from '@/helpers/request'
 import { useAsyncState } from '@vueuse/core'
 import styles from './index.module.less'
 import classNames from 'classnames'
+import { useRouter } from 'vue-router'
 
 export default defineComponent({
   name: 'Songbook',
@@ -14,12 +15,17 @@ export default defineComponent({
       }),
       null
     )
+    const router = useRouter()
     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.album}
+                key={item.id}
+                onClick={() => router.push('/music-album-detail/' + item.id)}
+              >
                 <div class={styles.main}>
                   <Image class={styles.img} src={item.albumCoverUrl} />
                   <p class={styles.favorite}>

+ 8 - 2
src/student/music/songbook/index.tsx

@@ -4,14 +4,20 @@ import Search from '@/components/col-search'
 import Album from './album'
 import List from './list'
 import styles from './index.module.less'
+import { useRouter } from 'vue-router'
 
 export default defineComponent({
   name: 'Songbook',
   setup() {
+    const router = useRouter()
     return () => (
       <div class={styles.songbook}>
-        <Sticky>
-          <Search showAction />
+        <Sticky
+          onClick={() => {
+            router.push('/music-search')
+          }}
+        >
+          <Search disabled showAction />
         </Sticky>
         <Cell
           class={styles.title}