feat: init

This commit is contained in:
2025-09-05 15:18:10 +08:00
parent ddbee614e8
commit 85244a451e
126 changed files with 3020 additions and 10278 deletions

View File

@@ -1,12 +0,0 @@
import zhCN from '@/locales/zh_CN.json'
import enUS from '@/locales/en_US.json'
import {useI18nStore} from '@/store/i18n';
export default function useI18n() {
const {lang} = useI18nStore();
const locales = lang === 'en_US' ? enUS : zhCN;
return (name: string) => {
return locales[name as keyof typeof locales] || name;
}
}

View File

@@ -1,60 +0,0 @@
import {useState, useEffect} from 'react';
import useI18n from "@/hooks/i18n";
// 定义位置坐标的类型
export interface Coordinates {
lat: number;
lng: number;
}
// 定义返回的位置状态的类型
export interface LocationState {
loaded: boolean;
coordinates: Coordinates | null;
}
// 定义错误状态的类型
export type ErrorState = string | null;
export const useLocation = (): { location: LocationState; error: ErrorState } => {
const t = useI18n();
// 用于存储位置信息和加载状态的状态
const [location, setLocation] = useState<LocationState>({
loaded: false,
coordinates: null,
});
// 用于存储任何错误消息的状态
const [error, setError] = useState<ErrorState>(null);
// 地理位置成功处理函数
const onSuccess = (location: GeolocationPosition) => {
setLocation({
loaded: true,
coordinates: {
lat: location.coords.latitude,
lng: location.coords.longitude,
},
});
};
// 地理位置错误处理函数
const onError = (error: GeolocationPositionError) => {
setError(error.message);
};
// 使用 useEffect 在组件挂载时执行地理位置请求
useEffect(() => {
// 检查浏览器是否支持地理位置
if (!navigator.geolocation) {
setError(t('hooks.location.unsupported'));
return;
}
// 请求用户的当前位置
navigator.geolocation.getCurrentPosition(onSuccess, onError);
}, []);
return {location, error};
};

View File

@@ -1,28 +0,0 @@
import {useState, useEffect} from 'react';
import isEqual from 'lodash.isequal';
function useSessionStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
// 初始化状态
const [storedValue, setStoredValue] = useState<T>(() => {
const item = sessionStorage.getItem(key);
if (item !== null) {
// 如果 sessionStorage 中有数据,则使用现有数据
return JSON.parse(item);
} else {
// 当 sessionStorage 中没有相应的键时,使用 initialValue 初始化,并写入 sessionStorage
sessionStorage.setItem(key, JSON.stringify(initialValue));
return initialValue;
}
});
// 监听并保存变化到 sessionStorage
useEffect(() => {
if (!isEqual(JSON.parse(sessionStorage.getItem(key) || 'null'), storedValue)) {
sessionStorage.setItem(key, JSON.stringify(storedValue));
}
}, [key, storedValue]);
return [storedValue, setStoredValue];
}
export default useSessionStorage;

View File

@@ -0,0 +1,16 @@
// hooks/useDocumentTitle.js
import { useEffect } from "react";
const useDocumentTitle = (title: string) => {
useEffect(() => {
if (title) {
document.title = title;
}
// 组件卸载时可以恢复默认标题
return () => {
document.title = "默认标题"; // 或者从配置中获取
};
}, [title]);
};
export default useDocumentTitle;

View File

