preview-index.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. import { defineComponent, nextTick, onMounted, onUnmounted, reactive, ref } 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. Cell,
  15. CellGroup,
  16. Field,
  17. Form,
  18. Radio,
  19. RadioGroup,
  20. Tag,
  21. showToast,
  22. Image,
  23. Checkbox
  24. } from 'vant'
  25. import { useRoute, useRouter } from 'vue-router'
  26. import { moneyFormat } from '@/helpers/utils'
  27. import request from '@/helpers/request'
  28. // 乐团交付,乐团停止或关闭,有新的交付团;则不允许报名
  29. const classList: any = []
  30. for (let i = 1; i <= 40; i++) {
  31. classList.push({ text: i + '班', value: i })
  32. }
  33. export default defineComponent({
  34. name: 'pre-goods-apply',
  35. setup() {
  36. const route = useRoute()
  37. // 获取是否已经看过训练工具图片
  38. const state = reactive({
  39. code: '' as any, // 微信授权code码
  40. detail: {} as any, // 学员详情
  41. toolImgStatus: false,
  42. currentGrade: [
  43. { text: '一年级', value: 1 },
  44. { text: '二年级', value: 2 },
  45. { text: '三年级', value: 3 },
  46. { text: '四年级', value: 4 },
  47. { text: '五年级', value: 5 },
  48. { text: '六年级', value: 6 },
  49. { text: '七年级', value: 7 },
  50. { text: '八年级', value: 8 },
  51. { text: '九年级', value: 9 }
  52. ], // 年级数组列表
  53. classList: classList,
  54. subjectList: [] as any, // 选报声部列表
  55. instrumentsInspectionDescribe: '', // 配置信息
  56. inspectPopupStatus: false, // 查看说明
  57. gradeStatus: false,
  58. classStatus: false,
  59. subjectStatus: false,
  60. registerInfo: {} as any, // 乐团信息
  61. goodsInfo: {} as any, // 商品
  62. textBookInfo: {} as any, // 教材
  63. inspectInfo: {} as any, // 乐器检查
  64. vipYearInfo: {} as any, // 学练工具
  65. inspectStatus: true,
  66. // 是否开启微信登录(测试使用)默认为false
  67. testIsWeixin: false,
  68. details: [] as any, //
  69. pattern: /^1(3|4|5|6|7|8|9)\d{9}$/,
  70. nameReg: /^[\u4E00-\u9FA5]+$/,
  71. paymentType: '',
  72. musicPaymentType: '', // 乐团中对应支付方式
  73. studentReadStatus: false, // 学生在读
  74. dialogStatus: false,
  75. dialogMessage: '',
  76. dialogOrchestraStatus: false, // 是否为不同的乐团
  77. dialogConfig: {} as any,
  78. orderInfo: {
  79. needPrice: 0,
  80. originalPrice: 0
  81. },
  82. submitStatus: false
  83. })
  84. const forms = reactive({
  85. username: null,
  86. sex: null as any,
  87. currentGrade: null,
  88. currentGradeTxt: null, // 年级编号
  89. currentClass: '', // 班级
  90. currentClassTxt: null, // 年级编号
  91. registerSubjectId: '',
  92. registerSubjectTxt: null, // 所在选报声部
  93. parentName: null,
  94. groupBuyType: '' as any,
  95. phone: null as any,
  96. learningTools: null,
  97. instrumentsBrand: null
  98. })
  99. const otherParams = reactive({
  100. toolPlan: {
  101. title: '', // 训练工具准备
  102. groupTitle: '', // 团购标题
  103. groupDesc: '', // 团购文案
  104. selfTitle: '', // 自备标题
  105. selfDesc: '' // 自备文案
  106. },
  107. leBao: {
  108. // 乐器保障
  109. show: 1, // 是否显示
  110. selected: 1 // 是否选择
  111. }
  112. })
  113. const validator = (val: any) => {
  114. // 校验函数返回 true 表示校验通过,false 表示不通过
  115. return state.nameReg.test(val) && val.length >= 2 && val.length <= 15
  116. }
  117. const message = (value: any) => {
  118. if (!value) {
  119. return '请填写学员真实姓名'
  120. } else if (!state.nameReg.test(value)) {
  121. return '学员姓名必须为中文'
  122. } else if (value.length < 2 || value.length > 15) {
  123. return '学员姓名必须为2~15个字'
  124. } else {
  125. return ''
  126. }
  127. }
  128. // 乐团报名
  129. const onSubmit = async () => {
  130. //
  131. }
  132. const messageContent = ref('')
  133. const orchestraName = ref(route.query.orchestraName || '') // 乐团名称
  134. const getMessage = (ev: any) => {
  135. if (ev.data.api === 'payment-notes') {
  136. const result = ev.data.data ? JSON.parse(ev.data.data) : {}
  137. console.log(result, 'result')
  138. messageContent.value = result.notice
  139. otherParams.toolPlan = result.toolPlan
  140. otherParams.leBao = result.leBao
  141. // document.body
  142. }
  143. }
  144. // 获取入选声部信息
  145. const getSubjects = async () => {
  146. try {
  147. const subjects = await request.post(
  148. '/api-student/open/orchestraSubjectConfig/pageByOrchestraId',
  149. {
  150. data: {
  151. orchestraId: route.query.id,
  152. page: 1,
  153. rows: 100
  154. }
  155. }
  156. )
  157. const rows = subjects.data.rows || []
  158. rows.forEach((item: any) => {
  159. state.subjectList.push({
  160. text: item.name,
  161. value: item.subjectId
  162. })
  163. })
  164. if (rows.length > 0) {
  165. forms.registerSubjectId = rows[0].subjectId // detail.registerSubjectId
  166. forms.registerSubjectTxt = rows[0].name
  167. registerGoods()
  168. }
  169. } catch {
  170. //
  171. }
  172. }
  173. // 获取商品信息
  174. const registerGoods = async () => {
  175. try {
  176. const { data } = await request.get(
  177. '/api-student/orchestraRegister/registerGoods/' + route.query.id,
  178. {
  179. params: {
  180. subjectId: forms.registerSubjectId
  181. }
  182. }
  183. )
  184. state.musicPaymentType = data.paymentServiceProvider || ''
  185. // 初始化数据商品数据
  186. const details = data.details || []
  187. details.forEach((item: any) => {
  188. if (item.goodsType === 'INSTRUMENTS') {
  189. const img = item.goodsUrl ? item.goodsUrl.split(',')[0] : ''
  190. state.goodsInfo = { ...item, goodsUrl: img }
  191. state.instrumentsInspectionDescribe = item.instrumentsInspectionDescribe
  192. } else if (item.goodsType === 'TEXTBOOK') {
  193. const img = item.goodsUrl ? item.goodsUrl.split(',')[0] : ''
  194. state.textBookInfo = { ...item, goodsUrl: img }
  195. } else if (item.goodsType === 'INSTRUMENT_INSPECT') {
  196. state.inspectInfo = { ...item }
  197. } else if (item.goodsType === 'VIP_YEAR') {
  198. state.vipYearInfo = { ...item }
  199. }
  200. state.details = details
  201. })
  202. } catch {
  203. //
  204. }
  205. }
  206. onMounted(async () => {
  207. await getSubjects()
  208. nextTick(() => {
  209. // 是否加载完成
  210. window.parent &&
  211. window.parent.postMessage(
  212. {
  213. api: 'onLoad',
  214. status: true
  215. },
  216. '*'
  217. )
  218. })
  219. window.addEventListener('message', getMessage)
  220. })
  221. onUnmounted(() => {
  222. window.removeEventListener('message', getMessage)
  223. })
  224. return () => (
  225. <div class={styles.goodsApply}>
  226. <img src={headBg} class={styles.headBg} />
  227. <div class={styles.goodsHeader}>
  228. <div class={styles.orchestraTitle}>
  229. <img class={styles.headTitle} src={headTitle} />
  230. <p class={[styles.name, 'van-multi-ellipsis--l3']}>{orchestraName.value}</p>
  231. </div>
  232. <img src={headPhone} class={styles.headPhone} />
  233. </div>
  234. <Form
  235. validateFirst
  236. errorMessageAlign="right"
  237. scrollToError={true}
  238. onSubmit={onSubmit}
  239. onFailed={(e: any) => {
  240. // 获取第一个校验错误的元素
  241. nextTick(() => {
  242. const isError = document.getElementsByClassName('van-field__error-message')
  243. // 滚动到错误元素对应位置
  244. isError[0]?.scrollIntoView({
  245. block: 'center',
  246. behavior: 'smooth'
  247. })
  248. })
  249. }}
  250. ref="form"
  251. class={styles.form}
  252. >
  253. <CellGroup class={styles.applyCellGroup} border={false}>
  254. <div class={[styles.title, styles.titleApply]}></div>
  255. <Field
  256. required
  257. label="学员信息"
  258. placeholder="请填写学员真实姓名"
  259. inputAlign="right"
  260. v-model={forms.username}
  261. maxlength={15}
  262. rules={[{ validator, message }]}
  263. />
  264. <Field
  265. required
  266. label="性别"
  267. inputAlign="right"
  268. rules={[{ required: true, message: '请选择性别' }]}
  269. >
  270. {{
  271. input: () => (
  272. <RadioGroup v-model={forms.sex}>
  273. <Tag
  274. size="large"
  275. type="primary"
  276. class={[styles.radioSection, forms.sex === 1 ? styles.active : '']}
  277. >
  278. <Radio class={styles.radioItem} name={1}></Radio>
  279. 男生
  280. </Tag>
  281. <Tag
  282. size="large"
  283. type="primary"
  284. class={[styles.radioSection, forms.sex === 0 ? styles.active : '']}
  285. >
  286. <Radio class={styles.radioItem} name={0}></Radio>女生
  287. </Tag>
  288. </RadioGroup>
  289. )
  290. }}
  291. </Field>
  292. <Field
  293. required
  294. label="年级"
  295. inputAlign="right"
  296. readonly
  297. isLink
  298. clickable={false}
  299. placeholder="请选择年级"
  300. v-model={forms.currentGradeTxt}
  301. onClick={() => (state.gradeStatus = true)}
  302. rules={[{ required: true, message: '请选择年级' }]}
  303. />
  304. <Field
  305. required
  306. label="班级"
  307. inputAlign="right"
  308. readonly
  309. isLink
  310. clickable={false}
  311. placeholder="请选择班级"
  312. v-model={forms.currentClassTxt}
  313. onClick={() => (state.classStatus = true)}
  314. rules={[{ required: true, message: '请选择班级' }]}
  315. />
  316. <Field
  317. required
  318. label="选报声部"
  319. inputAlign="right"
  320. readonly
  321. isLink
  322. clickable={false}
  323. placeholder="请选择选报声部"
  324. v-model={forms.registerSubjectTxt}
  325. onClick={() => {
  326. if (state.subjectList.length <= 0) {
  327. showToast('暂无报名选报声部')
  328. return
  329. }
  330. state.subjectStatus = true
  331. }}
  332. rules={[{ required: true, message: '请选择选报声部' }]}
  333. />
  334. </CellGroup>
  335. <CellGroup class={styles.applyCellGroup} border={false}>
  336. <div class={[styles.title, styles.titleParent]}></div>
  337. <Field
  338. required
  339. label="家长姓名"
  340. inputAlign="right"
  341. placeholder="请填写家长真实姓名"
  342. v-model={forms.parentName}
  343. maxlength={15}
  344. // rules={[{ required: true, message: '请填写家长真实姓名' }]}
  345. />
  346. <Field
  347. required
  348. label="手机号"
  349. inputAlign="right"
  350. placeholder="请输入手机号"
  351. v-model={forms.phone}
  352. maxlength={11}
  353. type="tel"
  354. // rules={[{ pattern: state.pattern, message: '输入监护人手机号码有误' }]}
  355. />
  356. </CellGroup>
  357. <CellGroup class={styles.applyCellGroup} border={false}>
  358. <div class={[styles.title, styles.titleTips]}></div>
  359. <div class={styles.tipsContainer} v-html={messageContent.value}>
  360. {/* <p class={styles.line}>
  361. <i class={[styles.num, styles.numOne]}></i>
  362. 为保障乐团训练质量,开训前,家长务必准备好①乐器和②管乐AI学练工具,准备方式不限;
  363. </p>
  364. <p class={styles.line}>
  365. <i class={[styles.num, styles.numTwo]}></i>
  366. 为了降低家长投入压力,可参与项目支持方提供的AI学练工具团购,政策如下:
  367. <p class={[styles.child]}>
  368. 1、支持方<span style="color: #F67146">免费</span>
  369. 提供一支全新乐器供学生训练(可带回家) ;<br />
  370. 2、乐器提供时间为一年,如期满继续训练,乐器将
  371. <span style="color: #F67146">赠送</span>至学生以作鼓励;
  372. <br />
  373. 3、如期间或期满不再训练,家长归还乐器即可,
  374. <span style="color: #F67146">无需额外支付</span>乐器费用。
  375. </p>
  376. </p> */}
  377. </div>
  378. </CellGroup>
  379. <CellGroup class={styles.applyCellGroup} border={false}>
  380. <div class={[styles.title, styles.titleTool]}></div>
  381. <Field
  382. required
  383. label={otherParams.toolPlan.title}
  384. labelAlign="top"
  385. rules={[{ required: true, message: '请选择训练工具的准备方式' }]}
  386. >
  387. {{
  388. input: () => (
  389. <RadioGroup v-model={forms.groupBuyType} class={styles.toolRadioGroup}>
  390. <Tag
  391. size="large"
  392. type="primary"
  393. class={[
  394. styles.radioSectionTag,
  395. styles.radioSection,
  396. forms.groupBuyType === 'GROUP_BUY' ? styles.active : ''
  397. ]}
  398. >
  399. <Radio
  400. class={styles.radioItem}
  401. name={'GROUP_BUY'}
  402. disabled={true}
  403. onClick={() => {
  404. // if (!forms.registerSubjectId) {
  405. // showToast('请选择选报声部')
  406. // return
  407. // }
  408. }}
  409. ></Radio>
  410. {otherParams.toolPlan.groupTitle}
  411. <p class={styles.radioTip}>{otherParams.toolPlan.groupDesc}</p>
  412. </Tag>
  413. <Tag
  414. size="large"
  415. type="primary"
  416. class={[
  417. styles.radioSectionTag,
  418. styles.radioSection,
  419. forms.groupBuyType === 'SELF' ? styles.active : ''
  420. ]}
  421. >
  422. <Radio class={styles.radioItem} name={'SELF'} disabled></Radio>
  423. {otherParams.toolPlan.selfTitle}
  424. <p class={styles.radioTip}>{otherParams.toolPlan.selfDesc}</p>
  425. </Tag>
  426. </RadioGroup>
  427. )
  428. }}
  429. </Field>
  430. </CellGroup>
  431. <CellGroup class={[styles.applyCellGroup, styles.groupBuy]} border={false}>
  432. <div class={[styles.title, styles.titleIntrumentTool]}></div>
  433. <Cell border={false}>
  434. {{
  435. icon: () => <Image src={state.vipYearInfo.goodsUrl} class={styles.goodsImg} />,
  436. value: () => (
  437. <div class={styles.vipYearInfo}>
  438. <div class={styles.goodsTitle}>
  439. {state.vipYearInfo.goodsName} <img src={icon12} />
  440. </div>
  441. <p class={styles.goodsTips}>乐团首次训练之日起生效</p>
  442. <p class={[styles.goodsMemo, 'van-multi-ellipsis--l2']}>
  443. {state.vipYearInfo.description}
  444. </p>
  445. <div class={styles.goodsPrice}>
  446. <div class={styles.priceGroup}>
  447. 团购价:
  448. <p>
  449. <span>¥</span> {moneyFormat(state.vipYearInfo.currentPrice)}
  450. </p>
  451. </div>
  452. </div>
  453. </div>
  454. )
  455. }}
  456. </Cell>
  457. <Cell border={false}>
  458. {{
  459. icon: () => <Image src={state.goodsInfo.goodsUrl} class={styles.goodsImg} />,
  460. value: () => (
  461. <div class={styles.goodsInfo}>
  462. <div class={styles.goodsTitle}>
  463. {state.goodsInfo.goodsName}
  464. {state.goodsInfo.currentPrice <= 0 ? <img src={iconFree} /> : ''}
  465. </div>
  466. <p class={[styles.goodsMemo, 'van-multi-ellipsis--l2']}>
  467. {state.goodsInfo.description}
  468. </p>
  469. <div class={styles.goodsPrice}>
  470. <div class={styles.priceGroup}>
  471. 团购价:
  472. <p>
  473. <del>
  474. <span>¥</span> {moneyFormat(state.goodsInfo.groupPrice)}
  475. </del>
  476. </p>
  477. </div>
  478. </div>
  479. </div>
  480. )
  481. }}
  482. </Cell>
  483. {otherParams.leBao.show ? (
  484. <Cell
  485. class={styles.inspectCell}
  486. style={{ backgroundColor: state.inspectStatus ? '#FFF3EA' : '#f4f4f4' }}
  487. >
  488. {{
  489. icon: () => (
  490. <img
  491. src={iconBao}
  492. class={styles.iconBao}
  493. onClick={() => {
  494. if (state.instrumentsInspectionDescribe) state.inspectPopupStatus = true
  495. }}
  496. />
  497. ),
  498. value: () => (
  499. <div class={styles.baoContainer}>
  500. <div
  501. class={styles.baoTitle}
  502. onClick={() => {
  503. if (state.instrumentsInspectionDescribe) state.inspectPopupStatus = true
  504. }}
  505. >
  506. 下校检查乐器 1-2次/学期
  507. </div>
  508. <div class={styles.baoPrice}>
  509. <p
  510. // onClick={() => {
  511. // state.inspectStatus = !state.inspectStatus
  512. // calcPrice()
  513. // }}
  514. >
  515. <span class={styles.prefix}>¥</span> {state.inspectInfo.currentPrice}
  516. <span class={styles.suffix}>/年</span>
  517. </p>
  518. <Checkbox
  519. v-model={otherParams.leBao.selected}
  520. // onClick={() => {
  521. // calcPrice()
  522. // }}
  523. >
  524. {{
  525. icon: (props: any) => (
  526. <img
  527. class={styles.checkboxImg}
  528. src={props.checked ? iconCheckboxActive : iconCheckbox}
  529. />
  530. )
  531. }}
  532. </Checkbox>
  533. </div>
  534. </div>
  535. )
  536. }}
  537. </Cell>
  538. ) : (
  539. ''
  540. )}
  541. </CellGroup>
  542. </Form>
  543. </div>
  544. )
  545. }
  546. })