A8000
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

254 lines
5.8 KiB

  1. <script setup lang="ts">
  2. import { ref, onMounted, onUnmounted } from 'vue'
  3. import { getUserList } from '@/services/Index/user-manage'
  4. import type { User } from '@/types/Index'
  5. import { createWebSocket, DeviceContextStateMessage } from '@/websocket/socket.ts'
  6. import { getServerInfo } from '@/utils/getServerInfo.ts'
  7. import router from '@/router/router.ts'
  8. import { isBoardParamInited, login } from '@/services'
  9. import circleUrl from '@/assets/avatar.png'
  10. import { VERSION } from '@/constant'
  11. const stateUrl = getServerInfo('/api/v1/app/ws/state')
  12. const wsState = createWebSocket(stateUrl.wsUrl)
  13. const handleDeviceContextState = (data: DeviceContextStateMessage['data']) => {
  14. if (data.loginFlag) {
  15. sessionStorage.setItem('token', JSON.stringify(data.loginUser))
  16. router.push('/index')
  17. }
  18. }
  19. //用户列表
  20. const userList = ref<User[]>([])
  21. //获取用户列表
  22. const getUserListData = async () => {
  23. const res = await getUserList()
  24. userList.value = res.data
  25. }
  26. onMounted(() => {
  27. wsState.subscribe<DeviceContextStateMessage>(
  28. 'DeviceContext',
  29. handleDeviceContextState,
  30. )
  31. wsState.connect()
  32. getUserListData()
  33. })
  34. onUnmounted(() => {
  35. wsState.unsubscribe<DeviceContextStateMessage>(
  36. 'DeviceContext',
  37. handleDeviceContextState,
  38. )
  39. })
  40. const activeUser = ref<User>()
  41. const password = ref('')
  42. const inputPin = (pin: string) => {
  43. if (password.value.length >= 4) {
  44. return
  45. }
  46. if (password.value.length < 4) {
  47. password.value += pin
  48. }
  49. if (password.value.length === 4) {
  50. // 输入完成,执行登录操作
  51. submitPin()
  52. }
  53. }
  54. const errorMsg = ref('')
  55. const submitPin = async () => {
  56. if (!activeUser.value?.id) {
  57. errorMsg.value = '请选择用户'
  58. return
  59. }
  60. if (password.value.length !== 4) {
  61. errorMsg.value = '请输入密码'
  62. return
  63. }
  64. let resData = await isBoardParamInited()
  65. if (resData.data) {
  66. //设备正在初始化
  67. errorMsg.value = '设备正在初始化,请稍候重试'
  68. return
  69. }
  70. const params = {
  71. id: activeUser.value?.id,
  72. password: password.value,
  73. }
  74. const res = await login(params)
  75. if (res.success) {
  76. sessionStorage.setItem('token', JSON.stringify(res.data))
  77. await router.push('/index')
  78. } else {
  79. errorMsg.value = res.info
  80. // password.value = ''
  81. }
  82. }
  83. </script>
  84. <template>
  85. <div class="login-box">
  86. <p class="login-title">登录</p>
  87. <div class="user-box">
  88. <p class="title">选择用户</p>
  89. <div class="user-list">
  90. <div class="user-info" v-for="user in userList" :key="user.id" @click="activeUser = user" :class="{ 'user-info-active': activeUser?.id === user.id }">
  91. <el-avatar :size="60" :src="circleUrl" />
  92. <span class="name">{{user.account}}</span>
  93. <el-icon v-show="activeUser?.id === user.id" color="#fff"><Select /></el-icon>
  94. </div>
  95. </div>
  96. </div>
  97. <div class="password-box">
  98. <p class="title">请输入4位PIN码</p>
  99. <div class="password-list">
  100. <div v-for="i in 4" :key="i" class="password-item" :class="{'password-item-fill': i <= password.length}"></div>
  101. </div>
  102. <div class="pin-keypad">
  103. <div
  104. v-for="n in 9"
  105. :key="n"
  106. class="key"
  107. @click="inputPin(n.toString())"
  108. >
  109. {{ n }}
  110. </div>
  111. <div class="key" @click="password = '';errorMsg = ''">重输</div>
  112. <div class="key" @click="inputPin('0')">0</div>
  113. <div class="key" @click="submitPin">确定</div>
  114. </div>
  115. <div class="error-box">{{errorMsg}}</div>
  116. </div>
  117. <div class="version">版本号{{`V${VERSION}`}}</div>
  118. </div>
  119. </template>
  120. <style lang="less" scoped>
  121. @keyframes iconAnim {
  122. 0% { transform: scale(0.8); opacity: 0; }
  123. 100% { transform: scale(1); opacity: 1; }
  124. }
  125. .login-title {
  126. position: absolute;
  127. top: 10%;
  128. font-size: 50px;
  129. font-weight: bold;
  130. }
  131. .login-box {
  132. width: 100%;
  133. height: 100%;
  134. background: #A0CEF2;
  135. display: flex;
  136. align-items: center;
  137. justify-content: center;
  138. position: relative;
  139. .user-box {
  140. width: 35%;
  141. height: 40%;
  142. margin-right: 20px;
  143. background: #fff;
  144. border-radius: 30px;
  145. padding: 60px 30px;
  146. display: flex;
  147. flex-direction: column;
  148. .user-list {
  149. width: 100%;
  150. flex: 1;
  151. overflow: auto;
  152. .user-info {
  153. display: flex;
  154. align-items: center;
  155. padding: 20px;
  156. border-radius: 20px;
  157. .name {
  158. margin-left: 30px;
  159. font-size: 25px;
  160. font-weight: bold;
  161. }
  162. .el-icon {
  163. animation: iconAnim 0.3s ease forwards;
  164. font-size: 30px;
  165. margin-left: auto;
  166. }
  167. }
  168. .user-info-active {
  169. background: #3E8ED1;
  170. color: #fff;
  171. }
  172. }
  173. }
  174. .password-box {
  175. margin-left: 20px;
  176. width: 35%;
  177. height: 40%;
  178. background: #fff;
  179. border-radius: 30px;
  180. padding: 60px 30px;
  181. display: flex;
  182. flex-direction: column;
  183. }
  184. }
  185. .title {
  186. font-size: 30px;
  187. font-weight: bold;
  188. }
  189. .password-list {
  190. display: flex;
  191. justify-content: center;
  192. padding: 20px 0;
  193. .password-item {
  194. width: 30px;
  195. height: 30px;
  196. border-radius: 50%;
  197. background: #CDCFD4;
  198. margin: 0 10px;
  199. }
  200. .password-item-fill {
  201. background: #3E8ED1;
  202. }
  203. }
  204. .pin-keypad {
  205. display: grid;
  206. grid-template-columns: repeat(3, 1fr);
  207. gap: 10px;
  208. margin-top: 30px;
  209. .key {
  210. width: 100px;
  211. height: 80px;
  212. background-color: #fff;
  213. border-radius: 15px;
  214. display: flex;
  215. align-items: center;
  216. justify-content: center;
  217. font-size: 32px;
  218. color: #333;
  219. cursor: pointer;
  220. transition: all 0.3s;
  221. border: 1px solid #E7EDF1;
  222. box-shadow: 0 3px 8px #E7EDF1;
  223. &:active {
  224. transform: scale(0.95);
  225. }
  226. }
  227. }
  228. .error-box {
  229. flex: 1;
  230. display: flex;
  231. justify-content: center;
  232. align-items: center;
  233. font-size: 20px;
  234. color: #ff0000;
  235. }
  236. .version{
  237. margin-top: 95vh;
  238. position: absolute;
  239. color: #f4f4f4;
  240. }
  241. </style>