vue3+ts 实现文件上传(包含上传、下载、和预览功能),番外(接口请求封装)

公共组件
上传文件包含上传、下载、和预览功能

<!-- 上传文件 -->
<template>
	<div class="demo-image__preview">
		<el-image-viewer
			hide-on-click-modal
			@close="
				() => {
					showViewer = false;
				}
			"
			v-if="showViewer"
			:url-list="srcList"
		/>
	</div>
	<el-upload
		style="width: 100%"
		action=""
		v-model:file-list="fileList"
		class="upload-demo"
		:http-request="uploadFile"
		multiple
		:on-preview="handlePreview"
		:on-success="handleSuccess"
		:on-remove="handleRemove"
		:on-error="handleError"
		:before-upload="handleBefore"
		:limit="attr.limit"
		:on-exceed="handleExceed"
		ref="fileRef"
		:disabled="attr.readonly"
	>
		<el-button type="primary" v-if="!attr.readonly">上传</el-button>
	</el-upload>
	<el-dialog title="查看视频" v-model="videoShow">
		<video
			ref="veo"
			@click.prevent.once="onPlay"
			@loadeddata="poster ? () => false : getPoster()"
			:src="src"
			:autoplay="autoplay"
			controls="true"
			style="width: 100%; height: 100%"
		></video>
	</el-dialog>
</template>

<script setup lang="ts" name="ImportExcel">
import { uploadFile as uploadFun, downloadFileByFileId } from "@/api/modules/upload";
import type { UploadProps, UploadUserFile, UploadRawFile } from "element-plus";
import { ElMessage, genFileId } from "element-plus";
const emit = defineEmits(["update:modelValue"]);
const videoShow = ref(false);
const props = defineProps({
	modelValue: [Array],
	attr: {
		type: Object,
		required: false,
		default: () => {}
	}
});

const fileRef = ref<any>(null);
//上传文件
function uploadFile(params: any) {
	let formData = new FormData();
	//传值
	formData.append("file", params.file);
	uploadFun(formData).then(res => {
		if (res.code == 200) {
			params.onSuccess(res.data);
		} else {
			params.onError(res.data);
		}
	});
}
function handleBefore(row: any) {
	if (row.size / 1024 / 1024 > 100) {
		ElMessage.error("文件大小不能超过100M");
		return false;
	}
	return true;
}
const fileList = ref<UploadUserFile[]>([]);
//上传成功之后的操作
const handleSuccess = (res: any, file: UploadUserFile, fileList: UploadUserFile[]) => {
	console.log(file);
	file.uid = res.id;
	let arr = fileList.map(item => {
		let obj: any = {};
		obj[props.attr.name] = item.name;
		obj[props.attr.id] = item.uid;
		return obj;
	});
	console.log(fileList);
	emit("update:modelValue", arr);
};

watch(
	() => props.modelValue,
	val => {
		if (val) {
			fileList.value = val.map((item: any) => {
				item.name = item[props.attr.name];
				item.uid = item[props.attr.id];
				// item.response = item;
				return item;
			});
		} else {
			fileList.value = [];
			return [];
		}
	},
	{ deep: true, immediate: true }
);

const veo = ref();

const originPlay = ref(true);
const autoplay = ref(false);
const hidden = ref(false); // 是否隐藏播放器中间的播放按钮
const second = ref(0.5);