@@ -1,198 +0,0 @@
// hooks/useFileUpload.ts (更新)
import { useState, useCallback } from "react";
import {
UploadConfig,
UploadProgress,
UploadResponse,
VoiceUploadStatus,
} from "../types/upload";
interface UseFileUploadReturn {
uploadStatus: VoiceUploadStatus;
uploadFile: (
file: Blob,
fileName: string,
config: UploadConfig
) => Promise<UploadResponse>;
resetUpload: () => void;
}
export const useFileUpload = (): UseFileUploadReturn => {
const [uploadStatus, setUploadStatus] = useState<VoiceUploadStatus>({
status: "idle",
});
// 检测文件类型并转换文件名
const getFileExtension = (mimeType: string): string => {
const mimeToExt: Record<string, string> = {
"audio/webm": ".webm",
"audio/mp4": ".m4a",
"audio/aac": ".aac",
"audio/wav": ".wav",
"audio/ogg": ".ogg",
"audio/mpeg": ".mp3",
};
// 处理带codecs的MIME类型
const baseMimeType = mimeType.split(";")[0];
return mimeToExt[baseMimeType] || ".webm";
};
const uploadFile = useCallback(
async (
file: Blob,
fileName: string,
config: UploadConfig
): Promise<UploadResponse> => {
// 检查文件大小
if (config.maxFileSize && file.size > config.maxFileSize) {
const error = `文件大小超过限制 (${Math.round(
config.maxFileSize / 1024 / 1024
)}MB)`;
setUploadStatus({
status: "error",
error,
});
throw new Error(error);
}
// 更宽松的文件类型检查支持iOS格式
const allowedTypes = config.allowedTypes || [
"audio/webm",
"audio/mp4",
"audio/aac",
"audio/wav",
"audio/ogg",
"audio/mpeg",
];
const baseMimeType = file.type.split(";")[0];
const isTypeAllowed = allowedTypes.some(
(type) => baseMimeType === type || baseMimeType === type.split(";")[0]
);
if (!isTypeAllowed) {
console.warn(`文件类型 ${file.type} 不在允许列表中,但继续上传`);
}
// 根据实际MIME类型调整文件名
const extension = getFileExtension(file.type);
const adjustedFileName = fileName.replace(/\.[^/.]+$/, "") + extension;
setUploadStatus({
status: "uploading",
progress: { loaded: 0, total: file.size, percentage: 0 },
});
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append(config.fieldName || "file", file, adjustedFileName);
// 添加额外的元数据
formData.append("fileName", adjustedFileName);
formData.append("originalFileName", fileName);
formData.append("fileSize", file.size.toString());
formData.append("fileType", file.type);
formData.append("uploadTime", new Date().toISOString());
formData.append("userAgent", navigator.userAgent);
const xhr = new XMLHttpRequest();
// 上传进度监听
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
const progress: UploadProgress = {
loaded: event.loaded,
total: event.total,
percentage: Math.round((event.loaded / event.total) * 100),
};
setUploadStatus({
status: "uploading",
progress,
});
}
});
// 上传完成监听
xhr.addEventListener("load", () => {
try {
const response: UploadResponse = JSON.parse(xhr.responseText);
if (xhr.status >= 200 && xhr.status < 300 && response.success) {
setUploadStatus({
status: "success",
response,
progress: {
loaded: file.size,
total: file.size,
percentage: 100,
},
});
resolve(response);
} else {
const error = response.error || `上传失败: ${xhr.status}`;
setUploadStatus({
status: "error",
error,
});
reject(new Error(error));
}
} catch (parseError) {
const error = "服务器响应格式错误";
setUploadStatus({
status: "error",
error,
});
reject(new Error(error));
}
});
// 上传错误监听
xhr.addEventListener("error", () => {
const error = "网络错误,上传失败";
setUploadStatus({
status: "error",
error,
});
reject(new Error(error));
});
// 上传中断监听
xhr.addEventListener("abort", () => {
const error = "上传已取消";
setUploadStatus({
status: "error",
error,
});
reject(new Error(error));
});
// 设置请求头
const headers = {
"X-Requested-With": "XMLHttpRequest",
...config.headers,
};
Object.entries(headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
});
// 发送请求
xhr.open(config.method || "POST", config.url);
xhr.send(formData);
});
},
[]
);
const resetUpload = useCallback(() => {
setUploadStatus({ status: "idle" });
}, []);
return {
uploadStatus,
uploadFile,
resetUpload,
};
};