Vue3 + TS + Antd + Pinia 从零搭建后台系统(三) Pinia + 全局loading + 公共方法

书接上回

本篇主要介绍: Pinia + 全局loading + 公共方法

继续填充

Pinia

想了解Pinia的可看该博主的文章,挺详细的。此篇不做讲解,只讲实际应用
链接: Pinia基础知识

index.ts

import type { App } from "vue";
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";

const store = createPinia() as any;

// 创建Store时,将persistent选项设置为true,则整个Store将使用默认持久化配置保存
store.use(piniaPluginPersistedstate);

export const setupStore = (app: App<Element>) => {
  app.use(store);
};

export { store };

user.ts 填前文中的坑,补充全局方法:token及login的全局变量及方法

import { defineStore } from "pinia";
import { store } from "./index";
import { UserLoginType, UserType } from "../api/login/types";
import type { RouteRecordRaw } from "vue-router";
import { Modal } from "ant-design-vue";
import { loginOutLocalApi } from "@/api/login";
import router from "@/router";

interface UserState {
  userInfo?: UserType;
  tokenKey: string;
  token: string;
  roleRouters?: string[] | AppCustomRouteRecordRaw[];
  rememberMe: boolean;
  loginInfo?: UserLoginType;
}

interface RouteMetaCustom extends Record<string | number | symbol, unknown> {
  hidden?: boolean;
  alwaysShow?: boolean;
  title?: string;
  icon?: string;
  noCache?: boolean;
  breadcrumb?: boolean;
  affix?: boolean;
  activeMenu?: string;
  noTagsView?: boolean;
  canTo?: boolean;
  permission?: string[];
}
interface AppCustomRouteRecordRaw extends Omit<RouteRecordRaw, "meta" | "component" | "children"> {
  name: string;
  meta: RouteMetaCustom;
  component: string;
  path: string;
  redirect: string;
  children?: AppCustomRouteRecordRaw[];
}
export const useUserStore = defineStore("user", {
  state: (): UserState => {
    return {
      userInfo: undefined,
      tokenKey: "Authorization",
      token: "",
      roleRouters: undefined,
      // 记住我
      rememberMe: true,
      loginInfo: undefined,
    };
  },
  getters: {
    getTokenKey(): string {
      return this.tokenKey;
    },
    getToken(): string {
      return this.token;
    },
    getUserInfo(): UserType | undefined {
      return this.userInfo;
    },
    getRoleRouters(): string[] | AppCustomRouteRecordRaw[] | undefined {
      return this.roleRouters;
    },
    getRememberMe(): boolean {
      console.log(this.rememberMe);

      return this.rememberMe;
    },
    getLoginInfo(): UserLoginType | undefined {
      return this.loginInfo;
    },
  },
  actions: {
    setTokenKey(tokenKey: string) {
      this.tokenKey = tokenKey;
    },
    setToken(token: string) {
      this.token = token;
    },
    setUserInfo(userInfo?: UserType) {
      this.userInfo = userInfo;
    },
    setRoleRouters(roleRouters: string[] | AppCustomRouteRecordRaw[]) {
      this.roleRouters = roleRouters;
    },
    logoutConfirm() {
      Modal.confirm({
        title: "提示",
        content: "是否退出本系统?",
        okText: "确定",
        cancelText: "取消",
        onOk: () => {
          const res = loginOutLocalApi();
          if (res) {
            this.reset();
          }
        },
      });
    },
    reset() {
      this.setToken("");
      this.setUserInfo(undefined);
      this.setRoleRouters([]);
      router.replace("/");
    },
    logout() {
      this.reset();
    },
    setRememberMe(rememberMe: boolean) {
      this.rememberMe = rememberMe;
    },
    setLoginInfo(loginInfo: UserLoginType | undefined) {
      this.loginInfo = loginInfo;
    },
  },
  persist: true,
});

export const useUserStoreWithOut = () => {
  return useUserStore(store);
};

此处为了接口的规范性,添加了interface。
在api文件夹中添加接口文件types.ts

export interface UserLoginType {
  username: string
  password: string
}

export interface UserType {
  username: string
  password: string
  role: string
  roleId: string
  tokenInfo: {
    token: string
  }
}

