xbx hace 2 semanas
padre
commit
848722504d

+ 0 - 4
.husky/pre-commit

@@ -1,4 +0,0 @@
-#!/usr/bin/env sh
-. "$(dirname -- "$0")/_/husky.sh"
-
-npx lint-staged

+ 8 - 10
package.json

@@ -23,17 +23,14 @@
     ]
   },
   "dependencies": {
-    "@types/jspdf": "^2.0.0",
+    "@ant-design/icons-vue": "^5.5.0",
+    "ant-design-vue": "^4.2.6",
+    "@vueuse/core": "^13.5.0",
     "axios": "^1.9.0",
-    "docx-pdf": "^0.0.1",
-    "html-to-image": "^1.11.13",
-    "html2canvas": "^1.4.1",
-    "jspdf": "^3.0.1",
-    "jspdf-autotable": "^5.0.2",
-    "jspdf-html2canvas": "^1.5.2",
-    "mammoth": "^1.9.1",
-    "pdf-lib": "^1.17.1",
-    "tesseract.js": "^6.0.1",
+    "dayjs": "^1.11.13",
+    "pinia": "^3.0.3",
+    "qs": "^6.14.0",
+    "query-string": "^9.2.2",
     "vue": "^3.5.13",
     "vue-router": "^4.5.1"
   },
@@ -44,6 +41,7 @@
     "@vue/eslint-config-typescript": "^14.5.0",
     "cross-env": "^7.0.3",
     "css-loader": "^7.1.2",
+    "@types/qs": "^6.14.0",
     "dotenv": "^16.5.0",
     "eslint": "^9.23.0",
     "eslint-config-prettier": "^10.1.5",

+ 0 - 0
public/aaa.txt


BIN
public/bgm.mp3


BIN
public/sound.wav


BIN
src/assets/bgm.mp3


BIN
src/assets/sound.wav


+ 24 - 0
src/router/constants.ts

@@ -0,0 +1,24 @@
+export const WHITE_LIST = [
+  { name: 'notFound', children: [] },
+  { name: 'login', children: [] },
+  { name: 'notAllow', children: [] },
+  {},
+];
+
+export const NOT_FOUND = {
+  name: 'notFound',
+};
+
+export const NOT_ALLOW = {
+  name: 'notAllow',
+};
+
+export const REDIRECT_ROUTE_NAME = 'Redirect';
+
+export const DEFAULT_ROUTE_NAME = 'Home';
+
+export const DEFAULT_ROUTE = {
+    title: '首页',
+    name: DEFAULT_ROUTE_NAME,
+    fullPath: '/home',
+  };

+ 5 - 0
src/router/guard/userLogin.ts

@@ -0,0 +1,5 @@
+import type { Router, LocationQueryRaw } from 'vue-router';
+
+export default function setupUserLoginInfoGuard(router: Router) {
+    
+}

+ 16 - 0
src/router/type.d.ts

@@ -0,0 +1,16 @@
+import 'vue-router';
+
+declare module 'vue-router' {
+  interface RouteMeta {
+    roles?: string[]; // Controls roles that have access to the page
+    requiresAuth: boolean; // Whether login is required to access the current page (every route must declare)
+    icon?: string; // The icon show in the side menu
+    locale?: string; // The locale name show in side menu and breadcrumb
+    hideInMenu?: boolean; // If true, it is not displayed in the side menu
+    hideChildrenInMenu?: boolean; // if set true, the children are not displayed in the side menu
+    activeMenu?: string; // if set name, the menu will be highlighted according to the name you set
+    order?: number; // Sort routing menu items. If set key, the higher the value, the more forward it is
+    noAffix?: boolean; // if set true, the tag will not affix in the tab-bar
+    ignoreCache?: boolean; // if set true, the page will not be cached
+  }
+}

+ 1 - 0
src/shims-vue.d.ts

@@ -1,5 +1,6 @@
 declare module "*.vue" {
 	import type { DefineComponent } from "vue";
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-empty-object-type
 	const component: DefineComponent<{}, {}, any>;
 	export default component;
 }

+ 85 - 0
src/utils/auth.ts

