index.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. import OHeader from '@/components/o-header'
  2. import OQrcode from '@/components/o-qrcode'
  3. import OSearch from '@/components/o-search'
  4. import OSticky from '@/components/o-sticky'
  5. import {
  6. ActionSheet,
  7. Cell,
  8. CellGroup,
  9. closeToast,
  10. Grid,
  11. GridItem,
  12. Icon,
  13. Image,
  14. List,
  15. Picker,
  16. Popup,
  17. showFailToast,
  18. showLoadingToast,
  19. showSuccessToast,
  20. showToast,
  21. Tag
  22. } from 'vant'
  23. import { defineComponent, onMounted, reactive } from 'vue'
  24. import styles from './index.module.less'
  25. import iconSaveImage from '@/school/orchestra/images/icon-save-image.png'
  26. import iconWechat from '@/school/orchestra/images/icon-wechat.png'
  27. import iconCallPhone from '@common/images/icon-call-phone.png'
  28. import iconCallMessage from '@common/images/icon-call-message.png'
  29. import iconTeacher from '@common/images/icon_teacher.png'
  30. import iconMessage from '@common/images/icon-message.png'
  31. import { useRouter } from 'vue-router'
  32. import request from '@/helpers/request'
  33. import { state } from '@/state'
  34. import OEmpty from '@/components/o-empty'
  35. import { manageTeacherType } from '@/constant'
  36. import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
  37. import html2canvas from 'html2canvas'
  38. import { forms } from '../train-planning/create'
  39. export default defineComponent({
  40. name: 'companion-teacher',
  41. setup() {
  42. const router = useRouter()
  43. const form = reactive({
  44. showMessage: false,
  45. showPopover: false,
  46. oPopover: false,
  47. showQrcode: false,
  48. schoolName: null,
  49. schoolId: null,
  50. url: null as any,
  51. subjectList: [{ text: '全部声部', value: 'ALL' }] as any,
  52. list: [] as any,
  53. listState: {
  54. dataShow: true, // 判断是否有数据
  55. loading: false,
  56. finished: false
  57. },
  58. subjectText: '全部声部',
  59. statusText: '状态',
  60. params: {
  61. keyword: null,
  62. delFlag: null,
  63. subjectId: null,
  64. page: 1,
  65. rows: 20
  66. },
  67. selectItem: {} as any
  68. })
  69. const getSubjects = async () => {
  70. try {
  71. const { data } = await request.post('/api-school/subject/page', {
  72. data: {
  73. page: 1,
  74. rows: 50
  75. }
  76. })
  77. // console.log(data, 'data')
  78. const temp = data.rows || []
  79. temp.forEach((row: any) => {
  80. form.subjectList.push({
  81. text: row.name,
  82. value: row.id
  83. })
  84. })
  85. } catch {
  86. //
  87. }
  88. }
  89. // 获取当前用户所在的学校
  90. const getDetail = async (id: string | number) => {
  91. try {
  92. const res = await request.get('/api-school/schoolStaff/detail/' + id)
  93. form.schoolName = res.data.schoolName
  94. form.schoolId = res.data.schoolId
  95. form.url =
  96. location.origin +
  97. '/orchestra-school/#/companion-teacher-register?id=' +
  98. res.data.schoolId +
  99. '&name=' +
  100. res.data.schoolName
  101. } catch {
  102. //
  103. }
  104. }
  105. const getList = async () => {
  106. try {
  107. const res = await request.post('/api-school/teacher/page', {
  108. data: {
  109. ...form.params,
  110. schoolId: state.user.data.school.id
  111. }
  112. })
  113. form.listState.loading = false
  114. const result = res.data || {}
  115. // 处理重复请求数据
  116. if (form.list.length > 0 && result.current === 1) {
  117. return
  118. }
  119. const rows = result.rows || []
  120. rows.forEach((item: any) => {
  121. item.subjectNames = item.subjectName ? item.subjectName.split(',') : []
  122. })
  123. form.list = form.list.concat(rows)
  124. form.listState.finished = result.current >= result.pages
  125. form.params.page = result.current + 1
  126. form.listState.dataShow = form.list.length > 0
  127. } catch {
  128. form.listState.dataShow = false
  129. form.listState.finished = true
  130. }
  131. }
  132. const onSearch = () => {
  133. form.params.page = 1
  134. form.list = []
  135. form.listState.dataShow = true // 判断是否有数据
  136. form.listState.loading = false
  137. form.listState.finished = false
  138. getList()
  139. }
  140. // 详情
  141. const onDetail = (item: any) => {
  142. router.push({
  143. path: '/companion-teacher-detail',
  144. query: {
  145. id: item.id
  146. }
  147. })
  148. }
  149. // 选择声部
  150. const onConfirmSubject = (item: any) => {
  151. form.subjectText = item.selectedOptions[0].text
  152. form.params.subjectId =
  153. item.selectedOptions[0].value === 'ALL' ? null : item.selectedOptions[0].value
  154. form.showPopover = false
  155. onSearch()
  156. }
  157. const imgs = reactive({
  158. saveLoading: false,
  159. image: null as any,
  160. shareLoading: false
  161. })
  162. const onSaveImg = async () => {
  163. // 判断是否在保存中...
  164. if (imgs.saveLoading) {
  165. return
  166. }
  167. imgs.saveLoading = true
  168. // 判断是否已经生成图片
  169. if (imgs.image) {
  170. saveImg()
  171. } else {
  172. const container: any = document.getElementById(`preview-container`)
  173. html2canvas(container, {
  174. allowTaint: true,
  175. useCORS: true,
  176. backgroundColor: null
  177. })
  178. .then(async (canvas) => {
  179. const url = canvas.toDataURL('image/png')
  180. imgs.image = url
  181. saveImg()
  182. })
  183. .catch(() => {
  184. closeToast()
  185. imgs.saveLoading = false
  186. })
  187. }
  188. }
  189. const onShare = () => {
  190. if (imgs.shareLoading) {
  191. return
  192. }
  193. imgs.shareLoading = true
  194. if (imgs.image) {
  195. openShare()
  196. } else {
  197. const container: any = document.getElementById(`share-preview-container`)
  198. html2canvas(container, {
  199. allowTaint: true,
  200. useCORS: true,
  201. backgroundColor: null
  202. })
  203. .then(async (canvas) => {
  204. const url = canvas.toDataURL('image/png')
  205. imgs.image = url
  206. openShare()
  207. })
  208. .catch(() => {
  209. closeToast()
  210. imgs.shareLoading = false
  211. })
  212. }
  213. }
  214. const openShare = () => {
  215. const image = imgs.image
  216. setTimeout(() => {
  217. imgs.shareLoading = false
  218. }, 100)
  219. if (image) {
  220. postMessage(
  221. {
  222. api: 'shareTripartite',
  223. content: {
  224. title: '',
  225. desc: '',
  226. image,
  227. video: '',
  228. type: 'image',
  229. // button: ['copy']
  230. shareType: 'wechat'
  231. }
  232. },
  233. (res: any) => {
  234. if (res && res.content) {
  235. showToast(res.content.message || (res.content.status ? '分享成功' : '分享失败'))
  236. }
  237. }
  238. )
  239. }
  240. }
  241. const saveImg = async () => {
  242. showLoadingToast({ message: '图片生成中...', forbidClick: true })
  243. setTimeout(() => {
  244. imgs.saveLoading = false
  245. }, 100)
  246. const res = await promisefiyPostMessage({
  247. api: 'savePicture',
  248. content: {
  249. base64: imgs.image
  250. }
  251. })
  252. if (res?.content?.status === 'success') {
  253. showSuccessToast('保存成功')
  254. form.showQrcode = false
  255. } else {
  256. showFailToast('保存失败')
  257. }
  258. }
  259. onMounted(() => {
  260. getDetail(state.user.data.id)
  261. getSubjects()
  262. getList()
  263. })
  264. return () => (
  265. <>
  266. <OSticky position="top">
  267. <OHeader border={false}>
  268. {{
  269. right: () => <Icon name="plus" size={19} onClick={() => (form.showQrcode = true)} />
  270. }}
  271. </OHeader>
  272. <OSearch
  273. placeholder="请输入伴学老师姓名"
  274. inputBackground="white"
  275. background="#f6f8f9"
  276. onSearch={(val: any) => {
  277. form.params.keyword = val
  278. onSearch()
  279. }}
  280. />
  281. <div style={{ padding: '12px 13px 16px', background: '#F8F8F8' }}>
  282. <div class={styles.searchBand} onClick={() => (form.showPopover = true)}>
  283. {form.subjectText} <Icon name={form.showPopover ? 'arrow-up' : 'arrow-down'} />
  284. </div>
  285. <div
  286. class={styles.searchBand}
  287. style="margin-left: 16px"
  288. onClick={() => (form.oPopover = true)}
  289. >
  290. {form.statusText} <Icon name={form.oPopover ? 'arrow-up' : 'arrow-down'} />
  291. </div>
  292. </div>
  293. </OSticky>
  294. {form.listState.dataShow ? (
  295. <List
  296. v-model:loading={form.listState.loading}
  297. finished={form.listState.finished}
  298. finishedText=" "
  299. class={[styles.liveList]}
  300. onLoad={getList}
  301. immediateCheck={false}
  302. >
  303. {form.list.map((item: any) => (
  304. <CellGroup inset style={{ marginBottom: '12px' }} onClick={() => onDetail(item)}>
  305. <Cell center isLink class={styles.manageCell}>
  306. {{
  307. icon: () => (
  308. <Image class={styles.img} src={item.avatar ? item.avatar : iconTeacher} />
  309. ),
  310. title: () => (
  311. <div class={styles.teacherContent}>
  312. <div class={styles.content}>
  313. <p class={[styles.name, 'van-ellipsis']}>{item.nickname}</p>
  314. </div>
  315. <div class={styles.classNum}>
  316. <p class={styles.num}>
  317. {item.completedCourseScheduleNum || 0}/
  318. {item.totalCourseScheduleNum || 0}
  319. </p>
  320. <p class={styles.numText}>课时</p>
  321. </div>
  322. <div
  323. class={styles.message}
  324. onClick={(e: any) => {
  325. e.stopPropagation()
  326. e.preventDefault()
  327. form.showMessage = true
  328. form.selectItem = item
  329. }}
  330. >
  331. <Image class={styles.messageImg} src={iconMessage} />
  332. </div>
  333. </div>
  334. ),
  335. value: () => (
  336. <span class={[styles.status, item.delFlag ? styles.frozen : '']}>
  337. {!item.delFlag ? '绑定' : '解绑'}
  338. </span>
  339. )
  340. }}
  341. </Cell>
  342. <Cell>
  343. {{
  344. title: () => (
  345. <div class={styles.subjectContainer}>
  346. <span>声部:</span>
  347. <div>
  348. {item.subjectNames &&
  349. item.subjectNames.length > 0 &&
  350. item.subjectNames.map((subject: any) => (
  351. <Tag type="primary" class={styles.tagSubject}>
  352. {subject}
  353. </Tag>
  354. ))}
  355. </div>
  356. </div>
  357. )
  358. }}
  359. </Cell>
  360. </CellGroup>
  361. ))}
  362. </List>
  363. ) : (
  364. <OEmpty btnStatus={false} classImgSize="SMALL" tips="暂无伴学老师" />
  365. )}
  366. <Popup
  367. v-model:show={form.showQrcode}
  368. position="bottom"
  369. style={{ background: 'transparent' }}
  370. >
  371. <div class={styles.codeContainer}>
  372. <div class={styles.codeImg} id="preview-container">
  373. <div class={styles.codeContent}>
  374. <h2 class={[styles.codeTitle, 'van-ellipsis']}>{form.schoolName}</h2>
  375. <div class={styles.codeName}>邀请您成为乐团伴学老师</div>
  376. <div class={styles.codeQr}>
  377. <OQrcode text={form.url} size={'100%'} />
  378. </div>
  379. <div style={{ textAlign: 'center' }}>
  380. <span class={styles.codeBtnText}>扫描上方二维码完成资料填写</span>
  381. </div>
  382. <div class={styles.codeTips}>二维码将在两小时后失效,请及时登记</div>
  383. </div>
  384. </div>
  385. <div class={styles.codeBottom}>
  386. <Icon
  387. name="cross"
  388. size={22}
  389. class={styles.close}
  390. color="#666"
  391. onClick={() => (form.showQrcode = false)}
  392. />
  393. <h3 class={styles.title}>
  394. <i></i>分享方式
  395. </h3>
  396. <Grid columnNum={2} border={false}>
  397. <GridItem onClick={onSaveImg}>
  398. {{
  399. icon: () => <Image class={styles.shareImg} src={iconSaveImage} />,
  400. text: () => <div class={styles.shareText}>保存图片</div>
  401. }}
  402. </GridItem>
  403. <GridItem onClick={onShare}>
  404. {{
  405. icon: () => <Image class={styles.shareImg} src={iconWechat} />,
  406. text: () => <div class={styles.shareText}>微信</div>
  407. }}
  408. </GridItem>
  409. </Grid>
  410. </div>
  411. </div>
  412. </Popup>
  413. <Popup
  414. v-model:show={form.showMessage}
  415. position="bottom"
  416. style={{ background: 'transparent' }}
  417. >
  418. <div class={styles.codeContainer}>
  419. <div class={styles.codeBottom}>
  420. <Icon
  421. name="cross"
  422. size={22}
  423. class={styles.close}
  424. color="#666"
  425. onClick={() => (form.showMessage = false)}
  426. />
  427. <h3 class={styles.title}>
  428. <i></i>联系方式
  429. </h3>
  430. <Grid columnNum={2} border={false}>
  431. <GridItem
  432. onClick={() => {
  433. postMessage({
  434. api: 'joinChatGroup',
  435. content: {
  436. type: 'single', // single 单人 multi 多人
  437. id: form.selectItem.id
  438. }
  439. })
  440. form.showMessage = false
  441. }}
  442. >
  443. {{
  444. icon: () => <Image class={styles.shareImg} src={iconCallMessage} />,
  445. text: () => <div class={styles.shareText}>发送消息</div>
  446. }}
  447. </GridItem>
  448. <GridItem
  449. onClick={() => {
  450. postMessage({
  451. api: 'callPhone',
  452. content: {
  453. phone: form.selectItem.phone
  454. }
  455. })
  456. form.showMessage = false
  457. }}
  458. >
  459. {{
  460. icon: () => <Image class={styles.shareImg} src={iconCallPhone} />,
  461. text: () => <div class={styles.shareText}>拨打电话</div>
  462. }}
  463. </GridItem>
  464. </Grid>
  465. </div>
  466. </div>
  467. </Popup>
  468. <ActionSheet
  469. v-model:show={form.oPopover}
  470. cancelText="取消"
  471. actions={
  472. [
  473. { name: '全部', id: 'ALL' },
  474. { name: '解绑', id: true },
  475. { name: '绑定', id: false }
  476. // { name: '注销', id: 'CANCEL' },
  477. // { name: '冻结', id: 'LOCKED' },
  478. // { name: '正常', id: 'ACTIVATION' }
  479. ] as any
  480. }
  481. onSelect={(val: any) => {
  482. form.statusText = val.name
  483. form.params.delFlag = val.id === 'ALL' ? null : val.id
  484. form.oPopover = false
  485. onSearch()
  486. }}
  487. />
  488. <Popup v-model:show={form.showPopover} round position="bottom">
  489. <Picker
  490. columns={form.subjectList}
  491. onCancel={() => (form.showPopover = false)}
  492. onConfirm={(item: any) => onConfirmSubject(item)}
  493. />
  494. </Popup>
  495. </>
  496. )
  497. }
  498. })