全局loading

使用antd的Spin组件封装全局loading。

在前文的axios中使用,请求开始时调用Spin.show() 结束时调用Spin.hide()
import { createApp, h } from "vue";
import { Spin } from "ant-design-vue";

let instance = null as any;

// 定义全屏遮罩样式
const style = {
  position: "fixed",
  left: 0,
  top: 0,
  width: "100%",
  height: "100%",
  display: "flex",
  justifyContent: "center",
  alignItems: "center",
  background: "rgba(255, 255, 255, 0.5)",
  zIndex: 99999,
};

function getInstance() {
  if (!instance) {
    const vn = createApp({
      data() {
        return {
          show: false,
          message: "Loading...",
          timeoutId: "loader",
        };
      },
      unmounted() {
        if (instance && instance.$el) {
          instance.$el.remove(); // 当组件卸载时,从DOM中移除元素以避免内存泄漏
        }
      },
      methods: {
        loading(val: string, timeout: number | undefined) {
          this.show = true;
          this.message = val || "Loading...";
          if (timeout) {
            this.timeoutId = setTimeout(() => {
              this.close();
            }, timeout); // 超时时间单位为毫秒
          }
        },
        close() {
          clearTimeout(this.timeoutId); // 清除定时器
          this.show = false;
        },
      },
      render() {
        return this.show ? h("div", { style }, [h(Spin, { tip: this.message })]) : null;
      },
    });
    const ele = document.createElement("div");
    instance = vn.mount(ele);
    document.body.appendChild(ele);
  }
  return instance;
}
export default {
  ...Spin,
  show(val = "Loading...", timeout = 0) {
    getInstance().loading(val, timeout);
  },
  hide() {
    getInstance().close();
  },
};

公共方法

公共方法都放在utils中,常用的如下:

/**
 * 生成随机字符串
 */
export function toAnyString() {
  const str: string = "xxxxx-xxxxx-4xxxx-yxxxx-xxxxx".replace(/[xy]/g, (c: string) => {
    const r: number = (Math.random() * 16) | 0;
    const v: number = c === "x" ? r : (r & 0x3) | 0x8;
    return v.toString();
  });
  return str;
}
/**
 * 首字母大写
 */
export function firstUpperCase(str: string) {
  return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
}
// 字符串间隔天数,yyyyMMdd-yyyyMMdd
export function intervalDays(date1: any, date2: any) {
  date1 = String(date1);
  date2 = String(date2);
  var d1 = date1.replace(/(\d{4})(\d{2})(\d{2})/gm, "$1-$2-$3");
  var d2 = date2.replace(/(\d{4})(\d{2})(\d{2})/gm, "$1-$2-$3");
  var h1 = new Date(d1);
  var h2 = new Date(d2);
  var days = h1.getTime() - h2.getTime();
  var time = parseInt(days / (1000 * 60 * 60 * 24) + "");
  return time;
}