const poster = ref("");
const getPoster = () => {
	// 在未设置封面时,自动截取视频0.5s对应帧作为视频封面
	// 由于不少视频第一帧为黑屏,故设置视频开始播放时间为0.5s,即取该时刻帧作为封面图
	veo.value.currentTime = second.value;
	// 创建canvas元素
	const canvas = document.createElement("canvas");
	const ctx = canvas.getContext("2d");
	// canvas画图
	canvas.width = veo.value.videoWidth;
	canvas.height = veo.value.videoHeight;
	ctx?.drawImage(veo.value, 0, 0, canvas.width, canvas.height);
};
//是否播放
const onPlay = () => {
	if (originPlay.value) {
		veo.value.currentTime = 0;
		originPlay.value = false;
	}
	if (autoplay.value) {
		veo.value?.pause();
	} else {
		hidden.value = true;
		veo.value?.play();
	}
};
const isView = (ext: any) => {
	return ["png", "jpg", "jpeg", "bmp", "gif", "webp", "psd", "svg"].indexOf(ext.toLowerCase()) !== -1;
};
//上传失败
const handleError = (res: any) => {
	console.log(res);
	ElMessage.error({ message: res.msg || "上传失败!" });
};
const src = ref("");
const srcList = ref([] as any[]);
const showViewer = ref(false);
//下载文件
const handlePreview: UploadProps["onPreview"] = uploadFile => {
	let suffix = uploadFile.name.substring(uploadFile.name.lastIndexOf(".") + 1);
	if (suffix == "mp4") {
		videoShow.value = true;
		src.value = "/api/dems-resource/file/loadOnlineVideo?id=" + uploadFile.uid;
		return;
	}
	if (isView(suffix)) {
		src.value = "/api/dems-resource/file/loadOnlineImage?id=" + uploadFile.uid;
		srcList.value = [src.value];
		showViewer.value = true;
		return;
	}
	downloadFileByFileId(uploadFile.name, uploadFile.uid + "");
};
//删除文件
const handleRemove: UploadProps["onRemove"] = (uploadFile, uploadFiles) => {
	let arr = uploadFiles.map(item => {
		let obj: any = {};
		obj[props.attr.name] = item.name;
		obj[props.attr.id] = item.uid;
		return obj;
	});
	emit("update:modelValue", arr);
};

const handleExceed: UploadProps["onExceed"] = files => {
	if (props.attr.limit == 1) {
		fileRef.value!.clearFiles();
		const file = files[0] as UploadRawFile;
		file.uid = genFileId();
		fileRef.value!.handleStart(file);
		fileRef.value!.submit();
	}
};
</script>
<style lang="scss" scoped>
.upload {
	width: 100%;
}
:deep(.el-dialog .el-dialog__header) {
	border-bottom: none !important;
}
</style>

main.ts全局引用

import customUpload from "@/components/Upload/custom-upload.vue";
app.component("CustomUpload", customUpload);

组件页面使用

多文件
	<custom-upload v-model="formInline.attachmentResultList" :attr="attrResult" />
js
const attrResult = ref({
	id: "fileUploadId",
	name: "attachmentName",
	limit: 999,
	readonly: true
});

单文件

	<custom-upload v-model="faultList" :attr="attr" />
	const attr = ref({
	id: "reportFileUploadId",
	name: "faultReport",
	limit: 1,
	readonly: false
});

子组件方法
//获取故障文件信息(永远只能上传一个,然后可以替换那一个文件)
const getBreakdownData = () => {
	if (faultList.value.length > 0) {
		let file = faultList.value[0];
		formInline.value.faultReport = file.faultReport;
		formInline.value.reportFileUploadId = file.reportFileUploadId;
	}
	getFaultDurationStr();
	return formInline.value;
};
defineExpose({
	getBreakdownData
});
父组件方法
const failureRecordSn = ref("");
save(){
	// 接收故障记录
		formInline.value.failureRecord = breakdownRef.value.getBreakdownData();
		formInline.value.failureRecord!.failureRecordSn = failureRecordSn.value;
		let failureRecord = { ...formInline.value.failureRecord };
		// 故障记录为空时删除
		if (!Object.values(failureRecord).some(i => !!i)) {
			delete formInline.value.failureRecord;
		}
}

用到的文件
upload.ts

//文件的接口类型
import { Upload } from "@/api/interface/index";
api封装
import http from "@/api";
//前缀
import { UPLOAD_FILE } from "@/api/config/servicePort";
import { ElMessage } from "element-plus";
//上传文件
export const uploadFile = (params: FormData) => {
	return http.upload<Upload.ResFileList>(UPLOAD_FILE + `/file/uploadFile`, params, {
		headers: { "Content-Type": "multipart/form-data" }
	});
};
// * 用文件名称下载文件
export const downloadFileByFileId = (fileName: string, id: string) => {
	http.download(UPLOAD_FILE + `/file/downloadFileByFileId`, { fileName, fileId: id }).then(res => {
		ElMessage.success({ message: "下载成功!" });
		const blob = new Blob([res]); //处理文档流
		const link = document.createElement("a");
		link.download = fileName;
		link.style.display = "none";
		link.href = URL.createObjectURL(blob);
		document.body.appendChild(link);
		link.click();
		URL.revokeObjectURL(link.href); // 释放URL 对象
		document.body.removeChild(link);
	});
};

