Browse Source

🦄 refactor: 美化部分页面

Pchen. 8 months ago
parent
commit
94146f3461

+ 285 - 0
face.html

@@ -0,0 +1,285 @@
+<!doctype html>
+<html>
+<head>
+  <meta charset="utf-8" />
+  <title>前置摄像头 人脸活体检测 + 录制上传示例</title>
+  <style>
+    video, canvas { max-width: 360px; border: 1px solid #ccc; display:block; }
+    #status { margin-top:8px; }
+  </style>
+  <!-- MediaPipe Face Mesh (browser) -->
+  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh@0.4/face_mesh.js"></script>
+  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils@0.4/camera_utils.js"></script>
+  <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils@0.4/drawing_utils.js"></script>
+</head>
+<body>
+  <h3>活体检测示例(张嘴 / 眨眼 / 转头)</h3>
+  <video id="inputVideo" autoplay playsinline muted></video>
+  <canvas id="overlay"></canvas>
+  <div id="status">等待摄像头权限...</div>
+  <button id="startTest">开始动作检测并录制通过视频</button>
+  <script>
+    const video = document.getElementById('inputVideo');
+    const canvas = document.getElementById('overlay');
+    const ctx = canvas.getContext('2d');
+    const status = document.getElementById('status');
+    const startBtn = document.getElementById('startTest');
+
+    // 状态机:按序进行检测(你也可以并行检测)
+    const ACTIONS = ['blink', 'open_mouth', 'turn_right']; // 示例序列
+    let currentActionIndex = 0;
+    let actionPassed = { blink:false, open_mouth:false, turn_right:false };
+
+    // MediaRecorder 相关
+    let mediaRecorder, recordedBlobs = [];
+
+    // 启动摄像头
+    async function startCamera() {
+      try {
+        const stream = await navigator.mediaDevices.getUserMedia({
+          video: { facingMode: 'user', width: 640, height: 480 }, audio: true
+        });
+        video.srcObject = stream;
+        await video.play();
+        canvas.width = video.videoWidth;
+        canvas.height = video.videoHeight;
+        status.textContent = '摄像头已开启,准备就绪。点击 "开始动作检测并录制通过视频"。';
+        return stream;
+      } catch (e) {
+        status.textContent = '无法打开摄像头: ' + e.message;
+        throw e;
+      }
+    }
+
+    // EAR - Eye Aspect Ratio 计算(基于 MediaPipe 的关键点索引)
+    // MediaPipe 面部关键点参考: https://github.com/tensorflow/tfjs-models/tree/master/face-landmarks-detection (但我们用通用索引)
+    // 这里用了左右眼的几个点的索引(MediaPipe 有 468 点):
+    const leftEyeIndices = { // 以常见的点索引为例,可能需要根据版本微调
+      outer: 33, inner: 133,
+      top1: 159, top2: 145,
+      bottom1: 23, bottom2: 27
+    };
+    const rightEyeIndices = {
+      outer: 362, inner: 263,
+      top1: 386, top2: 374,
+      bottom1: 253, bottom2: 257
+    };
+    const mouthIndices = {
+      top: 13, bottom: 14, left: 78, right: 308
+    };
+
+    function dist(a, b) {
+      return Math.hypot(a.x - b.x, a.y - b.y);
+    }
+
+    function computeEAR(landmarks, eyeIdx) {
+      // EAR ~ (||p2-p6|| + ||p3-p5||) / (2 * ||p1-p4||)
+      const p1 = landmarks[eyeIdx.outer];
+      const p4 = landmarks[eyeIdx.inner];
+      // vertical pairs
+      const p2 = landmarks[eyeIdx.top1];
+      const p3 = landmarks[eyeIdx.top2];
+      const p5 = landmarks[eyeIdx.bottom1];
+      const p6 = landmarks[eyeIdx.bottom2];
+      const vert = (dist(p2,p5) + dist(p3,p6)) / 2;
+      const horiz = dist(p1,p4);
+      if (horiz === 0) return 0;
+      return vert / horiz;
+    }
+
+    function computeMAR(landmarks) {
+      // 简单 MAR: 竖直嘴唇距离 / 眼距归一化
+      const top = landmarks[mouthIndices.top];
+      const bottom = landmarks[mouthIndices.bottom];
+      const left = landmarks[mouthIndices.left];
+      const right = landmarks[mouthIndices.right];
+      const mouthOpen = dist(top, bottom);
+      const eyeDist = dist(landmarks[leftEyeIndices.outer], landmarks[rightEyeIndices.outer]);
+      if (eyeDist === 0) return 0;
+      return mouthOpen / eyeDist;
+    }
+
+    function estimateYaw(landmarks) {
+      // 简单估算 yaw(左右转): 比较鼻子与两眼之间的相对位置
+      // 鼻尖索引在 MediaPipe 常见是 1 或 4 等,这里用 1(可能需微调)
+      const nose = landmarks[1] || landmarks[4] || landmarks[0];
+      const leftEye = landmarks[leftEyeIndices.outer];
+      const rightEye = landmarks[rightEyeIndices.outer];
+      // 若鼻子更靠左 => 向右转(相机视角)。用归一化比例作为估计值
+      const midEyeX = (leftEye.x + rightEye.x) / 2;
+      const dx = (nose.x - midEyeX); // 负 = nose 左偏, 正 = 右偏
+      // 将 dx 归一化到 -1..1
+      const w = canvas.width || 1;
+      return dx / w * 2;
+    }
+
+    // 初始化 MediaPipe FaceMesh
+    const faceMesh = new FaceMesh({
+      locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh@0.4/${file}`
+    });
+    faceMesh.setOptions({
+      maxNumFaces: 1,
+      refineLandmarks: true,
+      minDetectionConfidence: 0.5,
+      minTrackingConfidence: 0.5
+    });
+
+    faceMesh.onResults(onResults);
+
+    // Camera helper from MediaPipe(自动把 video 帧送入 faceMesh)
+    let mpCamera;
+    async function initFaceMesh() {
+      mpCamera = new Camera(video, {
+        onFrame: async () => {
+          await faceMesh.send({image: video});
+        },
+        width: 640,
+        height: 480
+      });
+      mpCamera.start();
+    }
+
+    // 处理每帧结果
+    let blinkCooldown = 0;
+    let mouthCooldown = 0;
+    let turnCooldown = 0;
+
+    function onResults(results) {
+      ctx.clearRect(0,0,canvas.width,canvas.height);
+      if (!results.multiFaceLandmarks || results.multiFaceLandmarks.length === 0) {
+        status.textContent = '未检测到人脸,请正对摄像头。';
+        return;
+      }
+      const landmarks = results.multiFaceLandmarks[0].map(p => ({x:p.x * canvas.width, y:p.y * canvas.height, z: p.z}));
+      // 可视化关键点(可选)
+      drawConnectors(ctx, results.multiFaceLandmarks[0], FACEMESH_TESSELATION, {color: '#C0C0C0', lineWidth: 1});
+      drawConnectors(ctx, results.multiFaceLandmarks[0], FACEMESH_RIGHT_EYE, {color: '#FF3030'});
+      drawConnectors(ctx, results.multiFaceLandmarks[0], FACEMESH_LEFT_EYE, {color: '#30FF30'});
+
+      // 计算指标
+      const leftEAR = computeEAR(landmarks, leftEyeIndices);
+      const rightEAR = computeEAR(landmarks, rightEyeIndices);
+      const ear = (leftEAR + rightEAR) / 2;
+      const mar = computeMAR(landmarks);
+      const yaw = estimateYaw(landmarks);
+
+      // 简单阈值(需按实际设备/光线/摄像头微调)
+      const BLINK_EAR_THRESHOLD = 0.18;   // 小于视为眨眼
+      const MAR_THRESHOLD = 0.15;         // 大于视为张嘴
+      const YAW_RIGHT_THRESHOLD = 0.03;   // 右转阈值(正/负方向看估算)
+      // 事件检测并防抖(cooldown)
+      const now = Date.now();
+      // 眨眼检测:EAR 瞬时低于阈值且未处于 cooldown
+      if (ear < BLINK_EAR_THRESHOLD && (now - blinkCooldown) > 800) {
+        actionPassed.blink = true;
+        blinkCooldown = now;
+        status.textContent = '检测到眨眼 ✅';
+      }
+
+      // 张嘴检测
+      if (mar > MAR_THRESHOLD && (now - mouthCooldown) > 1200) {
+        actionPassed.open_mouth = true;
+        mouthCooldown = now;
+        status.textContent = '检测到张嘴 ✅';
+      }
+
+      // 转头检测(示例检测向右转)
+      if (yaw > YAW_RIGHT_THRESHOLD && (now - turnCooldown) > 1200) {
+        actionPassed.turn_right = true;
+        turnCooldown = now;
+        status.textContent = '检测到向右转头 ✅';
+      }
+
+      // 更新动作进度(按 ACTIONS 顺序)
+      while (currentActionIndex < ACTIONS.length && actionPassed[ACTIONS[currentActionIndex]]) {
+        currentActionIndex++;
+      }
+      if (currentActionIndex >= ACTIONS.length) {
+        status.textContent = '动作序列全部完成,准备上传视频...';
+        finishAndUpload();
+      } else {
+        status.textContent = `请进行动作:${ACTIONS[currentActionIndex]} (已完成 ${currentActionIndex}/${ACTIONS.length})`;
+      }
+    }
+
+    // 录制并上传:开始录制视频并在动作完成后上传(也可以先录制,再在完成后上传)
+    async function startRecording(stream) {
+      recordedBlobs = [];
+      let options = { mimeType: 'video/webm;codecs=vp9,opus' };
+      try {
+        mediaRecorder = new MediaRecorder(stream, options);
+      } catch (e) {
+        options = { mimeType: 'video/webm;codecs=vp8,opus' };
+        mediaRecorder = new MediaRecorder(stream, options);
+      }
+      mediaRecorder.ondataavailable = (e) => {
+        if (e.data && e.data.size > 0) recordedBlobs.push(e.data);
+      };
+      mediaRecorder.start(100); // 每 100ms 收集
+      status.textContent = '已开始录制...';
+    }
+
+    // 停止并上传
+    async function finishAndUpload() {
+      // 防止重复触发
+      if (!mediaRecorder) {
+        status.textContent = '未找到录制器,直接开始并在1.5s后上传短片。';
+        // 快速录一段短片(1.5s)作为上传
+        const t0 = Date.now();
+        const s = video.srcObject;
+        await startRecording(s);
+        await new Promise(r => setTimeout(r, 1500));
+        mediaRecorder.stop();
+      } else {
+        mediaRecorder.stop();
+      }
+
+      // 等待 dataavailable 收集完成(用短延时)
+      await new Promise(r => setTimeout(r, 500));
+      const blob = new Blob(recordedBlobs, { type: 'video/webm' });
+      const form = new FormData();
+      form.append('file', blob, 'liveness.webm');
+      form.append('meta', JSON.stringify({ actions: ACTIONS, timestamp: Date.now() }));
+      status.textContent = '上传中...';
+
+      // 上传(替换为你的上传 URL)
+      try {
+        const resp = await fetch('/upload_liveness', {
+          method: 'POST',
+          body: form
+        });
+        if (resp.ok) {
+          status.textContent = '上传成功 ✅';
+        } else {
+          status.textContent = '上传失败,服务器返回 ' + resp.status;
+        }
+      } catch (e) {
+        status.textContent = '上传出错:' + e.message;
+      }
+    }
+
+    // UI 按钮:开始检测并录制
+    startBtn.addEventListener('click', async () => {
+      startBtn.disabled = true;
+      currentActionIndex = 0;
+      actionPassed = { blink:false, open_mouth:false, turn_right:false };
+      try {
+        const stream = await startCamera();
+        await initFaceMesh();
+        // 开始录制整个摄像头流(也可选择在动作完成后再录制)
+        await startRecording(stream);
+      } catch (e) {
+        console.error(e);
+      }
+    });
+
+    // 页面卸载时停止摄像头
+    window.addEventListener('beforeunload', () => {
+      if (video && video.srcObject) {
+        video.srcObject.getTracks().forEach(t => t.stop());
+      }
+      if (mpCamera && mpCamera.stop) mpCamera.stop();
+    });
+  </script>
+</body>
+</html>

+ 6 - 0
src/components/userCard/userCard.vue

@@ -150,3 +150,9 @@ onUnmounted(() => {
     stopPolling()
     stopPolling()
 })
 })
 </script>
 </script>
+
+<style scoped>
+.card {
+  font-family: -apple-system, BlinkMacSystemFont;
+}
+</style>

+ 4 - 6
src/pages/admin/lepaoAccount/accountList.vue

@@ -67,7 +67,7 @@
                 </a-col>
                 </a-col>
             </a-row>
             </a-row>
 
 
-            <a-table :data="data" class="table" :loading="loading" :columns="columns" :pagination="{
+            <a-table :data="data" :bordered="false" class="table" :loading="loading" :columns="columns" :pagination="{
                 showPageSize: true,
                 showPageSize: true,
                 showJumper: true,
                 showJumper: true,
                 showTotal: true,
                 showTotal: true,
@@ -99,7 +99,8 @@
                     <a-tag color="red" v-else>关闭</a-tag>
                     <a-tag color="red" v-else>关闭</a-tag>
                 </template>
                 </template>
                 <template #num="{ record }">
                 <template #num="{ record }">
-                    {{ record.term_num - record.total_num > 0 ? (record.term_num - record.total_num) : '已完成' }}
+                    {{ record.term_num - record.total_num > 0 ? `${record.total_num} / ${record.term_num}` :
+                '已完成' }}
                 </template>
                 </template>
                 <template #state="{ record }">
                 <template #state="{ record }">
                     <div v-if="record.state === 0" class="state">
                     <div v-if="record.state === 0" class="state">
@@ -254,10 +255,6 @@ const columns = [
         width: 150
         width: 150
     }, {
     }, {
         title: '累计次数',
         title: '累计次数',
-        dataIndex: 'total_num',
-        width: 90
-    }, {
-        title: '剩余次数',
         slotName: 'num',
         slotName: 'num',
         width: 90
         width: 90
     }, {
     }, {
@@ -302,6 +299,7 @@ const reset = () => {
     queryData.user_uuid = ''
     queryData.user_uuid = ''
     queryData.email = ''
     queryData.email = ''
     queryData.username = ''
     queryData.username = ''
+    queryData.state = -1
     getAccounts()
     getAccounts()
 }
 }
 
 

+ 1 - 1
src/pages/admin/lepaoRecords/lepaoRecords.vue

@@ -46,7 +46,7 @@
         </a-col>
         </a-col>
       </a-row>
       </a-row>
 
 
-      <a-table :data="data" stripe hoverable class="table" :loading="loading" :pagination="{
+      <a-table :data="data" :bordered="false" hoverable class="table" :loading="loading" :pagination="{
         showPageSize: true,
         showPageSize: true,
         showJumper: true,
         showJumper: true,
         showTotal: true,
         showTotal: true,

+ 1 - 1
src/pages/admin/user/userList.vue

@@ -50,7 +50,7 @@
                 </a-col>
                 </a-col>
             </a-row>
             </a-row>
 
 
-            <a-table :data="data" stripe hoverable column-resizable class="table" :loading="loading" :columns="columns"
+            <a-table :data="data" :bordered="false" hoverable column-resizable class="table" :loading="loading" :columns="columns"
                 :pagination="{
                 :pagination="{
                     showPageSize: true,
                     showPageSize: true,
                     showJumper: true,
                     showJumper: true,

+ 1 - 1
src/pages/download/index.vue

@@ -102,7 +102,7 @@ const html = decodeURI(atob('JTNDaDIlM0UlRTYlQjUlOEYlRTglQTclODglRTUlOTklQTglRTY
         }
         }
 
 
         .downcard:hover {
         .downcard:hover {
-            transform: translateY(-4px);
+            transform: translateY(-1px);
         }
         }
     }
     }
 }
 }

+ 4 - 0
src/pages/face/faceUpload.vue

@@ -0,0 +1,4 @@
+<template>
+    </template>
+
+<script setup></script>

+ 23 - 41
src/pages/lepao/accountList/index.vue

@@ -36,7 +36,7 @@
               <a-col :span="8">
               <a-col :span="8">
                 <a-form-item field="area" label="跑区">
                 <a-form-item field="area" label="跑区">
                   <a-select v-model="queryDataForm.area" placeholder="请选择乐跑跑区" default-value="">
                   <a-select v-model="queryDataForm.area" placeholder="请选择乐跑跑区" default-value="">
-                    <a-option value="">所有</a-option>
+                    <a-option value="">所有跑区</a-option>
                     <a-option v-for="(item, index) in area" :key="index" :value="item">
                     <a-option v-for="(item, index) in area" :key="index" :value="item">
                       {{ item }}
                       {{ item }}
                     </a-option>
                     </a-option>
@@ -44,12 +44,12 @@
                 </a-form-item>
                 </a-form-item>
               </a-col>
               </a-col>
               <a-col :span="8">
               <a-col :span="8">
-                <a-form-item field="email" label="用户邮箱">
+                <a-form-item field="email" label="邮箱">
                   <a-input v-model="queryDataForm.email" allow-clear />
                   <a-input v-model="queryDataForm.email" allow-clear />
                 </a-form-item>
                 </a-form-item>
               </a-col>
               </a-col>
               <a-col :span="8">
               <a-col :span="8">
-                <a-form-item field="username" label="账号名称">
+                <a-form-item field="username" label="姓名">
                   <a-input v-model="queryDataForm.username" allow-clear />
                   <a-input v-model="queryDataForm.username" allow-clear />
                 </a-form-item>
                 </a-form-item>
               </a-col>
               </a-col>
@@ -59,7 +59,7 @@
                 </a-form-item>
                 </a-form-item>
               </a-col>
               </a-col>
               <a-col :span="8">
               <a-col :span="8">
-                <a-form-item field="area" label="账号状态">
+                <a-form-item field="area" label="状态">
                   <a-select v-model="queryDataForm.state" :options="state" placeholder="请选择账号状态" :default-value="-1" />
                   <a-select v-model="queryDataForm.state" :options="state" placeholder="请选择账号状态" :default-value="-1" />
                 </a-form-item>
                 </a-form-item>
               </a-col>
               </a-col>
@@ -85,7 +85,7 @@
         </a-col>
         </a-col>
       </a-row>
       </a-row>
 
 
-      <a-table :data="data" stripe hoverable class="table" :loading="loading" :scroll="{
+      <a-table :data="data" :bordered="false" hoverable class="table" :loading="loading" expandable :scroll="{
         x: 1600
         x: 1600
       }" :pagination="{
       }" :pagination="{
         showPageSize: true,
         showPageSize: true,
@@ -108,7 +108,7 @@
         </template>
         </template>
 
 
         <template #columns>
         <template #columns>
-          <a-table-column title="" :width="60" data-index="user_avatar" tooltip>
+          <a-table-column title="" :width="50" data-index="user_avatar" tooltip>
             <template #cell="{ record }">
             <template #cell="{ record }">
               <a-avatar>
               <a-avatar>
                 <IconUser v-if="!record.user_avatar" />
                 <IconUser v-if="!record.user_avatar" />
@@ -116,16 +116,8 @@
               </a-avatar>
               </a-avatar>
             </template>
             </template>
           </a-table-column>
           </a-table-column>
-          <a-table-column title="学号" :width="120" data-index="student_num" ellipsis tooltip :filterable="{
-            filter: (value, record) => (record.student_num ?? '').includes(value),
-            slotName: 'name-filter',
-            icon: () => h(IconSearch)
-          }"></a-table-column>
-          <a-table-column title="用户名" :width="130" :filterable="{
-            filter: (value, record) => (record.name ?? '').includes(value),
-            slotName: 'name-filter',
-            icon: () => h(IconSearch)
-          }">
+          <a-table-column title="学号" :width="115" data-index="student_num" ellipsis tooltip></a-table-column>
+          <a-table-column title="姓名" :width="130">
             <template #cell="{ record }">
             <template #cell="{ record }">
               {{ record.name ?? '请使用乐跑登录器更新账号信息' }}
               {{ record.name ?? '请使用乐跑登录器更新账号信息' }}
             </template>
             </template>
@@ -148,11 +140,7 @@
             slotName: 'name-filter',
             slotName: 'name-filter',
             icon: () => h(IconSearch)
             icon: () => h(IconSearch)
           }"></a-table-column>
           }"></a-table-column>
-          <a-table-column title="跑区" :width="130" :filterable="{
-            filter: (value, record) => (record.name ?? '').includes(value),
-            slotName: 'name-filter',
-            icon: () => h(IconSearch)
-          }">
+          <a-table-column title="跑区" :width="130">
             <template #cell="{ record }">
             <template #cell="{ record }">
               <div class="vipcontent">
               <div class="vipcontent">
                 <span>{{ record.area || '随机分配' }} </span>
                 <span>{{ record.area || '随机分配' }} </span>
@@ -160,19 +148,8 @@
               </div>
               </div>
             </template>
             </template>
           </a-table-column>
           </a-table-column>
-          <a-table-column title="通知邮箱" :width="180" data-index="email" ellipsis tooltip :filterable="{
-            filter: (value, record) => (record.email ?? '').includes(value),
-            slotName: 'name-filter',
-            icon: () => h(IconSearch)
-          }"></a-table-column>
-          <a-table-column title="帐号状态" :width="100" ellipsis tooltip :filterable="{
-            filters: [
-              { text: '正常', value: 1 },
-              { text: '需登录', value: 0 },
-              { text: '状态异常', value: 2 },
-            ],
-            filter: (value, record) => record.state == value
-          }">
+          <a-table-column title="通知邮箱" :width="180" data-index="email" ellipsis tooltip></a-table-column>
+          <a-table-column title="帐号状态" :width="100" ellipsis tooltip>
             <template #cell="{ record }">
             <template #cell="{ record }">
               <div v-if="record.state === 0" class="state">
               <div v-if="record.state === 0" class="state">
                 <div class="circle zero"></div>需登录
                 <div class="circle zero"></div>需登录
@@ -202,20 +179,20 @@
               {{ autoTimeLabel(record.auto_time) }}
               {{ autoTimeLabel(record.auto_time) }}
             </template>
             </template>
           </a-table-column>
           </a-table-column>
-          <a-table-column title="累计次数" :width="110" ellipsis tooltip>
+          <a-table-column title="累计次数" :width="88" ellipsis tooltip>
             <template #cell="{ record }">
             <template #cell="{ record }">
               {{ record.term_num - record.total_num > 0 ? `${record.total_num} / ${record.term_num}` :
               {{ record.term_num - record.total_num > 0 ? `${record.total_num} / ${record.term_num}` :
                 '已完成' }}
                 '已完成' }}
             </template>
             </template>
           </a-table-column>
           </a-table-column>
-          <a-table-column title="添加时间" :width="170" ellipsis tooltip :sortable="{
+          <a-table-column title="添加时间" :width="145" ellipsis tooltip :sortable="{
             sortDirections: ['ascend', 'descend']
             sortDirections: ['ascend', 'descend']
           }">
           }">
             <template #cell="{ record }">
             <template #cell="{ record }">
               {{ stramptoTime(record.create_time) }}
               {{ stramptoTime(record.create_time) }}
             </template>
             </template>
           </a-table-column>
           </a-table-column>
-          <a-table-column title="上次登录时间" :width="170" ellipsis tooltip>
+          <a-table-column title="上次登录时间" :width="145" ellipsis tooltip>
             <template #cell="{ record }">
             <template #cell="{ record }">
               {{ record.update_time ? stramptoTime(record.update_time) : '待登录' }}
               {{ record.update_time ? stramptoTime(record.update_time) : '待登录' }}
             </template>
             </template>
@@ -353,6 +330,7 @@ const search = () => {
   queryData.student_num = queryDataForm.student_num
   queryData.student_num = queryDataForm.student_num
   queryData.email = queryDataForm.email
   queryData.email = queryDataForm.email
   queryData.username = queryDataForm.username
   queryData.username = queryDataForm.username
+  queryData.state = queryDataForm.state
 
 
   getAccountsAsync()
   getAccountsAsync()
 }
 }
@@ -362,11 +340,13 @@ const reset = () => {
   queryDataForm.student_num = ''
   queryDataForm.student_num = ''
   queryDataForm.email = ''
   queryDataForm.email = ''
   queryDataForm.username = ''
   queryDataForm.username = ''
+  queryDataForm.state = -1
 
 
   queryData.area = ''
   queryData.area = ''
   queryData.student_num = ''
   queryData.student_num = ''
   queryData.email = ''
   queryData.email = ''
   queryData.username = ''
   queryData.username = ''
+  queryData.state = -1
 
 
   getAccountsAsync()
   getAccountsAsync()
 }
 }
@@ -513,7 +493,7 @@ const getAccounts = async () => {
     const res = await accountList(reqData)
     const res = await accountList(reqData)
     if (!res || res.code !== 0)
     if (!res || res.code !== 0)
       return Notification.error({
       return Notification.error({
-        title: '获取路径数据失败!',
+        title: '获取乐跑账号数据失败!',
         content: res?.msg ?? '请稍后再试'
         content: res?.msg ?? '请稍后再试'
       })
       })
 
 
@@ -521,7 +501,7 @@ const getAccounts = async () => {
     pagination.total = res.pagination.total
     pagination.total = res.pagination.total
   } catch (error) {
   } catch (error) {
     Notification.error({
     Notification.error({
-      title: '获取路径数据失败!',
+      title: '获取乐跑账号数据失败!',
       content: error.message || '请稍后再试'
       content: error.message || '请稍后再试'
     })
     })
   }
   }
@@ -554,7 +534,7 @@ const DeleteAccount = async (item) => {
   Modal.confirm({
   Modal.confirm({
     title: '解绑账号',
     title: '解绑账号',
     content: () => h('div', [
     content: () => h('div', [
-      h('p', '您是否要解绑该账号?该操作不可逆')
+      h('p', '您是否要解绑该账号?')
     ]),
     ]),
     onOk: async () => {
     onOk: async () => {
       const res = await deleteAccount({ id: item.id })
       const res = await deleteAccount({ id: item.id })
@@ -588,7 +568,7 @@ const ChangeAutoRun = async (record) => {
 }
 }
 
 
 const stramptoTime = (time) => {
 const stramptoTime = (time) => {
-  return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })
+  return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
 }
 }
 
 
 let timer = null
 let timer = null
@@ -632,6 +612,8 @@ onUnmounted(() => {
 }
 }
 
 
 .table {
 .table {
+  font-family: -apple-system, BlinkMacSystemFont;
+
   .state {
   .state {
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;

+ 8 - 7
src/pages/lepao/lepaoRecords/index.vue

@@ -48,7 +48,7 @@
 
 
       <a-alert>仅保存成功上传至乐跑服务器的跑步记录。未成功的跑步(含状态无效)不会扣除账户乐跑次数。</a-alert>
       <a-alert>仅保存成功上传至乐跑服务器的跑步记录。未成功的跑步(含状态无效)不会扣除账户乐跑次数。</a-alert>
 
 
-      <a-table :data="data" stripe hoverable class="table" :loading="loading" :pagination="{
+      <a-table :data="data" :bordered="false" hoverable class="table" :loading="loading" :pagination="{
         showPageSize: true,
         showPageSize: true,
         showJumper: true,
         showJumper: true,
         showTotal: true,
         showTotal: true,
@@ -97,7 +97,7 @@
             </template>
             </template>
 
 
           </a-table-column>
           </a-table-column>
-          <a-table-column title="状态" ellipsis tooltip>
+          <a-table-column title="状态" ellipsis tooltip width="230">
             <template #cell="{ record }">
             <template #cell="{ record }">
               <div class="state">
               <div class="state">
                 <div class="circle one" v-if="record.result.record_failed_reason === '自动确认有效'"></div>
                 <div class="circle one" v-if="record.result.record_failed_reason === '自动确认有效'"></div>
@@ -115,27 +115,27 @@
               {{ record.result.pass_tit }}
               {{ record.result.pass_tit }}
             </template>
             </template>
           </a-table-column>
           </a-table-column>
-          <a-table-column title="乐跑距离" :width="110" ellipsis tooltip>
+          <a-table-column title="乐跑距离" :width="100" ellipsis tooltip>
             <template #cell="{ record }">
             <template #cell="{ record }">
               {{ record.result.distance }} Km
               {{ record.result.distance }} Km
             </template>
             </template>
           </a-table-column>
           </a-table-column>
-          <a-table-column title="跑步时长" :width="110" ellipsis tooltip>
+          <a-table-column title="跑步时长" :width="100" ellipsis tooltip>
             <template #cell="{ record }">
             <template #cell="{ record }">
               {{ formatSecondsToMinSec(record.result.time) }}
               {{ formatSecondsToMinSec(record.result.time) }}
             </template>
             </template>
           </a-table-column>
           </a-table-column>
-          <a-table-column title="平均配速" :width="110" ellipsis tooltip>
+          <a-table-column title="平均配速" :width="90" ellipsis tooltip>
             <template #cell="{ record }">
             <template #cell="{ record }">
               {{ calculatePace(record.result.time, record.result.distance) }}
               {{ calculatePace(record.result.time, record.result.distance) }}
             </template>
             </template>
           </a-table-column>
           </a-table-column>
-          <a-table-column title="乐跑时间" :width="170" ellipsis tooltip>
+          <a-table-column title="乐跑时间" :width="145" ellipsis tooltip>
             <template #cell="{ record }">
             <template #cell="{ record }">
               {{ stramptoTime(record.time) }}
               {{ stramptoTime(record.time) }}
             </template>
             </template>
           </a-table-column>
           </a-table-column>
-          <a-table-column title="操作" :width="170" ellipsis tooltip>
+          <a-table-column title="" :width="170" ellipsis tooltip fixed="right">
             <template #cell="{ record }">
             <template #cell="{ record }">
               <a-button @click="$router.push(`/lepao/recordDetail/${record.id}`)">查看详情</a-button>
               <a-button @click="$router.push(`/lepao/recordDetail/${record.id}`)">查看详情</a-button>
             </template>
             </template>
@@ -259,6 +259,7 @@ getRecords()
 
 
 .table {
 .table {
   margin-top: 15px;
   margin-top: 15px;
+  font-family: -apple-system, BlinkMacSystemFont;
 
 
   .state {
   .state {
     display: flex;
     display: flex;

+ 1 - 1
src/pages/power/accountList.vue

@@ -12,7 +12,7 @@
 
 
       <a-alert style="margin-top: 15px;">电费单价:¥0.54/千瓦时。仅保存最近30天的电费变更记录。电费余额每30分钟更新一次,添加任务后开始记录。</a-alert>
       <a-alert style="margin-top: 15px;">电费单价:¥0.54/千瓦时。仅保存最近30天的电费变更记录。电费余额每30分钟更新一次,添加任务后开始记录。</a-alert>
 
 
-      <a-table :data="data" :columns="columns" stripe hoverable class="table" :loading="loading" :scroll="{
+      <a-table :data="data" :columns="columns" :bordered="false" hoverable class="table" :loading="loading" :scroll="{
         x: 1600
         x: 1600
       }" :pagination="{ showPageSize: true, showJumper: true, defaultPageSize: 15 }">
       }" :pagination="{ showPageSize: true, showJumper: true, defaultPageSize: 15 }">
 
 

+ 1 - 1
src/utils/request.js

@@ -40,7 +40,7 @@ const errorHandler = (error) => {
 
 
     Notification.error({
     Notification.error({
       title: '请求失败',
       title: '请求失败',
-      content: '网络不畅或服务器停机维护中,请耐心等待'
+      content: '网络不畅或服务器停机维护中,请稍后再试'
     })
     })
   }
   }
 }
 }