new-index.tsx 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428
  1. import TheSticky from '@/components/the-sticky'
  2. import styles from './new-index.module.less'
  3. import { useEventListener, useThrottleFn, useWindowScroll } from '@vueuse/core'
  4. import { postMessage } from '@/helpers/native-message'
  5. import iconShare from '../../images/icon-share.png'
  6. import oStart from '../album-detail/icon-hart.png'
  7. import iStart from '../album-detail/icon-hart-active.png'
  8. import iconDownload from './images/icon-download.png'
  9. import iconMemberSmall from './images/icon-member-small.png'
  10. import {
  11. computed,
  12. defineComponent,
  13. nextTick,
  14. onMounted,
  15. onUnmounted,
  16. reactive,
  17. ref,
  18. toRaw,
  19. watch
  20. } from 'vue'
  21. import umiRequest from 'umi-request'
  22. import { useRoute, useRouter } from 'vue-router'
  23. import request from '@/helpers/request'
  24. import ColHeader from '@/components/col-header'
  25. import {
  26. Button,
  27. Cell,
  28. CellGroup,
  29. Dialog,
  30. Icon,
  31. Image,
  32. Popup,
  33. RadioGroup,
  34. Radio,
  35. Toast,
  36. Picker
  37. } from 'vant'
  38. import { useRect } from '@vant/use'
  39. import { Vue3Lottie } from 'vue3-lottie'
  40. import { getRandomKey, musicBuy } from '../music'
  41. import { getOssUploadUrl, state } from '@/state'
  42. import { browser, moneyFormat } from '@/helpers/utils'
  43. import { orderStatus } from '@/views/order-detail/orderStatus'
  44. import AstronautJSON from './animate/refresh_anim.json'
  45. import ColShare from '@/components/col-share'
  46. import iconListen from './images/icon_listen.png'
  47. import iconTeacher from '@common/images/icon_teacher.png'
  48. import emtpy from './images/emtpy.png'
  49. import { state as baseState } from '@/state'
  50. import activeButtonIcon from './images/icon_checkbox.png'
  51. import inactiveButtonIcon from './images/icon_checkbox_default.png'
  52. import staffDetafult from './images/staff-default.png'
  53. import firstDefault from './images/first-default.png'
  54. import fixedDefault from './images/fixed-default.png'
  55. import Plyr from 'plyr'
  56. import 'plyr/dist/plyr.css'
  57. import Download from './download'
  58. import { getInstrumentName } from '@/constant/instruments'
  59. import { getUploadSign, onOnlyFileUpload } from '@/helpers/oss-file-upload'
  60. import { svgtopng } from './formatSvgToImg'
  61. import { shareCall } from '@/teacher/share-page/share'
  62. import deepClone from '@/helpers/deep-clone'
  63. import { formatXML, getCustomInfo, onlyVisible } from './instrument'
  64. export default defineComponent({
  65. name: 'new-index',
  66. setup() {
  67. localStorage.setItem('behaviorId', getRandomKey())
  68. const router = useRouter()
  69. const route = useRoute()
  70. const loading = ref(false)
  71. const background = ref<string>('rgba(55, 205, 177, 0)')
  72. const color = ref<string>('#fff')
  73. const aId = Number(route.query.activityId) || 0
  74. const studentActivityId = ref(aId)
  75. const isError = ref(false)
  76. const headers = ref(null)
  77. const footers = ref(null)
  78. const heightInfo = ref<any>('0')
  79. const musicDetail = ref<any>(null)
  80. const audioFileUrl = ref('')
  81. const showImg = ref([] as any)
  82. const firstList = ref<Array<any>>([])
  83. const fixedList = ref<Array<any>>([])
  84. const staffList = ref<Array<any>>([])
  85. const uploadImgs = ref<boolean>(false)
  86. const defaultImgs = ref({
  87. first: false,
  88. fixed: false,
  89. staff: false
  90. })
  91. const accompanyUrl = ref<string>('')
  92. const downloadStatus = ref<boolean>(false)
  93. const staff = reactive({
  94. status: false,
  95. radio: 'staff' // staff first fixed
  96. })
  97. const colors: any = {
  98. FREE: {
  99. color: '#88D5AC',
  100. text: '免费'
  101. },
  102. VIP: {
  103. color: '#FFFA6B',
  104. text: '会员'
  105. },
  106. CHARGE: {
  107. color: '#AEFAFF',
  108. text: '点播'
  109. }
  110. }
  111. const apiSuffix = ref(
  112. baseState.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
  113. )
  114. // 更改预览状态
  115. const onChangeStaff = (type: string) => {
  116. staff.radio = type
  117. staff.status = false
  118. if (type == 'first' && !defaultImgs.value.first) {
  119. loading.value = false
  120. resetRenderPage('first', staffData.musicXml)
  121. } else if (type == 'fixed' && !defaultImgs.value.fixed) {
  122. loading.value = false
  123. resetRenderPage('fixed', staffData.musicXml)
  124. } else {
  125. if (!defaultImgs.value.staff) {
  126. loading.value = false
  127. resetRenderPage('staff', staffData.musicXml)
  128. }
  129. }
  130. }
  131. watch(
  132. () => staff.radio,
  133. (val: string) => {
  134. if (val == 'first') {
  135. showImg.value = deepClone(firstList.value)
  136. } else if (val == 'fixed') {
  137. showImg.value = deepClone(fixedList.value)
  138. } else {
  139. showImg.value = deepClone(staffList.value)
  140. }
  141. }
  142. )
  143. const FetchList = async (id?: any) => {
  144. if (loading.value) {
  145. return
  146. }
  147. loading.value = true
  148. isError.value = false
  149. try {
  150. const res = await request.get(`/music/sheet/detail/${route.query.id}`, {
  151. prefix:
  152. state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student',
  153. params: {
  154. tenantAlbumId: route.query.tenantAlbumId || null
  155. }
  156. })
  157. musicDetail.value = res.data
  158. // 取原音,如果有多个则默认第一个
  159. const background = res.data.background
  160. audioFileUrl.value =
  161. background && background.length > 0 ? background[0].audioFileUrl : ''
  162. showImg.value = res.data.musicImg ? res.data.musicImg.split(',') : []
  163. firstList.value = res.data.firstTone
  164. ? res.data.firstTone.split(',')
  165. : []
  166. fixedList.value = res.data.fixedTone
  167. ? res.data.fixedTone.split(',')
  168. : []
  169. staffList.value = res.data.musicImg ? res.data.musicImg.split(',') : []
  170. // 初始化默认数据是否有值
  171. if (firstList.value.length > 0) {
  172. defaultImgs.value.first = true
  173. }
  174. if (fixedList.value.length > 0) {
  175. defaultImgs.value.fixed = true
  176. }
  177. if (staffList.value.length > 0) {
  178. defaultImgs.value.staff = true
  179. }
  180. nextTick(() => {
  181. renderStaff()
  182. })
  183. } catch (error) {
  184. isError.value = true
  185. }
  186. if (musicDetail.value?.musicSheetType !== 'CONCERT') {
  187. loading.value = false
  188. }
  189. }
  190. const base64ToBlob = data => {
  191. const arr = data.split(','),
  192. mime = arr[0].match(/:(.*?);/)[1]
  193. const bstr = atob(arr[1])
  194. let n = bstr.length
  195. const u8arr = new Uint8Array(n)
  196. while (n--) {
  197. u8arr[n] = bstr.charCodeAt(n)
  198. }
  199. return new Blob([u8arr], { type: mime })
  200. }
  201. const uploadFunction = async file => {
  202. try {
  203. const formData = new FormData()
  204. const fileName =
  205. new Date().getTime() + Math.ceil(Math.random() * 1000) + '.png'
  206. const keyTime = new Date().getTime() + fileName
  207. const obj = {
  208. filename: keyTime,
  209. bucketName: 'cloud-coach',
  210. postData: {
  211. filename: keyTime,
  212. acl: 'public-read',
  213. key: keyTime
  214. }
  215. }
  216. // const res = await request.post(state.platformApi + '/getUploadSign', {
  217. // data: obj
  218. // })
  219. const res = await getUploadSign(obj)
  220. Toast.loading({
  221. message: '加载中...',
  222. forbidClick: true,
  223. loadingType: 'spinner',
  224. duration: 0
  225. })
  226. const dataObj = {
  227. policy: res.data.policy,
  228. signature: res.data.signature,
  229. key: keyTime,
  230. KSSAccessKeyId: res.data.kssAccessKeyId,
  231. acl: 'public-read',
  232. name: fileName
  233. }
  234. const files = base64ToBlob(file)
  235. const ossUploadUrl = getOssUploadUrl('cloud-coach')
  236. const imgurl = await onOnlyFileUpload(ossUploadUrl, {
  237. ...dataObj,
  238. file: files
  239. })
  240. Toast.clear()
  241. // const imgurl = getOssUploadUrl('cloud-coach') + keyTime
  242. await request.post(state.platformApi + '/open/music/sheet/img', {
  243. data: { musicSheetId: musicDetail.value.id, musicImg: imgurl }
  244. })
  245. // showImg.value.value = imgurl
  246. } catch (e) {
  247. console.log(e)
  248. }
  249. }
  250. const setAccompanyUrl = () => {
  251. let url = location.origin
  252. if (
  253. location.host.includes('dev.colexiu') ||
  254. location.host.includes('192.168') ||
  255. location.host.includes('localhost')
  256. ) {
  257. url = 'https://dev.colexiu.com'
  258. }
  259. const music = musicDetail.value
  260. let subjectId = ''
  261. if (music.background && music.background.length > 0) {
  262. subjectId = music.background[0].id
  263. }
  264. accompanyUrl.value =
  265. url +
  266. `/accompany/colxiu-website.html?id=${music.id}&part-index=${subjectId}`
  267. }
  268. const player = ref<any>(null)
  269. const audio = ref<any>(null)
  270. const freeRate = ref<any>(0)
  271. const initAudio = async () => {
  272. const controls = [
  273. // 'play-large',
  274. 'play',
  275. 'progress',
  276. 'captions',
  277. // 'fullscreen',
  278. 'duration'
  279. ]
  280. player.value = new Plyr(audio.value, {
  281. controls: controls
  282. })
  283. const config = await request.get(
  284. '/api-student/sysConfig/queryByParamNameList',
  285. {
  286. params: {
  287. paramNames: 'music_sheet_free_rate'
  288. }
  289. }
  290. )
  291. freeRate.value = config.data[0]?.paramValue || 0
  292. player.value.on('timeupdate', () => {
  293. // 允许播放时间
  294. const players = player.value
  295. const playTime = (players.duration * freeRate.value) / 100 || 0
  296. // 时间,不能播放
  297. if (players.currentTime >= playTime && !buyState.value.play) {
  298. players.stop()
  299. // players.pause()
  300. }
  301. })
  302. }
  303. const showLoading = async (e: any) => {
  304. // console.log(e, 'showLoading')
  305. if (e.data?.api === 'musicStaffRender') {
  306. const osmdImg = e.data.osmdImg
  307. showImg.value = []
  308. const imgs: any = []
  309. // 独奏并且的图片则不用生成
  310. if (
  311. musicDetail.value?.musicSheetType === 'SINGLE' &&
  312. musicDetail.value?.notation
  313. ) {
  314. if (
  315. (staff.radio === 'first' && firstList.value.length > 0) ||
  316. (staff.radio === 'fixed' && fixedList.value.length > 0) ||
  317. (staff.radio === 'staff' && staffList.value.length > 0)
  318. ) {
  319. if (staff.radio === 'first') {
  320. showImg.value = firstList.value
  321. } else if (staff.radio === 'fixed') {
  322. showImg.value = fixedList.value
  323. }
  324. if (staff.radio === 'staff') {
  325. showImg.value = staffList.value
  326. }
  327. loading.value = e.data.loading
  328. return
  329. }
  330. }
  331. uploadImgs.value = true
  332. if (osmdImg) {
  333. for (let i = 0; i < osmdImg.length; i++) {
  334. const img = await svgtopng(
  335. osmdImg[i].img,
  336. osmdImg[i].width,
  337. osmdImg[i].height
  338. )
  339. const fileName =
  340. route.query.id + state.user.data.userId + +new Date() + '.png'
  341. const obj = {
  342. filename: fileName,
  343. bucketName: 'cloud-coach',
  344. postData: {
  345. filename: fileName,
  346. acl: 'public-read',
  347. key: fileName
  348. }
  349. }
  350. const { data } = await getUploadSign(obj, true)
  351. const dataObj = {
  352. policy: data.policy,
  353. signature: data.signature,
  354. key: fileName,
  355. KSSAccessKeyId: data.kssAccessKeyId,
  356. acl: 'public-read',
  357. name: fileName
  358. }
  359. const files = base64ToBlob(img)
  360. const ossUploadUrl = getOssUploadUrl('cloud-coach')
  361. const imgurl = await onOnlyFileUpload(ossUploadUrl, {
  362. ...dataObj,
  363. file: files
  364. })
  365. imgs.push(imgurl)
  366. }
  367. }
  368. uploadImgs.value = false
  369. // 判断是否为独奏
  370. if (
  371. musicDetail.value?.musicSheetType === 'SINGLE' &&
  372. musicDetail.value?.notation
  373. ) {
  374. if (staff.radio === 'first') {
  375. firstList.value = imgs
  376. } else if (staff.radio === 'fixed') {
  377. fixedList.value = imgs
  378. } else if (staff.radio === 'staff') {
  379. staffList.value = imgs
  380. }
  381. showImg.value = imgs
  382. } else {
  383. showImg.value = imgs
  384. }
  385. loading.value = e.data.loading
  386. }
  387. }
  388. onMounted(async () => {
  389. postMessage({
  390. api: 'setStatusBarTextColor',
  391. content: { statusBarTextColor: true }
  392. })
  393. await FetchList()
  394. const { height } = useRect(headers as any)
  395. const footer = useRect(footers as any)
  396. heightInfo.value = height + footer.height
  397. // 初始化音频
  398. if (audioFileUrl.value) {
  399. initAudio()
  400. }
  401. window.addEventListener('message', showLoading)
  402. })
  403. onUnmounted(() => {
  404. postMessage({
  405. api: 'setStatusBarTextColor',
  406. content: { statusBarTextColor: false }
  407. })
  408. window.removeEventListener('message', showLoading)
  409. })
  410. const toggleFavorite = async () => {
  411. try {
  412. await request.post('/music/sheet/favorite/' + musicDetail.value?.id, {
  413. prefix:
  414. state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student'
  415. })
  416. musicDetail.value.favorite = musicDetail.value?.favorite ? 0 : 1
  417. musicDetail.value.favoriteCount = musicDetail.value?.favorite
  418. ? musicDetail.value.favoriteCount + 1
  419. : musicDetail.value.favoriteCount - 1 < 0
  420. ? 0
  421. : musicDetail.value.favoriteCount - 1
  422. setTimeout(() => {
  423. Toast(musicDetail.value?.favorite ? '收藏成功' : '取消收藏成功')
  424. }, 100)
  425. } catch (error) {
  426. //
  427. }
  428. }
  429. const onAddCourse = async () => {
  430. try {
  431. const res = await request.post('/api-teacher/courseCourseware/submit', {
  432. data: {
  433. musicSheetId: musicDetail.value.id,
  434. clientType: 'TEACHER',
  435. userId: state.user.data?.userId
  436. }
  437. })
  438. // console.log(res)
  439. setTimeout(() => {
  440. musicDetail.value.coursewareId = res.data.id || ''
  441. Toast('添加成功')
  442. musicDetail.value.coursewareStatus = 1
  443. }, 100)
  444. } catch {
  445. //
  446. }
  447. }
  448. const removeCourse = async () => {
  449. Dialog.confirm({
  450. title: '提示',
  451. message: '您是否确定移除课件',
  452. confirmButtonColor: '#269a93',
  453. cancelButtonText: '取消',
  454. confirmButtonText: '确定'
  455. }).then(async () => {
  456. try {
  457. await request.post(
  458. '/api-teacher/courseCourseware/remove/' +
  459. musicDetail.value.coursewareId,
  460. {
  461. data: {}
  462. }
  463. )
  464. setTimeout(() => {
  465. Toast('移除成功')
  466. musicDetail.value.coursewareStatus = 0
  467. }, 100)
  468. } catch {
  469. //
  470. }
  471. })
  472. }
  473. const onBuy = async () => {
  474. const music = musicDetail.value
  475. orderStatus.orderObject.orderType = 'MUSIC'
  476. orderStatus.orderObject.orderName = music.musicSheetName
  477. orderStatus.orderObject.orderDesc = music.musicSheetName
  478. orderStatus.orderObject.actualPrice = music.musicPrice
  479. orderStatus.orderObject.recomUserId = route.query.recomUserId || 0
  480. orderStatus.orderObject.activityId = route.query.activityId || 0
  481. orderStatus.orderObject.orderNo = ''
  482. orderStatus.orderObject.orderList = [
  483. {
  484. orderType: 'MUSIC',
  485. goodsName: music.musicSheetName,
  486. actualPrice: music.musicPrice,
  487. ...music
  488. }
  489. ]
  490. const res = await request.post('/api-student/userOrder/getPendingOrder', {
  491. data: {
  492. goodType: 'MUSIC',
  493. bizId: music.id
  494. }
  495. })
  496. const result = res.data
  497. if (result) {
  498. Dialog.confirm({
  499. title: '提示',
  500. message: '您有一个未支付的订单,是否继续支付?',
  501. theme: 'round-button',
  502. className: 'confirm-button-group',
  503. cancelButtonText: '取消订单',
  504. confirmButtonText: '继续支付'
  505. })
  506. .then(async () => {
  507. orderStatus.orderObject.orderNo = result.orderNo
  508. orderStatus.orderObject.actualPrice = result.actualPrice
  509. orderStatus.orderObject.discountPrice = result.discountPrice
  510. orderStatus.orderObject.paymentConfig = {
  511. ...result.paymentConfig,
  512. paymentVendor: result.paymentVendor,
  513. paymentVersion: result.paymentVersion
  514. }
  515. routerTo()
  516. })
  517. .catch(() => {
  518. Dialog.close()
  519. // 只用取消订单,不用做其它处理
  520. cancelPayment(result.orderNo)
  521. })
  522. } else {
  523. routerTo()
  524. }
  525. }
  526. const routerTo = () => {
  527. const music = musicDetail.value
  528. router.push({
  529. path: '/orderDetail',
  530. query: {
  531. orderType: 'MUSIC',
  532. musicId: music.id
  533. }
  534. })
  535. }
  536. const cancelPayment = async (orderNo: string) => {
  537. try {
  538. await request.post('/api-student/userOrder/orderCancel', {
  539. data: {
  540. orderNo
  541. }
  542. })
  543. } catch {
  544. //
  545. }
  546. }
  547. const paymentType = computed(() => {
  548. let paymentType = musicDetail.value?.paymentType
  549. if (typeof paymentType === 'string') {
  550. paymentType = paymentType.split(',')
  551. return paymentType
  552. }
  553. return []
  554. })
  555. const buyState = computed(() => {
  556. const music = musicDetail.value
  557. return {
  558. hasTenantAlbum: route.query?.tenantAlbumId ? true : false, // 是否从专辑来的
  559. play: music.play ? true : false, // 是否可以播放
  560. free: music?.paymentType.includes('FREE'),
  561. charge: music?.paymentType.includes('CHARGE'),
  562. vip: music?.paymentType.includes('VIP'),
  563. buy: music?.orderStatus === 'PAID' // 是否已买
  564. }
  565. })
  566. const shareStatus = ref(false)
  567. const shareUrl = ref('')
  568. const shareDiscount = ref(0)
  569. const onShare = async () => {
  570. try {
  571. const res = await request.post('/api-teacher/open/musicShareProfit', {
  572. data: {
  573. bizId: musicDetail.value?.id,
  574. userId: state.user.data?.userId
  575. }
  576. })
  577. let url =
  578. location.origin +
  579. `/teacher/#/shareMusic?id=${musicDetail.value?.id}&recomUserId=${state.user.data?.userId}&userType=${state.platformType}`
  580. // 判断是否有活动
  581. if (res.data.discount === 1) {
  582. url += `&activityId=${res.data.activityId}`
  583. }
  584. shareDiscount.value = res.data.discount || 0
  585. console.log(url)
  586. shareUrl.value = url
  587. shareStatus.value = true
  588. return
  589. } catch {
  590. //
  591. }
  592. }
  593. const staffData = reactive({
  594. open: false,
  595. iframeSrc: '',
  596. musicXml: '',
  597. instrumentName: '',
  598. iframeRef: null as any,
  599. partIndex: 0,
  600. partXmlIndex: 0,
  601. partList: [] as any[],
  602. tempPartList: [] as any[],
  603. xmlPartList: [] as any[]
  604. })
  605. /** 渲染五线谱 */
  606. // 长笛、单簧管、萨克斯、小号、长号、圆号、大号、上低音号
  607. const sortList = {
  608. 长笛: 1,
  609. 单簧管: 2,
  610. 中音单簧管: 3,
  611. 低音单簧管: 4,
  612. 高音萨克斯风: 5,
  613. 中音萨克斯风: 6,
  614. 次中音萨克斯风: 7,
  615. 低音萨克斯风: 8,
  616. 小号: 9,
  617. 长号: 10,
  618. 圆号: 11,
  619. 大号: 12,
  620. 上低音号: 13
  621. }
  622. const instrumentSort = (list: Array<any>) => {
  623. list.sort((a, b) => {
  624. return (
  625. (sortList[getInstrumentName(a.track)] || 20) -
  626. (sortList[getInstrumentName(b.track)] || 20)
  627. )
  628. })
  629. return list
  630. }
  631. const renderStaff = async () => {
  632. try {
  633. if (musicDetail.value?.xmlFileUrl) {
  634. // 获取文件
  635. const res = await umiRequest.get(musicDetail.value?.xmlFileUrl, {
  636. mode: 'cors'
  637. })
  638. let partNames: string[] = []
  639. const xml: any = new DOMParser().parseFromString(res, 'text/xml')
  640. for (const item of xml.getElementsByTagName('part-name')) {
  641. if (item.textContent) {
  642. partNames.push(item.textContent)
  643. }
  644. }
  645. partNames = partNames.filter(
  646. (item: any) => !item?.toLocaleUpperCase()?.includes('COMMON')
  647. )
  648. const partList: any = []
  649. for (let j = 0; j < partNames.length; j++) {
  650. partList.push({
  651. name: partNames[j],
  652. value: j
  653. })
  654. }
  655. staffData.xmlPartList = partList
  656. }
  657. // staffData.iframeSrc = `${
  658. // location.origin
  659. // }/osmd/index.html?t=${new Date().getTime()}`
  660. staffData.iframeSrc = `${location.origin}${
  661. location.pathname
  662. }osmd/index.html?t=${new Date().getTime()}`
  663. staffData.musicXml = musicDetail.value?.xmlFileUrl || ''
  664. const tempList = musicDetail.value?.background || []
  665. const tempPartList = [] as any
  666. staffData.xmlPartList.forEach((part: any) => {
  667. const item = tempList.find((item: any) => item.track === part.name)
  668. if (item) {
  669. tempPartList.push({
  670. ...item,
  671. index: part.value
  672. })
  673. }
  674. })
  675. staffData.partList = tempPartList
  676. console.log(
  677. staffData.partList,
  678. tempPartList,
  679. 'staffData.partList',
  680. tempList
  681. )
  682. staffData.tempPartList = JSON.parse(JSON.stringify(staffData.partList))
  683. staffData.partList = instrumentSort(staffData.partList)
  684. // console.log(staffData.partList, ' staffData.partList')
  685. staffData.partXmlIndex = staffData.partList[0].index || 0
  686. staffData.instrumentName =
  687. musicDetail.value?.musicSheetType === 'CONCERT'
  688. ? getInstrumentName(staffData.partList[staffData.partIndex]?.track)
  689. : ''
  690. // console.log(staffData.partList, 'staffData', staffData.xmlPartList)
  691. } catch (error) {
  692. //
  693. }
  694. }
  695. const musicIframeLoad = async () => {
  696. const iframeRef: any = document.getElementById('staffIframeRef')
  697. if (iframeRef && iframeRef.contentWindow.renderXml) {
  698. const res = await umiRequest.get(staffData.musicXml, {
  699. mode: 'cors'
  700. })
  701. const parseXmlInfo = getCustomInfo(res)
  702. const xml = formatXML(parseXmlInfo.parsedXML)
  703. const currentXml = onlyVisible(xml, staffData.partXmlIndex)
  704. iframeRef.contentWindow.renderXml(currentXml, 0)
  705. // iframeRef.contentWindow.renderXml(
  706. // staffData.musicXml,
  707. // staffData.partXmlIndex
  708. // )
  709. }
  710. }
  711. const resetRender = async () => {
  712. const iframeRef: any = document.getElementById('staffIframeRef')
  713. if (iframeRef && iframeRef.contentWindow.renderXml) {
  714. loading.value = true
  715. // iframeRef.contentWindow.resetRender(staffData.partXmlIndex)
  716. const res = await umiRequest.get(staffData.musicXml, {
  717. mode: 'cors'
  718. })
  719. const parseXmlInfo = getCustomInfo(res)
  720. const xml = formatXML(parseXmlInfo.parsedXML)
  721. const currentXml = onlyVisible(xml, staffData.partXmlIndex)
  722. iframeRef.contentWindow.renderXml(currentXml, 0)
  723. staffData.instrumentName = getInstrumentName(
  724. staffData.partList[staffData.partIndex]?.track
  725. )
  726. }
  727. }
  728. const resetRenderPage = async (type: string, xmlUrl: string) => {
  729. const iframeRef: any = document.getElementById('staffIframeRef')
  730. if (iframeRef && iframeRef.contentWindow.renderXml) {
  731. // console.log('resetRenderPage')
  732. // iframeRef.contentWindow.resetRenderPage(type, xmlUrl)
  733. const res = await umiRequest.get(staffData.musicXml, {
  734. mode: 'cors'
  735. })
  736. const parseXmlInfo = getCustomInfo(res)
  737. const xml = formatXML(parseXmlInfo.parsedXML)
  738. const currentXml = onlyVisible(xml, staffData.partXmlIndex)
  739. iframeRef.contentWindow.resetRenderPage(type, currentXml)
  740. }
  741. }
  742. const partColumns = computed(() => {
  743. return staffData.partList.map((item: any, index: number) => {
  744. const instrumentName =
  745. musicDetail.value?.musicSheetType === 'CONCERT'
  746. ? getInstrumentName(item.track)
  747. : ''
  748. return {
  749. text: item.track + (instrumentName ? `(${instrumentName})` : ''),
  750. value: index,
  751. xmlValue: item.index,
  752. track: item.track
  753. }
  754. })
  755. })
  756. const onDownloadApp = () => {
  757. Dialog.alert({
  758. title: '提示',
  759. message: '请在酷乐秀APP中使用',
  760. confirmButtonColor: '#2dc7aa'
  761. }).then(() => {
  762. window.location.href = location.origin + '/student/#/download'
  763. })
  764. }
  765. // 购买
  766. const onSubmit = async () => {
  767. const url =
  768. apiSuffix.value +
  769. '/tenantGroupAlbum/buyAlbumInfo?tenantGroupAlbumId=' +
  770. (route.query.taId || '')
  771. // if (state.albumId) {
  772. // url = url + '?albumId=' + state.albumId
  773. // }
  774. const { data } = await request.get(url)
  775. const details = data[0]
  776. orderStatus.orderObject.orderType = 'TENANT_ALBUM'
  777. orderStatus.orderObject.orderName = details.name
  778. orderStatus.orderObject.orderDesc = details.name
  779. orderStatus.orderObject.actualPrice = details.actualPrice
  780. // orderStatus.orderObject.recomUserId = route.query.recomUserId || 0
  781. // orderStatus.orderObject.activityId = route.query.activityId || 0
  782. orderStatus.orderObject.orderNo = ''
  783. orderStatus.orderObject.orderList = [
  784. {
  785. orderType: 'TENANT_ALBUM',
  786. goodsName: details.name,
  787. actualPrice: details.actualPrice,
  788. price: details.actualPrice,
  789. ...details
  790. }
  791. ]
  792. const res = await request.post('/api-student/userOrder/getPendingOrder', {
  793. data: {
  794. goodType: 'TENANT_ALBUM',
  795. bizId: details.id
  796. }
  797. })
  798. const result = res.data
  799. if (result) {
  800. Dialog.confirm({
  801. title: '提示',
  802. message: '您有一个未支付的订单,是否继续支付?',
  803. theme: 'round-button',
  804. className: 'confirm-button-group',
  805. cancelButtonText: '取消订单',
  806. confirmButtonText: '继续支付'
  807. })
  808. .then(async () => {
  809. orderStatus.orderObject.orderNo = result.orderNo
  810. orderStatus.orderObject.actualPrice = result.actualPrice
  811. orderStatus.orderObject.discountPrice = result.discountPrice
  812. orderStatus.orderObject.paymentConfig = {
  813. ...result.paymentConfig,
  814. paymentVendor: result.paymentVendor,
  815. paymentVersion: result.paymentVersion
  816. }
  817. routerToALBUM(details.id)
  818. })
  819. .catch(() => {
  820. Dialog.close()
  821. // 只用取消订单,不用做其它处理
  822. cancelPaymentALBUM(result.orderNo)
  823. })
  824. } else {
  825. routerToALBUM(details.id)
  826. }
  827. }
  828. const cancelPaymentALBUM = async (orderNo: string) => {
  829. try {
  830. await request.post('/api-student/userOrder/orderCancel/v2', {
  831. data: {
  832. orderNo
  833. }
  834. })
  835. } catch {
  836. //
  837. }
  838. }
  839. const routerToALBUM = (id: string) => {
  840. router.push({
  841. path: '/orderDetail',
  842. query: {
  843. orderType: 'ALBUM',
  844. album: id
  845. }
  846. })
  847. }
  848. return () => (
  849. <div class={styles.detail}>
  850. <TheSticky position="top">
  851. <ColHeader
  852. background={background.value}
  853. border={false}
  854. isFixed={false}
  855. color={color.value}
  856. backIconColor="white"
  857. />
  858. </TheSticky>
  859. <img class={styles.bgImg} src={musicDetail.value?.titleImg} />
  860. <div class={styles.musicContentBg}></div>
  861. <div class={styles.bg}>
  862. <div class={styles.alumWrap}>
  863. <div class={styles.img}>
  864. {/* {albumDetail.value?.paymentType === 'CHARGE' && (
  865. <span class={styles.albumType}>付费</span>
  866. )} */}
  867. <Image
  868. class={styles.image}
  869. width="100%"
  870. height="100%"
  871. fit="cover"
  872. src={musicDetail.value?.titleImg}
  873. />
  874. </div>
  875. <div class={styles.alumDes}>
  876. <div class={[styles.alumTitle, 'van-ellipsis']}>
  877. {musicDetail.value?.musicSheetName}
  878. </div>
  879. <div class={[styles.des, 'van-multi-ellipsis--l2']}>
  880. {!musicDetail.value?.composer
  881. ? `上传者:${musicDetail.value?.addName || ''}`
  882. : `作曲:${musicDetail.value?.composer || ''}`}
  883. </div>
  884. <div class={styles.tags}>
  885. {musicDetail.value?.id && (
  886. <>
  887. {musicDetail.value?.musicTagNames &&
  888. musicDetail.value?.musicTagNames.split(',').map(name => (
  889. <span
  890. style={{
  891. borderColor: colors.FREE.color,
  892. color: colors.FREE.color
  893. }}
  894. class={styles.tag}
  895. >
  896. {name}
  897. </span>
  898. ))}
  899. </>
  900. )}
  901. </div>
  902. </div>
  903. </div>
  904. <div
  905. class={[
  906. styles.alumCollect
  907. // musicDetail.value?.musicSheetType === 'CONCERT'
  908. // ? styles.alumCollectCencert
  909. // : ''
  910. ]}
  911. >
  912. <div
  913. class={styles.alumCollectItem}
  914. onClick={() => {
  915. if (browser().isApp) {
  916. onShare()
  917. } else {
  918. onDownloadApp()
  919. }
  920. }}
  921. >
  922. <Image src={iconShare} />
  923. <span>分享</span>
  924. </div>
  925. {/* {musicDetail.value?.musicSheetType !== 'CONCERT' && ( */}
  926. <div
  927. class={[
  928. styles.alumCollectItem,
  929. showImg.value.length <= 0 ? styles.alumCollectItemActive : ''
  930. ]}
  931. onClick={() => {
  932. if (browser().isApp) {
  933. if (showImg.value.length > 0) {
  934. downloadStatus.value = true
  935. }
  936. } else {
  937. onDownloadApp()
  938. }
  939. }}
  940. >
  941. <img src={iconDownload} />
  942. <span>下载</span>
  943. </div>
  944. {/* )} */}
  945. <div
  946. class={styles.alumCollectItem}
  947. onClick={() => toggleFavorite()}
  948. >
  949. <img src={musicDetail.value?.favorite ? iStart : oStart} />
  950. <span>{musicDetail.value?.favoriteCount}</span>
  951. </div>
  952. </div>
  953. {/* {buyState.value.hasTenantAlbum ? 'true' : 'false'} */}
  954. {musicDetail.value?.id &&
  955. !buyState.value.play &&
  956. !buyState.value.hasTenantAlbum && (
  957. <div class={styles.albumTips}>
  958. {buyState.value.charge && buyState.value.vip ? (
  959. <>
  960. <span>开通会员或点播单曲,即可自由练习该曲谱</span>
  961. <span class={styles.albumPrice}>
  962. ¥{moneyFormat(musicDetail.value?.musicPrice)}
  963. </span>
  964. </>
  965. ) : buyState.value.vip ? (
  966. <span>
  967. <img src={iconMemberSmall} class={styles.iconMemberSmall} />
  968. 此曲谱为会员专享,开通会员即可自由练习该曲谱
  969. </span>
  970. ) : buyState.value.charge ? (
  971. <>
  972. <span>此曲谱为点播曲谱,点播即可自由练习该曲谱</span>
  973. <span class={styles.albumPrice}>
  974. ¥{moneyFormat(musicDetail.value?.musicPrice)}
  975. </span>
  976. </>
  977. ) : (
  978. ''
  979. )}
  980. </div>
  981. )}
  982. </div>
  983. <div class={styles.musicContent}>
  984. {musicDetail.value?.notation ? (
  985. <span
  986. class={styles.iconTransfer}
  987. style={{
  988. display:
  989. musicDetail.value?.musicSheetType === 'SINGLE' ? '' : 'none',
  990. opacity: showImg.value.length <= 0 ? 0.6 : 1
  991. }}
  992. onClick={() => {
  993. if (showImg.value.length <= 0) return
  994. staff.status = true
  995. }}
  996. >
  997. 转谱
  998. </span>
  999. ) : null}
  1000. <span
  1001. class={styles.iconTransfer}
  1002. style={{
  1003. display:
  1004. musicDetail.value?.musicSheetType === 'CONCERT' ? '' : 'none'
  1005. }}
  1006. onClick={() => {
  1007. staffData.open = true
  1008. }}
  1009. >
  1010. 切换声轨
  1011. </span>
  1012. <p class={styles.musicTitle}>
  1013. {(musicDetail.value?.musicSheetName
  1014. ? musicDetail.value?.musicSheetName
  1015. : '') +
  1016. (staffData.instrumentName ? `(${staffData.instrumentName})` : '')}
  1017. </p>
  1018. {musicDetail.value?.musicSheetType === 'CONCERT' ||
  1019. !defaultImgs.value[staff.radio] ? (
  1020. <>
  1021. {loading.value && (
  1022. <div>
  1023. <Vue3Lottie
  1024. animationData={AstronautJSON}
  1025. class={styles.finch}
  1026. ></Vue3Lottie>
  1027. {/* <p class={styles.finchLoad}>加载中...</p> */}
  1028. </div>
  1029. )}
  1030. <iframe
  1031. id="staffIframeRef"
  1032. style={{
  1033. opacity: loading.value ? 0 : 1
  1034. }}
  1035. src={staffData.iframeSrc}
  1036. onLoad={() => {
  1037. if (!defaultImgs.value[staff.radio]) {
  1038. onChangeStaff(staff.radio)
  1039. } else {
  1040. musicIframeLoad()
  1041. }
  1042. }}
  1043. ></iframe>
  1044. {/* <OsmdPreview ref={osmdPreviewRef} /> */}
  1045. </>
  1046. ) : (
  1047. <>
  1048. {showImg.value.length > 0 ? (
  1049. <div class={styles.musicImg}>
  1050. <img src={showImg.value[0]} alt="" />
  1051. </div>
  1052. ) : loading.value ? (
  1053. <>
  1054. <Vue3Lottie
  1055. animationData={AstronautJSON}
  1056. class={styles.finch}
  1057. ></Vue3Lottie>
  1058. {/* <p class={styles.finchLoad}>加载中...</p> */}
  1059. </>
  1060. ) : (
  1061. <div class={styles.empty}>
  1062. <Image src={emtpy} class={styles.emptyImg} />
  1063. <p class={styles.emptyTip}>暂无乐谱预览图</p>
  1064. </div>
  1065. )}
  1066. </>
  1067. )}
  1068. </div>
  1069. {musicDetail.value?.id && (
  1070. <TheSticky position="bottom">
  1071. <div style={{ backgroundColor: '#fff' }}>
  1072. <div class={styles.videoOperation}>
  1073. {audioFileUrl.value && (
  1074. <>
  1075. {!buyState.value.play &&
  1076. freeRate.value != 100 &&
  1077. freeRate.value != 0 && (
  1078. <div class={[styles.audition]}>
  1079. <img src={iconListen} />
  1080. <span>每首曲目可试听{freeRate.value}%</span>
  1081. </div>
  1082. )}
  1083. <div class={[styles.audio, styles.collectCell]}>
  1084. <audio id="player" controls ref={audio}>
  1085. <source src={audioFileUrl.value} type="audio/mp3" />
  1086. </audio>
  1087. </div>
  1088. </>
  1089. )}
  1090. </div>
  1091. <div ref={footers} class={styles.footers}>
  1092. {/* 判断是否是免费的,或者已经购买过 */}
  1093. {buyState.value.play ? (
  1094. <Button
  1095. round
  1096. block
  1097. type="primary"
  1098. color="linear-gradient( 270deg, #FF204B 0%, #FE5B71 100%)"
  1099. onClick={() => {
  1100. if (!browser().isApp) {
  1101. onDownloadApp()
  1102. return
  1103. }
  1104. const throttleFn = useThrottleFn(() => {
  1105. player.value && player.value.stop()
  1106. const item: any = partColumns.value.find(
  1107. (c: any) => c.value === staffData.partIndex
  1108. )
  1109. // const index = staffData.tempPartList.findIndex(
  1110. // (i: any) => i.track === item?.track
  1111. // )
  1112. musicBuy(musicDetail.value, () => {}, {
  1113. 'part-index': item.xmlValue || 0,
  1114. sett: staff.radio,
  1115. // 1:忽略系统节拍器
  1116. ignoreSysMetronome:
  1117. route.query.subjectType === 'MUSIC' ? 1 : 0
  1118. })
  1119. }, 500)
  1120. throttleFn()
  1121. }}
  1122. >
  1123. 立即练习
  1124. </Button>
  1125. ) : buyState.value.hasTenantAlbum && !buyState.value.play ? (
  1126. <Button
  1127. disabled={route.query.buyStatus === '1'}
  1128. round
  1129. block
  1130. type="primary"
  1131. color="linear-gradient( 270deg, #FF204B 0%, #FE5B71 100%)"
  1132. onClick={() => {
  1133. if (!browser().isApp) {
  1134. onDownloadApp()
  1135. return
  1136. }
  1137. if (route.query.type === 'search') {
  1138. router.push('train-tool')
  1139. } else {
  1140. onSubmit()
  1141. }
  1142. }}
  1143. >
  1144. 开通训练教程
  1145. </Button>
  1146. ) : (
  1147. <div class={[styles.buyBtn]}>
  1148. {/* 判断是否是需要收费的 */}
  1149. {buyState.value.charge && (
  1150. <Button
  1151. round
  1152. type="primary"
  1153. color="linear-gradient(270deg, #FF204B 0%, #FE5B71 100%)"
  1154. class={styles.primary}
  1155. onClick={() => {
  1156. if (!browser().isApp) {
  1157. onDownloadApp()
  1158. return
  1159. }
  1160. onBuy()
  1161. }}
  1162. >
  1163. 立即点播
  1164. </Button>
  1165. )}
  1166. {/* 判断是否有会员的 */}
  1167. {buyState.value.vip && (
  1168. <Button
  1169. round
  1170. block={!buyState.value.charge ? true : false}
  1171. type="primary"
  1172. color="linear-gradient(270deg, #FF204B 0%, #FE5B71 100%)"
  1173. class={styles.memeber}
  1174. onClick={() => {
  1175. if (!browser().isApp) {
  1176. onDownloadApp()
  1177. return
  1178. }
  1179. router.push({
  1180. path: '/memberCenter',
  1181. query: {
  1182. ...route.query
  1183. }
  1184. })
  1185. }}
  1186. >
  1187. {studentActivityId.value > 0 && (
  1188. <div class={[styles.buttonDiscount]}>专属优惠</div>
  1189. )}
  1190. 开通会员
  1191. </Button>
  1192. )}
  1193. </div>
  1194. )}
  1195. </div>
  1196. </div>
  1197. </TheSticky>
  1198. )}
  1199. <Popup
  1200. v-model:show={shareStatus.value}
  1201. style={{ background: 'transparent' }}
  1202. teleport="body"
  1203. >
  1204. <ColShare
  1205. teacherId={state.user.data?.userId}
  1206. shareUrl={shareUrl.value}
  1207. shareType="music"
  1208. type="tenant"
  1209. >
  1210. <div class={styles.shareMate}>
  1211. {shareDiscount.value === 1 && (
  1212. <div class={styles.tagDiscount}>专属优惠</div>
  1213. )}
  1214. <img
  1215. class={styles.icon}
  1216. crossorigin="anonymous"
  1217. src={musicDetail.value?.titleImg + `?t=${+new Date()}`}
  1218. />
  1219. <div class={styles.info}>
  1220. <h4 class="van-multi-ellipsis--l2">
  1221. {musicDetail.value?.musicSheetName}
  1222. </h4>
  1223. <p>作曲人:{musicDetail.value?.composer}</p>
  1224. </div>
  1225. </div>
  1226. </ColShare>
  1227. </Popup>
  1228. <Popup v-model:show={downloadStatus.value} position="bottom" round>
  1229. {downloadStatus.value && (
  1230. <Download
  1231. imgList={JSON.parse(JSON.stringify(showImg.value))}
  1232. musicSheetName={musicDetail.value.musicSheetName}
  1233. />
  1234. )}
  1235. </Popup>
  1236. <Popup
  1237. v-model:show={staff.status}
  1238. teleport="body"
  1239. closeable
  1240. style={{ width: '80%' }}
  1241. class={styles.staffChange}
  1242. round
  1243. >
  1244. <div class={styles.staffContainer}>
  1245. <div class={styles.staffTitle}>选择转换曲谱</div>
  1246. <RadioGroup v-model={staff.radio}>
  1247. <CellGroup border={false}>
  1248. <Cell
  1249. center
  1250. border={false}
  1251. class={staff.radio === 'staff' ? styles.active : ''}
  1252. onClick={() => onChangeStaff('staff')}
  1253. >
  1254. {{
  1255. icon: () => (
  1256. <Image src={staffDetafult} class={styles.staffImg} />
  1257. ),
  1258. title: () => <span class={styles.name}>五线谱</span>,
  1259. value: () => (
  1260. <Radio name="staff">
  1261. {{
  1262. icon: (props: any) => (
  1263. <Icon
  1264. class={styles.boxStyle}
  1265. name={
  1266. props.checked
  1267. ? activeButtonIcon
  1268. : inactiveButtonIcon
  1269. }
  1270. />
  1271. )
  1272. }}
  1273. </Radio>
  1274. )
  1275. }}
  1276. </Cell>
  1277. <Cell
  1278. center
  1279. border={false}
  1280. class={staff.radio === 'first' ? styles.active : ''}
  1281. onClick={() => onChangeStaff('first')}
  1282. >
  1283. {{
  1284. icon: () => (
  1285. <Image src={firstDefault} class={styles.staffImg} />
  1286. ),
  1287. title: () => <span class={styles.name}>简谱-首调</span>,
  1288. value: () => (
  1289. <Radio name="first">
  1290. {{
  1291. icon: (props: any) => (
  1292. <Icon
  1293. class={styles.boxStyle}
  1294. name={
  1295. props.checked
  1296. ? activeButtonIcon
  1297. : inactiveButtonIcon
  1298. }
  1299. />
  1300. )
  1301. }}
  1302. </Radio>
  1303. )
  1304. }}
  1305. </Cell>
  1306. <Cell
  1307. center
  1308. border={false}
  1309. class={staff.radio === 'fixed' ? styles.active : ''}
  1310. onClick={() => onChangeStaff('fixed')}
  1311. >
  1312. {{
  1313. icon: () => (
  1314. <Image src={fixedDefault} class={styles.staffImg} />
  1315. ),
  1316. title: () => <span class={styles.name}>简谱-固定调</span>,
  1317. value: () => (
  1318. <Radio name="fixed">
  1319. {{
  1320. icon: (props: any) => (
  1321. <Icon
  1322. class={styles.boxStyle}
  1323. name={
  1324. props.checked
  1325. ? activeButtonIcon
  1326. : inactiveButtonIcon
  1327. }
  1328. />
  1329. )
  1330. }}
  1331. </Radio>
  1332. )
  1333. }}
  1334. </Cell>
  1335. </CellGroup>
  1336. </RadioGroup>
  1337. </div>
  1338. </Popup>
  1339. <Popup
  1340. teleport="body"
  1341. position="bottom"
  1342. round
  1343. v-model:show={staffData.open}
  1344. >
  1345. <Picker
  1346. columns={partColumns.value}
  1347. onConfirm={value => {
  1348. staffData.open = false
  1349. staffData.partIndex = value.value
  1350. staffData.partXmlIndex = value.xmlValue
  1351. showImg.value = []
  1352. nextTick(() => {
  1353. resetRender()
  1354. })
  1355. }}
  1356. onCancel={() => (staffData.open = false)}
  1357. />
  1358. </Popup>
  1359. </div>
  1360. )
  1361. }
  1362. })