@@ -0,0 +1,85 @@
+/**
+ * 认证工具模块,处理用户token的存储和管理
+ */
+import { localStorage } from './storage';
+
+// Token 相关的键名
+const TOKEN_KEY = 'user_token';
+const USER_INFO_KEY = 'user_info';
+
+/**
+ * 设置用户 token
+ * @param token 用户 token
+ * @param expire 过期时间(单位:毫秒),默认 24 小时
+ */
+export function setToken(token: string, expire: number = 24 * 60 * 60 * 1000): void {
+  localStorage.set(TOKEN_KEY, token, expire);
+}
+
+/**
+ * 获取用户 token
+ * @returns 用户 token,如果不存在或已过期则返回 null
+ */
+export function getToken(): string | null {
+  return localStorage.get<string>(TOKEN_KEY);
+}
+
+/**
+ * 删除用户 token
+ */
+export function removeToken(): void {
+  localStorage.remove(TOKEN_KEY);
+}
+
+/**
+ * 检查是否存在有效的 token
+ * @returns 是否存在有效的 token
+ */
+export function hasToken(): boolean {
+  return getToken() !== null;
+}
+
+/**
+ * 设置用户信息
+ * @param userInfo 用户信息对象
+ * @param expire 过期时间(单位:毫秒),默认与token相同
+ */
+export function setUserInfo<T extends Record<string, any>>(userInfo: T, expire: number = 24 * 60 * 60 * 1000): void {
+  localStorage.set(USER_INFO_KEY, userInfo, expire);
+}
+
+/**
+ * 获取用户信息
+ * @returns 用户信息对象,如果不存在或已过期则返回 null
+ */
+export function getUserInfo<T extends Record<string, any>>(): T | null {
+  return localStorage.get<T>(USER_INFO_KEY);
+}
+
+/**
+ * 删除用户信息
+ */
+export function removeUserInfo(): void {
+  localStorage.remove(USER_INFO_KEY);
+}
+
+/**
+ * 清除所有认证相关信息(登出时使用)
+ */
+export function clearAuth(): void {
+  removeToken();
+  removeUserInfo();
+}
+
+/**
+ * 刷新 token 的过期时间
+ * @param expire 新的过期时间(单位:毫秒),默认 24 小时
+ * @returns 是否成功刷新(如果 token 不存在则返回 false)
+ */
+export function refreshTokenExpire(expire: number = 24 * 60 * 60 * 1000): boolean {
+  const token = getToken();
+  if (!token) return false;
+  
+  setToken(token, expire);
+  return true;
+} 

+ 0 - 52
src/utils/image.ts

@@ -1,52 +0,0 @@
-export function urlToBase64(url: string, mineType?: string): Promise<string> {
-    return new Promise((resolve, reject) => {
-      let canvas = document.createElement('CANVAS') as Nullable<HTMLCanvasElement>
-      const ctx = canvas!.getContext('2d')
-  
-      const img = new Image()
-      img.crossOrigin = ''
-      img.onload = function () {
-        if (!canvas || !ctx) {
-          return reject()
-        }
-        canvas.height = img.height
-        canvas.width = img.width
-        ctx.drawImage(img, 0, 0)
-        const dataURL = canvas.toDataURL(mineType || 'image/png')
-        canvas = null
-        resolve(dataURL)
-      }
-      img.src = url
-    })
-  }
-
-/**
- * 将文件对象转换为base64格式
- * @param file 文件对象,通常来自input[type="file"]的选择
- * @returns Promise<string> 返回base64格式的字符串
- */
-export function fileToBase64(file: File): Promise<string> {
-  return new Promise((resolve, reject) => {
-    if (!file) {
-      reject(new Error('文件不能为空'))
-      return
-    }
-
-    // 验证是否为图片文件
-    if (!file.type.startsWith('image/')) {
-      reject(new Error('请上传图片文件'))
-      return
-    }
-
-    const reader = new FileReader()
-    reader.readAsDataURL(file)
-    
-    reader.onload = () => {
-      resolve(reader.result as string)
-    }
-    
-    reader.onerror = (error) => {
-      reject(error)
-    }
-  })
-}

+ 0 - 159
src/utils/screenshot.ts

