feat: init
This commit is contained in:
12
packages/hooks/src/i18n.ts
Normal file
12
packages/hooks/src/i18n.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
60
packages/hooks/src/location.ts
Normal file
60
packages/hooks/src/location.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
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};
|
||||
};
|
||||
28
packages/hooks/src/session.ts
Normal file
28
packages/hooks/src/session.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
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;
|
||||
198
packages/hooks/src/useFileUpload.ts
Normal file
198
packages/hooks/src/useFileUpload.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
// 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,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user