|
@@ -0,0 +1,316 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <van-row class="container">
|
|
|
|
|
+ <van-col style="background-color: #b0b2b2" span="6">
|
|
|
|
|
+ <van-row justify="center" :gutter="[0, 23]" style="margin-top: 23px">
|
|
|
|
|
+ <van-col span="24"><span>用户ID:</span> {{ userInfo.userId }}</van-col>
|
|
|
|
|
+ <van-col span="24"><span>用户姓名:</span> {{ userInfo.name }}</van-col>
|
|
|
|
|
+ <van-col span="24"><span>IP:</span> {{ ipAddress || '获取中...' }}</van-col>
|
|
|
|
|
+ <van-col span="24"><span>MAC:</span> {{ mac }}</van-col>
|
|
|
|
|
+ </van-row>
|
|
|
|
|
+ </van-col>
|
|
|
|
|
+ <van-col span="18" class="table-container">
|
|
|
|
|
+ <van-cell-group class="custom-cell-group">
|
|
|
|
|
+ <van-cell title="信息提示:" :value="message" />
|
|
|
|
|
+ </van-cell-group>
|
|
|
|
|
+ <div class="waterfall-table">
|
|
|
|
|
+ <!-- 固定表头 -->
|
|
|
|
|
+ <div class="table-header">
|
|
|
|
|
+ <div class="col index">序号</div>
|
|
|
|
|
+ <div class="col time">时间</div>
|
|
|
|
|
+ <div class="col operator">操作人</div>
|
|
|
|
|
+ <div class="col content">内容</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 表格主体 -->
|
|
|
|
|
+ <van-list
|
|
|
|
|
+ v-model:loading="loading"
|
|
|
|
|
+ :finished="finished"
|
|
|
|
|
+ finished-text="没有更多了"
|
|
|
|
|
+ @load="onLoad"
|
|
|
|
|
+ class="table-body">
|
|
|
|
|
+ <van-cell
|
|
|
|
|
+ v-for="(item, index) in list"
|
|
|
|
|
+ :key="item.id"
|
|
|
|
|
+ class="table-row"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="col index">{{ index + 1 }}</span>
|
|
|
|
|
+ <span class="col time">{{ item.time }}</span>
|
|
|
|
|
+ <span class="col operator">{{ item.operator }}</span>
|
|
|
|
|
+ <span class="col content">{{ item.content }}</span>
|
|
|
|
|
+ </van-cell>
|
|
|
|
|
+ </van-list>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </van-col>
|
|
|
|
|
+ </van-row>
|
|
|
|
|
+ <van-button class="add" icon="setting-o" type="primary" @click="_openSetting" />
|
|
|
|
|
+ <van-dialog v-model:show="setting.show" title="设置" width="410px" :before-close="_verifyMac"
|
|
|
|
|
+ show-cancel-button @confirm="_saveMac" @closed="_closeSetting">
|
|
|
|
|
+ <van-notice-bar
|
|
|
|
|
+ wrapable
|
|
|
|
|
+ left-icon="volume-o"
|
|
|
|
|
+ :scrollable="false"
|
|
|
|
|
+ :text="hintMessage"
|
|
|
|
|
+ />
|
|
|
|
|
+ <van-cell-group inset>
|
|
|
|
|
+ <van-field
|
|
|
|
|
+ v-model="setting.mac"
|
|
|
|
|
+ label="MAC地址"
|
|
|
|
|
+ placeholder="请输入MAC地址" />
|
|
|
|
|
+ <van-field
|
|
|
|
|
+ v-model="setting.password"
|
|
|
|
|
+ type="password"
|
|
|
|
|
+ label="密码"
|
|
|
|
|
+ placeholder="设置密码"
|
|
|
|
|
+ />
|
|
|
|
|
+ </van-cell-group>
|
|
|
|
|
+ </van-dialog>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { onMounted, onUnmounted, ref } from 'vue'
|
|
|
|
|
+import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
|
|
|
|
|
+import { saveMacAddress, readMacAddress } from '@/utils/android'
|
|
|
|
|
+import { showNotify } from 'vant'
|
|
|
|
|
+import { getUserIdByCert, getUserNameById } from '@/api/login/index.ts'
|
|
|
|
|
+
|
|
|
|
|
+// 在输入对话框或界面中添加提示文本
|
|
|
|
|
+const hintMessage = ref(`请前往:设置 → 关于平板电脑 → 设备WLAN MAC 地址 长按复制并粘贴到此处。`);
|
|
|
|
|
+const mac = ref('')
|
|
|
|
|
+const userInfo = ref({
|
|
|
|
|
+ userId: '',
|
|
|
|
|
+ name: ''
|
|
|
|
|
+})
|
|
|
|
|
+const setting = ref({
|
|
|
|
|
+ show: false,
|
|
|
|
|
+ mac: '',
|
|
|
|
|
+ password: ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const message = ref('')
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
|
+ closeListener()
|
|
|
|
|
+})
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ mac.value = readMacAddress()
|
|
|
|
|
+ if (mac.value) {
|
|
|
|
|
+ _openScan()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setting.value.show = true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ip
|
|
|
|
|
+ fetchIP()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const _openScan = () => {
|
|
|
|
|
+ openListener()
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ scanInit(_handlerScan)
|
|
|
|
|
+ },300)
|
|
|
|
|
+}
|
|
|
|
|
+const _handlerScan = (code) => {
|
|
|
|
|
+ console.log(code)
|
|
|
|
|
+ // 校验扫描的是否为登录二维码
|
|
|
|
|
+ const regex = /\{"sign":"([^"]*)",\s*"identity":(\d+)\}/;
|
|
|
|
|
+ if (code !== null && regex.test(code)) {
|
|
|
|
|
+ const match = regex.exec(code)
|
|
|
|
|
+ const sign = match[1]
|
|
|
|
|
+ const [userId, token] = sign.split('#');
|
|
|
|
|
+ const identity = match[2]
|
|
|
|
|
+ getUserIdByCert({sign: sign, identity: identity}).then(res => {
|
|
|
|
|
+ if (res && res.data) {
|
|
|
|
|
+ userInfo.value.userId = res.data
|
|
|
|
|
+ getUserNameById(res.data).then(res => {
|
|
|
|
|
+ if (res && res.data) {
|
|
|
|
|
+ userInfo.value.name = res.data
|
|
|
|
|
+ showNotify({ type: 'success', message: '登录成功!' });
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showNotify({ type: 'danger', message: '登录失败!' });
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const _openSetting = () => {
|
|
|
|
|
+ closeListener()
|
|
|
|
|
+ setting.value.show = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 校验mac地址是否已配置
|
|
|
|
|
+const _verifyMac = (done) => {
|
|
|
|
|
+ if (!mac.value) {
|
|
|
|
|
+ setting.value.show = true
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ return true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 关闭设置
|
|
|
|
|
+const _closeSetting = () => {
|
|
|
|
|
+ _openScan()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const _saveMac = () => {
|
|
|
|
|
+ if(!isValidMacAddress(setting.value.mac)) {
|
|
|
|
|
+ showNotify({ type: 'danger', message: '请输入正确的MAC地址!' });
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!isValidPassword(setting.value.password)) {
|
|
|
|
|
+ showNotify({ type: 'danger', message: '请输入正确的密码!' });
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // if(!saveMacAddress(setting.value.mac)) {
|
|
|
|
|
+ // showNotify({ type: 'danger', message: 'MAC保存失败!' });
|
|
|
|
|
+ // return
|
|
|
|
|
+ // }
|
|
|
|
|
+ showNotify({ type: 'success', message: 'MAC保存成功!' });
|
|
|
|
|
+ mac.value = setting.value.mac
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isValidMacAddress(mac) {
|
|
|
|
|
+ const regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
|
|
|
|
|
+ return mac !== null && regex.test(mac);
|
|
|
|
|
+}
|
|
|
|
|
+function isValidPassword(password) {
|
|
|
|
|
+ return password === '123456';
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 网络状态和ip
|
|
|
|
|
+const ipAddress = ref(null)
|
|
|
|
|
+
|
|
|
|
|
+// 获取 IP 地址
|
|
|
|
|
+const fetchIP = async () => {
|
|
|
|
|
+ const RTCPeerConnection = window.RTCPeerConnection;
|
|
|
|
|
+ if (RTCPeerConnection) {
|
|
|
|
|
+ const pc = new RTCPeerConnection({ iceServers: [] });
|
|
|
|
|
+ pc.createDataChannel('');
|
|
|
|
|
+ pc.createOffer()
|
|
|
|
|
+ .then(sdp => pc.setLocalDescription(sdp))
|
|
|
|
|
+ .catch(console.error);
|
|
|
|
|
+ pc.onicecandidate = e => {
|
|
|
|
|
+ if (!e.candidate) return;
|
|
|
|
|
+ const ip = /([0-9]{1,3}(\.[0-9]{1,3}){3})/.exec(e.candidate.candidate)?.[1];
|
|
|
|
|
+ if (ip) ipAddress.value = ip;
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const list = ref([]);
|
|
|
|
|
+const loading = ref(false);
|
|
|
|
|
+const finished = ref(false);
|
|
|
|
|
+
|
|
|
|
|
+// 模拟数据加载
|
|
|
|
|
+const onLoad = () => {
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ const newData = Array.from({ length: 100 }, (_, i) => ({
|
|
|
|
|
+ id: list.value.length + i + 1,
|
|
|
|
|
+ time: `2023-06-${Math.floor(Math.random() * 30) + 1} ${Math.floor(Math.random() * 24)}:${Math.floor(Math.random() * 60)}`,
|
|
|
|
|
+ operator: `用户${Math.floor(Math.random() * 1000)}`,
|
|
|
|
|
+ content: `${list.value.length + i + 1} - ${'这是一段动换行显示'}`
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ list.value.push(...newData);
|
|
|
|
|
+ loading.value = false;
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟数据加载完成
|
|
|
|
|
+ if (list.value.length >= 50) {
|
|
|
|
|
+ finished.value = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 1000);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="sass">
|
|
|
|
|
+.container
|
|
|
|
|
+ height: 100vh
|
|
|
|
|
+ display: flex
|
|
|
|
|
+ overflow: hidden
|
|
|
|
|
+ font-size: large
|
|
|
|
|
+
|
|
|
|
|
+.table-container
|
|
|
|
|
+ display: flex
|
|
|
|
|
+ flex-direction: column
|
|
|
|
|
+ height: 100%
|
|
|
|
|
+ overflow: hidden
|
|
|
|
|
+
|
|
|
|
|
+.add
|
|
|
|
|
+ position: fixed
|
|
|
|
|
+ bottom: 10px
|
|
|
|
|
+ left: 10px
|
|
|
|
|
+ z-index: 1000
|
|
|
|
|
+
|
|
|
|
|
+span
|
|
|
|
|
+ font-size: 7px
|
|
|
|
|
+
|
|
|
|
|
+/* 深度选择器穿透组件作用域 */
|
|
|
|
|
+:deep(.custom-cell-group)
|
|
|
|
|
+ /* 调整标题宽度 */
|
|
|
|
|
+ .van-cell__title
|
|
|
|
|
+ flex: 0 0 50px !important /* 固定宽度 */
|
|
|
|
|
+ font-size: x-large
|
|
|
|
|
+ max-width: 50px /* 最大宽度 */
|
|
|
|
|
+ min-width: 50px /* 最小宽度 */
|
|
|
|
|
+ /* 调整内容区域 */
|
|
|
|
|
+ .van-cell__value
|
|
|
|
|
+ flex: 1
|
|
|
|
|
+ color: red
|
|
|
|
|
+ font-size: large
|
|
|
|
|
+ text-align: left /* 左对齐 */
|
|
|
|
|
+ margin-left: 1px /* 可选:增加左边距 */
|
|
|
|
|
+
|
|
|
|
|
+.waterfall-table
|
|
|
|
|
+ position: relative
|
|
|
|
|
+ flex: 1
|
|
|
|
|
+ display: flex
|
|
|
|
|
+ flex-direction: column
|
|
|
|
|
+ overflow: hidden
|
|
|
|
|
+ height: 100%
|
|
|
|
|
+
|
|
|
|
|
+.table-header
|
|
|
|
|
+ display: flex
|
|
|
|
|
+ background: #f2f3f5
|
|
|
|
|
+ font-weight: bold
|
|
|
|
|
+ z-index: 100
|
|
|
|
|
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05)
|
|
|
|
|
+ flex-shrink: 0 // 防止表头被压缩
|
|
|
|
|
+
|
|
|
|
|
+.table-body
|
|
|
|
|
+ flex: 1
|
|
|
|
|
+ overflow-y: auto
|
|
|
|
|
+ position: relative
|
|
|
|
|
+ padding-top: 0 // 移除之前的padding-top
|
|
|
|
|
+
|
|
|
|
|
+.table-row
|
|
|
|
|
+ display: flex
|
|
|
|
|
+ box-sizing: border-box
|
|
|
|
|
+
|
|
|
|
|
+.col
|
|
|
|
|
+ padding: 8px 4px
|
|
|
|
|
+ overflow: hidden
|
|
|
|
|
+ text-overflow: ellipsis
|
|
|
|
|
+ box-sizing: border-box
|
|
|
|
|
+
|
|
|
|
|
+/* 列宽设置 - 确保表头和表体使用相同的宽度 */
|
|
|
|
|
+.index
|
|
|
|
|
+ flex: 0 0 30px
|
|
|
|
|
+ width: 30px
|
|
|
|
|
+ text-align: center
|
|
|
|
|
+
|
|
|
|
|
+.time
|
|
|
|
|
+ flex: 0 0 70px
|
|
|
|
|
+ width: 70px
|
|
|
|
|
+
|
|
|
|
|
+.operator
|
|
|
|
|
+ flex: 0 0 50px
|
|
|
|
|
+ width: 50px
|
|
|
|
|
+
|
|
|
|
|
+.content
|
|
|
|
|
+ flex: 1
|
|
|
|
|
+ min-width: 0 /* 允许内容换行 */
|
|
|
|
|
+ white-space: normal /* 允许换行 */
|
|
|
|
|
+</style>
|