@@ -1,159 +0,0 @@
-/**
- * 浏览器截图工具类
- * 用于OCR识别备选方案
- */
-
-/**
- * 在浏览器中截取屏幕区域
- * 注意:仅在支持屏幕捕获API的浏览器中有效
- * 
- * @returns 截图的base64字符串
- */
-export async function captureScreen(): Promise<string> {
-  try {
-    // 请求屏幕共享
-    const mediaStream = await navigator.mediaDevices.getDisplayMedia({
-      video: {
-        displaySurface: 'monitor'
-      },
-      audio: false
-    });
-    
-    // 获取视频轨道
-    const videoTrack = mediaStream.getVideoTracks()[0];
-    
-    // 创建一个视频元素来显示流
-    const video = document.createElement('video');
-    video.srcObject = mediaStream;
-    
-    // 当视频加载完毕后进行截图
-    return new Promise<string>((resolve, reject) => {
-      video.onloadedmetadata = () => {
-        // 设置视频尺寸
-        video.width = video.videoWidth;
-        video.height = video.videoHeight;
-        
-        // 播放视频以便捕获当前帧
-        video.play();
-        
-        // 创建canvas元素用于截图
-        const canvas = document.createElement('canvas');
-        canvas.width = video.width;
-        canvas.height = video.height;
-        
-        // 在canvas上绘制视频当前帧
-        const ctx = canvas.getContext('2d');
-        if (!ctx) {
-          reject(new Error('无法获取canvas上下文'));
-          return;
-        }
-        
-        // 绘制视频帧到canvas
-        setTimeout(() => {
-          try {
-            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
-            
-            // 停止所有轨道
-            mediaStream.getTracks().forEach(track => track.stop());
-            
-            // 将canvas转换为base64字符串
-            const dataUrl = canvas.toDataURL('image/png');
-            resolve(dataUrl);
-          } catch (error) {
-            reject(error);
-          }
-        }, 200);
-      };
-      
-      video.onerror = () => {
-        reject(new Error('视频加载失败'));
-      };
-    });
-  } catch (error) {
-    console.error('截图失败:', error);
-    throw error;
-  }
-}
-
-/**
- * 处理图像以提高OCR识别率
- * 基于Data URL的图像处理
- * 
- * @param imageDataUrl 图像的Data URL
- * @returns 处理后的Data URL
- */
-export async function preprocessImageForOCR(imageDataUrl: string): Promise<string> {
-  return new Promise((resolve, reject) => {
-    const img = new Image();
-    img.onload = () => {
-      // 创建canvas
-      const canvas = document.createElement('canvas');
-      const ctx = canvas.getContext('2d');
-      if (!ctx) {
-        reject(new Error('无法获取canvas上下文'));
-        return;
-      }
-      
-      // 设置canvas尺寸
-      canvas.width = img.width;
-      canvas.height = img.height;
-      
-      // 绘制原图到canvas
-      ctx.drawImage(img, 0, 0);
-      
-      // 获取图像数据
-      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
-      const data = imageData.data;
-      
-      // 简单的图像预处理 - 增强对比度和边缘
-      for (let i = 0; i < data.length; i += 4) {
-        // 计算灰度值
-        const r = data[i];
-        const g = data[i + 1];
-        const b = data[i + 2];
-        let gray = 0.299 * r + 0.587 * g + 0.114 * b;
-        
-        // 应用阈值处理以增强对比度
-        // 根据灰度值应用阈值处理,得到二值化图像
-        const threshold = 180;
-        gray = gray > threshold ? 255 : 0;
-        
-        // 将处理后的值写回
-        data[i] = data[i + 1] = data[i + 2] = gray;
-      }
-      
-      // 将处理后的图像数据写回canvas
-      ctx.putImageData(imageData, 0, 0);
-      
-      // 转换为data URL并返回
-      const processedDataUrl = canvas.toDataURL('image/png');
-      resolve(processedDataUrl);
-    };
-    
-    img.onerror = () => {
-      reject(new Error('图像加载失败'));
-    };
-    
-    img.src = imageDataUrl;
-  });
-}
-
-/**
- * 将data URL转换为Blob对象
- * 
- * @param dataURL 图像的Data URL
- * @returns Blob对象
- */
-export function dataURLtoBlob(dataURL: string): Blob {
-  const parts = dataURL.split(';base64,');
-  const contentType = parts[0].split(':')[1];
-  const raw = window.atob(parts[1]);
-  const rawLength = raw.length;
-  const uInt8Array = new Uint8Array(rawLength);
-  
-  for (let i = 0; i < rawLength; ++i) {
-    uInt8Array[i] = raw.charCodeAt(i);
-  }
-  
-  return new Blob([uInt8Array], { type: contentType });
-} 

