init: UserBaseFront (frontend)
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
import { createAlova, Method, StatesHook } from "alova";
|
||||
import fetchAdapter from "alova/fetch";
|
||||
import VueHook from "alova/vue";
|
||||
import { replaceOriginUrl } from "../bschSsoRedirect";
|
||||
import config from "@/assets/config/host.config";
|
||||
import Utils from "..";
|
||||
/* ============= 类型定义 ============= */
|
||||
export interface RequestHooks {
|
||||
/** 前置票据/通用头注入 */
|
||||
injectHeaders?: (method: Method) => void;
|
||||
/** 加载开始 */
|
||||
onLoadingStart?: () => void;
|
||||
/** 加载结束 */
|
||||
onLoadingEnd?: () => void;
|
||||
/** 业务错误提示(code !== 0)*/
|
||||
onBizError?: (msg: string) => void;
|
||||
/** http 错误提示(status !== 200)*/
|
||||
onHttpError?: (msg: string) => void;
|
||||
}
|
||||
|
||||
/** 和后端约定的通用响应格式 */
|
||||
export interface BschResponse<T = any> {
|
||||
code: number;
|
||||
msg?: string;
|
||||
data: T;
|
||||
urlToRedirectTo?: string;
|
||||
}
|
||||
|
||||
/* ============= 默认空实现 ============= */
|
||||
const defHooks: Required<RequestHooks> = {
|
||||
injectHeaders: () => {},
|
||||
onLoadingStart: () => {},
|
||||
onLoadingEnd: () => {},
|
||||
onBizError: () => {},
|
||||
onHttpError: () => {},
|
||||
};
|
||||
|
||||
/* ============= 错误处理(原 axios 逻辑平移) ============= */
|
||||
function errorHandle<T>(res: BschResponse<T>, status: number): T {
|
||||
// http 层错误
|
||||
if (status !== 200) {
|
||||
let msg = "网络错误";
|
||||
switch (status) {
|
||||
case 401:
|
||||
case 403:
|
||||
break;
|
||||
case 404:
|
||||
msg = "请求的资源不存在!";
|
||||
break;
|
||||
default:
|
||||
msg = res?.msg || "内部服务错误!";
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
// 业务层错误
|
||||
switch (res.code) {
|
||||
case 0:
|
||||
return res.data ?? (null as T);
|
||||
case 302:
|
||||
console.log(res);
|
||||
window.location.href = res.urlToRedirectTo!;
|
||||
// window.location.href = replaceOriginUrl(res.urlToRedirectTo!, config.serviceHost);
|
||||
throw new Error("redirect");
|
||||
case 4:
|
||||
throw new Error(res.msg || "参数错误!");
|
||||
default:
|
||||
throw new Error(res.msg || "业务错误");
|
||||
}
|
||||
}
|
||||
|
||||
/* ============= 工厂函数 ============= */
|
||||
export function createBschAlova(baseURL: string, hooks: RequestHooks = {}, statesHook: StatesHook<any> = VueHook) {
|
||||
const { injectHeaders, onLoadingStart, onLoadingEnd, onBizError, onHttpError } = {
|
||||
...defHooks,
|
||||
...hooks,
|
||||
};
|
||||
|
||||
let reqCount = 0;
|
||||
let timer: ReturnType<typeof setTimeout>;
|
||||
|
||||
/* ---- 请求拦截 ---- */
|
||||
function beforeRequest(method: Method) {
|
||||
reqCount++;
|
||||
if (reqCount === 1) timer = setTimeout(() => onLoadingStart(), 800);
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
if (reqCount === 0) {
|
||||
clearTimeout(timer);
|
||||
onLoadingEnd();
|
||||
}
|
||||
});
|
||||
// 1. 用户注入票据/角色等头
|
||||
injectHeaders(method);
|
||||
|
||||
method.config.credentials = "include";
|
||||
// 2. 通用头
|
||||
method.config.headers = Object.assign(method.config.headers || {}, {
|
||||
yschapirequest: "1",
|
||||
timeout: 20000,
|
||||
yschreferer: Utils.strToHexCharCode(window.location.href),
|
||||
});
|
||||
}
|
||||
|
||||
/* ---- 响应拦截 ---- */
|
||||
async function onSuccess(response: Response) {
|
||||
reqCount--;
|
||||
if (reqCount === 0) {
|
||||
clearTimeout(timer);
|
||||
onLoadingEnd();
|
||||
}
|
||||
const res: BschResponse = await response.json();
|
||||
try {
|
||||
return errorHandle(res, response.status);
|
||||
} catch (e: any) {
|
||||
console.log(e);
|
||||
|
||||
onBizError(e.message);
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function onError(err: any) {
|
||||
reqCount = 0;
|
||||
clearTimeout(timer);
|
||||
onLoadingEnd();
|
||||
|
||||
const msg = err?.message || "网络请求出错";
|
||||
onHttpError(msg);
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
||||
async function onComplete(method: Method) {
|
||||
if (method.fromCache) {
|
||||
reqCount--;
|
||||
if (reqCount === 0) {
|
||||
clearTimeout(timer);
|
||||
onLoadingEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return createAlova({
|
||||
statesHook,
|
||||
requestAdapter: fetchAdapter(),
|
||||
baseURL,
|
||||
cacheFor: null,
|
||||
beforeRequest,
|
||||
responded: { onSuccess, onError, onComplete },
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// @/utils/alova-iview.ts
|
||||
import { createBschAlova } from "./alova-core";
|
||||
import { Message } from "view-ui-plus";
|
||||
import conf from "@/assets/config/host.config";
|
||||
|
||||
/* 全局 loading 遮罩实例 */
|
||||
let spinInstance: any = null;
|
||||
|
||||
/* 主动关闭 loading */
|
||||
function closeLoading() {
|
||||
if (spinInstance) {
|
||||
Message.destroy();
|
||||
spinInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
const baseUrl = conf.serviceHost;
|
||||
|
||||
/* 创建带 View Design UI 的 Alova 实例 */
|
||||
export const alovaInstance = createBschAlova(baseUrl, {
|
||||
injectHeaders(method) {
|
||||
// currentRole: encodeURI(JSON.stringify({ userId, roleId, schId: schoolId })),
|
||||
if (localStorage.getItem("currentRole") != null) {
|
||||
method.config.headers["currentRole"] = localStorage.getItem("currentRole") as string;
|
||||
}
|
||||
},
|
||||
|
||||
/* 2. 加载动画 – View Design Spin */
|
||||
onLoadingStart() {
|
||||
spinInstance = Message.loading("加载中...");
|
||||
},
|
||||
onLoadingEnd: closeLoading,
|
||||
onBizError(msg: string) {
|
||||
if (!!msg) Message.error(msg);
|
||||
},
|
||||
|
||||
onHttpError(msg: string) {
|
||||
Message.error({ title: "网络错误", content: msg });
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
// @/utils/alova-vant.ts
|
||||
import { createBschAlova } from "./alova-core";
|
||||
import { Toast, Dialog, showFailToast, showDialog, showLoadingToast, closeToast } from "vant";
|
||||
import conf from "@/assets/config/host.config";
|
||||
|
||||
import { useBschTicketStore } from "@/store/TicketStore";
|
||||
import { WappUserHeader } from "@/types/Basics";
|
||||
|
||||
let toastLoading: ReturnType<typeof Toast.loading> | null = null;
|
||||
|
||||
/* 主动关闭 loading */
|
||||
function closeLoading() {
|
||||
if (toastLoading) {
|
||||
toastLoading.close();
|
||||
toastLoading = null;
|
||||
} else {
|
||||
closeToast();
|
||||
}
|
||||
}
|
||||
|
||||
// const baseUrl = conf.serviceHost + "/api";
|
||||
const baseUrl = conf.serviceHost + "/mobiapi";
|
||||
/* 创建带 Vant UI 的 Alova 实例 */
|
||||
export const alovaInstance = createBschAlova(baseUrl, {
|
||||
injectHeaders(method) {
|
||||
const store = useBschTicketStore();
|
||||
const authoHeaders = store.getAuthoHeaders;
|
||||
const userHeaders = store.getTicket;
|
||||
|
||||
const bschTicket: WappUserHeader = store.getTicket;
|
||||
|
||||
for (const key in authoHeaders) method.config.headers[key] = authoHeaders[key];
|
||||
for (const key in bschTicket) method.config.headers[key] = (bschTicket as any)[key];
|
||||
|
||||
// method.config.headers.currentRole = encodeURIComponent(
|
||||
// JSON.stringify({ roleId: userHeaders.roleId, childId: userHeaders.userId, schId: userHeaders.schoolId }),
|
||||
// );
|
||||
},
|
||||
/* 2. 加载动画 – Vant Toast */
|
||||
onLoadingStart() {
|
||||
toastLoading = showLoadingToast({
|
||||
message: "加载中...",
|
||||
forbidClick: true,
|
||||
duration: 0,
|
||||
});
|
||||
},
|
||||
onLoadingEnd: closeLoading,
|
||||
onBizError(msg) {
|
||||
showFailToast(msg);
|
||||
},
|
||||
onHttpError(msg) {
|
||||
showDialog({ title: "网络错误", message: msg });
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { BschResponse, AxiosResponse } from "axios";
|
||||
import axios from "axios";
|
||||
import { initAxiosInstance } from "./interceptor";
|
||||
|
||||
import { baseConfig } from "@/assets/config/api.config";
|
||||
|
||||
export interface BschResult {
|
||||
url: string;
|
||||
params?: object;
|
||||
data?: object;
|
||||
isMsg?: boolean; //是否自动显示错误提示
|
||||
}
|
||||
|
||||
class Abstract {
|
||||
private apiAxios<T>({ method, url, params, data, isMsg = true }: any): Promise<T> {
|
||||
const instance = initAxiosInstance(baseConfig);
|
||||
return new Promise((resolve, reject) => {
|
||||
instance({
|
||||
method,
|
||||
url,
|
||||
params,
|
||||
data,
|
||||
})
|
||||
.then((res: AxiosResponse<BschResponse, number>) => {
|
||||
// console.log(res);
|
||||
let { data } = res;
|
||||
resolve(data as T);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
reject(err);
|
||||
// throw err;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*GET类型的网络请求
|
||||
*
|
||||
* @protected
|
||||
* @template T
|
||||
* @param {BschResult} { url, params }
|
||||
* @returns {Promise<T>}
|
||||
* @memberof Abstract
|
||||
*/
|
||||
protected getRequest<T>({ url, params, isMsg = true }: BschResult): Promise<T> {
|
||||
return this.apiAxios<T>({ method: "GET", url, params, isMsg });
|
||||
}
|
||||
|
||||
/**
|
||||
*POST类型的网络请求
|
||||
*
|
||||
* @protected
|
||||
* @template T
|
||||
* @param {BschResult} { url, data }
|
||||
* @returns {Promise<T>}
|
||||
* @memberof Abstract
|
||||
*/
|
||||
protected postRequest<T>({ url, params, data, isMsg = true }: BschResult): Promise<T> {
|
||||
return this.apiAxios<T>({ method: "POST", url, params, data, isMsg });
|
||||
}
|
||||
|
||||
/**
|
||||
*PUT类型的网络请求
|
||||
*
|
||||
* @protected
|
||||
* @template T
|
||||
* @param {BschResult} { url, data }
|
||||
* @returns {Promise<T>}
|
||||
* @memberof Abstract
|
||||
*/
|
||||
protected putRequest<T>({ url, params, data, isMsg = true }: BschResult): Promise<T> {
|
||||
return this.apiAxios<T>({ method: "PUT", url, params, data, isMsg });
|
||||
}
|
||||
|
||||
/**
|
||||
*DELETE类型的网络请求
|
||||
*
|
||||
* @protected
|
||||
* @template T
|
||||
* @param {BschResult} { url, params }
|
||||
* @returns {Promise<T>}
|
||||
* @memberof Abstract
|
||||
*/
|
||||
protected deleteRequest<T>({ url, params, data, isMsg = true }: BschResult): Promise<T> {
|
||||
return this.apiAxios<T>({ method: "DELETE", url, params, data, isMsg });
|
||||
}
|
||||
}
|
||||
|
||||
export default Abstract;
|
||||
@@ -0,0 +1,61 @@
|
||||
import { AxiosResponse, BschResponse } from "axios";
|
||||
|
||||
function formrHandle(response: AxiosResponse) {
|
||||
let errorMsg: string = "";
|
||||
let { data, status } = response;
|
||||
if (status == 200) {
|
||||
switch (data.code) {
|
||||
case 0:
|
||||
return data;
|
||||
case 4:
|
||||
errorMsg = data.msg;
|
||||
break;
|
||||
case 302:
|
||||
errorMsg = "redirect";
|
||||
window.location.href = data.urlToRedirectTo;
|
||||
break;
|
||||
// errorMsg = "redirect";
|
||||
default:
|
||||
errorMsg = data.msg;
|
||||
}
|
||||
} else {
|
||||
switch (status) {
|
||||
case 401:
|
||||
break;
|
||||
case 403:
|
||||
break;
|
||||
case 404:
|
||||
errorMsg = "请求的资源不存在!";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!errorMsg) errorMsg = typeof data.code == "undefined" ? "内部服务错误!" : data.msg;
|
||||
}
|
||||
throw errorMsg;
|
||||
}
|
||||
|
||||
function formrHandle2(response: AxiosResponse<BschResponse, number>) {
|
||||
let errorMsg: string = "";
|
||||
let { data, status } = response;
|
||||
if (status == 200) {
|
||||
if (!!data.State) return response;
|
||||
else errorMsg = data.Message || JSON.stringify(data.Data);
|
||||
} else {
|
||||
switch (status) {
|
||||
case 401:
|
||||
errorMsg = "您的登录已过期!";
|
||||
break;
|
||||
case 403:
|
||||
break;
|
||||
case 404:
|
||||
errorMsg = "请求的资源不存在!";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!errorMsg) errorMsg = typeof data.State == "undefined" ? "内部服务错误!" : data.Message;
|
||||
}
|
||||
throw errorMsg;
|
||||
}
|
||||
|
||||
export default formrHandle;
|
||||
@@ -0,0 +1,39 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosRequestHeaders } from "axios";
|
||||
import formrHandle from "./format-handle";
|
||||
import { debugUser } from "@/assets/config/api.config";
|
||||
import conf from "@/assets/config/host.config";
|
||||
|
||||
export const initAxiosInstance: (config: AxiosRequestConfig) => AxiosInstance = config => {
|
||||
let axiosInstance: AxiosInstance = axios.create(config);
|
||||
|
||||
axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
|
||||
// if (config.params && typeof config.params != "string") config.params = utils.filterNull(config.params);
|
||||
if (localStorage.getItem("currentRole") != null) {
|
||||
(config.headers as AxiosRequestHeaders)["currentRole"] = localStorage.getItem("currentRole") as string;
|
||||
}
|
||||
//@ts-ignore
|
||||
const bschUtil = BschLoadUtil.getInstance();
|
||||
if (!bschUtil.getIsSelf()) {
|
||||
(config.headers as AxiosRequestHeaders)["ticket"] = sessionStorage.getItem("bschTicket") as string;
|
||||
(config.headers as AxiosRequestHeaders)["platform"] = "grzx";
|
||||
}
|
||||
if (!!conf.isDebug) {
|
||||
//如果是开发环境
|
||||
for (const key in debugUser) (config.headers as AxiosRequestHeaders)[key] = (debugUser as any)[key];
|
||||
// (config.headers as AxiosRequestHeaders)["yschapirequest"] = "1";
|
||||
}
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
axiosInstance.interceptors.response.use(
|
||||
response => {
|
||||
return formrHandle(response);
|
||||
},
|
||||
error => {
|
||||
return formrHandle(error.response);
|
||||
},
|
||||
);
|
||||
|
||||
return axiosInstance;
|
||||
};
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
import * as axios from "axios";
|
||||
|
||||
declare module "axios" {
|
||||
// 定制业务相关的网络请求响应格式, T 是具体的接口返回类型数据
|
||||
// export interface BschResponse<T> {
|
||||
// code: number;
|
||||
// msg?: string;
|
||||
// data?: T;
|
||||
// [keys: string]: any; //任意属性
|
||||
// }
|
||||
|
||||
export interface BschResponse {
|
||||
State: boolean;
|
||||
Data: any;
|
||||
Message: string;
|
||||
OtherData: any;
|
||||
Page: any;
|
||||
[keys: string]: any; //任意属性
|
||||
}
|
||||
|
||||
export interface BschResult {
|
||||
url: string;
|
||||
params?: object;
|
||||
data?: object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import Utils from "./index";
|
||||
|
||||
export function replaceOriginUrl(urlToRedirectTo: string, baseRedirectHost: string) {
|
||||
var newUrl = Utils.strToHexCharCode(window.location.href);
|
||||
// newUrl = hexHrefToString("https://base.bestsch.com/BschHome/base/index.html#/lightapp?id=1&name=2");
|
||||
// newUrl = Utils.strToHexCharCode("https://base.bestsch.com/BschHome/base/index.html#/lightapp?id=1&name=2");
|
||||
var ssoUrlObj = new URL(urlToRedirectTo);
|
||||
var paramsStr = ssoUrlObj.searchParams.get("service") || "";
|
||||
let ssoServiceUrl = decodeURI(paramsStr).split(";")[0];
|
||||
ssoServiceUrl += ";redirectToReferer=" + baseRedirectHost + "/api/base/RedirectTo/" + newUrl;
|
||||
ssoUrlObj.searchParams.set("service", encodeURI(ssoServiceUrl));
|
||||
return ssoUrlObj.href;
|
||||
}
|
||||
|
||||
export function hexStringToHref(urlHexStr: string, paramsKey: string = "hex") {
|
||||
let urlObj = new URL(urlHexStr);
|
||||
let hexStr = urlObj.searchParams.get(paramsKey) || "";
|
||||
urlObj.hash = Utils.hexToString(hexStr);
|
||||
urlObj.searchParams.delete(paramsKey);
|
||||
return urlObj.href;
|
||||
}
|
||||
|
||||
export function hexHrefToString(hrefStr: string, paramsKey: string = "hex") {
|
||||
var urlObj = new URL(hrefStr);
|
||||
var hasHexStr = Utils.strToHexCharCode(urlObj.hash);
|
||||
const newUrlObj = new URL(urlObj.origin);
|
||||
newUrlObj.pathname = urlObj.pathname;
|
||||
// newUrlObj.searchParams.set(paramsKey, hasHexStr);
|
||||
return newUrlObj.href + hasHexStr;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { App } from "vue";
|
||||
|
||||
import realImg from "./real-img";
|
||||
|
||||
// 自动导入当前文件夹下的所有自定义指令(默认导出项)
|
||||
export default (app: App<Element>) => {
|
||||
realImg(app);
|
||||
};
|
||||
|
||||
// const modules = import.meta.glob("../directive/**/**.ts");
|
||||
|
||||
// for (const path in modules) {
|
||||
// // 排除当前文件
|
||||
// if (path !== "../directive/index.ts") {
|
||||
// modules[path]().then(mod => {
|
||||
// mod.default(app);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { App } from "vue";
|
||||
|
||||
export default (app: App<Element>): void => {
|
||||
app.directive("real-img", {
|
||||
async mounted(el, binding, vnode: any) {
|
||||
console.log(el);
|
||||
if (el.getAttribute("data-load") == null) el.classList.add("img-hide");
|
||||
let imgURL = binding.value; //获取图片地址
|
||||
|
||||
if (imgURL) {
|
||||
let exist = await imageIsExist(imgURL);
|
||||
if (exist) {
|
||||
el.setAttribute("src", imgURL);
|
||||
el.setAttribute("data-load", "1");
|
||||
}
|
||||
}
|
||||
el.classList.remove("img-hide");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 检测图片是否存在
|
||||
* @param url
|
||||
*/
|
||||
let imageIsExist = function (url: string) {
|
||||
return new Promise(resolve => {
|
||||
var img: HTMLImageElement | null = new Image();
|
||||
img.onload = function () {
|
||||
if ((this as any).complete == true) {
|
||||
resolve(true);
|
||||
img = null;
|
||||
}
|
||||
};
|
||||
img.onerror = function () {
|
||||
resolve(false);
|
||||
img = null;
|
||||
};
|
||||
img.src = url;
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
import { ref } from "vue";
|
||||
export function useCompRef<T extends abstract new (...args: any) => any>(_comp: T) {
|
||||
return ref<InstanceType<T>>();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { VueGlobal } from "@/types/Basics";
|
||||
import { ComponentInternalInstance, getCurrentInstance } from "vue";
|
||||
export default function useCurrentInstance(): VueGlobal {
|
||||
const { appContext } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const proxy = appContext.config.globalProperties;
|
||||
return proxy as unknown as VueGlobal;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
type PathType = "image" | "icons";
|
||||
export const requireImg = (imgPath: string, basePath?: PathType) => {
|
||||
basePath = basePath || "image";
|
||||
return new URL(`../../assets/${basePath}/${imgPath}`, import.meta.url).href;
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { ref, watch, type Ref } from "vue";
|
||||
|
||||
/**
|
||||
* 将任意响应式数据与 sessionStorage 双向绑定
|
||||
* @param key sessionStorage 的 key
|
||||
* @param def 默认值(当 sessionStorage 没有时)
|
||||
* @param reviver 可选:解析时用 JSON.parse 的第二个参数
|
||||
* @returns Ref<T>
|
||||
*/
|
||||
export function useSessionStorage<T>(
|
||||
key: string,
|
||||
def: T,
|
||||
reviver?: (this: any, key: string, value: any) => any,
|
||||
): Ref<T> {
|
||||
/* ---------- 初始化 ---------- */
|
||||
const raw = sessionStorage.getItem(key);
|
||||
let init: T = def;
|
||||
if (raw) {
|
||||
try {
|
||||
init = JSON.parse(raw, reviver);
|
||||
} catch {
|
||||
/* 解析失败回退默认值 */
|
||||
}
|
||||
}
|
||||
const state = ref<T>(init) as Ref<T>;
|
||||
|
||||
/* ---------- 变化时落盘 ---------- */
|
||||
watch(
|
||||
state,
|
||||
newVal => {
|
||||
sessionStorage.setItem(key, JSON.stringify(newVal));
|
||||
},
|
||||
{ deep: true, flush: "post" },
|
||||
);
|
||||
|
||||
return state;
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
import { IObj, UseTableSetting } from "@/types/Basics";
|
||||
import { resolveComponent } from "vue";
|
||||
export default class Utils {
|
||||
/**
|
||||
* 格式化日期
|
||||
*
|
||||
* @param {(Date | string)} 日期
|
||||
* @param {string} fmt 格式化字符串
|
||||
* @returns {string} 日期字符串
|
||||
* @memberof Utils
|
||||
*/
|
||||
public static dateFormat(date: Date | string, fmt: string): string {
|
||||
if (typeof date === "string") date = date.replace(/\-/g, "/");
|
||||
let dt = new Date(date);
|
||||
let o: IObj = {
|
||||
"M+": dt.getMonth() + 1, //月份
|
||||
"d+": dt.getDate(), //日
|
||||
"H+": dt.getHours(), //小时
|
||||
"m+": dt.getMinutes(), //分
|
||||
"s+": dt.getSeconds(), //秒
|
||||
"q+": Math.floor((dt.getMonth() + 3) / 3), //季度
|
||||
S: dt.getMilliseconds(), //毫秒
|
||||
};
|
||||
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (dt.getFullYear() + "").substr(4 - RegExp.$1.length));
|
||||
|
||||
for (let k in o)
|
||||
if (new RegExp("(" + k + ")").test(fmt))
|
||||
fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
|
||||
return fmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取地址栏指定参数
|
||||
*/
|
||||
public static getUrlParam(name: string): string | null {
|
||||
let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
|
||||
let r = window.location.search.substr(1).match(reg);
|
||||
if (r != null) return unescape(r[2]);
|
||||
else return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成uuid
|
||||
*/
|
||||
public static uuid(): string {
|
||||
return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
||||
let r = (Math.random() * 16) | 0,
|
||||
v = c == "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文件大小
|
||||
*/
|
||||
public static getFileSize(size: number): string {
|
||||
if (!size) return "";
|
||||
if (size / 1024 < 1024) {
|
||||
size = Math.ceil(size / 1024);
|
||||
return size + "KB";
|
||||
} else if (size / (1024 * 1024) < 1024) {
|
||||
size = Math.ceil((size / (1024 * 1024)) * 100) / 100;
|
||||
return size + "MB";
|
||||
} else if (size / (1024 * 1024 * 1024) < 1024) {
|
||||
size = Math.ceil((size / (1024 * 1024 * 1024)) * 100) / 100;
|
||||
return size + "GB";
|
||||
} else return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定键
|
||||
*/
|
||||
public static delKey(obj: IObj, ...args: string[]) {
|
||||
args.forEach(v => delete obj[v]);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* iview Tooltip封装
|
||||
*/
|
||||
public static operatingHandle(
|
||||
h: any,
|
||||
info: string,
|
||||
iconClass: string,
|
||||
callback: Function,
|
||||
disabled: boolean = false,
|
||||
) {
|
||||
if (disabled) iconClass += " disabled";
|
||||
|
||||
return h(
|
||||
resolveComponent("Tooltip"),
|
||||
{
|
||||
content: info,
|
||||
placement: "bottom",
|
||||
transfer: true,
|
||||
},
|
||||
{
|
||||
default: () => [
|
||||
h(
|
||||
"div",
|
||||
{
|
||||
class: `operating-btn iconfont ${iconClass}`,
|
||||
onClick: callback,
|
||||
},
|
||||
null,
|
||||
),
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public static operatingTextHandle(
|
||||
h: any,
|
||||
info: string,
|
||||
className: string,
|
||||
callback: Function,
|
||||
disabled: boolean = false,
|
||||
) {
|
||||
if (disabled) className += " disabled";
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: `operating-link ${className}`,
|
||||
onClick: disabled ? null : callback,
|
||||
},
|
||||
info,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算表格高度
|
||||
*/
|
||||
public static useTableHeight({
|
||||
isSearch = false,
|
||||
isPage = false,
|
||||
isTabs = false,
|
||||
isOperat = false,
|
||||
otherH = 0,
|
||||
} = {}) {
|
||||
let bodyH = document.body.clientHeight,
|
||||
headerH = 112,
|
||||
searchH = 52,
|
||||
operatH = 48,
|
||||
tabsH = 56,
|
||||
pageH = 72,
|
||||
paddingH = 8;
|
||||
|
||||
//@ts-ignore
|
||||
const bschUtil = BschLoadUtil.getInstance();
|
||||
if (!bschUtil.getIsSelf()) headerH = 56;
|
||||
let tableH = bodyH - headerH - paddingH;
|
||||
isOperat && (tableH -= operatH);
|
||||
isTabs && (tableH -= tabsH);
|
||||
isSearch && (tableH -= searchH);
|
||||
isPage && (tableH -= pageH);
|
||||
otherH > 0 && (tableH -= otherH);
|
||||
return tableH;
|
||||
}
|
||||
/**
|
||||
* 数字转大写汉字
|
||||
* @param num
|
||||
* @returns
|
||||
*/
|
||||
public static convertToChinaNum(num: number) {
|
||||
var arr1 = new Array("零", "一", "二", "三", "四", "五", "六", "七", "八", "九");
|
||||
var arr2 = new Array(
|
||||
"",
|
||||
"十",
|
||||
"百",
|
||||
"千",
|
||||
"万",
|
||||
"十",
|
||||
"百",
|
||||
"千",
|
||||
"亿",
|
||||
"十",
|
||||
"百",
|
||||
"千",
|
||||
"万",
|
||||
"十",
|
||||
"百",
|
||||
"千",
|
||||
"亿",
|
||||
); //可继续追加更高位转换值
|
||||
if (!num || isNaN(num)) {
|
||||
return "零";
|
||||
}
|
||||
var english = num.toString().split("");
|
||||
var result = "";
|
||||
for (var i = 0; i < english.length; i++) {
|
||||
var des_i = english.length - 1 - i; //倒序排列设值
|
||||
result = arr2[i] + result;
|
||||
var arr1_index: any = english[des_i];
|
||||
result = arr1[arr1_index] + result;
|
||||
}
|
||||
//将【零千、零百】换成【零】 【十零】换成【十】
|
||||
result = result.replace(/零(千|百|十)/g, "零").replace(/十零/g, "十");
|
||||
//合并中间多个零为一个零
|
||||
result = result.replace(/零+/g, "零");
|
||||
//将【零亿】换成【亿】【零万】换成【万】
|
||||
result = result.replace(/零亿/g, "亿").replace(/零万/g, "万");
|
||||
//将【亿万】换成【亿】
|
||||
result = result.replace(/亿万/g, "亿");
|
||||
//移除末尾的零
|
||||
result = result.replace(/零+$/, "");
|
||||
//将【零一十】换成【零十】
|
||||
//result = result.replace(/零一十/g, '零十');//貌似正规读法是零一十
|
||||
//将【一十】换成【十】
|
||||
result = result.replace(/^一十/g, "十");
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象转url参数
|
||||
* @param num
|
||||
* @returns
|
||||
*/
|
||||
public static createUrl(url: string, query: any) {
|
||||
var temp: any[] = [];
|
||||
for (var k in query) {
|
||||
if (query[k] != null) temp.push(k + "=" + query[k]);
|
||||
}
|
||||
return url + "?" + temp.join("&");
|
||||
}
|
||||
/**
|
||||
* 获取当前角色信息
|
||||
* @returns
|
||||
*/
|
||||
public static getCurrentRoleInfo<T>() {
|
||||
let currentRole = localStorage.getItem("currentRole");
|
||||
if (!currentRole) return null;
|
||||
let { schId, roleId, childId, userId } = JSON.parse(decodeURI(currentRole ?? ""));
|
||||
return { schId, roleId, childId, userId } as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* tree对象转化为数组
|
||||
* @param tree
|
||||
* @returns
|
||||
*/
|
||||
public static treeToArray(tree: any[]): any[] {
|
||||
return tree.reduce((res, item) => {
|
||||
const { children, ...i } = item;
|
||||
return res.concat(i, children && children.length ? this.treeToArray(children) : []);
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组转化为tree对象
|
||||
* @param tree
|
||||
* @returns
|
||||
*/
|
||||
public static arrayToTree(arr: any[], pid: number): any[] {
|
||||
return arr
|
||||
.filter(item => item.pid === pid)
|
||||
.map(item => ({ ...item, children: this.arrayToTree(arr, item.id) }));
|
||||
}
|
||||
|
||||
//字符串的16进制表达
|
||||
public static strToHexCharCode(_str: string) {
|
||||
if (_str === "") return "";
|
||||
var hexCharCode: string[] = [];
|
||||
for (var i = 0; i < _str.length; i++) {
|
||||
var str = _str.charCodeAt(i).toString(16);
|
||||
if (_str.length == 1) str += "0" + _str;
|
||||
hexCharCode.push(str);
|
||||
}
|
||||
return hexCharCode.join("");
|
||||
}
|
||||
|
||||
public static hexToString(hex: string) {
|
||||
if (hex === "") return "";
|
||||
// 处理奇数长度,补前导零
|
||||
if (hex.length % 2 !== 0) {
|
||||
hex = "0" + hex;
|
||||
[[3]];
|
||||
}
|
||||
let str = "";
|
||||
for (let i = 0; i < hex.length; i += 2) {
|
||||
// 每两位解析为一个字符的Unicode值
|
||||
const hexPair = hex.substr(i, 2);
|
||||
const charCode = parseInt(hexPair, 16);
|
||||
str += String.fromCharCode(charCode);
|
||||
[[14, 16]];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将颜色值HEX格式转换为rgb的格式
|
||||
* @param {hex} hex 需要转换的rgb字符串
|
||||
* @param {number} opacity 透明度
|
||||
* @return {string} ;
|
||||
*/
|
||||
public static hexToRgb(hex: string, opacity: number = 1) {
|
||||
let str = hex.replace("#", "");
|
||||
if (str.length % 3) {
|
||||
return "hex格式不正确!";
|
||||
}
|
||||
//获取截取的字符长度
|
||||
let count = str.length / 3;
|
||||
//根据字符串的长度判断是否需要 进行幂次方
|
||||
let power = 6 / str.length;
|
||||
let r = parseInt("0x" + str.substring(0 * count, 1 * count)) ** power;
|
||||
let g = parseInt("0x" + str.substring(1 * count, 2 * count)) ** power;
|
||||
let b = parseInt("0x" + str.substring(2 * count)) ** power;
|
||||
return `rgb(${r}, ${g}, ${b},${opacity})`;
|
||||
}
|
||||
|
||||
public static removeHtmlTag(str: string) {
|
||||
str = str.replace(/<\/?[^>]*>/g, ""); //去除HTML tag
|
||||
str = str.replace(/[ | ]*\n/g, "\n"); //去除行尾空白
|
||||
str = str.replace(/\n[\s| | ]*\r/g, "\n"); //去除多余空行
|
||||
str = str.replace(/ /gi, ""); //去掉
|
||||
return str;
|
||||
}
|
||||
|
||||
public static unpackPerms = (bits: number) => [
|
||||
!!(bits & 1), // create
|
||||
!!(bits & 2), // update
|
||||
!!(bits & 4), // move
|
||||
!!(bits & 8), // delete
|
||||
];
|
||||
|
||||
public static copyToClipboard(text: string) {
|
||||
const input = document.createElement("input");
|
||||
input.setAttribute("value", text);
|
||||
document.body.appendChild(input);
|
||||
input.select();
|
||||
try {
|
||||
const successful = document.execCommand("copy");
|
||||
if (successful) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
} finally {
|
||||
document.body.removeChild(input);
|
||||
}
|
||||
}
|
||||
|
||||
public static sleep(ms: number, callback?: () => void): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
setTimeout(() => {
|
||||
callback?.();
|
||||
resolve();
|
||||
}, ms);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// import { BschIframeType, LightAppModal, IframePageParmas, RoleInfo, StateEnum } from "@/types";
|
||||
import mitt, { Emitter } from "mitt";
|
||||
|
||||
type MittType<T = any> = {
|
||||
reload: void; //刷新页面
|
||||
setMenuPath: number; //选择左侧菜单
|
||||
resetMenuPath: any[]; //重置设置左侧菜单
|
||||
setNavPath: any[]; //设置面包屑
|
||||
resetLayout: { identity: number; modId: number }; //重置页面菜单
|
||||
};
|
||||
|
||||
// 类型
|
||||
const emitter: Emitter<MittType> = mitt<MittType>();
|
||||
// 导出
|
||||
export default emitter;
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* NodeId 解码工具
|
||||
*/
|
||||
const ROOT_BITS = 4,
|
||||
TYPE_BITS = 4,
|
||||
SUBJ_BITS = 20,
|
||||
STAGE_BITS = 4,
|
||||
GRADE_BITS = 4,
|
||||
NODE_BITS = 28;
|
||||
|
||||
const ROOT_MASK = mask(ROOT_BITS),
|
||||
TYPE_MASK = mask(TYPE_BITS),
|
||||
SUBJ_MASK = mask(SUBJ_BITS),
|
||||
STAGE_MASK = mask(STAGE_BITS),
|
||||
GRADE_MASK = mask(GRADE_BITS),
|
||||
NODE_MASK = mask(NODE_BITS);
|
||||
|
||||
function mask(bits: number) {
|
||||
return (1n << BigInt(bits)) - 1n;
|
||||
}
|
||||
|
||||
function intToSubject(n: number) {
|
||||
if (n === 0) return null;
|
||||
return n <= 6 ? `SUB0${n}` : `SUB${n}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码入口:long → 普通对象
|
||||
* @param {string|number} compositeId 后端传来的 long 字符串或数字
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function decodeNodeId(compositeId: number | string) {
|
||||
const id = BigInt(compositeId);
|
||||
|
||||
const root = Number((id >> BigInt(TYPE_BITS + SUBJ_BITS + STAGE_BITS + GRADE_BITS + NODE_BITS)) & ROOT_MASK);
|
||||
const type = Number((id >> BigInt(SUBJ_BITS + STAGE_BITS + GRADE_BITS + NODE_BITS)) & TYPE_MASK);
|
||||
const subNum = Number((id >> BigInt(STAGE_BITS + GRADE_BITS + NODE_BITS)) & SUBJ_MASK);
|
||||
const stage = Number((id >> BigInt(GRADE_BITS + NODE_BITS)) & STAGE_MASK);
|
||||
const grade = Number((id >> BigInt(NODE_BITS)) & GRADE_MASK);
|
||||
const nodeId = Number(id & NODE_MASK);
|
||||
|
||||
return {
|
||||
rootType: root === 0 ? null : root,
|
||||
nodeType: type === 0 ? null : type,
|
||||
subject: intToSubject(subNum),
|
||||
stage: stage === 0 ? null : stage,
|
||||
grade: grade === 0 ? null : grade,
|
||||
nodeId: nodeId === 0 ? null : nodeId,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { BaseDict } from "@/types/Basics";
|
||||
import Utils from ".";
|
||||
|
||||
//当前周周一
|
||||
function getCurMonday(): Date {
|
||||
var now = new Date();
|
||||
var nowTime = now.getTime();
|
||||
var day = now.getDay();
|
||||
var oneDayTime = 24 * 60 * 60 * 1000;
|
||||
//显示周一
|
||||
var MondayTime = nowTime - (day - 1) * oneDayTime;
|
||||
var monday = new Date(MondayTime);
|
||||
return monday;
|
||||
}
|
||||
function addDay(date: Date, day: number) {
|
||||
var dateTime = new Date(date);
|
||||
dateTime.setDate(date.getDate() + day);
|
||||
dateTime = new Date(dateTime);
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
function format(dt: Date): string {
|
||||
return Utils.dateFormat(dt, "yyyy-MM-dd") + " 00:00:00";
|
||||
}
|
||||
|
||||
function shortcutDateList(): Array<any> {
|
||||
let now = new Date();
|
||||
const curMonday: Date = getCurMonday();
|
||||
let arr: any[] = [
|
||||
{
|
||||
k: "昨日",
|
||||
v: {
|
||||
startDate: format(addDay(now, -1)),
|
||||
endDate: format(addDay(now, -1)),
|
||||
},
|
||||
},
|
||||
{
|
||||
k: "今日",
|
||||
v: {
|
||||
startDate: format(now),
|
||||
endDate: format(now),
|
||||
},
|
||||
},
|
||||
{
|
||||
k: "上周",
|
||||
v: {
|
||||
startDate: format(addDay(curMonday, -7)),
|
||||
endDate: format(addDay(curMonday, -1)),
|
||||
},
|
||||
},
|
||||
{
|
||||
k: "本周",
|
||||
v: {
|
||||
startDate: format(curMonday),
|
||||
endDate: format(addDay(curMonday, 6)),
|
||||
},
|
||||
},
|
||||
{
|
||||
k: "本月",
|
||||
v: {
|
||||
startDate: format(new Date(now.getFullYear(), now.getMonth(), 1)),
|
||||
endDate: format(new Date(now.getFullYear(), now.getMonth() + 1, 0)),
|
||||
},
|
||||
},
|
||||
];
|
||||
return arr;
|
||||
}
|
||||
|
||||
export default shortcutDateList;
|
||||
Reference in New Issue
Block a user