定义接口interface
import { Upload } from "@/api/interface/index";
接口如下

// * 文件上传模块
export namespace Upload {
	export interface ResFileUrl {
		fileUrl: string;
	}
	export interface ResFileList {
		id: string;
		fileName: string;
		fileUrl: string;
		uploadTime: string;
		operator: string;
		fileType: string;
		fileSize: number;
		fileOldname: string;
	}
}

http在api/index.ts文件里面
包含拦截器,响应器和请求方式的封装
import http from "@/api";

import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from "axios";
import { showFullScreenLoading, tryHideFullScreenLoading } from "@/config/serviceLoading";
import { ResultData, GatewayResultData } from "@/api/interface";
import { ResultEnum } from "@/enums/httpEnum";
import { checkStatus } from "./helper/checkStatus";
import { ElMessage, ElMessageBox } from "element-plus";
import { GlobalStore } from "@/stores";
import { CallCenterStore } from "@/stores/modules/ccs";//这个可以不要(其他功能的)
import { LOGIN_URL } from "@/config/config";//主题颜色(// * 登录页地址(默认)
export const LOGIN_URL: string = "/login";)
import { encodeHtml, decodeHtml } from "@/utils/htmlUtil";//富文本
import router from "@/routers";
import qs from "qs";
const config = {
	// 默认地址请求地址,可在 .env.*** 文件中修改
	baseURL: import.meta.env.VITE_API_URL as string,
	// 设置超时时间(10s)
	timeout: ResultEnum.TIMEOUT as number,
	// 跨域时候允许携带凭证
	withCredentials: true
};

class RequestHttp {
	service: AxiosInstance;
	public constructor(config: AxiosRequestConfig) {
		// 实例化axios
		this.service = axios.create(config);

		/**
		 * @description 请求拦截器
		 * 客户端发送请求 -> [请求拦截器] -> 服务器
		 * token校验(JWT) : 接受服务器返回的token,存储到vuex/pinia/本地储存当中
		 */
		this.service.interceptors.request.use(
			(config: InternalAxiosRequestConfig) => {
				encodeHtml(config);
				const globalStore = GlobalStore();
				// * 如果当前请求不需要显示 loading,在 api 服务中通过指定的第三个参数: { headers: { noLoading: true } }来控制不显示loading,参见loginApi
				config.headers!.noLoading || showFullScreenLoading();
				const token = globalStore.token;
				if (config.headers && typeof config.headers?.set === "function") config.headers.set("Authorization", token);
				return config;
			},
			(error: AxiosError) => {
				return Promise.reject(error);
			}
		);

		/**
		 * @description 响应
		 *  服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
		 */
		this.service.interceptors.response.use(
			(response: AxiosResponse) => {
				const { data } = response;
				const globalStore = GlobalStore();
				const callCenterStore = CallCenterStore();
				// * 在请求结束后,并关闭请求 loading
				tryHideFullScreenLoading();
				// * 登陆失效(code == 401)
				if (data.code == ResultEnum.OVERDUE) {
					if (document.getElementsByClassName("el-message").length == 0) {
						ElMessage.error(data.msg);
						//错误提示
					}
					globalStore.setToken("");
					globalStore?.webSocket?.close();
					globalStore.setWebSocket(null);
					callCenterStore.clearAllData();
					router.replace(LOGIN_URL);
					return Promise.reject(data);
				}
				// * 全局错误信息拦截(防止下载文件得时候返回数据流,没有code,直接报错)
				if (data.code && data.code !== ResultEnum.SUCCESS) {
					if (data.code > 200 && data.code < 300) {
						ElMessage.warning(data.msg);
					} else {
						if (data.code !== 502) {
							ElMessage.error(data.msg);
						}
					}
					return Promise.reject(data);
				}
				decodeHtml(data, response.config.url);
				// * 成功请求(在页面上除非特殊情况,否则不用在页面处理失败逻辑)
				return data;
			},
			async (error: AxiosError) => {
				tryHideFullScreenLoading();
				const { response } = error;
				const { data, code } = (response?.data as any) || {};
				if (code == 400) {
					let arr = [];
					for (let v in data) {
						arr.push(data[v]);
					}
					ElMessageBox.alert(arr.join("</br>"), "错误", {
						confirmButtonText: "确认",
						dangerouslyUseHTMLString: true
					});
					return Promise.reject();
				}
				// 由于后端的微服务网关报错时返回的是特殊响应数据,此处对登录失败的情况做特殊处理
				if (response?.config?.url === "auth/token/login") {
					return { data: response.data };
				}
				tryHideFullScreenLoading();
				// 上传失败如果不return 不进el-upload的on-error
				if (
					response?.config?.url === "/resource/file/uploadFiles" ||
					response?.config?.url === "/dems-resource/file/uploadFile"
				) {
					return { data: response?.data };
				}
				// 请求超时 && 网络错误单独判断,没有 response
				if (error.message.indexOf("timeout") !== -1) ElMessage.error("请求超时!请您稍后重试");
				if (error.message.indexOf("Network Error") !== -1) ElMessage.error("网络错误!请您稍后重试");
				// 根据响应的错误状态码,做不同的处理
				if (response && response.data) {
					let data = <any>response.data;
					let obj = data.body ?? data;
					if ("msg" in obj) {
						ElMessage.error(obj.msg);
					} else {
						checkStatus(response.status);
					}
				} else if (response) {
					checkStatus(response.status);
				}
				// * 登陆失效(code == 401)
				if (response?.status == ResultEnum.OVERDUE) {
					if (document.getElementsByClassName("el-message").length == 0) {
						ElMessage.error(data.msg);
						//错误提示
					}
					const globalStore = GlobalStore();
					globalStore.setToken("");
					globalStore?.webSocket?.close();
					globalStore.setWebSocket(null);
					const callCenterStore = CallCenterStore();
					callCenterStore.clearAllData();
					router.replace(LOGIN_URL);
				}
				// 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
				if (!window.navigator.onLine) router.replace("/500");
				return Promise.reject(error);
			}
		);
	}