+ 0 - 141
src/utils/sorting.ts

@@ -1,141 +0,0 @@
-/**
- * 排序工具函数集合
- */
-
-/**
- * 比较函数类型定义
- * T 是要比较的元素类型
- * 返回负数表示 a < b,0 表示 a = b,正数表示 a > b
- */
-type CompareFunction<T> = (a: T, b: T) => number;
-
-/**
- * 默认比较函数,用于数字或字符串比较
- */
-const defaultCompare = <T>(a: T, b: T): number => {
-  if (a < b) return -1;
-  if (a > b) return 1;
-  return 0;
-};
-
-/**
- * 交换数组中的两个元素
- * @param arr 要操作的数组
- * @param i 第一个元素的索引
- * @param j 第二个元素的索引
- */
-function swap<T>(arr: T[], i: number, j: number): void {
-  const temp = arr[i];
-  arr[i] = arr[j];
-  arr[j] = temp;
-}
-
-/**
- * 分区函数 - 快速排序的核心
- * 选择一个基准值,将小于基准值的元素放在左侧,大于基准值的元素放在右侧
- * @param arr 要排序的数组
- * @param left 左边界索引
- * @param right 右边界索引
- * @param compare 比较函数
- * @returns 基准值的最终位置
- */
-function partition<T>(
-  arr: T[],
-  left: number,
-  right: number,
-  compare: CompareFunction<T>,
-): number {
-  // 选择最右边的元素作为基准值
-  const pivot = arr[right];
-
-  // 初始化小于基准值区域的指针
-  let i = left - 1;
-  //const numbers = [3, 1, 4, 1, 5, 9,8,7, 2, 6];
-  // 遍历当前分区
-  for (let j = left; j < right; j++) {
-    // 如果当前元素小于基准值
-    if (compare(arr[j], pivot) < 0) {
-      // 扩展小于基准值的区域
-      i++;
-      // 将当前元素交换到小于基准值的区域
-      swap(arr, i, j);
-    }
-  }
-
-  // 将基准值放到正确的位置
-  swap(arr, i + 1, right);
-
-  // 返回基准值的最终位置
-  return i + 1;
-}
-
-/**
- * 快速排序的递归实现
- * @param arr 要排序的数组
- * @param left 左边界索引
- * @param right 右边界索引
- * @param compare 比较函数
- */
-function quickSortRecursive<T>(
-  arr: T[],
-  left: number,
-  right: number,
-  compare: CompareFunction<T>,
-): void {
-  if (left < right) {
-    // 获取基准值的位置
-    const pivotIndex = partition(arr, left, right, compare);
-
-    // 递归排序基准值左侧的元素
-    quickSortRecursive(arr, left, pivotIndex - 1, compare);
-
-    // 递归排序基准值右侧的元素
-    quickSortRecursive(arr, pivotIndex + 1, right, compare);
-  }
-}
-
-/**
- * 快速排序算法
- * 时间复杂度: 平均 O(n log n), 最坏 O(n²)
- * 空间复杂度: O(log n) - 递归调用栈的深度
- *
- * @param arr 要排序的数组
- * @param compare 可选的比较函数,默认按自然顺序排序
- * @returns 排序后的数组(原地排序,返回原数组的引用)
- */
-export function quickSort<T>(
-  arr: T[],
-  compare: CompareFunction<T> = defaultCompare,
-): T[] {
-  if (arr.length <= 1) {
-    return arr;
-  }
-
-  // 复制数组以避免修改原数组(如果需要的话可以移除此行来实现原地排序)
-  // const result = [...arr];
-
-  // 调用递归版本的快速排序
-  quickSortRecursive(arr, 0, arr.length - 1, compare);
-
-  return arr;
-}
-
-/**
- * 使用示例:
- *
- * // 对数字数组进行排序
- * const numbers = [3, 1, 4, 1, 5, 9, 2, 6];
- * quickSort(numbers);  // [1, 1, 2, 3, 4, 5, 6, 9]
- *
- * // 对对象数组按特定属性排序
- * const users = [
- *   { name: 'Tom', age: 25 },
- *   { name: 'Jerry', age: 18 },
- *   { name: 'Spike', age: 32 }
- * ];
- * quickSort(users, (a, b) => a.age - b.age);  // 按年龄升序排序
- */
-
-const numbers = [3, 1, 4, 1, 5, 9, 8, 7, 2, 6];
-const sortedNumbers = quickSort(numbers); // [1, 1, 2, 3, 4, 5, 6, 9]
-console.log(sortedNumbers);

