index.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. import { defineComponent, onMounted, reactive, ref } from 'vue';
  2. import styles from './index.module.less';
  3. import MHeader from '@/components/m-header';
  4. import { Area, Button, CellGroup, Field, Form, Popup, showToast } from 'vant';
  5. import icon_school from './images/icon_school.png';
  6. import icon_person from './images/icon_person.png';
  7. import icon_submit from './images/icon_submit.png';
  8. import icon_logo from './images/logo.png';
  9. import icon_p1 from './images/icon_p1.png';
  10. import icons from './images/icons.json';
  11. import {
  12. api_openSendSms,
  13. api_schoolAdd,
  14. api_schoolSave,
  15. api_sysAreaQueryAllProvince
  16. } from './api';
  17. import { useRoute } from 'vue-router';
  18. import MImgCode from '@/components/m-img-code';
  19. import { browser } from '@/helpers/utils';
  20. export default defineComponent({
  21. name: 'SchoolRegister',
  22. setup() {
  23. const route = useRoute();
  24. const browserInfo = browser();
  25. const formOptions = {
  26. /** 性质 */
  27. nature: [
  28. { label: '公立', value: 'PUBLIC' },
  29. { label: '私立', value: 'PRIVATE' }
  30. ],
  31. types: [
  32. { label: '小学', value: 'PRIMARY' },
  33. { label: '初中', value: 'JUNIOR' },
  34. { label: '小初一体', value: 'PRIMARY_JUNIOR' }
  35. ],
  36. grades: [
  37. { label: '六年制', value: 'SIX_YEAR_SYSTEM' },
  38. { label: '五年制', value: 'FIVE_YEAR_SYSTEM' }
  39. ],
  40. grades1: [
  41. { label: '三年制', value: 'THREE_YEAR_SYSTEM' },
  42. { label: '四年制', value: 'FORE_YEAR_SYSTEM' }
  43. ],
  44. genaral: [
  45. { label: '男', value: '1' },
  46. { label: '女', value: '0' }
  47. ]
  48. };
  49. const forms = reactive({
  50. id: '',
  51. name: '', // 学校名称
  52. regionCode: '', // 所属区域
  53. cityCode: '', // 所属城市
  54. provinceCode: '', // 所属省份
  55. schoolNature: 'PUBLIC' as 'PUBLIC' | 'PRIVATE' | string, // 学校性质
  56. schoolType: 'PRIMARY' as 'PRIMARY' | 'JUNIOR' | 'PRIMARY_JUNIOR' | string, // 学校类型
  57. gradeYear: 'SIX_YEAR_SYSTEM' as
  58. | 'FIVE_YEAR_SYSTEM'
  59. | 'SIX_YEAR_SYSTEM'
  60. | string, // 学年制
  61. emergencyContact: '', // 校长姓名
  62. emergencyContactPhone: '', // 校长联系方式
  63. educationalAdministrationUsername: '', // 负责人姓名
  64. educationalAdministrationPhone: '', // 负责人联系方式
  65. genaral: '1', // 性别
  66. code: '', // 验证码
  67. buyGoods: true, // 是否购买商品
  68. tenantId: route.query.id || '', // 机构
  69. sourceForm: 'TEACHER'
  70. });
  71. const data = reactive({
  72. cityName: '', // 所属城市
  73. showArea: false,
  74. success: false,
  75. areaList: {} as any,
  76. sendMsg: '发送验证码',
  77. imgCodeStatus: false,
  78. inputFouce: 0
  79. });
  80. const formateArea = (area: any[]) => {
  81. const province_list: { [_: string]: string } = {};
  82. const city_list: { [_: string]: string } = {};
  83. const county_list: { [_: string]: string } = {};
  84. area.forEach((item: any) => {
  85. province_list[item.code] = item.name;
  86. });
  87. area.forEach((item: any) => {
  88. item.areas?.forEach((city: any) => {
  89. city_list[city.code] = city.name;
  90. });
  91. });
  92. area.forEach((item: any) => {
  93. item.areas?.forEach((city: any) => {
  94. city.areas?.forEach((county: any) => {
  95. county_list[county.code] = county.name;
  96. });
  97. });
  98. });
  99. return {
  100. province_list,
  101. city_list,
  102. county_list
  103. };
  104. };
  105. const getAreaList = () => {
  106. api_sysAreaQueryAllProvince().then(res => {
  107. if (res?.code === 200) {
  108. data.areaList = formateArea(res.data);
  109. console.log('🚀 ~ data.areaList:', data.areaList);
  110. }
  111. });
  112. };
  113. onMounted(() => {
  114. getAreaList();
  115. });
  116. /** 发送验证码 */
  117. const onSendSms = async () => {
  118. if (data.sendMsg.includes('s')) return;
  119. onCountDown();
  120. showToast('验证码已发送');
  121. };
  122. const onCountDown = () => {
  123. data.sendMsg = '60s';
  124. let count = 60;
  125. let timer = setInterval(() => {
  126. count--;
  127. data.sendMsg = `${count}s`;
  128. if (count <= 0) {
  129. data.sendMsg = '重新发送';
  130. clearInterval(timer);
  131. }
  132. }, 1000);
  133. };
  134. const formRef = ref();
  135. const handleSubmit = () => {
  136. forms.name = forms.name.trim();
  137. formRef.value
  138. .validate()
  139. .then(async () => {
  140. if (!forms.code) {
  141. showToast('请输入验证码');
  142. return;
  143. }
  144. const res = await api_schoolSave({ ...forms });
  145. if (res?.code === 200) {
  146. if (res?.data?.id) {
  147. forms.id = res.data.id;
  148. }
  149. data.success = true;
  150. }
  151. })
  152. .catch((err: any) => {
  153. console.log('🚀 ~ err:', err);
  154. formRef.value.scrollToField(err[0].name);
  155. });
  156. return;
  157. };
  158. return () => (
  159. <div class={styles.container}>
  160. <div class={styles.containerBg}>
  161. <img class={styles.titleIcon} src={icon_logo} />
  162. <div class={styles.title}>{route.query.name}</div>
  163. <div class={styles.tagWrap}>
  164. <div class={styles.tag}>
  165. <span>·</span> 音乐数字课堂学校登记 <span>·</span>
  166. </div>
  167. </div>
  168. <div class={styles.contentWrap}>
  169. <div class={styles.content}>
  170. <Form ref={formRef}>
  171. <CellGroup class={styles.group}>
  172. <img src={icon_school} class={styles.icon} />
  173. <Field
  174. border
  175. name="name"
  176. label="学校全称"
  177. rows="1"
  178. autosize
  179. // type="textarea"
  180. placeholder="请输入学校全称"
  181. inputAlign="right"
  182. v-model={forms.name}
  183. onUpdate:modelValue={(val: string) => {
  184. forms.name = val.trim();
  185. }}
  186. autocomplete="off"
  187. maxlength={20}
  188. rules={[
  189. {
  190. required: true,
  191. message: '请输入学校全称',
  192. trigger: 'onBlur'
  193. }
  194. ]}
  195. onFocus={() => {
  196. data.inputFouce++;
  197. }}
  198. />
  199. <Field
  200. isLink
  201. border
  202. name="cityName"
  203. label="所属城市"
  204. placeholder="请选择"
  205. readonly
  206. inputAlign="right"
  207. v-model={data.cityName}
  208. onClick={() => {
  209. if (browserInfo.ios && data.inputFouce < 2) {
  210. data.inputFouce++;
  211. setTimeout(() => {
  212. data.showArea = true;
  213. }, 700);
  214. } else {
  215. data.showArea = true;
  216. }
  217. }}
  218. rules={[{ required: true, message: '请选择' }]}></Field>
  219. <Field center border name="schoolNature" label="办学性质">
  220. {{
  221. input: () => (
  222. <>
  223. {formOptions.nature.map(item => {
  224. return (
  225. <Button
  226. class={styles.radio}
  227. size="small"
  228. color={
  229. item.value === forms.schoolNature
  230. ? '#198CFE'
  231. : ''
  232. }
  233. onClick={() =>
  234. (forms.schoolNature = item.value)
  235. }>
  236. {item.label}
  237. </Button>
  238. );
  239. })}
  240. </>
  241. )
  242. }}
  243. </Field>
  244. <Field center border label="学校类型" labelWidth="70px">
  245. {{
  246. input: () => (
  247. <>
  248. {formOptions.types.map(item => {
  249. return (
  250. <Button
  251. class={styles.radio}
  252. size="small"
  253. color={
  254. item.value === forms.schoolType
  255. ? '#198CFE'
  256. : ''
  257. }
  258. onClick={() => {
  259. forms.schoolType = item.value;
  260. forms.gradeYear =
  261. item.value === 'PRIMARY_JUNIOR'
  262. ? 'NINE_YEAR_SYSTEM'
  263. : item.value === 'JUNIOR'
  264. ? 'THREE_YEAR_SYSTEM'
  265. : 'SIX_YEAR_SYSTEM';
  266. }}>
  267. {item.label}
  268. </Button>
  269. );
  270. })}
  271. </>
  272. )
  273. }}
  274. </Field>
  275. {forms.schoolType ===
  276. 'PRIMARY_JUNIOR' ? null : forms.schoolType === 'JUNIOR' ? (
  277. <Field center border label="学年制">
  278. {{
  279. input: () => (
  280. <>
  281. {formOptions.grades1.map(item => {
  282. return (
  283. <Button
  284. class={styles.radio}
  285. size="small"
  286. color={
  287. item.value === forms.gradeYear
  288. ? '#198CFE'
  289. : ''
  290. }
  291. onClick={() =>
  292. (forms.gradeYear = item.value)
  293. }>
  294. {item.label}
  295. </Button>
  296. );
  297. })}
  298. </>
  299. )
  300. }}
  301. </Field>
  302. ) : (
  303. <Field center border label="学年制">
  304. {{
  305. input: () => (
  306. <>
  307. {formOptions.grades.map(item => {
  308. return (
  309. <Button
  310. class={styles.radio}
  311. size="small"
  312. color={
  313. item.value === forms.gradeYear
  314. ? '#198CFE'
  315. : ''
  316. }
  317. onClick={() =>
  318. (forms.gradeYear = item.value)
  319. }>
  320. {item.label}
  321. </Button>
  322. );
  323. })}
  324. </>
  325. )
  326. }}
  327. </Field>
  328. )}
  329. </CellGroup>
  330. <CellGroup class={styles.group}>
  331. <img src={icon_person} class={styles.icon} />
  332. <Field
  333. border
  334. name="emergencyContact"
  335. label="校长姓名"
  336. placeholder="请输入校长姓名"
  337. inputAlign="right"
  338. maxlength={14}
  339. v-model={forms.emergencyContact}
  340. onUpdate:modelValue={(val: string) => {
  341. forms.emergencyContact = val.trim();
  342. }}
  343. rules={[{ required: true, message: '请输入校长姓名' }]}
  344. />
  345. <Field
  346. border
  347. name="emergencyContactPhone"
  348. label="校长联系方式"
  349. maxlength={11}
  350. placeholder="请输入校长手机号码"
  351. inputAlign="right"
  352. v-model={forms.emergencyContactPhone}
  353. rules={[
  354. { required: true, message: '请输入校长手机号码' },
  355. {
  356. pattern: /^1[3456789]\d{9}$/,
  357. message: '请输入正确的手机号码'
  358. }
  359. ]}
  360. />
  361. <Field
  362. border
  363. name="educationalAdministrationUsername"
  364. label="负责人姓名"
  365. placeholder="请输入负责人姓名"
  366. inputAlign="right"
  367. maxlength={14}
  368. v-model={forms.educationalAdministrationUsername}
  369. onUpdate:modelValue={(val: string) => {
  370. forms.educationalAdministrationUsername = val.trim();
  371. }}
  372. rules={[
  373. {
  374. required: true,
  375. message: '请输入负责人姓名',
  376. trigger: 'onBlur'
  377. }
  378. ]}
  379. />
  380. <Field
  381. border
  382. name="educationalAdministrationPhone"
  383. label="负责人联系方式"
  384. labelWidth="40%"
  385. inputAlign="right"
  386. placeholder="请输入负责人手机号码"
  387. maxlength={11}
  388. v-model={forms.educationalAdministrationPhone}
  389. rules={[
  390. { required: true, message: '请输入负责人手机号码' },
  391. {
  392. pattern: /^1[3456789]\d{9}$/,
  393. message: '请输入正确的手机号码'
  394. }
  395. ]}
  396. />
  397. <Field center border label="性别">
  398. {{
  399. input: () => (
  400. <>
  401. {formOptions.genaral.map(item => {
  402. return (
  403. <Button
  404. class={styles.radio}
  405. size="small"
  406. color={
  407. item.value === forms.genaral ? '#198CFE' : ''
  408. }
  409. onClick={() => (forms.genaral = item.value)}>
  410. {item.label}
  411. </Button>
  412. );
  413. })}
  414. </>
  415. )
  416. }}
  417. </Field>
  418. <Field
  419. class={styles.codeWrap}
  420. center
  421. border
  422. name="code"
  423. label="验证码"
  424. labelWidth={70}
  425. placeholder="请输入验证码"
  426. v-model={forms.code}
  427. onUpdate:modelValue={(val: string) => {
  428. forms.code = val.trim();
  429. }}
  430. maxlength={6}>
  431. {{
  432. button: () => (
  433. <Button
  434. disabled={data.sendMsg.includes('s')}
  435. class={styles.sendBtn}
  436. size="small"
  437. type="primary"
  438. color="#198CFE"
  439. onClick={() => {
  440. if (!forms.educationalAdministrationPhone) {
  441. showToast('请输入负责人手机号码');
  442. return;
  443. }
  444. if (
  445. !/^1[3456789]\d{9}$/.test(
  446. forms.educationalAdministrationPhone
  447. )
  448. ) {
  449. showToast('手机号码格式不正确');
  450. return;
  451. }
  452. data.imgCodeStatus = true;
  453. }}>
  454. {data.sendMsg}
  455. </Button>
  456. )
  457. }}
  458. </Field>
  459. <div style={{ padding: '10px 16px' }}>
  460. <div class={styles.tips}>
  461. 负责人即为该学校音乐数字课堂老师端管理员,手机号即为音乐数字课堂老师端账号,默认密码为:yyszkt+手机号后四位
  462. </div>
  463. </div>
  464. </CellGroup>
  465. <Button
  466. class={styles.submit}
  467. round
  468. block
  469. onClick={() => handleSubmit()}>
  470. <img class={styles.submitIcon} src={icon_submit} />
  471. </Button>
  472. </Form>
  473. <Popup v-model:show={data.showArea} position="bottom">
  474. <Area
  475. areaList={data.areaList}
  476. onCancel={() => (data.showArea = false)}
  477. onConfirm={({ selectedOptions }) => {
  478. forms.provinceCode = selectedOptions[0].value;
  479. forms.cityCode = selectedOptions[1].value;
  480. forms.regionCode = selectedOptions[2].value;
  481. data.cityName = selectedOptions
  482. .map((item: any) => item.text)
  483. .join('-');
  484. data.showArea = false;
  485. }}
  486. />
  487. </Popup>
  488. <Popup
  489. class="popup-custom van-scale"
  490. transition="van-scale"
  491. closeOnClickOverlay={false}
  492. v-model:show={data.success}>
  493. <div class={styles.successWrap}>
  494. <img class={styles.p1} src={icon_p1} />
  495. <img class={styles.p2} src={icons.icon_p2} />
  496. <div class={styles.btnWrap}>
  497. <div class={styles.btnTitle}>您已成功登记</div>
  498. <div class={styles.btnDes}>欢迎您使用音乐数字课堂~</div>
  499. <Button
  500. class={styles.btn}
  501. type="primary"
  502. round
  503. onClick={() => (data.success = false)}>
  504. 我知道了
  505. </Button>
  506. </div>
  507. </div>
  508. </Popup>
  509. {data.imgCodeStatus ? (
  510. <MImgCode
  511. v-model:value={data.imgCodeStatus}
  512. clientId="cooleshow-teacher"
  513. type="RESISTER"
  514. phone={forms.educationalAdministrationPhone}
  515. onClose={() => {
  516. data.imgCodeStatus = false;
  517. }}
  518. onSendCode={onSendSms}
  519. />
  520. ) : null}
  521. </div>
  522. </div>
  523. </div>
  524. </div>
  525. );
  526. }
  527. });