index.tsx 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114
  1. import { defineComponent, nextTick, onMounted, reactive } from 'vue'
  2. import styles from './index.module.less'
  3. import headTitle from './images/header-title.png'
  4. import headPhone from './images/header-phone.png'
  5. import headBg from './images/header-bg.png'
  6. import iconHead from './images/icon-head.png'
  7. import iconBao from './images/icon-bao.png'
  8. import iconFree from './images/icon-free.png'
  9. import icon12 from './images/icon-12.png'
  10. import iconCheckbox from './images/icon-checkbox-ring.png'
  11. import iconCheckboxActive from './images/icon-checkbox-active.png'
  12. import {
  13. Button,
  14. CellGroup,
  15. Field,
  16. Form,
  17. Radio,
  18. RadioGroup,
  19. Tag,
  20. showToast,
  21. Image,
  22. showImagePreview,
  23. Cell,
  24. Checkbox,
  25. Picker,
  26. Popup
  27. } from 'vant'
  28. import OSticky from '@/components/o-sticky'
  29. import { browser, getUrlCode, moneyFormat, removeAuth } from '@/helpers/utils'
  30. import request from '@/helpers/request'
  31. import { useRoute, useRouter } from 'vue-router'
  32. import { goWechatAuth, setLogout } from '@/state'
  33. import ODialog from '@/components/o-dialog'
  34. import InspectModal from './modal/inspect-modal'
  35. import qs from 'query-string'
  36. // 乐团交付,乐团停止或关闭,有新的交付团;则不允许报名
  37. const classList: any = []
  38. for (let i = 1; i <= 40; i++) {
  39. classList.push({ text: i + '班', value: i })
  40. }
  41. export default defineComponent({
  42. name: 'pre-goods-apply',
  43. setup() {
  44. const route = useRoute()
  45. const router = useRouter()
  46. // 获取是否已经看过训练工具图片
  47. const readToolImg = localStorage.getItem('read-tool-image')
  48. const state = reactive({
  49. code: '' as any, // 微信授权code码
  50. detail: {} as any, // 学员详情
  51. toolImgStatus: readToolImg ? true : false,
  52. currentGrade: [
  53. { text: '一年级', value: 1 },
  54. { text: '二年级', value: 2 },
  55. { text: '三年级', value: 3 },
  56. { text: '四年级', value: 4 },
  57. { text: '五年级', value: 5 },
  58. { text: '六年级', value: 6 },
  59. { text: '七年级', value: 7 },
  60. { text: '八年级', value: 8 },
  61. { text: '九年级', value: 9 }
  62. ], // 年级数组列表
  63. classList: classList,
  64. subjectList: [] as any, // 入选声部列表
  65. instrumentsInspectionDescribe: '', // 配置信息
  66. inspectPopupStatus: false, // 查看说明
  67. gradeStatus: false,
  68. classStatus: false,
  69. subjectStatus: false,
  70. registerInfo: {} as any, // 乐团信息
  71. goodsInfo: {} as any, // 商品
  72. textBookInfo: {} as any, // 教材
  73. inspectInfo: {} as any, // 乐器检查
  74. vipYearInfo: {} as any, // 学练工具
  75. inspectStatus: true,
  76. // 是否开启微信登录(测试使用)默认为false
  77. testIsWeixin: false,
  78. details: [] as any, //
  79. pattern: /^1(3|4|5|6|7|8|9)\d{9}$/,
  80. nameReg: /^[\u4E00-\u9FA5]+$/,
  81. paymentType: '',
  82. musicPaymentType: '', // 乐团中对应支付方式
  83. studentReadStatus: false, // 学生在读
  84. dialogStatus: false,
  85. dialogMessage: '',
  86. dialogOrchestraStatus: false, // 是否为不同的乐团
  87. dialogConfig: {} as any,
  88. orderInfo: {
  89. needPrice: 0,
  90. originalPrice: 0
  91. },
  92. submitStatus: false
  93. })
  94. const forms = reactive({
  95. username: null,
  96. sex: null as any,
  97. currentGrade: null,
  98. currentGradeTxt: null, // 年级编号
  99. currentClass: '', // 班级
  100. currentClassTxt: null, // 年级编号
  101. registerSubjectId: '',
  102. registerSubjectTxt: null, // 所在入选声部
  103. parentName: null,
  104. groupBuyType: '' as any,
  105. phone: null as any,
  106. learningTools: null,
  107. instrumentsBrand: null
  108. })
  109. const otherParams = reactive({
  110. toolPlan: {
  111. title: '', // 训练工具准备
  112. groupTitle: '', // 团购标题
  113. groupDesc: '', // 团购文案
  114. selfTitle: '', // 自备标题
  115. selfDesc: '' // 自备文案
  116. },
  117. leBao: {
  118. // 乐器保障
  119. show: 1, // 是否显示
  120. selected: 1 // 是否选择
  121. }
  122. })
  123. // 获取乐团报名信息
  124. const studentRegister = async () => {
  125. try {
  126. const { data } = await request.get(
  127. '/api-student/orchestraRegister/register/' + route.query.id
  128. )
  129. const detail = data || {}
  130. state.detail = detail
  131. const grade: any = state.currentGrade.find((item: any) => item.value == detail.currentGrade)
  132. const cls: any = state.classList.find((item: any) => item.value == detail.currentClass)
  133. const subjects: any = state.subjectList.find(
  134. (item: any) => item.value == detail.registerSubjectId
  135. )
  136. forms.username = detail.username
  137. forms.sex = detail.sex ? 1 : 0
  138. forms.currentGrade = detail.currentGrade
  139. forms.currentGradeTxt = grade ? grade.text : ''
  140. forms.currentClass = detail.currentClass
  141. forms.currentClassTxt = cls ? cls.text : ''
  142. forms.registerSubjectId = subjects ? subjects.value : '' // detail.registerSubjectId
  143. forms.registerSubjectTxt = subjects ? subjects.text : ''
  144. forms.parentName = detail.parentName
  145. forms.phone = detail.phone
  146. forms.instrumentsBrand = detail.instrumentsBrand
  147. forms.learningTools = detail.learningTools
  148. if (detail.registerSubjectId && detail.registerSubjectId !== 999) {
  149. // 更新商品信息
  150. await registerGoods()
  151. }
  152. forms.groupBuyType = detail.groupBuyType
  153. } catch (e) {
  154. //
  155. console.log(e)
  156. }
  157. }
  158. // 获取入选声部信息
  159. const getSubjects = async () => {
  160. try {
  161. const subjects = await request.post(
  162. '/api-student/open/orchestraSubjectConfig/pageByOrchestraId',
  163. {
  164. data: {
  165. orchestraId: route.query.id,
  166. page: 1,
  167. rows: 100
  168. }
  169. }
  170. )
  171. const rows = subjects.data.rows || []
  172. rows.forEach((item: any) => {
  173. state.subjectList.push({
  174. text: item.name,
  175. value: item.subjectId
  176. })
  177. })
  178. } catch {
  179. //
  180. }
  181. }
  182. const validator = (val: any) => {
  183. // 校验函数返回 true 表示校验通过,false 表示不通过
  184. return state.nameReg.test(val) && val.length >= 2 && val.length <= 15
  185. }
  186. const message = (value: any) => {
  187. if (!value) {
  188. return '请填写学员真实姓名'
  189. } else if (!state.nameReg.test(value)) {
  190. return '学员姓名必须为中文'
  191. } else if (value.length < 2 || value.length > 15) {
  192. return '学员姓名必须为2~15个字'
  193. } else {
  194. return ''
  195. }
  196. }
  197. const onRegisterUser = async () => {
  198. try {
  199. const params: any = {
  200. orchestraId: route.query.id,
  201. schoolId: state.detail.schoolId,
  202. ...forms
  203. }
  204. // 判断是否已报过名
  205. // if (state.detail.id) {
  206. // params.id = state.detail.id
  207. // }
  208. // 开始报名
  209. await request.post('/api-student/orchestraRegister/save', {
  210. hideLoading: false,
  211. data: {
  212. ...params,
  213. code: state.code
  214. }
  215. })
  216. if (forms.groupBuyType === 'SELF') {
  217. router.push({
  218. path: '/preGoodsSuccess'
  219. })
  220. }
  221. } catch (e: any) {
  222. //
  223. return Promise.reject(e)
  224. }
  225. }
  226. // 乐团报名
  227. const onSubmit = async () => {
  228. try {
  229. if (forms.groupBuyType === 'SELF') {
  230. state.submitStatus = true
  231. return
  232. }
  233. // 判断是否购买乐器
  234. // 乐器报名前先注册用户
  235. await onRegisterUser()
  236. // 重新计算金额
  237. calcPrice()
  238. const params: any = [] // 支付参数
  239. // 教材
  240. const textBookInfo = state.textBookInfo
  241. params.push({
  242. goodsId: textBookInfo.goodsId,
  243. goodsNum: 1,
  244. goodsType: textBookInfo.goodsType,
  245. paymentCashAmount: textBookInfo.currentPrice, // 现金支付金额
  246. paymentCouponAmount: 0 // 优惠券金额
  247. })
  248. // 商品
  249. const goodsInfo = state.goodsInfo
  250. params.push({
  251. goodsId: goodsInfo.goodsId,
  252. goodsNum: 1,
  253. goodsType: goodsInfo.goodsType,
  254. paymentCashAmount: goodsInfo.currentPrice, // 现金支付金额
  255. paymentCouponAmount: 0 // 优惠券金额
  256. })
  257. // 检查
  258. if (state.inspectStatus) {
  259. const inspectInfo = state.inspectInfo
  260. params.push({
  261. goodsId: inspectInfo.goodsId,
  262. goodsNum: 1,
  263. goodsType: inspectInfo.goodsType,
  264. paymentCashAmount: inspectInfo.currentPrice, // 现金支付金额
  265. paymentCouponAmount: 0 // 优惠券金额
  266. })
  267. }
  268. // 学练工具
  269. const vipYear = state.vipYearInfo
  270. params.push({
  271. goodsId: vipYear.goodsId,
  272. goodsNum: 1,
  273. goodsType: vipYear.goodsType,
  274. paymentCashAmount: vipYear.currentPrice, // 现金支付金额
  275. paymentCouponAmount: 0 // 优惠券金额
  276. })
  277. // 创建订单
  278. const { data } = await request.post('/api-student/userPaymentOrder/executeOrder', {
  279. hideLoading: false,
  280. data: {
  281. paymentType: state.musicPaymentType || state.paymentType,
  282. bizId: route.query.id, // 乐团编号
  283. orderType: 'ORCHESTRA',
  284. paymentCashAmount: state.orderInfo.needPrice || 0,
  285. paymentCouponAmount: 0,
  286. goodsInfos: params,
  287. orderName: '乐团报名缴费',
  288. orderDesc: '乐团报名缴费'
  289. }
  290. })
  291. console.log(data)
  292. router.push({
  293. path: '/orderDetail',
  294. query: {
  295. pm: 1, // h5乐团报名
  296. config: JSON.stringify({ ...data.paymentConfig, paymentType: data.paymentType }),
  297. orderNo: data.orderNo
  298. }
  299. })
  300. } catch {
  301. //
  302. }
  303. }
  304. const getRegisterStatus = async () => {
  305. try {
  306. const { data } = await request.get(
  307. '/api-student/orchestraRegister/registerStatus/' + route.query.id
  308. )
  309. state.registerInfo = data || {}
  310. const trainingToolsConfig = data.trainingToolsConfig
  311. ? JSON.parse(data.trainingToolsConfig)
  312. : {}
  313. otherParams.toolPlan = trainingToolsConfig
  314. const musicalInstrumentConfig = data.musicalInstrumentConfig
  315. ? JSON.parse(data.musicalInstrumentConfig)
  316. : {}
  317. otherParams.leBao = musicalInstrumentConfig
  318. // 默认根据设置显示
  319. state.inspectStatus = musicalInstrumentConfig.selected ? true : false
  320. // 判断学生是否在读
  321. if (data.learningOrchestra?.length) {
  322. state.studentReadStatus = true
  323. }
  324. } catch {
  325. //
  326. }
  327. }
  328. const getAppIdAndCode = async () => {
  329. try {
  330. const { data } = await request.get('/api-student/open/paramConfig/wechatAppId')
  331. // 判断是否有微信appId
  332. if (data) {
  333. // goAuth(data)
  334. goWechatAuth(data)
  335. }
  336. } catch {
  337. //
  338. }
  339. }
  340. // 查询未支付订单
  341. const paymentOrderUnpaid = async () => {
  342. try {
  343. const { data } = await request.get('/api-student/userPaymentOrder/unpaid', {
  344. params: {
  345. orchestraId: route.query.id
  346. }
  347. })
  348. // 判断是否有待支付订单
  349. if (data.id) {
  350. if (data.orchestraId !== route.query.id) {
  351. state.dialogMessage = '您在其它乐团存在待支付订单,取消后才可继续报名,是否确认取消?'
  352. state.dialogStatus = true
  353. state.dialogOrchestraStatus = true
  354. state.dialogConfig = data
  355. } else {
  356. state.dialogMessage = '您有待支付的订单,是否继续支付'
  357. state.dialogStatus = true
  358. state.dialogConfig = data
  359. }
  360. }
  361. } catch {
  362. //
  363. }
  364. }
  365. // 获取支付渠道
  366. const sysParamConfig = async () => {
  367. try {
  368. const { data } = await request.get('/api-student/sysParamConfig/queryByParamName', {
  369. params: {
  370. paramName: 'payment_service_provider'
  371. }
  372. })
  373. state.paymentType = data.paramValue || ''
  374. } catch {
  375. //
  376. }
  377. }
  378. // 获取商品信息
  379. const registerGoods = async () => {
  380. try {
  381. const { data } = await request.get(
  382. '/api-student/orchestraRegister/registerGoods/' + route.query.id,
  383. {
  384. params: {
  385. subjectId: forms.registerSubjectId
  386. }
  387. }
  388. )
  389. state.musicPaymentType = data.paymentServiceProvider || ''
  390. // 初始化数据商品数据
  391. const details = data.details || []
  392. details.forEach((item: any) => {
  393. if (item.goodsType === 'INSTRUMENTS') {
  394. const img = item.goodsUrl ? item.goodsUrl.split(',')[0] : ''
  395. state.goodsInfo = { ...item, goodsUrl: img }
  396. state.instrumentsInspectionDescribe = item.instrumentsInspectionDescribe
  397. } else if (item.goodsType === 'TEXTBOOK') {
  398. const img = item.goodsUrl ? item.goodsUrl.split(',')[0] : ''
  399. state.textBookInfo = { ...item, goodsUrl: img }
  400. } else if (item.goodsType === 'INSTRUMENT_INSPECT') {
  401. state.inspectInfo = { ...item }
  402. } else if (item.goodsType === 'VIP_YEAR') {
  403. state.vipYearInfo = { ...item }
  404. }
  405. state.details = details
  406. })
  407. calcPrice()
  408. } catch {
  409. //
  410. }
  411. }
  412. // 初始化金额
  413. const calcPrice = () => {
  414. // const details = state.details
  415. const tempPrice = {
  416. needPrice: 0, //需要支付金额
  417. originalPrice: 0 // 原价
  418. }
  419. // 商品
  420. tempPrice.needPrice += parseFloat(state.goodsInfo.currentPrice || 0)
  421. tempPrice.originalPrice += parseFloat(state.goodsInfo.originalPrice || 0)
  422. // 学练工具
  423. tempPrice.needPrice += parseFloat(state.vipYearInfo.currentPrice || 0)
  424. tempPrice.originalPrice += parseFloat(state.vipYearInfo.originalPrice || 0)
  425. // 教材
  426. tempPrice.needPrice += parseFloat(state.textBookInfo.currentPrice || 0)
  427. tempPrice.originalPrice += parseFloat(state.textBookInfo.originalPrice || 0)
  428. // 检查乐器
  429. if (state.inspectStatus) {
  430. tempPrice.needPrice += parseFloat(state.inspectInfo.currentPrice || 0)
  431. tempPrice.originalPrice += parseFloat(state.inspectInfo.originalPrice || 0)
  432. }
  433. state.orderInfo = tempPrice
  434. }
  435. const onLoutout = () => {
  436. setLogout()
  437. removeAuth()
  438. const query = {
  439. returnUrl: route.path,
  440. ...route.query
  441. } as any
  442. window.location.href =
  443. window.location.origin + window.location.pathname + '#/loginMusic?' + qs.stringify(query)
  444. // router.replace({
  445. // path: '/loginMusic',
  446. // query: query
  447. // })
  448. }
  449. onMounted(async () => {
  450. // state.code = route.query.code || ''
  451. // console.log('pre register code: ' + state.code)
  452. await getSubjects()
  453. await studentRegister()
  454. sysParamConfig()
  455. // 查询未支付订单
  456. // registerGoods()
  457. paymentOrderUnpaid()
  458. // 判断是否有授权码
  459. // if (!props.code) {
  460. // setLogout()
  461. // const query = {
  462. // returnUrl: route.path,
  463. // ...route.query
  464. // } as any
  465. // router.replace({
  466. // path: '/loginMusic',
  467. // query: query
  468. // })
  469. // }
  470. })
  471. // 先请求接口 判断是否有code
  472. if (state.testIsWeixin) {
  473. getRegisterStatus()
  474. } else {
  475. if (browser().weixin) {
  476. // 微信公众号支付
  477. //授权
  478. const code = getUrlCode()
  479. if (!code) {
  480. getAppIdAndCode()
  481. } else {
  482. state.code = code
  483. getRegisterStatus()
  484. }
  485. } else {
  486. onLoutout()
  487. }
  488. }
  489. return () => (
  490. <div class={styles.goodsApply}>
  491. <img src={headBg} class={styles.headBg} />
  492. <div class={styles.goodsHeader}>
  493. <div class={styles.orchestraTitle}>
  494. <img class={styles.headTitle} src={headTitle} />
  495. <p class={[styles.name, 'van-multi-ellipsis--l3']}>
  496. {state.registerInfo.orchestraName}
  497. </p>
  498. </div>
  499. <img src={headPhone} class={styles.headPhone} />
  500. </div>
  501. <Form
  502. validateFirst
  503. errorMessageAlign="right"
  504. scrollToError={true}
  505. onSubmit={onSubmit}
  506. onFailed={(e: any) => {
  507. // 获取第一个校验错误的元素
  508. nextTick(() => {
  509. const isError = document.getElementsByClassName('van-field__error-message')
  510. // 滚动到错误元素对应位置
  511. isError[0]?.scrollIntoView({
  512. block: 'center',
  513. behavior: 'smooth'
  514. })
  515. })
  516. }}
  517. ref="form"
  518. class={styles.form}
  519. >
  520. <CellGroup class={styles.applyCellGroup} border={false}>
  521. <div class={[styles.title, styles.titleApply]}></div>
  522. <Field
  523. required
  524. label="学员信息"
  525. placeholder="请填写学员真实姓名"
  526. inputAlign="right"
  527. v-model={forms.username}
  528. maxlength={15}
  529. rules={[{ validator, message }]}
  530. />
  531. <Field
  532. required
  533. label="性别"
  534. inputAlign="right"
  535. rules={[{ required: true, message: '请选择性别' }]}
  536. >
  537. {{
  538. input: () => (
  539. <RadioGroup v-model={forms.sex}>
  540. <Tag
  541. size="large"
  542. type="primary"
  543. class={[styles.radioSection, forms.sex === 1 ? styles.active : '']}
  544. >
  545. <Radio class={styles.radioItem} name={1}></Radio>
  546. 男生
  547. </Tag>
  548. <Tag
  549. size="large"
  550. type="primary"
  551. class={[styles.radioSection, forms.sex === 0 ? styles.active : '']}
  552. >
  553. <Radio class={styles.radioItem} name={0}></Radio>女生
  554. </Tag>
  555. </RadioGroup>
  556. )
  557. }}
  558. </Field>
  559. <Field
  560. required
  561. label="年级"
  562. inputAlign="right"
  563. readonly
  564. isLink
  565. clickable={false}
  566. placeholder="请选择年级"
  567. v-model={forms.currentGradeTxt}
  568. onClick={() => (state.gradeStatus = true)}
  569. rules={[{ required: true, message: '请选择年级' }]}
  570. />
  571. <Field
  572. required
  573. label="班级"
  574. inputAlign="right"
  575. readonly
  576. isLink
  577. clickable={false}
  578. placeholder="请选择班级"
  579. v-model={forms.currentClassTxt}
  580. onClick={() => (state.classStatus = true)}
  581. rules={[{ required: true, message: '请选择班级' }]}
  582. />
  583. <Field
  584. required
  585. label="入选声部"
  586. inputAlign="right"
  587. readonly
  588. isLink
  589. clickable={false}
  590. placeholder="请选择入选声部"
  591. v-model={forms.registerSubjectTxt}
  592. onClick={() => {
  593. if (state.subjectList.length <= 0) {
  594. showToast('暂无报名入选声部')
  595. return
  596. }
  597. state.subjectStatus = true
  598. }}
  599. rules={[{ required: true, message: '请选择入选声部' }]}
  600. />
  601. </CellGroup>
  602. <CellGroup class={styles.applyCellGroup} border={false}>
  603. <div class={[styles.title, styles.titleParent]}></div>
  604. <Field
  605. required
  606. label="家长姓名"
  607. inputAlign="right"
  608. placeholder="请填写家长真实姓名"
  609. v-model={forms.parentName}
  610. maxlength={15}
  611. rules={[{ required: true, message: '请填写家长真实姓名' }]}
  612. />
  613. <Field
  614. required
  615. label="手机号"
  616. inputAlign="right"
  617. placeholder="请输入手机号"
  618. v-model={forms.phone}
  619. maxlength={11}
  620. type="tel"
  621. rules={[{ pattern: state.pattern, message: '输入监护人手机号码有误' }]}
  622. />
  623. </CellGroup>
  624. <CellGroup class={styles.applyCellGroup} border={false}>
  625. <div class={[styles.title, styles.titleTips]}></div>
  626. <div class={styles.tipsContainer} v-html={state.registerInfo.joinNotice}>
  627. {/* <p class={styles.line}>
  628. <i class={[styles.num, styles.numOne]}></i>
  629. 为保障乐团训练质量,开训前,家长务必准备好①乐器和②管乐AI学练工具,准备方式不限;
  630. </p>
  631. <p class={styles.line}>
  632. <i class={[styles.num, styles.numTwo]}></i>
  633. 为了降低家长投入压力,可参与项目支持方提供的AI学练工具团购,政策如下:
  634. <p class={[styles.child]}>
  635. 1、支持方<span style="color: #F67146">免费</span>
  636. 提供一支全新乐器供学生训练(可带回家) ;<br />
  637. 2、乐器提供时间为一年,如期满继续训练,乐器将
  638. <span style="color: #F67146">赠送</span>至学生以作鼓励;
  639. <br />
  640. 3、如期间或期满不再训练,家长归还乐器即可,
  641. <span style="color: #F67146">无需额外支付</span>乐器费用。
  642. </p>
  643. </p> */}
  644. </div>
  645. </CellGroup>
  646. <CellGroup class={styles.applyCellGroup} border={false}>
  647. <div class={[styles.title, styles.titleTool]}></div>
  648. <Field
  649. required
  650. label={otherParams.toolPlan.title}
  651. labelAlign="top"
  652. rules={[{ required: true, message: otherParams.toolPlan.title }]}
  653. >
  654. {{
  655. input: () => (
  656. <RadioGroup v-model={forms.groupBuyType} class={styles.toolRadioGroup}>
  657. <Tag
  658. size="large"
  659. type="primary"
  660. class={[
  661. styles.radioSectionTag,
  662. styles.radioSection,
  663. forms.groupBuyType === 'GROUP_BUY' ? styles.active : ''
  664. ]}
  665. >
  666. <Radio
  667. class={styles.radioItem}
  668. name={'GROUP_BUY'}
  669. disabled={forms.registerSubjectId ? false : true}
  670. onClick={() => {
  671. if (!forms.registerSubjectId) {
  672. showToast('请选择入选声部')
  673. return
  674. }
  675. }}
  676. ></Radio>
  677. {otherParams.toolPlan.groupTitle}
  678. <p class={styles.radioTip}>{otherParams.toolPlan.groupDesc}</p>
  679. </Tag>
  680. <Tag
  681. size="large"
  682. type="primary"
  683. class={[
  684. styles.radioSectionTag,
  685. styles.radioSection,
  686. forms.groupBuyType === 'SELF' ? styles.active : ''
  687. ]}
  688. >
  689. <Radio class={styles.radioItem} name={'SELF'}></Radio>
  690. {otherParams.toolPlan.selfTitle}
  691. <p class={styles.radioTip}>{otherParams.toolPlan.selfDesc}</p>
  692. </Tag>
  693. </RadioGroup>
  694. )
  695. }}
  696. </Field>
  697. </CellGroup>
  698. {forms.groupBuyType === 'GROUP_BUY' && (
  699. <>
  700. <CellGroup class={[styles.applyCellGroup, styles.groupBuy]} border={false}>
  701. <div class={[styles.title, styles.titleIntrumentTool]}></div>
  702. <Cell border={false}>
  703. {{
  704. icon: () => <Image src={state.vipYearInfo.goodsUrl} class={styles.goodsImg} />,
  705. value: () => (
  706. <div class={styles.vipYearInfo}>
  707. <div class={styles.goodsTitle}>
  708. {state.vipYearInfo.goodsName} <img src={icon12} />
  709. </div>
  710. <p class={styles.goodsTips}>乐团首次训练之日起生效</p>
  711. <p class={[styles.goodsMemo, 'van-multi-ellipsis--l2']}>
  712. {state.vipYearInfo.description}
  713. </p>
  714. <div class={styles.goodsPrice}>
  715. <div class={styles.priceGroup}>
  716. 团购价:
  717. <p>
  718. <span>¥</span> {moneyFormat(state.vipYearInfo.currentPrice)}
  719. </p>
  720. </div>
  721. {/* <del class={styles.priceOrigin}>
  722. 原价: ¥{moneyFormat(state.vipYearInfo.originalPrice)}
  723. </del> */}
  724. </div>
  725. </div>
  726. )
  727. }}
  728. </Cell>
  729. <Cell border={false}>
  730. {{
  731. icon: () => <Image src={state.goodsInfo.goodsUrl} class={styles.goodsImg} />,
  732. value: () => (
  733. <div class={styles.goodsInfo}>
  734. <div class={styles.goodsTitle}>
  735. {state.goodsInfo.goodsName}
  736. {state.goodsInfo.currentPrice <= 0 ? <img src={iconFree} /> : ''}
  737. </div>
  738. <p class={[styles.goodsMemo, 'van-multi-ellipsis--l2']}>
  739. {state.goodsInfo.description}
  740. </p>
  741. <div class={styles.goodsPrice}>
  742. <div class={styles.priceGroup}>
  743. 团购价:
  744. <p>
  745. {state.goodsInfo.currentPrice <= 0 ? (
  746. <del>
  747. <span>¥</span> {moneyFormat(state.goodsInfo.groupPrice)}
  748. </del>
  749. ) : (
  750. <>
  751. <span>¥</span> {moneyFormat(state.goodsInfo.currentPrice)}
  752. </>
  753. )}
  754. </p>
  755. </div>
  756. {/* {state.goodsInfo.currentPrice > 0 ? (
  757. <>
  758. <div class={styles.priceGroup}>
  759. 团购价:
  760. <p>
  761. <span>¥</span> {moneyFormat(state.goodsInfo.currentPrice)}
  762. </p>
  763. </div>
  764. <div class={styles.priceOrigin}>
  765. 原价: ¥{moneyFormat(state.goodsInfo.originalPrice)}
  766. </div>
  767. </>
  768. ) : (
  769. <>
  770. <div class={styles.priceGroup}>
  771. 原价:
  772. <p>
  773. <span>¥</span>{' '}
  774. <del>{moneyFormat(state.goodsInfo.originalPrice)}</del>
  775. </p>
  776. </div>
  777. </>
  778. )} */}
  779. </div>
  780. </div>
  781. )
  782. }}
  783. </Cell>
  784. {otherParams.leBao.show ? (
  785. <Cell
  786. class={styles.inspectCell}
  787. style={{ backgroundColor: state.inspectStatus ? '#FFF3EA' : '#f4f4f4' }}
  788. >
  789. {{
  790. icon: () => (
  791. <img
  792. src={iconBao}
  793. class={styles.iconBao}
  794. onClick={() => {
  795. if (state.instrumentsInspectionDescribe) state.inspectPopupStatus = true
  796. }}
  797. />
  798. ),
  799. value: () => (
  800. <div class={styles.baoContainer}>
  801. <div
  802. class={styles.baoTitle}
  803. onClick={() => {
  804. if (state.instrumentsInspectionDescribe)
  805. state.inspectPopupStatus = true
  806. }}
  807. >
  808. 下校检查乐器 1-2次/学期
  809. </div>
  810. <div class={styles.baoPrice}>
  811. <p
  812. onClick={() => {
  813. state.inspectStatus = !state.inspectStatus
  814. calcPrice()
  815. }}
  816. >
  817. <span class={styles.prefix}>¥</span> {state.inspectInfo.currentPrice}
  818. <span class={styles.suffix}>/年</span>
  819. </p>
  820. <Checkbox
  821. v-model={state.inspectStatus}
  822. onClick={() => {
  823. calcPrice()
  824. }}
  825. >
  826. {{
  827. icon: (props: any) => (
  828. <img
  829. class={styles.checkboxImg}
  830. src={props.checked ? iconCheckboxActive : iconCheckbox}
  831. />
  832. )
  833. }}
  834. </Checkbox>
  835. </div>
  836. </div>
  837. )
  838. }}
  839. </Cell>
  840. ) : (
  841. ''
  842. )}
  843. </CellGroup>
  844. <OSticky position="bottom">
  845. <div class={styles.paymentContainer}>
  846. <div class={styles.payemntPrice}>
  847. <p class={styles.needPrice}>
  848. 总计:
  849. <span class={styles.numFont}>
  850. <span>¥ </span>
  851. {moneyFormat(state.orderInfo.needPrice)}
  852. </span>
  853. </p>
  854. </div>
  855. <div class={styles.paymentBtn}>
  856. <Button
  857. color="linear-gradient(135deg, #FF8C4A 0%, #FF531C 100%)"
  858. round
  859. native-type="submit"
  860. >
  861. 团购支付
  862. </Button>
  863. </div>
  864. </div>
  865. </OSticky>
  866. </>
  867. )}
  868. {forms.groupBuyType === 'SELF' && (
  869. <>
  870. <CellGroup class={[styles.applyCellGroup, styles.self]} border={false}>
  871. <div
  872. class={styles.toolImg}
  873. onClick={() => {
  874. showImagePreview([state.registerInfo?.instrumentPriceImg])
  875. localStorage.setItem('read-tool-image', '1')
  876. state.toolImgStatus = true
  877. }}
  878. >
  879. <Image src={state.registerInfo?.instrumentPriceImg} />
  880. {!state.toolImgStatus && (
  881. <div class={styles.toolImgOverflow}>
  882. <Button>点击查看《乐团训练工具标准配置表》</Button>
  883. <img src={iconHead} class={styles.iconHead} />
  884. </div>
  885. )}
  886. </div>
  887. <Field
  888. required
  889. label="您计划配置的乐器品牌"
  890. labelAlign="top"
  891. border={false}
  892. placeholder="请填写您计划配置的乐器品牌"
  893. v-model={forms.instrumentsBrand}
  894. maxlength={20}
  895. class={styles.toolInput}
  896. rules={[{ required: true, message: '请填写您计划配置的乐器品牌' }]}
  897. />
  898. <Field
  899. style="padding-top: 0;"
  900. required
  901. label="您计划配置的AI学练工具品牌"
  902. labelAlign="top"
  903. border={false}
  904. class={styles.toolInput}
  905. placeholder="请填写您计划配置的AI学练工具品牌"
  906. v-model={forms.learningTools}
  907. maxlength={20}
  908. rules={[{ required: true, message: '请填写您计划配置的AI学练工具品牌' }]}
  909. />
  910. </CellGroup>
  911. <div class={'btnGroup'} style={{ paddingTop: '30px' }}>
  912. <Button
  913. type="primary"
  914. color="linear-gradient(135deg, #FF8C4A 0%, #FF531C 100%)"
  915. round
  916. block
  917. native-type="submit"
  918. >
  919. 提交
  920. </Button>
  921. </div>
  922. </>
  923. )}
  924. </Form>
  925. {/* 年级 */}
  926. <Popup
  927. v-model:show={state.gradeStatus}
  928. position="bottom"
  929. round
  930. safeAreaInsetBottom
  931. // duration={0}
  932. lazyRender={false}
  933. class={'popupBottomSearch'}
  934. >
  935. <Picker
  936. showToolbar
  937. columns={state.currentGrade}
  938. onCancel={() => (state.gradeStatus = false)}
  939. onConfirm={(val: any) => {
  940. const selectedOption = val.selectedOptions[0]
  941. forms.currentGrade = selectedOption.value
  942. forms.currentGradeTxt = selectedOption.text
  943. state.gradeStatus = false
  944. }}
  945. />
  946. </Popup>
  947. {/* 班级 */}
  948. <Popup v-model:show={state.classStatus} position="bottom" round class={'popupBottomSearch'}>
  949. <Picker
  950. showToolbar
  951. columns={state.classList}
  952. onCancel={() => (state.classStatus = false)}
  953. onConfirm={(val: any) => {
  954. const selectedOption = val.selectedOptions[0]
  955. forms.currentClass = selectedOption.value
  956. forms.currentClassTxt = selectedOption.text
  957. state.classStatus = false
  958. }}
  959. />
  960. </Popup>
  961. {/* 入选声部 */}
  962. <Popup
  963. v-model:show={state.subjectStatus}
  964. position="bottom"
  965. round
  966. class={'popupBottomSearch'}
  967. >
  968. <Picker
  969. showToolbar
  970. columns={state.subjectList}
  971. onCancel={() => (state.subjectStatus = false)}
  972. onConfirm={async (val: any) => {
  973. const selectedOption = val.selectedOptions[0]
  974. forms.registerSubjectId = selectedOption.value
  975. forms.registerSubjectTxt = selectedOption.text
  976. state.subjectStatus = false
  977. // 更新商品信息
  978. await registerGoods()
  979. }}
  980. />
  981. </Popup>
  982. {/* 入选声部 */}
  983. <Popup v-model:show={state.inspectPopupStatus} round closeable class={styles.inspectPopup}>
  984. <InspectModal describe={state.instrumentsInspectionDescribe} />
  985. </Popup>
  986. {/* 确认信息 */}
  987. <ODialog
  988. title="确认信息"
  989. v-model:show={state.submitStatus}
  990. message={
  991. '<p style="text-align: left;">请确认您准备的乐器和AI学练工具符合<span style="color: #F67146;font-weight: bold;">《乐团训练工具标准配置表》</span></p>'
  992. }
  993. class={styles.dialogSubmit}
  994. allowHtml
  995. confirmButtonText="返回"
  996. cancelButtonText="确认"
  997. showCancelButton
  998. onConfirm={() => {
  999. state.submitStatus = false
  1000. }}
  1001. onCancel={async () => {
  1002. onRegisterUser()
  1003. }}
  1004. />
  1005. {/* 学生在读 */}
  1006. <ODialog
  1007. title="提示"
  1008. v-model:show={state.studentReadStatus}
  1009. message={'您已在其他乐团在读,请退团再报名该乐团。'}
  1010. confirmButtonText="确认"
  1011. showCancelButton={false}
  1012. onConfirm={() => {
  1013. // state.submitStatus = false
  1014. onLoutout()
  1015. }}
  1016. />
  1017. <ODialog
  1018. title="提示"
  1019. v-model:show={state.dialogStatus}
  1020. message={state.dialogMessage}
  1021. confirmButtonText={state.dialogOrchestraStatus ? '确认取消' : '继续支付'}
  1022. cancelButtonText="取消订单"
  1023. showCancelButton={!state.dialogOrchestraStatus}
  1024. messageAlign={state.dialogOrchestraStatus ? 'left' : 'center'}
  1025. onConfirm={async () => {
  1026. if (state.dialogOrchestraStatus) {
  1027. try {
  1028. await request.post(
  1029. '/api-student/userPaymentOrder/cancelPayment/' + state.dialogConfig.orderNo
  1030. )
  1031. } catch {
  1032. //
  1033. }
  1034. } else {
  1035. const paymentConfig = state.dialogConfig.paymentConfig
  1036. router.push({
  1037. path: '/orderDetail',
  1038. query: {
  1039. pm: 1, // h5乐团报名
  1040. config: JSON.stringify({
  1041. ...paymentConfig.paymentConfig,
  1042. paymentType: paymentConfig.paymentType
  1043. }),
  1044. orderNo: paymentConfig.orderNo
  1045. }
  1046. })
  1047. }
  1048. }}
  1049. onCancel={async () => {
  1050. try {
  1051. await request.post(
  1052. '/api-student/userPaymentOrder/cancelPayment/' + state.dialogConfig.orderNo
  1053. )
  1054. } catch {
  1055. //
  1056. }
  1057. }}
  1058. />
  1059. </div>
  1060. )
  1061. }
  1062. })