瀏覽代碼

临时添加上传曲谱-需求暂停

lex 10 月之前
父節點
當前提交
bf54de0a23

二進制
src/teacher/music/upload/images/banner-bg.png


二進制
src/teacher/music/upload/images/bell.png


二進制
src/teacher/music/upload/images/btn-bg.png


二進制
src/teacher/music/upload/images/music-icon.png


二進制
src/teacher/music/upload/images/title-bg.png


二進制
src/teacher/music/upload/images/title-bg2.png


二進制
src/teacher/music/upload/images/upload-icon.png


+ 1076 - 0
src/teacher/music/upload/index copy.tsx

@@ -0,0 +1,1076 @@
+import { defineComponent } from 'vue'
+import {
+  Button,
+  Field,
+  Sticky,
+  Form,
+  Tag,
+  Radio,
+  RadioGroup,
+  Popup,
+  Icon,
+  Empty,
+  Picker,
+  Toast,
+  NoticeBar
+} from 'vant'
+import ColFieldGroup from '@/components/col-field-group'
+// import { MusicType } from 'src/teacher/music/list/item.d'
+import SubjectModel from '@/business-components/subject-list'
+import ColField from '@/components/col-field'
+
+import {
+  teachercanEvaluateType,
+  teacherChargeType,
+  teachershowAudiType,
+  teachershowFingeringType,
+  teachershowHasBeatType,
+  teacherNotationType,
+  teacherStyleType,
+  teacherExquisiteType
+} from '@/constant/music'
+import { getXmlInfo, FormatXMLInfo } from '@/helpers/music-xml'
+import Upload from './upload'
+import styles from './index.module.less'
+import SelectTag from '@/views/music/search/select-tag'
+import { browser } from '@/helpers/utils'
+import { postMessage } from '@/helpers/native-message'
+import { teacherState } from '@/teacher/teacher-cert/teacherState'
+import request from '@/helpers/request'
+import requestOrigin from 'umi-request'
+import UploadIcon from './upload.svg'
+import ColUpload from '@/components/col-upload'
+import { verifyNumberIntegerAndFloat } from '@/helpers/toolsValidate'
+import { state } from '@/state'
+import ColHeader from '@/components/col-header'
+
+export type BackgroundMp3 = {
+  url?: string
+  track?: string
+}
+
+// 校验函数返回 true 表示校验通过,false 表示不通过
+export const validator = val => {
+  console.log(val)
+  if (Number(val) <= 0) {
+    return '收费金额必须大于0'
+  } else {
+    return true
+  }
+}
+
+export default defineComponent({
+  name: 'MusicUpload',
+  data() {
+    return {
+      reason: '',
+      audioType: 'MP3',
+      xmlFileUrl: '',
+      xmlFileLoading: false,
+      midiUrl: '',
+      midiLoading: false,
+      mp3Url: '',
+      bgmp3Url: '',
+      mp3Loading: false,
+      bgmp3Loading: false,
+      musicSheetName: '',
+      composer: '',
+      speed: '',
+      hasBeat: 0,
+      titleImg: '',
+      accompanimentType: 'HOMEMODE',
+      chargeType: 0,
+      paymentType: '',
+      showFingering: 1,
+      canEvaluate: 1,
+      notation: 1,
+      musicPrice: '',
+      subJectIndex: 0,
+      selectTagVisible: false,
+      subJectVisible: false,
+      tags: [] as string[],
+      tagsNames: [] as Array<{ [id in string]: string }>,
+      formated: {} as FormatXMLInfo,
+      tagVisibility: false,
+      subjectListres: [] as any[],
+      subjectListNames: {} as any,
+      selectedSubjectList: null as any,
+      vlewSubjectList: null as any,
+      submitLoading: false,
+      showPicker: false,
+      music_sheet_service_fee: 0,
+      music_account_period: 0,
+      exquisiteFlag: 0,
+      backgroundMp3s: [
+        {
+          url: '',
+          track: ''
+        }
+      ] as BackgroundMp3[],
+      checked: false
+    }
+  },
+  watch: {
+    formated() {
+      this.mergeXmlData(this.formated)
+    },
+    chargeType() {
+      if (this.chargeType === 0) {
+        this.musicPrice = ''
+        this.paymentType = ''
+      }
+    }
+  },
+  computed: {
+    choiceSubjectIds() {
+      // 选择的科目编号
+      let ids = teacherState.teacherCert.subjectId
+        ? teacherState.teacherCert.subjectId.split(',')
+        : []
+      ids = ids.map((item: any) => Number(item))
+      return ids
+    },
+    subjectList() {
+      // 学科列表
+      const subjects: any = this.subjectListres || []
+      return subjects
+    },
+    choiceSubject() {
+      // 选择的科目
+      const 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/queryByParamNameList', {
+        params: {
+          paramNames: 'music_sheet_service_fee,music_account_period'
+        }
+      })
+      .then((res: any) => {
+        console.log(res, 'res')
+        const data = res.data || []
+        data.forEach((item: any) => {
+          if (item.paramName === 'music_sheet_service_fee') {
+            this.music_sheet_service_fee = item.paramValue
+          } else if (item.paramName === 'music_account_period') {
+            this.music_account_period = item.paramValue
+          }
+        })
+      })
+
+    // console.log(config, 'config')
+    // 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) {
+    await request.get('/api-teacher/subject/subjectSelect').then(res => {
+      const list: any[] = []
+      for (const item of res.data || []) {
+        const slist: any[] = (item as any).subjects || []
+        list.push(...slist)
+      }
+      this.subjectListres = list
+      this.subjectListNames = this.getSubjectListNames(list)
+    })
+
+    if (this.$route.params.id) {
+      this.setDetail(this.$route.params.id as string)
+    }
+
+    const resVersion = await request.post('/api-teacher/open/appVersion', {
+      data: {
+        platform:
+          state.platformType === 'STUDENT' ? 'ios-student' : 'ios-teacher',
+        version: state.version
+      }
+    })
+    this.checked = resVersion.data.check ? true : false
+    // 审核版本金额默认为0
+    if (this.checked) {
+      this.chargeType = 0
+    }
+    // }
+  },
+  methods: {
+    async setDetail(id: string) {
+      try {
+        const res = await request.get('/api-teacher/music/sheet/detail/' + id)
+        this.chargeType = res.data.paymentType === 'FREE' ? 0 : 2
+        this.paymentType = res.data.paymentType
+        this.showFingering = res.data.showFingering
+        this.canEvaluate = res.data.canEvaluate
+        if (this.chargeType) {
+          this.musicPrice = res.data.musicPrice
+        }
+
+        this.composer = res.data.composer
+        this.musicSheetName = res.data.musicSheetName
+        this.audioType = res.data.audioType
+        this.notation = res.data.notation
+        this.selectedSubjectList = {
+          label: res.data.musicSubject,
+          value: res.data.subjectNames
+        }
+        this.vlewSubjectList = {
+          label: res.data.musicSubject,
+          value: res.data.subjectNames
+        }
+        this.subJectIndex = Object.keys(this.subjectListNames).findIndex(
+          key => key === res.data.musicSubject
+        )
+        const names = res.data.musicTagNames.split(',')
+        this.tags = res.data.musicTag.split(',')
+        this.tags = this.tags.filter((el: any) => {
+          return el != ''
+        })
+
+        for (let i = 0; i < names.length; i++) {
+          this.tagsNames[this.tags[i]] = names[i]
+        }
+        this.exquisiteFlag = res.data.exquisiteFlag
+        this.xmlFileUrl = res.data.xmlFileUrl
+        this.accompanimentType = res.data.accompanimentType
+        this.titleImg = res.data.titleImg
+
+        // this.audioType = res.data.mp3Type
+
+        if (this.audioType === 'MP3') {
+          this.hasBeat =
+            (res.data.audioType === 'MP3' &&
+              res.data.mp3Type === 'MP3_METRONOME') ||
+            res.data.audioType === 'MIDI'
+              ? 1
+              : 0
+          this.mp3Url = res.data.audioFileUrl || res.data.url //res.data.metronomeUrl || res.data.url
+        } else {
+          this.midiUrl = res.data.midiUrl
+        }
+
+        this.backgroundMp3s = (res.data.background || []).map((item, index) => {
+          if (index === 0) {
+            this.bgmp3Url = item.audioFileUrl
+          }
+          return {
+            url: item.audioFileUrl,
+            track: item.track
+          }
+        })
+        this.reason = res.data.reason
+
+        console.log(this.bgmp3Url)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    createSubmitData() {
+      const beatType = this.hasBeat ? 'MP3_METRONOME' : 'MP3'
+      const mp3Type = this.audioType === 'MP3' ? beatType : 'MIDI'
+      return {
+        audioType: this.audioType,
+        sourceType: 'TEACHER',
+        mp3Type,
+        hasBeat: Number(this.hasBeat),
+        accompanimentType: this.accompanimentType,
+        titleImg: this.titleImg,
+        url: this.hasBeat ? '' : this.mp3Url,
+        metronomeUrl: this.hasBeat ? this.mp3Url : '',
+        audioFileUrl: this.mp3Url,
+        showFingering: Number(this.showFingering),
+        musicTag: this.tags.join(','),
+        musicSubject: Number(this.selectedSubjectList?.label) || undefined,
+        musicSheetName: this.musicSheetName,
+        midiUrl: this.midiUrl,
+        notation: Number(this.notation),
+        xmlFileUrl: this.xmlFileUrl,
+        canEvaluate: Number(this.canEvaluate),
+        chargeType: this.chargeType === 0 ? 'FREE' : 'CHARGE',
+        paymentType: this.chargeType === 0 ? 'FREE' : 'CHARGE',
+        exquisiteFlag: this.exquisiteFlag,
+        composer: this.composer,
+        musicPrice: this.chargeType === 0 ? 0 : this.musicPrice, // 当选择免费时,重置金额为0
+        background: this.backgroundMp3s.map(item => ({
+          audioFileUrl: this.bgmp3Url,
+          track: item.track
+          // metronomeUrl: this.hasBeat ? this.bgmp3Url : ''
+        }))
+      }
+    },
+    async submit(vals: any) {
+      console.log(vals)
+      this.submitLoading = true
+      try {
+        if (this.$route.params.id) {
+          await request.post('/api-teacher/music/sheet/update', {
+            data: {
+              ...this.createSubmitData(),
+              id: this.$route.params.id
+            }
+          })
+        } else {
+          await request.post('/api-teacher/music/sheet/create', {
+            data: this.createSubmitData()
+          })
+        }
+      } catch (error) {}
+
+      Toast('上传成功')
+      setTimeout(() => {
+        postMessage({
+          api: 'back'
+        })
+        this.submitLoading = false
+      }, 800)
+      console.log(vals)
+    },
+    onFormatter(val: any) {
+      return verifyNumberIntegerAndFloat(val)
+    },
+    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() {
+      console.log('failed', this.backgroundMp3s)
+    },
+    mergeXmlData(data: FormatXMLInfo) {
+      this.formated = data
+      // this.backgroundMp3s = data.partNames.map((partName: string) => ({
+      //   track: partName
+      // }))
+      if (!this.musicSheetName) {
+        this.musicSheetName = data.title
+      }
+      if (!this.composer) {
+        this.composer = data.composer
+      }
+      // if (!this.speed && data.speed) {
+      //   this.speed = '' + data.speed
+      // }
+    },
+    readerFile(file: File) {
+      const reader = new FileReader()
+      reader.onload = () => {
+        const xml = reader.result as string
+        this.formated = getXmlInfo(xml)
+      }
+      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() {
+      this.xmlFileLoading = true
+      postMessage(
+        { api: 'chooseFile', content: { type: 'xml', bucket: 'cloud-coach' } },
+        evt => {
+          // @ts-ignore
+          this.xmlFileUrl = evt?.fileUrl || this.xmlFileUrl || ''
+          this.xmlFileLoading = false
+          if (this.xmlFileUrl) {
+            requestOrigin(this.xmlFileUrl).then(
+              res => (this.formated = getXmlInfo(res))
+            )
+          }
+        }
+      )
+    },
+    naiveMidFile() {
+      this.midiLoading = true
+      postMessage(
+        { api: 'chooseFile', content: { type: 'midi', bucket: 'cloud-coach' } },
+        evt => {
+          // @ts-ignore
+          this.midiUrl = evt?.fileUrl || this.midiUrl || ''
+          this.midiLoading = false
+          // this.midiUrl = path
+        }
+      )
+    },
+    naiveMp3File() {
+      this.mp3Loading = true
+      postMessage(
+        { api: 'chooseFile', content: { type: 'mp3', bucket: 'cloud-coach' } },
+        evt => {
+          // @ts-ignore
+          this.mp3Url = evt?.fileUrl || this.mp3Url || ''
+          this.mp3Loading = false
+          // this.midiUrl = path
+        }
+      )
+    },
+    naiveBGMp3File() {
+      this.bgmp3Loading = true
+      postMessage(
+        { api: 'chooseFile', content: { type: 'mp3', bucket: 'cloud-coach' } },
+        evt => {
+          this.bgmp3Url
+          // @ts-ignore
+          this.bgmp3Url = evt?.fileUrl || this.bgmp3Url || ''
+          this.bgmp3Loading = false
+          // this.midiUrl = path
+        }
+      )
+    },
+    fileName(name = '') {
+      return name.split('/').pop()
+    },
+    removeBackground(index: number) {
+      this.backgroundMp3s.splice(index, 1)
+    },
+    onDetail(type: string) {
+      let url = `${location.origin}/teacher/#/registerProtocol`
+
+      if (type === 'question') {
+        url = `${location.origin}/teacher/muic-standard/question.html`
+      } else if (type === 'music') {
+        url = `${location.origin}/teacher/muic-standard/index.html`
+      }
+
+      postMessage({
+        api: 'openWebView',
+        content: {
+          url,
+          orientation: 1,
+          isHideTitle: false
+        }
+      })
+    }
+  },
+  render() {
+    console.log(this.formated)
+    const browserInfo = browser()
+    return (
+      <Form class={styles.form} onSubmit={this.submit} onFailed={this.failed}>
+        <ColHeader />
+        {this.reason && (
+          <NoticeBar wrapable scrollable={false} text={this.reason} />
+        )}
+        <div class={styles.container}>
+          <div class={styles.tips}>
+            <div class={styles.tipsTitle}>注意事项:</div>
+            <div class={styles.tipsContent}>
+              1、必须是上传人自己参与制作的作品。
+              <br />
+              2、歌曲及歌曲信息中请勿涉及政治、宗教、广告、涉毒、犯罪、色情、低俗、暴力、血腥、消极等违规内容,违反者直接删除内容。多次违反将封号。
+              <br />
+              3、点击查看
+              <span onClick={() => this.onDetail('protocol')}>
+                《用户注册协议》
+              </span>
+              ,如果您上传了文件,即认为您完全同意并遵守该协议的内容;
+            </div>
+          </div>
+          <ColFieldGroup class={styles.area}>
+            <ColField border={false} required title="MusicXML文件">
+              <Field
+                name="xmlFileUrl"
+                modelValue={this.xmlFileUrl}
+                rules={[{ required: true, message: '请选择MusicXML文件' }]}
+                // @ts-ignore
+                vSlots={{
+                  input: () =>
+                    browserInfo.isApp ? (
+                      <Button
+                        icon={UploadIcon}
+                        class={styles.upbtn}
+                        onClick={this.naiveXMLFile}
+                        loading={this.xmlFileLoading}
+                      >
+                        {this.xmlFileUrl
+                          ? this.fileName(this.xmlFileUrl)
+                          : '上传文件'}
+                      </Button>
+                    ) : (
+                      <>
+                        <Upload
+                          onUpdate:modelValue={val => (this.xmlFileUrl = val)}
+                          accept=".xml"
+                          formatFile={this.readerFile}
+                        />
+                        <div style={{ marginLeft: '8px' }}>
+                          {this.fileName(this.xmlFileUrl)}
+                        </div>
+                      </>
+                    )
+                }}
+              />
+            </ColField>
+          </ColFieldGroup>
+          <div class={styles.tips}>
+            <div class={styles.tipsTitle}>曲谱审核标准:</div>
+            <div class={styles.tipsContent}>
+              1、文件大小不要超过5MB,不符合版面规范的乐谱,审核未通过的不予上架,详情参考
+              <span onClick={() => this.onDetail('music')}>
+                《曲谱排版规范》
+              </span>
+              ; 1、必须是上传人自己参与制作的作品。
+              <br />
+              2、XML与MIDI文件内容必须一致,推荐使用Sibelius打谱软件。导出设置:导出XML-未压缩(*.xml)/导出MIDI:音色-其他回放设备General
+              MIDI、MIDI、MIDI文件类型-类型0、不要勾选“将弱拍小节导出为具有休止符的完整小节”。点击查看
+              <span onClick={() => this.onDetail('question')}>
+                《常见问题》
+              </span>
+            </div>
+          </div>
+          <ColFieldGroup class={styles.area}>
+            <ColField required title="播放类型" border={false}>
+              <RadioGroup
+                class={styles['radio-group']}
+                modelValue={this.audioType}
+                onUpdate:modelValue={val => (this.audioType = val)}
+              >
+                {Object.keys(teachershowAudiType).map((item: string) => {
+                  const isActive = item === this.audioType
+                  const type = isActive ? 'primary' : 'default'
+                  return (
+                    <Radio class={styles.radio} name={item}>
+                      <Tag size="large" plain={isActive} type={type}>
+                        {teachershowAudiType[item]}
+                      </Tag>
+                    </Radio>
+                  )
+                })}
+              </RadioGroup>
+            </ColField>
+            {this.audioType === 'MP3' ? (
+              <>
+                {/* <ColField required title="是否带节拍器" border={false}>
+                  <RadioGroup
+                    class={styles['radio-group']}
+                    modelValue={this.hasBeat}
+                    onUpdate:modelValue={val => (this.hasBeat = val)}
+                  >
+                    {Object.keys(teachershowHasBeatType).map((item: string) => {
+                      const isActive = item === String(this.hasBeat)
+                      const type = isActive ? 'primary' : 'default'
+                      return (
+                        <Radio class={styles.radio} name={item}>
+                          <Tag size="large" plain={isActive} type={type}>
+                            {teachershowHasBeatType[item]}
+                          </Tag>
+                        </Radio>
+                      )
+                    })}
+                  </RadioGroup>
+                </ColField> */}
+                <ColField required title="伴奏类型" border={false}>
+                  <RadioGroup
+                    class={styles['radio-group']}
+                    modelValue={this.accompanimentType}
+                    onUpdate:modelValue={val => (this.accompanimentType = val)}
+                  >
+                    {Object.keys(teacherStyleType).map((item: string) => {
+                      const isActive = item === String(this.accompanimentType)
+                      const type = isActive ? 'primary' : 'default'
+                      return (
+                        <Radio class={styles.radio} name={item}>
+                          <Tag size="large" plain={isActive} type={type}>
+                            {teacherStyleType[item]}
+                          </Tag>
+                        </Radio>
+                      )
+                    })}
+                  </RadioGroup>
+                </ColField>
+                <ColField border={false} title="伴奏文件">
+                  <Field
+                    name="mp3Url"
+                    modelValue={this.mp3Url}
+                    // @ts-ignore
+                    vSlots={{
+                      input: () =>
+                        browserInfo.isApp ? (
+                          <Button
+                            icon={UploadIcon}
+                            class={styles.upbtn}
+                            onClick={this.naiveMp3File}
+                            loading={this.mp3Loading}
+                          >
+                            {this.mp3Url
+                              ? this.fileName(this.mp3Url)
+                              : '上传文件'}
+                          </Button>
+                        ) : (
+                          <>
+                            <Upload
+                              onUpdate:modelValue={val => (this.mp3Url = val)}
+                              accept=".mp3"
+                            />
+                            <div style={{ marginLeft: '8px' }}>
+                              {this.fileName(this.mp3Url)}
+                            </div>
+                          </>
+                        )
+                    }}
+                  />
+                </ColField>
+              </>
+            ) : (
+              <ColField border={false} required title="MIDI文件">
+                <Field
+                  name="midiUrl"
+                  modelValue={this.midiUrl}
+                  rules={[{ required: true, message: '请选择MIDI文件' }]}
+                  // @ts-ignore
+                  vSlots={{
+                    input: () =>
+                      browserInfo.isApp ? (
+                        <Button
+                          icon={UploadIcon}
+                          class={styles.upbtn}
+                          onClick={this.naiveMidFile}
+                          loading={this.midiLoading}
+                        >
+                          {this.midiUrl
+                            ? this.fileName(this.midiUrl)
+                            : '上传文件'}
+                        </Button>
+                      ) : (
+                        <>
+                          <Upload
+                            onUpdate:modelValue={val => (this.midiUrl = val)}
+                            accept=".mid,.midi"
+                          />
+                          <div style={{ marginLeft: '8px' }}>
+                            {this.fileName(this.midiUrl)}
+                          </div>
+                        </>
+                      )
+                  }}
+                />
+              </ColField>
+            )}
+          </ColFieldGroup>
+          <div class={styles.tips}>
+            <div class={styles.tipsContent}>
+              1、推荐上传自制伴奏,伴奏和谱面必须对齐。自制伴奏可以设置更高的收费标准。
+              <br />
+              2、普通伴奏如果涉及到版权纠纷,根据
+              <span onClick={() => this.onDetail('protocol')}>
+                《用户注册协议》
+              </span>
+              平台有权进行下架处理。
+            </div>
+          </div>
+          <ColFieldGroup class={styles.area}>
+            {this.audioType === 'MP3' &&
+              this.backgroundMp3s.map((item, index) => (
+                <ColField
+                  required
+                  border={false}
+                  title={(item.track || '') + '原音文件'}
+                  // @ts-ignore
+                  vSlots={{
+                    right: () =>
+                      this.backgroundMp3s.length > 1 ? (
+                        <Button
+                          onClick={() => this.removeBackground(index)}
+                          style={{ border: 'none' }}
+                          icon="cross"
+                        ></Button>
+                      ) : null
+                  }}
+                >
+                  <Field
+                    name="url"
+                    modelValue={this.bgmp3Url}
+                    // @ts-ignore
+                    vSlots={{
+                      input: () =>
+                        browserInfo.isApp ? (
+                          <Button
+                            icon={UploadIcon}
+                            class={styles.upbtn}
+                            onClick={this.naiveBGMp3File}
+                            loading={this.bgmp3Loading}
+                          >
+                            {this.bgmp3Url
+                              ? this.fileName(this.bgmp3Url)
+                              : '上传文件'}
+                          </Button>
+                        ) : (
+                          <>
+                            <Upload
+                              onUpdate:modelValue={val => (this.bgmp3Url = val)}
+                              accept=".mp3"
+                            />
+                            <div style={{ marginLeft: '8px' }}>
+                              {this.fileName(this.bgmp3Url)}
+                            </div>
+                          </>
+                        )
+                    }}
+                  />
+                </ColField>
+              ))}
+            <ColField required title="曲目名称">
+              <Field
+                clearable
+                name="musicSheetName"
+                modelValue={this.musicSheetName}
+                rules={[{ required: true, message: '请输入曲目名称' }]}
+                class={styles['clear-px']}
+                placeholder="请输入曲目名称"
+                onUpdate:modelValue={val => (this.musicSheetName = val)}
+              />
+            </ColField>
+          </ColFieldGroup>
+          <div class={styles.tips}>
+            <div class={styles.tipsContent}>
+              1、同一首曲目不可重复上传,如有不同版本统一用“()”补充。举例:人生的旋转木马(长笛二重奏版)。
+              <br />
+              2、曲目名后可添加曲目信息备注,包含但不限于曲目类型等。曲目名《xxxx》,举例:人生的旋转木马《哈尔的移动城堡》(长笛二重奏版)
+              <br />
+              3、其他信息不要写在曲目名里,如歌手、上传人员昵称等。
+            </div>
+          </div>
+          <ColFieldGroup class={styles.area}>
+            <ColField border={false} required title="曲谱封面">
+              <ColUpload
+                cropper
+                bucket="cloud-coach"
+                options={{
+                  autoCropWidth: 600,
+                  autoCropHeight: 600
+                }}
+                v-model={this.titleImg}
+                class={styles.imgContainer}
+              />
+            </ColField>
+          </ColFieldGroup>
+          <ColFieldGroup class={styles.area}>
+            <ColField required title="艺术家">
+              <Field
+                clearable
+                class={styles['clear-px']}
+                placeholder="请输入艺术家姓名"
+                name="composer"
+                modelValue={this.composer}
+                rules={[{ required: true, message: '请输入艺术家姓名' }]}
+                onUpdate:modelValue={val => (this.composer = val)}
+              />
+            </ColField>
+            {/* <ColField required title="默认速度">
+              <Field
+                clearable
+                name="playSpeed"
+                modelValue={this.speed}
+                rules={[{ required: true, message: '请输入默认速度' }]}
+                onUpdate:modelValue={val => (this.speed = val)}
+                class={styles['clear-px']}
+                placeholder="请输入默认速度"
+              />
+            </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>
+          <div class={styles.tips}>
+            <div class={styles.tipsContent}>
+              XML文件中,选择的曲目声部需要在总谱的置顶位置。
+            </div>
+          </div>
+          <ColFieldGroup class={styles.area}>
+            <ColField
+              border={false}
+              required
+              title="曲目标签"
+              v-slots={{
+                right: () => (
+                  <Button
+                    class={styles.select}
+                    round
+                    type="primary"
+                    size="small"
+                    onClick={() => (this.tagVisibility = true)}
+                  >
+                    选择
+                  </Button>
+                )
+              }}
+            >
+              <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 title="是否评测" border={false}>
+              <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="指法展示" border={false}>
+              <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> */}
+
+            {/* 判断是否是审核版本 */}
+            {!this.checked && (
+              <ColField required title="是否收费" border={false}>
+                <RadioGroup
+                  class={styles['radio-group']}
+                  modelValue={this.chargeType}
+                  onUpdate:modelValue={val => {
+                    this.chargeType = Number(val)
+                  }}
+                >
+                  {Object.keys(teacherChargeType).map((item: string) => {
+                    const isActive = item === String(this.chargeType)
+                    const type = isActive ? 'primary' : 'default'
+                    return (
+                      <Radio class={styles.radio} name={item}>
+                        <Tag size="large" plain={isActive} type={type}>
+                          {teacherChargeType[item]}
+                        </Tag>
+                      </Radio>
+                    )
+                  })}
+                </RadioGroup>
+              </ColField>
+            )}
+
+            {this.chargeType === 2 && (
+              <ColField required title="收费价格">
+                <Field
+                  clearable
+                  class={styles['clear-px']}
+                  placeholder="请输入收费价格"
+                  formatter={this.onFormatter}
+                  v-slots={{ button: () => '元' }}
+                  modelValue={this.musicPrice}
+                  rules={[
+                    { required: true, validator, message: '请输入收费价格' }
+                  ]}
+                  onUpdate:modelValue={val => (this.musicPrice = val)}
+                />
+              </ColField>
+            )}
+            {/* <ColField required title="支持简谱" border={false}>
+              <RadioGroup
+                class={styles['radio-group']}
+                modelValue={this.notation}
+                onUpdate:modelValue={val => {
+                  this.notation = Number(val)
+                }}
+              >
+                {Object.keys(teacherNotationType).map((item: string) => {
+                  const isActive = item === String(this.notation)
+                  const type = isActive ? 'primary' : 'default'
+                  return (
+                    <Radio class={styles.radio} name={item}>
+                      <Tag size="large" plain={isActive} type={type}>
+                        {teacherNotationType[item]}
+                      </Tag>
+                    </Radio>
+                  )
+                })}
+              </RadioGroup>
+            </ColField> */}
+            <ColField required title="是否精品乐谱" border={false}>
+              <RadioGroup
+                class={styles['radio-group']}
+                modelValue={this.exquisiteFlag}
+                onUpdate:modelValue={val => {
+                  this.exquisiteFlag = Number(val)
+                }}
+              >
+                {Object.keys(teacherExquisiteType).map((item: string) => {
+                  const isActive = item === String(this.exquisiteFlag)
+                  const type = isActive ? 'primary' : 'default'
+                  return (
+                    <Radio class={styles.radio} name={item}>
+                      <Tag size="large" plain={isActive} type={type}>
+                        {teacherExquisiteType[item]}
+                      </Tag>
+                    </Radio>
+                  )
+                })}
+              </RadioGroup>
+            </ColField>
+          </ColFieldGroup>
+          {this.chargeType === 2 && (
+            <div class={styles.rule}>
+              <p>扣除手续费后该曲目预计收入为:</p>
+              <p>
+                每人:
+                <span>
+                  {(
+                    ((parseFloat(this.musicPrice || '0') || 0) *
+                      (100 - this.music_sheet_service_fee)) /
+                    100
+                  ).toFixed(2)}
+                </span>
+                元/人
+              </p>
+
+              <p>
+                您的乐谱收入在学员购买后{this.music_account_period}
+                天结算到您的账户中
+              </p>
+            </div>
+          )}
+        </div>
+        <Sticky offsetBottom={0} position="bottom">
+          <div class={styles['button-area']}>
+            <Button
+              type="primary"
+              block
+              round
+              native-type="submit"
+              loading={this.submitLoading}
+            >
+              确认
+            </Button>
+          </div>
+        </Sticky>
+        <Popup
+          show={this.showPicker}
+          round
+          position="bottom"
+          teleport="body"
+          onUpdate:show={val => (this.showPicker = val)}
+        >
+          <Picker
+            defaultIndex={this.subJectIndex}
+            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
+            onConfirm={this.onComfirm}
+            onCancel={() => {}}
+            rowSingle
+            defaultValue={this.tags.join(',')}
+            needAllButton={false}
+          />
+        </Popup>
+      </Form>
+    )
+  }
+})

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

@@ -0,0 +1,145 @@
+.form {
+  --van-notice-bar-background-color: var(--tag-bg-color);
+  --van-notice-bar-text-color: var(--van-tag-primary-color);
+}
+.container {
+  background-color: var(--col-background-color);
+  min-height: 100vh;
+  margin: 14px 0;
+  .area {
+    padding: 20px 20px 0;
+    margin-bottom: 12px;
+  }
+  .select {
+    font-size: 14px;
+    padding: 6px 12px;
+  }
+}
+
+.clear-px {
+  padding-left: 0;
+  padding-right: 0;
+}
+
+.rule {
+  font-size: 14px;
+  line-height: 27px;
+  color: var(--tips-color);
+  margin: 0 14px;
+  margin-bottom: 20px;
+  > p > span {
+    color: var(--strong--color);
+    font-weight: bold;
+  }
+}
+
+.button-area {
+  padding: 10px 14px;
+  background-color: var(--white);
+  box-shadow: 0 0 10px var(--box-shadow-color);
+}
+
+.radio-group {
+  display: flex;
+  margin-top: 14px;
+  .radio:first-child {
+    :global {
+      .van-radio__label {
+        margin-left: 0;
+      }
+    }
+  }
+}
+
+.radio {
+  :global {
+    .van-radio__icon {
+      display: none;
+    }
+    .van-tag--large {
+      width: 94px;
+      height: 30px;
+      font-size: 16px;
+      text-align: center;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+    .van-tag {
+      box-sizing: border-box;
+    }
+    .van-tag--default {
+      color: var(--van-tag-text-default-color);
+    }
+    .van-tag--primary {
+      background-color: var(--tag-bg-color);
+    }
+  }
+}
+
+.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;
+  }
+}
+
+.upbtn {
+  border: 1px solid #cfcfcf;
+  width: 100%;
+  background: #fbfbfb;
+  color: #666666;
+  height: 77px;
+  border-radius: 5px;
+  border-style: dashed;
+  i {
+    font-size: 24px;
+  }
+}
+
+.tips {
+  font-size: 12px;
+  color: #e0945a;
+  line-height: 18px;
+  padding: 15px 11px;
+  background: #fff3eb;
+  border-radius: 10px;
+  margin: 0 14px 12px;
+
+  .tipsTitle {
+    font-size: 14px;
+    font-weight: 600;
+    color: #e0945a;
+    line-height: 20px;
+    padding-bottom: 6px;
+  }
+
+  span {
+    color: #5aa9e0;
+  }
+}
+
+.imgContainer {
+  width: 150px;
+  height: 150px;
+  border-radius: 10px;
+  overflow: hidden;
+  margin: 0 0 12px;
+  position: relative;
+}

+ 186 - 17
src/teacher/music/upload/index.module.less

@@ -1,15 +1,130 @@
 .form {
   --van-notice-bar-background-color: var(--tag-bg-color);
   --van-notice-bar-text-color: var(--van-tag-primary-color);
+  min-height: 100%;
+  background: url('./images/banner-bg.png') no-repeat center top;
+  background-size: contain;
+  background-color: #BEEBFD;
 }
+
+.area {
+  position: relative;
+  padding: 44px 0 12px;
+  margin: 22px 14px 12px;
+  border-radius: 16px;
+
+  &.topArea {
+    margin-top: 100px;
+  }
+
+  .section-title {
+    position: absolute;
+    top: -10px;
+    left: 50%;
+    margin-left: -72.5px;
+    width: 145px;
+    height: 36px;
+    background: url('./images/title-bg.png') no-repeat center;
+    background-size: contain;
+
+    &.section-title2 {
+      background: url('./images/title-bg2.png') no-repeat center;
+      background-size: contain;
+    }
+  }
+
+  :global {
+    .van-field {
+      padding: 14px 12px;
+    }
+
+    .van-field__label {
+      font-weight: 500;
+      font-size: 16px;
+      color: #000;
+      margin-right: 0;
+      flex: 1 auto;
+    }
+
+    .van-field__value {
+      font-size: 16px;
+    }
+
+  }
+
+  .fieldTypeBottom {
+    flex-direction: column;
+
+    :global {
+      .van-field__value {
+        padding-top: 12px;
+      }
+
+      .van-field__label {
+        width: 100%;
+        margin-right: 0;
+      }
+    }
+
+    .fieldTitle {
+      display: flex;
+      justify-content: space-between;
+
+      i {
+        color: #ee0a24;
+      }
+
+      .titleTip {
+        font-size: 12px;
+        color: #AAAAAA;
+      }
+    }
+  }
+
+  .textareaType {
+    :global {
+      .van-field__value {
+        margin-top: 12px;
+        background: #F6F8F9;
+        border-radius: 10px;
+        padding-left: 10px;
+        padding-right: 10px;
+      }
+
+      .van-field__word-limit {
+        position: absolute;
+        top: -35px;
+        right: 0;
+        font-size: 12px;
+        color: #AAAAAA;
+      }
+    }
+  }
+
+  .inputControl {
+    :global {
+      .van-field__body {
+        justify-content: flex-end;
+      }
+    }
+
+    input {
+      background: #F6F8F9;
+      border-radius: 6px;
+      height: 32px;
+      text-align: center;
+      width: 70px;
+    }
+  }
+}
+
 .container {
   background-color: var(--col-background-color);
   min-height: 100vh;
   margin: 14px 0;
-  .area {
-    padding: 20px 20px 0;
-    margin-bottom: 12px;
-  }
+
+
+
   .select {
     font-size: 14px;
     padding: 6px 12px;
@@ -26,22 +141,50 @@
   line-height: 27px;
   color: var(--tips-color);
   margin: 0 14px;
-  margin-bottom: 20px;
-  > p > span {
+
+  >p>span {
     color: var(--strong--color);
     font-weight: bold;
   }
 }
 
+
+.showField {
+  :global {
+    .van-field__control {
+      display: flex;
+      flex-wrap: wrap;
+    }
+  }
+}
+
 .button-area {
   padding: 10px 14px;
-  background-color: var(--white);
-  box-shadow: 0 0 10px var(--box-shadow-color);
+
+  // background-color: var(--white);
+  // box-shadow: 0 0 10px var(--box-shadow-color);
+  :global {
+    .van-button {
+      // background: url('');
+      background: transparent;
+      padding: 0;
+      border: 0;
+    }
+
+    .van-button__text {
+      height: 100%;
+    }
+  }
+
+  img {
+    width: 100%;
+    max-height: 100%;
+  }
 }
 
 .radio-group {
   display: flex;
-  margin-top: 14px;
+
   .radio:first-child {
     :global {
       .van-radio__label {
@@ -56,6 +199,7 @@
     .van-radio__icon {
       display: none;
     }
+
     .van-tag--large {
       width: 94px;
       height: 30px;
@@ -64,13 +208,17 @@
       display: flex;
       align-items: center;
       justify-content: center;
+      border-radius: 6px;
     }
+
     .van-tag {
       box-sizing: border-box;
     }
+
     .van-tag--default {
       color: var(--van-tag-text-default-color);
     }
+
     .van-tag--primary {
       background-color: var(--tag-bg-color);
     }
@@ -81,18 +229,28 @@
   margin-right: 5px;
   margin-bottom: 5px;
   margin-top: 5px;
+  background: #F2FFFC;
+  border-radius: 4px;
+  height: 28px;
+  font-size: 14px;
+  color: #00B2A7 !important;
+  padding-top: 0;
+  padding-bottom: 0;
 }
 
 .file {
   display: flex;
   width: 100%;
-  > div {
+
+  >div {
     flex: 1;
-    > span {
+
+    >span {
       display: inline-block;
       margin-left: 5px;
     }
   }
+
   .delbtn {
     padding: 0;
     height: auto;
@@ -103,14 +261,24 @@
 .upbtn {
   border: 1px solid #cfcfcf;
   width: 100%;
-  background: #fbfbfb;
   color: #666666;
-  height: 77px;
-  border-radius: 5px;
+  height: 48px;
+  background: #F6F8F9;
+  border-radius: 6px;
   border-style: dashed;
+
   i {
     font-size: 24px;
   }
+
+  :global {
+    .van-button__text {
+      max-width: 90%;
+      text-overflow: ellipsis;
+      overflow: hidden;
+      white-space: nowrap;
+    }
+  }
 }
 
 .tips {
@@ -136,10 +304,11 @@
 }
 
 .imgContainer {
-  width: 150px;
-  height: 150px;
+  width: 101px !important;
+  height: 101px !important;
   border-radius: 10px;
   overflow: hidden;
   margin: 0 0 12px;
+  margin-top: 0 !important;
   position: relative;
-}
+}

文件差異過大導致無法顯示
+ 550 - 647
src/teacher/music/upload/index.tsx


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

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

+ 108 - 0
src/teacher/music/upload/instrumentModal/index.tsx

@@ -0,0 +1,108 @@
+import { Ref, defineComponent, ref, watch } from 'vue'
+import { Tag, CheckboxGroup, Checkbox, RadioGroup, Radio, Button } from 'vant'
+import styles from './index.module.less'
+import classNames from 'classnames'
+
+export default defineComponent({
+  name: 'SelectTagChild',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    child: {
+      type: Array,
+      default: () => []
+    },
+    choiceSubjectIds: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['close', 'confirm'],
+  setup(props, { attrs, emit, expose }) {
+    const subjects: Ref<Array<any | number>> = ref([...props.choiceSubjectIds])
+    const list = ref([...props.child])
+    const onSelect = val => {
+      setTimeout(() => {
+        val.forEach(i => {
+          if (!subjects.value.includes(i)) {
+            subjects.value.push(i)
+          }
+        })
+      }, 100)
+    }
+
+    watch(
+      () => props.show,
+      () => {
+        if (!props.show) {
+          resetTags()
+        } else {
+          subjects.value = [...props.choiceSubjectIds]
+          list.value = [...props.child]
+        }
+      }
+    )
+
+    const resetTags = () => {
+      console.log(props.choiceSubjectIds)
+      subjects.value = [...props.choiceSubjectIds]
+      emit('close')
+    }
+
+    expose({
+      resetTags
+    })
+    return () => {
+      return (
+        <div class={styles.select}>
+          <h4 class={styles.title}>全部声部</h4>
+          <div class={styles.content}>
+            <CheckboxGroup
+              class={classNames(styles.childContent, styles['radio-group'])}
+              modelValue={subjects.value}
+              onUpdate:modelValue={val => {
+                onSelect!(val)
+              }}
+            >
+              {list.value.map((item: any) => (
+                <Checkbox
+                  key={item.id}
+                  name={item.id}
+                  class={styles.radio}
+                  onClick={() => {
+                    onSelect!([item.id])
+                  }}
+                >
+                  <Tag
+                    class={classNames(styles.item, 'van-ellipsis')}
+                    plain={!subjects.value.includes(item.id)}
+                    type="primary"
+                    round
+                    size="large"
+                  >
+                    {item.name}
+                  </Tag>
+                </Checkbox>
+              ))}
+            </CheckboxGroup>
+          </div>
+          <footer class="van-safe-area-bottom van-hairline--top">
+            <Button class={styles.btn} round onClick={resetTags}>
+              重置
+            </Button>
+            <Button
+              class={styles.btn}
+              type="primary"
+              round
+              onClick={() => emit('confirm', subjects.value)}
+            >
+              确认
+            </Button>
+          </footer>
+        </div>
+      )
+    }
+  }
+})

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

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

+ 103 - 0
src/teacher/music/upload/subjectModal/index.tsx

@@ -0,0 +1,103 @@
+import { Ref, defineComponent, ref, watch } from 'vue'
+import { Tag, CheckboxGroup, Checkbox, RadioGroup, Radio, Button } from 'vant'
+import styles from './index.module.less'
+import classNames from 'classnames'
+
+export default defineComponent({
+  name: 'SelectTagChild',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    child: {
+      type: Array,
+      default: () => []
+    },
+    choiceSubjectIds: {
+      type: Array,
+      default: () => []
+    }
+  },
+  emits: ['close', 'confirm'],
+  setup(props, { attrs, emit }) {
+    const subjects: Ref<Array<any | number>> = ref([...props.choiceSubjectIds])
+
+    const onSelect = val => {
+      setTimeout(() => {
+        val.forEach(i => {
+          if (!subjects.value.includes(i)) {
+            subjects.value.push(i)
+          }
+        })
+      }, 100)
+    }
+
+    watch(
+      () => props.show,
+      () => {
+        if (!props.show) {
+          resetTags()
+        } else {
+          subjects.value = [...props.choiceSubjectIds]
+        }
+      }
+    )
+
+    const resetTags = () => {
+      console.log(props.choiceSubjectIds)
+      subjects.value = [...props.choiceSubjectIds]
+      emit('close')
+    }
+    return () => {
+      return (
+        <div class={styles.select}>
+          <h4 class={styles.title}>全部声部</h4>
+          <div class={styles.content}>
+            <CheckboxGroup
+              class={classNames(styles.childContent, styles['radio-group'])}
+              modelValue={subjects.value}
+              onUpdate:modelValue={val => {
+                onSelect!(val)
+              }}
+            >
+              {props.child.map((item: any) => (
+                <Checkbox
+                  key={item.id}
+                  name={item.id}
+                  class={styles.radio}
+                  onClick={() => {
+                    onSelect!([item.id])
+                  }}
+                >
+                  <Tag
+                    class={classNames(styles.item, 'van-ellipsis')}
+                    plain={!subjects.value.includes(item.id)}
+                    type="primary"
+                    round
+                    size="large"
+                  >
+                    {item.name}
+                  </Tag>
+                </Checkbox>
+              ))}
+            </CheckboxGroup>
+          </div>
+          <footer class="van-safe-area-bottom van-hairline--top">
+            <Button class={styles.btn} round onClick={resetTags}>
+              重置
+            </Button>
+            <Button
+              class={styles.btn}
+              type="primary"
+              round
+              onClick={() => emit('confirm', subjects.value)}
+            >
+              确认
+            </Button>
+          </footer>
+        </div>
+      )
+    }
+  }
+})

+ 1 - 1
vite.config.ts

@@ -12,7 +12,7 @@ function resolve(dir: string) {
 // https://vitejs.dev/config/
 // https://github.com/vitejs/vite/issues/1930 .env
 // const proxyUrl = 'https://online.colexiu.com/'
-const proxyUrl = 'https://test.colexiu.com/'
+const proxyUrl = 'https://dev.colexiu.com/'
 // const proxyUrl = 'http://192.168.3.14:8000/'
 export default defineConfig({
   base: './',

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