	// * 常用请求方法封装
	get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
		return this.service.get(url, { params, ..._object });
	}
	post<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
		return this.service.post(url, qs.stringify(params), _object);
	}
	postJson<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
		return this.service.post(url, params, _object);
	}
	put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
		return this.service.put(url, params, _object);
	}
	delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
		return this.service.delete(url, { params, ..._object });
	}
	download(url: string, params?: object, _object = {}): Promise<BlobPart> {
		return this.service.get(url, { params, ..._object, responseType: "blob", timeout: 0 });
	}
	getRequest<T>(url: string, params?: object, _object = {}): Promise<GatewayResultData<T>> {
		return this.service.get(url, { params, ..._object });
	}
	upload<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
		return this.service.post(url, params, { ..._object, timeout: 0 });
	}
}

export default new RequestHttp(config);

import { showFullScreenLoading, tryHideFullScreenLoading } from "@/config/serviceLoading";

import { ElLoading } from "element-plus";

/* 全局请求 loading(服务方式调用) */
let loadingInstance: ReturnType<typeof ElLoading.service>;

/**
 * @description 开启 Loading
 * */
const startLoading = () => {
	loadingInstance = ElLoading.service({
		fullscreen: true,
		lock: true,
		text: "Loading",
		background: "rgba(0, 0, 0, 0.7)"
	});
};

/**
 * @description 结束 Loading
 * */
const endLoading = () => {
	loadingInstance.close();
};

/**
 * @description 显示全屏加载
 * */
let needLoadingRequestCount = 0;
export const showFullScreenLoading = () => {
	if (needLoadingRequestCount === 0) {
		startLoading();
	}
	needLoadingRequestCount++;
};

/**
 * @description 隐藏全屏加载
 * */
export const tryHideFullScreenLoading = () => {
	if (needLoadingRequestCount <= 0) return;
	needLoadingRequestCount--;
	if (needLoadingRequestCount === 0) {
		endLoading();
	}
};

interface接口

import { ResultData, GatewayResultData } from "@/api/interface";

// * 请求响应参数(不包含data)
export interface Result {
	code: number;
	msg: string;
}

/**
 * 请求响应参数(包含data)
 */
export interface ResultData<T = any> extends Result {
	data: T;
}

/**
 * 请求响应参数(网关的特殊响应)
 */
export interface GatewayResultData<T = any> extends Result {
	body: T;
}

/**
 *  后端返回的分页响应参数
 */