+ 124 - 0
src/utils/storage.ts

@@ -0,0 +1,124 @@
+/**
+ * 存储类,用于处理 localStorage 和 sessionStorage
+ * 支持设置过期时间、JSON对象存储等功能
+ */
+
+// 存储项的数据结构
+interface StorageData<T = any> {
+  value: T; // 存储的值
+  expire?: number; // 过期时间的时间戳
+}
+
+// 存储类型
+type StorageType = 'localStorage' | 'sessionStorage';
+
+/**
+ * 存储类,提供对 localStorage 和 sessionStorage 的封装
+ * 支持设置过期时间、JSON对象存储等功能
+ */
+class Storage {
+  private storage: globalThis.Storage;
+
+  /**
+   * 构造函数
+   * @param type 存储类型,可选 'localStorage' 或 'sessionStorage'
+   */
+  constructor(type: StorageType = 'localStorage') {
+    this.storage = type === 'localStorage' ? window.localStorage : window.sessionStorage;
+  }
+
+  /**
+   * 设置存储项
+   * @param key 键名
+   * @param value 值
+   * @param expire 过期时间(单位:毫秒),不设置则永不过期
+   */
+  set<T>(key: string, value: T, expire?: number): void {
+    const data: StorageData<T> = {
+      value
+    };
+    
+    // 如果设置了过期时间,计算过期的时间戳
+    if (expire) {
+      data.expire = Date.now() + expire;
+    }
+    
+    // 将数据转换为 JSON 字符串存储
+    this.storage.setItem(key, JSON.stringify(data));
+  }
+
+  /**
+   * 获取存储项
+   * @param key 键名
+   * @returns 存储的值,如果不存在或已过期则返回 null
+   */
+  get<T>(key: string): T | null {
+    const item = this.storage.getItem(key);
+    
+    // 如果项不存在,返回 null
+    if (!item) return null;
+    
+    try {
+      const data: StorageData<T> = JSON.parse(item);
+      
+      // 检查是否过期
+      if (data.expire && Date.now() > data.expire) {
+        // 已过期,删除该项并返回 null
+        this.remove(key);
+        return null;
+      }
+      
+      return data.value;
+    } catch (error) {
+      // 解析 JSON 出错,返回 null
+      console.error(`Error parsing storage item with key "${key}":`, error);
+      return null;
+    }
+  }
+
+  /**
+   * 删除存储项
+   * @param key 键名
+   */
+  remove(key: string): void {
+    this.storage.removeItem(key);
+  }
+
+  /**
+   * 清空所有存储项
+   */
+  clear(): void {
+    this.storage.clear();
+  }
+
+  /**
+   * 检查是否存在某个键
+   * @param key 键名
+   * @returns 是否存在且未过期
+   */
+  has(key: string): boolean {
+    return this.get(key) !== null;
+  }
+
+  /**
+   * 获取所有未过期的键
+   * @returns 键名数组
+   */
+  keys(): string[] {
+    const keys: string[] = [];
+    for (let i = 0; i < this.storage.length; i++) {
+      const key = this.storage.key(i);
+      if (key && this.has(key)) {
+        keys.push(key);
+      }
+    }
+    return keys;
+  }
+}
+
+// 创建默认实例
+export const localStorage = new Storage('localStorage');
+export const sessionStorage = new Storage('sessionStorage');
+
+// 导出类和默认实例
+export default Storage; 

+ 6 - 0
src/views/home/index.vue

@@ -0,0 +1,6 @@
+<template>
+    <div>我是首页</div>
+</template>
+<script setup lang="ts">
+
+</script>