| | |
| | | "@capacitor/browser": "^6.0.4", |
| | | "@capacitor/camera": "^6.1.2", |
| | | "@capacitor/core": "6.2.0", |
| | | "@capacitor/filesystem": "^6.0.3", |
| | | "@capacitor/geolocation": "^6.1.0", |
| | | "@ionic-native/bluetooth-le": "^5.36.0", |
| | | "@ionic-native/bluetooth-serial": "^5.36.0", |
| | | "@ionic-native/http": "^5.36.0", |
| | | "axios": "^1.7.2", |
| | | "buffer": "^6.0.3", |
| | | "cordova-plugin-bluetooth-serial": "^0.4.7", |
| | | "crc": "^4.3.2", |
| | | "echarts": "^5.5.1", |
| | | "element-plus": "^2.7.3", |
| | |
| | | "videojs-flash": "^2.2.1", |
| | | "vue": "^3.5.12", |
| | | "vue-qrcode-reader": "^5.6.0", |
| | | "vue-router": "^4.3.0" |
| | | "vue-router": "^4.3.0", |
| | | "vue-virtual-scroller": "^2.0.0-beta.8" |
| | | }, |
| | | "devDependencies": { |
| | | "@element-plus/icons": "^0.0.11", |
| | |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "tslib": "^2.1.0" |
| | | } |
| | | }, |
| | | "node_modules/@capacitor/filesystem": { |
| | | "version": "6.0.3", |
| | | "resolved": "https://registry.npmmirror.com/@capacitor/filesystem/-/filesystem-6.0.3.tgz", |
| | | "integrity": "sha512-PdIP/yOGAbG1lq1wbFbSPhXQ9/5lpTpeiok2NneawJOk6UXvy9W7QZXRo7wXAP7J6FdzU7bKfOORRXpOJpgXyw==", |
| | | "license": "MIT", |
| | | "peerDependencies": { |
| | | "@capacitor/core": "^6.0.0" |
| | | } |
| | | }, |
| | | "node_modules/@capacitor/geolocation": { |
| | | "version": "6.1.0", |
| | | "resolved": "https://registry.npmmirror.com/@capacitor/geolocation/-/geolocation-6.1.0.tgz", |
| | | "integrity": "sha512-jEY5DcZirxX1gOBuOvf/FL7FlPMOKcsnF8PlfYxu7OFwRDD7HRwehEPWtJXR6wSYLgsCujm7yRqKrjtyN+f14g==", |
| | | "license": "MIT", |
| | | "peerDependencies": { |
| | | "@capacitor/core": "^6.0.0" |
| | | } |
| | | }, |
| | | "node_modules/@ctrl/tinycolor": { |
| | |
| | | "version": "5.36.0", |
| | | "resolved": "https://registry.npmmirror.com/@ionic-native/bluetooth-le/-/bluetooth-le-5.36.0.tgz", |
| | | "integrity": "sha512-VJYX2gx85PH1SXVVm+wOeMXVt5U0W5t8HAAx4MWBaAjRoREQSYtKVxWtLURhYKwS4Pu82v/EQmRbwz2AyEg+Wg==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/cordova": "latest" |
| | | }, |
| | | "peerDependencies": { |
| | | "@ionic-native/core": "^5.1.0", |
| | | "rxjs": "^5.5.0 || ^6.5.0" |
| | | } |
| | | }, |
| | | "node_modules/@ionic-native/bluetooth-serial": { |
| | | "version": "5.36.0", |
| | | "resolved": "https://registry.npmmirror.com/@ionic-native/bluetooth-serial/-/bluetooth-serial-5.36.0.tgz", |
| | | "integrity": "sha512-etb2xbKQBbfI0mGkxgXDuQL40ndu0lJA48Ukz7XUaphUHSP62TofBoPZkXY/CmNvx2ZTCVBB9+2zOFsyyo6iQg==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/cordova": "latest" |
| | |
| | | "url": "https://github.com/sponsors/mesqueeb" |
| | | } |
| | | }, |
| | | "node_modules/cordova-plugin-bluetooth-serial": { |
| | | "version": "0.4.7", |
| | | "resolved": "https://registry.npmmirror.com/cordova-plugin-bluetooth-serial/-/cordova-plugin-bluetooth-serial-0.4.7.tgz", |
| | | "integrity": "sha512-Z62yZwl77CNoTLW3SwLvvvjPGq+sA1LBsSpuvdC3LePICiJHzFu2Zi5CtGSkjmkj+qOrgHoawOeXVgjN3LrPag==", |
| | | "license": "Apache-2.0" |
| | | }, |
| | | "node_modules/crc": { |
| | | "version": "4.3.2", |
| | | "resolved": "https://registry.npmmirror.com/crc/-/crc-4.3.2.tgz", |
| | |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/isaacs" |
| | | } |
| | | }, |
| | | "node_modules/mitt": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmmirror.com/mitt/-/mitt-2.1.0.tgz", |
| | | "integrity": "sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/mlly": { |
| | | "version": "1.7.3", |
| | |
| | | } |
| | | } |
| | | }, |
| | | "node_modules/vue-observe-visibility": { |
| | | "version": "2.0.0-alpha.1", |
| | | "resolved": "https://registry.npmmirror.com/vue-observe-visibility/-/vue-observe-visibility-2.0.0-alpha.1.tgz", |
| | | "integrity": "sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==", |
| | | "license": "MIT", |
| | | "peerDependencies": { |
| | | "vue": "^3.0.0" |
| | | } |
| | | }, |
| | | "node_modules/vue-qrcode-reader": { |
| | | "version": "5.6.0", |
| | | "resolved": "https://registry.npmmirror.com/vue-qrcode-reader/-/vue-qrcode-reader-5.6.0.tgz", |
| | |
| | | "node": ">=18.0.0" |
| | | } |
| | | }, |
| | | "node_modules/vue-resize": { |
| | | "version": "2.0.0-alpha.1", |
| | | "resolved": "https://registry.npmmirror.com/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz", |
| | | "integrity": "sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==", |
| | | "license": "MIT", |
| | | "peerDependencies": { |
| | | "vue": "^3.0.0" |
| | | } |
| | | }, |
| | | "node_modules/vue-router": { |
| | | "version": "4.4.5", |
| | | "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.4.5.tgz", |
| | |
| | | "vue": "^3.2.0" |
| | | } |
| | | }, |
| | | "node_modules/vue-virtual-scroller": { |
| | | "version": "2.0.0-beta.8", |
| | | "resolved": "https://registry.npmmirror.com/vue-virtual-scroller/-/vue-virtual-scroller-2.0.0-beta.8.tgz", |
| | | "integrity": "sha512-b8/f5NQ5nIEBRTNi6GcPItE4s7kxNHw2AIHLtDp+2QvqdTjVN0FgONwX9cr53jWRgnu+HRLPaWDOR2JPI5MTfQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "mitt": "^2.1.0", |
| | | "vue-observe-visibility": "^2.0.0-alpha.1", |
| | | "vue-resize": "^2.0.0-alpha.1" |
| | | }, |
| | | "peerDependencies": { |
| | | "vue": "^3.2.0" |
| | | } |
| | | }, |
| | | "node_modules/webpack-virtual-modules": { |
| | | "version": "0.6.2", |
| | | "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", |
| | |
| | | "@capacitor/browser": "^6.0.4", |
| | | "@capacitor/camera": "^6.1.2", |
| | | "@capacitor/core": "6.2.0", |
| | | "@capacitor/filesystem": "^6.0.3", |
| | | "@capacitor/geolocation": "^6.1.0", |
| | | "@ionic-native/bluetooth-le": "^5.36.0", |
| | | "@ionic-native/bluetooth-serial": "^5.36.0", |
| | | "@ionic-native/http": "^5.36.0", |
| | | "axios": "^1.7.2", |
| | | "buffer": "^6.0.3", |
| | | "cordova-plugin-bluetooth-serial": "^0.4.7", |
| | | "crc": "^4.3.2", |
| | | "echarts": "^5.5.1", |
| | | "element-plus": "^2.7.3", |
| | |
| | | "videojs-flash": "^2.2.1", |
| | | "vue": "^3.5.12", |
| | | "vue-qrcode-reader": "^5.6.0", |
| | | "vue-router": "^4.3.0" |
| | | "vue-router": "^4.3.0", |
| | | "vue-virtual-scroller": "^2.0.0-beta.8" |
| | | }, |
| | | "devDependencies": { |
| | | "@element-plus/icons": "^0.0.11", |
| | |
| | | import axios from "axios"; |
| | | import pinia from './pinia.js'; |
| | | import { storeToRefs } from 'pinia' |
| | | import { useUserStore } from "@/stores/user.js"; |
| | | const { serverIp } = storeToRefs(useUserStore(pinia)); |
| | | |
| | | import config from "./config"; |
| | | const { serviceIp } = config; |
| | | |
| | | if (process.env.NODE_ENV == "development") { |
| | | // 跨域请求 |
| | | axios.defaults.baseURL = "http://localhost:8100/bl/"; |
| | | axios.defaults.withCredentials = true; // 保持请求头 |
| | | } else { |
| | | axios.defaults.baseURL = location.protocol + "//" + serviceIp + ":8100/bl/"; |
| | | // axios.defaults.baseURL = 'http:' + "//" + serviceIp + ":8100/bl/"; |
| | | // axios.defaults.baseURL = location.protocol + "//" + serviceIp + ":8443/bl/"; |
| | | } |
| | | |
| | | |
| | | // 添加请求拦截器 |
| | | axios.interceptors.request.use( |
| | | function (config) { |
| | | // 在发送请求之前做些什么 |
| | | const axiosInstance = axios.create(); |
| | | axiosInstance.defaults.withCredentials = true; // 保持请求头 |
| | | axiosInstance.interceptors.request.use( |
| | | config => { |
| | | // 在发送请求之前做些什么,例如添加 token |
| | | return config; |
| | | }, |
| | | function (error) { |
| | | error => { |
| | | // 对请求错误做些什么 |
| | | return Promise.reject(error); |
| | | } |
| | | ); |
| | | |
| | | // 添加响应拦截器 |
| | | axios.interceptors.response.use( |
| | | function (response) { |
| | | // 响应拦截器 |
| | | axiosInstance.interceptors.response.use( |
| | | response => { |
| | | // 对响应数据做点什么 |
| | | return response; |
| | | }, |
| | | function (error) { |
| | | error => { |
| | | // 对响应错误做点什么 |
| | | return Promise.reject(error); |
| | | } |
| | | ); |
| | | |
| | | export default axios; |
| | | export default async function request(config) { |
| | | |
| | | // 构建完整的 URL |
| | | const fullUrl = `${location.protocol}//${serverIp.value}:8100/bl/${config.url}`; // 直接使用 userStore.serverIp |
| | | |
| | | // 发送请求,使用提取或默认的配置 |
| | | const response = await axiosInstance({ |
| | | ...config, |
| | | url: fullUrl, // 覆盖原始配置中的 url |
| | | }); |
| | | |
| | | return response; |
| | | } |
| | |
| | | import config from "./config"; |
| | | const { serviceIp } = config; |
| | | |
| | | // import config from "./config"; |
| | | // const { serviceIp } = config; |
| | | import { useUserStore } from "@/stores/user.js"; |
| | | import pinia from './pinia.js'; |
| | | const { serverIp } = useUserStore(pinia); |
| | | /** |
| | | * 获取Websocket的连接 |
| | | * @param action |
| | |
| | | if (window.location.protocol == "https:") { |
| | | wsProtocol = "wss://"; |
| | | } |
| | | if (process.env.NODE_ENV == "development") { |
| | | hostname = "localhost"; |
| | | } else { |
| | | hostname = serviceIp; |
| | | _port = window.location.port; |
| | | } |
| | | // if (process.env.NODE_ENV == "development") { |
| | | // hostname = "localhost"; |
| | | // } else { |
| | | // } |
| | | |
| | | hostname = serverIp; |
| | | // _port = window.location.port; |
| | | |
| | | // 处理端口为80 |
| | | _port = _port == 80 ? "" : ":" + _port; |
| | | return wsProtocol + hostname + _port + "/bl/" + action; |
New file |
| | |
| | | |
| | | import { Capacitor } from '@capacitor/core'; // 如果你使用的是Capacitor |
| | | import { Geolocation } from '@capacitor/geolocation'; |
| | | import { BluetoothLe } from '@capacitor-community/bluetooth-le'; |
| | | |
| | | export default async function requestLocationPermission() { |
| | | // 对于 Android,请求 BLUETOOTH 和 BLUETOOTH_CONNECT 权限 |
| | | if (Capacitor.getPlatform() === 'android') { |
| | | const permissionStatus = await Geolocation.requestPermissions(); |
| | | // console.log('BluetoothLe.requestPermissions', BluetoothLe, BluetoothLe.requestPermissions, '============='); |
| | | |
| | | try { |
| | | |
| | | const permissions = await BluetoothLe.requestPermissions(); |
| | | |
| | | console.log('permissions', permissions, '============='); |
| | | } catch (error) { |
| | | console.log('申请蓝牙权限失败', error, '============='); |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | // if (permissions.bleScan && permissions.bleAdvertise && permissions.bleConnect && permissionStatus.location === 'granted') { |
| | | if (permissionStatus.location === 'granted') { |
| | | // 权限已授予,获取位置 |
| | | // const position = await Geolocation.getCurrentPosition(); |
| | | // console.log('Latitude:', position.coords.latitude); |
| | | // console.log('Longitude:', position.coords.longitude); |
| | | console.log('位置权限已授予'); |
| | | return true; |
| | | } else { |
| | | console.log('位置权限未授予'); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | // if (Capacitor.isNativePlatform() && Capacitor.getPlatform() === 'android') { // 如果你使用的是Capacitor |
| | | // let _premission = null; |
| | | // switch (type) { |
| | | // case 'scan': |
| | | // _premission = permissionInstance.PERMISSION.BLUETOOTH_SCAN; |
| | | // break; |
| | | // case 'connect': |
| | | // // _premission = permissionInstance.PERMISSION.BLUETOOTH |
| | | // _premission = 'android.permission.BLUETOOTH_CONNECT'; |
| | | // break; |
| | | // case 'location': |
| | | // _premission = permissionInstance.PERMISSION.ACCESS_FINE_LOCATION; |
| | | // break; |
| | | // } |
| | | // // const androidPermissions = await import('@ionic-native/android-permissions/ngx').then(mod => mod.AndroidPermissions); |
| | | |
| | | // // 检查是否已经获得位置权限 |
| | | // const hasPermission = await permissionInstance.checkPermission(_premission); |
| | | |
| | | // if (!hasPermission) { |
| | | // // 如果没有获得权限,则请求权限 |
| | | // const result = await permissionInstance.requestPermission(_premission); |
| | | |
| | | // console.log('result1312313123213213', result, '============='); |
| | | |
| | | // // 根据请求结果执行相应操作 |
| | | // if (result.hasPermission) { |
| | | // console.log('Location permission granted 12313213.'); |
| | | // // 继续执行需要位置权限的逻辑 |
| | | // return true; |
| | | // } else { |
| | | // console.error('Location permission denied.'); |
| | | // // 处理权限被拒绝的情况 |
| | | // // 注意:在某些情况下,用户可能选择了“不再询问”,此时可能需要引导用户到应用的设置页面手动授予权限 |
| | | // return false; |
| | | // } |
| | | // } else { |
| | | // console.log('Location permission already granted.'); |
| | | // // 继续执行需要位置权限的逻辑(如果之前没有执行的话) |
| | | // return true |
| | | // } |
| | | // } |
| | | // 对于iOS,通常不需要在这里显式请求权限,因为系统会在需要时提示用户 |
| | | // 但你可以在Info.plist中添加必要的权限描述以确保应用能够请求这些权限 |
| | | } |
New file |
| | |
| | | import { createPinia } from 'pinia'; |
| | | const pinia = createPinia(); |
| | | export default pinia; |
New file |
| | |
| | | // 节流 |
| | | export const throttle = function (fn, delay = 300){ |
| | | var lastTime, timer; |
| | | return function () { |
| | | var args = arguments; |
| | | var nowTime = Date.now(); |
| | | if(lastTime && nowTime - lastTime < delay){ |
| | | if (timer) clearTimeout(timer); |
| | | timer = setTimeout(function () { |
| | | lastTime = nowTime; |
| | | fn.apply(null, args); |
| | | }, delay) |
| | | }else{ |
| | | lastTime = nowTime; |
| | | fn.apply(null, args); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | export function formatAreaTree(item, ids, list) { |
| | | // parentId 不在id列表中; |
| | | // if (item.parentId === 0) { |
| | | if (ids.indexOf(item.parentId) === -1) { |
| | | list.push({ |
| | | label: item.areaName, |
| | | id: item.id, |
| | | data: item, |
| | | areaDescript: item.areaDescript, |
| | | charger: item.areaUsers.map((v) => v.uname).join(","), |
| | | children: [], |
| | | }); |
| | | } else { |
| | | let isCurrentChild = false; |
| | | for (let i = 0; i < list.length; i++) { |
| | | const listItem = list[i]; |
| | | if (listItem.id === item.parentId) { |
| | | isCurrentChild = true; |
| | | listItem.children.push({ |
| | | label: item.areaName, |
| | | id: item.id, |
| | | data: item, |
| | | areaDescript: item.areaDescript, |
| | | charger: item.areaUsers.map((v) => v.uname).join(","), |
| | | children: [], |
| | | }); |
| | | } |
| | | } |
| | | |
| | | for (let i = 0; i < list.length; i++) { |
| | | const listItem = list[i]; |
| | | if (!isCurrentChild && listItem.children !== 0) { |
| | | formatAreaTree(item, ids, listItem.children); |
| | | } |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | import pinia from './pinia.js'; |
| | | import { storeToRefs } from 'pinia' |
| | | import { useUserStore } from "@/stores/user.js"; |
| | | const { uname } = storeToRefs(useUserStore(pinia)); |
| | | |
| | | export default uname; |
New file |
| | |
| | | <template> |
| | | <svg viewBox="0 0 1024 1024" version="1.1" width="1em" height="1em" xmlns="http://www.w3.org/2000/svg" |
| | | fill="currentColor"> |
| | | <path |
| | | d="M786.9 319.1H609.5V189.8c0-34.9-28.4-63.4-63.3-63.4H289.5c-34.9 0-63.3 28.4-63.3 63.4v642.4c0 35 28.4 63.4 63.3 63.4h256.7c34.9 0 63.3-28.4 63.3-63.4V445.9h177.3c8.4 0 15.2-6.8 15.2-15.2v-96.4c0.1-8.3-6.7-15.2-15.1-15.2zM546.3 865.2H289.5c-18.2 0-32.9-14.8-32.9-33V189.8c0-18.2 14.8-33 32.9-33h256.7c18.1 0 32.9 14.8 32.9 33v129.3h-90.5c-18.1-20.4-43.5-32.1-70.8-32.1-52.6 0-95.4 42.8-95.4 95.5s42.8 95.5 95.4 95.5c27.3 0 52.7-11.7 70.8-32.1h90.5v386.3h0.1c0 18.2-14.8 33-32.9 33z m225.6-449.7H481.7c-0.3 0-0.6 0.2-0.9 0.2-1.1 0.1-2.2 0.3-3.2 0.7-0.9 0.3-1.7 0.5-2.5 0.9-0.9 0.4-1.6 1-2.4 1.6-0.8 0.6-1.6 1.2-2.2 2-0.2 0.3-0.6 0.4-0.8 0.7-12.5 16.6-31.3 26.1-51.7 26.1-35.9 0-65-29.2-65-65.1 0-35.9 29.2-65.1 65-65.1 20.3 0 39.2 9.5 51.7 26.1 0.2 0.3 0.5 0.4 0.8 0.7 0.7 0.8 1.5 1.4 2.4 2.1 0.8 0.6 1.5 1.1 2.3 1.6 0.8 0.4 1.7 0.6 2.6 0.9 1 0.3 2 0.6 3.1 0.6 0.3 0 0.6 0.2 1 0.2h290v65.8z"> |
| | | </path> |
| | | <path |
| | | d="M461.3 794.1c2.9-3.2 4.3-7.5 3.8-11.8l-10.2-91.2c14.5-9.1 26.3-23 26.3-51.6 0-15.9-5.9-30.4-15.6-41.6-11.6-13.4-28.7-21.8-47.7-21.8-34.9 0-63.3 28.4-63.3 63.4 0 20.6 9.9 39.3 26.3 51.1l-10.2 91.7c-0.5 4.3 0.9 8.6 3.8 11.8 2.9 3.2 7 5.1 11.3 5.1H450c4.3 0 8.4-1.8 11.3-5.1z m-37.7-110c0 0.1-0.1 0.3 0 0.4l9.4 84.4h-30.2v-0.1l9.3-83.2 0.1-1.2c0-0.2-0.1-0.3 0-0.4 0.1-1.2-0.1-2.3-0.3-3.4-0.1-0.8-0.1-1.7-0.4-2.5-0.3-1-0.9-1.8-1.4-2.6s-0.8-1.7-1.4-2.4c-0.6-0.7-1.4-1.2-2-1.7-0.8-0.7-1.6-1.4-2.6-2-0.1-0.1-0.2-0.2-0.4-0.3-1.9-0.9-3.6-2-5.2-3.1-8.5-6.1-13.5-15.8-13.5-26.5 0-16.8 12.6-30.7 28.8-32.7 1.4-0.2 2.7-0.3 4.1-0.3 18.2 0 32.9 14.8 32.9 33 0 18.5-6 23.4-18.7 29.6-0.1 0.1-0.2 0.2-0.4 0.3-1 0.5-1.8 1.3-2.6 2-0.7 0.6-1.4 1-2 1.7-0.6 0.7-1 1.6-1.5 2.5s-1 1.6-1.3 2.6c-0.3 0.8-0.2 1.7-0.4 2.5-0.2 1.1-0.4 2.2-0.3 3.4z"> |
| | | </path> |
| | | </svg> |
| | | </template> |
| | |
| | | import { BluetoothLE } from "@ionic-native/bluetooth-le"; |
| | | import { ref, onMounted } from "vue"; |
| | | |
| | | import { Buffer } from "buffer"; |
| | | import requestLocationPermission from '@/assets/js/permissionUtils'; |
| | | |
| | | import CRC from "crc"; |
| | | |
| | |
| | | return; |
| | | } |
| | | // 初始化蓝牙 |
| | | BluetoothLE.initialize().subscribe( |
| | | BluetoothLE.initialize( |
| | | { |
| | | request: true, // 表示请求权限 |
| | | statusReceiver: false // 可以根据需要设置为 true 或 false |
| | | } |
| | | ).subscribe( |
| | | (result) => { |
| | | console.log("蓝牙初始化成功", result); |
| | | |
| | |
| | | } |
| | | }; |
| | | |
| | | const scan = async () => { |
| | | if (!MAC.value) { |
| | | console.log("MAC 为空"); |
| | | return false; |
| | | } |
| | | try { |
| | | return new Promise( |
| | | async (resolve, reject) => |
| | | await BluetoothLE.startScan().subscribe({ |
| | | next: (device) => { |
| | | console.log("发现设备", device); |
| | | if ( |
| | | device.address && |
| | | device.address.toUpperCase() === MAC.value |
| | | ) { |
| | | BluetoothLE.stopScan(); |
| | | resolve(device); |
| | | } |
| | | }, |
| | | error: (error) => { |
| | | console.error("扫描设备失败", error); |
| | | resolve(false); |
| | | }, |
| | | complete: () => { |
| | | console.log("扫描设备完成"); |
| | | }, |
| | | }) |
| | | ); |
| | | } catch (error) { |
| | | console.log("扫描失败", error); |
| | | return false; |
| | | } |
| | | }; |
| | | // const scan = async () => { |
| | | // if (!MAC.value) { |
| | | // console.log("MAC 为空"); |
| | | // return false; |
| | | // } |
| | | // try { |
| | | // return new Promise( |
| | | // async (resolve, reject) => |
| | | // await BluetoothLE.startScan().subscribe({ |
| | | // next: (device) => { |
| | | // console.log("发现设备", device); |
| | | // if ( |
| | | // device.address && |
| | | // device.address.toUpperCase() === MAC.value |
| | | // ) { |
| | | // BluetoothLE.stopScan(); |
| | | // resolve(device); |
| | | // } |
| | | // }, |
| | | // error: (error) => { |
| | | // console.error("扫描设备失败", error); |
| | | // resolve(false); |
| | | // }, |
| | | // complete: () => { |
| | | // console.log("扫描设备完成"); |
| | | // }, |
| | | // }) |
| | | // ); |
| | | // } catch (error) { |
| | | // console.log("扫描失败", error); |
| | | // return false; |
| | | // } |
| | | // }; |
| | | // const scan = async () => { |
| | | // if (!MAC.value) { |
| | | // console.log("MAC 为空"); |
| | | // return false; |
| | | // } |
| | | // try { |
| | | // let deviceFound = null; |
| | | // const subscription = BluetoothLE.startScan().subscribe({ |
| | | // next: (device) => { |
| | | // console.log("发现设备", device); |
| | | // if (device.address && device.address.toUpperCase() === MAC.value) { |
| | | // BluetoothLE.stopScan(); |
| | | // deviceFound = device; |
| | | // } |
| | | // }, |
| | | // error: (error) => { |
| | | // console.error("扫描设备失败", error); |
| | | // deviceFound = false; |
| | | // }, |
| | | // complete: () => { |
| | | // console.log("扫描设备完成"); |
| | | // } |
| | | // }); |
| | | // // 等待一段时间,例如 10 秒 |
| | | // await new Promise(resolve => setTimeout(resolve, 10000)); |
| | | // subscription.unsubscribe(); |
| | | // return deviceFound; |
| | | // } catch (error) { |
| | | // console.log("扫描失败", error); |
| | | // return false; |
| | | // } |
| | | // }; |
| | | |
| | | // 发现服务和 发现特征 |
| | | function getServer() { |
| | |
| | | }) |
| | | .then(() => { |
| | | console.log("写入数据成功"); |
| | | return true; |
| | | }) |
| | | .catch((error) => { |
| | | console.log("写入数据失败", error); |
| | | return false; |
| | | }); |
| | | } |
| | | |
| | |
| | | } |
| | | // TODO 鉴权 |
| | | |
| | | let a = await scan(); |
| | | console.log("a", a, "============="); |
| | | if (!a || !a.address || a.address.toUpperCase() != MAC.value) { |
| | | console.log("未发现设备", "============="); |
| | | // let a = await scan(); |
| | | // console.log("a", a, "============="); |
| | | // if (!a || !a.address || a.address.toUpperCase() != MAC.value) { |
| | | // console.log("未发现设备", "============="); |
| | | // return false; |
| | | // } |
| | | let hasPermission = await requestLocationPermission(); |
| | | if (!hasPermission) { |
| | | console.log("用户没有授予蓝牙连接权限", "============="); |
| | | return false; |
| | | } |
| | | let connected = await connect(); |
| | | let res = true; |
| | | console.log('connected', connected, '============='); |
| | | if (!connected) { |
| | | console.log("连接失败", "============="); |
| | | return false; |
| | | res = false; |
| | | } |
| | | let serv = await getServer(); |
| | | console.log("serv", serv, "============="); |
| | | let readRes = await read(); |
| | | console.log('readRes', readRes, '============='); |
| | | |
| | | |
| | | let writeRes = await write(); |
| | | console.log("writeRes", writeRes, "============="); |
| | | if (!writeRes) { |
| | | console.log("写入失败", "============="); |
| | | res = false; |
| | | } |
| | | disconnect(); |
| | | close(); |
| | | setMac(""); |
| | | |
| | | return res; |
| | | // return new Promise( async (resolve, reject) => { |
| | | // // let connected = await connect(); |
| | | // // console.log("connected", connected, "============="); |
| | |
| | | _initBL(); |
| | | }); |
| | | |
| | | return { read, write, close, scan, connect, disconnect, open, setMac }; |
| | | return { read, write, close, connect, disconnect, open, setMac }; |
| | | }; |
New file |
| | |
| | | import axios from '@/assets/js/axios'; |
| | | import uname from '@/assets/js/uname'; |
| | | |
| | | /** |
| | | * 获取当前用户是否有指定mac地址的锁具的蓝牙开锁权限 |
| | | * mac 全部转为大写 |
| | | */ |
| | | export function getPrivilegeByMac(mac) { |
| | | return axios({ |
| | | method: "GET", |
| | | url: "authInf/getAuthByUidAndMac", |
| | | params: { |
| | | mac, |
| | | uname: uname.value |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 开锁后 告诉后台开锁状态 便于记录 |
| | | */ |
| | | export function setLogByUid(mac, result) { |
| | | return axios({ |
| | | method: "GET", |
| | | url: "app/setLogByUid", |
| | | params: { |
| | | mac, |
| | | result, |
| | | uname: uname.value |
| | | } |
| | | }); |
| | | } |
| | |
| | | <script setup> |
| | | import { ref } from "vue"; |
| | | import { RouterView } from "vue-router"; |
| | | import scanView from "../views/home/viewer.vue"; |
| | | import iconBluetooth from "@/components/icons/iconBluetooth.vue"; |
| | | import { Camera, CameraResultType, CameraSource } from "@capacitor/camera"; |
| | | import useBLELock from "@/hooks/useBLELock"; |
| | | import useElemnetVant from "@/hooks/useElementVant.js"; |
| | | const { $loading , $toast} = useElemnetVant(); |
| | | const active = ref("home"); |
| | | const viewerVisible = ref(false); |
| | | const { open, setMac, close } = useBLELock(); |
| | | import { ref, watch } from "vue"; |
| | | import { RouterView, useRoute } from "vue-router"; |
| | | import scanView from "../views/home/viewer.vue"; |
| | | import iconBluetooth from "@/components/icons/iconBluetooth.vue"; |
| | | import { Camera, CameraResultType, CameraSource } from "@capacitor/camera"; |
| | | import useBLELock from "@/hooks/useBLELock"; |
| | | import useElemnetVant from "@/hooks/useElementVant.js"; |
| | | import { useUserStore } from "@/stores/user.js"; |
| | | import { getPrivilegeByMac, setLogByUid } from './apis'; |
| | | const { $loading, $toast } = useElemnetVant(); |
| | | const active = ref("home"); |
| | | const viewerVisible = ref(false); |
| | | const { open, setMac, close } = useBLELock(); |
| | | const route = useRoute(); |
| | | const { urole } = useUserStore(); |
| | | |
| | | // 扫码成功 (文件/string) 得到蓝牙Mac; 请求后台是否有此锁的权限? 有就连接 开锁; 没有就提示 |
| | | const gotQrCode = (data) => { |
| | | // state.qrCodeVisible = false |
| | | viewerVisible.value = false; |
| | | // if (!_.isEmpty(data)) { |
| | | // audio.play(); |
| | | // 处理结果 |
| | | // state.result = data |
| | | console.log("data", data, "============="); |
| | | // 验证是我们的蓝牙Mac |
| | | let reg = /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/; |
| | | if (!reg.test(data)) { |
| | | $toast("不是我们的锁具的二维码 无法开锁"); |
| | | return false; |
| | | } |
| | | |
| | | setMac(data); |
| | | let loading = $loading(); |
| | | // close(); |
| | | open() |
| | | .then((res) => { |
| | | loading.close(); |
| | | }) |
| | | .catch((err) => { |
| | | console.log(err); |
| | | loading.close(); |
| | | }); |
| | | // 扫码成功 (文件/string) 得到蓝牙Mac; 请求后台是否有此锁的权限? 有就连接 开锁; 没有就提示 |
| | | const gotQrCode = async (data) => { |
| | | // state.qrCodeVisible = false |
| | | viewerVisible.value = false; |
| | | // if (!_.isEmpty(data)) { |
| | | // audio.play(); |
| | | // 处理结果 |
| | | // state.result = data |
| | | console.log("data", data, "============="); |
| | | // 验证是我们的蓝牙Mac |
| | | let reg = /^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$/; |
| | | if (!reg.test(data)) { |
| | | $toast("二维码有误 无法开锁"); |
| | | return false; |
| | | } |
| | | |
| | | // 这里可以进行后续处理,比如发送请求... |
| | | // } |
| | | }; |
| | | $toast('正在查询权限'); |
| | | let pri = await getPrivilege(data.toUpperCase()); |
| | | if (!pri) { |
| | | $toast('没有权限, 拒绝开锁'); |
| | | setLogByUid(data.toUpperCase(), 0); |
| | | return false; |
| | | } |
| | | |
| | | async function checkCameraPermissions() { |
| | | try { |
| | | const permissionStatus = await Camera.checkPermissions(); |
| | | console.log("相机权限检查结果:", permissionStatus, "============="); |
| | | $toast('权限验证通过, 正在开锁'); |
| | | setMac(data); |
| | | let loading = $loading(); |
| | | // close(); |
| | | open() |
| | | .then((res) => { |
| | | loading.close(); |
| | | if (res) { |
| | | setLogByUid(data.toUpperCase(), 1); |
| | | $toast('开锁成功'); |
| | | } else { |
| | | $toast('开锁失败'); |
| | | setLogByUid(data.toUpperCase(), 0); |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.log(err); |
| | | $toast('开锁失败'); |
| | | setLogByUid(data.toUpperCase(), 0); |
| | | loading.close(); |
| | | }); |
| | | |
| | | if (permissionStatus.camera === "granted") { |
| | | // 相机权限已授予,可以进行相机操作 |
| | | console.log("相机权限已授予"); |
| | | } else if (permissionStatus.camera === "denied") { |
| | | // 相机权限被拒绝,你可能需要提示用户开启权限 |
| | | console.log("相机权限被拒绝"); |
| | | } else if (permissionStatus.camera === "prompt") { |
| | | // 可能需要重新请求权限,这可能是因为权限状态不确定 |
| | | console.log("可能需要重新请求相机权限"); |
| | | } |
| | | } catch (error) { |
| | | console.error("检查相机权限时出错:", error); |
| | | } |
| | | } |
| | | // 这里可以进行后续处理,比如发送请求... |
| | | // } |
| | | }; |
| | | |
| | | /** |
| | | * 点蓝牙开锁 先扫描二维码 获取到设备id 然后再判断有没有权限开锁 |
| | | */ |
| | | async function scanQr() { |
| | | console.log("scanQr"); |
| | | await checkCameraPermissions(); |
| | | viewerVisible.value = true; |
| | | } |
| | | async function getPrivilege(data) { |
| | | |
| | | let res = await getPrivilegeByMac(data); |
| | | let { code, data: result } = res.data; |
| | | if (code) { |
| | | |
| | | return result; |
| | | } else { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | |
| | | async function checkCameraPermissions() { |
| | | try { |
| | | const permissionStatus = await Camera.checkPermissions(); |
| | | // console.log("相机权限检查结果:", permissionStatus, "============="); |
| | | |
| | | if (permissionStatus.camera === "granted") { |
| | | // 相机权限已授予,可以进行相机操作 |
| | | console.log("相机权限已授予"); |
| | | } else if (permissionStatus.camera === "denied") { |
| | | // 相机权限被拒绝,你可能需要提示用户开启权限 |
| | | console.log("相机权限被拒绝"); |
| | | } else if (permissionStatus.camera === "prompt") { |
| | | // 可能需要重新请求权限,这可能是因为权限状态不确定 |
| | | console.log("可能需要重新请求相机权限"); |
| | | } |
| | | } catch (error) { |
| | | console.error("检查相机权限时出错:", error); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 点蓝牙开锁 先扫描二维码 获取到设备id 然后再判断有没有权限开锁 |
| | | */ |
| | | async function scanQr() { |
| | | console.log("scanQr"); |
| | | await checkCameraPermissions(); |
| | | viewerVisible.value = true; |
| | | } |
| | | |
| | | // 监听路由变化 |
| | | watch( |
| | | () => route.path, |
| | | (newPath) => { |
| | | switch (newPath) { |
| | | case "/home": |
| | | active.value = "home"; |
| | | break; |
| | | case "/monitor": |
| | | active.value = "search"; |
| | | break; |
| | | case "/user": |
| | | active.value = "user"; |
| | | break; |
| | | case '/alarm': |
| | | active.value = 'alarm'; |
| | | break; |
| | | default: |
| | | active.value = "home"; |
| | | } |
| | | }, |
| | | { immediate: true } |
| | | ); |
| | | |
| | | |
| | | </script> |
| | | |
| | | <template> |
| | |
| | | </div> |
| | | <div class="footer"> |
| | | <van-tabbar v-model="active" :fixed="false"> |
| | | <van-tabbar-item name="home" icon="home-o">首页</van-tabbar-item> |
| | | <van-tabbar-item name="search" icon="search">监控</van-tabbar-item> |
| | | <van-tabbar-item name="lock" @click="scanQr" |
| | | ><span>蓝牙开锁</span |
| | | ><template #icon |
| | | ><el-icon class="ico"><icon-bluetooth /></el-icon></template |
| | | ></van-tabbar-item> |
| | | <van-tabbar-item name="alarm" icon="warning-o">告警</van-tabbar-item> |
| | | <van-tabbar-item name="user" icon="user-o">我的</van-tabbar-item> |
| | | <van-tabbar-item name="home" replace to="/home" icon="home-o" v-if="urole > 0">首页</van-tabbar-item> |
| | | <van-tabbar-item name="search" replace to="/monitor" icon="search">监控</van-tabbar-item> |
| | | <van-tabbar-item name="lock" @click="scanQr"><span>蓝牙开锁</span><template #icon><el-icon |
| | | class="ico"><icon-bluetooth /></el-icon></template></van-tabbar-item> |
| | | <van-tabbar-item name="alarm" replace to="/alarm" icon="warning-o" v-if="urole > 0">告警</van-tabbar-item> |
| | | <van-tabbar-item name="user" replace to="/user" icon="user-o">我的</van-tabbar-item> |
| | | </van-tabbar> |
| | | </div> |
| | | <!-- 扫描 --> |
| | | <div class="viewer" v-if="viewerVisible"> |
| | | <scan-view @on-success="gotQrCode" /> |
| | | <scan-view @on-success="gotQrCode" @on-close="viewerVisible = false" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | display: flex; |
| | | color: #000; |
| | | flex-direction: column; |
| | | |
| | | .p-contain { |
| | | flex: 1; |
| | | position: relative; |
| | | } |
| | | |
| | | .footer { |
| | | margin-top: 12px; |
| | | |
| | | :deep(.van-tabbar::after) { |
| | | content: none; |
| | | } |
| | | |
| | | .ico { |
| | | padding: 4px; |
| | | color: #fff; |
| | |
| | | outline: 6px solid rgba(18, 150, 219, 0.3); |
| | | } |
| | | } |
| | | |
| | | .viewer { |
| | | position: fixed; |
| | | z-index: 9; |
| | |
| | | top: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: rgba(0, 0, 0, 0.8); |
| | | } |
| | | } |
| | | </style> |
| | |
| | | import "./styles/global.less"; |
| | | |
| | | import { createApp } from "vue"; |
| | | import { createPinia } from "pinia"; |
| | | // import { createPinia } from "pinia"; |
| | | import pinia from "./assets/js/pinia.js"; |
| | | |
| | | import ElementPlus from "element-plus"; |
| | | import VueVirtualScroller from 'vue-virtual-scroller' |
| | | |
| | | import * as ElementPlusIconsVue from "@element-plus/icons-vue"; |
| | | import "element-plus/dist/index.css"; |
| | | import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'; |
| | | import zhCn from "element-plus/es/locale/lang/zh-cn"; |
| | | import { BluetoothLE } from '@ionic-native/bluetooth-le'; |
| | | // import { BluetoothLE } from '@ionic-native/bluetooth-le'; |
| | | |
| | | import "@/assets/js/axios"; |
| | | // import "@/assets/js/axios"; |
| | | import "@/permission"; |
| | | |
| | | // Toast |
| | |
| | | |
| | | import App from "./App.vue"; |
| | | import router from "./router"; |
| | | import "@/permission"; |
| | | |
| | | const app = createApp(App); |
| | | |
| | | app.use(BluetoothLE); |
| | | // app.use(BluetoothLE); |
| | | app.use(VueVirtualScroller); |
| | | // app.config.globalProperties.$message = ElMessage; |
| | | |
| | | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { |
| | | app.component(key, component); |
| | | } |
| | | |
| | | app.use(createPinia()); |
| | | // app.use(createPinia()); |
| | | app.use(pinia); |
| | | // app.use(ElementPlus); |
| | | app.use(ElementPlus, { locale: zhCn }); |
| | | app.use(router); |
| | |
| | | import router from './router' |
| | | import { ElMessage } from "element-plus"; |
| | | import NProgress from 'nprogress' // progress bar |
| | | import 'nprogress/nprogress.css' // progress bar style |
| | | // import NProgress from 'nprogress' // progress bar |
| | | // import 'nprogress/nprogress.css' // progress bar style |
| | | |
| | | NProgress.configure({ showSpinner: false }) // NProgress Configuration |
| | | // NProgress.configure({ showSpinner: false }) // NProgress Configuration |
| | | |
| | | const whiteList = ['/login'] // no redirect whitelist |
| | | router.beforeEach(async (to, from, next) => { |
| | |
| | | if (to.path === '/login') { |
| | | // if is logged in, redirect to the home page |
| | | next({ path: '/' }) |
| | | NProgress.done() |
| | | // NProgress.done() |
| | | } else { |
| | | if (username) { |
| | | next() |
| | |
| | | } catch (error) { |
| | | ElMessage.error(error || 'Has Error') |
| | | next(`/login?redirect=${to.path}`) |
| | | NProgress.done() |
| | | // NProgress.done() |
| | | } |
| | | } |
| | | } |
| | |
| | | next() |
| | | } else { |
| | | next(`/login?redirect=${to.path}`) |
| | | NProgress.done() |
| | | // NProgress.done() |
| | | } |
| | | } |
| | | }) |
| | | |
| | | router.afterEach(() => { |
| | | // finish progress bar |
| | | NProgress.done() |
| | | // NProgress.done() |
| | | }) |
| | |
| | | component: layout, |
| | | children: [ |
| | | { |
| | | path: "/home", |
| | | path: "home", |
| | | name: "home", |
| | | component: () => import("@/views/home/index.vue"), |
| | | }, |
| | | // 监控 |
| | | { |
| | | path: "monitor", |
| | | name: "monitor", |
| | | component: () => import("@/views/monitor/index.vue"), |
| | | }, |
| | | // 告警 |
| | | { |
| | | path: "alarm", |
| | | name: "alarm", |
| | | component: () => import("@/views/alarm/index.vue"), |
| | | }, |
| | | // 用户 |
| | | { |
| | | path: "user", |
| | | name: "user", |
| | | component: () => import("@/views/user/index.vue"), |
| | | }, |
| | | ], |
| | | }, |
| | |
| | | name: "login", |
| | | component: () => import("@/views/login/index.vue"), |
| | | }, |
| | | // 测试 |
| | | // { |
| | | // path: "/test", |
| | | // name: "test", |
| | | // component: test, |
| | | // }, |
| | | ], |
| | | }); |
| | | |
| | |
| | | state() { |
| | | return { |
| | | uid: localStorage.getItem("uid"), |
| | | uname: localStorage.getItem("uname") |
| | | uname: localStorage.getItem("uname"), |
| | | urole: localStorage.getItem("urole"), |
| | | serverIp: localStorage.getItem("serverIp") || '192.168.10.82', |
| | | }; |
| | | }, |
| | | actions: { |
| | |
| | | this.uid = value; |
| | | localStorage.setItem("uid", value); |
| | | }, |
| | | setRole(value) { |
| | | this.urole = value; |
| | | localStorage.setItem("urole", value); |
| | | }, |
| | | setIp(ip) { |
| | | this.serverIp = ip; |
| | | localStorage.setItem("serverIp", ip); |
| | | } |
| | | }, |
| | | getters: {}, |
| | | }); |
New file |
| | |
| | | import axios from '@/assets/js/axios'; |
| | | import uname from '@/assets/js/uname'; |
| | | |
| | | /** |
| | | * 查询区域管理员点击查看开锁操作异常记录 |
| | | */ |
| | | export function getAlarmList(pageNum, pageSize) { |
| | | return axios({ |
| | | url: 'app/getCtlog', |
| | | method: 'GET', |
| | | params: { |
| | | pageNum, |
| | | pageSize, |
| | | uname: uname.value |
| | | } |
| | | }); |
| | | } |
New file |
| | |
| | | <script setup> |
| | | import { ref, onMounted } from "vue"; |
| | | import { getAlarmList } from './apis'; |
| | | import { throttle } from '@/assets/js/tools/throttle'; |
| | | |
| | | const sum = ref(0); |
| | | const errorNum = ref(0); |
| | | const pageCurr = ref(1); |
| | | const pageSize = 10; |
| | | const loading = ref(false); |
| | | const hasNextPage = ref(false); |
| | | |
| | | const list = ref([]); |
| | | |
| | | function getList() { |
| | | loading.value = true; |
| | | getAlarmList(pageCurr.value, pageSize).then((res) => { |
| | | let { code, data, data2 } = res.data; |
| | | let _list = []; |
| | | let _sum = 0, |
| | | _error = 0; |
| | | let hasNext = false; |
| | | if (code && data) { |
| | | console.log(data2); |
| | | _sum = data2.sumLog; |
| | | _error = data2.errorLogNum; |
| | | let logs = data2.allLogs; |
| | | _list = logs.list.map(v => { |
| | | v.lockPath = v.areaInf?.areaPath; |
| | | return v; |
| | | }); |
| | | hasNext = logs.hasNextPage; |
| | | } |
| | | list.value = [...list.value, ..._list]; |
| | | sum.value = _sum; |
| | | errorNum.value = _error; |
| | | hasNextPage.value = hasNext; |
| | | loading.value = false; |
| | | }) |
| | | .catch((err) => { |
| | | loading.value = false; |
| | | console.log(err); |
| | | }); |
| | | |
| | | } |
| | | const handleScroll = (event) => { |
| | | const scroller = event.target; |
| | | if (loading.value) { |
| | | return true; |
| | | } |
| | | // 是否有下一页 如果没有则退出 |
| | | if (!hasNextPage.value) { |
| | | return false; |
| | | } |
| | | |
| | | if (scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 100) { |
| | | // 滚动位置接近底部(这里设置了100px的缓冲距离),加载下一页数据 |
| | | loadNextPage(); |
| | | } |
| | | }; |
| | | |
| | | const throttleHandleScroll = throttle(handleScroll, 1500); |
| | | |
| | | |
| | | function loadNextPage() { |
| | | pageCurr.value++; |
| | | getList(); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="p-alarm"> |
| | | <div class="title">实时告警</div> |
| | | <div class="info"> |
| | | <div class="item"> |
| | | <div class="label">异常告警</div> |
| | | <div class="value">{{ sum }}</div> |
| | | |
| | | </div> |
| | | <div class="item"> |
| | | <div class="label">开锁异常</div> |
| | | <div class="value">{{ errorNum }}</div> |
| | | </div> |
| | | <div class="item"> |
| | | <div class="label">关锁异常</div> |
| | | <div class="value">--</div> |
| | | </div> |
| | | </div> |
| | | <div class="main"> |
| | | <div class="scroller"> |
| | | <RecycleScroller :class="['rec-scroller', {loading: loading, 'no-more': !loading && !hasNextPage}]" :items="list" :item-size="100" @touchmove="throttleHandleScroll" key-field="num" |
| | | v-slot="{ item }"> |
| | | <div class="item"> |
| | | <div class="station">{{ item.lockPath }}</div> |
| | | <div class="icon"></div> |
| | | <div class="lock-name">{{item.lockName}}</div> |
| | | <div class="lock-id">{{ item.lockId }}</div> |
| | | <div class="event">开锁异常</div> |
| | | <div class="date-time">{{ item.ctlTime }}</div> |
| | | </div> |
| | | </RecycleScroller> |
| | | </div> |
| | | <!-- 加载中 --> |
| | | <!-- <van-loading v-if="loading" size="24px">加载中...</van-loading> --> |
| | | <!-- 到底了 --> |
| | | <!-- <div class="no-more" v-if="!loading && !hasNextPage">没有更多了</div> --> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="less"> |
| | | .p-alarm { |
| | | height: 100%; |
| | | background: #f2f2f2 linear-gradient(#81d3f8, #81d3f8) top center / 100% 20% no-repeat; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .title { |
| | | height: 50px; |
| | | line-height: 50px; |
| | | text-align: center; |
| | | background: #81d3f8; |
| | | color: #027db4; |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .info { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | margin: 0 1.6em; |
| | | padding: 10px 0; |
| | | // background: #81d3f8; |
| | | background: #fff; |
| | | border-radius: 10px 10px 0 0; |
| | | color: #f00; |
| | | font-size: 14px; |
| | | font-weight: bold; |
| | | |
| | | .item { |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 14px; |
| | | |
| | | .label { |
| | | // color: #090; |
| | | |
| | | &::after { |
| | | content: ':'; |
| | | } |
| | | } |
| | | |
| | | .value { |
| | | margin-left: 0.6em; |
| | | // color: #027db4; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .main { |
| | | flex: 1; |
| | | position: relative; |
| | | background: #ddd; |
| | | |
| | | .scroller { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | overflow-y: auto; |
| | | |
| | | } |
| | | |
| | | .rec-scroller { |
| | | height: 100%; |
| | | &.loading { |
| | | &::after { |
| | | content: '加载中...'; |
| | | display: block; |
| | | text-align: center; |
| | | color: #999; |
| | | padding-bottom: 1em; |
| | | } |
| | | } |
| | | &.no-more { |
| | | &::after { |
| | | content: '--没有更多了--'; |
| | | display: block; |
| | | text-align: center; |
| | | color: #999; |
| | | padding-bottom: 1em; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item { |
| | | background: #f0f0f0; |
| | | padding: 10px 0; |
| | | margin-bottom: 10px; |
| | | display: grid; |
| | | gap: 4px; |
| | | grid-template-columns: 1fr 6fr 4fr; |
| | | grid-auto-rows: 24px 1.3fr 1fr; |
| | | |
| | | &::before { |
| | | content: ''; |
| | | } |
| | | |
| | | .station { |
| | | |
| | | // align-self: center; |
| | | grid-column: 1 e('/') 3; |
| | | grid-row: 1 e('/') 2; |
| | | font-size: 12px; |
| | | display: flex; |
| | | align-items: center; |
| | | background: #81d3f8; |
| | | border-radius: 0 20px 20px 0; |
| | | color: #333; |
| | | padding-left: 0.4em; |
| | | } |
| | | |
| | | .icon { |
| | | grid-column: 1 e('/') 2; |
| | | grid-row: 2 e('/') 4; |
| | | border-radius: 50%; |
| | | background: url("data:image/svg+xml,%3csvg viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M786.9 319.1H609.5V189.8c0-34.9-28.4-63.4-63.3-63.4H289.5c-34.9 0-63.3 28.4-63.3 63.4v642.4c0 35 28.4 63.4 63.3 63.4h256.7c34.9 0 63.3-28.4 63.3-63.4V445.9h177.3c8.4 0 15.2-6.8 15.2-15.2v-96.4c0.1-8.3-6.7-15.2-15.1-15.2zM546.3 865.2H289.5c-18.2 0-32.9-14.8-32.9-33V189.8c0-18.2 14.8-33 32.9-33h256.7c18.1 0 32.9 14.8 32.9 33v129.3h-90.5c-18.1-20.4-43.5-32.1-70.8-32.1-52.6 0-95.4 42.8-95.4 95.5s42.8 95.5 95.4 95.5c27.3 0 52.7-11.7 70.8-32.1h90.5v386.3h0.1c0 18.2-14.8 33-32.9 33z m225.6-449.7H481.7c-0.3 0-0.6 0.2-0.9 0.2-1.1 0.1-2.2 0.3-3.2 0.7-0.9 0.3-1.7 0.5-2.5 0.9-0.9 0.4-1.6 1-2.4 1.6-0.8 0.6-1.6 1.2-2.2 2-0.2 0.3-0.6 0.4-0.8 0.7-12.5 16.6-31.3 26.1-51.7 26.1-35.9 0-65-29.2-65-65.1 0-35.9 29.2-65.1 65-65.1 20.3 0 39.2 9.5 51.7 26.1 0.2 0.3 0.5 0.4 0.8 0.7 0.7 0.8 1.5 1.4 2.4 2.1 0.8 0.6 1.5 1.1 2.3 1.6 0.8 0.4 1.7 0.6 2.6 0.9 1 0.3 2 0.6 3.1 0.6 0.3 0 0.6 0.2 1 0.2h290v65.8z' fill='%232F3C42' %3e%3c/path%3e%3cpath d='M461.3 794.1c2.9-3.2 4.3-7.5 3.8-11.8l-10.2-91.2c14.5-9.1 26.3-23 26.3-51.6 0-15.9-5.9-30.4-15.6-41.6-11.6-13.4-28.7-21.8-47.7-21.8-34.9 0-63.3 28.4-63.3 63.4 0 20.6 9.9 39.3 26.3 51.1l-10.2 91.7c-0.5 4.3 0.9 8.6 3.8 11.8 2.9 3.2 7 5.1 11.3 5.1H450c4.3 0 8.4-1.8 11.3-5.1z m-37.7-110c0 0.1-0.1 0.3 0 0.4l9.4 84.4h-30.2v-0.1l9.3-83.2 0.1-1.2c0-0.2-0.1-0.3 0-0.4 0.1-1.2-0.1-2.3-0.3-3.4-0.1-0.8-0.1-1.7-0.4-2.5-0.3-1-0.9-1.8-1.4-2.6s-0.8-1.7-1.4-2.4c-0.6-0.7-1.4-1.2-2-1.7-0.8-0.7-1.6-1.4-2.6-2-0.1-0.1-0.2-0.2-0.4-0.3-1.9-0.9-3.6-2-5.2-3.1-8.5-6.1-13.5-15.8-13.5-26.5 0-16.8 12.6-30.7 28.8-32.7 1.4-0.2 2.7-0.3 4.1-0.3 18.2 0 32.9 14.8 32.9 33 0 18.5-6 23.4-18.7 29.6-0.1 0.1-0.2 0.2-0.4 0.3-1 0.5-1.8 1.3-2.6 2-0.7 0.6-1.4 1-2 1.7-0.6 0.7-1 1.6-1.5 2.5s-1 1.6-1.3 2.6c-0.3 0.8-0.2 1.7-0.4 2.5-0.2 1.1-0.4 2.2-0.3 3.4z' fill='%232F3C42'%3e%3c/path%3e%3c/svg%3e") center center / contain no-repeat; |
| | | // margin: 0 10px; |
| | | } |
| | | |
| | | .lock-name { |
| | | grid-area: 2 e('/') 2 e('/') 3 e('/') 3; |
| | | font-size: 16px; |
| | | color: #000; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .lock-id { |
| | | grid-area: 3 e('/') 2 e('/') 4 e('/') 3; |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | .event { |
| | | font-size: 14px; |
| | | color: #f00; |
| | | margin: 0 10px; |
| | | } |
| | | |
| | | .date-time { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | import axios from '@/assets/js/axios'; |
| | | import uname from '@/assets/js/uname'; |
| | | |
| | | /** |
| | | * 锁具动态 分页 首页用查第一页 |
| | | */ |
| | | export function getAllLogByUid(pageNum, pageSize) { |
| | | return axios({ |
| | | url: 'app/getAllLogByUid', |
| | | method: 'GET', |
| | | params: { |
| | | uname: uname.value, |
| | | pageNum, |
| | | pageSize |
| | | } |
| | | }); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 锁具告警 分页 首页用查第一页 |
| | | */ |
| | | export function getErrLogByUid(pageNum, pageSize) { |
| | | return axios({ |
| | | url: 'app/getErrLogByUid', |
| | | method: 'GET', |
| | | params: { |
| | | uname: uname.value, |
| | | pageNum, |
| | | pageSize |
| | | } |
| | | }); |
| | | } |
| | |
| | | <script setup> |
| | | import { ref, onMounted, inject, watch, reactive } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | import { ref, onMounted, inject, watch, reactive } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | |
| | | import { getAllLogByUid, getErrLogByUid } from './apis'; |
| | | import { getAreaLockById, getAinfByManage } from '../monitor/apis'; |
| | | |
| | | |
| | | const router = useRouter(); |
| | | const pageSize = 10; |
| | | const logList = ref([]); |
| | | const alarmList = ref([]); |
| | | const areaNum = ref(0); |
| | | const sum = ref(0); |
| | | const num_open = ref(0); |
| | | const num_close = ref(0); |
| | | async function getLogs() { |
| | | try { |
| | | let res = await getAllLogByUid(1, pageSize); |
| | | console.log('res', res, '============='); |
| | | const { code, data, data2 } = res.data; |
| | | let list = []; |
| | | if (code && data) { |
| | | list = data2.list; |
| | | } |
| | | logList.value = list; |
| | | |
| | | const router = useRouter(); |
| | | } catch (error) { |
| | | console.log('error', error, '============='); |
| | | } |
| | | |
| | | } |
| | | |
| | | async function getAlarms() { |
| | | try { |
| | | let res = await getErrLogByUid(1, pageSize); |
| | | console.log('res', res, '============='); |
| | | const { code, data, data2 } = res.data; |
| | | let list = []; |
| | | if (code && data) { |
| | | list = data2.list; |
| | | } |
| | | alarmList.value = list; |
| | | |
| | | } catch (error) { |
| | | console.log('error', error, '============='); |
| | | |
| | | } |
| | | } |
| | | |
| | | async function getAreas() { |
| | | try { |
| | | let res = await getAinfByManage(); |
| | | const { code, data, data2 } = res.data; |
| | | let _data = []; |
| | | let len = 0; |
| | | if (code && data) { |
| | | _data = data2; |
| | | len = data2.length; |
| | | } |
| | | areaNum.value = len; |
| | | if (len) { |
| | | getLocks(_data[0].id); |
| | | } |
| | | } catch (error) { |
| | | console.log('error', error, '============='); |
| | | } |
| | | } |
| | | |
| | | function getLocks(id) { |
| | | getAreaLockById(id).then((res) => { |
| | | let { code, data, data2 } = res.data; |
| | | let _sum = 0, |
| | | _num_open = 0, |
| | | _num_close = 0, |
| | | _num_online = 0, |
| | | _num_unLoad = 0, |
| | | _num_offline = 0; |
| | | let list = []; |
| | | if (code && data) { |
| | | _sum = data2.sumLinf; |
| | | _num_open = data2.openNum; |
| | | _num_close = data2.closeNum; |
| | | _num_online = data2.onlineNum; |
| | | _num_offline = data2.offLineNum; |
| | | _num_unLoad = data2.unLoadNum; |
| | | } |
| | | sum.value = _sum; |
| | | num_open.value = _num_open; |
| | | num_close.value = _num_close; |
| | | // num_online.value = _num_online; |
| | | // num_offline.value = _num_offline; |
| | | // num_unLoad.value = _num_unLoad; |
| | | }) |
| | | .catch((err) => { |
| | | console.log(err); |
| | | }); |
| | | |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getLogs(); |
| | | getAlarms(); |
| | | getAreas(); |
| | | }); |
| | | |
| | | </script> |
| | | |
| | |
| | | <!-- 头部 --> |
| | | <div class="card header"> |
| | | <div class="title">鸿蒙电子智能锁</div> |
| | | |
| | | |
| | | <div class=""> |
| | | <span>安全智能</span> |
| | | <span>操作便捷</span> |
| | |
| | | <div class="card disc"> |
| | | <div class="dis-item"> |
| | | <div class="icon house"></div> |
| | | <div class="name">管理机房</div> |
| | | <div class="num">4</div> |
| | | <div class="name">管理区域</div> |
| | | <div class="num">{{ areaNum }}</div> |
| | | </div> |
| | | <div class="dis-item"> |
| | | <div class="icon lock-ai"></div> |
| | | <div class="name">管理锁具</div> |
| | | <div class="num">4</div> |
| | | <div class="num">{{ sum }}</div> |
| | | </div> |
| | | <div class="dis-item open"> |
| | | <div class="icon lock-open"></div> |
| | | <div class="name">当前开启</div> |
| | | <div class="num">4</div> |
| | | <div class="num">{{ num_open }}</div> |
| | | </div> |
| | | <div class="dis-item close"> |
| | | <div class="icon lock-close"></div> |
| | | <div class="name">当前关闭</div> |
| | | <div class="num">4</div> |
| | | <div class="num">{{ num_close }}</div> |
| | | </div> |
| | | </div> |
| | | <!-- 锁具告警 --> |
| | |
| | | <!-- 滚动区 --> |
| | | <div class="scroll-wraper posR"> |
| | | <div class="scroll pos-full"> |
| | | <div class="alarm-item"> |
| | | <div class="date">2024-11-06</div> |
| | | <div class="time">17:25:10</div> |
| | | <div class="id">ID2020000226514</div> |
| | | <div class="lockName">蓝牙机柜锁1</div> |
| | | <div class="state">开锁异常</div> |
| | | </div> |
| | | <div class="alarm-item"> |
| | | <div class="date">2024-11-06</div> |
| | | <div class="time">17:25:10</div> |
| | | <div class="id">ID2020000226514</div> |
| | | <div class="lockName">蓝牙机柜锁1</div> |
| | | <div class="state">开锁异常</div> |
| | | </div> |
| | | <div class="alarm-item"> |
| | | <div class="date">2024-11-06</div> |
| | | <div class="time">17:25:10</div> |
| | | <div class="id">ID2020000226514</div> |
| | | <div class="lockName">蓝牙机柜锁1</div> |
| | | <div class="state">开锁异常</div> |
| | | </div> |
| | | <div class="alarm-item"> |
| | | <div class="date">2024-11-06</div> |
| | | <div class="time">17:25:10</div> |
| | | <div class="id">ID2020000226514</div> |
| | | <div class="lockName">蓝牙机柜锁1</div> |
| | | <div class="alarm-item" v-for="(item, idx) in alarmList" :key="'alarm_' + idx"> |
| | | <div class="date">{{ item.ctlTime }}</div> |
| | | <div class="id">{{ item.lockId }}</div> |
| | | <div class="lockName">{{ item.lockName }}</div> |
| | | <div class="state">开锁异常</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 更多告警 --> |
| | | <a href="javascript:void(0);" class="">更多告警</a> |
| | | <a href="javascript:void(0);" class="more" @click="router.push('/alarm')">更多告警</a> |
| | | </div> |
| | | <!-- 实时动态 --> |
| | | <div class="card dynamic"> |
| | | <div class="title-level1">实时动态</div> |
| | | <div class="scroll-wraper posR"> |
| | | <div class="scroll pos-full"> |
| | | <div class="rt-item"> |
| | | <div class="date">2024-11-06</div> |
| | | <div class="time">17:25:10</div> |
| | | <div class="id">ID2020000226514</div> |
| | | <div class="lockName">蓝牙机柜锁1</div> |
| | | <div class="state">开锁成功</div> |
| | | </div> |
| | | <div class="rt-item"> |
| | | <div class="date">2024-11-06</div> |
| | | <div class="time">17:25:10</div> |
| | | <div class="id">ID2020000226514</div> |
| | | <div class="lockName">蓝牙机柜锁1</div> |
| | | <div class="state">开锁成功</div> |
| | | </div> |
| | | <div class="rt-item"> |
| | | <div class="date">2024-11-06</div> |
| | | <div class="time">17:25:10</div> |
| | | <div class="id">ID2020000226514</div> |
| | | <div class="lockName">蓝牙机柜锁1</div> |
| | | <div class="state">闭锁成功</div> |
| | | <div class="rt-item" v-for="(item, idx) in logList" :key="'rt_' + idx"> |
| | | <div class="date">{{ item.ctlTime }}</div> |
| | | <div class="id">{{ item.lockId }}</div> |
| | | <div class="lockName">{{ item.lockName }}</div> |
| | | <div class="state">{{ item.ctlResult ? '开锁成功' : '开锁失败' }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .card { |
| | | margin-left: 6px; |
| | | margin-right: 6px; |
| | | margin-bottom: 12px; |
| | | padding: 6px; |
| | | background: #fff; |
| | | border-radius: 6px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | &.header { |
| | | display: flex; |
| | | flex-direction: column; |
| | |
| | | padding-left: 3em; |
| | | font-size: 12px; |
| | | color: #4f98f6; |
| | | background: #81d3f8; |
| | | background: #81d3f8 url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24' %3e %3cg fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' color='currentColor' %3e %3cpath d='m12.308 18l-1.461-4.521a.72.72 0 0 0-.693-.479a.72.72 0 0 0-.693.479L8 18m7-5v5m-6.462-1.5h3.231' %3e%3c/path%3e %3cpath d='M4.268 18.845c.225 1.67 1.608 2.979 3.292 3.056c1.416.065 2.855.099 4.44.099s3.024-.034 4.44-.1c1.684-.076 3.067-1.385 3.292-3.055c.147-1.09.268-2.207.268-3.345s-.121-2.255-.268-3.345c-.225-1.67-1.608-2.979-3.292-3.056A95 95 0 0 0 12 9c-1.585 0-3.024.034-4.44.1c-1.684.076-3.067 1.385-3.292 3.055C4.12 13.245 4 14.362 4 15.5s.121 2.255.268 3.345' %3e%3c/path%3e %3cpath d='M7.5 9V6.5a4.5 4.5 0 0 1 9 0V9'%3e%3c/path%3e %3c/g%3e %3c/svg%3e") right center / auto 50% no-repeat; |
| | | border-radius: 0; |
| | | margin-left: 0; |
| | | margin-right: 0; |
| | | flex: 36; |
| | | |
| | | .title { |
| | | font-weight: bold; |
| | | font-size: 20px; |
| | | } |
| | | |
| | | span { |
| | | display: inline-block; |
| | | margin-right: 2em; |
| | | } |
| | | } |
| | | |
| | | &.disc { |
| | | flex: 19.6; |
| | | display: grid; |
| | | grid-template-columns: repeat(4, 1fr); |
| | | gap: 10px; |
| | | |
| | | .dis-item { |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: center; |
| | | align-items: center; |
| | | |
| | | .icon { |
| | | background: #81d3f8; |
| | | background-repeat: no-repeat; |
| | |
| | | width: 18vw; |
| | | height: 18vw; |
| | | border-radius: 50%; |
| | | |
| | | &.house { |
| | | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3e%3cpath fill='currentColor' d='m12 5.15l-8 3.2V19h2v-6q0-.825.588-1.412T8 11h8q.825 0 1.413.588T18 13v6h2V8.35zM2 19V8.35q0-.625.338-1.125T3.25 6.5l8-3.2q.35-.15.75-.15t.75.15l8 3.2q.575.225.913.725T22 8.35V19q0 .825-.587 1.413T20 21h-4v-8H8v8H4q-.825 0-1.412-.587T2 19m7 2v-2h2v2zm2-3v-2h2v2zm2 3v-2h2v2zM8 11h8z'%3e%3c/path%3e%3c/svg%3e"); |
| | | } |
| | | |
| | | &.lock-ai { |
| | | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24' %3e %3cg fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' color='currentColor' %3e %3cpath d='m12.308 18l-1.461-4.521a.72.72 0 0 0-.693-.479a.72.72 0 0 0-.693.479L8 18m7-5v5m-6.462-1.5h3.231' %3e%3c/path%3e %3cpath d='M4.268 18.845c.225 1.67 1.608 2.979 3.292 3.056c1.416.065 2.855.099 4.44.099s3.024-.034 4.44-.1c1.684-.076 3.067-1.385 3.292-3.055c.147-1.09.268-2.207.268-3.345s-.121-2.255-.268-3.345c-.225-1.67-1.608-2.979-3.292-3.056A95 95 0 0 0 12 9c-1.585 0-3.024.034-4.44.1c-1.684.076-3.067 1.385-3.292 3.055C4.12 13.245 4 14.362 4 15.5s.121 2.255.268 3.345' %3e%3c/path%3e %3cpath d='M7.5 9V6.5a4.5 4.5 0 0 1 9 0V9'%3e%3c/path%3e %3c/g%3e %3c/svg%3e"); |
| | | } |
| | | |
| | | &.lock-open { |
| | | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3e%3cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10' stroke-width='1.5' d='M14 10V7c0-2.21 1.79-4 4-4s4 1.79 4 4v3M4.6 10h10.8c.88 0 1.6.72 1.6 1.6v7c0 1.32-1.08 2.4-2.4 2.4H5.4C4.08 21 3 19.92 3 18.6v-7c0-.88.72-1.6 1.6-1.6'%3e%3c/path%3e%3c/svg%3e"); |
| | | } |
| | | |
| | | &.lock-close { |
| | | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3e%3cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='10' stroke-width='1.5' d='M8 10V7c0-2.21 1.79-4 4-4s4 1.79 4 4v3m-9.4 0h10.8c.88 0 1.6.72 1.6 1.6v7c0 1.32-1.08 2.4-2.4 2.4H7.4C6.08 21 5 19.92 5 18.6v-7c0-.88.72-1.6 1.6-1.6'%3e%3c/path%3e%3c/svg%3e"); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | &.alarm { |
| | | flex: 29; |
| | | } |
| | | |
| | | &.dynamic { |
| | | flex: 29; |
| | | } |
| | | |
| | | .scroll-wraper { |
| | | flex: 1; |
| | | } |
| | | |
| | | .scroll { |
| | | // -webkit-overflow-scrolling: touch; |
| | | overflow-y: auto; |
| | | |
| | | .alarm-item, |
| | | .rt-item { |
| | | color: #7f7f7f; |
| | | padding: 4px 8px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .more { |
| | | text-align: center; |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | canvas2d.value.strokeStyle = color; |
| | | canvas2d.value.stroke(); |
| | | } |
| | | const emit = defineEmits(["on-success"]); |
| | | const emit = defineEmits(["on-success", "on-close"]); |
| | | |
| | | function getData(data) { |
| | | emit("on-success", data); |
| | | closeCamera(); |
| | | } |
| | | |
| | | function close() { |
| | | emit("on-close"); |
| | | closeCamera(); |
| | | } |
| | | |
| | |
| | | <template> |
| | | <div> |
| | | <div class="canvasBox"> |
| | | <div class="close" @click="close"> |
| | | <el-icon><CircleClose /></el-icon> |
| | | </div> |
| | | <div class="box"> |
| | | <div class="line"></div> |
| | | <div class="angle"></div> |
| | | </div> |
| | | <div v-if="isUseTorch" class="box2"> |
| | | <div class="track" @click="openTrack"> |
| | | <div class="flash-light" v-if="trackStatus"> |
| | | 00 |
| | | </div> |
| | | <div class="flash-light" v-else> |
| | | 11 |
| | | <div :class="['flash-light', {open : trackStatus}]"> |
| | | </div> |
| | | {{ trackStatus ? "关闭闪光灯" : "打开闪光灯" }} |
| | | </div> |
| | |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | background-image: linear-gradient( |
| | | /* background-image: linear-gradient( |
| | | 0deg, |
| | | transparent 24%, |
| | | rgba(32, 255, 77, 0.1) 25%, |
| | |
| | | transparent |
| | | ); |
| | | background-size: 3rem 3rem; |
| | | background-position: -1rem -1rem; |
| | | background-position: -1rem -1rem; */ |
| | | z-index: 10; |
| | | background-color: #1110; |
| | | } |
| | | |
| | | .flash-light { |
| | | width: 4em; |
| | | height: 4em; |
| | | background: url("data:image/svg+xml,%3csvg viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' fill='%23cdcdcd'%3e%3cpath d='M675.7 266.3H348.4c-46.1 0-83.6 37.5-83.6 83.6v96.4c0 70.9 33.3 137.2 89.6 179.7v275.2c0 46.1 37.5 83.7 83.7 83.7h148c46.1 0 83.7-37.5 83.7-83.7V626c56.3-42.5 89.6-108.8 89.6-179.7v-96.2c0.1-22.3-8.5-43.3-24.3-59.2-16-15.8-37-24.6-59.4-24.6z m-327.3 43.8h327.3c10.6 0 20.6 4.2 28.1 11.7 7.5 7.5 11.6 17.5 11.6 28.2v39.4H308.6V350c0-22 17.8-39.9 39.8-39.9z m287.1 286.5c-6 4.1-9.6 10.9-9.6 18.1v286.5c0 22-17.9 39.9-39.9 39.9H438c-22 0-39.9-17.9-39.9-39.9V614.8c0-7.3-3.6-14.1-9.6-18.1-49.5-33.4-79.1-88.7-79.8-148.2h406.6c-0.7 59.4-30.3 114.7-79.8 148.1z'%3e%3c/path%3e%3cpath d='M524 625.6h-23.8c-9.7 0-17.6 7.9-17.6 17.6v122.2c0 9.7 7.9 17.6 17.6 17.6H524c9.7 0 17.6-7.9 17.6-17.6V643.2c0-9.7-7.9-17.6-17.6-17.6z'%3e%3c/path%3e%3c/svg%3e") center center / contain no-repeat; |
| | | } |
| | | .flash-light.open { |
| | | background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' fill='%23cde919'%3e%3cpath d='M675.7 266.3H348.4c-46.1 0-83.6 37.5-83.6 83.6v96.4c0 70.9 33.3 137.2 89.6 179.7v275.2c0 46.1 37.5 83.7 83.7 83.7h148c46.1 0 83.7-37.5 83.7-83.7V626c56.3-42.5 89.6-108.8 89.6-179.7v-96.2c0.1-22.3-8.5-43.3-24.3-59.2-16-15.8-37-24.6-59.4-24.6z m-327.3 43.8h327.3c10.6 0 20.6 4.2 28.1 11.7 7.5 7.5 11.6 17.5 11.6 28.2v39.4H308.6V350c0-22 17.8-39.9 39.8-39.9z m287.1 286.5c-6 4.1-9.6 10.9-9.6 18.1v286.5c0 22-17.9 39.9-39.9 39.9H438c-22 0-39.9-17.9-39.9-39.9V614.8c0-7.3-3.6-14.1-9.6-18.1-49.5-33.4-79.1-88.7-79.8-148.2h406.6c-0.7 59.4-30.3 114.7-79.8 148.1z'%3e%3c/path%3e%3cpath d='M524 625.6h-23.8c-9.7 0-17.6 7.9-17.6 17.6v122.2c0 9.7 7.9 17.6 17.6 17.6H524c9.7 0 17.6-7.9 17.6-17.6V643.2c0-9.7-7.9-17.6-17.6-17.6zM306.7 239.3c11.5 11.5 30.3 11.5 41.8 0 11.5-11.5 11.5-30.3 0-41.8l-37.4-37.4c-11.5-11.5-30.3-11.5-41.8 0-11.5 11.5-11.5 30.3 0 41.8l37.4 37.4zM675.5 239.3c11.5 11.5 30.3 11.5 41.8 0l37.4-37.4c11.5-11.5 11.5-30.3 0-41.8-11.5-11.5-30.3-11.5-41.8 0l-37.4 37.4c-11.4 11.5-11.4 30.3 0 41.8zM512.1 164c16.2 0 29.5-13.3 29.5-29.5V81.6c0-16.2-13.3-29.5-29.5-29.5s-29.5 13.3-29.5 29.5v52.8c0 16.3 13.2 29.6 29.5 29.6z'%3e%3c/path%3e%3c/svg%3e"); |
| | | } |
| | | .box { |
| | | width: 11.9375rem; |
| | | height: 11.9375rem; |
| | |
| | | overflow: hidden; |
| | | border: 0.1rem solid rgba(0, 255, 51, 0.2); |
| | | z-index: 11; |
| | | } |
| | | .close { |
| | | position: absolute; |
| | | top: 2rem; |
| | | right: 1rem; |
| | | font-size: 2.25rem; |
| | | color: #fff; |
| | | z-index: 32; |
| | | } |
| | | |
| | | .line { |
| | |
| | | |
| | | .track { |
| | | position: absolute; |
| | | bottom: -6.25rem; |
| | | bottom: -8.25rem; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | z-index: 20; |
| | |
| | | usnId: encodeURIComponent(formatPassword(usnId)), |
| | | }, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 检查Ip的正确性 |
| | | */ |
| | | export function checkServerIp() { |
| | | return axios({ |
| | | url: 'heart/test', |
| | | method: 'GET' |
| | | }); |
| | | } |
| | |
| | | <script setup> |
| | | import { ref, reactive, watch, onMounted } from "vue"; |
| | | import useElementVant from "@/hooks/useElementVant"; |
| | | import { useUserStore } from "@/stores/user.js"; |
| | | import { useRouter, useRoute } from "vue-router"; |
| | | import { ref, reactive, watch, onMounted } from "vue"; |
| | | import useElementVant from "@/hooks/useElementVant"; |
| | | import { useUserStore } from "@/stores/user.js"; |
| | | import { useRouter, useRoute } from "vue-router"; |
| | | import { storeToRefs } from 'pinia' |
| | | |
| | | import { Dialog } from 'vant'; |
| | | import { Dialog } from 'vant'; |
| | | |
| | | const { $confirm, $message, $loading, $toast, Toast } = useElementVant(); |
| | | const { setName, setId, addRemember, removeRemember } = useUserStore(); |
| | | const { $confirm, $message, $loading, $toast, Toast } = useElementVant(); |
| | | const { setName, setId, setRole, setIp } = useUserStore(); |
| | | const { serverIp } = storeToRefs(useUserStore()); |
| | | |
| | | import { login } from "./apis"; |
| | | import { login, checkServerIp } from "./apis"; |
| | | |
| | | const router = useRouter(); |
| | | const route = useRoute(); |
| | | const router = useRouter(); |
| | | const route = useRoute(); |
| | | |
| | | const userName = ref(""); |
| | | const password = ref(""); |
| | | const platformName = ref("鸿蒙智能电子锁系统42334"); |
| | | const redirect = ref(); |
| | | const userName = ref(""); |
| | | const password = ref(""); |
| | | const platformName = ref("鸿蒙智能电子锁系统"); |
| | | const redirect = ref(); |
| | | const ipEditVisible = ref(false); |
| | | const ip = ref(''); |
| | | const ipState = ref('检测中...'); |
| | | let oldIp = ''; |
| | | |
| | | onMounted(() => { |
| | | let res = checkIp(serverIp.value); |
| | | if (res) { |
| | | ipState.value = '可用'; |
| | | } else { |
| | | ipState.value = '不可用'; |
| | | } |
| | | }); |
| | | watch( |
| | | () => route, |
| | | (route) => { |
| | | redirect.value = route.query && route.query.redirect; |
| | | }, |
| | | { |
| | | immediate: true, |
| | | } |
| | | ); |
| | | |
| | | onMounted(() => {}); |
| | | watch( |
| | | () => route, |
| | | (route) => { |
| | | redirect.value = route.query && route.query.redirect; |
| | | }, |
| | | { |
| | | immediate: true, |
| | | } |
| | | ); |
| | | // 登录 |
| | | function submit() { |
| | | if (userName.value == "") { |
| | | $toast("请输入账号!"); |
| | | return; |
| | | } |
| | | if (password.value == "") { |
| | | $toast("请输入密码!"); |
| | | return; |
| | | } |
| | | // 开启等待框 |
| | | Toast.loading({ |
| | | message: "登录中...", |
| | | duration: 0, |
| | | }); |
| | | login(userName.value, password.value) |
| | | .then((res) => { |
| | | // console.log(res); |
| | | let { code, data, data2, msg } = res.data; |
| | | // // 对结果进行处理 |
| | | if (code && data) { |
| | | $toast("登录成功!"); |
| | | handleLogin(data2); |
| | | } else { |
| | | $toast(msg); |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | console.log(error); |
| | | // 关闭等待 |
| | | // console.log(error); |
| | | $toast("网络异常" + error); |
| | | }); |
| | | } |
| | | // 登录验证 |
| | | function handleLogin(res) { |
| | | setId(res.uid); |
| | | setName(res.uname); |
| | | setRole(res.urole); |
| | | let url = res.urole != 0 ? '/home' : '/monitor'; |
| | | router.push({ path: redirect.value || url }); |
| | | } |
| | | |
| | | // 登录 |
| | | function submit() { |
| | | if (userName.value == "") { |
| | | $toast("请输入账号!"); |
| | | return; |
| | | } |
| | | if (password.value == "") { |
| | | $toast("请输入密码!"); |
| | | return; |
| | | } |
| | | // 开启等待框 |
| | | Toast.loading({ |
| | | message: "登录中...", |
| | | duration: 0, |
| | | }); |
| | | login(userName.value, password.value) |
| | | .then((res) => { |
| | | // console.log(res); |
| | | let {code,data,data2, msg} = res.data; |
| | | Dialog({ message: JSON.stringify(res.data) }); |
| | | // // 对结果进行处理 |
| | | if (code && data) { |
| | | $toast("登录成功!"); |
| | | handleLogin(data2); |
| | | } else { |
| | | $toast(msg); |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | console.log(error); |
| | | // 关闭等待 |
| | | // console.log(error); |
| | | $toast("网络异常" + error); |
| | | }); |
| | | } |
| | | // 登录验证 |
| | | function handleLogin(res) { |
| | | setId(res.uid); |
| | | setName(res.uname); |
| | | router.push({ path: redirect.value || "/home" }); |
| | | } |
| | | function initPageConfig() { |
| | | // getRealTabsConfig() |
| | | // .then((res) => { |
| | | // let rs = res?.data?.data || []; |
| | | // let arr = []; |
| | | // for (let key in rs) { |
| | | // arr.push(...rs[key]); |
| | | // } |
| | | // // 设置pageConfig |
| | | // this.$store.dispatch("user/changeRealTabsConfig", arr); |
| | | // // 设置用户的权限 |
| | | // this.$store.dispatch("user/getPermits"); |
| | | // this.$toast("登录成功!"); |
| | | // this.$router.push({ |
| | | // path: "/home", |
| | | // }); |
| | | // }) |
| | | // .catch((error) => { |
| | | // // 设置pageConfig |
| | | // this.$store.dispatch("user/changeRealTabsConfig", []); |
| | | // }); |
| | | } |
| | | function changeIp() { |
| | | console.log('serverIp', serverIp.value, '============='); |
| | | |
| | | ip.value = serverIp.value; |
| | | oldIp = serverIp.value; |
| | | ipEditVisible.value = true; |
| | | } |
| | | async function checkIp(Ip) { |
| | | let loading = $loading(); |
| | | try { |
| | | let res = await checkServerIp(Ip); |
| | | console.log('res', res, res.status, '============='); |
| | | loading.close(); |
| | | if (res.status == 200) { |
| | | return true; |
| | | } else { |
| | | return false; |
| | | } |
| | | } catch (error) { |
| | | console.log('error', error, '============='); |
| | | loading.close(); |
| | | return false; |
| | | } |
| | | |
| | | } |
| | | function editCancel() { |
| | | ipEditVisible.value = false; |
| | | } |
| | | |
| | | async function editConfirm() { |
| | | let _ip = ip.value.trim(); |
| | | // 校验ip |
| | | let reg = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; |
| | | if (!reg.test(_ip)) { |
| | | return $toast("请输入正确的IP地址!"); |
| | | } |
| | | setIp(_ip); |
| | | console.log('_ip', _ip, serverIp.value, '============='); |
| | | |
| | | ipState.value = '检测中...'; |
| | | let res = await checkIp(_ip); |
| | | |
| | | if (res) { |
| | | setIp(_ip); |
| | | ipState.value = '可用'; |
| | | ipEditVisible.value = false; |
| | | $toast('设置成功'); |
| | | } else { |
| | | ipState.value = '不可用'; |
| | | $toast('ip不通, 设置失败'); |
| | | setIp(oldIp); |
| | | } |
| | | } |
| | | function initPageConfig() { |
| | | // getRealTabsConfig() |
| | | // .then((res) => { |
| | | // let rs = res?.data?.data || []; |
| | | // let arr = []; |
| | | // for (let key in rs) { |
| | | // arr.push(...rs[key]); |
| | | // } |
| | | // // 设置pageConfig |
| | | // this.$store.dispatch("user/changeRealTabsConfig", arr); |
| | | // // 设置用户的权限 |
| | | // this.$store.dispatch("user/getPermits"); |
| | | // this.$toast("登录成功!"); |
| | | // this.$router.push({ |
| | | // path: "/home", |
| | | // }); |
| | | // }) |
| | | // .catch((error) => { |
| | | // // 设置pageConfig |
| | | // this.$store.dispatch("user/changeRealTabsConfig", []); |
| | | // }); |
| | | } |
| | | </script> |
| | | <template> |
| | | <div class="loginDiv"> |
| | |
| | | </div> |
| | | <div class="lineInput"> |
| | | <img src="../../assets/img/login-ico2.png" class="ico2" /> |
| | | <van-field |
| | | v-model="password" |
| | | placeholder="请输入密码" |
| | | type="password" |
| | | /> |
| | | <van-field v-model="password" placeholder="请输入密码" type="password" /> |
| | | </div> |
| | | <div class="subBtn" @click="submit">登录</div> |
| | | <div class="info"> |
| | | <div class="label">入口Ip</div> |
| | | <div class="value">{{ serverIp }} <span>({{ ipState }})</span></div> |
| | | <div class="btn" @click="changeIp">切换</div> |
| | | </div> |
| | | </div> |
| | | <el-dialog v-model="ipEditVisible" title="切换入口Ip" width="80%" align="center" center> |
| | | <van-field v-model="ip" placeholder="请输入服务器IP" /> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="editCancel">取消</el-button> |
| | | <el-button type="primary" @click="editConfirm"> |
| | | 确认 |
| | | </el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | font-size: 22px; |
| | | margin-top: 4vh; |
| | | } |
| | | |
| | | .info { |
| | | margin-top: 1.8em; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | |
| | | .label { |
| | | color: #999; |
| | | font-size: 14px; |
| | | |
| | | &::after { |
| | | content: ":"; |
| | | } |
| | | } |
| | | |
| | | .value { |
| | | color: #08aeec; |
| | | flex: 1; |
| | | padding-left: 0.4em; |
| | | } |
| | | |
| | | .btn { |
| | | color: #08aeec; |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | import axios from '@/assets/js/axios'; |
| | | import uname from '@/assets/js/uname'; |
| | | |
| | | /** |
| | | * 区域管理员登录查看自己管理的区域 树 |
| | | */ |
| | | export function getAinfByManage() { |
| | | return axios({ |
| | | method: "GET", |
| | | // url: "app/getAinfByManage", |
| | | url: 'areaInf/getAllAreaInf', |
| | | params: { |
| | | uname: uname.value |
| | | } |
| | | }); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 区域管理员点击指定区域查看所有的锁信息 |
| | | */ |
| | | export function getlinfByAid(id) { |
| | | return axios({ |
| | | method: "GET", |
| | | url: "app/getlinfByAid", |
| | | params: { |
| | | id, |
| | | uname: uname.value |
| | | }, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 查询区域管理员查看指定区域的锁的状态等信息 |
| | | * id 从所管理区域的树中获取 |
| | | */ |
| | | export function getAreaLockById(id) { |
| | | return axios({ |
| | | url: 'app/getAreaLockById', |
| | | method: 'GET', |
| | | params: { |
| | | id, |
| | | uname: uname.value |
| | | } |
| | | }); |
| | | } |
New file |
| | |
| | | <script setup name="monitor"> |
| | | import { ref, onMounted, computed, watchEffect } from "vue"; |
| | | import IconLock from "@/components/icons/iconLock.vue"; |
| | | import { useUserStore } from "@/stores/user.js"; |
| | | import { getAuthByUid } from '@/views/user/apis'; |
| | | import { getAinfByManage, getAreaLockById } from './apis'; |
| | | import { formatAreaTree } from '@/assets/js/tree.js'; |
| | | import useWebSocket from '@/hooks/useWebSocket'; |
| | | |
| | | const { urole, uname } = useUserStore(); |
| | | |
| | | let wsUrl = ''; |
| | | if (urole == 0) { |
| | | wsUrl = 'authUname' |
| | | } else { |
| | | wsUrl = 'areaLockState'; |
| | | } |
| | | |
| | | const { sendData, message } = useWebSocket(wsUrl); |
| | | |
| | | |
| | | const lockList = ref([]); |
| | | const areaVisible = ref(false); |
| | | const tableData = ref([]); |
| | | const tableRef = ref(); |
| | | const currentArea = ref(''); |
| | | const currentId = ref(''); |
| | | const sum = ref(0); |
| | | const num_open = ref(0); |
| | | const num_close = ref(0); |
| | | const num_online = ref(0); |
| | | const num_offline = ref(0); |
| | | const num_unLoad = ref(0); |
| | | |
| | | |
| | | |
| | | // const sum = computed(() => { |
| | | // return lockList.value.length; |
| | | // }); |
| | | |
| | | // const num_open = computed(() => { |
| | | // return lockList.value.filter(v => v.state == 1).length; |
| | | // }); |
| | | |
| | | // const num_close = computed(() => { |
| | | // return lockList.value.filter(v => v.state == 0).length; |
| | | // }); |
| | | |
| | | function getLockList(res) { |
| | | |
| | | let { code, data, data2 } = res; |
| | | let list = []; |
| | | let _sum = 0, |
| | | _num_open = 0, |
| | | _num_close = 0, |
| | | _num_online = 0, |
| | | _num_unLoad = 0, |
| | | _num_offline = 0; |
| | | if (code && data) { |
| | | console.log('data2', data2, '============='); |
| | | for ( let i = 0; i < data2.length; i++ ) { |
| | | let item = data2[i]; |
| | | list.push({ |
| | | ...item, |
| | | stateStr: ['闭锁', '开锁'][item.lockState] || '--', |
| | | }); |
| | | _num_close += item.lockState == 0 ? 1 : 0; |
| | | _num_open += item.lockState == 1 ? 1 : 0; |
| | | _num_unLoad += item.lockState == -1 ? 1 : 0; |
| | | _num_online += item.lockOnline == 1 ? 1 : 0; |
| | | _num_offline += item.lockOnline == 0 ? 1 : 0; |
| | | } |
| | | _sum = data2.length; |
| | | |
| | | } |
| | | sum.value = _sum; |
| | | num_open.value = _num_open; |
| | | num_close.value = _num_close; |
| | | num_online.value = _num_online; |
| | | num_offline.value = _num_offline; |
| | | num_unLoad.value = _num_unLoad; |
| | | |
| | | lockList.value = list; |
| | | |
| | | } |
| | | |
| | | function getAreas() { |
| | | getAinfByManage().then((res) => { |
| | | let { code, data, data2 } = res.data; |
| | | let _data = []; |
| | | if (code && data) { |
| | | _data = data2; |
| | | } |
| | | console.log('_data', _data, '============='); |
| | | |
| | | const treeList = []; |
| | | let ids = _data.map((v) => v.id); |
| | | for (let i = 0; i < _data.length; i++) { |
| | | formatAreaTree(_data[i], ids, treeList); |
| | | } |
| | | // console.log(_data, 'data'); |
| | | console.log(treeList, "treeList"); |
| | | tableData.value = treeList; |
| | | if (treeList.length > 0) { |
| | | select({ row: treeList[0] }); |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.log(err); |
| | | }); |
| | | |
| | | } |
| | | function select({ row }) { |
| | | areaVisible.value = false; |
| | | console.log(row, row.data); |
| | | currentArea.value = row.data.areaName; |
| | | currentId.value = row.data.id; |
| | | sendData(currentId.value); |
| | | } |
| | | |
| | | watchEffect(() => { |
| | | if (message.value) { |
| | | if (urole == 0) { |
| | | getLockList(JSON.parse(message.value)); |
| | | } else { |
| | | getLocks(JSON.parse(message.value)); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | |
| | | function getLocks(res) { |
| | | |
| | | let { code, data, data2 } = res; |
| | | let _sum = 0, |
| | | _num_open = 0, |
| | | _num_close = 0, |
| | | _num_online = 0, |
| | | _num_unLoad = 0, |
| | | _num_offline = 0; |
| | | let list = []; |
| | | if (code && data) { |
| | | // console.log('data2', data2, '============='); |
| | | |
| | | list = data2.allLinfs.map(v => ({ |
| | | ...v, |
| | | stateStr: ['闭锁', '开锁'][v.lockState] || '--', |
| | | })); |
| | | _sum = data2.sumLinf; |
| | | _num_open = data2.openNum; |
| | | _num_close = data2.closeNum; |
| | | _num_online = data2.onlineNum; |
| | | _num_offline = data2.offLineNum; |
| | | _num_unLoad = data2.unLoadNum; |
| | | } |
| | | lockList.value = list; |
| | | sum.value = _sum; |
| | | num_open.value = _num_open; |
| | | num_close.value = _num_close; |
| | | num_online.value = _num_online; |
| | | num_offline.value = _num_offline; |
| | | num_unLoad.value = _num_unLoad; |
| | | |
| | | } |
| | | |
| | | // 递归函数,用于展开或折叠所有行及其子行 |
| | | const toggleRowsExpansion = (rows, expand, ref) => { |
| | | rows.forEach((row) => { |
| | | ref.toggleRowExpansion(row, expand); |
| | | if (row.children && row.children.length) { |
| | | // 如果当前行有子行,则递归调用 |
| | | toggleRowsExpansion(row.children, expand, ref); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | |
| | | const expandAll = (expand) => { |
| | | toggleRowsExpansion(tableData.value, expand, tableRef.value); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | // 如果是管理员,获取授权用户列表 如果是普通用户 只能查出锁具列表 |
| | | if (urole == 0) { |
| | | // getLockList(); |
| | | sendData(uname); |
| | | } else { |
| | | getAreas(); |
| | | } |
| | | }) |
| | | |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="p-monitor"> |
| | | <div class="title"> |
| | | 实时监控 |
| | | </div> |
| | | <!-- 区域 --> |
| | | <div class="area-name" v-if="urole > 0" @click="areaVisible = true">{{ currentArea }}</div> |
| | | <!-- info --> |
| | | <div class="info"> |
| | | <!-- <div class="item"> --> |
| | | <div class="label">锁具</div> |
| | | <div class="value">{{ sum }}</div> |
| | | <!-- </div> --> |
| | | <!-- <div class="item"> --> |
| | | <div class="label">在线</div> |
| | | <div class="value">{{ num_online }}</div> |
| | | <!-- </div> --> |
| | | <!-- <div class="item"> --> |
| | | <div class="label">离线</div> |
| | | <div class="value">{{ num_offline }}</div> |
| | | <div class="label">未安装</div> |
| | | <div class="value">{{ num_unLoad }}</div> |
| | | <!-- </div> --> |
| | | <!-- <div class="item"> --> |
| | | <div class="label">开启</div> |
| | | <div class="value">{{ num_open }}</div> |
| | | <!-- </div> --> |
| | | <!-- <div class="item"> --> |
| | | <div class="label">关闭</div> |
| | | <div class="value">{{ num_close }}</div> |
| | | <!-- </div> --> |
| | | </div> |
| | | <!-- 列表 --> |
| | | <div class="list"> |
| | | <div class="scroller" v-if="lockList.length"> |
| | | <div class="lock-item" v-for="(lock, idx) in lockList" :key="'lock_' + idx"> |
| | | <div class="station">{{ lock.areaPath }}</div> |
| | | <div :class="['icon', { online: lock.lockOnline }]"> |
| | | <el-icon><icon-lock class="icon-lock" /></el-icon> |
| | | </div> |
| | | <div :class="['state1', { online: lock.lockOnline }]">{{ lock.lockOnline ? '在线' : '离线' }}</div> |
| | | <div class="lock-name">{{lock.lockName}}</div> |
| | | <div class="lock-id">{{ lock.lockId }}</div> |
| | | <div class="state">{{ lock.stateStr }}</div> |
| | | </div> |
| | | </div> |
| | | <van-empty v-else image-size="10rem" description="没有管理任何锁具" /> |
| | | </div> |
| | | <van-action-sheet v-model:show="areaVisible" title="选择区域"> |
| | | <template #description> |
| | | <van-button type="primary" size="small" @click="expandAll(true)">全部展开</van-button> |
| | | <van-button type="success" size="small" @click="expandAll(false)">全部折叠</van-button> |
| | | </template> |
| | | <div class="content"> |
| | | <el-table stripe ref="tableRef" :data="tableData" :show-header="false" |
| | | :tree-props="{ children: 'children', checkStrictly: true }" row-key="id" default-expand-all> |
| | | <!-- <el-table-column type="selection" width="55" /> --> |
| | | <el-table-column prop="label" label="区域名称" /> |
| | | <el-table-column align="center" fixed="right" label="操作" width="80"> |
| | | <template #default="scope"> |
| | | <el-button type="primary" size="small" @click="select(scope)">选择</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </van-action-sheet> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="less"> |
| | | .p-monitor { |
| | | height: 100%; |
| | | // background: #f2f2f2 linear-gradient(#81d3f8, #81d3f8) top center / 100% 20% no-repeat; |
| | | // padding: 10px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .title { |
| | | height: 50px; |
| | | line-height: 50px; |
| | | text-align: center; |
| | | background: #81d3f8; |
| | | color: #027db4; |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .area-name { |
| | | background: #81d3f8; |
| | | text-align: center; |
| | | font-weight: bold; |
| | | font-size: 18px; |
| | | } |
| | | |
| | | .info { |
| | | // display: flex; |
| | | // justify-content: space-around; |
| | | display: grid; |
| | | grid-template-columns: repeat(6, 1fr); |
| | | gap: 4px; |
| | | place-items: center; |
| | | padding: 10px; |
| | | background: #81d3f8; |
| | | border-radius: 0 0 8px 8px; |
| | | |
| | | // .item { |
| | | // display: flex; |
| | | // align-items: center; |
| | | // font-size: 16px; |
| | | |
| | | // } |
| | | .label { |
| | | color: #090; |
| | | justify-self: end; |
| | | |
| | | &::after { |
| | | content: ':'; |
| | | } |
| | | } |
| | | |
| | | .value { |
| | | justify-self: start; |
| | | // margin-left: 0.6em; |
| | | color: #027db4; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .list { |
| | | flex: 1; |
| | | position: relative; |
| | | |
| | | .scroller { |
| | | position: absolute; |
| | | top: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | left: 0; |
| | | overflow-y: auto; |
| | | |
| | | .lock-item { |
| | | // border-radius: 6px; |
| | | // height: 50px; |
| | | // background: #fff; |
| | | // display: flex; |
| | | // align-items: center; |
| | | // justify-content: space-between; |
| | | // padding: 0 8px; |
| | | |
| | | background: #fff; |
| | | padding: 10px 0; |
| | | margin-bottom: 10px; |
| | | display: grid; |
| | | gap: 4px; |
| | | grid-template-columns: 1fr 1fr 5fr 4fr; |
| | | grid-auto-rows: 24px 1.3fr 1fr; |
| | | |
| | | &::before { |
| | | content: ''; |
| | | } |
| | | |
| | | &~.lock-item { |
| | | margin-top: 6px; |
| | | } |
| | | |
| | | .station { |
| | | |
| | | // align-self: center; |
| | | grid-column: 1 e('/') 4; |
| | | grid-row: 1 e('/') 2; |
| | | font-size: 12px; |
| | | display: flex; |
| | | align-items: center; |
| | | background: #81d3f8; |
| | | border-radius: 0 20px 20px 0; |
| | | color: #333; |
| | | padding-left: 0.4em; |
| | | } |
| | | |
| | | .icon { |
| | | grid-column: 1 e('/') 2; |
| | | grid-row: 2 e('/') 4; |
| | | font-size: 30px; |
| | | color: #aaa; |
| | | |
| | | &.online { |
| | | color: #0a0; |
| | | } |
| | | } |
| | | |
| | | .state1 { |
| | | grid-area: 2 e('/') 2 e('/') 3 e('/') 3; |
| | | font-size: 12px; |
| | | color: #aaa; |
| | | |
| | | &.online { |
| | | color: #0a0; |
| | | } |
| | | |
| | | // color: #000; |
| | | // font-weight: bold; |
| | | } |
| | | |
| | | .lock-name { |
| | | grid-area: 2 e('/') 3 e('/') 3 e('/') 4; |
| | | font-size: 16px; |
| | | color: #000; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .lock-id { |
| | | grid-area: 3 e('/') 3 e('/') 4 e('/') 4; |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | .state { |
| | | font-weight: bold; |
| | | color: #0f0; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | :deep(.van-action-sheet__description) { |
| | | padding: 0; |
| | | |
| | | button+button { |
| | | margin-left: 10px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | import axios from '@/assets/js/axios'; |
| | | import uname from '@/assets/js/uname'; |
| | | |
| | | /** |
| | | * 登出 |
| | | * @param params |
| | | * @returns |
| | | */ |
| | | export function logout() { |
| | | return axios({ |
| | | url: 'login/logout', |
| | | method: 'GET' |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 普通用户登录查看自己授权记录 锁的个数 |
| | | * @param params |
| | | * @returns |
| | | */ |
| | | export function getAuthByUid() { |
| | | return axios({ |
| | | url: 'app/getAuthByUid', |
| | | method: 'GET', |
| | | params: { |
| | | uname: uname.value |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * 区域管理员登录查看自己管理记录 |
| | | * @param params |
| | | * @returns |
| | | */ |
| | | export function getAuthByUid2() { |
| | | return axios({ |
| | | url: 'app/getInfByAreaManage', |
| | | method: 'GET', |
| | | params: { |
| | | uname: uname.value |
| | | } |
| | | }); |
| | | } |
New file |
| | |
| | | <script setup> |
| | | import { ref, onMounted } from "vue"; |
| | | import { logout, getAuthByUid, getAuthByUid2 } from './apis'; |
| | | import { useUserStore } from "@/stores/user.js"; |
| | | import { useRouter } from "vue-router"; |
| | | |
| | | const router = useRouter(); |
| | | |
| | | const { urole } = useUserStore(); |
| | | |
| | | const lockNum = ref(0); |
| | | const areaNum = ref(0); |
| | | const userNum = ref(0); |
| | | |
| | | function logoutUser() { |
| | | localStorage.removeItem("uname"); |
| | | localStorage.removeItem("uid"); |
| | | localStorage.removeItem("urole"); |
| | | location.reload(); |
| | | logout(); |
| | | } |
| | | |
| | | function getLockList() { |
| | | |
| | | getAuthByUid().then((res) => { |
| | | let { code, data, data2 } = res.data; |
| | | let len = 0; |
| | | if (code && data) { |
| | | console.log(data2.length); |
| | | len = data2.length; |
| | | } |
| | | lockNum.value = len; |
| | | }) |
| | | .catch((err) => { |
| | | console.log(err); |
| | | }); |
| | | |
| | | } |
| | | |
| | | function getLockListAdmin() { |
| | | getAuthByUid2().then((res) => { |
| | | let { code, data, data2 } = res.data; |
| | | let _userNum = 0, |
| | | _lockNum = 0, |
| | | _areaNum = 0; |
| | | if (code && data) { |
| | | _userNum = data2.userNum; |
| | | _lockNum = data2.lockNum; |
| | | _areaNum = data2.areaNum; |
| | | } |
| | | lockNum.value = _lockNum; |
| | | userNum.value = _userNum; |
| | | areaNum.value = _areaNum; |
| | | }) |
| | | .catch((err) => { |
| | | console.log(err); |
| | | }); |
| | | |
| | | } |
| | | |
| | | function toMonitor() { |
| | | router.push({ path: "/monitor" }); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | // 如果是管理员,获取授权用户列表 如果是普通用户 只能查出锁具列表 |
| | | if (urole == 0) { |
| | | getLockList(); |
| | | } else { |
| | | getLockListAdmin(); |
| | | } |
| | | }) |
| | | |
| | | </script> |
| | | |
| | | <template> |
| | | <div class="p-user"> |
| | | <div class="title">我的</div> |
| | | <div class="header"> |
| | | <div class="avatar"> |
| | | <van-icon name="contact" /> |
| | | </div> |
| | | <div class="role">{{urole == 0 ? '普通用户' : '区域管理员'}}</div> |
| | | <div class="info"> |
| | | <div class="item" v-if="urole > 0"> |
| | | <div class="ico house" @click="toMonitor"></div> |
| | | <div class="label">管理区域</div> |
| | | <div class="value">{{ areaNum }}</div> |
| | | </div> |
| | | <div class="item" @click="toMonitor"> |
| | | <div class="ico lock"></div> |
| | | <div class="label">管理锁具</div> |
| | | <div class="value">{{ lockNum }}</div> |
| | | </div> |
| | | <div class="item" v-if="urole > 0"> |
| | | <div class="ico user"></div> |
| | | <div class="label">用户</div> |
| | | <div class="value">{{ userNum }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- <div class="main"> |
| | | <div class="title-level1">系统设置</div> |
| | | <van-cell title="账号安全" is-link to="home" /> |
| | | <van-cell title="人脸认证" is-link to="home" /> |
| | | <van-cell title="系统设置" is-link to="home" /> |
| | | <van-cell title="帮助中心" is-link to="home" /> |
| | | <van-cell title="检查版本" is-link to="home" /> |
| | | </div> --> |
| | | <div class="footer"> |
| | | <van-button type="primary" block @click="logoutUser">退出登录</van-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <style scoped lang="less"> |
| | | .p-user { |
| | | height: 100%; |
| | | background: #f2f2f2 linear-gradient(#81d3f8, #81d3f8) top center / 100% 20% no-repeat; |
| | | padding: 10px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .title { |
| | | height: 50px; |
| | | line-height: 50px; |
| | | text-align: center; |
| | | color: #027db4; |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .header { |
| | | background-color: #fff; |
| | | padding-bottom: 10px; |
| | | border-radius: 6px; |
| | | margin-top: 2em; |
| | | |
| | | .avatar { |
| | | background: #81d3f8; |
| | | border: 3px #fff solid; |
| | | width: 4em; |
| | | height: 4em; |
| | | border-radius: 50%; |
| | | margin: 0 auto -1.8em; |
| | | transform: translate(0, -50%); |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | |
| | | .van-icon { |
| | | font-size: 2em; |
| | | color: #fff; |
| | | } |
| | | } |
| | | |
| | | .role { |
| | | text-align: center; |
| | | font-size: 14px; |
| | | color: #999; |
| | | } |
| | | |
| | | .info { |
| | | margin-top: 10px; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | |
| | | .item { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | |
| | | .ico { |
| | | width: 40px; |
| | | height: 40px; |
| | | |
| | | // background: #81d3f8; |
| | | // border-radius: 50%; |
| | | &.house { |
| | | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='%2381d3f8' d='M10.707 2.293a1 1 0 0 0-1.414 0l-7 7a1 1 0 0 0 1.414 1.414L4 10.414V17a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1v-2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1v-6.586l.293.293a1 1 0 0 0 1.414-1.414z'%3e%3c/path%3e%3c/svg%3e"); |
| | | } |
| | | |
| | | &.lock { |
| | | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3e%3cpath fill='%2381d3f8' d='M17 9V7c0-2.8-2.2-5-5-5S7 4.2 7 7v2c-1.7 0-3 1.3-3 3v7c0 1.7 1.3 3 3 3h10c1.7 0 3-1.3 3-3v-7c0-1.7-1.3-3-3-3M9 7c0-1.7 1.3-3 3-3s3 1.3 3 3v2H9zm4.1 8.5l-.1.1V17c0 .6-.4 1-1 1s-1-.4-1-1v-1.4c-.6-.6-.7-1.5-.1-2.1s1.5-.7 2.1-.1c.6.5.7 1.5.1 2.1'%3e%3c/path%3e%3c/svg%3e"); |
| | | } |
| | | |
| | | &.user { |
| | | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='%2381d3f8' d='M9 6a3 3 0 1 1-6 0a3 3 0 0 1 6 0m8 0a3 3 0 1 1-6 0a3 3 0 0 1 6 0m-4.07 11q.07-.49.07-1a6.97 6.97 0 0 0-1.5-4.33A5 5 0 0 1 19 16v1zM6 11a5 5 0 0 1 5 5v1H1v-1a5 5 0 0 1 5-5'%3e%3c/path%3e%3c/svg%3e"); |
| | | } |
| | | } |
| | | |
| | | .label { |
| | | margin-top: 5px; |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | .value { |
| | | margin-top: 5px; |
| | | font-weight: bold; |
| | | font-size: 16px; |
| | | color: #81d3f8; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .main { |
| | | margin-top: 10px; |
| | | background-color: #fff; |
| | | padding: 10px; |
| | | border-radius: 6px; |
| | | } |
| | | |
| | | .footer { |
| | | margin-top: 10px; |
| | | border-radius: 6px; |
| | | background-color: #fff; |
| | | padding: 10px; |
| | | |
| | | } |
| | | } |
| | | </style> |