index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  1. <template>
  2. <view>
  3. <swiper class="student-swiper" :class="students && students.length > 1 ? 'show-dots' : ''"
  4. :indicator-dots="students && students.length > 1" indicator-color="#ffffff" indicator-active-color="#666666"
  5. previous-margin="30rpx" next-margin="30rpx" v-if="students && students.length > 0" @change="onSwiperChange">
  6. <swiper-item v-for="student in students" :key="student.id" @click="onSwiperClick(student.id)">
  7. <view class="student-card">
  8. <view class="student-card-left">
  9. <image class="student-avatar" :src="student.avatar"></image>
  10. <view class="student-info">
  11. <text class="student-name">{{ student.name }}</text>
  12. <text class="student-class">{{ student.className }}</text>
  13. </view>
  14. </view>
  15. <view @click.stop="handleScan()" class="scan-btn" v-if="isLogin">
  16. <Icon name="icon-saoyisao2" size="72" color="#bfbfbf" />
  17. <text>扫一扫</text>
  18. </view>
  19. </view>
  20. </swiper-item>
  21. </swiper>
  22. <!-- 登录之后 -->
  23. <view class="container" v-if="isLogin">
  24. <view class="function-list">
  25. <!-- 充值功能 -->
  26. <view class="function-item" @click="goToRecharge">
  27. <view class="function-icon recharge-icon">
  28. <Icon name="icon-qianbao" size="60" color="#07c160" />
  29. </view>
  30. <text class="function-name">充值</text>
  31. </view>
  32. <!-- 校车位置(家长) -->
  33. <!-- <view class="function-item" @click="goToBusLocation">
  34. <view class="function-icon">
  35. <Icon name="icon-cheliangyuyue" size="60" color="#e59f40" />
  36. </view>
  37. <text class="function-name">校车位置</text>
  38. </view> -->
  39. <!-- 呼叫留言 -->
  40. <!-- <view class="function-item" @click="goToCallCenter">
  41. <view class="function-icon">
  42. <Icon name="icon-dianhua" size="60" color="#ff9f43" />
  43. </view>
  44. <text class="function-name">呼叫留言</text>
  45. </view> -->
  46. <!-- 留言功能 -->
  47. <!-- <view class="function-item" @click="goToMessage">
  48. <view class="function-icon">
  49. <Icon name="icon-jinchu" size="60" color="#393f51" />
  50. <view class="badge" v-if="hasNewMessage"></view>
  51. </view>
  52. <text class="function-name">留言</text>
  53. </view> -->
  54. <!-- <view class="function-item" @click="goToInOut">
  55. <view class="function-icon">
  56. <Icon name="icon-duihuapaopao2" size="60" color="#393f51" />
  57. <view class="badge" v-if="hasNewRecord"></view>
  58. </view>
  59. <text class="function-name">进出</text>
  60. </view> -->
  61. <!-- <view class="function-item" @click="goToBzrDm">
  62. <view class="function-icon">
  63. <Icon name="icon-dianming" size="60" color="#8992ef"/>
  64. </view>
  65. <text class="function-name">班主任点名(小程序)</text>
  66. </view> -->
  67. <!-- <view class="function-item" @click="goToBzrDmWebview">
  68. <view class="function-icon">
  69. <Icon name="icon-dianming" size="60" color="#8992ef" />
  70. </view>
  71. <text class="function-name">班主任点名</text>
  72. </view> -->
  73. <view class="function-item" v-for="item in btnList" :key="item.id" @click="commonBtnClick(item)">
  74. <view class="function-icon">
  75. <image :src="getFullIconUrl(item.icon)" mode="aspectFill"></image>
  76. </view>
  77. <text class="function-name">{{ item.desc }}</text>
  78. </view>
  79. <!-- <view class="function-item" @click="goToDmWebview">
  80. <view class="function-icon">
  81. <Icon name="icon-dianming" size="60" color="#8992ef"/>
  82. </view>
  83. <text class="function-name">点名</text>
  84. </view> -->
  85. <!-- <view class="function-item" @click="goToXunChaWebview">
  86. <view class="function-icon">
  87. <Icon name="icon-xuncha" size="60" color="#475fab" />
  88. </view>
  89. <text class="function-name">校长巡查</text>
  90. </view> -->
  91. <!-- <view class="function-item" @click="goXuncha">
  92. <view class="function-icon">
  93. <Icon name="icon-xuncha" size="60" color="#475fab" />
  94. </view>
  95. <text class="function-name">巡查</text>
  96. </view> -->
  97. <!-- <view class="function-item" @click="goToXfjl">
  98. <view class="function-icon">
  99. <Icon name="icon-xiaofeijilu" size="60" color="#c48c21" />
  100. </view>
  101. <text class="function-name">消费记录</text>
  102. </view> -->
  103. <!-- <view class="function-item" @click="goToTestList">
  104. <view class="function-icon">
  105. <Icon name="icon-dianming" size="60" color="#ff6b6b"/>
  106. </view>
  107. <text class="function-name">测试通用列表</text>
  108. </view> -->
  109. <!-- <view class="function-item" @click="goToClyyWebview">
  110. <view class="function-icon">
  111. <Icon name="icon-cheliangyuyue" size="60" color="#54a400" />
  112. </view>
  113. <text class="function-name">车辆预约</text>
  114. </view> -->
  115. <!-- 测试订阅消息跳转 -->
  116. <!-- <view class="function-item" @click="goToNoticeRedirect">
  117. <view class="function-icon">
  118. <Icon name="icon-xiaoxi" size="60" color="#ff6b6b" />
  119. </view>
  120. <text class="function-name">测试订阅跳转</text>
  121. </view> -->
  122. <!-- <view class="function-item" @click="goToLoginTest">
  123. <view class="function-icon">
  124. <Icon name="icon-denglu" size="60" color="#40ac6d" />
  125. </view>
  126. <text class="function-name">登录测试(H5)</text>
  127. </view>-->
  128. </view>
  129. </view>
  130. <!-- 未登录显示 -->
  131. <view class="unlogin" v-else>
  132. <image :src="unloginImageUrl" mode="aspectFit" />
  133. </view>
  134. <!-- 登录确认弹窗 -->
  135. <LoginConfirmModal
  136. ref="loginConfirmModalRef"
  137. v-model:visible="showLoginConfirm"
  138. :loginType="currentLoginType"
  139. @confirm="handleLoginConfirm"
  140. @cancel="handleLoginCancel"
  141. />
  142. </view>
  143. </template>
  144. <script setup>
  145. import { ref, computed } from 'vue'
  146. import { useUserStore } from '@/store/modules/user'
  147. import websocketService from '@/utils/websocket'
  148. import Icon from '@/components/icon/index.vue';
  149. import { onShow, onHide, onLoad, onUnload } from '@dcloudio/uni-app'
  150. import { goTo } from '@/utils/navigation'
  151. import { userApi } from '@/api/user';
  152. import { commonApi } from '@/api/common'
  153. import env from '@/config/env.js'
  154. import { getImageUrl } from '@/utils/util'
  155. // 组件导入
  156. import LoginPopup from '@/components/login/LoginPopup.vue'
  157. import LoginConfirmModal from '@/components/LoginConfirmModal/index.vue'
  158. const userStore = useUserStore();
  159. const hasNewMessage = ref(false);
  160. const currentStudentIndex = ref(0);
  161. const isLogin = ref(false);
  162. let userInfo = uni.getStorageSync('userInfo') || {};
  163. const messageListenersBound = ref(false)
  164. const studentIds = ref([]);
  165. const unloginImageUrl = "/static/images/unlogin.jpg";
  166. const students = ref([
  167. {
  168. id: 0,
  169. name: '请登录',
  170. className: '',
  171. avatar: '/static/logo.png'
  172. }
  173. ])
  174. // 登录确认弹窗相关
  175. const loginConfirmModalRef = ref(null)
  176. const showLoginConfirm = ref(false)
  177. const currentLoginType = ref({})
  178. const pendingLoginData = ref(null)
  179. const templateId = 'rVi7erjgdVRVBMgIhhHgPu9JMp3TM7Ug30x1ycAXY0w'
  180. // 登录类型配置映射
  181. const LOGIN_TYPE_MAP = {
  182. 1: { // PC端
  183. systemName: 'PC管理系统',
  184. title: '扫码登录',
  185. tip: '确认登录PC端管理系统吗?',
  186. icon: '/static/logo.png',
  187. },
  188. 2: { // 门系统
  189. systemName: '门禁系统',
  190. title: '扫码登录',
  191. tip: '确认登录门禁系统吗?',
  192. icon: '/static/logo.png',
  193. }
  194. // 未来可以继续扩展其他系统...
  195. }
  196. const btnList = ref([])
  197. const getFullIconUrl = (icon) => {
  198. return `${env.baseUrl}/skin/mp_easy/icon/${icon}.svg`
  199. }
  200. const commonBtnClick = (item) => {
  201. if (!checkLogin()) {
  202. pleaseLogin()
  203. return
  204. }
  205. console.log(`准备进入 ${item.desc},先检查订阅消息`)
  206. // 先处理订阅,订阅完成后再跳转
  207. openPopup(() => {
  208. console.log('订阅处理完成,准备跳转')
  209. goTo('/pages/common/webview', {
  210. dest: item.dest,
  211. title: item.desc,
  212. service: item.init
  213. })
  214. })
  215. }
  216. const goXuncha = () => {
  217. if (!checkLogin()) {
  218. pleaseLogin()
  219. return
  220. }
  221. }
  222. // 跳转到充值页面
  223. const goToRecharge = () => {
  224. if (!checkLogin()) {
  225. pleaseLogin()
  226. return
  227. }
  228. goTo('/pages/payment/recharge')
  229. }
  230. const goToBusLocation = () => {
  231. if (!checkLogin()) {
  232. pleaseLogin()
  233. return
  234. }
  235. goTo('/pages/xcdm/parent')
  236. }
  237. const goToCallCenter = () => {
  238. if (!checkLogin()) {
  239. pleaseLogin()
  240. return
  241. }
  242. goTo('/pages/parent/call-center')
  243. }
  244. const checkLogin = () => {
  245. if (typeof userInfo == 'string') {
  246. userInfo = JSON.parse(userInfo)
  247. }
  248. // console.log('userInfo', userInfo)
  249. if (!userInfo || !userInfo.yhsbToken) {
  250. return false
  251. }
  252. return true
  253. }
  254. const pleaseLogin = () => {
  255. uni.showToast({
  256. title: '请先登录',
  257. icon: 'none'
  258. })
  259. // 跳转到H5登录页面
  260. userStore.showH5Login()
  261. }
  262. // 扫码功能
  263. const handleScan = () => {
  264. console.log('handleScan')
  265. if (!checkLogin()) {
  266. pleaseLogin()
  267. return
  268. }
  269. // 调用微信小程序扫码API
  270. uni.scanCode({
  271. onlyFromCamera: false, // 允许从相册选择二维码图片
  272. scanType: ['qrCode', 'barCode'], // 支持二维码和条形码
  273. success: async (res) => {
  274. try {
  275. console.log('扫码结果:', res)
  276. const jsonStr = '{' + res.result.replace(/(\w+):/g, '"$1":') + '}';
  277. const obj = JSON.parse(jsonStr);
  278. console.log('扫码结果:', res, obj)
  279. // 获取登录类型配置
  280. const loginType = LOGIN_TYPE_MAP[obj.ss]
  281. if (!loginType) {
  282. uni.showToast({
  283. title: '不支持的登录类型',
  284. icon: 'none'
  285. })
  286. return
  287. }
  288. // 保存待处理的登录数据
  289. pendingLoginData.value = obj
  290. // 设置当前登录类型并显示确认弹窗
  291. currentLoginType.value = loginType
  292. showLoginConfirm.value = true
  293. } catch (error) {
  294. console.error('解析扫码结果失败:', error)
  295. uni.showToast({
  296. title: '二维码格式错误',
  297. icon: 'none'
  298. })
  299. }
  300. },
  301. fail: (err) => {
  302. console.error('扫码失败:', err)
  303. if (err.errMsg && err.errMsg.indexOf('cancel') === -1) {
  304. uni.showToast({
  305. title: '扫码失败',
  306. icon: 'none'
  307. })
  308. }
  309. }
  310. })
  311. }
  312. // 确认登录
  313. const handleLoginConfirm = async () => {
  314. if (!pendingLoginData.value) return
  315. const obj = pendingLoginData.value
  316. let resp = null
  317. try {
  318. uni.showLoading({
  319. title: '登录中...',
  320. mask: true
  321. })
  322. if (obj.ss == 1) { // PC端
  323. console.log('pc扫码登录')
  324. resp = await userApi.sacnLoginQrcode({ k: obj.k })
  325. } else if (obj.ss == 2) { // 门系统
  326. console.log('门系统扫码登录')
  327. resp = await userApi.sacnGateLoginQrcode({ k: obj.k })
  328. }
  329. console.log('扫码登录结果:', resp)
  330. uni.hideLoading()
  331. uni.showToast({
  332. title: '登录成功',
  333. icon: 'success'
  334. })
  335. } catch (error) {
  336. uni.hideLoading()
  337. console.error('登录失败:', error)
  338. uni.showToast({
  339. title: error.message || '登录失败',
  340. icon: 'none'
  341. })
  342. } finally {
  343. pendingLoginData.value = null
  344. }
  345. }
  346. // 取消登录
  347. const handleLoginCancel = () => {
  348. pendingLoginData.value = null
  349. console.log('用户取消登录')
  350. }
  351. const onSwiperChange = (e) => {
  352. currentStudentIndex.value = e.detail.current;
  353. }
  354. const onSwiperClick = (studentId) => {
  355. if (studentId == 0) {
  356. pleaseLogin()
  357. }else{
  358. goTo('/pages/my/changeInfo')
  359. }
  360. }
  361. const goToInOut = () => {
  362. if (checkLogin()) {
  363. // 获取当前学生ID
  364. const currentStudentId = students.value[currentStudentIndex.value].id;
  365. // 检查 studentIds 数组中是否存在当前学生ID
  366. const index = studentIds.value.indexOf(currentStudentId);
  367. if (index !== -1) {
  368. // 如果存在,才从数组中移除
  369. this.studentIds.splice(index, 1);
  370. }
  371. uni.navigateTo({
  372. url: '/pages/parent/in-out?role=parent&studentId=' + students.value[currentStudentIndex.value].id
  373. })
  374. } else {
  375. pleaseLogin()
  376. }
  377. }
  378. const goToMessage = () => {
  379. if (checkLogin()) {
  380. uni.navigateTo({
  381. url: '/pages/parent/message?role=parent'
  382. })
  383. } else {
  384. pleaseLogin()
  385. }
  386. }
  387. const goToBzrDm = () => {
  388. if (checkLogin()) {
  389. // 跳转到通用列表页面,传递班主任点名的配置
  390. const config = {
  391. ssServ: 'bjdm_cx',
  392. title: '班主任点名',
  393. baseParams: {}
  394. }
  395. const url = `/pages/bjdm/bjdm_bzrDm_list`
  396. goTo(url)
  397. } else {
  398. pleaseLogin()
  399. }
  400. }
  401. const goToTestList = () => {
  402. if (checkLogin()) {
  403. // 测试通用列表页面,可以传递不同的服务配置
  404. const config = {
  405. ssServ: 'bjdm_cx', // 可以改成其他服务测试
  406. title: '测试通用列表页面',
  407. baseParams: {
  408. // 可以添加额外的查询参数
  409. testParam: 'test'
  410. }
  411. }
  412. const url = `/pages/common/list?ssServ=${config.ssServ}&baseParams=${encodeURIComponent(JSON.stringify(config.baseParams))}`
  413. uni.navigateTo({
  414. url: url
  415. })
  416. } else {
  417. pleaseLogin()
  418. }
  419. }
  420. const goToXfjl = () => {
  421. if (checkLogin()) {
  422. uni.navigateTo({
  423. url: '/pages/xfjl/index'
  424. })
  425. } else {
  426. pleaseLogin()
  427. }
  428. }
  429. const goToClyy = () => {
  430. if (checkLogin()) {
  431. const yhid = uni.getStorageSync('userInfo').yhid
  432. // 测试通用列表页面,可以传递不同的服务配置
  433. // const url = `/pages/common/list?ssServ=${config.ssServ}&baseParams=${encodeURIComponent(JSON.stringify(config.baseParams))}`
  434. // goTo(url)
  435. goTo('/pages/clyy/clyy_inp')
  436. } else {
  437. pleaseLogin()
  438. }
  439. }
  440. const goToClyyWebview = () => {
  441. if (!checkLogin()) {
  442. pleaseLogin()
  443. return
  444. }
  445. console.log('准备进入车辆预约,先检查订阅消息')
  446. // 先处理订阅,订阅完成后再跳转
  447. openPopup(() => {
  448. console.log('订阅处理完成,准备跳转到车辆预约页面')
  449. goTo('/pages/common/webview', {
  450. dest: 'mp_clyy_inp',
  451. title: '车辆预约',
  452. })
  453. })
  454. }
  455. const goToXunChaWebview = () => {
  456. const testParams = {
  457. dest: 'mp_rcXcdjl_excelZxxzAdd', // 测试用的dest参数
  458. ssToken: '1b298017d5764bb39708ab3724739cbe', // 测试用的ssToken
  459. // 可以添加其他测试参数
  460. title: '校长巡查',
  461. }
  462. // 构建跳转URL
  463. const queryString = Object.keys(testParams)
  464. .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(testParams[key])}`)
  465. .join('&')
  466. // 跳转到redirect页面
  467. if (checkLogin()) {
  468. goTo(`/pages/common/webview?${queryString}`)
  469. // goTo('/pages/common/webview', {
  470. // dest: 'mp_excelRcXcdjl_edit',
  471. // ssToken: '39101b19b2c545b2a62c72177e033cc5',
  472. // })
  473. } else {
  474. pleaseLogin()
  475. }
  476. }
  477. const goToBzrDmWebview = () => {
  478. if (checkLogin()) {
  479. goTo('/pages/common/webview', {
  480. dest: 'mp_objList',
  481. title: '班主任点名(webview)',
  482. service: 'bjdm_cx'
  483. })
  484. // goTo('/pages/common/webview', {
  485. // dest: 'upload-image-demo',
  486. // })
  487. } else {
  488. pleaseLogin()
  489. }
  490. }
  491. const goToDmWebview = () => {
  492. goTo('/pages/common/webview', {
  493. dest: 'bjdm_bzrDm',
  494. title: '班主任点名(webview)',
  495. })
  496. }
  497. const goToLoginTest = () => {
  498. // console.log('🔐 跳转到H5登录测试页面')
  499. goTo('/pages/common/webview', {
  500. dest: 'login',
  501. title: '登录'
  502. })
  503. }
  504. // 测试订阅消息跳转
  505. const goToNoticeRedirect = () => {
  506. // 模拟订阅消息的参数,可以测试不同的dest和ssToken
  507. const testParams = {
  508. dest: 'mp_ccChk', // 测试用的dest参数
  509. ssToken: '5d8c4af16247457fbd9ea71f5982a733', // 测试用的ssToken
  510. // 可以添加其他测试参数
  511. testParam: 'test'
  512. }
  513. // 构建跳转URL
  514. const queryString = Object.keys(testParams)
  515. .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(testParams[key])}`)
  516. .join('&')
  517. // 跳转到redirect页面
  518. goTo(`/pages/common/webview?${queryString}`)
  519. }
  520. const hasNewRecord = computed(() => {
  521. // 获取当前选中的学生ID
  522. const currentStudentId = students[currentStudentIndex.value]?.id;
  523. // 如果当前没有选中学生,返回 false
  524. if (!currentStudentId) return false;
  525. // 检查当前学生ID是否在 studentIds 数组中
  526. return this.studentIds.includes(currentStudentId);
  527. })
  528. const openPopup = (callback) => {
  529. // console.log('openPopup 被调用')
  530. // 获取用户的当前设置,判断是否点击了"总是保持以上,不在询问"
  531. uni.getSetting({
  532. withSubscriptions: true, // 是否获取用户订阅消息的订阅状态,默认false不返回
  533. success(res) {
  534. // console.log('获取设置成功:', res)
  535. console.log('订阅设置:', res.subscriptionsSetting)
  536. if (res.subscriptionsSetting && res.subscriptionsSetting[templateId] === 'accept') {
  537. // console.log('用户已经同意订阅该模板消息')
  538. // // 已订阅,直接执行回调
  539. callback && callback()
  540. } else {
  541. console.log('用户未同意订阅或设置不存在,调起订阅消息弹窗')
  542. //因为没有选择总是保持,所以需要调起授权弹窗再次授权
  543. authorizationBtn(callback);
  544. }
  545. },
  546. fail(err) {
  547. console.error('获取设置失败:', err)
  548. // 获取设置失败,也尝试调起订阅弹窗
  549. authorizationBtn(callback);
  550. }
  551. })
  552. }
  553. const authorizationBtn = (callback) => {
  554. console.log('authorizationBtn')
  555. uni.requestSubscribeMessage({
  556. // tmplIds: ['5piwpV8roTajn7Mo2NrBpTjFTXwWEr002Ho-rw4HGQw'],
  557. tmplIds: [templateId],
  558. success(res) {
  559. console.log('订阅消息请求结果:', res)
  560. // const templateId = '5piwpV8roTajn7Mo2NrBpTjFTXwWEr002Ho-rw4HGQw'
  561. if (res[templateId] === 'accept') {
  562. console.log('用户同意订阅消息')
  563. uni.showToast({
  564. title: '订阅成功',
  565. icon: 'success'
  566. })
  567. } else {
  568. console.log('用户拒绝订阅消息')
  569. }
  570. // 无论用户是否同意,都执行回调
  571. callback && callback()
  572. },
  573. fail(err) {
  574. console.error('订阅消息请求失败:', err)
  575. // 失败也执行回调
  576. callback && callback()
  577. }
  578. })
  579. }
  580. const requestDeviceAuth = async () => {
  581. try {
  582. // 先检查是否已授权
  583. const voipList = await new Promise((resolve, reject) => {
  584. uni.getDeviceVoIPList({
  585. success: res => resolve(res.list),
  586. fail: err => reject(err)
  587. })
  588. })
  589. // console.log('voipList', voipList)
  590. // 如果已授权,直接返回
  591. if (voipList.some(device => device.status === 1)) {
  592. return true
  593. }
  594. // 获取设备票据(需要后端接口支持)
  595. // const ticketData = await userApi.getDeviceTicket()
  596. // 请求设备授权
  597. await new Promise((resolve, reject) => {
  598. uni.requestDeviceVoIP({
  599. isGroup: true,
  600. groupId: 'gUpNA_xJr-WkYBIeGofWSPQ',
  601. success: res => {
  602. // console.log('授权成功:', res)
  603. },
  604. fail: err => {
  605. console.error('授权失败:', err)
  606. if (err.errCode === 9) {
  607. uni.showModal({
  608. title: '提示',
  609. content: '请在设置中开启设备通话权限',
  610. success: () => {
  611. uni.openSetting()
  612. }
  613. })
  614. }
  615. reject(err)
  616. }
  617. })
  618. })
  619. return true
  620. } catch (error) {
  621. console.error('设备授权处理失败:', error)
  622. return false
  623. }
  624. }
  625. const getUserInfo = async () => {
  626. let userInfo = uni.getStorageSync('userInfo')
  627. if (typeof userInfo === 'string'){
  628. userInfo = JSON.parse(userInfo)
  629. }
  630. const userMoreInfo = await userApi.mp_infoHomep_load(userInfo.userId)
  631. console.log('userMoreInfo', userMoreInfo.data.ryInfo, typeof userMoreInfo.data.ryInfo)
  632. btnList.value = JSON.parse(userMoreInfo.data.btnList) // 更新按钮列表
  633. const rylbm = userMoreInfo.data.ryInfo?.rylbm;
  634. let huixian = '已登录';
  635. if (userMoreInfo.data.ryInfo?.bjid) { // 有班级id 优先班级
  636. const bjid = userMoreInfo.data.ryInfo?.bjid
  637. const bj = await commonApi.getDictByCbNameAndValue('bj', bjid)
  638. huixian = bj.data.result[bjid]
  639. }else if(userMoreInfo.data.ryInfo?.bmid){ // 教职工
  640. const bmid = userMoreInfo.data.ryInfo?.bmid
  641. const bm = await commonApi.getDictByCbNameAndValue('bm', bmid)
  642. huixian = bm.data.result[bmid]
  643. }else {
  644. huixian = '已登录'
  645. }
  646. // 如果有头像的话,则直接取头像
  647. if (userMoreInfo.data.ryInfo?.yszwj) {
  648. const avatar = getImageUrl(userMoreInfo.data.ryInfo.yszwj)
  649. students.value[0] = {
  650. id: 1,
  651. name: userInfo.xm,
  652. className: huixian,
  653. avatar: avatar
  654. }
  655. } else {
  656. // 如果性别码码为1,则取性别为男的头像 否则取性别为女
  657. if (userMoreInfo.data.ryInfo?.xbm == 1) {
  658. students.value[0] = {
  659. id: 1,
  660. name: userInfo.xm,
  661. className: huixian,
  662. avatar: '/static/images/yishuzhao_nan.svg'
  663. }
  664. } else {
  665. students.value[0] = {
  666. id: 1,
  667. name: userInfo.xm,
  668. className: huixian,
  669. avatar: '/static/images/yishuzhao_nv.svg'
  670. }
  671. }
  672. }
  673. let ryInfo = userMoreInfo.data.ryInfo
  674. console.log('ryInfo', ryInfo)
  675. uni.setStorageSync('userInfo', {
  676. ...userInfo,
  677. ...ryInfo,
  678. }) // 更新用户信息
  679. }
  680. const handleInit = (data) => {
  681. // console.log('handleInit', data)
  682. studentIds.value = data.studentIds || [];
  683. }
  684. const handleNewMessage = (data) => {
  685. // console.log('handleNewMessage', data)
  686. if (!studentIds.value.includes(data.studentId)) {
  687. studentIds.value.push(data.studentId);
  688. }
  689. }
  690. const handleNewRecord = (data) => {
  691. // console.log('handleNewRecord', data)
  692. }
  693. const handLogin = (data) => {
  694. isLogin.value = true
  695. requestDeviceAuth()
  696. getUserInfo()
  697. // websocketService.connect()
  698. if (!messageListenersBound.value) {
  699. uni.$on('init', handleInit)
  700. uni.$on('newMessage', handleNewMessage)
  701. uni.$on('newRecord', handleNewRecord)
  702. messageListenersBound.value = true
  703. }
  704. }
  705. const handleOnShow = () => {
  706. // console.log('首页显示')
  707. uni.$on('login', handLogin)
  708. if (checkLogin()) {
  709. // 如果已经登录,则执行登录后的操作
  710. handLogin()
  711. } else {
  712. isLogin.value = false
  713. }
  714. }
  715. const handleOnHide = () => {
  716. // console.log('首页隐藏')
  717. }
  718. const handleOnLoad = () => {
  719. // console.log('首页加载')
  720. // 监听登录事件(只绑定一次)
  721. uni.$on('login', handLogin)
  722. if (checkLogin()) {
  723. // 如果已经登录,则执行登录后的操作
  724. handLogin()
  725. } else {
  726. isLogin.value = false
  727. }
  728. }
  729. const handleOnUnload = () => {
  730. // console.log('首页卸载')
  731. if (messageListenersBound.value) {
  732. uni.$off('newMessage', handleNewMessage)
  733. uni.$off('newRecord', handleNewRecord)
  734. messageListenersBound.value = false
  735. }
  736. // websocketService.disconnect()
  737. }
  738. // 暴露生命周期方法供主容器调用(主容器会调用这些而非页面级 onLoad/onShow)
  739. defineExpose({
  740. onShow: handleOnShow,
  741. onHide: handleOnHide,
  742. onLoad: handleOnLoad,
  743. onUnload: handleOnUnload
  744. })
  745. </script>
  746. <style lang="scss">
  747. .wx-swiper-dots.wx-swiper-dots-horizontal {
  748. width: calc(100% - 90rpx);
  749. display: flex;
  750. justify-content: flex-end;
  751. display: none;
  752. display: flex;
  753. justify-content: flex-end;
  754. display: none;
  755. }
  756. .show-dots .wx-swiper-dots.wx-swiper-dots-horizontal {
  757. display: flex;
  758. }
  759. .wx-swiper-dot {
  760. border: 2rpx solid #666666 !important;
  761. background: #ffffff !important;
  762. }
  763. .wx-swiper-dot-active {
  764. background: #666666 !important;
  765. }
  766. .container {
  767. border-top: 2rpx solid #dcdcdc;
  768. padding: 30rpx;
  769. }
  770. .student-swiper {
  771. height: 250rpx;
  772. margin-top: 40rpx;
  773. // margin-bottom: 40rpx;
  774. }
  775. .student-card {
  776. box-sizing: border-box;
  777. display: flex;
  778. align-items: center;
  779. justify-content: space-between;
  780. padding: 30rpx;
  781. background: #FFFFFF;
  782. border-radius: 12rpx;
  783. box-shadow: 0rpx 6rpx 15rpx rgba(0, 0, 0, .3);
  784. margin: 10rpx 15rpx;
  785. }
  786. .student-card-left{
  787. display: flex;
  788. align-items: center;
  789. width: 70%;
  790. }
  791. .student-avatar {
  792. width: 124rpx;
  793. height: 124rpx;
  794. border-radius: 50%;
  795. margin-right: 20rpx;
  796. border: 2rpx solid #eee;
  797. flex-shrink: 0;
  798. }
  799. .student-info {
  800. height: 100rpx;
  801. display: flex;
  802. flex-direction: column;
  803. justify-content: flex-start;
  804. width: calc(100% - 124rpx);
  805. }
  806. .student-name {
  807. font-size: 36rpx;
  808. font-weight: bold;
  809. color: #333333;
  810. margin-bottom: 8rpx;
  811. white-space: nowrap;
  812. overflow: hidden;
  813. text-overflow: ellipsis;
  814. }
  815. .student-class {
  816. font-size: 30rpx;
  817. color: #666666;
  818. }
  819. .scan-btn{
  820. display: flex;
  821. justify-content: center;
  822. align-items: center;
  823. flex-direction: column;
  824. color:#999999;
  825. font-size: 32rpx;
  826. gap: 4rpx;
  827. border-left: 2rpx solid #dcdcdc;
  828. padding-left: 40rpx;
  829. padding-right: 10rpx;
  830. flex-shrink: 0;
  831. }
  832. .function-list {
  833. display: grid;
  834. grid-template-columns: repeat(4, 1fr);
  835. gap: 30rpx;
  836. // padding: 0 20rpx;
  837. }
  838. .function-item {
  839. display: flex;
  840. flex-direction: column;
  841. align-items: center;
  842. font-size: 28.87rpx;
  843. color: #333333;
  844. }
  845. .function-icon {
  846. position: relative;
  847. width: 100rpx;
  848. height: 100rpx;
  849. background: #ffffff;
  850. border: 2rpx solid #e9e9e9;
  851. box-shadow: 5rpx 5rpx 10rpx rgba(0, 0, 0, .3);
  852. border-radius: 10rpx;
  853. display: flex;
  854. align-items: center;
  855. justify-content: center;
  856. margin-bottom: 10rpx;
  857. }
  858. .function-icon image {
  859. width: 60rpx;
  860. height: 60rpx;
  861. }
  862. .badge {
  863. position: absolute;
  864. top: 1rpx;
  865. right: 1rpx;
  866. width: 20rpx;
  867. height: 20rpx;
  868. background: #eb6100;
  869. border-radius: 50%;
  870. }
  871. .function-name {
  872. font-size: 26rpx;
  873. color: #333333;
  874. }
  875. .login-btn {
  876. width: 100%;
  877. height: 88rpx;
  878. line-height: 88rpx;
  879. background: #666666;
  880. color: #fff;
  881. border-radius: 44rpx;
  882. font-size: 32rpx;
  883. border: none;
  884. }
  885. .login-btn::after {
  886. border: none;
  887. }
  888. .login-btn:active {
  889. opacity: 0.8;
  890. }
  891. .unlogin {
  892. width: 100%;
  893. height: calc(100vh - 226rpx);
  894. image {
  895. width: 100%;
  896. height: 100%;
  897. }
  898. }
  899. </style>