feat: 上传

This commit is contained in:
2025-09-27 15:14:02 +08:00
parent 7296a13e88
commit 60c28c2297
7 changed files with 134 additions and 51 deletions

View File

@@ -4,7 +4,6 @@
"type": "module",
"scripts": {
"dev": "vite",
"dev:https": "vite --https",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"

View File

@@ -0,0 +1,40 @@
import useAxios from "axios-hooks";
import { Page, Result } from "@/types/http";
export interface MockResult {
id: number;
}
export interface MockPage {
id: number;
}
/**
* fetch the data
* 详细使用可以查看 useAxios 的文档
*/
export const useUploadAudio = () => {
const url = `/app-api/ai/sample/translate`;
const [{ data, loading, error }, execute] = useAxios<Result<any>>(
{
url,
method: "POST",
headers: {
"Content-Type": "multipart/form-data",
},
},
{ manual: true } // 手动触发
);
const uploadAudio = (formData: FormData) => {
return execute({
data: formData,
headers: {
"Content-Type": "multipart/form-data",
},
});
};
return { data, loading, error, uploadAudio };
};

View File

@@ -1,49 +1,51 @@
import Axios, {
AxiosError,
AxiosInstance as AxiosType,
AxiosResponse,
InternalAxiosRequestConfig
} from 'axios';
import {STORAGE_AUTHORIZE_KEY} from "@/composables/authorization.ts";
AxiosError,
AxiosInstance as AxiosType,
AxiosResponse,
InternalAxiosRequestConfig,
} from "axios";
import { STORAGE_AUTHORIZE_KEY } from "@/composables/authorization.ts";
export interface ResponseBody<T = any> {
code: number;
data?: T;
msg: string;
code: number;
data?: T;
msg: string;
}
async function requestHandler(config: InternalAxiosRequestConfig): Promise<InternalAxiosRequestConfig> {
const token = localStorage.getItem(STORAGE_AUTHORIZE_KEY);
if (token) {
config.headers[STORAGE_AUTHORIZE_KEY] = token;
}
return config;
async function requestHandler(
config: InternalAxiosRequestConfig
): Promise<InternalAxiosRequestConfig> {
const token = localStorage.getItem(STORAGE_AUTHORIZE_KEY);
if (token) {
config.headers[STORAGE_AUTHORIZE_KEY] = token;
}
return config;
}
function responseHandler(response: AxiosResponse<ResponseBody>): AxiosResponse<ResponseBody> {
// 响应拦截器逻辑...
return response;
// 响应拦截器逻辑...
return response;
}
function errorHandler(error: AxiosError<ResponseBody>): Promise<AxiosError<ResponseBody>> {
// 错误处理逻辑...
return Promise.reject(error);
// 错误处理逻辑...
return Promise.reject(error);
}
class AxiosInstance {
private readonly instance: AxiosType;
private readonly instance: AxiosType;
constructor(baseURL: string) {
this.instance = Axios.create({baseURL});
constructor(baseURL: string) {
this.instance = Axios.create({ baseURL });
this.instance.interceptors.request.use(requestHandler, errorHandler);
this.instance.interceptors.response.use(responseHandler, errorHandler);
}
this.instance.interceptors.request.use(requestHandler, errorHandler);
this.instance.interceptors.response.use(responseHandler, errorHandler);
}
public getInstance(): AxiosType {
return this.instance;
}
public getInstance(): AxiosType {
return this.instance;
}
}
const baseURL = import.meta.env.VITE_BASE_URL || 'http://127.0.0.1:8080';
const baseURL = import.meta.env.VITE_BASE_URL || "http://192.168.1.231:48080";
export const axiosInstance = new AxiosInstance(baseURL).getInstance();

View File

@@ -5,7 +5,8 @@ import microphoneSvg from "@/assets/translate/microphone.svg";
import microphoneDisabledSvg from "@/assets/translate/microphoneDisabledSvg.svg";
import { createStartRecordSound, createSendSound } from "@/utils/voice";
import "./index.less";
import { useUploadAudio } from "@/api/translate";
import VConsole from "vconsole";
interface DefinedProps {
onRecordingComplete: (url: string, finalDuration: number) => void;
isRecording: boolean;
@@ -13,6 +14,7 @@ interface DefinedProps {
}
function Index(props: DefinedProps) {
const { isRecording } = props;
const { loading: uploadLoading, error: uploadError, uploadAudio } = useUploadAudio();
const [hasPermission, setHasPermission] = useState<boolean>(false); //是否有权限
const [isPermissioning, setIsPermissioning] = useState<boolean>(true); //获取权限中
const [recordingDuration, setRecordingDuration] = useState<number>(0); //录音时长进度
@@ -23,6 +25,12 @@ function Index(props: DefinedProps) {
// 音效相关
const sendSoundRef = useRef<HTMLAudioElement | null>(null);
const startRecordSoundRef = useRef<HTMLAudioElement | null>(null);
useEffect(() => {
// 只在开发环境启用
if (process.env.NODE_ENV === "development") {
new VConsole();
}
}, []);
useEffect(() => {
initializeSounds();
checkMicrophonePermission();
@@ -59,7 +67,17 @@ function Index(props: DefinedProps) {
console.error("音效初始化失败:", error);
}
};
const handleUploadAudio = async (formData: FormData) => {
// 打印FormData内容
console.log(formData);
try {
const response = await uploadAudio(formData);
console.log("上传成功:", response.data);
} catch (error) {
console.error("上传失败:", error);
}
};
const renderBtn = useCallback(() => {
if (!hasPermission) {
//没有权限
@@ -210,7 +228,7 @@ function Index(props: DefinedProps) {
//录音完成
// 在发送时检查录音时长
const onRecordingComplete = useCallback(
(blob: Blob) => {
async (blob: Blob) => {
if (isCancelledRef.current) {
Toast.show("已取消");
return;
@@ -221,6 +239,10 @@ function Index(props: DefinedProps) {
return;
}
const formData = new FormData();
formData.append("file", blob);
await handleUploadAudio(formData);
const audioUrl = URL.createObjectURL(blob);
const audio = new Audio();
audio.src = audioUrl;
@@ -231,7 +253,7 @@ function Index(props: DefinedProps) {
Toast.show("录音时间太短,请重新录音");
return;
}
alert(audio.duration);
// alert(audio.duration);
playSound(sendSoundRef);
props.onRecordingComplete?.(audioUrl, Math.floor(audio.duration));
});

View File

@@ -4,12 +4,18 @@ import path, { resolve } from "path";
import { inspectorServer } from "@react-dev-inspector/vite-plugin";
import basicSsl from "@vitejs/plugin-basic-ssl";
export default defineConfig({
plugins: [react(), basicSsl()],
// basicSsl()
plugins: [react()],
server: {
https: {},
port: 3000,
host: "0.0.0.0",
open: true,
proxy: {
"/app-api": {
target: "http://192.168.1.231:48080",
changeOrigin: true,
},
},
},
css: {
modules: {},