index.tsx 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134
  1. import { defineComponent } from 'vue'
  2. import {
  3. Button,
  4. Field,
  5. Form,
  6. Tag,
  7. Radio,
  8. RadioGroup,
  9. Popup,
  10. Empty,
  11. Toast,
  12. CellGroup
  13. } from 'vant'
  14. import { state as appState } from '@/state'
  15. import { teachershowAudiType, teacherPaymentType } from '@/constant/music'
  16. import { getXmlInfo, FormatXMLInfo } from '@/helpers/music-xml'
  17. import Upload from './upload'
  18. import styles from './index.module.less'
  19. import { browser } from '@/helpers/utils'
  20. import { postMessage } from '@/helpers/native-message'
  21. import request from '@/helpers/request'
  22. import requestOrigin from 'umi-request'
  23. import UploadIcon from './images/music-icon.png'
  24. import btnBg from './images/btn-bg.png'
  25. import iconBrid from './images/icon-brid.png'
  26. import iconTitleUpload from './images/icon-title-upload.png'
  27. import iconTitleUpdate from './images/icon-title-update.png'
  28. import ColUpload from '@/components/col-upload'
  29. import {
  30. verifiyNumberInteger,
  31. verifyNumberIntegerAndFloat
  32. } from '@/helpers/toolsValidate'
  33. import ColHeader from '@/components/col-header'
  34. import ColSticky from '@/components/col-sticky'
  35. import MessageTip from './message-tip'
  36. import SelectTag from './select-tag'
  37. import ListenAudio from './listen-audio'
  38. export type BackgroundMp3 = {
  39. url?: string
  40. id?: string
  41. trackName?: string
  42. track?: string
  43. loading?: boolean
  44. }
  45. // 校验函数返回 true 表示校验通过,false 表示不通过
  46. export const validator = val => {
  47. if (Number(val) <= 0) {
  48. return '收费金额必须大于0'
  49. } else {
  50. return true
  51. }
  52. }
  53. export default defineComponent({
  54. name: 'MusicUpload',
  55. data() {
  56. return {
  57. reason: '',
  58. playMode: 'MP3',
  59. xmlFileUrl: '',
  60. xmlFileLoading: false,
  61. midiFileUrl: '',
  62. midiLoading: false,
  63. mp3Url: '',
  64. bgmp3Url: '',
  65. mp3Loading: false,
  66. name: '',
  67. composer: '',
  68. remark: '',
  69. // repeatedBeats: 0,
  70. playSpeed: '' as any,
  71. // hasBeat: 0,
  72. musicCover: '',
  73. paymentType: 'FREE',
  74. // showFingering: 1,
  75. // canEvaluate: 1,
  76. // notation: 1,
  77. musicPrice: '',
  78. // subJectIndex: 0,
  79. // subjectIds: [] as any, // 可用声部
  80. musicalInstrumentIdList: [] as any, // 可用乐器
  81. selectTagVisible: false,
  82. subJectVisible: false,
  83. instrumentVisible: false,
  84. tags: [] as any[],
  85. tagsNames: [] as Array<{ [id in string]: string }>,
  86. formated: {} as FormatXMLInfo,
  87. tagVisibility: false,
  88. listenAudioShow: false,
  89. // subjectListres: [] as any[],
  90. // subjectListNames: {} as any,
  91. // selectedSubjectList: null as any,
  92. // vlewSubjectList: null as any,
  93. submitLoading: false,
  94. // showPicker: false,
  95. music_sheet_service_fee: 0,
  96. music_account_period: 0,
  97. // exquisiteFlag: 0,
  98. backgroundMp3s: [] as BackgroundMp3[],
  99. // checked: false,
  100. auditStatus: '' as any,
  101. messageTipStatus: false,
  102. messageTipTitle: '上传须知',
  103. messageTipType: 'upload' as 'upload' | 'error' | 'origin',
  104. cbsInstrumentList: [] as any,
  105. tagList: [] as any,
  106. musicSheetAuthRecordId: null as any,
  107. fileInfo: {
  108. url: '',
  109. name: '' as any
  110. }
  111. }
  112. },
  113. watch: {
  114. formated() {
  115. this.mergeXmlData(this.formated)
  116. }
  117. },
  118. computed: {
  119. auditDisabled(): boolean {
  120. return this.auditStatus === 'DOING'
  121. }
  122. },
  123. async mounted() {
  124. const isCatchTip = localStorage.getItem('isCatchTip')
  125. if (!isCatchTip) {
  126. this.messageTipStatus = true
  127. }
  128. // 获取基础数据
  129. request
  130. .get('/api-teacher/sysConfig/queryByParamNameList', {
  131. params: {
  132. paramNames: 'music_sheet_service_fee,music_account_period'
  133. }
  134. })
  135. .then((res: any) => {
  136. const data = res.data || []
  137. data.forEach((item: any) => {
  138. if (item.paramName === 'music_sheet_service_fee') {
  139. this.music_sheet_service_fee = item.paramValue
  140. } else if (item.paramName === 'music_account_period') {
  141. this.music_account_period = item.paramValue
  142. }
  143. })
  144. })
  145. const prefix =
  146. appState.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
  147. request(prefix + '/MusicTag/tree').then((res: any) => {
  148. this.tagList = res.data || []
  149. })
  150. if (this.$route.params.id) {
  151. this.setDetail(this.$route.params.id as string)
  152. }
  153. request
  154. .post('/api-teacher/musicalInstrument/list')
  155. .then((response: any) => {
  156. const data = response.data || []
  157. data.forEach((item: any) => {
  158. this.cbsInstrumentList.push({
  159. id: item.id,
  160. name: item.name,
  161. code: item.code,
  162. loading: false
  163. })
  164. })
  165. })
  166. },
  167. methods: {
  168. async setDetail(id: string) {
  169. try {
  170. const { data } = await request.get(
  171. `/api-teacher/music/sheet/detail/${id}`
  172. )
  173. this.musicSheetAuthRecordId = data.musicSheetAuthRecordId // 审核编号
  174. this.auditStatus = data.auditStatus
  175. console.log(this.musicSheetAuthRecordId)
  176. this.playMode = data.audioType || 'MP3'
  177. this.xmlFileUrl = data.xmlFileUrl
  178. this.name = data.musicSheetName
  179. this.composer = data.composer
  180. this.playSpeed = data.playSpeed
  181. // this.tags = data.musicTag?.split(',')
  182. const names = data.musicTagNames.split(',')
  183. this.tags = data.musicTag.split(',')
  184. this.tags = this.tags
  185. .filter((el: any) => {
  186. return el != ''
  187. })
  188. .map(e => Number(e))
  189. for (let i = 0; i < names.length; i++) {
  190. this.tagsNames[this.tags[i]] = names[i]
  191. }
  192. this.musicCover = data.titleImg
  193. this.midiFileUrl = data.midiUrl
  194. this.mp3Url = data.metronomeUrl
  195. this.remark = data.remark
  196. this.paymentType = data.paymentType
  197. this.musicPrice = data.musicPrice || 0
  198. // this.extConfigJson = data.extConfigJson
  199. this.backgroundMp3s = data.background.map((item: any) => {
  200. return {
  201. url: item.audioFileUrl,
  202. trackName: item.musicalInstrumentName,
  203. id: item.musicalInstrumentId,
  204. track: item.track,
  205. loading: false
  206. }
  207. })
  208. } catch (error) {
  209. console.log(error)
  210. }
  211. },
  212. onComfirm(tags: any) {
  213. const names: any = []
  214. this.tagList.forEach(tag => {
  215. if (tags.includes(tag.id)) {
  216. names[tag.id] = tag.name
  217. }
  218. })
  219. this.tagsNames = names
  220. this.tagVisibility = false
  221. const data = Object.values(tags).flat().filter(Boolean) as string[]
  222. console.log(data)
  223. this.tags = data
  224. },
  225. readerFile(file: File) {
  226. const reader = new FileReader()
  227. reader.onload = () => {
  228. const xml = reader.result as string
  229. const formated = getXmlInfo(xml)
  230. let resultIndexStatus = false
  231. const partNames = formated.partNames || []
  232. const tempMp3s: BackgroundMp3[] = []
  233. for (const i of partNames) {
  234. let index = -1
  235. this.cbsInstrumentList.forEach((item: any, j: number) => {
  236. const code = item.code ? item.code.split(',') : ''
  237. for (const p of code) {
  238. if (i.indexOf(p) > -1) {
  239. index = j
  240. }
  241. }
  242. })
  243. if (index === -1) {
  244. resultIndexStatus = true
  245. break
  246. }
  247. const currentItem = this.cbsInstrumentList[index]
  248. if (currentItem) {
  249. tempMp3s.push({
  250. url: '',
  251. id: currentItem.id,
  252. trackName: currentItem.name,
  253. track: i,
  254. loading: currentItem.loading
  255. })
  256. }
  257. }
  258. if (partNames.length <= 0 || resultIndexStatus) {
  259. this.messageTipStatus = true
  260. this.messageTipTitle = '解析失败'
  261. this.messageTipType = 'error'
  262. this.xmlFileUrl = ''
  263. return
  264. }
  265. this.formated = formated
  266. this.backgroundMp3s = tempMp3s
  267. }
  268. reader.readAsText(file)
  269. },
  270. mergeXmlData(data: FormatXMLInfo) {
  271. this.formated = data
  272. // this.backgroundMp3s = data.partNames.map((partName: string) => ({
  273. // track: partName
  274. // }))
  275. if (!this.name) {
  276. this.name = data.title
  277. }
  278. if (!this.composer) {
  279. this.composer = data.composer
  280. }
  281. if (!this.playSpeed && data.speed) {
  282. this.playSpeed = '' + data.speed
  283. } else {
  284. this.playSpeed = '100'
  285. }
  286. },
  287. naiveXMLFile() {
  288. this.xmlFileLoading = true
  289. postMessage(
  290. { api: 'chooseFile', content: { type: 'xml', bucket: 'cloud-coach' } },
  291. evt => {
  292. // @ts-ignore
  293. this.xmlFileUrl = evt?.fileUrl || this.xmlFileUrl || ''
  294. this.xmlFileLoading = false
  295. if (this.xmlFileUrl) {
  296. requestOrigin(this.xmlFileUrl).then(res => {
  297. // this.formated = getXmlInfo(res)
  298. const formated = getXmlInfo(res)
  299. let resultIndexStatus = false
  300. const partNames = formated.partNames || []
  301. const tempMp3s: BackgroundMp3[] = []
  302. for (const i of partNames) {
  303. let index = -1
  304. this.cbsInstrumentList.forEach((item: any, j: number) => {
  305. const code = item.code ? item.code.split(',') : ''
  306. for (const p of code) {
  307. if (i.indexOf(p) > -1) {
  308. index = j
  309. }
  310. }
  311. })
  312. if (index === -1) {
  313. resultIndexStatus = true
  314. break
  315. }
  316. const currentItem = this.cbsInstrumentList[index]
  317. if (currentItem) {
  318. console.log(currentItem, 'currentItem')
  319. tempMp3s.push({
  320. url: '',
  321. id: currentItem.id,
  322. trackName: currentItem.name,
  323. track: i,
  324. loading: currentItem.loading
  325. })
  326. }
  327. }
  328. if (partNames.length <= 0 || resultIndexStatus) {
  329. this.messageTipStatus = true
  330. this.messageTipTitle = '解析失败'
  331. this.messageTipType = 'error'
  332. this.xmlFileUrl = ''
  333. return
  334. }
  335. this.formated = formated
  336. this.backgroundMp3s = tempMp3s
  337. })
  338. }
  339. }
  340. )
  341. },
  342. naiveMidFile() {
  343. this.midiLoading = true
  344. postMessage(
  345. { api: 'chooseFile', content: { type: 'midi', bucket: 'cloud-coach' } },
  346. evt => {
  347. // @ts-ignore
  348. this.midiFileUrl = evt?.fileUrl || this.midiFileUrl || ''
  349. this.midiLoading = false
  350. // this.midiFileUrl = path
  351. }
  352. )
  353. },
  354. naiveMp3File() {
  355. this.mp3Loading = true
  356. postMessage(
  357. { api: 'chooseFile', content: { type: 'mp3', bucket: 'cloud-coach' } },
  358. evt => {
  359. // @ts-ignore
  360. this.mp3Url = evt?.fileUrl || this.mp3Url || ''
  361. this.mp3Loading = false
  362. // this.midiFileUrl = path
  363. }
  364. )
  365. },
  366. naiveBGMp3File(index) {
  367. this.backgroundMp3s[index].loading = true
  368. postMessage(
  369. { api: 'chooseFile', content: { type: 'mp3', bucket: 'cloud-coach' } },
  370. evt => {
  371. // @ts-ignore
  372. const url = evt?.fileUrl || this.backgroundMp3s[index].url || ''
  373. this.backgroundMp3s[index].url = url
  374. this.backgroundMp3s[index].loading = false
  375. }
  376. )
  377. },
  378. onFormatter(val: any) {
  379. return verifyNumberIntegerAndFloat(val)
  380. },
  381. onFormatter2(val: any) {
  382. const num = verifiyNumberInteger(val)
  383. // if (num && Number(num) > 270) {
  384. // return '270'
  385. // }
  386. // if (num && Number(num) < 45) {
  387. // return '45'
  388. // }
  389. return num
  390. },
  391. fileName(name = '') {
  392. return name?.split('/').pop()
  393. },
  394. createSubmitData() {
  395. return {
  396. musicSheetJson: {
  397. playMode: this.playMode, // 播放模式
  398. xmlFileUrl: this.xmlFileUrl, // XML
  399. name: this.name, // 曲目名称
  400. composer: this.composer, // 音乐人
  401. playSpeed: this.playSpeed, // 曲目速度
  402. remark: this.remark,
  403. musicTagIds: this.tags?.join(','),
  404. musicCover: this.musicCover, // 曲目封面
  405. multiTracksSelection: this.backgroundMp3s
  406. .map(item => item.track)
  407. ?.join(','), // 声轨名
  408. midiFileUrl: this.midiFileUrl, // MID文件
  409. musicSheetAccompanimentList: [
  410. {
  411. audioFileUrl: this.mp3Url,
  412. sortNumber: 1,
  413. audioPlayType: 'PLAY'
  414. }
  415. ], // 伴奏
  416. musicPrice: this.musicPrice,
  417. paymentType: this.paymentType,
  418. // audioType: 'HOMEMODE', // HOMEMODE 默认自制
  419. musicSheetSoundList: this.backgroundMp3s.map(item => ({
  420. musicalInstrumentId: item.id,
  421. musicalInstrumentName: item.trackName,
  422. track: item.track,
  423. audioFileUrl: item.url,
  424. audioPlayType: 'PLAY' // SING
  425. })), // 原音
  426. musicalInstrumentIds: this.backgroundMp3s
  427. .map(item => item.id)
  428. ?.join(','), // 乐器编号
  429. extConfigJson: '{"repeatedBeats":0,"gradualTimes":{},"isEvxml":0}'
  430. }
  431. }
  432. },
  433. async onSubmit(vals: any) {
  434. console.log(vals, this.createSubmitData())
  435. this.submitLoading = true
  436. try {
  437. if (this.$route.params.id) {
  438. await request.post('/api-teacher/musicSheetAuthRecord/update', {
  439. data: {
  440. ...this.createSubmitData(),
  441. id: this.musicSheetAuthRecordId
  442. }
  443. })
  444. } else {
  445. await request.post('/api-teacher/musicSheetAuthRecord/save', {
  446. data: this.createSubmitData()
  447. })
  448. }
  449. Toast('上传成功')
  450. setTimeout(() => {
  451. postMessage({
  452. api: 'back'
  453. })
  454. }, 800)
  455. } catch (error) {
  456. //
  457. } finally {
  458. setTimeout(() => {
  459. this.submitLoading = false
  460. }, 800)
  461. }
  462. console.log(vals)
  463. },
  464. onFailed(e: any) {
  465. console.log('failed', e)
  466. }
  467. },
  468. render() {
  469. return (
  470. <Form
  471. class={styles.form}
  472. onSubmit={this.onSubmit}
  473. onFailed={this.onFailed}
  474. disabled={this.auditDisabled}
  475. >
  476. <ColHeader
  477. title=" "
  478. hideHeader={false}
  479. background="transparent"
  480. border={false}
  481. />
  482. <div class={styles.bridSection}>
  483. <img src={iconBrid} class={styles.iconBrid} />
  484. </div>
  485. <CellGroup class={[styles.area, styles.topArea]} border={false}>
  486. <div
  487. class={styles.uploadMessage}
  488. onClick={() => {
  489. this.messageTipStatus = true
  490. this.messageTipTitle = '上传须知'
  491. this.messageTipType = 'upload'
  492. }}
  493. >
  494. 上传须知
  495. </div>
  496. <img
  497. class={styles.titleImg}
  498. src={this.$route.params.id ? iconTitleUpdate : iconTitleUpload}
  499. />
  500. <div class={styles['section-title']}></div>
  501. <Field required label="播放模式" center inputAlign="right">
  502. {{
  503. input: () => (
  504. <RadioGroup
  505. class={styles['radio-group']}
  506. modelValue={this.playMode}
  507. onUpdate:modelValue={val => (this.playMode = val)}
  508. disabled={this.auditDisabled}
  509. >
  510. {Object.keys(teachershowAudiType).map((item: string) => {
  511. const isActive = item === this.playMode
  512. const type = isActive ? 'primary' : 'default'
  513. return (
  514. <Radio class={styles.radio} name={item}>
  515. <Tag size="large" plain={isActive} type={type}>
  516. {teachershowAudiType[item]}
  517. </Tag>
  518. </Radio>
  519. )
  520. })}
  521. </RadioGroup>
  522. )
  523. }}
  524. </Field>
  525. {this.playMode === 'MP3' && (
  526. <Field
  527. name="mp3Url"
  528. class={styles.fieldTypeBottom}
  529. modelValue={this.mp3Url}
  530. rules={[{ required: true, message: '请选择MP3文件' }]}
  531. >
  532. {{
  533. label: () => (
  534. <div class={styles.fieldTitle}>
  535. <span>
  536. <i>*</i>上传伴奏
  537. </span>
  538. <span class={styles.titleTip}>仅支持MP3格式文件</span>
  539. </div>
  540. ),
  541. input: () =>
  542. browser().isApp ? (
  543. <div class={styles.btnSection}>
  544. <Button
  545. icon={UploadIcon}
  546. class={styles.upbtn}
  547. loading={this.mp3Loading}
  548. disabled={this.auditDisabled}
  549. onClick={() => {
  550. if (this.mp3Url) {
  551. this.listenAudioShow = true
  552. this.fileInfo = {
  553. url: this.mp3Url,
  554. name: this.fileName(this.mp3Url)
  555. }
  556. return
  557. }
  558. this.naiveMp3File()
  559. }}
  560. >
  561. {this.mp3Url
  562. ? <span style='text-decoration-line: underline;color: #14BC9C;'>{this.fileName(this.mp3Url)}</span>
  563. : '上传伴奏文件'}
  564. </Button>
  565. {this.mp3Url && !this.auditDisabled && (
  566. <i
  567. class={styles.iconDelete}
  568. onClick={() => {
  569. this.mp3Url = ''
  570. }}
  571. ></i>
  572. )}
  573. </div>
  574. ) : (
  575. <>
  576. <Upload
  577. onUpdate:modelValue={val => {
  578. this.mp3Url = val
  579. }}
  580. accept=".mp3"
  581. disabled={this.auditDisabled}
  582. />
  583. <div
  584. style={{ marginLeft: '8px' }}
  585. onClick={() => {
  586. this.listenAudioShow = true
  587. this.fileInfo = {
  588. url: this.mp3Url,
  589. name: this.fileName(this.mp3Url)
  590. }
  591. }}
  592. >
  593. {this.fileName(this.mp3Url)}
  594. </div>
  595. </>
  596. )
  597. }}
  598. </Field>
  599. )}
  600. {this.playMode === 'MIDI' && (
  601. <Field
  602. name="midiFileUrl"
  603. class={styles.fieldTypeBottom}
  604. modelValue={this.midiFileUrl}
  605. rules={[{ required: true, message: '请选择MIDI文件' }]}
  606. >
  607. {{
  608. label: () => (
  609. <div class={styles.fieldTitle}>
  610. <span>
  611. <i>*</i>上传MIDI
  612. </span>
  613. <span class={styles.titleTip}>仅支持MIDI格式文件</span>
  614. </div>
  615. ),
  616. input: () =>
  617. browser().isApp ? (
  618. <div class={styles.btnSection}>
  619. <Button
  620. icon={UploadIcon}
  621. class={styles.upbtn}
  622. loading={this.mp3Loading}
  623. disabled={this.auditDisabled}
  624. onClick={() => {
  625. if (this.midiFileUrl) return
  626. this.naiveMidFile()
  627. }}
  628. >
  629. {this.midiFileUrl
  630. ? this.fileName(this.midiFileUrl)
  631. : '上传MIDI文件'}
  632. </Button>
  633. {this.midiFileUrl && !this.auditDisabled && (
  634. <i
  635. class={styles.iconDelete}
  636. onClick={() => {
  637. this.midiFileUrl = ''
  638. }}
  639. ></i>
  640. )}
  641. </div>
  642. ) : (
  643. <>
  644. <Upload
  645. onUpdate:modelValue={val => (this.midiFileUrl = val)}
  646. accept=".mid"
  647. formatFile={this.readerFile}
  648. disabled={this.auditDisabled}
  649. />
  650. <div style={{ marginLeft: '8px' }}>
  651. {this.fileName(this.midiFileUrl)}
  652. </div>
  653. </>
  654. )
  655. }}
  656. </Field>
  657. )}
  658. <Field
  659. name="xmlFileUrl"
  660. class={styles.fieldTypeBottom}
  661. modelValue={this.xmlFileUrl}
  662. rules={[{ required: true, message: '请选择XML文件' }]}
  663. >
  664. {{
  665. label: () => (
  666. <div class={styles.fieldTitle}>
  667. <span>
  668. <i>*</i>上传XML
  669. </span>
  670. <span class={styles.titleTip}>仅支持XML格式文件</span>
  671. </div>
  672. ),
  673. input: () =>
  674. browser().isApp ? (
  675. <div class={styles.btnSection}>
  676. <Button
  677. icon={UploadIcon}
  678. class={styles.upbtn}
  679. loading={this.xmlFileLoading}
  680. disabled={this.auditDisabled}
  681. onClick={() => {
  682. if (this.xmlFileUrl) return
  683. this.naiveXMLFile()
  684. }}
  685. >
  686. {this.xmlFileUrl
  687. ? this.fileName(this.xmlFileUrl)
  688. : '上传XML文件'}
  689. </Button>
  690. {this.xmlFileUrl && !this.auditDisabled && (
  691. <i
  692. class={styles.iconDelete}
  693. onClick={() => {
  694. this.xmlFileUrl = ''
  695. this.backgroundMp3s = []
  696. }}
  697. ></i>
  698. )}
  699. </div>
  700. ) : (
  701. <>
  702. <Upload
  703. onUpdate:modelValue={val => (this.xmlFileUrl = val)}
  704. accept=".xml,."
  705. formatFile={this.readerFile}
  706. disabled={this.auditDisabled}
  707. />
  708. <div style={{ marginLeft: '8px' }}>
  709. {this.fileName(this.xmlFileUrl)}
  710. </div>
  711. </>
  712. )
  713. }}
  714. </Field>
  715. {this.backgroundMp3s.length >= 1 && this.playMode === 'MP3' && (
  716. <>
  717. <Field
  718. class={[styles.fieldTypeBottom, styles.hideValue]}
  719. modelValue={this.xmlFileUrl}
  720. border={false}
  721. >
  722. {{
  723. label: () => (
  724. <div class={styles.fieldTitle}>
  725. <span class={styles.titleName}>
  726. <i>*</i>上传原音
  727. <i
  728. class={styles.iconQuestion}
  729. onClick={() => {
  730. this.messageTipStatus = true
  731. this.messageTipTitle = '原音文件'
  732. this.messageTipType = 'origin'
  733. }}
  734. ></i>
  735. </span>
  736. <span class={styles.titleTip}>仅支持MP3格式文件</span>
  737. </div>
  738. )
  739. }}
  740. </Field>
  741. {this.backgroundMp3s.map((mp3, index) => (
  742. <Field
  743. name="url"
  744. class={[styles.fieldTypeBottom, styles.musicTrack]}
  745. modelValue={mp3.url}
  746. rules={[{ required: true, message: '请选择原音文件文件' }]}
  747. >
  748. {{
  749. label: () => (
  750. <div class={styles.fieldTitle}>
  751. <span>所属轨道:{mp3.trackName}</span>
  752. </div>
  753. ),
  754. input: () =>
  755. browser().isApp ? (
  756. <div class={styles.btnSection}>
  757. <Button
  758. icon={UploadIcon}
  759. class={styles.upbtn}
  760. loading={mp3.loading}
  761. disabled={this.auditDisabled}
  762. onClick={() => {
  763. if (mp3.url) return
  764. this.naiveBGMp3File(index)
  765. }}
  766. >
  767. {mp3.url ? this.fileName(mp3.url) : '上传原声文件'}
  768. </Button>
  769. {mp3.url && !this.auditDisabled && (
  770. <i
  771. class={styles.iconDelete}
  772. onClick={() => {
  773. mp3.url = ''
  774. }}
  775. ></i>
  776. )}
  777. </div>
  778. ) : (
  779. <>
  780. <Upload
  781. onUpdate:modelValue={val => (mp3.url = val)}
  782. accept=".mp3"
  783. disabled={this.auditDisabled}
  784. />
  785. <div style={{ marginLeft: '8px' }}>
  786. {this.fileName(mp3.url)}
  787. </div>
  788. </>
  789. )
  790. }}
  791. </Field>
  792. ))}
  793. </>
  794. )}
  795. </CellGroup>
  796. <CellGroup class={[styles.area]} border={false}>
  797. <div
  798. class={[styles['section-title'], styles['section-title2']]}
  799. ></div>
  800. <Field
  801. label="曲目名称"
  802. required
  803. name="name"
  804. modelValue={this.name}
  805. rules={[{ required: true, message: '请输入曲目名称' }]}
  806. errorMessageAlign="right"
  807. placeholder="请输入曲目名称"
  808. inputAlign="right"
  809. autocomplete="off"
  810. maxlength={20}
  811. onUpdate:modelValue={val => (this.name = val)}
  812. />
  813. <Field
  814. label="音乐人"
  815. required
  816. name="composer"
  817. modelValue={this.composer}
  818. rules={[{ required: true, message: '请输入音乐人' }]}
  819. errorMessageAlign="right"
  820. placeholder="请输入音乐人"
  821. inputAlign="right"
  822. autocomplete="off"
  823. maxlength={14}
  824. onUpdate:modelValue={val => (this.composer = val)}
  825. />
  826. <Field
  827. label="曲目描述"
  828. clearable
  829. name="remark"
  830. modelValue={this.remark}
  831. onUpdate:modelValue={val => (this.remark = val)}
  832. class={[styles.fieldTypeBottom, styles.textareaType]}
  833. placeholder="请输入曲目描述"
  834. type="textarea"
  835. maxlength={200}
  836. showWordLimit
  837. />
  838. <Field
  839. label="曲目封面"
  840. required
  841. class={[styles.fieldTypeBottom]}
  842. rules={[{ required: true, message: '请上传曲目封面' }]}
  843. >
  844. {{
  845. input: () => (
  846. <ColUpload
  847. cropper
  848. bucket="cloud-coach"
  849. tips="上传封面"
  850. disabled={this.auditDisabled}
  851. options={{
  852. autoCropWidth: 600,
  853. autoCropHeight: 600
  854. }}
  855. v-model={this.musicCover}
  856. class={styles.imgContainer}
  857. />
  858. )
  859. }}
  860. </Field>
  861. <Field
  862. required
  863. label="曲目速度"
  864. name="playSpeed"
  865. inputAlign="right"
  866. rules={[{ required: true, message: '请输入曲目速度' }]}
  867. errorMessageAlign="right"
  868. v-model={this.playSpeed}
  869. placeholder="请输入曲目速度,范围45-270"
  870. // class={styles.inputControl}
  871. formatter={this.onFormatter2}
  872. onBlur={() => {
  873. if (this.playSpeed && Number(this.playSpeed) > 270) {
  874. this.playSpeed = '270'
  875. }
  876. if (this.playSpeed && Number(this.playSpeed) < 45) {
  877. this.playSpeed = '45'
  878. }
  879. }}
  880. autocomplete="off"
  881. ></Field>
  882. <Field
  883. label="曲目标签"
  884. placeholder="请选择曲目标签"
  885. inputAlign="right"
  886. isLink
  887. required
  888. readonly
  889. border={this.tags.length > 0 ? false : true}
  890. name="tags"
  891. modelValue={this.tags?.join(',')}
  892. class={this.tags.length > 0 ? styles.tagMore : ''}
  893. rules={[{ required: true, message: '请选择曲目标签' }]}
  894. errorMessageAlign="right"
  895. disabled={this.auditDisabled}
  896. onClick={() => {
  897. if (this.auditDisabled) return
  898. this.tagVisibility = true
  899. }}
  900. ></Field>
  901. {this.tags.length > 0 && (
  902. <Field class={styles.showField}>
  903. {{
  904. input: () =>
  905. this.tags.length > 0 ? (
  906. this.tags.map((item: any, index: number) => (
  907. <Tag
  908. type="primary"
  909. size="large"
  910. class={styles.tags}
  911. closeable={!this.auditDisabled}
  912. onClose={() => {
  913. this.tags.splice(index, 1)
  914. }}
  915. >
  916. {this.tagsNames[item]}
  917. </Tag>
  918. ))
  919. ) : (
  920. <Empty
  921. style={{ width: '100%' }}
  922. description="请选择曲目标签"
  923. imageSize={0}
  924. />
  925. )
  926. }}
  927. </Field>
  928. )}
  929. <Field required label="是否收费" center inputAlign="right">
  930. {{
  931. input: () => (
  932. <RadioGroup
  933. class={styles['radio-group']}
  934. modelValue={this.paymentType}
  935. onUpdate:modelValue={val => {
  936. this.paymentType = val
  937. }}
  938. disabled={this.auditDisabled}
  939. >
  940. {Object.keys(teacherPaymentType).map((item: string) => {
  941. const isActive = item === String(this.paymentType)
  942. const type = isActive ? 'primary' : 'default'
  943. return (
  944. <Radio class={styles.radio} name={item}>
  945. <Tag size="large" plain={isActive} type={type}>
  946. {teacherPaymentType[item]}
  947. </Tag>
  948. </Radio>
  949. )
  950. })}
  951. </RadioGroup>
  952. )
  953. }}
  954. </Field>
  955. {this.paymentType === 'CHARGE' && (
  956. <>
  957. <Field
  958. label="收费价格"
  959. required
  960. border={false}
  961. class={styles.inputControl}
  962. placeholder=" "
  963. formatter={this.onFormatter}
  964. autocomplete="off"
  965. v-slots={{ button: () => '元' }}
  966. modelValue={this.musicPrice}
  967. maxlength={8}
  968. center
  969. rules={[
  970. { required: true, validator, message: '请输入收费价格' }
  971. ]}
  972. errorMessageAlign="right"
  973. onUpdate:modelValue={val => (this.musicPrice = val)}
  974. />
  975. <div class={styles.rule}>
  976. <p>
  977. 扣除手续费后该曲目预计收入为:
  978. <span>
  979. {(
  980. ((parseFloat(this.musicPrice || '0') || 0) *
  981. (100 - this.music_sheet_service_fee)) /
  982. 100
  983. ).toFixed(2)}
  984. <span>元/人</span>
  985. </span>
  986. </p>
  987. {/* <p>
  988. 每人:
  989. <span>
  990. {(
  991. ((parseFloat(this.musicPrice || '0') || 0) *
  992. (100 - this.music_sheet_service_fee)) /
  993. 100
  994. ).toFixed(2)}
  995. </span>
  996. 元/人
  997. </p> */}
  998. <p>
  999. 您的乐谱收入在学员购买后{this.music_account_period}
  1000. 天结算到您的账户中
  1001. </p>
  1002. </div>
  1003. </>
  1004. )}
  1005. </CellGroup>
  1006. {/* <ColSticky position="bottom" background="transparent"> */}
  1007. <div class={[styles['button-area']]}>
  1008. <Button
  1009. type="primary"
  1010. block
  1011. round
  1012. native-type="submit"
  1013. disabled={this.submitLoading || this.auditDisabled}
  1014. >
  1015. <img src={btnBg} />
  1016. </Button>
  1017. </div>
  1018. {/* </ColSticky> */}
  1019. {/* <Popup
  1020. show={this.showPicker}
  1021. round
  1022. position="bottom"
  1023. teleport="body"
  1024. onUpdate:show={val => (this.showPicker = val)}
  1025. >
  1026. <Picker
  1027. defaultIndex={this.subJectIndex}
  1028. columnsFieldNames={{
  1029. text: 'value'
  1030. }}
  1031. columns={Object.entries(this.subjectListNames).map(
  1032. ([key, value]) => ({ label: key, value })
  1033. )}
  1034. onCancel={() => (this.showPicker = false)}
  1035. onConfirm={val => {
  1036. this.selectedSubjectList = val
  1037. this.vlewSubjectList = val
  1038. this.showPicker = false
  1039. }}
  1040. />
  1041. </Popup> */}
  1042. <Popup
  1043. show={this.tagVisibility}
  1044. round
  1045. closeable
  1046. position="bottom"
  1047. style={{ height: '35%' }}
  1048. teleport="body"
  1049. onUpdate:show={val => (this.tagVisibility = val)}
  1050. >
  1051. <SelectTag
  1052. onConfirm={this.onComfirm}
  1053. show={this.tagVisibility}
  1054. onClose={() => {
  1055. this.tagVisibility = false
  1056. }}
  1057. tagList={this.tagList}
  1058. defaultValue={this.tags}
  1059. />
  1060. </Popup>
  1061. <Popup
  1062. show={this.listenAudioShow}
  1063. round
  1064. closeable
  1065. teleport="body"
  1066. class={styles.listenAudioShow}
  1067. onUpdate:show={val => (this.listenAudioShow = val)}
  1068. >
  1069. {this.listenAudioShow && <ListenAudio fileInfo={this.fileInfo} />}
  1070. </Popup>
  1071. <MessageTip
  1072. title={this.messageTipTitle}
  1073. type={this.messageTipType}
  1074. show={this.messageTipStatus}
  1075. onConfirm={() => {
  1076. if (this.messageTipType === 'upload') {
  1077. localStorage.setItem('isCatchTip', '1')
  1078. }
  1079. this.messageTipStatus = false
  1080. }}
  1081. />
  1082. </Form>
  1083. )
  1084. }
  1085. })