export interface ResPage<T> {
	// 查询数据列表
	records: T[];
	// 当前页
	current: number;
	// 每页显示条数
	size: number;
	// 总数
	total: number;
}

/**
 * 分页请求参数
 */
export interface ReqPage {
	// 当前页
	pageNum: number;
	//每页记录数
	pageSize: number;
	// 排序字段
	orderByColumn?: string;
	// 排序的方向
	isAsc?: "asc" | "desc";
}

// * 文件上传模块
export namespace Upload {
	export interface ResFileUrl {
		fileUrl: string;
	}
	export interface ResFileList {
		id: string;
		fileName: string;
		fileUrl: string;
		uploadTime: string;
		operator: string;
		fileType: string;
		fileSize: number;
		fileOldname: string;
	}
}

枚举配置

import { ResultEnum } from "@/enums/httpEnum";

// * 请求枚举配置
/**
 * @description:请求配置
 */
export enum ResultEnum {
	SUCCESS = 200,
	ERROR = 500,
	OVERDUE = 401,
	TIMEOUT = 10000,
	TYPE = "success"
}

/**
 * @description:请求方法
 */
export enum RequestEnum {
	GET = "GET",
	POST = "POST",
	PATCH = "PATCH",
	PUT = "PUT",
	DELETE = "DELETE"
}

/**
 * @description:常用的contentTyp类型
 */
export enum ContentTypeEnum {
	// json
	JSON = "application/json;charset=UTF-8",
	// text
	TEXT = "text/plain;charset=UTF-8",
	// form-data 一般配合qs
	FORM_URLENCODED = "application/x-www-form-urlencoded;charset=UTF-8",
	// form-data 上传
	FORM_DATA = "multipart/form-data;charset=UTF-8"
}

状态检查
import { checkStatus } from "./helper/checkStatus";

import { ElMessage } from "element-plus";

/**
 * @description: 校验网络请求状态码
 * @param {Number} status
 * @return void
 */
export const checkStatus = (status: number): void => {
	switch (status) {
		case 400:
			ElMessage.error("请求失败!请您稍后重试");
			break;
		case 401:
			ElMessage.error("登录失效!请您重新登录");
			break;
		case 403:
			ElMessage.error("当前账号无权限访问!");
			break;
		case 404:
			ElMessage.error("你所访问的资源不存在!");
			break;
		case 405:
			ElMessage.error("请求方式错误!请您稍后重试");
			break;
		case 408:
			ElMessage.error("请求超时!请您稍后重试");
			break;
		case 500:
			ElMessage.error("服务异常!");
			break;
		case 502:
			ElMessage.error("网关错误!");
			break;
		case 503:
			ElMessage.error("服务不可用!");
			break;
		case 504:
			ElMessage.error("网关超时!");
			break;
		default:
			ElMessage.error("请求失败!");
	}
};

import { GlobalStore } from "@/stores";

import { defineStore, createPinia } from "pinia";
import { GlobalState, ThemeConfigProps, AssemblySizeType } from "./interface";
import { DEFAULT_PRIMARY } from "@/config/config";
import piniaPersistConfig from "@/config/piniaPersist";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";

// defineStore 调用后返回一个函数,调用该函数获得 Store 实体
export const GlobalStore = defineStore({
	// id: 必须的,在所有 Store 中唯一
	id: "GlobalState",
	// state: 返回对象的函数
	state: (): GlobalState => ({
		// token
		token: "",
		// 用户信息
		userInfo: "",
		// webSocket 对象
		webSocket: null,
		// element组件大小
		assemblySize: "default",
		// language
		language: "",
		// isAdmin
		isAdmin: false,
		// themeConfig
		themeConfig: {
			// 当前页面是否全屏
			maximize: false,
			// 布局切换 ==>  纵向:vertical | 经典:classic | 横向:transverse | 分栏:columns
			layout: "vertical",
			// 默认 primary 主题颜色
			primary: DEFAULT_PRIMARY,
			// 深色模式
			isDark: false,
			// 灰色模式
			isGrey: false,
			// 色弱模式
			isWeak: false,
			// 折叠菜单
			isCollapse: false,
			// 面包屑导航
			breadcrumb: true,
			// 面包屑导航图标
			breadcrumbIcon: true,
			// 标签页
			tabs: true,
			// 标签页图标
			tabsIcon: true,
			// 页脚
			footer: true
		}
	}),
	getters: {},
	actions: {
		// setToken
		setToken(token: string) {
			this.token = token;
		},
		// setUserInfo
		setUserInfo(userInfo: any) {
			this.userInfo = userInfo;
		},
		// setWebSocket
		setWebSocket(webSocket: WebSocket | null) {
			this.webSocket = webSocket;
		},
		// setAssemblySizeSize
		setAssemblySizeSize(assemblySize: AssemblySizeType) {
			this.assemblySize = assemblySize;
		},
		// updateLanguage
		updateLanguage(language: string) {
			this.language = language;
		},
		// setThemeConfig
		setThemeConfig(themeConfig: ThemeConfigProps) {
			this.themeConfig = themeConfig;
		},
		setIsAdmin(isAdmin: boolean) {
			this.isAdmin = isAdmin;
		}
	},
	persist: piniaPersistConfig("GlobalState")
});

