فهرست منبع

Merge remote-tracking branch 'origin/iteration-login' into startLogin

mo 1 سال پیش
والد
کامیت
31bfc73036

+ 1 - 3
src/components/TheSearch/index.tsx

@@ -25,9 +25,7 @@ export default defineComponent({
         placeholder="请输入搜索关键词"
         clearable
         v-model:value={searchData.value}
-        onClear={() =>
-          emit('search', searchData.value ? searchData.value.trim() : '')
-        }
+        onClear={() => emit('search', '')}
         onKeyup={(e: KeyboardEvent) => {
           e.stopPropagation();
           if (e.code === 'Enter') {

+ 1 - 0
src/components/upload-file/copper.tsx

@@ -84,6 +84,7 @@ export default defineComponent({
           imageSmoothingQuality: 'high'
         })
         .toBlob((blob: any) => {
+          console.log(blob, '1212');
           emit('cropperOk', blob);
           state.confirmLoading = false;
         });

+ 28 - 0
src/utils/index.ts

@@ -4,6 +4,7 @@ import { NIcon, NTag } from 'naive-ui';
 import { PageEnum } from '@/enums/pageEnum';
 import { isObject } from './is/index';
 import { cloneDeep } from 'lodash';
+import dayjs from 'dayjs';
 /**
  * render 图标
  * */
@@ -292,3 +293,30 @@ export function scrollToErrorForm() {
     behavior: 'smooth'
   });
 }
+
+export const getTimes = (
+  times: any,
+  keys: Array<string> = [],
+  format = 'YYYY-MM-DD'
+) => {
+  if (times && times.length) {
+    return format == 'YYYY-MM-DD'
+      ? {
+          [keys[0] || 'start']: dayjs(times[0]).isValid()
+            ? dayjs(times[0]).format(format) + ' 00:00:00'
+            : '',
+          [keys[1] || 'end']: dayjs(times[1]).isValid()
+            ? dayjs(times[1]).format(format) + ' 23:59:59'
+            : ''
+        }
+      : {
+          [keys[0] || 'start']: dayjs(times[0]).isValid()
+            ? dayjs(times[0]).format(format)
+            : '',
+          [keys[1] || 'end']: dayjs(times[1]).isValid()
+            ? dayjs(times[1]).format(format)
+            : ''
+        };
+  }
+  return {};
+};

+ 1 - 0
src/views/attend-class/index.tsx

@@ -736,6 +736,7 @@ export default defineComponent({
                 item={item}
                 isActive={popupData.activeIndex === index}
                 isCollect={false}
+                isShowCollect={false}
                 onClick={(item: any) => {
                   popupData.open = false;
                   toggleMaterial(item.id);

+ 8 - 0
src/views/classList/api.ts

@@ -105,3 +105,11 @@ export const getStudentDetail = (params: any) => {
     requestType: 'form'
   });
 };
+/*
+ * 上课记录
+ */
+export const courseSchedulePage = (params: any) => {
+  return request.post('/edu-app/courseSchedule/page', {
+    data: params
+  });
+};

+ 8 - 6
src/views/classList/classDetail.tsx

@@ -1,16 +1,16 @@
 import { defineComponent, ref } from 'vue';
 import styles from './index.module.less';
-import { NTabs, NTabPane, NSpace } from 'naive-ui';
-import { useRoute, useRouter } from 'vue-router';
+import { NTabs, NTabPane } from 'naive-ui';
+import { useRoute } from 'vue-router';
 import CBreadcrumb from '@/components/CBreadcrumb';
 import ClassStudent from './components/classStudent';
 import AfterWork from './components/afterWork';
+import ClassRecord from './components/classRecord';
 import TestRecode from './components/testRecode';
 export default defineComponent({
   name: 'base-setting',
-  setup(props, { emit, attrs }) {
-    const activeTab = ref('student');
-    const router = useRouter();
+  setup() {
+    const activeTab = ref('attendclass');
     const route = useRoute();
     const routerList = ref([
       { name: '班级管理', path: '/classList' },
@@ -36,7 +36,9 @@ export default defineComponent({
             <NTabPane name="practice" tab="练习记录">
               <TestRecode></TestRecode>
             </NTabPane>
-            <NTabPane name="attendclass" tab="上课记录"></NTabPane>
+            <NTabPane name="attendclass" tab="上课记录">
+              <ClassRecord />
+            </NTabPane>
           </NTabs>
         </div>
       </div>

+ 64 - 0
src/views/classList/components/classRecord.module.less

@@ -0,0 +1,64 @@
+.tableContainer {
+  background: #F7F9FF;
+  border-radius: 16px;
+  width: 548px;
+  height: 195px;
+}
+
+.header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 20px;
+  font-size: 20px;
+  color: #131415;
+  line-height: 28px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+
+  .time {
+    font-size: 20px;
+    color: #131415;
+    line-height: 28px;
+  }
+
+  .ntag {
+    color: #2089FF;
+    border-radius: 6px;
+    // border: 1px solid #97C7FF;
+    font-size: 16px;
+    --n-border: 1px solid #97C7FF;
+    padding: 4px 14px;
+  }
+}
+
+.content {
+  padding: 20px;
+  display: flex;
+  align-items: center;
+
+  .navatar {
+    width: 60px;
+    height: 60px;
+    border-radius: 50%;
+    padding: 2px;
+    border: 1px solid #198CFE;
+    margin-right: 15px;
+    flex-shrink: 0;
+  }
+
+  .userInfo {
+    h2 {
+      font-size: 20px;
+      font-weight: 600;
+      color: #131415;
+      line-height: 28px;
+    }
+
+    p {
+      font-size: 16px;
+      color: #777777;
+      line-height: 26px;
+    }
+  }
+
+}

+ 132 - 0
src/views/classList/components/classRecord.tsx

@@ -0,0 +1,132 @@
+import { defineComponent, onMounted, reactive } from 'vue';
+import { NAvatar, NButton, NForm, NFormItem, NSpace, NTag } from 'naive-ui';
+import Pagination from '@/components/pagination';
+import { courseSchedulePage } from '../api';
+import { useRoute } from 'vue-router';
+import CDatePicker from '/src/components/CDatePicker';
+import styles from './classRecord.module.less';
+import teacherIcon from '@components/layout/images/teacherIcon.png';
+import dayjs from 'dayjs';
+import { getTimes } from '/src/utils';
+export default defineComponent({
+  name: 'class-record',
+  setup() {
+    const nowTime = dayjs().format('YYYY-MM-DD');
+    const state = reactive({
+      searchForm: {
+        createTimer: [
+          dayjs(nowTime).subtract(1, 'month').valueOf(),
+          dayjs(nowTime).valueOf()
+        ] as any
+      },
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      tableList: [] as any
+    });
+    const route = useRoute();
+    const search = () => {
+      state.pagination.page = 1;
+      getList();
+    };
+
+    const onReset = () => {
+      state.searchForm = { createTimer: null as any };
+      search();
+    };
+    const getList = async () => {
+      state.loading = true;
+      try {
+        const res = await courseSchedulePage({
+          classGroupId: route.query.id,
+          ...getTimes(
+            state.searchForm.createTimer,
+            ['startTime', 'endTime'],
+            'YYYY-MM-DD'
+          ),
+          ...state.pagination
+        });
+        state.tableList = res.data.rows;
+        state.pagination.pageTotal = res.data.total;
+        state.loading = false;
+      } catch (e) {
+        state.loading = false;
+        console.log(e);
+      }
+    };
+    onMounted(() => {
+      getList();
+    });
+    return () => (
+      <div>
+        <div class={styles.searchList}>
+          <NForm label-placement="left" inline>
+            <NFormItem>
+              <CDatePicker
+                v-model:timerValue={state.searchForm.createTimer}
+                separator={'-'}
+                type="daterange"></CDatePicker>
+            </NFormItem>
+
+            <NFormItem>
+              <NSpace justify="end">
+                <NButton type="primary" class="searchBtn" onClick={search}>
+                  搜索
+                </NButton>
+                <NButton
+                  type="primary"
+                  ghost
+                  class="resetBtn"
+                  onClick={onReset}>
+                  重置
+                </NButton>
+              </NSpace>
+            </NFormItem>
+          </NForm>
+        </div>
+        <div class={styles.tableWrap}>
+          <NSpace>
+            {state.tableList.map((item: any) => (
+              <div class={styles.tableContainer}>
+                <div class={styles.header}>
+                  <div class={styles.time}>
+                    {dayjs(item.classDate).format('YYYY-MM-DD')}
+                  </div>
+                  <NTag type="primary" class={styles.ntag} strong>
+                    三年二班
+                  </NTag>
+                </div>
+                <div class={styles.content}>
+                  <NAvatar
+                    class={styles.navatar}
+                    round
+                    src={item.teacherAvatar || teacherIcon}
+                  />
+                  <div class={styles.userInfo}>
+                    <h2>{item.teacherName}</h2>
+                    <p>
+                      人教版二年级上册 | 第二十一单元
+                      |【歌表演】我和我的祖国一刻也不能分割
+                    </p>
+                  </div>
+                </div>
+              </div>
+            ))}
+          </NSpace>
+
+          <Pagination
+            v-model:page={state.pagination.page}
+            v-model:pageSize={state.pagination.rows}
+            v-model:pageTotal={state.pagination.pageTotal}
+            onList={getList}
+            sync
+            saveKey="classRecord-key"
+          />
+        </div>
+      </div>
+    );
+  }
+});

+ 15 - 27
src/views/natural-resources/components/my-resources/upload-modal/upload-file.tsx

@@ -223,6 +223,9 @@ export default defineComponent({
     };
 
     const onFinish = (options: any) => {
+      onFinishAfter(options);
+    };
+    const onFinishAfter = async (options: any) => {
       const url = ossUploadUrl + state.key;
       const type = formatUrlType(url);
       let coverImg = '';
@@ -232,8 +235,7 @@ export default defineComponent({
         coverImg = PageEnum.SONG_DEFAULT_COVER;
       } else if (type === 'VIDEO') {
         // 获取视频封面图
-        // await getVideoCoverImg();
-        coverImg = 'https://gyt.ks3-cn-beijing.ksyuncs.com/1688997532875.png';
+        coverImg = await getVideoCoverImg();
       }
       emit('update:fileList', url);
       emit('readFileInputEventAsArrayBuffer', tempFiileBuffer.value);
@@ -246,26 +248,9 @@ export default defineComponent({
       visiable.value = false;
       btnLoading.value = false;
     };
-
-    // 获取文件流
-    const base64ImgtoFile = (dataurl: string, filename = 'file') => {
-      const arr: any = dataurl.split(',');
-      const mime = arr[0].match(/:(.*?);/)[1];
-      const suffix = mime.split('/')[1];
-      const bstr = atob(arr[1]);
-      let n = bstr.length;
-      const u8arr = new Uint8Array(n);
-      while (n--) {
-        u8arr[n] = bstr.charCodeAt(n);
-      }
-      return new File([u8arr], `${filename}.${suffix}`, {
-        type: mime
-      });
-    };
-
     const getVideoMsg = (file: any) => {
       return new Promise(resolve => {
-        let dataURL = '';
+        // let dataURL = '';
         const videoElement = document.createElement('video');
         videoElement.currentTime = 1;
         videoElement.src = URL.createObjectURL(file);
@@ -276,8 +261,12 @@ export default defineComponent({
           canvas.width = width;
           canvas.height = height;
           canvas.getContext('2d').drawImage(videoElement, 0, 0, width, height); //绘制canvas
-          dataURL = canvas.toDataURL('image/jpeg'); //转换为base64
-          resolve(dataURL);
+          // dataURL = canvas.toDataURL('image/jpeg'); //转换为base64
+          console.log(canvas);
+          canvas.toBlob((blob: any) => {
+            console.log(blob);
+            resolve(blob);
+          });
         });
       });
     };
@@ -286,8 +275,7 @@ export default defineComponent({
       try {
         btnLoading.value = true;
         const imgBlob: any = await getVideoMsg(tempFiileBuffer.value);
-        console.log(imgBlob, 'downloading');
-        const fileName = `${props.path}${Date.now() + '.jpeg'}`;
+        const fileName = `${props.path}${Date.now() + '.png'}`;
         const obj = {
           filename: fileName,
           bucketName: props.bucketName,
@@ -304,6 +292,7 @@ export default defineComponent({
           policy: data.policy,
           signature: data.signature,
           key: fileName,
+          acl: 'public-read',
           KSSAccessKeyId: data.kssAccessKeyId,
           name: fileName
         } as any;
@@ -311,12 +300,11 @@ export default defineComponent({
         for (const key in fileParams) {
           formData.append(key, fileParams[key]);
         }
-        console.log(base64ImgtoFile(imgBlob), 'base64ImgtoFile(imgBlob)');
-        formData.append('file', base64ImgtoFile(imgBlob), fileName);
+
+        formData.append('file', imgBlob);
         await axios.post(ossUploadUrl, formData);
 
         const url = ossUploadUrl + fileName;
-        console.log(url, '12312312');
         return url;
       } finally {
         btnLoading.value = false;

+ 4 - 4
src/views/prepare-lessons/api.ts

@@ -125,10 +125,10 @@ export const courseScheduleStart = (params: any) => {
 };
 
 /**
- * 备课 - 查询标签树列表
+ * 备课 - 音乐教材版本
  */
-export const musicTagTree = (params?: any) => {
-  return request.get('/edu-app/musicTag/tree', {
-    params: params
+export const bookVersionPage = (params?: any) => {
+  return request.post('/edu-app/bookVersion/page', {
+    data: params
   });
 };

+ 7 - 4
src/views/prepare-lessons/components/directory-main/select-lessonware/index.tsx

@@ -18,7 +18,7 @@ import AddTeaching, {
 import {
   lessonCoursewarePage,
   lessonCoursewareRemove,
-  musicTagTree
+  bookVersionPage
 } from '../../../api';
 import iconUploadBg from '../images/icon-upload-bg.svg';
 import { useCatchStore } from '/src/store/modules/catchData';
@@ -92,9 +92,12 @@ export default defineComponent({
     onMounted(async () => {
       // 获取教材分类列表
       try {
-        const { data } = await musicTagTree({});
-        console.log(data);
-        const temp = data || [];
+        const { data } = await bookVersionPage({
+          page: 1,
+          rows: 99,
+          type: 'COURSEWARE'
+        });
+        const temp = data.rows || [];
         temp.forEach((item: any) => {
           forms.musicTagList.push({
             id: item.id,

+ 2 - 1
src/views/prepare-lessons/components/lesson-main/courseware/index.tsx

@@ -153,7 +153,8 @@ export default defineComponent({
       await catchStore.getSubjects();
 
       const subjectList = catchStore.getSubjectList;
-      if (subjectList.length > 0) {
+      // 并且没有声部时才会更新
+      if (subjectList.length > 0 && !prepareStore.getSubjectId) {
         prepareStore.setSubjectId(subjectList[0].id);
       }
 

+ 1 - 1
src/views/prepare-lessons/components/lesson-main/train/index.tsx

@@ -146,7 +146,7 @@ export default defineComponent({
       await catchStore.getSubjects();
 
       const subjectList = catchStore.getSubjectList;
-      if (subjectList.length > 0) {
+      if (subjectList.length > 0 && !prepareStore.getSubjectId) {
         prepareStore.setSubjectId(subjectList[0].id);
       }
 

+ 3 - 2
src/views/prepare-lessons/components/resource-main/components/select-music/index.tsx

@@ -25,7 +25,7 @@ export default defineComponent({
         musicSheetCategoriesId: '',
         status: 1,
         versionFlag: false,
-        subjectId: null
+        musicSubject: null
       },
       tableList: [] as any,
       editStatus: false,
@@ -40,7 +40,7 @@ export default defineComponent({
         const { data } = await musicSheetPage({
           ...state.searchGroup,
           ...state.pagination,
-          subjectId: prepareStore.getSubjectId
+          musicSubject: prepareStore.getSubjectId
         });
         state.loading = false;
         const tempRows = data.rows || [];
@@ -149,6 +149,7 @@ export default defineComponent({
                   {state.tableList.map((item: any) => (
                     <CardType
                       isShowAdd
+                      isShowCollect={false}
                       item={item}
                       onAdd={(child: any) => onAdd(child)}
                     />

+ 11 - 0
src/views/prepare-lessons/index.tsx

@@ -4,6 +4,7 @@ import DirectoryList from './components/directory-main';
 import LessonMain from './components/lesson-main';
 import ResourceMain from './components/resource-main';
 import { useResizeObserver } from '@vueuse/core';
+import { onBeforeRouteLeave } from 'vue-router';
 import { usePrepareStore } from '/src/store/modules/prepareLessons';
 
 export default defineComponent({
@@ -24,6 +25,16 @@ export default defineComponent({
         }
       );
     });
+
+    // 当前页面离开时
+    onBeforeRouteLeave(() => {
+      // 离开时恢复默认
+      prepareStore.setTabType('courseware');
+      prepareStore.setSelectMusicStatus(false);
+      prepareStore.setSelectResourceStatus(false);
+      prepareStore.setIsAddResource(false);
+      prepareStore.setIsAddTrain(false);
+    });
     return () => (
       <div class={styles.prepareLessons}>
         {/* 左侧目录 */}

+ 1 - 0
src/views/prepare-lessons/model/select-music/index.tsx

@@ -114,6 +114,7 @@ export default defineComponent({
                       {state.tableList.map((item: any) => (
                         <CardType
                           isShowAdd
+                          isShowCollect={false}
                           item={item}
                           onAdd={() => emit('add', item)}
                         />

+ 23 - 1
src/views/xiaoku-ai/api.ts

@@ -1,8 +1,30 @@
 import request from '@/utils/request';
 
 /**
- * 老师列表
+ * 曲谱分类列表
  */
 export const api_musicTagTree = () => {
   return request.get('/edu-app/musicTag/tree');
 };
+/**
+ * 曲谱教材列表分页
+ */
+export const api_musicSheetCategoriesPage = (params: any) => {
+  return request.post('/edu-app/musicSheetCategories/page', {
+    data: params
+  });
+};
+/**
+ * 乐器列表
+ */
+export const api_subjectList = () => {
+  return request.post('/edu-app/subject/list');
+};
+/**
+ * 曲谱列表分页
+ */
+export const api_musicSheetPage = (params: any) => {
+  return request.post('/edu-app/musicSheet/page', {
+    data: params
+  });
+};

+ 1 - 0
src/views/xiaoku-ai/index.module.less

@@ -33,6 +33,7 @@
 
         :global {
             .n-button {
+                min-width: 102px;
                 height: 37px;
                 padding: 0 24px;
                 font-size: 18px;

+ 164 - 55
src/views/xiaoku-ai/index.tsx

@@ -1,54 +1,111 @@
-import {
-  TransitionGroup,
-  computed,
-  defineComponent,
-  onMounted,
-  reactive,
-  ref
-} from 'vue';
+import { defineComponent, onMounted, reactive } from 'vue';
 import styles from './index.module.less';
 import TheSearch from '@/components/TheSearch';
-import { NButton, NImage, NSpace } from 'naive-ui';
+import { NButton, NImage, NSpace, NSpin } from 'naive-ui';
 import { useRouter } from 'vue-router';
-import mock from './mock.json';
-import { api_musicTagTree } from './api';
+import { api_musicSheetCategoriesPage, api_musicTagTree } from './api';
 
 export default defineComponent({
   name: 'XiaokuAi',
   setup() {
     const router = useRouter();
+    const forms = reactive({
+      musicTagIds: [] as any[],
+      keyword: '',
+      page: 1,
+      rows: 9999
+    });
     const data = reactive({
       tags: [] as any[],
-      tagIndex: 0,
+      tagChildren: [] as any[],
+      tagActiveId: '',
       tagActive: {} as any,
-      list: mock.list
+      list: [] as any,
+      loading: false
     });
     const getTags = async () => {
       const res = await api_musicTagTree();
-      if (Array.isArray(res?.data)) {
+      if (Array.isArray(res?.data) && res.data.length) {
         data.tags = res.data;
-        const list = renderTag(res.data);
-        console.log(list);
+        data.tagActiveId = res.data[0].id;
+        const list: any[] = [];
+        renderTag(res.data[0].children, list);
+        data.tagChildren = list;
+      }
+    };
+
+    const getList = async () => {
+      data.loading = true;
+      const res = await api_musicSheetCategoriesPage({
+        ...forms,
+        musicTagIds: [data.tagActiveId, ...forms.musicTagIds].filter(Boolean)
+      });
+      if (Array.isArray(res?.data?.rows)) {
+        data.list = res.data.rows;
       }
+      data.loading = false;
     };
 
     // 递归渲染标签
-    const renderTag = (_data: any[]): any => {
+    const renderTag = (_data: any[], _list: any[]): any => {
       const item = {
         columnName: _data[0].columnName,
-        list: _data.map((item: any) => {
-          return {
-            name: item.name,
-            list: Array.isArray(item.children) ? renderTag(item.children) : []
-          };
-        })
+        list: [] as any[]
       };
-
-      return item;
+      const childrens = [];
+      for (let i = 0; i < _data.length; i++) {
+        item.list.push({
+          name: _data[i].name,
+          id: _data[i].id,
+          activeIndex: -1
+        });
+        if (_data[i].children) {
+          childrens.push(..._data[i].children);
+        }
+      }
+      _list.push(item);
+      if (childrens.length) {
+        renderTag(childrens, _list);
+      }
     };
-    onMounted(() => {
-      getTags();
+    onMounted(async () => {
+      await getTags();
+      getList();
     });
+
+    /** 改变顶级分类 */
+    const changeTag = (item: any, index: number) => {
+      data.tagActiveId = item.id;
+      forms.musicTagIds = [];
+      const list: any[] = [];
+      renderTag(data.tags[index].children, list);
+      data.tagChildren = list;
+      getList();
+    };
+
+    /** 选中子选项 */
+    const selectChildTag = (columnIndex: number, index: number) => {
+      const oldActiveItem =
+        data.tagChildren[columnIndex].list[
+          data.tagChildren[columnIndex].activeIndex
+        ];
+      const activeItem = data.tagChildren[columnIndex].list[index];
+      if (oldActiveItem && oldActiveItem.id !== activeItem.id) {
+        forms.musicTagIds = forms.musicTagIds.filter(
+          item => item !== oldActiveItem.id
+        );
+      }
+      if (forms.musicTagIds.includes(activeItem.id)) {
+        forms.musicTagIds = forms.musicTagIds.filter(
+          item => item !== activeItem.id
+        );
+        data.tagChildren[columnIndex].activeIndex = -1;
+      } else {
+        forms.musicTagIds.push(activeItem.id);
+        data.tagChildren[columnIndex].activeIndex = index;
+      }
+      getList();
+    };
     return () => (
       <div class={styles.container}>
         <div class={styles.tools}>
@@ -62,7 +119,13 @@ export default defineComponent({
                 {data.tags.map((item: any, index: number) => {
                   return (
                     <>
-                      <NButton round secondary type={'default'}>
+                      <NButton
+                        round
+                        secondary
+                        type={
+                          data.tagActiveId === item.id ? 'primary' : 'default'
+                        }
+                        onClick={() => changeTag(item, index)}>
                         {item.name}
                       </NButton>
                     </>
@@ -70,37 +133,83 @@ export default defineComponent({
                 })}
               </NSpace>
             </div>
-          </div>
-          <TheSearch round />
-        </div>
-        {/* <div class={styles.content}>
-          <NSpace size={[50, 40]}>
-            {data.list.map((item, index) => {
+
+            {data.tagChildren.map((column: any, columnIndex: number) => {
               return (
-                <div
-                  class={styles.item}
-                  key={`item-${index}`}
-                  onClick={() => router.push({ path: '/xiaoku-music' })}>
-                  <div class={styles.cover}>
-                    <div class={styles.itemImg}>
-                      <div class={styles.itemBg}></div>
-                      <NImage
-                        objectFit="cover"
-                        src={item.src}
-                        lazy
-                        previewDisabled={true}
-                        onLoad={e => {
-                          (e.target as any).dataset.loaded = 'true';
-                        }}
-                      />
-                    </div>
-                  </div>
-                  <div class={styles.itemName}>{item.name}</div>
+                <div class={styles.tags}>
+                  <NSpace size={[24, 12]}>
+                    <NButton quaternary disabled>
+                      {column.columnName}
+                    </NButton>
+
+                    {column.list.map((item: any, index: number) => {
+                      return (
+                        <>
+                          <NButton
+                            round
+                            secondary
+                            type={
+                              column.activeIndex === index
+                                ? 'primary'
+                                : 'default'
+                            }
+                            onClick={() => selectChildTag(columnIndex, index)}>
+                            {item.name}
+                          </NButton>
+                        </>
+                      );
+                    })}
+                  </NSpace>
                 </div>
               );
             })}
-          </NSpace>
-        </div> */}
+          </div>
+          <TheSearch
+            round
+            onSearch={val => {
+              forms.keyword = val;
+              getList();
+            }}
+          />
+        </div>
+        <NSpin show={data.loading}>
+          <div class={styles.content}>
+            <NSpace size={[50, 40]}>
+              {data.list.map((item: any, index: number) => {
+                return (
+                  <div
+                    class={styles.item}
+                    key={`item-${index}`}
+                    onClick={() =>
+                      router.push({
+                        path: '/xiaoku-music',
+                        query: {
+                          id: item.id,
+                          name: item.name
+                        }
+                      })
+                    }>
+                    <div class={styles.cover}>
+                      <div class={styles.itemImg}>
+                        <div class={styles.itemBg}></div>
+                        <NImage
+                          objectFit="cover"
+                          src={item.coverImg}
+                          lazy
+                          previewDisabled={true}
+                          onLoad={e => {
+                            (e.target as any).dataset.loaded = 'true';
+                          }}
+                        />
+                      </div>
+                    </div>
+                    <div class={styles.itemName}>{item.name}</div>
+                  </div>
+                );
+              })}
+            </NSpace>
+          </div>
+        </NSpin>
       </div>
     );
   }

+ 0 - 124
src/views/xiaoku-ai/mock.json

@@ -1,124 +0,0 @@
-{
-    "list": [
-        {
-            "name": "一年级上册(简谱)",
-            "category": "1",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879396909941.png"
-        },
-        {
-            "name": "一年级下册(简谱)",
-            "category": "1",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879397066202.png"
-        },
-        {
-            "name": "一年级上册",
-            "category": "2",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879399237211.png"
-        },
-        {
-            "name": "一年级下册",
-            "category": "2",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879399268072.png"
-        },
-        {
-            "name": "二年级上册(简谱)",
-            "category": "1",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879397152383.png"
-        },
-        {
-            "name": "二年级下册(简谱)",
-            "category": "1",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879397257464.png"
-        },
-        {
-            "name": "三年级上册(简谱)",
-            "category": "1",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879397333805.png"
-        },
-        {
-            "name": "三年级下册(简谱)",
-            "category": "1",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879397403546.png"
-        },
-        {
-            "name": "四年级上册(简谱)",
-            "category": "1",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879397449747.png"
-        },
-        {
-            "name": "四年级下册(简谱)",
-            "category": "1",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879397477658.png"
-        },
-        {
-            "name": "三年级下册",
-            "category": "2",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879399375956.png"
-        },
-        {
-            "name": "四年级上册",
-            "category": "2",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879399407587.png"
-        },
-        {
-            "name": "四年级下册",
-            "category": "2",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879399431318.png"
-        },
-        {
-            "name": "五年级上册",
-            "category": "2",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879399462639.png"
-        },
-        {
-            "name": "五年级上册(简谱)",
-            "category": "1",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879397501719.png"
-        },
-        {
-            "name": "五年级下册(简谱)",
-            "category": "1",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/168793975222810.png"
-        },
-        {
-            "name": "六年级上册(简谱)",
-            "category": "1",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/168793975447311.png"
-        },
-        {
-            "name": "六年级下册(简谱)",
-            "category": "1",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/168793975664712.png"
-        },
-        {
-            "name": "二年级上册",
-            "category": "2",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879399291733.png"
-        },
-        {
-            "name": "二年级下册",
-            "category": "2",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879399318924.png"
-        },
-        {
-            "name": "三年级上册",
-            "category": "2",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/16879399348525.png"
-        },
-        {
-            "name": "五年级下册",
-            "category": "2",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/168793994852610.png"
-        },
-        {
-            "name": "六年级上册",
-            "category": "2",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/168793995083811.png"
-        },
-        {
-            "name": "六年级下册",
-            "category": "2",
-            "src": "https://daya.ks3-cn-beijing.ksyuncs.com/168793995297612.png"
-        }
-    ]
-}

+ 1 - 3
src/views/xiaoku-music/component/play-item/index.tsx

@@ -15,8 +15,6 @@ import icon_next from '../../images/icon_next.svg';
 import icon_play from '../../images/icon_play.svg';
 import icon_pause from '../../images/icon_pause.svg';
 import { getSecondRPM } from '/src/utils';
-import icon_favitor from '/src/common/images/icon-collect-default.png';
-import icon_favitorActive from '/src/common/images/icon-collect-active.png';
 import TheNoticeBar from '/src/components/TheNoticeBar';
 
 export default defineComponent({
@@ -173,7 +171,7 @@ export default defineComponent({
             <div class={styles.time}>{time.value}</div>
             <audio
               ref={audioRef}
-              src={props.item.audioFileUrl}
+              src={props.item.audioFileUrl || props.item.metronomeUrl}
               onLoadedmetadata={onLoadedmetadata}
               onTimeupdate={() => {
                 if (timer) return;

+ 45 - 12
src/views/xiaoku-music/index.module.less

@@ -1,4 +1,6 @@
 .container {
+    display: flex;
+    flex-direction: column;
     height: 100%;
 
     :global {
@@ -33,6 +35,7 @@
 
     &> :global(.n-space) {
         height: 36Px;
+        flex-shrink: 0;
     }
 
     .separator {
@@ -43,11 +46,15 @@
 
 .wrap {
     padding-top: 20Px;
-    height: calc(100% - 36Px);
+    flex: 1;
+    transition: padding .3s;
+    overflow: hidden;
 }
 
 .content {
-    height: calc(100% - 20Px);
+    display: flex;
+    flex-direction: column;
+    height: 100%;
     background: #DDF2FF;
     border-radius: 20Px;
 }
@@ -58,10 +65,6 @@
     align-items: center;
     flex-shrink: 0;
 
-    .tags {
-        margin-right: 15Px;
-    }
-
     :global {
         .n-input {
             margin-left: auto;
@@ -71,10 +74,10 @@
 }
 
 .contentWrap {
-    height: calc(100% - 82Px);
+    flex: 1;
     display: flex;
     padding: 0 20Px 20Px;
-    transition: padding .3s;
+    overflow: hidden;
 }
 
 .musicList {
@@ -92,9 +95,29 @@
     .wrapList {
         width: 512px;
         min-width: 294Px;
+        min-height: 300Px;
         background: #fff;
         border-radius: 16Px;
-        padding: 8Px;
+    }
+
+    .empty {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        min-height: 300Px;
+    }
+}
+
+.itemContainer {
+    border-radius: 16Px;
+    padding: 4Px 8Px;
+
+    &:first-child {
+        padding-top: 8Px;
+    }
+
+    &:last-child {
+        padding-bottom: 8Px;
     }
 }
 
@@ -103,8 +126,8 @@
     display: flex;
     align-items: center;
     padding: 10Px;
-    margin: 4Px 0;
     border-radius: 12Px;
+
     cursor: pointer;
 
     &:hover {
@@ -152,6 +175,7 @@
         display: flex;
         flex-direction: column;
         align-items: flex-start;
+
         .titleName {
             font-size: 16Px;
             font-weight: 600;
@@ -183,6 +207,7 @@
         flex-shrink: 0;
         min-width: 62Px;
         min-height: 30Px;
+
         :global {
             .n-button__content {
                 &>img {
@@ -199,12 +224,19 @@
         transform: translate(124%, -50%);
         opacity: 0;
     }
-    .showPlayLoading{
+
+    .showPlayLoading {
         opacity: 0;
     }
 
 }
 
+.loadingWrap {
+    display: flex;
+    justify-content: center;
+    min-height: 80Px;
+}
+
 .musicStaff {
     display: flex;
     flex-direction: column;
@@ -239,7 +271,8 @@
         height: 48px;
         cursor: pointer;
         transition: all .3s;
-        &:hover{
+
+        &:hover {
             transform: scale(1.1);
         }
 

+ 179 - 85
src/views/xiaoku-music/index.tsx

@@ -3,7 +3,10 @@ import {
   TransitionGroup,
   computed,
   defineComponent,
-  reactive
+  nextTick,
+  onMounted,
+  reactive,
+  ref
 } from 'vue';
 import styles from './index.module.less';
 import icon_back from './images/icon_back.svg';
@@ -12,11 +15,12 @@ import {
   NBreadcrumb,
   NBreadcrumbItem,
   NButton,
+  NEmpty,
   NImage,
-  NSpace
+  NSpace,
+  NSpin
 } from 'naive-ui';
 import TheSearch from '/src/components/TheSearch';
-import listData from './data.json';
 import { IMusicItem } from './type';
 import icon_arrow from './images/icon_arrow.svg';
 import icon_play from './images/icon_play.svg';
@@ -24,30 +28,91 @@ import icon_pause from './images/icon_pause.svg';
 import icon_goXiaoku from './images/icon_goXiaoku.svg';
 import icon_favitor from '/src/common/images/icon-collect-default.png';
 import icon_favitorActive from '/src/common/images/icon-collect-active.png';
-import { useRouter } from 'vue-router';
+import { useRoute, useRouter } from 'vue-router';
 import PlayItem from './component/play-item';
 import PlayLoading from './component/play-loading';
 import TheNoticeBar from '/src/components/TheNoticeBar';
+import { api_musicSheetPage, api_subjectList } from '../xiaoku-ai/api';
+import { useUserStore } from '/src/store/modules/users';
 
 export default defineComponent({
   name: 'XiaokuMusic',
   setup() {
+    const user = useUserStore();
+    const route = useRoute();
     const router = useRouter();
+    const forms = reactive({
+      page: 1,
+      rows: 20,
+      status: true,
+      keyword: '', // 关键词
+      musicSheetCategoriesId: route.query.id || ''
+    });
     const data = reactive({
-      tags: [
-        { name: '全部', id: 0 },
-        { name: '竖笛', id: 1 },
-        { name: '排箫', id: 2 },
-        { name: '口风琴', id: 3 },
-        { name: '陶笛', id: 4 },
-        { name: '葫芦丝', id: 5 }
-      ],
+      loading: false,
+      finshed: false,
+      reshing: false,
+      tags: [] as any[],
       tagIndex: 0,
-      list: listData.rows as unknown as IMusicItem[],
+      list: [] as unknown as IMusicItem[],
       listActive: 0,
       playState: 'pause' as 'play' | 'pause',
       showPlayer: false
     });
+    const getSubjects = async () => {
+      const res = await api_subjectList();
+      if (Array.isArray(res?.data)) {
+        data.tags = [{ name: '全部', id: 0 }, ...res.data];
+      }
+    };
+    const getList = async () => {
+      data.loading = true;
+      let res = {} as any;
+      try {
+        res = await api_musicSheetPage({
+          ...forms,
+          musicSubject: data.tagIndex ? data.tagIndex : ''
+        });
+      } catch (error) {
+        console.log(error);
+      }
+      if (data.reshing) {
+        data.list = [];
+        data.reshing = false;
+      }
+      if (res?.code === 200 && Array.isArray(res?.data?.rows)) {
+        data.list = [...data.list, ...res.data.rows];
+        data.finshed = res.data.rows.length < forms.rows;
+        console.log('🚀 ~ data.finshed:', data.finshed);
+      }
+      data.loading = false;
+    };
+
+    const handleGetList = () => {
+      forms.page = 1;
+      data.finshed = false;
+      getList();
+    };
+    const spinRef = ref();
+    const handleResh = () => {
+      console.log(data.finshed);
+      if (data.loading || data.finshed) return;
+      forms.page = forms.page + 1;
+      getList();
+    };
+
+    onMounted(async () => {
+      getSubjects();
+      await getList();
+      const obv = new IntersectionObserver(entries => {
+        if (entries[0].intersectionRatio > 0) {
+          handleResh();
+        }
+      });
+      nextTick(() => {
+        obv.observe(spinRef.value);
+      });
+    });
 
     /** 改变模仿的曲谱 */
     const handleChange = (item: IMusicItem) => {
@@ -114,90 +179,115 @@ export default defineComponent({
               曲谱列表
             </NBreadcrumbItem>
             <img class={styles.separator} src={icon_separator} />
-            <NBreadcrumbItem>一年级上册人教版(2013版)</NBreadcrumbItem>
+            <NBreadcrumbItem>{route.query.name}</NBreadcrumbItem>
           </NBreadcrumb>
         </NSpace>
-        <div class={styles.wrap}>
+        <div
+          class={styles.wrap}
+          style={{ paddingBottom: data.showPlayer ? '108Px' : '' }}>
           <div class={styles.content}>
             <div class={styles.tools}>
-              <div class={styles.tags}>
-                <NSpace size={[24, 12]} wrap={false}>
-                  {data.tags.map((item, index) => (
-                    <NButton
-                      round
-                      textColor={data.tagIndex === index ? '#fff' : '#000'}
-                      color={data.tagIndex === index ? '#198CFE' : '#fff'}
-                      onClick={() => (data.tagIndex = index)}>
-                      {item.name}
-                    </NButton>
-                  ))}
-                </NSpace>
-              </div>
-              <TheSearch round />
+              <NSpace
+                style={{ width: '100%' }}
+                size={[24, 12]}
+                wrapItem={false}>
+                {data.tags.map(item => (
+                  <NButton
+                    round
+                    textColor={data.tagIndex === item.id ? '#fff' : '#000'}
+                    color={data.tagIndex === item.id ? '#198CFE' : '#fff'}
+                    onClick={() => {
+                      data.tagIndex = item.id;
+                      data.reshing = true;
+                      handleGetList();
+                    }}>
+                    {item.name}
+                  </NButton>
+                ))}
+              </NSpace>
+              <TheSearch
+                style={{ marginLeft: 'auto' }}
+                round
+                onSearch={val => {
+                  forms.keyword = val;
+                  data.reshing = true;
+                  handleGetList();
+                }}
+              />
             </div>
 
-            <div
-              class={styles.contentWrap}
-              style={{ paddingBottom: data.showPlayer ? '90px' : '' }}>
+            <div class={styles.contentWrap}>
               <div class={styles.musicList}>
                 <div class={styles.wrapList}>
                   {data.list.map((item: IMusicItem, index) => {
                     return (
-                      <div
-                        class={[
-                          styles.item,
-                          data.listActive === index && styles.active
-                        ]}
-                        onClick={() => handleChange(item)}>
-                        <div class={styles.img}>
-                          <NImage
-                            lazy
-                            objectFit="cover"
-                            previewDisabled={true}
-                            src={item.titleImg}
-                            onLoad={e => {
-                              (e.target as any).dataset.loaded = 'true';
-                            }}
-                          />
-                          <PlayLoading
-                            class={[
-                              data.listActive === index &&
-                              data.playState === 'play'
-                                ? ''
-                                : styles.showPlayLoading
-                            ]}
-                          />
-                        </div>
-                        <div class={styles.title}>
-                          <div class={styles.titleName}>
-                            <TheNoticeBar text={item.musicSheetName} />
+                      <div class={styles.itemContainer}>
+                        <div
+                          class={[
+                            styles.item,
+                            data.listActive === index && styles.active
+                          ]}
+                          onClick={() => handleChange(item)}>
+                          <div class={styles.img}>
+                            <NImage
+                              lazy
+                              objectFit="cover"
+                              previewDisabled={true}
+                              src={item.titleImg}
+                              onLoad={e => {
+                                (e.target as any).dataset.loaded = 'true';
+                              }}
+                            />
+                            <PlayLoading
+                              class={[
+                                data.listActive === index &&
+                                data.playState === 'play'
+                                  ? ''
+                                  : styles.showPlayLoading
+                              ]}
+                            />
+                          </div>
+                          <div class={styles.title}>
+                            <div class={styles.titleName}>
+                              <TheNoticeBar text={item.musicSheetName} />
+                            </div>
+                            <div class={styles.titleDes}>{item.composer}</div>
                           </div>
-                          <div class={styles.titleDes}>{item.composer}</div>
+                          <NButton
+                            color="#259CFE"
+                            textColor="#fff"
+                            round
+                            class={styles.btn}
+                            type="primary"
+                            onClick={(e: Event) => {
+                              e.stopPropagation();
+                              handlePlay(item);
+                            }}>
+                            试听
+                            <img
+                              src={
+                                data.listActive === index &&
+                                data.playState === 'play'
+                                  ? icon_pause
+                                  : icon_play
+                              }
+                            />
+                          </NButton>
+                          <img class={styles.arrow} src={icon_arrow} />
                         </div>
-                        <NButton
-                          color="#259CFE"
-                          textColor="#fff"
-                          round
-                          class={styles.btn}
-                          type="primary"
-                          onClick={(e: Event) => {
-                            e.stopPropagation();
-                            handlePlay(item);
-                          }}>
-                          试听
-                          <img
-                            src={
-                              data.listActive === index &&
-                              data.playState === 'play'
-                                ? icon_pause
-                                : icon_play
-                            }
-                          />
-                        </NButton>
-                        <img class={styles.arrow} src={icon_arrow} />
                       </div>
                     );
                   })}
+                  {!data.finshed && (
+                    <div ref={spinRef} class={styles.loadingWrap}>
+                      <NSpin show={true}></NSpin>
+                    </div>
+                  )}
+                  {!data.loading && data.list.length === 0 && (
+                    <div class={styles.empty}>
+                      <NEmpty />
+                    </div>
+                  )}
                 </div>
               </div>
 
@@ -206,18 +296,22 @@ export default defineComponent({
                   {activeItem.value.musicSheetName}
                 </div>
                 <img
+                  style={{ display: activeItem.value.id ? '' : 'none' }}
                   class={styles.goBtn}
                   src={icon_goXiaoku}
                   onClick={() => {
                     handleChangeAudio('pause');
                     const origin = /(localhost|192)/.test(location.host)
-                      ? 'https://dev.kt.colexiu.com'
+                      ? 'https://test.lexiaoya.cn'
                       : location.origin;
-                    const src = `${origin}/instrument?platform=pc&id=${activeItem.value.id}`;
+                    const src = `${origin}/instrument?platform=pc&id=${activeItem.value.id}&Authorization=${user.getToken}`;
                     window.open(src);
                   }}
                 />
-                <div class={styles.favitor} onClick={() => handleFavitor()}>
+                <div
+                  style={{ display: activeItem.value.id ? '' : 'none' }}
+                  class={styles.favitor}
+                  onClick={() => handleFavitor()}>
                   <Transition name="favitor" mode="out-in">
                     {activeItem.value.delFlag ? (
                       <img src={icon_favitorActive} key="1" />

+ 2 - 0
src/views/xiaoku-music/type.ts

@@ -5,6 +5,8 @@ export interface IMusicItem {
   composer: string;
   titleImg: string;
   audioFileUrl: string;
+  /** 伴奏 */
+  metronomeUrl: string;
   /** 首调 */
   firstTone: string;
   delFlag: boolean;