Browse Source

添加曲库、系统应用

yuanliang 1 year ago
parent
commit
f802e44e42

+ 0 - 21
components.d.ts

@@ -10,28 +10,19 @@ declare module '@vue/runtime-core' {
     Application: typeof import('./src/components/Application/Application.vue')['default']
     CountTo: typeof import('./src/components/CountTo/CountTo.vue')['default']
     Lockscreen: typeof import('./src/components/Lockscreen/Lockscreen.vue')['default']
-    NAlert: typeof import('naive-ui')['NAlert']
     NAvatar: typeof import('naive-ui')['NAvatar']
     NBackTop: typeof import('naive-ui')['NBackTop']
-    NBadge: typeof import('naive-ui')['NBadge']
     NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
     NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
     NButton: typeof import('naive-ui')['NButton']
-    NCard: typeof import('naive-ui')['NCard']
     NCheckbox: typeof import('naive-ui')['NCheckbox']
-    NCol: typeof import('naive-ui')['NCol']
     NConfigProvider: typeof import('naive-ui')['NConfigProvider']
     NCountdown: typeof import('naive-ui')['NCountdown']
     NDialogProvider: typeof import('naive-ui')['NDialogProvider']
-    NDivider: typeof import('naive-ui')['NDivider']
     NDrawer: typeof import('naive-ui')['NDrawer']
-    NDrawerContent: typeof import('naive-ui')['NDrawerContent']
     NDropdown: typeof import('naive-ui')['NDropdown']
     NForm: typeof import('naive-ui')['NForm']
     NFormItem: typeof import('naive-ui')['NFormItem']
-    NGi: typeof import('naive-ui')['NGi']
-    NGrid: typeof import('naive-ui')['NGrid']
-    NGridItem: typeof import('naive-ui')['NGridItem']
     NIcon: typeof import('naive-ui')['NIcon']
     NInput: typeof import('naive-ui')['NInput']
     NInputGroup: typeof import('naive-ui')['NInputGroup']
@@ -39,25 +30,13 @@ declare module '@vue/runtime-core' {
     NLayoutContent: typeof import('naive-ui')['NLayoutContent']
     NLayoutHeader: typeof import('naive-ui')['NLayoutHeader']
     NLayoutSider: typeof import('naive-ui')['NLayoutSider']
-    NList: typeof import('naive-ui')['NList']
-    NListItem: typeof import('naive-ui')['NListItem']
     NMenu: typeof import('naive-ui')['NMenu']
     NMessageProvider: typeof import('naive-ui')['NMessageProvider']
     NModal: typeof import('naive-ui')['NModal']
     NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
-    NProgress: typeof import('naive-ui')['NProgress']
     NRadio: typeof import('naive-ui')['NRadio']
     NRadioGroup: typeof import('naive-ui')['NRadioGroup']
-    NRow: typeof import('naive-ui')['NRow']
-    NSelect: typeof import('naive-ui')['NSelect']
-    NSkeleton: typeof import('naive-ui')['NSkeleton']
     NSpace: typeof import('naive-ui')['NSpace']
-    NSpin: typeof import('naive-ui')['NSpin']
-    NSwitch: typeof import('naive-ui')['NSwitch']
-    NTabPane: typeof import('naive-ui')['NTabPane']
-    NTabs: typeof import('naive-ui')['NTabs']
-    NTag: typeof import('naive-ui')['NTag']
-    NThing: typeof import('naive-ui')['NThing']
     NTooltip: typeof import('naive-ui')['NTooltip']
     Recharge: typeof import('./src/components/Lockscreen/Recharge.vue')['default']
     RouterError: typeof import('./src/components/RouterError/RouterError.vue')['default']

+ 3 - 0
src/router/constant.ts

@@ -63,5 +63,8 @@ export const asyncRoutes = {
     // practiceManage: () => import('@/views/practice-manage/index'),
     deviceNumManage: () => import('@/views/system-manage/device-num-manage/index'),
     smsCodeMessage: () => import('@/views/sms-code-message/index'), // 短信验证码
+    musicSheet: () => import('@/views/music-library/music-sheet/index'), // 曲谱管理
+    projectMusicSheetManager: () => import('@/views/music-library/project-music-sheet/index'), // 曲谱管理
+    appClient: () => import('@/views/app-manage/index'), // 应用管理
 
 }

+ 9 - 0
src/views/app-manage/api.ts

@@ -0,0 +1,9 @@
+import request from '@/utils/request/index'
+// 查询列表
+export const sysMessagePage = (params: object) => {
+  return request({
+    url: '/cbs-app/sysMessage/page',
+    method: 'post',
+    data: params
+  } as any)
+}

+ 59 - 0
src/views/app-manage/index.tsx

@@ -0,0 +1,59 @@
+import { NTabPane, NTabs } from 'naive-ui'
+import { defineComponent, h, reactive, resolveDynamicComponent } from 'vue'
+import CategroryList from '@/views/music-categrory/index'
+import { useRoute } from 'vue-router'
+import { getTabsCache, setTabsCaches } from '@/hooks/use-async'
+export default defineComponent({
+  name: 'app-manage',
+  setup() {
+    const state = reactive({
+      tabName: 'MusicList' as 'MusicList' | 'TagList' | 'CategroryList',
+      searchId: null
+    })
+    const route = useRoute()
+    getTabsCache((val: any) => {
+      if (val.form.tabName) {
+        state.tabName = val.form.tabName
+      }
+    })
+    const setTabName = (val: any) => {
+      console.log('setTabName', val)
+      state.tabName = val.tabName
+      state.searchId = val.id
+    }
+    const setTabs = (val: any) => {
+      setTabsCaches(val, 'tabName', route)
+    }
+    return () => {
+      return (
+          <div class="system-menu-container">
+            <h2>曲目管理</h2>
+
+            <div class={['section-container']} style="padding-top: 0">
+              <NTabs
+                  type="line"
+                  size="large"
+                  v-model:value={state.tabName}
+                  onUpdate:value={(val: any) => setTabs(val)}
+              >
+                <NTabPane name="MusicList" tab="曲目列表"
+                    //v-auth="musicSheet/page1602301588206350338"
+                >
+                </NTabPane>
+                {/* <NTabPane name="TagList" tab="曲目标签管理" v-auth="musicTag/page1602301689389740033">
+                <TagList />
+              </NTabPane> */}
+                <NTabPane
+                    name="CategroryList"
+                    tab="曲目分类管理"
+                    //v-auth="/musicCategrory1607664813521346561"
+                >
+                  <CategroryList onSetTabName={setTabName} />
+                </NTabPane>
+              </NTabs>
+            </div>
+          </div>
+      )
+    }
+  }
+})

+ 373 - 0
src/views/music-library/api.ts

@@ -0,0 +1,373 @@
+import request from '@/utils/request'
+
+/**
+ * @description: 帮助中心列表
+ */
+export const helpCenterContentPage = (params: object) => {
+  return request({
+    url: '/cbs-app/helpCenterContent/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 帮助中心-启用/停用
+ */
+export const helpCenterContentStatus = (params: any) => {
+  return request({
+    url: '/cbs-app/helpCenterContent/status/' + params.id,
+    method: 'post'
+  } as any)
+}
+
+
+/**
+ * @description: 帮助中心添加
+ */
+export const helpCenterContentSave = (params: object) => {
+  return request({
+    url: '/cbs-app/helpCenterContent/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 帮助中心修改
+ */
+export const helpCenterContentUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/helpCenterContent/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 帮助中心删除
+ */
+export const helpCenterContentRemove = (params: object) => {
+  return request({
+    url: '/cbs-app/helpCenterContent/remove',
+    method: 'post',
+    data: params,
+    requestType: 'form'
+  } as any)
+}
+
+/**
+ * @description: 帮助中心分类列表
+ */
+export const helpCenterCatalogPage = (params: object) => {
+  return request({
+    url: '/cbs-app/helpCenterCatalog/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 帮助中心分类添加
+ */
+export const helpCenterCatalogSave = (params: object) => {
+  return request({
+    url: '/cbs-app/helpCenterCatalog/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 帮助中心分类更新
+ */
+export const helpCenterCatalogUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/helpCenterCatalog/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 帮助中心分类删除
+ */
+export const helpCenterCatalogRemove = (params: object) => {
+  return request({
+    url: '/cbs-app/helpCenterCatalog/remove',
+    method: 'post',
+    data: params,
+    requestType: 'form'
+  } as any)
+}
+
+/**
+ * @description: 资讯列表
+ */
+export const sysNewsInformationPage = (params: object) => {
+  return request({
+    url: '/cbs-app/sysNewsInformation/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 资讯添加
+ */
+export const sysNewsInformationSave = (params: object) => {
+  return request({
+    url: '/cbs-app/sysNewsInformation/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 资讯更新
+ */
+export const sysNewsInformationUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/sysNewsInformation/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 资讯删除
+ */
+export const sysNewsInformationRemove = (params: object) => {
+  return request({
+    url: '/cbs-app/sysNewsInformation/remove',
+    method: 'post',
+    data: params,
+    requestType: 'form'
+  } as any)
+}
+
+/**
+ * @description: 公告列表
+ */
+export const sysNoticePage = (params: object) => {
+  return request({
+    url: '/cbs-app/sysNotice/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 公告添加
+ */
+export const sysNoticeSave = (params: object) => {
+  return request({
+    url: '/cbs-app/sysNotice/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 公告更新
+ */
+export const sysNoticeUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/sysNotice/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 公告删除
+ */
+export const sysNoticeRemove = (params: object) => {
+  return request({
+    url: '/cbs-app/sysNotice/remove',
+    method: 'post',
+    data: params,
+    requestType: 'form'
+  } as any)
+}
+
+/**
+ * @description: 公告启用/停用
+ */
+export const sysNoticeStatus = (params?: any) => {
+  return request({
+    url: '/cbs-app/sysNotice/status/' + params.id,
+    method: 'post',
+    requestType: 'form'
+  } as any)
+}
+
+/**
+ * @description: APP按钮管理列表
+ */
+export const sysMenuButtonPage = (params: object) => {
+  return request({
+    url: '/cbs-app/sysMenuButton/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: APP按钮管理添加
+ */
+export const sysMenuButtonSave = (params: object) => {
+  return request({
+    url: '/cbs-app/sysMenuButton/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: APP按钮管理更新
+ */
+export const sysMenuButtonUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/sysMenuButton/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: APP按钮管理删除
+ */
+export const sysMenuButtonRemove = (params: object) => {
+  return request({
+    url: '/cbs-app/sysMenuButton/remove',
+    method: 'post',
+    data: params,
+    requestType: 'form'
+  } as any)
+}
+
+/**
+ * @description: 乐谱标签列表
+ */
+export const musicTagPage = (params: object) => {
+  return request({
+    url: '/cbs-app/musicTag/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 乐谱标签添加
+ */
+export const musicTagSave = (params: object) => {
+  return request({
+    url: '/cbs-app/musicTag/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 乐谱标签更新
+ */
+export const musicTagUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/musicTag/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 乐谱标签删除
+ */
+export const musicTagRemove = (params: object) => {
+  return request({
+    url: '/cbs-app/musicTag/remove',
+    method: 'post',
+    data: params,
+    requestType: 'form'
+  } as any)
+}
+
+/**
+ * @description: 乐谱标签启用/停用
+ */
+export const musicTagState = (params: any) => {
+  return request({
+    url: '/cbs-app/musicTag/state/' + params.id,
+    method: 'post',
+    requestType: 'form'
+  } as any)
+}
+
+/**
+ * @description: 乐谱列表
+ */
+export const musicSheetPage = (params: object) => {
+  return request({
+    url: '/cbs-app/musicSheet/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 乐谱添加
+ */
+export const musicSheetSave = (params: object) => {
+  return request({
+    url: '/cbs-app/musicSheet/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 乐谱更新
+ */
+export const musicSheetUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/musicSheet/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 乐谱删除
+ */
+export const musicSheetRemove = (params: object) => {
+  return request({
+    url: '/cbs-app/musicSheet/remove',
+    method: 'post',
+    data: params,
+    requestType: 'form'
+  } as any)
+}
+
+/**
+ * @description: 乐谱详情
+ */
+export const musicSheetDetail = (params?: any) => {
+  return request({
+    url: '/cbs-app/musicSheet/detail/' + params.id,
+    method: 'get',
+    requestType: 'form'
+  } as any)
+}
+
+
+/**
+ * @description: 乐谱-启用-停用
+ */
+export const musicSheetStatus = (params?: any) => {
+  return request({
+    url: '/cbs-app/musicSheet/status',
+    method: 'post',
+    requestType: 'form',
+    data: params
+  } as any)
+}

+ 576 - 0
src/views/music-library/music-sheet/component/music-list.tsx

@@ -0,0 +1,576 @@
+import SaveForm from '@/components/save-form'
+import Pagination from '@/components/pagination'
+import {
+  NButton,
+  NDataTable,
+  NFormItem,
+  NImage,
+  NInput,
+  NModal,
+  NSelect,
+  NSpace,
+  NTag,
+  useDialog,
+  useMessage,
+  NCascader,
+  NDescriptions,
+  NDescriptionsItem, NIcon
+} from 'naive-ui'
+import {defineComponent, onMounted, reactive, ref, watch} from 'vue'
+import {musicSheetPage, musicSheetRemove, musicSheetStatus, musicTagPage} from '../../api'
+import {getMusicSheetCategories} from '@/views/music-categrory/api'
+import MusicOperation from '../modal/music-operation'
+import {subjectBasicConfigPage, subjectPage} from '@/views/system-manage/api'
+import {accompanimentTypeArray, audioTypeArray} from '@/utils/searchArray'
+import MusicPreView from '../modal/musicPreView'
+import TheTooltip from '@/components/TheTooltip'
+import {filterPointCategory} from '@/views/teaching-manage/unit-test'
+import UseProject from "@views/music-library/music-sheet/modal/use-project";
+import {DeleteFilled} from "@vicons/antd";
+import {IMAGE_SVG} from "@wangeditor/editor/dist/editor/src/constants/svg";
+
+export default defineComponent({
+  name: 'content-flash',
+  props: ['searchId'],
+  setup(props, {emit}) {
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null,
+        musicTag: null,
+        musicSubject: null,
+        audioType: null,
+        accompanimentType: null,
+        status: null,
+        topFlag: null,
+        author: null,
+        belongTo: null,
+        app: null,
+        authorFrom: null,
+        musicSheetCategoriesId: props.searchId
+      },
+      dataList: [] as any,
+      subjectList: [] as any,
+      tagList: [] as any,
+      visiableMusic: false,
+      musicOperation: 'add',
+      musicData: {} as any,
+      musicSheetCategories: [] as any,
+      musicPreview: false,
+      musicScore: null as any,
+      showUseProject: false, // 适用项目
+      showUseProjectData: undefined // 适用项目行数据
+    })
+
+    const columns = (): any => {
+      return [
+        {
+          type: 'selection'
+        },
+        {
+          title: '曲目编号',
+          key: 'id'
+        },
+        // {
+        //   title (column: any) {
+        //     return <NImage width={60} height={60} src={column.titleImg}/>
+        //   },
+        //   key: 'id'
+        // },
+        {
+          title: '曲目名称',
+          key: 'name'
+        },
+        {
+          title: '封面图',
+          key: 'titleImg',
+          render(row: any): JSX.Element {
+            return <NImage width={60} height={60} src={row.titleImg}/>
+          }
+        },
+        {
+          title: '音乐人',
+          key: 'author'
+        },
+        {
+          title: '曲目类型',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '作者属性',
+          key: 'authorFrom'
+        },
+        {
+          title: '所属人',
+          key: 'userName'
+        },
+        {
+          title: '适用项目',
+          key: 'projectName',
+          render(row: any) {
+            return (
+                <NSpace>
+                  <NButton type="primary"
+                           size="small"
+                           text
+                           onClick={() => {
+                             state.showUseProject = true
+                             state.showUseProjectData = row
+                           }}
+                  >
+                    管乐迷
+                    <NIcon size={15} style="padding-left: 9px">
+                      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+                        <path d="M2 26h28v2H2z" fill="currentColor"></path>
+                        <path d="M25.4 9c.8-.8.8-2 0-2.8l-3.6-3.6c-.8-.8-2-.8-2.8 0l-15 15V24h6.4l15-15zm-5-5L24 7.6l-3 3L17.4 7l3-3zM6 22v-3.6l10-10l3.6 3.6l-10 10H6z" fill="currentColor"></path>
+                      </svg>
+                    </NIcon>
+                  </NButton>
+                </NSpace>
+            )
+          }
+        },
+        {
+          title: '状态',
+          key: 'status',
+          render(row: any) {
+            return (
+                <NTag type={row.status ? 'primary' : 'default'}>{row.status ? '启用' : '停用'}</NTag>
+            )
+          }
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+                <NSpace>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      onClick={() => {
+                        state.musicPreview = true
+                        state.musicScore = row
+                      }}
+                  >
+                    预览
+                  </NButton>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/update1602302618558099458"
+                      onClick={() => {
+                        state.visiableMusic = true
+                        state.musicOperation = 'preview'
+                        state.musicData = row
+                      }}
+                  >
+                    查看
+                  </NButton>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/update1602302618558099458"
+                      onClick={() => {
+                        state.visiableMusic = true
+                        state.musicOperation = 'edit'
+                        state.musicData = row
+                      }}
+                  >
+                    修改
+                  </NButton>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/status1612431726029942786"
+                      onClick={() => onChangeStatus(row)}
+                  >
+                    {row.status ? '停用' : '启用'}
+                  </NButton>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      onClick={() => {
+                      }}
+                  >
+                    生成图片
+                  </NButton>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      onClick={() => onRmove(row)}
+                      //v-auth="musicSheet/remove1602302689404088321"
+                  >
+                    删除
+                  </NButton>
+                  {/* <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  onClick={() => onRmove(row)}
+                  v-auth="musicSheet/remove1599694768375701505"
+                >
+                  {row.topFlag ? '取消置顶' : '置顶'}
+                </NButton> */}
+                </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    const onChangeStatus = (row: any) => {
+      const statusStr = row.status ? '停用' : '启用'
+      dialog.warning({
+        title: '警告',
+        content: `是否${statusStr}?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await musicSheetStatus({
+              id: row.id
+            })
+            getList()
+            message.success(`${statusStr}成功`)
+          } catch {
+          }
+        }
+      })
+    }
+
+    const onRmove = (row: any): void => {
+      dialog.warning({
+        title: '警告',
+        content: `删除"${row.musicSheetName}",是否继续?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await musicSheetRemove({id: row.id})
+            getList()
+            message.success('删除成功')
+          } catch {
+          }
+        }
+      })
+    }
+
+    const getList = async () => {
+      try {
+        state.loading = true
+        const {data} = await musicSheetPage({...state.pagination, ...state.searchForm})
+        state.loading = false
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {
+        state.loading = false
+      }
+    }
+
+    // 获取标签
+    const getTagList = async () => {
+      try {
+        const {data} = await musicTagPage({page: 1, rows: 999})
+        const tempList = data.rows || []
+        tempList.forEach((item: any) => {
+          item.label = item.name
+          item.value = item.id
+        })
+        state.tagList = tempList
+      } catch {
+      }
+    }
+
+    // 获取分类
+    const getMusicSheetCategorieList = async () => {
+      try {
+        const {data} = await getMusicSheetCategories({enable: true})
+        state.musicSheetCategories = filterPointCategory(data, 'musicSheetCategoriesList')
+      } catch (e) {
+      }
+    }
+
+    // 获取声部
+    const getSubjectList = async () => {
+      try {
+        const {data} = await subjectPage({page: 1, rows: 999})
+        const tempList = data.rows || []
+        tempList.forEach((item: any) => {
+          item.label = item.name
+          item.value = item.id
+        })
+        state.subjectList = tempList
+      } catch {
+      }
+    }
+
+    const saveForm = ref()
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+    watch(
+        () => props.searchId,
+        (val) => {
+          console.log(val, 'searchId')
+        }
+    )
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    onMounted(() => {
+      // getTagList()
+      console.log()
+      if (props.searchId) {
+        state.searchForm.musicSheetCategoriesId = props.searchId || null
+      }
+
+      getSubjectList()
+      getList()
+      getMusicSheetCategorieList()
+    })
+
+    return () => (
+        <div class="system-menu-container">
+          <SaveForm
+              ref={saveForm}
+              model={state.searchForm}
+              onSubmit={onSubmit}
+              saveKey="music-list"
+              onSetModel={(val: any) => (state.searchForm = val)}
+          >
+            <NFormItem label="关键词" path="keyword">
+              <NInput
+                  placeholder="曲目编号/名称"
+                  v-model:value={state.searchForm.keyword}
+                  clearable
+              />
+            </NFormItem>
+            <NFormItem label="分类" path="musicSheetCategoriesId">
+              <NCascader
+                  valueField="id"
+                  labelField="name"
+                  children-field="musicSheetCategoriesList"
+                  placeholder="请选择分类"
+                  v-model:value={state.searchForm.musicSheetCategoriesId}
+                  options={state.musicSheetCategories}
+                  clearable
+              />
+            </NFormItem>
+            <NFormItem label="声部" path="musicSubject">
+              <NSelect
+                  placeholder="请选择声部"
+                  v-model:value={state.searchForm.musicSubject}
+                  options={state.subjectList}
+                  clearable
+              />
+            </NFormItem>
+            <NFormItem label="作者属性" path="authorFrom">
+              <NSelect
+                  placeholder="请选择作者属性"
+                  v-model:value={state.searchForm.authorFrom}
+                  options={
+                    [
+                      {
+                        label: '平台',
+                        value: 'PLATFORM'
+                      },
+                      {
+                        label: '机构',
+                        value: 'TENANT'
+                      },
+                      {
+                        label: '个人',
+                        value: 'SELF'
+                      }
+                    ] as any
+                  }
+                  clearable
+              />
+            </NFormItem>
+            <NFormItem label="音乐人" path="author">
+              <NInput
+                  placeholder="请选择音乐人"
+                  v-model:value={state.searchForm.author}
+                  clearable
+              />
+            </NFormItem>
+            <NFormItem label="所属人" path="author">
+              <NInput
+                  placeholder="请选择所属人"
+                  v-model:value={state.searchForm.belongTo}
+                  clearable
+              />
+            </NFormItem>
+            <NFormItem label="使用项目" path="app">
+              <NSelect
+                  placeholder="请选择使用项目"
+                  v-model:value={state.searchForm.app}
+                  options={
+                    [
+                      {
+                        label: '管乐迷',
+                        value: 'MEC'
+                      },
+                      {
+                        label: '管乐团',
+                        value: 'jMEDU'
+                      },
+                      {
+                        label: '酷乐秀',
+                        value: 'COOLESHOW'
+                      },
+                      {
+                        label: '音乐数字乐堂',
+                        value: 'COOLESHOW-EDU'
+                      }
+                    ] as any
+                  }
+                  clearable
+              />
+            </NFormItem>
+            <NFormItem label="状态" path="status">
+              <NSelect
+                  v-model={[state.searchForm.status, 'value']}
+                  placeholder="请选择状态"
+                  clearable
+                  options={
+                    [
+                      {
+                        label: '启用',
+                        value: true
+                      },
+                      {
+                        label: '停用',
+                        value: false
+                      }
+                    ] as any
+                  }
+              />
+            </NFormItem>
+            <NFormItem>
+              <NSpace>
+                <NButton type="primary" onClick={onSearch}>
+                  搜索
+                </NButton>
+                <NButton type="default" onClick={onBtnReset}>
+                  重置
+                </NButton>
+              </NSpace>
+            </NFormItem>
+          </SaveForm>
+          <div class={['section-container']}>
+            <NSpace style={{paddingBottom: '12px'}}>
+              <NButton
+                  type="primary"
+                  //v-auth="musicSheet/save1602302550719426561"
+                  onClick={() => {
+                    state.visiableMusic = true
+                    state.musicOperation = 'add'
+                    state.musicData = {}
+                  }}
+              >
+                新增曲目
+              </NButton>
+              <NButton
+                  //v-auth="musicSheet/save1602302550719426561"
+                  onClick={() => {
+                    // state.musicOperation = 'unable'
+                    // state.musicData = {}
+                  }}
+              >
+                批量禁用
+              </NButton>
+              <NButton
+                  //v-auth="musicSheet/save1602302550719426561"
+                  onClick={() => {
+                    // state.musicOperation = 'enable'
+                    // state.musicData = {}
+                  }}
+              >
+                批量启用
+              </NButton>
+            </NSpace>
+
+            <NDataTable
+                loading={state.loading}
+                columns={columns()}
+                data={state.dataList}
+            ></NDataTable>
+            <Pagination
+                v-model:page={state.pagination.page}
+                v-model:pageSize={state.pagination.rows}
+                v-model:pageTotal={state.pagination.pageTotal}
+                onList={getList}
+                sync
+                saveKey="music-list"
+            ></Pagination>
+          </div>
+          <NModal
+              v-model:show={state.visiableMusic}
+              preset="dialog"
+              showIcon={false}
+              title={() => {
+                if (state.musicOperation === 'add') {
+                  return '添加曲目';
+                } else if (state.musicOperation === 'preview') {
+                  return '曲目详情';
+                }
+                return '修改曲目';
+              }}
+              style={{width: '980px'}}
+          >
+            <MusicOperation
+                type={state.musicOperation}
+                data={state.musicData}
+                subjectList={state.subjectList}
+                musicSheetCategories={state.musicSheetCategories}
+                // tagList={state.tagList}
+                onClose={() => (state.visiableMusic = false)}
+                onGetList={getList}
+            />
+          </NModal>
+          <NModal
+              blockScroll={true}
+              v-model:show={state.musicPreview}
+              preset="dialog"
+              showIcon={false}
+              title={'曲目预览'}
+              style={{width: 'auto'}}
+          >
+            <MusicPreView item={state.musicScore}/>
+          </NModal>
+
+          <NModal
+              blockScroll={true}
+              v-model:show={state.showUseProject}
+              preset="dialog"
+              showIcon={false}
+              title={'适用项目'}
+              style={{width: '500px',height: '650px'}}
+          >
+            <UseProject item={state.musicScore} rowData={state.showUseProjectData}/>
+          </NModal>
+        </div>
+    )
+  }
+})

+ 262 - 0
src/views/music-library/music-sheet/component/tag-list.tsx

@@ -0,0 +1,262 @@
+import SaveForm from '@/components/save-form'
+import Pagination from '@/components/pagination'
+import {
+  NButton,
+  NDataTable,
+  NFormItem,
+  NInput,
+  NModal,
+  NSpace,
+  NTag,
+  useDialog,
+  useMessage
+} from 'naive-ui'
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { musicTagPage, musicTagRemove, musicTagState } from '../../api'
+import TagOperation from '../modal/tag-operation'
+
+export default defineComponent({
+  name: 'content-flash',
+  setup() {
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null
+      },
+      dataList: [] as any,
+      visiableTag: false,
+      tagOperation: 'add',
+      tagData: {} as any
+    })
+
+    const columns = () => {
+      return [
+        {
+          title: '编号',
+          key: 'id'
+        },
+        {
+          title: '名称',
+          key: 'name'
+        },
+        // {
+        //   title: '平台乐谱启用/总数',
+        //   key: 'enablePlatformMusicSheetNum',
+        //   render(row: any) {
+        //     return `${row.enablePlatformMusicSheetNum}/${row.musicPlatformSheetNum}`
+        //   }
+        // },
+        // {
+        //   title: '老师乐谱启用/总数',
+        //   key: 'enableTeacherMusicSheetNum',
+        //   render(row: any) {
+        //     return `${row.enableTeacherMusicSheetNum}/${row.musicTeacherSheetNum}`
+        //   }
+        // },
+        {
+          title: '更新人',
+          key: 'updateUser'
+        },
+        {
+          title: '更新时间',
+          key: 'updateTime'
+        },
+        {
+          title: '排序',
+          key: 'sortNumber'
+        },
+        {
+          title: '状态',
+          key: 'state',
+          render(row: any) {
+            return (
+              <NTag type={row.state ? 'primary' : 'default'}>{row.state ? '启用' : '停用'}</NTag>
+            )
+          }
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          render(row: any) {
+            return (
+              <NSpace>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  onClick={() => onChangeState(row)}
+                  //v-auth="musicTag/state1602302067191672833"
+                >
+                  {row.state ? '停用' : '启用'}
+                </NButton>
+
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  //v-auth="musicTag/update1602301883418243073"
+                  onClick={() => {
+                    state.visiableTag = true
+                    state.tagOperation = 'edit'
+                    state.tagData = row
+                  }}
+                >
+                  修改
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  onClick={() => onRmove(row)}
+                  //v-auth="musicTag/remove1602301979954343937"
+                >
+                  删除
+                </NButton>
+              </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    const onChangeState = (row: any) => {
+      // musicTag/state
+      const statusStr = row.state ? '停用' : '启用'
+      dialog.warning({
+        title: '警告',
+        content: `是否${statusStr}?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await musicTagState({
+              id: row.id
+            })
+            getList()
+            message.success(`${statusStr}成功`)
+          } catch {}
+        }
+      })
+    }
+
+    const onRmove = (row: any): void => {
+      dialog.warning({
+        title: '警告',
+        content: `删除"${row.name}",是否继续?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await musicTagRemove({ id: row.id })
+            getList()
+            message.success('删除成功')
+          } catch {}
+        }
+      })
+    }
+
+    const getList = async () => {
+      try {
+        state.loading = true
+        const { data } = await musicTagPage({ ...state.pagination, ...state.searchForm })
+        state.loading = false
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {
+        state.loading = false
+      }
+    }
+
+    const saveForm = ref()
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    onMounted(() => {
+      getList()
+    })
+    return () => (
+      <div class="system-menu-container">
+        <SaveForm
+          ref={saveForm}
+          model={state.searchForm}
+          onSubmit={onSubmit}
+          saveKey="tag-list"
+          onSetModel={(val: any) => (state.searchForm = val)}
+        >
+          <NFormItem label="名称" path="keyword">
+            <NInput placeholder="请输入名称" v-model:value={state.searchForm.keyword} clearable />
+          </NFormItem>
+          <NFormItem>
+            <NSpace>
+              <NButton type="primary" onClick={onSearch}>
+                搜索
+              </NButton>
+              <NButton type="default" onClick={onBtnReset}>
+                重置
+              </NButton>
+            </NSpace>
+          </NFormItem>
+        </SaveForm>
+        <div class={['section-container']}>
+          <NSpace style={{ paddingBottom: '12px' }}>
+            <NButton
+              type="primary"
+              //v-auth="musicTag/save1602301806310158338"
+              onClick={() => {
+                state.visiableTag = true
+                state.tagOperation = 'add'
+                state.tagData = {}
+              }}
+            >
+              添加曲目标签
+            </NButton>
+          </NSpace>
+
+          <NDataTable
+            loading={state.loading}
+            columns={columns()}
+            data={state.dataList}
+          ></NDataTable>
+          <Pagination
+            v-model:page={state.pagination.page}
+            v-model:pageSize={state.pagination.rows}
+            v-model:pageTotal={state.pagination.pageTotal}
+            onList={getList}
+            sync
+            saveKey="tag-list"
+          ></Pagination>
+        </div>
+        <NModal
+          v-model:show={state.visiableTag}
+          preset="dialog"
+          showIcon={false}
+          title={state.tagOperation === 'add' ? '新增曲目标签' : '修改曲目标签'}
+          style={{ width: '500px' }}
+        >
+          <TagOperation
+            type={state.tagOperation}
+            data={state.tagData}
+            onClose={() => (state.visiableTag = false)}
+            onGetList={getList}
+          />
+        </NModal>
+      </div>
+    )
+  }
+})

+ 62 - 0
src/views/music-library/music-sheet/index.tsx

@@ -0,0 +1,62 @@
+import { NTabPane, NTabs } from 'naive-ui'
+import { defineComponent, h, reactive, resolveDynamicComponent } from 'vue'
+import MusicList from './component/music-list'
+import TagList from './component/tag-list'
+import CategroryList from '@/views/music-categrory/index'
+import { useRoute } from 'vue-router'
+import { getTabsCache, setTabsCaches } from '@/hooks/use-async'
+export default defineComponent({
+  name: 'city-manage',
+  setup() {
+    const state = reactive({
+      tabName: 'MusicList' as 'MusicList' | 'TagList' | 'CategroryList',
+      searchId: null
+    })
+    const route = useRoute()
+    getTabsCache((val: any) => {
+      if (val.form.tabName) {
+        state.tabName = val.form.tabName
+      }
+    })
+    const setTabName = (val: any) => {
+      console.log('setTabName', val)
+      state.tabName = val.tabName
+      state.searchId = val.id
+    }
+    const setTabs = (val: any) => {
+      setTabsCaches(val, 'tabName', route)
+    }
+    return () => {
+      return (
+        <div class="system-menu-container">
+          <h2>曲目管理</h2>
+
+          <div class={['section-container']} style="padding-top: 0">
+            <NTabs
+              type="line"
+              size="large"
+              v-model:value={state.tabName}
+              onUpdate:value={(val: any) => setTabs(val)}
+            >
+              <NTabPane name="MusicList" tab="曲目列表"
+                  //v-auth="musicSheet/page1602301588206350338"
+              >
+                <MusicList searchId={state.searchId} />
+              </NTabPane>
+              {/* <NTabPane name="TagList" tab="曲目标签管理" v-auth="musicTag/page1602301689389740033">
+                <TagList />
+              </NTabPane> */}
+              <NTabPane
+                name="CategroryList"
+                tab="曲目分类管理"
+                //v-auth="/musicCategrory1607664813521346561"
+              >
+                <CategroryList onSetTabName={setTabName} />
+              </NTabPane>
+            </NTabs>
+          </div>
+        </div>
+      )
+    }
+  }
+})

+ 20 - 0
src/views/music-library/music-sheet/modal/index.module.less

@@ -0,0 +1,20 @@
+.audioSection {
+  position: relative;
+  background: #f7f7f7;
+  padding-right: 20px;
+  border-radius: 12px;
+  padding-top: 24px;
+  margin-bottom: 12px;
+
+  .btnRemove {
+    position: absolute;
+    bottom: 12px;
+    right: 20px;
+  }
+}
+
+.formContainer{
+  max-height: 80vh;
+  overflow-y: auto;
+  padding: 0 10px;
+}

+ 962 - 0
src/views/music-library/music-sheet/modal/music-operation.tsx

@@ -0,0 +1,962 @@
+import {
+  NForm,
+  NFormItem,
+  NInput,
+  NSelect,
+  NSpace,
+  NButton,
+  useMessage,
+  NRadioGroup,
+  NRadio,
+  NGrid,
+  NFormItemGi,
+  NInputNumber,
+  NGi,
+  useDialog,
+  NCascader,
+  NAlert,
+  NInputGroup,
+  NInputGroupLabel, NCheckbox
+} from 'naive-ui'
+import type {SelectOption} from 'naive-ui'
+import {defineComponent, onMounted, PropType, reactive, ref} from 'vue'
+import {musicSheetDetail, musicSheetSave, musicSheetUpdate} from '../../api'
+import UploadFile from '@/components/upload-file'
+import styles from './index.module.less'
+import deepClone from '@/utils/deep.clone'
+import axios from 'axios'
+import {Checkbox, CheckboxGroup} from "vant";
+
+/**
+ * 获取指定元素下一个Note元素
+ * @param ele 指定元素
+ * @param selectors 选择器
+ */
+const getNextNote = (ele: any, selectors: any) => {
+  let index = 0
+  const parentEle = ele.closest(selectors)
+  let pointer = parentEle
+  const measure = parentEle?.closest('measure')
+  let siblingNote = null
+  // 查找到相邻的第一个note元素
+  while (!siblingNote && index < (measure?.childNodes.length || 50)) {
+    index++
+    if (pointer?.nextElementSibling?.tagName === 'note') {
+      siblingNote = pointer?.nextElementSibling
+    }
+    pointer = pointer?.nextElementSibling
+  }
+  return siblingNote
+}
+
+export const onlyVisible = (xml: any, partIndex: any) => {
+  if (!xml) return ''
+  const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
+  const partList =
+      xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
+  const parts = xmlParse.getElementsByTagName('part')
+  const visiblePartInfo = partList[partIndex]
+  if (visiblePartInfo) {
+    const id = visiblePartInfo.getAttribute('id')
+    Array.from(parts).forEach((part) => {
+      if (part && part.getAttribute('id') !== id) {
+        part.parentNode?.removeChild(part)
+        // 不等于第一行才添加避免重复添加
+      }
+
+      // 最后一个小节的结束线元素不在最后 调整
+      if (part && part.getAttribute('id') === id) {
+        const barlines = part.getElementsByTagName('barline')
+        const lastParent = barlines[barlines.length - 1]?.parentElement
+        if (lastParent?.lastElementChild?.tagName !== 'barline') {
+          const children: any[] = (lastParent?.children as any) || []
+          for (let el of children) {
+            if (el.tagName === 'barline') {
+              // 将结束线元素放到最后
+              lastParent?.appendChild(el)
+              break
+            }
+          }
+        }
+      }
+    })
+    Array.from(partList).forEach((part) => {
+      if (part && part.getAttribute('id') !== id) {
+        part.parentNode?.removeChild(part)
+      }
+    })
+    // 处理装饰音问题
+    const notes = xmlParse.getElementsByTagName('note')
+    const getNextvNoteDuration = (i: any) => {
+      let nextNote = notes[i + 1]
+      // 可能存在多个装饰音问题,取下一个非装饰音时值
+      for (let index = i; index < notes.length; index++) {
+        const note = notes[index]
+        if (!note.getElementsByTagName('grace')?.length) {
+          nextNote = note
+          break
+        }
+      }
+      const nextNoteDuration = nextNote?.getElementsByTagName('duration')[0]
+      return nextNoteDuration
+    }
+    Array.from(notes).forEach((note, i) => {
+      const graces = note.getElementsByTagName('grace')
+      if (graces && graces.length) {
+        note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
+      }
+    })
+  }
+  return new XMLSerializer().serializeToString(xmlParse)
+}
+
+const speedInfo = {
+  'rall.': 1.333333333,
+  'poco rit.': 1.333333333,
+  'rit.': 1.333333333,
+  'molto rit.': 1.333333333,
+  'molto rall': 1.333333333,
+  molto: 1.333333333,
+  lentando: 1.333333333,
+  allargando: 1.333333333,
+  morendo: 1.333333333,
+  'accel.': 0.8,
+  calando: 2,
+  'poco accel.': 0.8,
+  'gradually slowing': 1.333333333,
+  slowing: 1.333333333,
+  slow: 1.333333333,
+  slowly: 1.333333333,
+  faster: 1.333333333
+}
+
+/**
+ * 按照xml进行减慢速度的计算
+ * @param xml 始终按照第一分谱进行减慢速度的计算
+ */
+export function getGradualLengthByXml(xml: string) {
+  const firstPartXml = onlyVisible(xml, 0)
+  const xmlParse = new DOMParser().parseFromString(firstPartXml, 'text/xml')
+  const measures = Array.from(xmlParse.querySelectorAll('measure'))
+  const notes = Array.from(xmlParse.querySelectorAll('note'))
+  const words = Array.from(xmlParse.querySelectorAll('words'))
+  const metronomes = Array.from(xmlParse.querySelectorAll('metronome'))
+
+  const eles = []
+
+  for (const ele of [...words, ...metronomes]) {
+    const note = getNextNote(ele, 'direction')
+    // console.log(ele, note)
+    if (note) {
+      const measure = note?.closest('measure')
+      const measureNotes = Array.from(measure.querySelectorAll('note'))
+
+      const noteInMeasureIndex = Array.from(measure.childNodes)
+          .filter((item: any) => item.nodeName === 'note')
+          .findIndex((item) => item === note)
+
+      let allDuration = 0
+      let leftDuration = 0
+      for (let i = 0; i < measureNotes.length; i++) {
+        const n: any = measureNotes[i]
+        const duration = +(n.querySelector('duration')?.textContent || '0')
+        allDuration += duration
+        if (i < noteInMeasureIndex) {
+          leftDuration = allDuration
+        }
+      }
+      eles.push({
+        ele,
+        index: notes.indexOf(note),
+        noteInMeasureIndex,
+        textContent: ele.textContent,
+        measureIndex: measures.indexOf(measure), //,measure?.getAttribute('number')
+        type: ele.tagName,
+        allDuration,
+        leftDuration
+      })
+    }
+  }
+
+  // 结尾处手动插入一个音符节点
+  eles.push({
+    ele: notes[notes.length - 1],
+    index: notes.length,
+    noteInMeasureIndex: 0,
+    textContent: '',
+    type: 'metronome',
+    allDuration: 1,
+    leftDuration: 1,
+    measureIndex: measures.length
+  })
+
+  const gradualNotes: any[] = []
+  eles.sort((a, b) => a.index - b.index)
+  const keys = Object.keys(speedInfo).map((w) => w.toLocaleLowerCase())
+  let isLastNoteAndNotClosed = false
+  for (const ele of eles) {
+    const textContent: any = ele.textContent?.toLocaleLowerCase().trim()
+    if (ele === eles[eles.length - 1]) {
+      if (gradualNotes[gradualNotes.length - 1]?.length === 1) {
+        isLastNoteAndNotClosed = true
+      }
+    }
+
+    const isKeyWork = keys.find((k) => {
+      const ks = k.split(' ')
+      return textContent && ks.includes(textContent)
+    })
+    if (
+        ele.type === 'metronome' ||
+        (ele.type === 'words' && (textContent.startsWith('a tempo') || isKeyWork)) ||
+        isLastNoteAndNotClosed
+    ) {
+      const indexOf = gradualNotes.findIndex((item) => item.length === 1)
+      if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) {
+        gradualNotes[indexOf][1] = {
+          start: ele.index,
+          measureIndex: ele.measureIndex,
+          noteInMeasureIndex: ele.noteInMeasureIndex,
+          allDuration: ele.allDuration,
+          leftDuration: ele.leftDuration,
+          type: textContent
+        }
+      }
+    }
+    if (ele.type === 'words' && isKeyWork) {
+      gradualNotes.push([
+        {
+          start: ele.index,
+          measureIndex: ele.measureIndex,
+          noteInMeasureIndex: ele.noteInMeasureIndex,
+          allDuration: ele.allDuration,
+          leftDuration: ele.leftDuration,
+          type: textContent
+        }
+      ])
+    }
+  }
+  return gradualNotes
+}
+
+export default defineComponent({
+  name: 'music-operation',
+  props: {
+    type: {
+      type: String,
+      default: 'add'
+    },
+    data: {
+      type: Object as PropType<any>,
+      default: () => {
+      }
+    },
+    tagList: {
+      type: Array as PropType<Array<SelectOption>>,
+      default: () => []
+    },
+    subjectList: {
+      type: Array as PropType<Array<SelectOption>>,
+      default: () => []
+    },
+    musicSheetCategories: {
+      type: Array as PropType<Array<SelectOption>>,
+      default: () => []
+    }
+  },
+  emits: ['close', 'getList'],
+  setup(props, {slots, attrs, emit}) {
+    const forms = reactive({
+      graduals: {} as any, // 渐变速度
+      audioType: 'MP3', // 播放类型
+      mp3Type: 'MP3', // 是否含节拍器
+      xmlFileUrl: null, // XML
+      midiUrl: null, // mid
+      metronomeUrl: null, // 伴奏 根据mp3Type 是否是为包含节拍器
+      musicSheetName: null, // 曲目名称
+      musicTag: [] as any, // 曲目标签
+      composer: null, // 音乐人
+      playSpeed: null, // 曲谱速度
+      showFingering: null as any, // 是否显示指法
+      canEvaluate: null as any, // 是否评测
+      musicSubject: null as any, // 可用声部
+      notation: null as any, // 能否转和简谱
+      auditVersion: null as any, // 审核版本
+      accompanimentType: 1, // 伴奏类型
+      sortNumber: null, // 排序
+      titleImg: null, // 曲谱封面
+      remark: null, // 曲谱描述
+      background: [] as any, // 原音
+      musicSheetCategoriesId: null,
+      status: false,
+      musicSheetType: 'SINGLE' as 'SINGLE' | 'CONCERT',
+      author: null, //音乐人
+      authorFrom: undefined,
+      belongTo: undefined,
+      speed: undefined,
+      playMetronome: 1,
+      playStyle: undefined // 播放方式
+    })
+    const state = reactive({
+      tagList: [...props.tagList] as any, // 标签列表
+      xmlFirstSpeed: null as any, // 第一个音轨速度
+      partListNames: [] as any, // 所有音轨声部列表
+      musicSheetCategories: [...props.musicSheetCategories] as any
+    })
+    const gradualData = reactive({
+      list: [] as any[],
+      gradualRefs: [] as any[]
+    })
+
+    const btnLoading = ref(false)
+    const formsRef = ref()
+    const message = useMessage()
+    const dialog = useDialog()
+
+    // 提交记录
+    const onSubmit = async () => {
+      formsRef.value.validate(async (error: any) => {
+        console.log(error, 'error')
+        if (error) {
+          message.error(error[0]?.[0]?.message)
+          return
+        }
+        try {
+          const obj = {
+            ...forms,
+            musicTag: '-1',
+            extConfigJson: JSON.stringify({gradualTimes: forms.graduals})
+          }
+          if (forms.audioType == 'MIDI') {
+            obj.background = []
+          }
+          btnLoading.value = true
+          if (props.type === 'add') {
+            await musicSheetSave({...obj})
+            message.success('添加成功')
+          } else if (props.type === 'edit') {
+            await musicSheetUpdate({...obj, id: props.data.id})
+            message.success('修改成功')
+          }
+          emit('getList')
+          emit('close')
+        } catch (e) {
+          console.log(e)
+        }
+        setTimeout(() => {
+          btnLoading.value = false
+        }, 100)
+      })
+    }
+
+    // 上传XML,初始化音轨 音轨速度
+    const readFileInputEventAsArrayBuffer = (file: any) => {
+      const xmlRead = new FileReader()
+      xmlRead.onload = (res) => {
+        try {
+          gradualData.list = getGradualLengthByXml(res?.target?.result as any).filter(
+              (item: any) => item.length === 2
+          )
+        } catch (error) {
+        }
+        state.partListNames = getPartListNames(res?.target?.result as any) as any
+        // 这里是如果没有当前音轨就重新写
+        for (let j = 0; j < state.partListNames.length; j++) {
+          if (!forms.background[j]) {
+            forms.background.push({audioFileUrl: null, track: null})
+          }
+          forms.background[j].track = state.partListNames[j].value
+        }
+
+        // 循环添加所在音轨的原音
+        for (let index = forms.background.length; index < state.partListNames.length; index++) {
+          const part = state.partListNames[index].value
+          const sysData = {
+            ...forms.background[0],
+            track: part
+          }
+          if (!sysData.speed) {
+            sysData.speed = state.xmlFirstSpeed
+          }
+          createSys(sysData)
+        }
+
+        if (forms.background.length == 0) {
+          forms.background.push({audioFileUrl: '', track: ''})
+        }
+
+
+      }
+      xmlRead.readAsText(file)
+    }
+
+    // 获取xml中所有轨道
+    const getPartListNames = (xml: any) => {
+      if (!xml) return []
+      const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
+      const partList =
+          xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
+      const partListNames = Array.from(partList).map((item) => {
+        const part = item.getElementsByTagName('part-name')?.[0].textContent || ''
+        return {
+          value: part,
+          label: part
+        }
+      })
+
+      if (partListNames.length > 0) {
+        forms.background = forms.background.slice(0, partListNames.length)
+      }
+
+      state.xmlFirstSpeed = xmlParse.getElementsByTagName('per-minute')?.[0]?.textContent || ''
+      if (!forms.playSpeed) {
+        forms.playSpeed = state.xmlFirstSpeed
+      }
+      return partListNames
+    }
+
+    // 判断选择的音轨是否在选中
+    const initPartsListStatus = (track: string): any => {
+      const _names = state.partListNames.filter((n: any) => n.value?.toLocaleUpperCase?.() != 'COMMON')
+      const partListNames = deepClone(_names) || []
+      partListNames.forEach((item: any) => {
+        const index = forms.background.findIndex((ground: any) => item.value == ground.track)
+        if (index > -1 && track != item.value) {
+          item.disabled = true
+        } else {
+          item.disabled = false
+        }
+      })
+      return partListNames || []
+    }
+
+    // 添加原音
+    const createSys = (initData?: any) => {
+      forms.background.push({
+        audioFileUrl: null, // 原音
+        track: null, // 轨道
+        ...initData
+      })
+    }
+    // 删除原音
+    const removeSys = (index: number) => {
+      dialog.warning({
+        title: '警告',
+        content: `是否确认删除此原音?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          forms.background.splice(index, 1)
+        }
+      })
+    }
+
+    onMounted(async () => {
+      if (props.type === 'edit' || props.type === 'preview') {
+        const detail = props.data
+        try {
+          const {data} = await musicSheetDetail({id: detail.id})
+          forms.audioType = data.audioType
+          forms.mp3Type = data.mp3Type
+          forms.xmlFileUrl = data.xmlFileUrl
+          forms.midiUrl = data.midiUrl
+          forms.metronomeUrl = data.metronomeUrl
+          forms.musicSheetName = data.musicSheetName
+          forms.musicTag = data.musicTag?.split(',')
+          forms.composer = data.composer
+          forms.playSpeed = data.playSpeed
+          forms.showFingering = Number(data.showFingering)
+          forms.canEvaluate = Number(data.canEvaluate)
+          forms.musicSubject = data.musicSubject ? Number(data.musicSubject) : null
+          forms.notation = Number(data.notation)
+          forms.auditVersion = Number(data.auditVersion)
+          forms.accompanimentType = data.accompanimentType
+          forms.sortNumber = data.sortNumber
+          forms.titleImg = data.titleImg
+          forms.remark = data.remark
+          forms.status = data.status
+          forms.musicSheetCategoriesId = data.musicSheetCategoriesId
+          forms.background = data.background || []
+          forms.musicSheetType = data.musicSheetType || "SINGLE"
+          // 获取渐变 和 是否多声部
+          try {
+            const extConfigJson = data.extConfigJson ? JSON.parse(data.extConfigJson) : {}
+            forms.graduals = extConfigJson.gradualTimes || {}
+          } catch (error) {
+          }
+          axios.get(data.xmlFileUrl).then((res: any) => {
+            if (res?.data) {
+              gradualData.list = getGradualLengthByXml(res?.data as any).filter(
+                  (item: any) => item.length === 2
+              )
+              state.partListNames = getPartListNames(res?.data as any) as any
+            }
+          })
+        } catch (error) {
+          console.log(error)
+        }
+      }
+    })
+
+    return () => (
+        <div style="background: #fff; padding-top: 12px">
+          <NForm
+              class={styles.formContainer}
+              model={forms}
+              ref={formsRef}
+              label-placement="left"
+              label-width="130"
+          >
+            <NAlert showIcon={false} style={{marginBottom:"12px" }}>曲目信息</NAlert>
+            <NGrid cols={2}>
+              <NFormItemGi
+                  label="曲目名称"
+                  path="musicSheetName"
+                  rule={[
+                    {
+                      required: true,
+                      message: '请输入曲目名称'
+                    }
+                  ]}
+              >
+                <NInput v-model:value={forms.musicSheetName} placeholder="请输入曲目名称"/>
+              </NFormItemGi>
+              <NFormItemGi
+                  label="音乐人"
+                  path="author"
+                  rule={[
+                    {
+                      required: true,
+                      message: '请输入音乐人'
+                    }
+                  ]}
+              >
+                <NInput v-model:value={forms.author} placeholder="请输入音乐人"
+                        showCount
+                        maxlength={14}
+                />
+              </NFormItemGi>
+            </NGrid>
+            <NGrid cols={2}>
+              <NFormItemGi label="曲目描述" path="remark">
+                <NInput
+                    placeholder="请输入曲目描述"
+                    type="textarea"
+                    rows={4}
+                    showCount
+                    maxlength={200}
+                    v-model:value={forms.remark}
+                />
+              </NFormItemGi>
+              <NFormItemGi label="曲谱封面" path="titleImg"
+                           rule={[
+                             {
+                               required: true
+                             }
+                           ]}>
+                <UploadFile
+                    accept=".jpg,.jpeg,.png"
+                    tips="请上传大小2M以内的JPG、PNG图片"
+                    v-model:fileList={forms.titleImg}
+                    cropper
+                    bucketName="cloud-coach"
+                    options={{
+                      autoCrop: true, //是否默认生成截图框
+                      enlarge: 2, //  图片放大倍数
+                      autoCropWidth: 200, //默框高度
+                      fixedBox: true, //是否固定截图框大认生成截图框宽度
+                      autoCropHeight: 200, //默认生成截图小 不允许改变
+                      previewsCircle: false, //预览图是否是原圆形
+                      title: '曲谱封面'
+                    }}
+                />
+              </NFormItemGi>
+            </NGrid>
+            <NGrid cols={2}>
+              <NFormItemGi
+                  label="曲目类型"
+                  path="musicSheetType"
+                  rule={[
+                    {
+                      required: true,
+                      message: '请选择曲目类型'
+                    }
+                  ]}
+              >
+                <NRadioGroup v-model:value={forms.musicSheetType}>
+                  <NRadio value="SINGLE">独奏</NRadio>
+                  <NRadio value="CONCERT">合奏</NRadio>
+                </NRadioGroup>
+              </NFormItemGi>
+
+              <NFormItemGi
+                  label="作者属性"
+                  path="authorFrom"
+                  rule={[
+                    {
+                      required: true,
+                      message: '请选择作者属性'
+                    }
+                  ]}
+              >
+                <NSelect
+                    v-model:value={forms.authorFrom}
+                    options={
+                      [
+                        {
+                          label: '平台',
+                          value: 'PALTFORM'
+                        },
+                        {
+                          label: '机构',
+                          value: 'TENANT'
+                        },
+                        {
+                          label: '个人',
+                          value: 'SELF'
+                        }
+                      ] as any
+                    }
+                    filterable
+                    clearable
+                    placeholder="请选择作者属性"
+                />
+              </NFormItemGi>
+
+            </NGrid>
+            <NGrid cols={2}>
+              <NFormItemGi
+                  label="所属人"
+                  path="belongTo"
+                  rule={[
+                    {
+                      required: true,
+                      message: '请选择曲目所属人'
+                    }
+                  ]}
+              >
+                <NSelect
+                    v-model:value={forms.belongTo}
+                    options={
+                      [
+                        {
+                          label: '小A',
+                          value: '小A'
+                        }
+                      ] as any
+                    }
+                    filterable
+                    clearable
+                    placeholder="请选择曲目所属人"
+                />
+              </NFormItemGi>
+              <NFormItemGi label="速度" path="speed">
+                <NInputNumber
+                    placeholder="请输入速度"
+                    v-model:value={forms.speed}
+                />
+              </NFormItemGi>
+            </NGrid>
+            <NAlert showIcon={false} style={{marginBottom:"12px" }}>曲目上传</NAlert>
+            <NGrid cols={2}>
+              <NFormItemGi label="播放模式" path="audioType"
+                           rule={[
+                             {
+                               required: true,
+                               message: '请选择播放模式'
+                             }
+                           ]}
+              >
+                <NRadioGroup
+                    v-model:value={forms.audioType}
+                    onUpdateValue={(value: string | number | boolean) => {
+                      if (value === 'MP3') {
+                        forms.mp3Type = 'MP3'
+                      } else {
+                        forms.mp3Type = 'MIDI'
+                      }
+                    }}
+                >
+                  <NRadio value="MP3">MP3</NRadio>
+                  <NRadio value="MIDI">MIDI</NRadio>
+                </NRadioGroup>
+              </NFormItemGi>
+              {forms.mp3Type === 'MP3' && (
+                  <NFormItemGi
+                      label="伴奏类型"
+                      path="audioType"
+                      rule={[
+                        {
+                          required: true
+                        }
+                      ]}
+                  >
+                    <NRadioGroup
+                        v-model:value={forms.accompanimentType}
+                    >
+                      <NRadio value={1}>自制伴奏</NRadio>
+                      <NRadio value={2}>普通伴奏</NRadio>
+                    </NRadioGroup>
+                  </NFormItemGi>
+              )}
+            </NGrid>
+            <NGrid cols={2}>
+              {forms.mp3Type === 'MP3' && (
+                  <NFormItemGi
+                      label="上传伴奏"
+                      path="xmlFileUrl"
+                      rule={[
+                        {
+                          required: true,
+                          message: '请选择上传.mp3/.aac'
+                        }
+                      ]}
+                  >
+                    <UploadFile
+                        size={10}
+                        v-model:fileList={forms.xmlFileUrl}
+                        tips="仅支持上传.mp3/.aac格式文件"
+                        listType="image"
+                        accept=".mp3,.aac"
+                        bucketName="cloud-coach"
+                        text="点击上传伴奏文件"
+                        onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
+                    />
+                  </NFormItemGi>
+              )}
+              {forms.mp3Type === 'MIDI' && (
+                  <NFormItemGi
+                      label="上传MIDI"
+                      path="xmlFileUrl"
+                      rule={[
+                        {
+                          required: true,
+                          message: '请选择上传.MIDI格式文件'
+                        }
+                      ]}
+                  >
+                    <UploadFile
+                        size={10}
+                        v-model:fileList={forms.xmlFileUrl}
+                        tips="仅支持上传.MIDI格式文件"
+                        listType="image"
+                        accept=".mp3,.aac"
+                        bucketName="cloud-coach"
+                        text="点击上传MIDI文件"
+                        onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
+                    />
+                  </NFormItemGi>
+              )}
+              <NFormItemGi
+                  label="上传XML"
+                  path="xmlFileUrl"
+                  rule={[
+                    {
+                      required: true,
+                      message: '请选择上传XML'
+                    }
+                  ]}
+              >
+                <UploadFile
+                    size={10}
+                    v-model:fileList={forms.xmlFileUrl}
+                    tips="仅支持上传.xml格式文件"
+                    listType="image"
+                    accept=".xml"
+                    bucketName="cloud-coach"
+                    text="点击上传XML文件"
+                    onReadFileInputEventAsArrayBuffer={readFileInputEventAsArrayBuffer}
+                />
+              </NFormItemGi>
+
+            </NGrid>
+
+            <NGrid cols={2}>
+              <NFormItemGi label="可用声部" path="musicSubject"
+                           rule={[
+                             {
+                               required: true
+                             }
+                           ]}
+              >
+                <NSelect
+                    v-model:value={forms.musicSubject}
+                    options={props.subjectList}
+                    multiple
+                    filterable
+                    clearable
+                    placeholder="请选择可用声部"
+                />
+              </NFormItemGi>
+              { (forms.mp3Type === 'MP3' || forms.musicSheetType === 'SINGLE') && (
+                  <NFormItemGi
+                      label="页面渲染声轨"
+                      path="audioType"
+                      rule={[
+                        {
+                          required: true,
+                          message: '请选择页面渲染声轨'
+                        }
+                      ]}
+                  >
+                    <CheckboxGroup>
+                      <NCheckbox value="长笛">长笛</NCheckbox>
+                      <NCheckbox value="竖笛">竖笛</NCheckbox>
+                      <NCheckbox value="葫芦丝">葫芦丝</NCheckbox>
+                      <NCheckbox value="萨克斯">萨克斯</NCheckbox>
+                    </CheckboxGroup>
+                  </NFormItemGi>
+              )}
+              {forms.mp3Type === 'MIDI' && forms.musicSheetType === 'CONCERT' && (
+                  <NFormItemGi
+                      label="用户可切换声轨"
+                      path="audioType"
+                      rule={[
+                        {
+                          required: true,
+                          message: '请选择用户可切换声轨'
+                        }
+                      ]}
+                  >
+                    <CheckboxGroup>
+                      <NCheckbox value="长笛">长笛</NCheckbox>
+                      <NCheckbox value="竖笛">竖笛</NCheckbox>
+                      <NCheckbox value="葫芦丝">葫芦丝</NCheckbox>
+                      <NCheckbox value="萨克斯">萨克斯</NCheckbox>
+                    </CheckboxGroup>
+                  </NFormItemGi>
+              )}
+
+            </NGrid>
+
+            <NGrid cols={2}>
+              <NFormItemGi label="是否播放节拍器" path="audioType"
+                           rule={[
+                             {
+                               required: true,
+                               message: '请选择是否播放节拍器'
+                             }
+                           ]}
+              >
+                <NRadioGroup
+                    v-model:value={forms.playMetronome}
+                >
+                  <NRadio value={1}>是</NRadio>
+                  <NRadio value={0}>否</NRadio>
+                </NRadioGroup>
+              </NFormItemGi>
+              {forms.playMetronome && (
+                  <NFormItemGi label="播放方式" path="audioType"
+                               rule={[
+                                 {
+                                   required: true,
+                                   message: '请选择播放方式'
+                                 }
+                               ]}
+                  >
+                    <NRadioGroup
+                        v-model:value={forms.playStyle}
+                    >
+                      <NRadio value="MP3">系统节拍器</NRadio>
+                      <NRadio value="MIDI">MP3节拍器</NRadio>
+                    </NRadioGroup>
+                  </NFormItemGi>
+              )}
+            </NGrid>
+            {/* 只有播放类型为mp3时才会有原音 */}
+            {forms.audioType === 'MP3' && (
+                <>
+                  {forms.background.map((item: any, index: number) => (
+                      <>
+                        {item.track?.toLocaleUpperCase?.() != 'COMMON' && <NGrid class={styles.audioSection}>
+                            <NFormItemGi
+                                span={12}
+                                label="原音"
+                                path={`background[${index}].audioFileUrl`}
+                                rule={[
+                                  {
+                                    required: true,
+                                    message: `请上传${
+                                        item.track ? item.track + '的' : '第' + (index + 1) + '个'
+                                    }原音`
+                                  }
+                                ]}
+                            >
+                                <UploadFile
+                                    size={10}
+                                    v-model:fileList={item.audioFileUrl}
+                                    tips="仅支持上传.mp3/.aac格式文件"
+                                    listType="image"
+                                    accept=".mp3,.aac"
+                                    bucketName="cloud-coach"
+                                />
+                            </NFormItemGi>
+                          {state.partListNames.length > 1 && (
+                              <NFormItemGi
+                                  span={12}
+                                  label="所属轨道"
+                                  path={`background[${index}].track`}
+                                  rule={[
+                                    {
+                                      required: true,
+                                      message: '请选择所属轨道'
+                                    }
+                                  ]}
+                              >
+                                <NSelect
+                                    placeholder="请选择所属轨道"
+                                    v-model:value={item.track}
+                                    options={initPartsListStatus(item.track)}
+                                />
+                              </NFormItemGi>
+                          )}
+                            <NGi class={styles.btnRemove}>
+                                <NButton
+                                    type="primary"
+                                    text
+                                    disabled={forms.background.length === 1}
+                                    onClick={() => removeSys(index)}
+                                >
+                                    删除
+                                </NButton>
+                            </NGi>
+                        </NGrid>}
+                      </>
+                  ))}
+
+                  <NButton
+                      type="primary"
+                      dashed
+                      block
+                      disabled={state.partListNames.length <= forms.background.length}
+                      style={{
+                        marginBottom: '24px'
+                      }}
+                      onClick={createSys}
+                  >
+                    添加原音
+                  </NButton>
+                </>
+            )}
+          </NForm>
+
+          {props.type !== 'preview' &&
+              (
+                  <NSpace justify="end">
+                    <NButton type="default" onClick={() => emit('close')}>
+                      取消
+                    </NButton>
+                    <NButton
+                        type="primary"
+                        onClick={() => onSubmit()}
+                        loading={btnLoading.value}
+                        disabled={btnLoading.value}
+                    >
+                      确认
+                    </NButton>
+                  </NSpace>
+              )}
+        </div>
+    )
+  }
+})

+ 23 - 0
src/views/music-library/music-sheet/modal/musicPreView.tsx

@@ -0,0 +1,23 @@
+import { defineComponent } from 'vue'
+import { useUserStore } from '@/store/modules/user'
+
+export default defineComponent({
+  name: 'musicPreView',
+  props: {
+    item: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  setup(props, { emit }) {
+    const userStore = useUserStore()
+    const token = userStore.getToken
+    const prefix = /(localhost|192)/.test(location.host) ? 'https://ponline.colexiu.com' : location.origin
+    const src = prefix + `/orchestra-music-score/?_t=${Date.now()}&id=${props.item.id}&modelType=practice&modeType=json&Authorization=${token}`
+    return () => (
+      <div>
+        <iframe width={'667px'} height={'375px'} frameborder="0" src={src}></iframe>
+      </div>
+    )
+  }
+})

+ 125 - 0
src/views/music-library/music-sheet/modal/tag-operation.tsx

@@ -0,0 +1,125 @@
+import {
+  NForm,
+  NFormItem,
+  NInput,
+  NSelect,
+  NSpace,
+  NButton,
+  useMessage,
+  NCascader,
+  NInputNumber
+} from 'naive-ui'
+import type { TreeSelectOption } from 'naive-ui'
+import { defineComponent, PropType, reactive, ref } from 'vue'
+import { musicTagSave, musicTagUpdate } from '../../api'
+
+export default defineComponent({
+  name: 'city-operation',
+  props: {
+    type: {
+      type: String,
+      default: 'add'
+    },
+    data: {
+      type: Object as PropType<any>,
+      default: () => {}
+    },
+    catalogList: {
+      type: Array as PropType<Array<TreeSelectOption>>,
+      default: () => []
+    },
+    clientTypeList: {
+      type: Array as PropType<Array<string>>,
+      default: () => []
+    }
+  },
+  emits: ['close', 'getList'],
+  setup(props, { slots, attrs, emit }) {
+    const forms = reactive({
+      name: null,
+      sortNumber: null
+    })
+
+    const btnLoading = ref(false)
+    const formsRef = ref()
+    const message = useMessage()
+
+    // 提交记录
+    const onSubmit = async () => {
+      formsRef.value.validate(async (error: any) => {
+        if (error) return
+        try {
+          btnLoading.value = true
+          if (props.type === 'add') {
+            await musicTagSave({ ...forms })
+            message.success('添加成功')
+          } else if (props.type === 'edit') {
+            await musicTagUpdate({ ...forms, id: props.data.id })
+            message.success('修改成功')
+          }
+          emit('getList')
+          emit('close')
+        } catch {
+          //
+        }
+        setTimeout(() => {
+          btnLoading.value = false
+        }, 100)
+      })
+    }
+
+    // 初始化数据
+    if (props.type === 'edit') {
+      const data = props.data
+      forms.name = data.name
+      forms.sortNumber = data.sortNumber
+    }
+
+    return () => (
+      <div style="background: #fff; padding-top: 12px">
+        <NForm model={forms} ref={formsRef} label-placement="left" label-width="auto">
+          <NFormItem
+            label="名称"
+            path="name"
+            rule={[
+              {
+                required: true,
+                message: '请输入名称'
+              }
+            ]}
+          >
+            <NInput
+              v-model:value={forms.name}
+              placeholder="请输入名称"
+              clearable
+              showCount
+              maxlength={6}
+            />
+          </NFormItem>
+          <NFormItem label="排序" path="sortNumber">
+            <NInputNumber
+              v-model:value={forms.sortNumber}
+              placeholder="请输入排序"
+              min={0}
+              style={{ width: '100%' }}
+            />
+          </NFormItem>
+        </NForm>
+
+        <NSpace justify="end">
+          <NButton type="default" onClick={() => emit('close')}>
+            取消
+          </NButton>
+          <NButton
+            type="primary"
+            onClick={() => onSubmit()}
+            loading={btnLoading.value}
+            disabled={btnLoading.value}
+          >
+            保存
+          </NButton>
+        </NSpace>
+      </div>
+    )
+  }
+})

+ 213 - 0
src/views/music-library/music-sheet/modal/use-project.tsx

@@ -0,0 +1,213 @@
+import {defineComponent, reactive, onMounted} from 'vue'
+import {useUserStore} from '@/store/modules/user'
+import styles from "@views/music-library/music-sheet/modal/index.module.less";
+import {ref} from 'vue';
+import {NButton, NCheckbox, NForm, NFormItem, NGrid, NInput, NInputNumber, NSelect, NSpace, NTab, NTabPane, NTabs} from "naive-ui";
+import {CheckboxGroup} from "vant";
+import {setTabsCaches} from "@/hooks/use-async";
+import {useRoute} from "vue-router";
+
+export default defineComponent({
+  name: 'musicPreView',
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+      }
+    },
+    rowData: {
+      type: Object,
+      default: () => {
+      }
+    }
+  },
+  setup(props, {emit}) {
+    const state = reactive({
+      tabName: '管乐迷'
+    })
+
+    onMounted(() => {
+      // getTagList()
+      console.log(props.rowData)
+    })
+
+    const userStore = useUserStore()
+    const token = userStore.getToken
+    const forms = reactive({})
+    const formsRef = ref()
+    const route = useRoute()
+    const setTabs = (val: any) => {
+      setTabsCaches(val, 'tabName', route)
+    }
+
+    function onSubmit() {
+
+    }
+
+
+    return () => (
+        <div>
+          <NForm
+              class={styles.formContainer}
+              model={forms}
+              ref={formsRef}
+              label-placement="left"
+              label-width="85"
+          >
+            <NFormItem
+                label="适用应用:"
+                path="audioType"
+                rule={[
+                  {
+                    required: true,
+                    message: '请选择播放类型'
+                  }
+                ]}
+            >
+              <CheckboxGroup>
+                <NCheckbox value="长笛">长笛</NCheckbox>
+                <NCheckbox value="竖笛">竖笛</NCheckbox>
+                <NCheckbox value="葫芦丝">葫芦丝</NCheckbox>
+                <NCheckbox value="萨克斯">萨克斯</NCheckbox>
+              </CheckboxGroup>
+            </NFormItem>
+            <NTabs
+                type="line"
+                v-model:value={state.tabName}
+                onUpdate:value={(val: any) => setTabs(val)}
+            >
+              <NTabPane
+                  name="管乐迷"
+                  tab="管乐迷"
+              >
+                <NFormItem
+                    label="曲目分类"
+                    path="musicSheetType"
+                >
+                  <NSelect>
+                  </NSelect>
+                </NFormItem>
+                <NFormItem
+                    label="是否收费"
+                    path="musicSheetType"
+                >
+                  <NSelect>
+                  </NSelect>
+                </NFormItem>
+                <NFormItem
+                    label="排序值"
+                    path="musicSheetType"
+                >
+                  <NInput></NInput>
+                </NFormItem>
+              </NTabPane>
+              <NTabPane
+                  name="管乐团"
+                  tab="管乐团"
+              >
+                <NFormItem
+                    label="分类"
+                    path="musicSheetType"
+                >
+                  <NSelect>
+                  </NSelect>
+                </NFormItem>
+                <NFormItem
+                    label="排序值"
+                    path="musicSheetType"
+                >
+                  <NInput></NInput>
+                </NFormItem>
+              </NTabPane>
+
+              <NTabPane
+                  name="酷乐秀"
+                  tab="酷乐秀"
+              >
+                <NFormItem
+                    label="可用途径"
+                    path="musicSheetType"
+                >
+                  <NSelect>
+                  </NSelect>
+                </NFormItem>
+                <NFormItem
+                    label="曲目标签"
+                    path="musicSheetType"
+                >
+                  <NSelect>
+                  </NSelect>
+                </NFormItem>
+                <NFormItem
+                    label="是否收费"
+                    path="musicSheetType"
+                >
+                  <NSelect>
+                  </NSelect>
+                </NFormItem>
+                <NFormItem
+                    label="曲目价格"
+                    path="musicSheetType"
+                >
+                  <NSelect>
+                  </NSelect>
+                </NFormItem>
+                <NFormItem
+                    label="是否置顶"
+                    path="musicSheetType"
+                >
+                  <NSelect>
+                  </NSelect>
+                </NFormItem>
+                <NFormItem
+                    label="精品乐谱"
+                    path="musicSheetType"
+                >
+                  <NSelect>
+                  </NSelect>
+                </NFormItem>
+                <NFormItem
+                    label="排序值"
+                    path="musicSheetType"
+                >
+                  <NInput></NInput>
+                </NFormItem>
+              </NTabPane>
+
+              <NTabPane
+                  name="音乐数字课堂"
+                  tab="音乐数字课堂"
+              >
+                <NFormItem
+                    label="曲目分类"
+                    path="musicSheetType"
+                >
+                  <NSelect>
+                  </NSelect>
+                </NFormItem>
+                <NFormItem
+                    label="排序值"
+                    path="musicSheetType"
+                >
+                  <NInput></NInput>
+                </NFormItem>
+              </NTabPane>
+            </NTabs>
+          </NForm>
+          <NSpace justify="end">
+            <NButton type="default" onClick={() => emit('close')}>
+              取消
+            </NButton>
+            <NButton
+                type="primary"
+                onClick={() => onSubmit()}
+                // loading={btnLoading.value}
+                // disabled={btnLoading.value}
+            >
+              确认
+            </NButton>
+          </NSpace>
+        </div>
+    )
+  }
+})

+ 56 - 0
src/views/music-library/project-music-sheet/index.tsx

@@ -0,0 +1,56 @@
+import { NTabPane, NTabs } from 'naive-ui'
+import { defineComponent, h, reactive, resolveDynamicComponent } from 'vue'
+import { useRoute } from 'vue-router'
+import { getTabsCache, setTabsCaches } from '@/hooks/use-async'
+import Mec from "@views/music-library/project-music-sheet/module/mec/mec";
+import Jmedu from "@views/music-library/project-music-sheet/module/jmedu/jmedu";
+export default defineComponent({
+  name: 'project-music-sheet',
+  setup() {
+    const state = reactive({
+      tabName: 'mec' as 'mec' | 'jmedu' | 'cooleshow' | 'cooleshow-edu',
+      searchId: null
+    })
+    const route = useRoute()
+    const setTabs = (val: any) => {
+      setTabsCaches(val, 'tabName', route)
+    }
+    return () => {
+      return (
+        <div class="system-menu-container">
+          <h2>项目曲目管理</h2>
+
+          <div class={['section-container']} style="padding-top: 0">
+            <NTabs
+              type="line"
+              size="large"
+              v-model:value={state.tabName}
+              onUpdate:value={(val: any) => setTabs(val)}
+            >
+              <NTabPane name="mec" tab="管乐迷"
+                  //v-auth="musicSheet/page1602301588206350338"
+              >
+                <Mec></Mec>
+              </NTabPane>
+              {/* <NTabPane name="TagList" tab="曲目标签管理" v-auth="musicTag/page1602301689389740033">
+                <TagList />
+              </NTabPane> */}
+              <NTabPane
+                name="jmedu"
+                tab="管乐团"
+              >
+                <Jmedu></Jmedu>
+              </NTabPane>
+              <NTabPane
+                  name="cooleshow"
+                  tab="酷乐秀"
+              >
+                <Jmedu></Jmedu>
+              </NTabPane>
+            </NTabs>
+          </div>
+        </div>
+      )
+    }
+  }
+})

+ 240 - 0
src/views/music-library/project-music-sheet/module/cooleshow/addMusic.tsx

@@ -0,0 +1,240 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import SaveForm from "@components/save-form";
+import {NButton, NDataTable, NDatePicker, NDescriptions, NDescriptionsItem, NFormItem, NImage, NInput, NModal, NSelect, NSpace, NStep, NSteps} from "naive-ui";
+import Pagination from "@components/pagination";
+import TheTooltip from "@components/TheTooltip";
+
+export default defineComponent({
+  name: 'project-music-sheet-mec',
+  setup(props, {slots, attrs, emit}) {
+    const state = reactive({
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null,
+        subjectType: null,
+        musicSubject: null,
+        status: null
+      },
+      subjectList: [],
+      showAdd: false,
+      currentStep: 1,
+      dataList: []
+    })
+
+    onMounted(() => {
+    })
+
+    const saveForm = ref()
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '曲目信息',
+          key: 'id',
+          render: (row: any) => (
+              <>
+                <NDescriptions labelPlacement="left" column={1}>
+                  <NDescriptionsItem label="曲目名称">
+                    <TheTooltip content={row.musicSheetName}/>{' '}
+                  </NDescriptionsItem>
+                  <NDescriptionsItem label="曲目编号">{row.id}</NDescriptionsItem>
+                </NDescriptions>
+              </>
+          )
+        },
+        {
+          title: '封面图',
+          key: 'titleImg',
+          render(row: any) {
+            return <NImage width={60} height={60} src={row.titleImg}/>
+          }
+        },
+        {
+          title: '声部',
+          key: 'titleImg',
+        },
+        {
+          title: '曲目名称',
+          key: 'titleImg',
+        },
+        {
+          title: '音乐人',
+          key: 'titleImg',
+        },
+        {
+          title: '曲目类型',
+          key: 'titleImg',
+        },
+        {
+          title: '作者属性',
+          key: 'titleImg',
+        },
+        {
+          title: '所属人',
+          key: 'titleImg',
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+                <NSpace>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/status1612431726029942786"
+                      // onClick={() => onChangeStatus(row)}
+                  >
+                    {row.status ? '停用' : '启用'}
+                  </NButton>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/update1602302618558099458"
+                      onClick={() => {
+                        // state.visiableMusic = true
+                        // state.musicOperation = 'edit'
+                        // state.musicData = row
+                      }}
+                  >
+                    修改
+                  </NButton>
+                </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    const updateCurrent = (val: any) => {
+      state.currentStep = val
+    }
+
+    return () => {
+      return (
+          <div class="system-menu-container">
+            <NSpace vertical size="medium">
+              <NSteps current={state.currentStep} onUpdateCurrent={updateCurrent} style={"margin-bottom: 20px;"}>
+                <NStep
+                    title="选择分类"
+                    description=""
+                >
+                </NStep>
+                <NStep
+                    title="选择分类下曲目"
+                    description=""
+                >
+                </NStep>
+                <NStep
+                    title="设置曲目信息"
+                    description=""
+                >
+                </NStep>
+              </NSteps>
+            </NSpace>
+            {state.currentStep === 1 && (
+                <SaveForm>
+                  <NFormItem label="关键词" path="keyword">
+                    <NSelect></NSelect>
+                  </NFormItem>
+                </SaveForm>
+            )}
+            {state.currentStep === 2 && (
+                <div class="system-menu-container">
+                  <SaveForm>
+                    <NFormItem label="关键词" path="keyword">
+                      <NSelect></NSelect>
+                    </NFormItem>
+                    <NFormItem label="曲目类型" path="keyword">
+                      <NSelect></NSelect>
+                    </NFormItem>
+                    <NFormItem label="声部" path="keyword">
+                      <NSelect></NSelect>
+                    </NFormItem>
+                    <NFormItem label="曲目来源" path="keyword">
+                      <NSelect></NSelect>
+                    </NFormItem>
+                    <NFormItem>
+                      <NSpace>
+                        <NButton type="primary" onClick={onSearch}>
+                          搜索
+                        </NButton>
+                        <NButton type="default" onClick={onBtnReset}>
+                          重置
+                        </NButton>
+                      </NSpace>
+                    </NFormItem>
+                  </SaveForm>
+                  <NDataTable
+                      loading={state.loading}
+                      columns={columns()}
+                      data={state.dataList}
+                  ></NDataTable>
+                  <Pagination
+                      v-model:page={state.pagination.page}
+                      v-model:pageSize={state.pagination.rows}
+                      v-model:pageTotal={state.pagination.pageTotal}
+                      // onList={getList}
+                      sync
+                      saveKey="music-list"
+                  ></Pagination>
+                </div>
+            )}
+            {state.currentStep === 3 && (
+                <div class="system-menu-container" style={"margin-top: 15px;"}>
+                  <NDataTable
+                      loading={state.loading}
+                      columns={columns()}
+                      data={state.dataList}
+                  ></NDataTable>
+                </div>
+            )}
+            <NSpace justify="end">
+              <NButton type="default" onClick={() => {
+                if (state.currentStep > 1) {
+                  state.currentStep = state.currentStep - 1;
+                } else {
+                  emit('close')
+                }
+              }}>
+                {state.currentStep === 1 ? '取消' : '上一步'}
+              </NButton>
+              <NButton
+                  type="primary"
+                  onClick={() => {
+                    if (state.currentStep < 3) {
+                      state.currentStep = state.currentStep + 1;
+                    } else {
+                      onSubmit()
+                    }
+                  }}
+                  // loading={btnLoading.value}
+                  // disabled={btnLoading.value}
+              >
+                {state.currentStep === 3 ? '确定' : '下一步'}
+              </NButton>
+            </NSpace>
+          </div>
+      )
+    }
+  }
+})

+ 349 - 0
src/views/music-library/project-music-sheet/module/cooleshow/cooleshow.tsx

@@ -0,0 +1,349 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import SaveForm from "@components/save-form";
+import {NButton, NDataTable, NDatePicker, NDescriptions, NDescriptionsItem, NFormItem, NImage, NInput, NModal, NSelect, NSpace} from "naive-ui";
+import Pagination from "@components/pagination";
+import TheTooltip from "@components/TheTooltip";
+import AddMusic from "@views/music-library/project-music-sheet/module/cooleshow/addMusic";
+
+export default defineComponent({
+  name: 'project-music-sheet-cooleshow',
+
+  setup() {
+    const state = reactive({
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null,
+        subjectType: null,
+        musicSubject: null,
+        status: null
+      },
+      subjectList: [],
+      showAdd: false
+    })
+
+    onMounted(() => {
+    })
+
+    const saveForm = ref()
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '曲目信息',
+          key: 'id',
+          render: (row: any) => (
+              <>
+                <NDescriptions labelPlacement="left" column={1}>
+                  <NDescriptionsItem label="曲目名称">
+                    <TheTooltip content={row.musicSheetName}/>{' '}
+                  </NDescriptionsItem>
+                  <NDescriptionsItem label="曲目编号">{row.id}</NDescriptionsItem>
+                </NDescriptions>
+              </>
+          )
+        },
+        {
+          title: '封面图',
+          key: 'titleImg',
+          render(row: any) {
+            return <NImage width={60} height={60} src={row.titleImg}/>
+          }
+        },
+        {
+          title: '曲目来源',
+          key: 'musicSheetCategoriesName',
+          render: (row: any) => (
+              <>
+                <NDescriptions labelPlacement="left" column={1}>
+                  <NDescriptionsItem label="曲目来源">
+                    <TheTooltip content={row.musicSheetCategoriesName}/>{' '}
+                  </NDescriptionsItem>
+                  <NDescriptionsItem label="所属人">
+                    {row.accompanimentType === 'HOMEMODE' ? '自制伴奏' : '普通伴奏'}
+                  </NDescriptionsItem>
+                </NDescriptions>
+              </>
+          )
+        },
+        {
+          title: '曲目类型',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '可用声部',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '分类',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '是否收费',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '上传人',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '上传时间',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '状态',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+                <NSpace>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/status1612431726029942786"
+                      // onClick={() => onChangeStatus(row)}
+                  >
+                    {row.status ? '停用' : '启用'}
+                  </NButton>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/update1602302618558099458"
+                      onClick={() => {
+                        // state.visiableMusic = true
+                        // state.musicOperation = 'edit'
+                        // state.musicData = row
+                      }}
+                  >
+                    修改
+                  </NButton>
+                </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    return () => {
+      return (
+          <div class="system-menu-container">
+            <SaveForm
+                ref={saveForm}
+                model={state.searchForm}
+                onSubmit={onSubmit}
+                saveKey="music-list"
+                onSetModel={(val: any) => (state.searchForm = val)}
+            >
+
+              <NFormItem label="关键词" path="keyword">
+                <NInput
+                    placeholder="曲目名称/编号"
+                    v-model:value={state.searchForm.keyword}
+                    clearable
+                />
+              </NFormItem>
+
+              <NFormItem label="曲目类型" path="subjectType">
+                <NSelect
+                    placeholder="请选择曲目类型"
+                    v-model:value={state.searchForm.subjectType}
+                    options={
+                      [
+                        {
+                          label: '独奏',
+                          value: '1'
+                        },
+                        {
+                          label: '合奏',
+                          value: '2'
+                        }
+                      ]
+                    }
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="伴奏类型" path="musicSubject">
+                <NSelect
+                />
+              </NFormItem>
+              <NFormItem label="声部" path="musicSubject">
+                <NSelect
+                    placeholder="请选择声部"
+                    v-model:value={state.searchForm.musicSubject}
+                    options={state.subjectList}
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="状态" path="status">
+                <NSelect
+                    v-model={[state.searchForm.status, 'value']}
+                    placeholder="请选择状态"
+                    clearable
+                    options={
+                      [
+                        {
+                          label: '启用',
+                          value: true
+                        },
+                        {
+                          label: '停用',
+                          value: false
+                        }
+                      ] as any
+                    }
+                />
+              </NFormItem>
+              <NFormItem label="曲目来源" path="authorFrom">
+                <NSelect
+                    placeholder="请选择曲目来源"
+                    // v-model:value={state.searchForm.authorFrom}
+                    options={
+                      [
+                        {
+                          label: '平台',
+                          value: 'PLATFORM'
+                        },
+                        {
+                          label: '机构',
+                          value: 'TENANT'
+                        },
+                        {
+                          label: '个人',
+                          value: 'SELF'
+                        }
+                      ] as any
+                    }
+                    clearable
+                />
+              </NFormItem>
+
+              <NFormItem label="曲目类型" path="authorFrom">
+                <NSelect
+                    placeholder="请选择收费类型"
+                    // v-model:value={state.searchForm.authorFrom}
+                    options={
+                      [
+                        {
+                          label: '独奏',
+                          value: 'PLATFORM'
+                        },
+                        {
+                          label: '合奏',
+                          value: 'TENANT'
+                        }
+                      ] as any
+                    }
+                    clearable
+                />
+              </NFormItem>
+
+              <NFormItem label="所属人" path="authorFrom">
+                <NInput></NInput>
+              </NFormItem>
+              <NFormItem label="曲目分类" path="authorFrom">
+                <NSelect></NSelect>
+              </NFormItem>
+              <NFormItem label="上传时间" path="authorFrom">
+                <NDatePicker
+                    // v-model:value={state.searchForm.times}
+                    type="daterange"
+                    clearable
+                    value-format="yyyy.MM.dd"
+                    startPlaceholder="开始时间"
+                    endPlaceholder="结束时间"
+                />
+              </NFormItem>
+              <NFormItem>
+                <NSpace>
+                  <NButton type="primary" onClick={onSearch}>
+                    搜索
+                  </NButton>
+                  <NButton type="default" onClick={onBtnReset}>
+                    重置
+                  </NButton>
+                </NSpace>
+              </NFormItem>
+            </SaveForm>
+
+            <div class={['section-container']}>
+              <NSpace style={{paddingBottom: '12px'}}>
+                <NButton
+                    type="primary"
+                    //v-auth="musicSheet/save1602302550719426561"
+                    onClick={() => {
+                      state.showAdd = true
+                    }}
+                >
+                  新增
+                </NButton>
+                <NButton
+                    //v-auth="musicSheet/save1602302550719426561"
+                    onClick={() => {
+                      // state.musicOperation = 'unable'
+                      // state.musicData = {}
+                    }}
+                >
+                  批量禁用
+                </NButton>
+                <NButton
+                    //v-auth="musicSheet/save1602302550719426561"
+                    onClick={() => {
+                      // state.musicOperation = 'enable'
+                      // state.musicData = {}
+                    }}
+                >
+                  批量启用
+                </NButton>
+              </NSpace>
+
+              <NDataTable
+                  loading={state.loading}
+                  columns={columns()}
+                  // data={state.dataList}
+              ></NDataTable>
+
+              <Pagination
+                  v-model:page={state.pagination.page}
+                  v-model:pageSize={state.pagination.rows}
+                  v-model:pageTotal={state.pagination.pageTotal}
+                  // onList={getList}
+                  sync
+                  saveKey="music-list"
+              ></Pagination>
+            </div>
+
+            <NModal
+                v-model:show={state.showAdd}
+                preset="dialog"
+                showIcon={false}
+                title={'添加曲目'}
+                style={{width: '1200px',height: '800px'}}
+            >
+              <AddMusic/>
+            </NModal>
+          </div>
+      )
+    }
+  }
+})

+ 240 - 0
src/views/music-library/project-music-sheet/module/jmedu/addMusic.tsx

@@ -0,0 +1,240 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import SaveForm from "@components/save-form";
+import {NButton, NDataTable, NDatePicker, NDescriptions, NDescriptionsItem, NFormItem, NImage, NInput, NModal, NSelect, NSpace, NStep, NSteps} from "naive-ui";
+import Pagination from "@components/pagination";
+import TheTooltip from "@components/TheTooltip";
+
+export default defineComponent({
+  name: 'project-music-sheet-mec',
+  setup(props, {slots, attrs, emit}) {
+    const state = reactive({
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null,
+        subjectType: null,
+        musicSubject: null,
+        status: null
+      },
+      subjectList: [],
+      showAdd: false,
+      currentStep: 1,
+      dataList: []
+    })
+
+    onMounted(() => {
+    })
+
+    const saveForm = ref()
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '曲目信息',
+          key: 'id',
+          render: (row: any) => (
+              <>
+                <NDescriptions labelPlacement="left" column={1}>
+                  <NDescriptionsItem label="曲目名称">
+                    <TheTooltip content={row.musicSheetName}/>{' '}
+                  </NDescriptionsItem>
+                  <NDescriptionsItem label="曲目编号">{row.id}</NDescriptionsItem>
+                </NDescriptions>
+              </>
+          )
+        },
+        {
+          title: '封面图',
+          key: 'titleImg',
+          render(row: any) {
+            return <NImage width={60} height={60} src={row.titleImg}/>
+          }
+        },
+        {
+          title: '声部',
+          key: 'titleImg',
+        },
+        {
+          title: '曲目名称',
+          key: 'titleImg',
+        },
+        {
+          title: '音乐人',
+          key: 'titleImg',
+        },
+        {
+          title: '曲目类型',
+          key: 'titleImg',
+        },
+        {
+          title: '作者属性',
+          key: 'titleImg',
+        },
+        {
+          title: '所属人',
+          key: 'titleImg',
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+                <NSpace>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/status1612431726029942786"
+                      // onClick={() => onChangeStatus(row)}
+                  >
+                    {row.status ? '停用' : '启用'}
+                  </NButton>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/update1602302618558099458"
+                      onClick={() => {
+                        // state.visiableMusic = true
+                        // state.musicOperation = 'edit'
+                        // state.musicData = row
+                      }}
+                  >
+                    修改
+                  </NButton>
+                </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    const updateCurrent = (val: any) => {
+      state.currentStep = val
+    }
+
+    return () => {
+      return (
+          <div class="system-menu-container">
+            <NSpace vertical size="medium">
+              <NSteps current={state.currentStep} onUpdateCurrent={updateCurrent} style={"margin-bottom: 20px;"}>
+                <NStep
+                    title="选择分类"
+                    description=""
+                >
+                </NStep>
+                <NStep
+                    title="选择分类下曲目"
+                    description=""
+                >
+                </NStep>
+                <NStep
+                    title="设置曲目信息"
+                    description=""
+                >
+                </NStep>
+              </NSteps>
+            </NSpace>
+            {state.currentStep === 1 && (
+                <SaveForm>
+                  <NFormItem label="关键词" path="keyword">
+                    <NSelect></NSelect>
+                  </NFormItem>
+                </SaveForm>
+            )}
+            {state.currentStep === 2 && (
+                <div class="system-menu-container">
+                  <SaveForm>
+                    <NFormItem label="关键词" path="keyword">
+                      <NSelect></NSelect>
+                    </NFormItem>
+                    <NFormItem label="曲目类型" path="keyword">
+                      <NSelect></NSelect>
+                    </NFormItem>
+                    <NFormItem label="声部" path="keyword">
+                      <NSelect></NSelect>
+                    </NFormItem>
+                    <NFormItem label="曲目来源" path="keyword">
+                      <NSelect></NSelect>
+                    </NFormItem>
+                    <NFormItem>
+                      <NSpace>
+                        <NButton type="primary" onClick={onSearch}>
+                          搜索
+                        </NButton>
+                        <NButton type="default" onClick={onBtnReset}>
+                          重置
+                        </NButton>
+                      </NSpace>
+                    </NFormItem>
+                  </SaveForm>
+                  <NDataTable
+                      loading={state.loading}
+                      columns={columns()}
+                      data={state.dataList}
+                  ></NDataTable>
+                  <Pagination
+                      v-model:page={state.pagination.page}
+                      v-model:pageSize={state.pagination.rows}
+                      v-model:pageTotal={state.pagination.pageTotal}
+                      // onList={getList}
+                      sync
+                      saveKey="music-list"
+                  ></Pagination>
+                </div>
+            )}
+            {state.currentStep === 3 && (
+                <div class="system-menu-container" style={"margin-top: 15px;"}>
+                  <NDataTable
+                      loading={state.loading}
+                      columns={columns()}
+                      data={state.dataList}
+                  ></NDataTable>
+                </div>
+            )}
+            <NSpace justify="end">
+              <NButton type="default" onClick={() => {
+                if (state.currentStep > 1) {
+                  state.currentStep = state.currentStep - 1;
+                } else {
+                  emit('close')
+                }
+              }}>
+                {state.currentStep === 1 ? '取消' : '上一步'}
+              </NButton>
+              <NButton
+                  type="primary"
+                  onClick={() => {
+                    if (state.currentStep < 3) {
+                      state.currentStep = state.currentStep + 1;
+                    } else {
+                      onSubmit()
+                    }
+                  }}
+                  // loading={btnLoading.value}
+                  // disabled={btnLoading.value}
+              >
+                {state.currentStep === 3 ? '确定' : '下一步'}
+              </NButton>
+            </NSpace>
+          </div>
+      )
+    }
+  }
+})

+ 349 - 0
src/views/music-library/project-music-sheet/module/jmedu/jmedu.tsx

@@ -0,0 +1,349 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import SaveForm from "@components/save-form";
+import {NButton, NDataTable, NDatePicker, NDescriptions, NDescriptionsItem, NFormItem, NImage, NInput, NModal, NSelect, NSpace} from "naive-ui";
+import Pagination from "@components/pagination";
+import TheTooltip from "@components/TheTooltip";
+import AddMusic from "@views/music-library/project-music-sheet/module/jmedu/addMusic";
+
+export default defineComponent({
+  name: 'project-music-sheet-jmedu',
+
+  setup() {
+    const state = reactive({
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null,
+        subjectType: null,
+        musicSubject: null,
+        status: null
+      },
+      subjectList: [],
+      showAdd: false
+    })
+
+    onMounted(() => {
+    })
+
+    const saveForm = ref()
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '曲目信息',
+          key: 'id',
+          render: (row: any) => (
+              <>
+                <NDescriptions labelPlacement="left" column={1}>
+                  <NDescriptionsItem label="曲目名称">
+                    <TheTooltip content={row.musicSheetName}/>{' '}
+                  </NDescriptionsItem>
+                  <NDescriptionsItem label="曲目编号">{row.id}</NDescriptionsItem>
+                </NDescriptions>
+              </>
+          )
+        },
+        {
+          title: '封面图',
+          key: 'titleImg',
+          render(row: any) {
+            return <NImage width={60} height={60} src={row.titleImg}/>
+          }
+        },
+        {
+          title: '曲目来源',
+          key: 'musicSheetCategoriesName',
+          render: (row: any) => (
+              <>
+                <NDescriptions labelPlacement="left" column={1}>
+                  <NDescriptionsItem label="曲目来源">
+                    <TheTooltip content={row.musicSheetCategoriesName}/>{' '}
+                  </NDescriptionsItem>
+                  <NDescriptionsItem label="所属人">
+                    {row.accompanimentType === 'HOMEMODE' ? '自制伴奏' : '普通伴奏'}
+                  </NDescriptionsItem>
+                </NDescriptions>
+              </>
+          )
+        },
+        {
+          title: '曲目类型',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '可用声部',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '分类',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '是否收费',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '上传人',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '上传时间',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '状态',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+                <NSpace>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/status1612431726029942786"
+                      // onClick={() => onChangeStatus(row)}
+                  >
+                    {row.status ? '停用' : '启用'}
+                  </NButton>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/update1602302618558099458"
+                      onClick={() => {
+                        // state.visiableMusic = true
+                        // state.musicOperation = 'edit'
+                        // state.musicData = row
+                      }}
+                  >
+                    修改
+                  </NButton>
+                </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    return () => {
+      return (
+          <div class="system-menu-container">
+            <SaveForm
+                ref={saveForm}
+                model={state.searchForm}
+                onSubmit={onSubmit}
+                saveKey="music-list"
+                onSetModel={(val: any) => (state.searchForm = val)}
+            >
+
+              <NFormItem label="关键词" path="keyword">
+                <NInput
+                    placeholder="曲目名称/编号"
+                    v-model:value={state.searchForm.keyword}
+                    clearable
+                />
+              </NFormItem>
+
+              <NFormItem label="曲目类型" path="subjectType">
+                <NSelect
+                    placeholder="请选择曲目类型"
+                    v-model:value={state.searchForm.subjectType}
+                    options={
+                      [
+                        {
+                          label: '独奏',
+                          value: '1'
+                        },
+                        {
+                          label: '合奏',
+                          value: '2'
+                        }
+                      ]
+                    }
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="伴奏类型" path="musicSubject">
+                <NSelect
+                />
+              </NFormItem>
+              <NFormItem label="声部" path="musicSubject">
+                <NSelect
+                    placeholder="请选择声部"
+                    v-model:value={state.searchForm.musicSubject}
+                    options={state.subjectList}
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="状态" path="status">
+                <NSelect
+                    v-model={[state.searchForm.status, 'value']}
+                    placeholder="请选择状态"
+                    clearable
+                    options={
+                      [
+                        {
+                          label: '启用',
+                          value: true
+                        },
+                        {
+                          label: '停用',
+                          value: false
+                        }
+                      ] as any
+                    }
+                />
+              </NFormItem>
+              <NFormItem label="曲目来源" path="authorFrom">
+                <NSelect
+                    placeholder="请选择曲目来源"
+                    // v-model:value={state.searchForm.authorFrom}
+                    options={
+                      [
+                        {
+                          label: '平台',
+                          value: 'PLATFORM'
+                        },
+                        {
+                          label: '机构',
+                          value: 'TENANT'
+                        },
+                        {
+                          label: '个人',
+                          value: 'SELF'
+                        }
+                      ] as any
+                    }
+                    clearable
+                />
+              </NFormItem>
+
+              <NFormItem label="曲目类型" path="authorFrom">
+                <NSelect
+                    placeholder="请选择收费类型"
+                    // v-model:value={state.searchForm.authorFrom}
+                    options={
+                      [
+                        {
+                          label: '独奏',
+                          value: 'PLATFORM'
+                        },
+                        {
+                          label: '合奏',
+                          value: 'TENANT'
+                        }
+                      ] as any
+                    }
+                    clearable
+                />
+              </NFormItem>
+
+              <NFormItem label="所属人" path="authorFrom">
+                <NInput></NInput>
+              </NFormItem>
+              <NFormItem label="曲目分类" path="authorFrom">
+                <NSelect></NSelect>
+              </NFormItem>
+              <NFormItem label="上传时间" path="authorFrom">
+                <NDatePicker
+                    // v-model:value={state.searchForm.times}
+                    type="daterange"
+                    clearable
+                    value-format="yyyy.MM.dd"
+                    startPlaceholder="开始时间"
+                    endPlaceholder="结束时间"
+                />
+              </NFormItem>
+              <NFormItem>
+                <NSpace>
+                  <NButton type="primary" onClick={onSearch}>
+                    搜索
+                  </NButton>
+                  <NButton type="default" onClick={onBtnReset}>
+                    重置
+                  </NButton>
+                </NSpace>
+              </NFormItem>
+            </SaveForm>
+
+            <div class={['section-container']}>
+              <NSpace style={{paddingBottom: '12px'}}>
+                <NButton
+                    type="primary"
+                    //v-auth="musicSheet/save1602302550719426561"
+                    onClick={() => {
+                      state.showAdd = true
+                    }}
+                >
+                  新增
+                </NButton>
+                <NButton
+                    //v-auth="musicSheet/save1602302550719426561"
+                    onClick={() => {
+                      // state.musicOperation = 'unable'
+                      // state.musicData = {}
+                    }}
+                >
+                  批量禁用
+                </NButton>
+                <NButton
+                    //v-auth="musicSheet/save1602302550719426561"
+                    onClick={() => {
+                      // state.musicOperation = 'enable'
+                      // state.musicData = {}
+                    }}
+                >
+                  批量启用
+                </NButton>
+              </NSpace>
+
+              <NDataTable
+                  loading={state.loading}
+                  columns={columns()}
+                  // data={state.dataList}
+              ></NDataTable>
+
+              <Pagination
+                  v-model:page={state.pagination.page}
+                  v-model:pageSize={state.pagination.rows}
+                  v-model:pageTotal={state.pagination.pageTotal}
+                  // onList={getList}
+                  sync
+                  saveKey="music-list"
+              ></Pagination>
+            </div>
+
+            <NModal
+                v-model:show={state.showAdd}
+                preset="dialog"
+                showIcon={false}
+                title={'添加曲目'}
+                style={{width: '1200px',height: '800px'}}
+            >
+              <AddMusic/>
+            </NModal>
+          </div>
+      )
+    }
+  }
+})

+ 240 - 0
src/views/music-library/project-music-sheet/module/mec/addMusic.tsx

@@ -0,0 +1,240 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import SaveForm from "@components/save-form";
+import {NButton, NDataTable, NDatePicker, NDescriptions, NDescriptionsItem, NFormItem, NImage, NInput, NModal, NSelect, NSpace, NStep, NSteps} from "naive-ui";
+import Pagination from "@components/pagination";
+import TheTooltip from "@components/TheTooltip";
+
+export default defineComponent({
+  name: 'project-music-sheet-mec',
+  setup(props, {slots, attrs, emit}) {
+    const state = reactive({
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null,
+        subjectType: null,
+        musicSubject: null,
+        status: null
+      },
+      subjectList: [],
+      showAdd: false,
+      currentStep: 1,
+      dataList: []
+    })
+
+    onMounted(() => {
+    })
+
+    const saveForm = ref()
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '曲目信息',
+          key: 'id',
+          render: (row: any) => (
+              <>
+                <NDescriptions labelPlacement="left" column={1}>
+                  <NDescriptionsItem label="曲目名称">
+                    <TheTooltip content={row.musicSheetName}/>{' '}
+                  </NDescriptionsItem>
+                  <NDescriptionsItem label="曲目编号">{row.id}</NDescriptionsItem>
+                </NDescriptions>
+              </>
+          )
+        },
+        {
+          title: '封面图',
+          key: 'titleImg',
+          render(row: any) {
+            return <NImage width={60} height={60} src={row.titleImg}/>
+          }
+        },
+        {
+          title: '声部',
+          key: 'titleImg',
+        },
+        {
+          title: '曲目名称',
+          key: 'titleImg',
+        },
+        {
+          title: '音乐人',
+          key: 'titleImg',
+        },
+        {
+          title: '曲目类型',
+          key: 'titleImg',
+        },
+        {
+          title: '作者属性',
+          key: 'titleImg',
+        },
+        {
+          title: '所属人',
+          key: 'titleImg',
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+                <NSpace>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/status1612431726029942786"
+                      // onClick={() => onChangeStatus(row)}
+                  >
+                    {row.status ? '停用' : '启用'}
+                  </NButton>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/update1602302618558099458"
+                      onClick={() => {
+                        // state.visiableMusic = true
+                        // state.musicOperation = 'edit'
+                        // state.musicData = row
+                      }}
+                  >
+                    修改
+                  </NButton>
+                </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    const updateCurrent = (val: any) => {
+      state.currentStep = val
+    }
+
+    return () => {
+      return (
+          <div class="system-menu-container">
+            <NSpace vertical size="medium">
+              <NSteps current={state.currentStep} onUpdateCurrent={updateCurrent} style={"margin-bottom: 20px;"}>
+                <NStep
+                    title="选择分类"
+                    description=""
+                >
+                </NStep>
+                <NStep
+                    title="选择分类下曲目"
+                    description=""
+                >
+                </NStep>
+                <NStep
+                    title="设置曲目信息"
+                    description=""
+                >
+                </NStep>
+              </NSteps>
+            </NSpace>
+            {state.currentStep === 1 && (
+                <SaveForm>
+                  <NFormItem label="关键词" path="keyword">
+                    <NSelect></NSelect>
+                  </NFormItem>
+                </SaveForm>
+            )}
+            {state.currentStep === 2 && (
+                <div class="system-menu-container">
+                  <SaveForm>
+                    <NFormItem label="关键词" path="keyword">
+                      <NSelect></NSelect>
+                    </NFormItem>
+                    <NFormItem label="曲目类型" path="keyword">
+                      <NSelect></NSelect>
+                    </NFormItem>
+                    <NFormItem label="声部" path="keyword">
+                      <NSelect></NSelect>
+                    </NFormItem>
+                    <NFormItem label="曲目来源" path="keyword">
+                      <NSelect></NSelect>
+                    </NFormItem>
+                    <NFormItem>
+                      <NSpace>
+                        <NButton type="primary" onClick={onSearch}>
+                          搜索
+                        </NButton>
+                        <NButton type="default" onClick={onBtnReset}>
+                          重置
+                        </NButton>
+                      </NSpace>
+                    </NFormItem>
+                  </SaveForm>
+                  <NDataTable
+                      loading={state.loading}
+                      columns={columns()}
+                      data={state.dataList}
+                  ></NDataTable>
+                  <Pagination
+                      v-model:page={state.pagination.page}
+                      v-model:pageSize={state.pagination.rows}
+                      v-model:pageTotal={state.pagination.pageTotal}
+                      // onList={getList}
+                      sync
+                      saveKey="music-list"
+                  ></Pagination>
+                </div>
+            )}
+            {state.currentStep === 3 && (
+                <div class="system-menu-container" style={"margin-top: 15px;"}>
+                  <NDataTable
+                      loading={state.loading}
+                      columns={columns()}
+                      data={state.dataList}
+                  ></NDataTable>
+                </div>
+            )}
+            <NSpace justify="end">
+              <NButton type="default" onClick={() => {
+                if (state.currentStep > 1) {
+                  state.currentStep = state.currentStep - 1;
+                } else {
+                  emit('close')
+                }
+              }}>
+                {state.currentStep === 1 ? '取消' : '上一步'}
+              </NButton>
+              <NButton
+                  type="primary"
+                  onClick={() => {
+                    if (state.currentStep < 3) {
+                      state.currentStep = state.currentStep + 1;
+                    } else {
+                      onSubmit()
+                    }
+                  }}
+                  // loading={btnLoading.value}
+                  // disabled={btnLoading.value}
+              >
+                {state.currentStep === 3 ? '确定' : '下一步'}
+              </NButton>
+            </NSpace>
+          </div>
+      )
+    }
+  }
+})

+ 349 - 0
src/views/music-library/project-music-sheet/module/mec/mec.tsx

@@ -0,0 +1,349 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import SaveForm from "@components/save-form";
+import {NButton, NDataTable, NDatePicker, NDescriptions, NDescriptionsItem, NFormItem, NImage, NInput, NModal, NSelect, NSpace} from "naive-ui";
+import Pagination from "@components/pagination";
+import TheTooltip from "@components/TheTooltip";
+import AddMusic from "@views/music-library/project-music-sheet/module/mec/addMusic";
+
+export default defineComponent({
+  name: 'project-music-sheet-mec',
+
+  setup() {
+    const state = reactive({
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null,
+        subjectType: null,
+        musicSubject: null,
+        status: null
+      },
+      subjectList: [],
+      showAdd: false
+    })
+
+    onMounted(() => {
+    })
+
+    const saveForm = ref()
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '曲目信息',
+          key: 'id',
+          render: (row: any) => (
+              <>
+                <NDescriptions labelPlacement="left" column={1}>
+                  <NDescriptionsItem label="曲目名称">
+                    <TheTooltip content={row.musicSheetName}/>{' '}
+                  </NDescriptionsItem>
+                  <NDescriptionsItem label="曲目编号">{row.id}</NDescriptionsItem>
+                </NDescriptions>
+              </>
+          )
+        },
+        {
+          title: '封面图',
+          key: 'titleImg',
+          render(row: any) {
+            return <NImage width={60} height={60} src={row.titleImg}/>
+          }
+        },
+        {
+          title: '曲目来源',
+          key: 'musicSheetCategoriesName',
+          render: (row: any) => (
+              <>
+                <NDescriptions labelPlacement="left" column={1}>
+                  <NDescriptionsItem label="曲目来源">
+                    <TheTooltip content={row.musicSheetCategoriesName}/>{' '}
+                  </NDescriptionsItem>
+                  <NDescriptionsItem label="所属人">
+                    {row.accompanimentType === 'HOMEMODE' ? '自制伴奏' : '普通伴奏'}
+                  </NDescriptionsItem>
+                </NDescriptions>
+              </>
+          )
+        },
+        {
+          title: '曲目类型',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '可用声部',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '分类',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '是否收费',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '上传人',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '上传时间',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '状态',
+          key: 'musicSheetCategoriesName'
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+                <NSpace>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/status1612431726029942786"
+                      // onClick={() => onChangeStatus(row)}
+                  >
+                    {row.status ? '停用' : '启用'}
+                  </NButton>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      //v-auth="musicSheet/update1602302618558099458"
+                      onClick={() => {
+                        // state.visiableMusic = true
+                        // state.musicOperation = 'edit'
+                        // state.musicData = row
+                      }}
+                  >
+                    修改
+                  </NButton>
+                </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    return () => {
+      return (
+          <div class="system-menu-container">
+            <SaveForm
+                ref={saveForm}
+                model={state.searchForm}
+                onSubmit={onSubmit}
+                saveKey="music-list"
+                onSetModel={(val: any) => (state.searchForm = val)}
+            >
+
+              <NFormItem label="关键词" path="keyword">
+                <NInput
+                    placeholder="曲目名称/编号"
+                    v-model:value={state.searchForm.keyword}
+                    clearable
+                />
+              </NFormItem>
+
+              <NFormItem label="曲目类型" path="subjectType">
+                <NSelect
+                    placeholder="请选择曲目类型"
+                    v-model:value={state.searchForm.subjectType}
+                    options={
+                      [
+                        {
+                          label: '独奏',
+                          value: '1'
+                        },
+                        {
+                          label: '合奏',
+                          value: '2'
+                        }
+                      ]
+                    }
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="声部" path="musicSubject">
+                <NSelect
+                    placeholder="请选择声部"
+                    v-model:value={state.searchForm.musicSubject}
+                    options={state.subjectList}
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="状态" path="status">
+                <NSelect
+                    v-model={[state.searchForm.status, 'value']}
+                    placeholder="请选择状态"
+                    clearable
+                    options={
+                      [
+                        {
+                          label: '启用',
+                          value: true
+                        },
+                        {
+                          label: '停用',
+                          value: false
+                        }
+                      ] as any
+                    }
+                />
+              </NFormItem>
+              <NFormItem label="曲目来源" path="authorFrom">
+                <NSelect
+                    placeholder="请选择曲目来源"
+                    // v-model:value={state.searchForm.authorFrom}
+                    options={
+                      [
+                        {
+                          label: '平台',
+                          value: 'PLATFORM'
+                        },
+                        {
+                          label: '机构',
+                          value: 'TENANT'
+                        },
+                        {
+                          label: '个人',
+                          value: 'SELF'
+                        }
+                      ] as any
+                    }
+                    clearable
+                />
+              </NFormItem>
+
+              <NFormItem label="收费类型" path="authorFrom">
+                <NSelect
+                    placeholder="请选择收费类型"
+                    // v-model:value={state.searchForm.authorFrom}
+                    options={
+                      [
+                        {
+                          label: '免费',
+                          value: 'PLATFORM'
+                        },
+                        {
+                          label: '收费',
+                          value: 'TENANT'
+                        },
+                        {
+                          label: '单曲收费',
+                          value: 'SELF'
+                        }
+                      ] as any
+                    }
+                    clearable
+                />
+              </NFormItem>
+
+              <NFormItem label="所属人" path="authorFrom">
+                <NInput></NInput>
+              </NFormItem>
+              <NFormItem label="曲目分类" path="authorFrom">
+                <NSelect></NSelect>
+              </NFormItem>
+              <NFormItem label="上传时间" path="authorFrom">
+                <NDatePicker
+                    // v-model:value={state.searchForm.times}
+                    type="daterange"
+                    clearable
+                    value-format="yyyy.MM.dd"
+                    startPlaceholder="开始时间"
+                    endPlaceholder="结束时间"
+                />
+              </NFormItem>
+              <NFormItem>
+                <NSpace>
+                  <NButton type="primary" onClick={onSearch}>
+                    搜索
+                  </NButton>
+                  <NButton type="default" onClick={onBtnReset}>
+                    重置
+                  </NButton>
+                </NSpace>
+              </NFormItem>
+            </SaveForm>
+
+            <div class={['section-container']}>
+              <NSpace style={{paddingBottom: '12px'}}>
+                <NButton
+                    type="primary"
+                    //v-auth="musicSheet/save1602302550719426561"
+                    onClick={() => {
+                      state.showAdd = true
+                    }}
+                >
+                  新增
+                </NButton>
+                <NButton
+                    //v-auth="musicSheet/save1602302550719426561"
+                    onClick={() => {
+                      // state.musicOperation = 'unable'
+                      // state.musicData = {}
+                    }}
+                >
+                  批量禁用
+                </NButton>
+                <NButton
+                    //v-auth="musicSheet/save1602302550719426561"
+                    onClick={() => {
+                      // state.musicOperation = 'enable'
+                      // state.musicData = {}
+                    }}
+                >
+                  批量启用
+                </NButton>
+              </NSpace>
+
+              <NDataTable
+                  loading={state.loading}
+                  columns={columns()}
+                  // data={state.dataList}
+              ></NDataTable>
+
+              <Pagination
+                  v-model:page={state.pagination.page}
+                  v-model:pageSize={state.pagination.rows}
+                  v-model:pageTotal={state.pagination.pageTotal}
+                  // onList={getList}
+                  sync
+                  saveKey="music-list"
+              ></Pagination>
+            </div>
+
+            <NModal
+                v-model:show={state.showAdd}
+                preset="dialog"
+                showIcon={false}
+                title={'添加曲目'}
+                style={{width: '1200px',height: '800px'}}
+            >
+              <AddMusic/>
+            </NModal>
+          </div>
+      )
+    }
+  }
+})