index.vue 24 KB

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