// piniaPersist(持久化)
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);

export default pinia;

import { GlobalState, ThemeConfigProps, AssemblySizeType } from "./interface";

/* GlobalState */
export interface GlobalState {
	token: string;
	userInfo: any;
	webSocket: WebSocket | null;
	assemblySize: AssemblySizeType;
	language: string;
	themeConfig: ThemeConfigProps;
	isAdmin: boolean;
}

/* themeConfigProp */
export interface ThemeConfigProps {
	maximize: boolean;
	layout: LayoutType;
	primary: string;
	isDark: boolean;
	isGrey: boolean;
	isCollapse: boolean;
	isWeak: boolean;
	breadcrumb: boolean;
	breadcrumbIcon: boolean;
	tabs: boolean;
	tabsIcon: boolean;
	footer: boolean;
}

export type AssemblySizeType = "default" | "small" | "large";

export type LayoutType = "vertical" | "classic" | "transverse" | "columns";

/* tabsMenuProps */
export interface TabsMenuProps {
	icon: string;
	title: string;
	path: string;
	name: string;
	close: boolean;
}

/* TabsState */
export interface TabsState {
	tabsMenuList: TabsMenuProps[];
}

/* AuthState */
export interface AuthState {
	routeName: string;
	authButtonList: {
		[key: string]: string[];
	};
	authMenuList: Menu.MenuOptions[];
}

/* keepAliveState */
export interface keepAliveState {
	keepAliveName: string[];
}

import { DEFAULT_PRIMARY } from "@/config/config";

// ? 全局不动配置项 只做导出不做修改

// * 首页地址(默认)
export const HOME_URL: string = "/home";

// * 登录页地址(默认)
export const LOGIN_URL: string = "/login";

// * 默认主题颜色
export const DEFAULT_PRIMARY: string = "#009688";

// * 路由白名单地址(必须是本地存在的路由 staticRouter.ts)
export const ROUTER_WHITE_LIST: string[] = ["/500"];

// * 高德地图 key
export const AMAP_MAP_KEY: string = "";

// * 百度地图 key
export const BAIDU_MAP_KEY: string = "";

import piniaPersistConfig from "@/config/piniaPersist";

import { PersistedStateOptions } from "pinia-plugin-persistedstate";
/**
 * @description pinia持久化参数配置
 * @param {String} key 存储到持久化的 name
 * @param {Array} paths 需要持久化的 state name
 * @return persist
 * */
const piniaPersistConfig = (key: string, paths?: string[]) => {
	const persist: PersistedStateOptions = {
		key,
		// storage: localStorage,
		storage: sessionStorage,
		paths
	};
	return persist;
};

export default piniaPersistConfig;
相关推荐
m0_7482309413 分钟前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端·rust·excel
qq_5895681021 分钟前
Echarts的高级使用,动画,交互api
前端·javascript·echarts
j喬乔22 分钟前
Node导入不了命名函数?记一次Bug的探索
typescript·node.js
黑客老陈1 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss
正小安1 小时前
Vite系列课程 | 11. Vite 配置文件中 CSS 配置(Modules 模块化篇)
前端·vite
暴富的Tdy2 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js
neeef_se2 小时前
Vue中使用a标签下载静态资源文件(比如excel、pdf等),纯前端操作
前端·vue.js·excel
m0_748235612 小时前
web 渗透学习指南——初学者防入狱篇
前端
℘团子এ2 小时前
js和html中,将Excel文件渲染在页面上
javascript·html·excel