瀏覽代碼

✨ feat: 曲谱上传

wolyshaw 3 年之前
父節點
當前提交
1d179fcc94

+ 12 - 0
src/constant/music.ts

@@ -17,3 +17,15 @@ export const teacherChargeType = {
   2: '是',
   2: '是',
   0: '否'
   0: '否'
 }
 }
+
+/** 老师端是否可以评测类型 */
+export const teachercanEvaluateType = {
+  0: '否',
+  1: '是'
+}
+
+/** 老师端展示指法类型 */
+export const teachershowFingeringType = {
+  0: '否',
+  1: '是'
+}

+ 1 - 4
src/student/music/album/index.tsx

@@ -48,10 +48,7 @@ export default defineComponent({
     }
     }
 
 
     const onComfirm = tags => {
     const onComfirm = tags => {
-      const data = Object.values(tags)
-        .map((items: any) => items.join(','))
-        .filter(Boolean)
-        .join(',')
+      const data = Object.values(tags).flat().filter(Boolean).join(',')
       params.musicTagIds = data
       params.musicTagIds = data
       params.page = 1
       params.page = 1
       FetchList()
       FetchList()

+ 1 - 4
src/student/music/list/index.tsx

@@ -48,10 +48,7 @@ export default defineComponent({
     }
     }
 
 
     const onComfirm = tags => {
     const onComfirm = tags => {
-      const data = Object.values(tags)
-        .map((items: any) => items.join(','))
-        .filter(Boolean)
-        .join(',')
+      const data = Object.values(tags).flat().filter(Boolean).join(',')
       params.musicTagIds = data
       params.musicTagIds = data
       params.page = 1
       params.page = 1
       FetchList()
       FetchList()

+ 1 - 4
src/student/music/search/index.tsx

@@ -53,10 +53,7 @@ export default defineComponent({
     }
     }
 
 
     const onComfirm = tags => {
     const onComfirm = tags => {
-      const data = Object.values(tags)
-        .map((items: any) => items.join(','))
-        .filter(Boolean)
-        .join(',')
+      const data = Object.values(tags).flat().filter(Boolean).join(',')
       tagids.value = data
       tagids.value = data
       FetchList()
       FetchList()
       tagVisibility.value = false
       tagVisibility.value = false

+ 11 - 4
src/student/music/search/select-tag.tsx

@@ -1,6 +1,7 @@
 import request from '@/helpers/request'
 import request from '@/helpers/request'
 import { useAsyncState } from '@vueuse/core'
 import { useAsyncState } from '@vueuse/core'
-import { defineComponent, reactive, watch } from 'vue'
+import { defineComponent, reactive, ref, watch } from 'vue'
+import { state as appState } from '@/state'
 import { Button } from 'vant'
 import { Button } from 'vant'
 import SelectTagsChild from './select-tags-child'
 import SelectTagsChild from './select-tags-child'
 import styles from './select.module.less'
 import styles from './select.module.less'
@@ -22,8 +23,10 @@ export default defineComponent({
     }
     }
   },
   },
   setup({ onCancel, onComfirm, defaultValue }) {
   setup({ onCancel, onComfirm, defaultValue }) {
+    const prefix =
+      appState.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
     const { isLoading, state } = useAsyncState(
     const { isLoading, state } = useAsyncState(
-      request('/api-student/MusicTag/tree'),
+      request(prefix + '/MusicTag/tree'),
       null
       null
     )
     )
     const resetTags = () => {
     const resetTags = () => {
@@ -37,11 +40,15 @@ export default defineComponent({
 
 
     const defaultTags = (defaultValue || '').split(',').map(id => Number(id))
     const defaultTags = (defaultValue || '').split(',').map(id => Number(id))
     const tags: { [key in string]: number[] } = reactive({})
     const tags: { [key in string]: number[] } = reactive({})
+    const names = {}
     watch(state, () => {
     watch(state, () => {
       if (state.value) {
       if (state.value) {
         const list = (state.value && state.value.data) || []
         const list = (state.value && state.value.data) || []
         for (const item of list) {
         for (const item of list) {
-          const allids = item.children.map(c => c.id)
+          const allids = item.children.map(c => {
+            names[c.id] = c.name
+            return c.id
+          })
           tags[item.id] = defaultTags.filter(id => {
           tags[item.id] = defaultTags.filter(id => {
             return allids.includes(Number(id))
             return allids.includes(Number(id))
           })
           })
@@ -79,7 +86,7 @@ export default defineComponent({
               class={styles.btn}
               class={styles.btn}
               type="primary"
               type="primary"
               round
               round
-              onClick={() => onComfirm(tags)}
+              onClick={() => onComfirm(tags, names)}
             >
             >
               确认
               确认
             </Button>
             </Button>

+ 23 - 0
src/teacher/music/upload/index.module.less

@@ -68,3 +68,26 @@
     }
     }
   }
   }
 }
 }
+
+.tags {
+  margin-right: 5px;
+  margin-bottom: 5px;
+  margin-top: 5px;
+}
+
+.file {
+  display: flex;
+  width: 100%;
+  > div {
+    flex: 1;
+    > span {
+      display: inline-block;
+      margin-left: 5px;
+    }
+  }
+  .delbtn {
+    padding: 0;
+    height: auto;
+    border: none;
+  }
+}

+ 399 - 23
src/teacher/music/upload/index.tsx

@@ -1,25 +1,61 @@
 import { defineComponent } from 'vue'
 import { defineComponent } from 'vue'
-import { Button, Field, Sticky, Form, Tag, Radio, RadioGroup } from 'vant'
+import {
+  Button,
+  Field,
+  Sticky,
+  Form,
+  Tag,
+  Radio,
+  RadioGroup,
+  Popup,
+  Icon,
+  Empty,
+  Picker
+} from 'vant'
 import ColFieldGroup from '@/components/col-field-group'
 import ColFieldGroup from '@/components/col-field-group'
 import { MusicType } from 'src/teacher/music/list/item.d'
 import { MusicType } from 'src/teacher/music/list/item.d'
+import SubjectModel from '@/business-components/subject-list'
 import ColField from '@/components/col-field'
 import ColField from '@/components/col-field'
-import { teacherChargeType } from '@/constant/music'
+import {
+  teachercanEvaluateType,
+  teacherChargeType,
+  teachershowFingeringType
+} from '@/constant/music'
 import { getXmlInfo, FormatXMLInfo } from '@/helpers/music-xml'
 import { getXmlInfo, FormatXMLInfo } from '@/helpers/music-xml'
 import Upload from './upload'
 import Upload from './upload'
 import styles from './index.module.less'
 import styles from './index.module.less'
+import SelectTag from '@/student/music/search/select-tag'
+import { browser } from '@/helpers/utils'
+import { postMessage } from '@/helpers/native-message'
+import classNames from 'classnames'
+import { teacherState } from '@/teacher/teacher-cert/teacherState'
+import request from '@/helpers/request'
 
 
 export default defineComponent({
 export default defineComponent({
   name: 'MusicUpload',
   name: 'MusicUpload',
   data() {
   data() {
     return {
     return {
+      xmlFileUrl: '',
+      midiUrl: '',
       musicSheetName: '',
       musicSheetName: '',
       composer: '',
       composer: '',
       speed: '',
       speed: '',
       chargeType: '0',
       chargeType: '0',
+      showFingering: 1,
+      canEvaluate: 1,
       musicPrice: '',
       musicPrice: '',
       selectTagVisible: false,
       selectTagVisible: false,
-      tags: ['12312'] as string[],
-      formated: {} as FormatXMLInfo
+      subJectVisible: false,
+      tags: [] as string[],
+      tagsNames: [] as Array<{ [id in string]: string }>,
+      formated: {} as FormatXMLInfo,
+      tagVisibility: false,
+      subjectListNames: {} as any,
+      selectedSubjectList: null as any,
+      vlewSubjectList: null as any,
+      submitLoading: false,
+      showPicker: false,
+      music_sheet_service_fee: 0
     }
     }
   },
   },
   watch: {
   watch: {
@@ -32,10 +68,85 @@ export default defineComponent({
       }
       }
     }
     }
   },
   },
+  computed: {
+    choiceSubjectIds() {
+      // 选择的科目编号
+      let ids = teacherState.teacherCert.subjectId
+        ? teacherState.teacherCert.subjectId.split(',')
+        : []
+      ids = ids.map((item: any) => Number(item))
+      return ids
+    },
+    subjectList() {
+      // 学科列表
+      return teacherState.subjectList || []
+    },
+    choiceSubject() {
+      // 选择的科目
+      let tempArr: any[] = []
+      this.subjectList.forEach((parent: any) => {
+        parent.subjects &&
+          parent.subjects.forEach((sub: any) => {
+            if (this.choiceSubjectIds.includes(sub.id)) {
+              tempArr.push(sub as never)
+            }
+          })
+      })
+      return tempArr
+    }
+  },
+  async mounted() {
+    request
+      .get('/api-teacher/sysConfig/queryByParamName', {
+        params: {
+          paramName: 'music_sheet_service_fee'
+        }
+      })
+      .then(res => (this.music_sheet_service_fee = res.data.paramValue))
+    // if (teacherState.subjectList.length <= 0) {
+    request.get('/api-teacher/subject/subjectSelect').then(res => {
+      teacherState.subjectList = res.data || []
+      this.subjectListNames = this.getSubjectListNames(teacherState.subjectList)
+    })
+
+    // }
+  },
   methods: {
   methods: {
-    submit(vals: any) {
+    async submit(vals: any) {
+      this.submitLoading = true
+      try {
+        await request.post('/api-teacher/music/sheet/create', {
+          data: {
+            audioType: 'MIDI',
+            sourceType: 'TEACHER',
+            showFingering: this.showFingering,
+            musicTag: this.tags.join(','),
+            musicSubject: this.selectedSubjectList?.label,
+            musicSheetName: this.musicSheetName,
+            midiUrl: this.midiUrl,
+            xmlFileUrl: this.xmlFileUrl,
+            canEvaluate: this.canEvaluate,
+            chargeType: this.chargeType === '0' ? 'FREE' : 'CHARGE',
+            composer: this.composer,
+            musicPrice: this.musicPrice
+          }
+        })
+      } catch (error) {}
+      this.submitLoading = false
       console.log(vals)
       console.log(vals)
     },
     },
+    getSubjectListNames(list) {
+      const data = {}
+      for (const item of list) {
+        data[item.id] = item.name
+        if (item.subjects) {
+          for (const sub of item.subjects) {
+            data[sub.id] = sub.name
+          }
+        }
+      }
+      return data
+    },
     failed() {
     failed() {
       console.log('failed', this.musicSheetName)
       console.log('failed', this.musicSheetName)
     },
     },
@@ -58,18 +169,115 @@ export default defineComponent({
         this.formated = getXmlInfo(xml)
         this.formated = getXmlInfo(xml)
       }
       }
       reader.readAsText(file)
       reader.readAsText(file)
+    },
+    onChoice(val: any) {
+      this.subJectVisible = false
+      this.selectedSubjectList = [val]
+    },
+    onComfirm(tags: any, names: any) {
+      this.tagsNames = names
+      this.tagVisibility = false
+      const data = Object.values(tags).flat().filter(Boolean) as string[]
+      console.log(data)
+      this.tags = data
+    },
+    naiveXMLFile() {
+      postMessage({ api: 'chooseFile', content: { type: 'xml' } }, path => {})
+    },
+    naiveMidFile() {
+      postMessage({ api: 'chooseFile', content: { type: 'midi' } }, path => {})
+    },
+    fileName(name = '') {
+      return name.split('/').pop()
     }
     }
   },
   },
   render() {
   render() {
+    const browserInfo = browser()
     return (
     return (
       <Form onSubmit={this.submit} onFailed={this.failed}>
       <Form onSubmit={this.submit} onFailed={this.failed}>
         <div class={styles.container}>
         <div class={styles.container}>
           <ColFieldGroup class={styles.area}>
           <ColFieldGroup class={styles.area}>
-            <ColField required title="MusicXML文件">
-              <Upload
-                onUpdate:modelValue={val => console.log(val)}
-                accept=".xml"
-                formatFile={this.readerFile}
+            <ColField border={false} required title="MusicXML文件">
+              <Field
+                name="xmlFileUrl"
+                modelValue={this.xmlFileUrl}
+                rules={[{ required: true, message: '请选择MusicXML文件' }]}
+                // @ts-ignore
+                vSlots={{
+                  input: () =>
+                    browserInfo.isApp ? (
+                      <>
+                        {this.xmlFileUrl ? (
+                          <>
+                            <div
+                              class={classNames(styles.file, 'van-ellipsis')}
+                            >
+                              <div>
+                                <Icon name="music-o" />
+                                <span>{this.fileName(this.xmlFileUrl)}</span>
+                              </div>
+                              <Button
+                                class={styles.delbtn}
+                                icon="delete-o"
+                                onClick={() => (this.xmlFileUrl = '')}
+                              ></Button>
+                            </div>
+                          </>
+                        ) : (
+                          <Button icon="upgrade" onClick={this.naiveXMLFile}>
+                            上传文件
+                          </Button>
+                        )}
+                      </>
+                    ) : (
+                      <Upload
+                        onUpdate:modelValue={val => (this.xmlFileUrl = val)}
+                        accept=".xml"
+                        formatFile={this.readerFile}
+                      />
+                    )
+                }}
+              />
+            </ColField>
+            <ColField border={false} required title="MIDI文件">
+              <Field
+                name="midiUrl"
+                modelValue={this.midiUrl}
+                rules={[{ required: true, message: '请选择MIDI文件' }]}
+                // @ts-ignore
+                vSlots={{
+                  input: () =>
+                    browserInfo.isApp ? (
+                      <>
+                        {this.midiUrl ? (
+                          <>
+                            <div
+                              class={classNames(styles.file, 'van-ellipsis')}
+                            >
+                              <div>
+                                <Icon name="music-o" />
+                                <span>{this.fileName(this.midiUrl)}</span>
+                              </div>
+                              <Button
+                                class={styles.delbtn}
+                                onClick={() => (this.midiUrl = '')}
+                                icon="delete-o"
+                              ></Button>
+                            </div>
+                          </>
+                        ) : (
+                          <Button icon="upgrade" onClick={this.naiveMidFile}>
+                            上传文件
+                          </Button>
+                        )}
+                      </>
+                    ) : (
+                      <Upload
+                        onUpdate:modelValue={val => (this.midiUrl = val)}
+                        accept=".mid"
+                      />
+                    )
+                }}
               />
               />
             </ColField>
             </ColField>
           </ColFieldGroup>
           </ColFieldGroup>
@@ -96,9 +304,23 @@ export default defineComponent({
                 onUpdate:modelValue={val => (this.composer = val)}
                 onUpdate:modelValue={val => (this.composer = val)}
               />
               />
             </ColField>
             </ColField>
+            <ColField required title="曲目声部">
+              <Field
+                is-link
+                readonly
+                class={styles['clear-px']}
+                placeholder="请选择曲目声部"
+                name="vlewSubjectList"
+                modelValue={this.vlewSubjectList?.value}
+                rules={[{ required: true, message: '请选择曲目声部' }]}
+                // onUpdate:modelValue={val => (this.selectedSubjectList = )}
+                onClick={() => (this.showPicker = true)}
+              ></Field>
+            </ColField>
           </ColFieldGroup>
           </ColFieldGroup>
           <ColFieldGroup class={styles.area}>
           <ColFieldGroup class={styles.area}>
             <ColField
             <ColField
+              border={false}
               required
               required
               title="曲目标签"
               title="曲目标签"
               v-slots={{
               v-slots={{
@@ -108,26 +330,80 @@ export default defineComponent({
                     round
                     round
                     type="primary"
                     type="primary"
                     size="small"
                     size="small"
-                    onClick={() => (this.selectTagVisible = true)}
+                    onClick={() => (this.tagVisibility = true)}
                   >
                   >
                     选择
                     选择
                   </Button>
                   </Button>
                 )
                 )
               }}
               }}
             >
             >
-              {this.tags.length > 0 &&
-                this.tags.map((item: any) => (
-                  <Tag
+              <Field
+                name="tags"
+                modelValue={this.tags.length ? 1 : undefined}
+                rules={[{ required: true, message: '请选择曲目标签' }]}
+                // @ts-ignore
+                vSlots={{
+                  input: () =>
+                    this.tags.length > 0 ? (
+                      this.tags.map((item: any) => (
+                        <Tag type="primary" size="large" class={styles.tags}>
+                          {this.tagsNames[item]}
+                        </Tag>
+                      ))
+                    ) : (
+                      <Empty
+                        style={{ width: '100%' }}
+                        description="请选择曲目标签"
+                        imageSize={0}
+                      />
+                    )
+                }}
+              />
+            </ColField>
+          </ColFieldGroup>
+          {/* <ColFieldGroup class={styles.area}>
+            <ColField
+              required
+              border={false}
+              title="曲目声部"
+              v-slots={{
+                right: () => (
+                  <Button
+                    class={styles.select}
+                    round
                     type="primary"
                     type="primary"
-                    size="large"
-                    closeable
-                    onClose={() => console.log(item)}
+                    size="small"
+                    onClick={() => (this.subJectVisible = true)}
                   >
                   >
-                    {item}
-                  </Tag>
-                ))}
+                    选择
+                  </Button>
+                )
+              }}
+            >
+              <Field
+                name="tags"
+                modelValue={this.selectedSubjectList.length ? 1 : undefined}
+                rules={[{ required: true, message: '请选择曲目声部' }]}
+                // @ts-ignore
+                vSlots={{
+                  input: () =>
+                    this.selectedSubjectList.length > 0 ? (
+                      this.selectedSubjectList.map((item: any) => (
+                        <Tag type="primary" size="large" class={styles.tags}>
+                          {this.subjectListNames[item]}
+                        </Tag>
+                      ))
+                    ) : (
+                      <Empty
+                        style={{ width: '100%' }}
+                        description="请选择曲目声部"
+                        imageSize={0}
+                      />
+                    )
+                }}
+              />
             </ColField>
             </ColField>
-          </ColFieldGroup>
+          </ColFieldGroup> */}
           <ColFieldGroup class={styles.area}>
           <ColFieldGroup class={styles.area}>
             <ColField required title="默认速度">
             <ColField required title="默认速度">
               <Field
               <Field
@@ -140,6 +416,44 @@ export default defineComponent({
                 placeholder="请输入默认速度"
                 placeholder="请输入默认速度"
               />
               />
             </ColField>
             </ColField>
+            <ColField required title="是否评测">
+              <RadioGroup
+                class={styles['radio-group']}
+                modelValue={this.canEvaluate}
+                onUpdate:modelValue={val => (this.canEvaluate = val)}
+              >
+                {Object.keys(teachercanEvaluateType).map((item: string) => {
+                  const isActive = item === String(this.canEvaluate)
+                  const type = isActive ? 'primary' : 'default'
+                  return (
+                    <Radio class={styles.radio} name={item}>
+                      <Tag size="large" plain={isActive} type={type}>
+                        {teachercanEvaluateType[item]}
+                      </Tag>
+                    </Radio>
+                  )
+                })}
+              </RadioGroup>
+            </ColField>
+            <ColField required title="指法展示">
+              <RadioGroup
+                class={styles['radio-group']}
+                modelValue={this.showFingering}
+                onUpdate:modelValue={val => (this.showFingering = val)}
+              >
+                {Object.keys(teachershowFingeringType).map((item: string) => {
+                  const isActive = item === String(this.showFingering)
+                  const type = isActive ? 'primary' : 'default'
+                  return (
+                    <Radio class={styles.radio} name={item}>
+                      <Tag size="large" plain={isActive} type={type}>
+                        {teachershowFingeringType[item]}
+                      </Tag>
+                    </Radio>
+                  )
+                })}
+              </RadioGroup>
+            </ColField>
             <ColField required title="是否收费">
             <ColField required title="是否收费">
               <RadioGroup
               <RadioGroup
                 class={styles['radio-group']}
                 class={styles['radio-group']}
@@ -177,7 +491,13 @@ export default defineComponent({
             <div class={styles.rule}>
             <div class={styles.rule}>
               <p>扣除手续费后该曲目预计收入为:</p>
               <p>扣除手续费后该曲目预计收入为:</p>
               <p>
               <p>
-                单课时:<span>5.8</span>元/人
+                单课时:
+                <span>
+                  {((parseFloat(this.musicPrice || '0') || 0) *
+                    (100 - this.music_sheet_service_fee)) /
+                    100}
+                </span>
+                元/人
               </p>
               </p>
               <p>您的乐谱收入将在学员购买后结算到您的账户中</p>
               <p>您的乐谱收入将在学员购买后结算到您的账户中</p>
             </div>
             </div>
@@ -185,11 +505,67 @@ export default defineComponent({
         </div>
         </div>
         <Sticky offsetBottom={0} position="bottom">
         <Sticky offsetBottom={0} position="bottom">
           <div class={styles['button-area']}>
           <div class={styles['button-area']}>
-            <Button type="primary" block round native-type="submit">
+            <Button
+              type="primary"
+              block
+              round
+              native-type="submit"
+              loading={this.submitLoading}
+            >
               确认
               确认
             </Button>
             </Button>
           </div>
           </div>
         </Sticky>
         </Sticky>
+        <Popup
+          show={this.showPicker}
+          round
+          position="bottom"
+          teleport="body"
+          onUpdate:show={val => (this.showPicker = val)}
+        >
+          <Picker
+            columnsFieldNames={{
+              text: 'value'
+            }}
+            columns={Object.entries(this.subjectListNames).map(
+              ([key, value]) => ({ label: key, value })
+            )}
+            onCancel={() => (this.showPicker = false)}
+            onConfirm={val => {
+              this.selectedSubjectList = val
+              this.vlewSubjectList = val
+              this.showPicker = false
+            }}
+          />
+        </Popup>
+
+        <Popup
+          show={this.subJectVisible}
+          round
+          closeable
+          position="bottom"
+          style={{ height: '60%' }}
+          teleport="body"
+          onUpdate:show={val => (this.subJectVisible = val)}
+        >
+          <SubjectModel
+            subjectList={this.subjectList}
+            choiceSubjectIds={this.choiceSubjectIds}
+            onChoice={this.onChoice}
+            selectType="Radio"
+          />
+        </Popup>
+        <Popup
+          show={this.tagVisibility}
+          round
+          closeable
+          position="bottom"
+          style={{ height: '60%' }}
+          teleport="body"
+          onUpdate:show={val => (this.tagVisibility = val)}
+        >
+          <SelectTag onComfirm={this.onComfirm} onCancel={() => {}} />
+        </Popup>
       </Form>
       </Form>
     )
     )
   }
   }

+ 8 - 2
src/teacher/music/upload/upload.tsx

@@ -49,9 +49,15 @@ export default defineComponent({
         modelValue={this.list}
         modelValue={this.list}
         beforeDelete={this.beforeRead}
         beforeDelete={this.beforeRead}
         onUpdate:modelValue={async val => {
         onUpdate:modelValue={async val => {
-          await this.upload(val[0].file)
+          if (val[0]) {
+            await this.upload(val[0].file)
+            this.formatFile(val[0].file)
+          }
           this.list = val
           this.list = val
-          this.formatFile(val[0].file)
+        }}
+        onDelete={() => {
+          this.list = []
+          this.$emit('update:modelValue', null)
         }}
         }}
       >
       >
         <Button loading={this.uploading}>上传文件</Button>
         <Button loading={this.uploading}>上传文件</Button>