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;
相关推荐
计算机魔术师1 小时前
管谷歌的不是Pichai一个人,是一整层AI智能体
前端
米米商聊1 小时前
图标能点,按钮就可用了吗?键盘、名称与收藏状态
前端·javascript
Younglina2 小时前
三个晚上,我和 WorkBuddy 做了个生图网站
前端·后端·ai编程
xy34532 小时前
Axure 9.0 内联框架加载方式
前端·ui·html·axure·原型·产品设计
技术小甜甜3 小时前
【Cursor】Skill 被标黄色警告,怎么改成合格的 SKILL.md
开发语言·javascript·ai编程·cursor·1024程序员节·ai agent·skills
陈随易3 小时前
副业搞钱点子之收入查看器
前端·后端
Profile排查笔记3 小时前
浏览器指纹逆向怎么分析:从网络请求追踪到 JavaScript 采集链
前端·人工智能·后端·自动化
hudie7654 小时前
LikeShop 二开最常改的 5 个文件(附改前改后对比)
前端·uni-app·php·个人开发
qetfw4 小时前
Bash 函数与流程控制:read、if、for、case 验证
linux·前端·javascript·bash
鬼手点金5 小时前
从 API 消息到模型 Token:Chat Template
java·前端·javascript·数据库·人工智能·深度学习·1024程序员节