// 传入Date 输出 yyyyMMdd 字符串类型格式
export function dateToStr1(date: any) {
  if (date === null || date === "") {
    return "";
  }
  const Y = date.getFullYear() + "";
  const M = (date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1) + "";
  const D = (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + "";
  return Y + M + D;
}

// 传入Date 输出 yyyy-MM-dd 字符串类型格式
export function dateToStr2(date: any) {
  const Y = date.getFullYear() + "";
  const M = (date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1) + "";
  const D = (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + "";
  return "" + Y + "-" + M + "-" + D;
}

// 传入Date 输出 yyyyMMddhhmmss / yyyy-mm-dd hh:mm:ss 字符串类型格式
export function dateToStr3(date: any) {
  const Y = date.getFullYear() + "";
  const M = (date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1) + "";
  const D = (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + "";
  const hString = date.getHours().toString();
  const mString = date.getMinutes().toString();
  const sString = date.getSeconds().toString();
  let h = "";
  let m = "";
  let s = "";
  hString.length <= 1 ? (h = "0" + hString) : (h = hString);
  mString.length <= 1 ? (m = "0" + mString) : (m = mString);
  sString.length <= 1 ? (s = "0" + sString) : (s = sString);
  return Y + M + D + h + m + s;
}

// 传入Date 输出 yyyy-mm-dd hh:mm:ss 字符串类型格式
export function dateToStr4(date: any) {
  const Y = date.getFullYear() + "";
  const M = (date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1) + "";
  const D = (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + "";
  const hString = date.getHours().toString();
  const mString = date.getMinutes().toString();
  const sString = date.getSeconds().toString();
  let h = "";
  let m = "";
  let s = "";
  hString.length <= 1 ? (h = "0" + hString) : (h = hString);
  mString.length <= 1 ? (m = "0" + mString) : (m = mString);
  sString.length <= 1 ? (s = "0" + sString) : (s = sString);
  return Y + "-" + M + "-" + D + " " + h + ":" + m + ":" + s;
}

// 传入yyyyMMdd字符串类型格式 输出 Date
export function strToDate(str: string | number) {
  const dateStr = String(str).replace(/^(\d{4})(\d{2})(\d{2})$/, "$1-$2-$3");
  var date = new Date(dateStr.replace(/-/g, "/"));
  return date;
}

// 传入yyyyMMdd字符串类型格式 输出yyyy-MM-dd
export function strToDateStr(str: string | number) {
  const dateStr = str ? String(str).replace(/^(\d{4})(\d{2})(\d{2})$/, "$1-$2-$3") : "";
  return dateStr;
}

// 传入两个yyyyMMdd字符串类型格式 ,输出两个日期的月份差,str1必须大于str2
export function dateToStr5(str1: string, str2: string) {
  //先计算年份差距
  var year = 0;
  var month = 0;
  //const year = parseInt(str1.substring(4, 6)) - parseInt(str2.substring(4, 6));
  if (parseInt(str1.substring(4, 6)) < parseInt(str2.substring(4, 6))) {
    year = parseInt(str1.substring(0, 4)) - 1 - parseInt(str2.substring(0, 4));
    month = parseInt(str1.substring(4, 6)) + 12 - parseInt(str2.substring(4, 6));
    console.log("1");
  } else {
    year = parseInt(str1.substring(0, 4)) - parseInt(str2.substring(0, 4));
    month = parseInt(str1.substring(4, 6)) - parseInt(str2.substring(4, 6));
  }
  var day = year * 12 + month;
  return day;
}

// 传入yyyyMMdd字符串类型格式 输出 yyyy-MM-dd
export function strToStr(value: string) {
  let date = "";
  if (!value) {
    return "";
  }
  if (value.length == 8) {
    const year = value.substring(0, 4);
    const month = value.substring(4, 6);
    const day = value.substring(6, 8);
    date = year + "-" + month + "-" + day;
  } else if (value.length == 6) {
    const year = value.substring(0, 4);
    const month = value.substring(4, 6);
    date = year + "-" + month;
  } else if (value.length == 4) {
    const year = value.substring(0, 4);
    date = year;
  }
  return date;
}

// n 天之前或之后的日期 输出Date类型(days可以正负)
export function getDaysAfterDate(date: Date, days: number) {
  return new Date(date.getTime() - days * 24 * 60 * 60 * 1000);
}

// 输出当前 yyyy_MM_ddhhmmss 字符串类型格式 适用导出文件名
export function dateFileName() {
  const index = new Date()
    .toLocaleString("chinese", { hour12: false })
    .toString()
    .replace(new RegExp(":| ", "g"), "");
  return index;
}

// 输入数字转成日期(5位,excel表格日期),默认转换成YYYY-MM-DD
export function numberToDate(number: number, format: string) {
  if (number != undefined) {
    let date = new Date((number - 1) * 24 * 3600000 + 1) as any;
    date.setYear(date.getFullYear() - 70);
    const Y = date.getFullYear();
    const dateRel =
      (Y % 4 === 0 && Y % 100 !== 0) || Y % 400 == 0
        ? new Date((number - 2) * 24 * 3600000 + 1)
        : (new Date((number - 1) * 24 * 3600000 + 1) as any);
    dateRel.setYear(date.getFullYear() - 70);
    const M =
      (dateRel.getMonth() + 1 < 10 ? "0" + (dateRel.getMonth() + 1) : dateRel.getMonth() + 1) + "";
    const D = (dateRel.getDate() < 10 ? "0" + dateRel.getDate() : dateRel.getDate()) + "";
    if (format && format.length == 1) {
      return Y + format + M + format + D;
    } else {
      return Y + "-" + M + "-" + D;
    }
  } else {
    return undefined;
  }
}

// 判断值是否是数字
export function isRealNum(val: any) {
  if (typeof val !== "number") {
    return false;
  }
  if (!isNaN(val)) {
    return true;
  } else {
    return false;
  }
}

// 判断字符是否包含数字
export function regNumber(str: string) {
  var reg = /\d+/;
  if (reg.test(str)) {
    return true;
  } else {
    return false;
  }
}

// 判断一个字符串中是否包含字母
export function isLetter(str: any) {
  for (var i in str) {
    var asc = str.charCodeAt(i);
    if ((asc >= 65 && asc <= 90) || (asc >= 97 && asc <= 122)) {
      return true;
    }
  }
  return false;
}

// 判断一个字符串中是否包含汉字
export function isChina(str: string) {
  if (/.*[\u4e00-\u9fa5]+.*$/.test(str)) {
    return true;
  }
  return false;
}

//判断是否包含特殊字符
export function isSpecialCharacter(str: string) {
  if (str === "" || str === null) {
    return false;
  }
  if (
    str.indexOf("<") > -1 ||
    str.indexOf(">") > -1 ||
    str.indexOf("&") > -1 ||
    str.indexOf("'") > -1 ||
    str.indexOf('"') > -1 ||
    str.indexOf("\\") > -1 ||
    str.indexOf("/") > -1 ||
    str.indexOf(":") > -1 ||
    str.indexOf("*") > -1 ||
    str.indexOf("?") > -1 ||
    str.indexOf("|") > -1
  ) {
    return true;
  }
  return false;
}

本篇结束,今日份的结束。后面文章会更新:Layout布局,导航栏,标签页。

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue3中使用TypeScript结合Pinia来实现全局loading的方式如下: 首先,我们需要安装vue-router、pinia和axios,可以使用以下命令进行安装: ``` npm install vue-router@next pinia axios ``` 接下来我们创建一个store模块来管理全局loading状态。在src/store目录下创建一个名为loading.ts的文件,代码如下: ```typescript import { store } from 'pinia'; export const useLoadingStore = store('loading'); export const loadingStore = useLoadingStore({ state: () => ({ isLoading: false, }), actions: { setLoading(loading: boolean) { this.isLoading = loading; }, }, }); ``` 然后在src/main.ts文件中注册pinia和创建一个全局loading插件,代码如下: ```typescript import { createApp } from 'vue'; import { createPinia } from 'pinia'; import App from './App.vue'; import router from './router'; import axios from 'axios'; import { loadingStore } from './store/loading'; const app = createApp(App); const pinia = createPinia(); app.use(pinia); // 创建全局loading插件 app.config.globalProperties.$loading = { show() { loadingStore.setLoading(true); }, hide() { loadingStore.setLoading(false); }, }; // axios拦截器 axios.interceptors.request.use(function (config) { loadingStore.setLoading(true); return config; }, function (error) { return Promise.reject(error); }); axios.interceptors.response.use(function (response) { loadingStore.setLoading(false); return response; }, function (error) { loadingStore.setLoading(false); return Promise.reject(error); }); app.use(router); app.mount('#app'); ``` 最后,在需要使用loading的地方,可以通过以下方式来调用全局loading状态: ```typescript import { defineComponent } from 'vue'; import { loadingStore } from './store/loading'; export default defineComponent({ methods: { fetchData() { loadingStore.setLoading(true); // 发起异步请求 // ... loadingStore.setLoading(false); }, }, }); ``` 以上就是使用Vue3和TypeScript结合Pinia实现全局loading方法。我们首先在store模块中定义了一个loading状态,并提供了相应的方法来控制loading的显示和隐藏。然后在main.ts中创建了全局loading插件,并通过axios的拦截器来控制loading的显示和隐藏。最后,在需要使用loading的地方调用相应的方法即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值