index.tsx 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002
  1. import { defineComponent } from 'vue'
  2. import {
  3. Button,
  4. Field,
  5. Sticky,
  6. Form,
  7. Tag,
  8. Radio,
  9. RadioGroup,
  10. Popup,
  11. Icon,
  12. Empty,
  13. Picker,
  14. Toast,
  15. NoticeBar
  16. } from 'vant'
  17. import ColFieldGroup from '@/components/col-field-group'
  18. // import { MusicType } from 'src/teacher/music/list/item.d'
  19. import SubjectModel from '@/business-components/subject-list'
  20. import ColField from '@/components/col-field'
  21. import {
  22. teachercanEvaluateType,
  23. teacherChargeType,
  24. teachershowAudiType,
  25. teachershowFingeringType,
  26. teachershowHasBeatType,
  27. teacherNotationType,
  28. teacherStyleType
  29. } from '@/constant/music'
  30. import { getXmlInfo, FormatXMLInfo } from '@/helpers/music-xml'
  31. import Upload from './upload'
  32. import styles from './index.module.less'
  33. import SelectTag from '@/views/music/search/select-tag'
  34. import { browser } from '@/helpers/utils'
  35. import { postMessage } from '@/helpers/native-message'
  36. import { teacherState } from '@/teacher/teacher-cert/teacherState'
  37. import request from '@/helpers/request'
  38. import requestOrigin from 'umi-request'
  39. import UploadIcon from './upload.svg'
  40. import ColUpload from '@/components/col-upload'
  41. import { verifyNumberIntegerAndFloat } from '@/helpers/toolsValidate'
  42. export type BackgroundMp3 = {
  43. url?: string
  44. track?: string
  45. }
  46. // 校验函数返回 true 表示校验通过,false 表示不通过
  47. export const validator = val => {
  48. console.log(val)
  49. if (Number(val) <= 0) {
  50. return '收费金额必须大于0'
  51. } else {
  52. return true
  53. }
  54. }
  55. export default defineComponent({
  56. name: 'MusicUpload',
  57. data() {
  58. return {
  59. reason: '',
  60. audioType: 'MP3',
  61. xmlFileUrl: '',
  62. xmlFileLoading: false,
  63. midiUrl: '',
  64. midiLoading: false,
  65. mp3Url: '',
  66. bgmp3Url: '',
  67. mp3Loading: false,
  68. bgmp3Loading: false,
  69. musicSheetName: '',
  70. composer: '',
  71. speed: '',
  72. hasBeat: 0,
  73. titleImg: '',
  74. accompanimentType: 'HOMEMODE',
  75. chargeType: 0,
  76. showFingering: 1,
  77. canEvaluate: 1,
  78. notation: 0,
  79. musicPrice: '',
  80. subJectIndex: 0,
  81. selectTagVisible: false,
  82. subJectVisible: false,
  83. tags: [] as string[],
  84. tagsNames: [] as Array<{ [id in string]: string }>,
  85. formated: {} as FormatXMLInfo,
  86. tagVisibility: false,
  87. subjectListres: [] as any[],
  88. subjectListNames: {} as any,
  89. selectedSubjectList: null as any,
  90. vlewSubjectList: null as any,
  91. submitLoading: false,
  92. showPicker: false,
  93. music_sheet_service_fee: 0,
  94. backgroundMp3s: [
  95. {
  96. url: '',
  97. track: ''
  98. }
  99. ] as BackgroundMp3[]
  100. }
  101. },
  102. watch: {
  103. formated() {
  104. this.mergeXmlData(this.formated)
  105. },
  106. chargeType() {
  107. if (this.chargeType === 0) {
  108. this.musicPrice = ''
  109. }
  110. }
  111. },
  112. computed: {
  113. choiceSubjectIds() {
  114. // 选择的科目编号
  115. let ids = teacherState.teacherCert.subjectId
  116. ? teacherState.teacherCert.subjectId.split(',')
  117. : []
  118. ids = ids.map((item: any) => Number(item))
  119. return ids
  120. },
  121. subjectList() {
  122. // 学科列表
  123. const subjects: any = this.subjectListres || []
  124. return subjects
  125. },
  126. choiceSubject() {
  127. // 选择的科目
  128. const tempArr: any[] = []
  129. this.subjectList.forEach((parent: any) => {
  130. parent.subjects &&
  131. parent.subjects.forEach((sub: any) => {
  132. if (this.choiceSubjectIds.includes(sub.id)) {
  133. tempArr.push(sub as never)
  134. }
  135. })
  136. })
  137. return tempArr
  138. }
  139. },
  140. async mounted() {
  141. request
  142. .get('/api-teacher/sysConfig/queryByParamName', {
  143. params: {
  144. paramName: 'music_sheet_service_fee'
  145. }
  146. })
  147. .then(res => (this.music_sheet_service_fee = res.data.paramValue))
  148. // if (teacherState.subjectList.length <= 0) {
  149. await request.get('/api-teacher/subject/subjectSelect').then(res => {
  150. const list: any[] = []
  151. for (const item of res.data || []) {
  152. const slist: any[] = (item as any).subjects || []
  153. list.push(...slist)
  154. }
  155. this.subjectListres = list
  156. this.subjectListNames = this.getSubjectListNames(list)
  157. })
  158. if (this.$route.params.id) {
  159. this.setDetail(this.$route.params.id as string)
  160. }
  161. // }
  162. },
  163. methods: {
  164. async setDetail(id: string) {
  165. try {
  166. const res = await request.get('/api-teacher/music/sheet/detail/' + id)
  167. this.chargeType = res.data.chargeType === 'FREE' ? 0 : 2
  168. this.showFingering = res.data.showFingering
  169. this.canEvaluate = res.data.canEvaluate
  170. if (this.chargeType) {
  171. this.musicPrice = res.data.musicPrice
  172. }
  173. this.composer = res.data.composer
  174. this.musicSheetName = res.data.musicSheetName
  175. this.audioType = res.data.audioType
  176. this.notation = res.data.notation
  177. this.selectedSubjectList = {
  178. label: res.data.musicSubject,
  179. value: res.data.subjectNames
  180. }
  181. this.vlewSubjectList = {
  182. label: res.data.musicSubject,
  183. value: res.data.subjectNames
  184. }
  185. this.subJectIndex = Object.keys(this.subjectListNames).findIndex(
  186. key => key === res.data.musicSubject
  187. )
  188. const names = res.data.musicTagNames.split(',')
  189. this.tags = res.data.musicTag.split(',')
  190. this.tags = this.tags.filter((el: any) => {
  191. return el != ''
  192. })
  193. for (let i = 0; i < names.length; i++) {
  194. this.tagsNames[this.tags[i]] = names[i]
  195. }
  196. this.xmlFileUrl = res.data.xmlFileUrl
  197. this.accompanimentType = res.data.accompanimentType
  198. this.titleImg = res.data.titleImg
  199. // this.audioType = res.data.mp3Type
  200. if (this.audioType === 'MP3') {
  201. this.hasBeat =
  202. (res.data.audioType === 'MP3' &&
  203. res.data.mp3Type === 'MP3_METRONOME') ||
  204. res.data.audioType === 'MIDI'
  205. ? 1
  206. : 0
  207. this.mp3Url = res.data.audioFileUrl || res.data.url //res.data.metronomeUrl || res.data.url
  208. } else {
  209. this.midiUrl = res.data.midiUrl
  210. }
  211. this.backgroundMp3s = (res.data.background || []).map((item, index) => {
  212. if (index === 0) {
  213. this.bgmp3Url = item.audioFileUrl
  214. }
  215. return {
  216. url: item.audioFileUrl,
  217. track: item.track
  218. }
  219. })
  220. this.reason = res.data.reason
  221. console.log(this.bgmp3Url)
  222. } catch (error) {
  223. console.log(error)
  224. }
  225. },
  226. createSubmitData() {
  227. const beatType = this.hasBeat ? 'MP3_METRONOME' : 'MP3'
  228. const mp3Type = this.audioType === 'MP3' ? beatType : 'MIDI'
  229. return {
  230. audioType: this.audioType,
  231. sourceType: 'TEACHER',
  232. mp3Type,
  233. hasBeat: Number(this.hasBeat),
  234. accompanimentType: this.accompanimentType,
  235. titleImg: this.titleImg,
  236. url: this.hasBeat ? '' : this.mp3Url,
  237. metronomeUrl: this.hasBeat ? this.mp3Url : '',
  238. audioFileUrl: this.mp3Url,
  239. showFingering: Number(this.showFingering),
  240. musicTag: this.tags.join(','),
  241. musicSubject: Number(this.selectedSubjectList?.label) || undefined,
  242. musicSheetName: this.musicSheetName,
  243. midiUrl: this.midiUrl,
  244. notation: Number(this.notation),
  245. xmlFileUrl: this.xmlFileUrl,
  246. canEvaluate: Number(this.canEvaluate),
  247. chargeType: this.chargeType === 0 ? 'FREE' : 'CHARGE',
  248. composer: this.composer,
  249. musicPrice: this.chargeType === 0 ? 0 : this.musicPrice, // 当选择免费时,重置金额为0
  250. background: this.backgroundMp3s.map(item => ({
  251. audioFileUrl: this.bgmp3Url,
  252. track: item.track
  253. // metronomeUrl: this.hasBeat ? this.bgmp3Url : ''
  254. }))
  255. }
  256. },
  257. async submit(vals: any) {
  258. console.log(vals)
  259. this.submitLoading = true
  260. try {
  261. if (this.$route.params.id) {
  262. await request.post('/api-teacher/music/sheet/update', {
  263. data: {
  264. ...this.createSubmitData(),
  265. id: this.$route.params.id
  266. }
  267. })
  268. } else {
  269. await request.post('/api-teacher/music/sheet/create', {
  270. data: this.createSubmitData()
  271. })
  272. }
  273. } catch (error) {}
  274. this.submitLoading = false
  275. Toast('上传成功')
  276. setTimeout(() => {
  277. postMessage({
  278. api: 'back'
  279. })
  280. }, 800)
  281. console.log(vals)
  282. },
  283. onFormatter(val: any) {
  284. return verifyNumberIntegerAndFloat(val)
  285. },
  286. getSubjectListNames(list) {
  287. const data = {}
  288. for (const item of list) {
  289. data[item.id] = item.name
  290. if (item.subjects) {
  291. for (const sub of item.subjects) {
  292. data[sub.id] = sub.name
  293. }
  294. }
  295. }
  296. return data
  297. },
  298. failed() {
  299. console.log('failed', this.backgroundMp3s)
  300. },
  301. mergeXmlData(data: FormatXMLInfo) {
  302. this.formated = data
  303. // this.backgroundMp3s = data.partNames.map((partName: string) => ({
  304. // track: partName
  305. // }))
  306. if (!this.musicSheetName) {
  307. this.musicSheetName = data.title
  308. }
  309. if (!this.composer) {
  310. this.composer = data.composer
  311. }
  312. // if (!this.speed && data.speed) {
  313. // this.speed = '' + data.speed
  314. // }
  315. },
  316. readerFile(file: File) {
  317. const reader = new FileReader()
  318. reader.onload = () => {
  319. const xml = reader.result as string
  320. this.formated = getXmlInfo(xml)
  321. }
  322. reader.readAsText(file)
  323. },
  324. onChoice(val: any) {
  325. this.subJectVisible = false
  326. this.selectedSubjectList = [val]
  327. },
  328. onComfirm(tags: any, names: any) {
  329. this.tagsNames = names
  330. this.tagVisibility = false
  331. const data = Object.values(tags).flat().filter(Boolean) as string[]
  332. console.log(data)
  333. this.tags = data
  334. },
  335. naiveXMLFile() {
  336. this.xmlFileLoading = true
  337. postMessage(
  338. { api: 'chooseFile', content: { type: 'xml', bucket: 'cloud-coach' } },
  339. evt => {
  340. // @ts-ignore
  341. this.xmlFileUrl = evt?.fileUrl || this.xmlFileUrl || ''
  342. this.xmlFileLoading = false
  343. if (this.xmlFileUrl) {
  344. requestOrigin(this.xmlFileUrl).then(
  345. res => (this.formated = getXmlInfo(res))
  346. )
  347. }
  348. }
  349. )
  350. },
  351. naiveMidFile() {
  352. this.midiLoading = true
  353. postMessage(
  354. { api: 'chooseFile', content: { type: 'midi', bucket: 'cloud-coach' } },
  355. evt => {
  356. // @ts-ignore
  357. this.midiUrl = evt?.fileUrl || this.midiUrl || ''
  358. this.midiLoading = false
  359. // this.midiUrl = path
  360. }
  361. )
  362. },
  363. naiveMp3File() {
  364. this.mp3Loading = true
  365. postMessage(
  366. { api: 'chooseFile', content: { type: 'mp3', bucket: 'cloud-coach' } },
  367. evt => {
  368. // @ts-ignore
  369. this.mp3Url = evt?.fileUrl || this.mp3Url || ''
  370. this.mp3Loading = false
  371. // this.midiUrl = path
  372. }
  373. )
  374. },
  375. naiveBGMp3File() {
  376. this.bgmp3Loading = true
  377. postMessage(
  378. { api: 'chooseFile', content: { type: 'mp3', bucket: 'cloud-coach' } },
  379. evt => {
  380. this.bgmp3Url
  381. // @ts-ignore
  382. this.bgmp3Url = evt?.fileUrl || this.bgmp3Url || ''
  383. this.bgmp3Loading = false
  384. // this.midiUrl = path
  385. }
  386. )
  387. },
  388. fileName(name = '') {
  389. return name.split('/').pop()
  390. },
  391. removeBackground(index: number) {
  392. this.backgroundMp3s.splice(index, 1)
  393. },
  394. onDetail(type: string) {
  395. let url = `${location.origin}/teacher/#/registerProtocol`
  396. if (type === 'question') {
  397. url = `${location.origin}/teacher/muic-standard/question.html`
  398. } else if (type === 'music') {
  399. url = `${location.origin}/teacher/muic-standard/index.html`
  400. }
  401. postMessage({
  402. api: 'openWebView',
  403. content: {
  404. url,
  405. orientation: 1,
  406. isHideTitle: false
  407. }
  408. })
  409. }
  410. },
  411. render() {
  412. console.log(this.formated)
  413. const browserInfo = browser()
  414. return (
  415. <Form class={styles.form} onSubmit={this.submit} onFailed={this.failed}>
  416. {this.reason && (
  417. <NoticeBar wrapable scrollable={false} text={this.reason} />
  418. )}
  419. <div class={styles.container}>
  420. <div class={styles.tips}>
  421. <div class={styles.tipsTitle}>注意事项:</div>
  422. <div class={styles.tipsContent}>
  423. 1、必须是上传人自己参与制作的作品。
  424. <br />
  425. 2、歌曲及歌曲信息中请勿涉及政治、宗教、广告、涉毒、犯罪、色情、低俗、暴力、血腥、消极等违规内容,违反者直接删除内容。多次违反将封号。
  426. <br />
  427. 3、点击查看{' '}
  428. <span onClick={() => this.onDetail('protocol')}>
  429. 《用户注册协议》
  430. </span>
  431. ,如果您上传了文件,即认为您完全同意并遵守该协议的内容;
  432. </div>
  433. </div>
  434. <ColFieldGroup class={styles.area}>
  435. <ColField border={false} required title="MusicXML文件">
  436. <Field
  437. name="xmlFileUrl"
  438. modelValue={this.xmlFileUrl}
  439. rules={[{ required: true, message: '请选择MusicXML文件' }]}
  440. // @ts-ignore
  441. vSlots={{
  442. input: () =>
  443. browserInfo.isApp ? (
  444. <Button
  445. icon={UploadIcon}
  446. class={styles.upbtn}
  447. onClick={this.naiveXMLFile}
  448. loading={this.xmlFileLoading}
  449. >
  450. {this.xmlFileUrl
  451. ? this.fileName(this.xmlFileUrl)
  452. : '上传文件'}
  453. </Button>
  454. ) : (
  455. <>
  456. <Upload
  457. onUpdate:modelValue={val => (this.xmlFileUrl = val)}
  458. accept=".xml"
  459. formatFile={this.readerFile}
  460. />
  461. <div style={{ marginLeft: '8px' }}>
  462. {this.fileName(this.xmlFileUrl)}
  463. </div>
  464. </>
  465. )
  466. }}
  467. />
  468. </ColField>
  469. </ColFieldGroup>
  470. <div class={styles.tips}>
  471. <div class={styles.tipsTitle}>曲谱审核标准:</div>
  472. <div class={styles.tipsContent}>
  473. 1、文件大小不要超过5MB,不符合版面规范的乐谱,审核未通过的不予上架,详情参考
  474. <span onClick={() => this.onDetail('music')}>
  475. 《曲谱排版规范》
  476. </span>
  477. ; 1、必须是上传人自己参与制作的作品。
  478. <br />
  479. 2、XML与MIDI文件内容必须一致,推荐使用Sibelius打谱软件。导出设置:导出XML-未压缩(*.xml)/导出MIDI:音色-其他回放设备General
  480. MIDI、MIDI、MIDI文件类型-类型0、不要勾选“将弱拍小节导出为具有休止符的完整小节”。点击查看
  481. <span onClick={() => this.onDetail('question')}>
  482. 《常见问题》
  483. </span>
  484. </div>
  485. </div>
  486. <ColFieldGroup class={styles.area}>
  487. <ColField required title="播放类型" border={false}>
  488. <RadioGroup
  489. class={styles['radio-group']}
  490. modelValue={this.audioType}
  491. onUpdate:modelValue={val => (this.audioType = val)}
  492. >
  493. {Object.keys(teachershowAudiType).map((item: string) => {
  494. const isActive = item === this.audioType
  495. const type = isActive ? 'primary' : 'default'
  496. return (
  497. <Radio class={styles.radio} name={item}>
  498. <Tag size="large" plain={isActive} type={type}>
  499. {teachershowAudiType[item]}
  500. </Tag>
  501. </Radio>
  502. )
  503. })}
  504. </RadioGroup>
  505. </ColField>
  506. {this.audioType === 'MP3' ? (
  507. <>
  508. <ColField required title="是否带节拍器" border={false}>
  509. <RadioGroup
  510. class={styles['radio-group']}
  511. modelValue={this.hasBeat}
  512. onUpdate:modelValue={val => (this.hasBeat = val)}
  513. >
  514. {Object.keys(teachershowHasBeatType).map((item: string) => {
  515. const isActive = item === String(this.hasBeat)
  516. const type = isActive ? 'primary' : 'default'
  517. return (
  518. <Radio class={styles.radio} name={item}>
  519. <Tag size="large" plain={isActive} type={type}>
  520. {teachershowHasBeatType[item]}
  521. </Tag>
  522. </Radio>
  523. )
  524. })}
  525. </RadioGroup>
  526. </ColField>
  527. <ColField required title="伴奏类型" border={false}>
  528. <RadioGroup
  529. class={styles['radio-group']}
  530. modelValue={this.accompanimentType}
  531. onUpdate:modelValue={val => (this.accompanimentType = val)}
  532. >
  533. {Object.keys(teacherStyleType).map((item: string) => {
  534. const isActive = item === String(this.accompanimentType)
  535. const type = isActive ? 'primary' : 'default'
  536. return (
  537. <Radio class={styles.radio} name={item}>
  538. <Tag size="large" plain={isActive} type={type}>
  539. {teacherStyleType[item]}
  540. </Tag>
  541. </Radio>
  542. )
  543. })}
  544. </RadioGroup>
  545. </ColField>
  546. <ColField border={false} title="伴奏文件">
  547. <Field
  548. name="mp3Url"
  549. modelValue={this.mp3Url}
  550. // @ts-ignore
  551. vSlots={{
  552. input: () =>
  553. browserInfo.isApp ? (
  554. <Button
  555. icon={UploadIcon}
  556. class={styles.upbtn}
  557. onClick={this.naiveMp3File}
  558. loading={this.mp3Loading}
  559. >
  560. {this.mp3Url
  561. ? this.fileName(this.mp3Url)
  562. : '上传文件'}
  563. </Button>
  564. ) : (
  565. <>
  566. <Upload
  567. onUpdate:modelValue={val => (this.mp3Url = val)}
  568. accept=".mp3"
  569. />
  570. <div style={{ marginLeft: '8px' }}>
  571. {this.fileName(this.mp3Url)}
  572. </div>
  573. </>
  574. )
  575. }}
  576. />
  577. </ColField>
  578. </>
  579. ) : (
  580. <ColField border={false} required title="MIDI文件">
  581. <Field
  582. name="midiUrl"
  583. modelValue={this.midiUrl}
  584. rules={[{ required: true, message: '请选择MIDI文件' }]}
  585. // @ts-ignore
  586. vSlots={{
  587. input: () =>
  588. browserInfo.isApp ? (
  589. <Button
  590. icon={UploadIcon}
  591. class={styles.upbtn}
  592. onClick={this.naiveMidFile}
  593. loading={this.midiLoading}
  594. >
  595. {this.midiUrl
  596. ? this.fileName(this.midiUrl)
  597. : '上传文件'}
  598. </Button>
  599. ) : (
  600. <>
  601. <Upload
  602. onUpdate:modelValue={val => (this.midiUrl = val)}
  603. accept=".mid,.midi"
  604. />
  605. <div style={{ marginLeft: '8px' }}>
  606. {this.fileName(this.midiUrl)}
  607. </div>
  608. </>
  609. )
  610. }}
  611. />
  612. </ColField>
  613. )}
  614. </ColFieldGroup>
  615. <div class={styles.tips}>
  616. <div class={styles.tipsContent}>
  617. 1、推荐上传自制伴奏,伴奏和谱面必须对齐。自制伴奏可以设置更高的收费标准。
  618. <br />
  619. 2、普通伴奏如果涉及到版权纠纷,根据
  620. <span onClick={() => this.onDetail('protocol')}>
  621. 《用户注册协议》
  622. </span>
  623. 平台有权进行下架处理。
  624. </div>
  625. </div>
  626. <ColFieldGroup class={styles.area}>
  627. {this.audioType === 'MP3' &&
  628. this.backgroundMp3s.map((item, index) => (
  629. <ColField
  630. required
  631. border={false}
  632. title={(item.track || '') + '原音文件'}
  633. // @ts-ignore
  634. vSlots={{
  635. right: () =>
  636. this.backgroundMp3s.length > 1 ? (
  637. <Button
  638. onClick={() => this.removeBackground(index)}
  639. style={{ border: 'none' }}
  640. icon="cross"
  641. ></Button>
  642. ) : null
  643. }}
  644. >
  645. <Field
  646. name="url"
  647. modelValue={this.bgmp3Url}
  648. // @ts-ignore
  649. vSlots={{
  650. input: () =>
  651. browserInfo.isApp ? (
  652. <Button
  653. icon={UploadIcon}
  654. class={styles.upbtn}
  655. onClick={this.naiveBGMp3File}
  656. loading={this.bgmp3Loading}
  657. >
  658. {this.bgmp3Url
  659. ? this.fileName(this.bgmp3Url)
  660. : '上传文件'}
  661. </Button>
  662. ) : (
  663. <>
  664. <Upload
  665. onUpdate:modelValue={val => (this.bgmp3Url = val)}
  666. accept=".mp3"
  667. />
  668. <div style={{ marginLeft: '8px' }}>
  669. {this.fileName(this.bgmp3Url)}
  670. </div>
  671. </>
  672. )
  673. }}
  674. />
  675. </ColField>
  676. ))}
  677. <ColField required title="曲目名称">
  678. <Field
  679. clearable
  680. name="musicSheetName"
  681. modelValue={this.musicSheetName}
  682. rules={[{ required: true, message: '请输入曲目名称' }]}
  683. class={styles['clear-px']}
  684. placeholder="请输入曲目名称"
  685. onUpdate:modelValue={val => (this.musicSheetName = val)}
  686. />
  687. </ColField>
  688. </ColFieldGroup>
  689. <div class={styles.tips}>
  690. <div class={styles.tipsContent}>
  691. 1、同一首曲目不可重复上传,如有不同版本统一用“()”补充。举例:人生的旋转木马(长笛二重奏版)。
  692. <br />
  693. 2、曲目名后可添加曲目信息备注,包含但不限于曲目类型等。曲目名《xxxx》,举例:人生的旋转木马《哈尔的移动城堡》(长笛二重奏版)
  694. <br />
  695. 3、其他信息不要写在曲目名里,如歌手、上传人员昵称等。
  696. </div>
  697. </div>
  698. <ColFieldGroup class={styles.area}>
  699. <ColField border={false} required title="曲谱封面">
  700. <ColUpload
  701. cropper
  702. bucket="cloud-coach"
  703. options={{
  704. autoCropWidth: 600,
  705. autoCropHeight: 600
  706. }}
  707. v-model={this.titleImg}
  708. class={styles.imgContainer}
  709. />
  710. </ColField>
  711. </ColFieldGroup>
  712. <ColFieldGroup class={styles.area}>
  713. <ColField required title="艺术家">
  714. <Field
  715. clearable
  716. class={styles['clear-px']}
  717. placeholder="请输入艺术家姓名"
  718. name="composer"
  719. modelValue={this.composer}
  720. rules={[{ required: true, message: '请输入艺术家姓名' }]}
  721. onUpdate:modelValue={val => (this.composer = val)}
  722. />
  723. </ColField>
  724. {/* <ColField required title="默认速度">
  725. <Field
  726. clearable
  727. name="playSpeed"
  728. modelValue={this.speed}
  729. rules={[{ required: true, message: '请输入默认速度' }]}
  730. onUpdate:modelValue={val => (this.speed = val)}
  731. class={styles['clear-px']}
  732. placeholder="请输入默认速度"
  733. />
  734. </ColField> */}
  735. <ColField required title="曲目声部">
  736. <Field
  737. is-link
  738. readonly
  739. class={styles['clear-px']}
  740. placeholder="请选择曲目声部"
  741. name="vlewSubjectList"
  742. modelValue={this.vlewSubjectList?.value}
  743. rules={[{ required: true, message: '请选择曲目声部' }]}
  744. // onUpdate:modelValue={val => (this.selectedSubjectList = )}
  745. onClick={() => (this.showPicker = true)}
  746. ></Field>
  747. </ColField>
  748. </ColFieldGroup>
  749. <div class={styles.tips}>
  750. <div class={styles.tipsContent}>
  751. XML文件中,选择的曲目声部需要在总谱的置顶位置。
  752. </div>
  753. </div>
  754. <ColFieldGroup class={styles.area}>
  755. <ColField
  756. border={false}
  757. required
  758. title="曲目标签"
  759. v-slots={{
  760. right: () => (
  761. <Button
  762. class={styles.select}
  763. round
  764. type="primary"
  765. size="small"
  766. onClick={() => (this.tagVisibility = true)}
  767. >
  768. 选择
  769. </Button>
  770. )
  771. }}
  772. >
  773. <Field
  774. name="tags"
  775. modelValue={this.tags.length ? 1 : undefined}
  776. rules={[{ required: true, message: '请选择曲目标签' }]}
  777. // @ts-ignore
  778. vSlots={{
  779. input: () =>
  780. this.tags.length > 0 ? (
  781. this.tags.map((item: any) => (
  782. <Tag type="primary" size="large" class={styles.tags}>
  783. {this.tagsNames[item]}
  784. </Tag>
  785. ))
  786. ) : (
  787. <Empty
  788. style={{ width: '100%' }}
  789. description="请选择曲目标签"
  790. imageSize={0}
  791. />
  792. )
  793. }}
  794. />
  795. </ColField>
  796. </ColFieldGroup>
  797. <ColFieldGroup class={styles.area}>
  798. <ColField required title="是否评测" border={false}>
  799. <RadioGroup
  800. class={styles['radio-group']}
  801. modelValue={this.canEvaluate}
  802. onUpdate:modelValue={val => (this.canEvaluate = val)}
  803. >
  804. {Object.keys(teachercanEvaluateType).map((item: string) => {
  805. const isActive = item === String(this.canEvaluate)
  806. const type = isActive ? 'primary' : 'default'
  807. return (
  808. <Radio class={styles.radio} name={item}>
  809. <Tag size="large" plain={isActive} type={type}>
  810. {teachercanEvaluateType[item]}
  811. </Tag>
  812. </Radio>
  813. )
  814. })}
  815. </RadioGroup>
  816. </ColField>
  817. <ColField required title="指法展示" border={false}>
  818. <RadioGroup
  819. class={styles['radio-group']}
  820. modelValue={this.showFingering}
  821. onUpdate:modelValue={val => (this.showFingering = val)}
  822. >
  823. {Object.keys(teachershowFingeringType).map((item: string) => {
  824. const isActive = item === String(this.showFingering)
  825. const type = isActive ? 'primary' : 'default'
  826. return (
  827. <Radio class={styles.radio} name={item}>
  828. <Tag size="large" plain={isActive} type={type}>
  829. {teachershowFingeringType[item]}
  830. </Tag>
  831. </Radio>
  832. )
  833. })}
  834. </RadioGroup>
  835. </ColField>
  836. <ColField required title="是否收费" border={false}>
  837. <RadioGroup
  838. class={styles['radio-group']}
  839. modelValue={this.chargeType}
  840. onUpdate:modelValue={val => {
  841. this.chargeType = Number(val)
  842. }}
  843. >
  844. {Object.keys(teacherChargeType).map((item: string) => {
  845. const isActive = item === String(this.chargeType)
  846. const type = isActive ? 'primary' : 'default'
  847. return (
  848. <Radio class={styles.radio} name={item}>
  849. <Tag size="large" plain={isActive} type={type}>
  850. {teacherChargeType[item]}
  851. </Tag>
  852. </Radio>
  853. )
  854. })}
  855. </RadioGroup>
  856. </ColField>
  857. <ColField required title="支持简谱" border={false}>
  858. <RadioGroup
  859. class={styles['radio-group']}
  860. modelValue={this.notation}
  861. onUpdate:modelValue={val => {
  862. this.notation = Number(val)
  863. }}
  864. >
  865. {Object.keys(teacherNotationType).map((item: string) => {
  866. const isActive = item === String(this.notation)
  867. const type = isActive ? 'primary' : 'default'
  868. return (
  869. <Radio class={styles.radio} name={item}>
  870. <Tag size="large" plain={isActive} type={type}>
  871. {teacherNotationType[item]}
  872. </Tag>
  873. </Radio>
  874. )
  875. })}
  876. </RadioGroup>
  877. </ColField>
  878. {this.chargeType === 2 && (
  879. <ColField required title="收费价格">
  880. <Field
  881. clearable
  882. class={styles['clear-px']}
  883. placeholder="请输入收费价格"
  884. formatter={this.onFormatter}
  885. v-slots={{ button: () => '元' }}
  886. modelValue={this.musicPrice}
  887. rules={[
  888. { required: true, validator, message: '请输入收费价格' }
  889. ]}
  890. onUpdate:modelValue={val => (this.musicPrice = val)}
  891. />
  892. </ColField>
  893. )}
  894. </ColFieldGroup>
  895. {this.chargeType === 2 && (
  896. <div class={styles.rule}>
  897. <p>扣除手续费后该曲目预计收入为:</p>
  898. <p>
  899. 每人:
  900. <span>
  901. {(
  902. ((parseFloat(this.musicPrice || '0') || 0) *
  903. (100 - this.music_sheet_service_fee)) /
  904. 100
  905. ).toFixed(2)}
  906. </span>
  907. 元/人
  908. </p>
  909. <p>您的乐谱收入将在学员购买后结算到您的账户中</p>
  910. </div>
  911. )}
  912. </div>
  913. <Sticky offsetBottom={0} position="bottom">
  914. <div class={styles['button-area']}>
  915. <Button
  916. type="primary"
  917. block
  918. round
  919. native-type="submit"
  920. loading={this.submitLoading}
  921. >
  922. 确认
  923. </Button>
  924. </div>
  925. </Sticky>
  926. <Popup
  927. show={this.showPicker}
  928. round
  929. position="bottom"
  930. teleport="body"
  931. onUpdate:show={val => (this.showPicker = val)}
  932. >
  933. <Picker
  934. defaultIndex={this.subJectIndex}
  935. columnsFieldNames={{
  936. text: 'value'
  937. }}
  938. columns={Object.entries(this.subjectListNames).map(
  939. ([key, value]) => ({ label: key, value })
  940. )}
  941. onCancel={() => (this.showPicker = false)}
  942. onConfirm={val => {
  943. this.selectedSubjectList = val
  944. this.vlewSubjectList = val
  945. this.showPicker = false
  946. }}
  947. />
  948. </Popup>
  949. <Popup
  950. show={this.subJectVisible}
  951. round
  952. closeable
  953. position="bottom"
  954. style={{ height: '60%' }}
  955. teleport="body"
  956. onUpdate:show={val => (this.subJectVisible = val)}
  957. >
  958. <SubjectModel
  959. subjectList={this.subjectList}
  960. choiceSubjectIds={this.choiceSubjectIds}
  961. onChoice={this.onChoice}
  962. selectType="Radio"
  963. />
  964. </Popup>
  965. <Popup
  966. show={this.tagVisibility}
  967. round
  968. closeable
  969. position="bottom"
  970. style={{ height: '60%' }}
  971. teleport="body"
  972. onUpdate:show={val => (this.tagVisibility = val)}
  973. >
  974. <SelectTag
  975. onConfirm={this.onComfirm}
  976. onCancel={() => {}}
  977. rowSingle
  978. defaultValue={this.tags.join(',')}
  979. needAllButton={false}
  980. />
  981. </Popup>
  982. </Form>
  983. )
  984. }
  985. })