feat: 接入 UserHome 前端业务代码(含真实 src/依赖,保留 projman 标准 CI)
deploy-userbase-front / build-and-deploy (push) Failing after 1m54s
build / build (push) Successful in 3m36s

This commit is contained in:
chenxing
2026-09-09 22:37:59 +08:00
parent a53ce07228
commit 4279f99be1
270 changed files with 12479 additions and 25715 deletions
-151
View File
@@ -1,151 +0,0 @@
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 },
});
}
-40
View File
@@ -1,40 +0,0 @@
// @/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 });
},
});
-54
View File
@@ -1,54 +0,0 @@
// @/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 });
},
});
+65
View File
@@ -85,6 +85,71 @@ class Abstract {
protected deleteRequest<T>({ url, params, data, isMsg = true }: BschResult): Promise<T> {
return this.apiAxios<T>({ method: "DELETE", url, params, data, isMsg });
}
/**
* Get转发类型的网络请求
* @param
* @returns
*/
protected getRelayRequest<T>({ baseHost, url, params }: any): Promise<T> {
const tempConfig = _.cloneDeep(baseConfig);
const header = tempConfig.headers;
tempConfig.headers = {
...header,
RelayUrl: baseHost + url,
};
const instance = initAxiosInstance(tempConfig);
return new Promise((resolve, reject) => {
instance({
method: "GET",
url: "/api/relay/doGet",
params,
})
.then(res => {
let { data } = res;
resolve(data as T);
})
.catch(err => {
reject(err);
// throw err;
});
});
}
/**
* Post转发类型的网络请求
* @param
* @returns
*/
protected postRelayRequest<T>({ baseHost, url, params, data }: any): Promise<T> {
const tempConfig = _.cloneDeep(baseConfig);
const header = tempConfig.headers;
tempConfig.headers = {
...header,
RelayUrl: baseHost + url,
};
const instance = initAxiosInstance(tempConfig);
let apiUrl = !!data ? "/api/relay/doPost" : "/api/relay/doPost2";
return new Promise((resolve, reject) => {
instance({
method: "POST",
url: apiUrl,
params,
data,
})
.then(res => {
let { data } = res.data;
console.log(data);
resolve(data as T);
})
.catch(err => {
reject(err);
// throw err;
});
});
}
}
export default Abstract;
-2
View File
@@ -1,5 +1,4 @@
import { AxiosResponse, BschResponse } from "axios";
function formrHandle(response: AxiosResponse) {
let errorMsg: string = "";
let { data, status } = response;
@@ -14,7 +13,6 @@ function formrHandle(response: AxiosResponse) {
errorMsg = "redirect";
window.location.href = data.urlToRedirectTo;
break;
// errorMsg = "redirect";
default:
errorMsg = data.msg;
}
+7 -8
View File
@@ -2,25 +2,24 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosRequestHe
import formrHandle from "./format-handle";
import { debugUser } from "@/assets/config/api.config";
import conf from "@/assets/config/host.config";
import Utils from "@/utils";
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);
// 通用请求头:yschapirequest 标识 API 请求,yschreferer 记录来源页面(十六进制)
(config.headers as AxiosRequestHeaders)["yschapirequest"] = "1";
(config.headers as AxiosRequestHeaders)["yschreferer"] = Utils.strToHexCharCode(window.location.href);
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;
@@ -32,7 +31,7 @@ export const initAxiosInstance: (config: AxiosRequestConfig) => AxiosInstance =
},
error => {
return formrHandle(error.response);
},
}
);
return axiosInstance;
+13 -13
View File
@@ -2,22 +2,22 @@ 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;
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;
+41
View File
@@ -0,0 +1,41 @@
import { IframePageParmas, LinkOpenTypeEnum, StateEnum } from "@/types";
import mittBus from "@/utils/mitt";
interface LinkPageOptions {
url: string;
type?: number | LinkOpenTypeEnum;
id?: number;
isTicket?: boolean;
title?: string;
isHeader?: boolean;
modalWidth?: number;
modalHeight?: number;
}
export function bschLinkPage(options: LinkPageOptions) {
const { url, type = LinkOpenTypeEnum.Open, title } = options;
const openType: LinkOpenTypeEnum = type as LinkOpenTypeEnum;
if (openType === LinkOpenTypeEnum.Open) {
window.open(url);
} else if (openType === LinkOpenTypeEnum.Href) {
window.location.href = url;
} else if (openType === LinkOpenTypeEnum.IFrame) {
const { isHeader = false } = options;
const params: IframePageParmas = {
url,
appName: title ?? "",
isHeader,
};
mittBus.emit("openBschIframePage", params);
} else if (openType === LinkOpenTypeEnum.Modal) {
const { modalWidth = 80, modalHeight = 70 } = options;
mittBus.emit("showLightAppModal", { width: modalWidth, height: modalHeight, url, title: title ?? "轻应用" });
// const { modalTitle = "新窗口", modalWidth = 80, modalHeight = 70 } = options;
}
}
export function isTickeRoterByName(name: string) {
return ["BschAppDetails", "AppDetails"].includes(name);
}
+16
View File
@@ -0,0 +1,16 @@
import { BschTransferRoleInfo } from "@/types";
/**
* 角色信息格式化编码
*/
export function bschRoleEncode(info: BschTransferRoleInfo) {
let str = JSON.stringify(info);
str = encodeURIComponent(str);
str = window.btoa(str);
return str;
}
export function bschRoleParamsEncode(str: string) {
str = encodeURIComponent(str);
str = window.btoa(str);
return str;
}
+1 -1
View File
@@ -7,7 +7,7 @@ export function replaceOriginUrl(urlToRedirectTo: string, baseRedirectHost: stri
var ssoUrlObj = new URL(urlToRedirectTo);
var paramsStr = ssoUrlObj.searchParams.get("service") || "";
let ssoServiceUrl = decodeURI(paramsStr).split(";")[0];
ssoServiceUrl += ";redirectToReferer=" + baseRedirectHost + "/api/base/RedirectTo/" + newUrl;
ssoServiceUrl += ";redirectToReferer=" + baseRedirectHost + "/api/RedirectTo/" + newUrl;
ssoUrlObj.searchParams.set("service", encodeURI(ssoServiceUrl));
return ssoUrlObj.href;
}
+17 -12
View File
@@ -3,22 +3,27 @@ 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");
await bindImg(el, binding);
},
async beforeUpdate(el, binding) {
await bindImg(el, binding);
},
});
};
async function bindImg(el: any, binding: any) {
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
+42
View File
@@ -0,0 +1,42 @@
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from "echarts/core";
/** 引入柱状图 + 折线图 + 饼图,图表后缀都为 Chart,一般常用的就这三个,如果还需要其他的,就自行添加 */
// import { BarChart, LineChart, PieChart } from "echarts/charts";
import { BarChart, PieChart } from "echarts/charts";
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
ToolboxComponent,
LegendComponent,
} from "echarts/components";
// 标签自动布局,全局过渡动画等特性
import { LabelLayout, UniversalTransition } from "echarts/features";
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from "echarts/renderers";
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
ToolboxComponent,
LegendComponent,
LabelLayout,
UniversalTransition,
CanvasRenderer,
BarChart,
PieChart,
]);
// 导出
export default echarts;
-43
View File
@@ -1,43 +0,0 @@
import { computed, ref } from "vue";
import { Message } from "view-ui-plus";
import { useDefaultStore } from "@/store";
import { UserRoleEnum } from "@/types";
/**
* 应用管理页学校上下文(管理端权限分级改造 §4.3)。<br>
* SCHOOL 模式专属:多校管理员(schoolGroupIds.length > 1)页面顶部出现「学校」下拉,
* currentSchoolId 随接口下发给 AppList/AppSortList/AppSort/EditApp/EditType/AppTypeSort,
* 覆盖只对所选学校生效;单校管理员不显示下拉(后端自动取唯一学校),
* 超管/局端不进入本上下文(局端无应用管理菜单)。
*/
export function useAppSchoolScope() {
const store = useDefaultStore();
const isSchoolMode = computed(() => store.userRole === UserRoleEnum.School);
const schoolGroupIds = computed<number[]>(() => store.userScope?.schoolGroupIds ?? []);
const multiSchool = computed(() => isSchoolMode.value && schoolGroupIds.value.length > 1);
const schoolList = ref<{ id: number; name: string }[]>([]);
// 默认不选中(currentSchoolId=undefined):查询走后端「多校并集」模式;
// 写操作(编辑/排序)要求先选定学校,由页面用 requireSchool() 守卫
const currentSchoolId = ref<number | undefined>(undefined);
/** 多校模式下写操作前置校验:未选学校时提示并阻断,返回 false */
function requireSchool(): boolean {
if (multiSchool.value && !currentSchoolId.value) {
Message.warning("您管理多所学校,请先在顶部选择学校后再操作");
return false;
}
return true;
}
if (multiSchool.value) {
// 下拉数据源(SUPER/BUREAU 后端返回空数组,前端也不渲染下拉)
Promise.resolve(Apis.AppSettings.schoolList({ cacheFor: null }))
.then((list: any) => {
schoolList.value = Array.isArray(list) ? list : [];
})
.catch(e => console.warn("获取可管理学校列表失败", e));
}
return { isSchoolMode, multiSchool, schoolList, currentSchoolId, requireSchool };
}
+44
View File
@@ -0,0 +1,44 @@
import { ref, onMounted, onUnmounted, Ref } from "vue";
export function useContextMenu(container: Ref) {
const x = ref(0);
const y = ref(0);
const visible = ref(false);
function openMenu(e: any) {
// console.log("点击右键");
// console.log(e);
e.preventDefault();
e.stopPropagation();
visible.value = true;
x.value = e.clientX;
y.value = e.clientY;
}
function closeMenu() {
visible.value = false;
}
function keyCloseMenu(e: KeyboardEvent) {
if (e.code === "Escape") closeMenu();
}
onMounted(() => {
const div = container.value;
div.addEventListener("contextmenu", openMenu);
window.addEventListener("click", closeMenu, true);
window.addEventListener("contextmenu", closeMenu, true);
window.addEventListener("keydown", keyCloseMenu, true);
});
onUnmounted(() => {
const div = container.value;
if (!!div) div.removeEventListener("contextmenu", openMenu);
window.removeEventListener("click", closeMenu, true);
window.removeEventListener("contextmenu", closeMenu, true);
window.removeEventListener("keydown", keyCloseMenu, true);
});
return {
x,
y,
visible,
};
}
+31
View File
@@ -0,0 +1,31 @@
import { ref, onMounted, onUnmounted, Ref } from "vue";
export function useDraggable(container: Ref, originalX: number, originalY: number) {
const x = ref(originalX);
const y = ref(originalY);
const isDraggable = ref(false);
function handleDraggable(e: any) {
// console.log("点击右键");
e.preventDefault();
e.stopPropagation();
isDraggable.value = true;
x.value = e.pageX - originalX;
y.value = e.pageY - originalY;
}
onMounted(() => {
const div = container.value;
div.addEventListener("mousedown", handleDraggable);
});
onUnmounted(() => {
const div = container.value;
if (!!div) div.removeEventListener("mousedown", handleDraggable);
});
return {
x,
y,
isDraggable,
};
}
-37
View File
@@ -1,37 +0,0 @@
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;
}
+43 -46
View File
@@ -1,5 +1,6 @@
import { IObj, UseTableSetting } from "@/types/Basics";
import { IObj, StatsRealyConf, UseTableSetting } from "@/types/Basics";
import { resolveComponent } from "vue";
import { requireImg } from "./hook/useImg";
export default class Utils {
/**
* 格式化日期
@@ -83,7 +84,7 @@ export default class Utils {
info: string,
iconClass: string,
callback: Function,
disabled: boolean = false,
disabled: boolean = false
) {
if (disabled) iconClass += " disabled";
@@ -102,10 +103,10 @@ export default class Utils {
class: `operating-btn iconfont ${iconClass}`,
onClick: callback,
},
null,
null
),
],
},
}
);
}
@@ -114,7 +115,7 @@ export default class Utils {
info: string,
className: string,
callback: Function,
disabled: boolean = false,
disabled: boolean = false
) {
if (disabled) className += " disabled";
return h(
@@ -123,7 +124,7 @@ export default class Utils {
class: `operating-link ${className}`,
onClick: disabled ? null : callback,
},
info,
info
);
}
@@ -144,10 +145,6 @@ export default class Utils {
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);
@@ -180,7 +177,7 @@ export default class Utils {
"十",
"百",
"千",
"亿",
"亿"
); //可继续追加更高位转换值
if (!num || isNaN(num)) {
return "零";
@@ -308,45 +305,45 @@ export default class Utils {
}
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;
// 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;
const doc = new DOMParser().parseFromString(str, "text/html");
return doc.body.textContent || "";
}
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 noProtocolPath(url: string) {
let uri = new URL(url);
return `//${uri.hostname}${uri.pathname}`;
}
public static sleep(ms: number, callback?: () => void): Promise<void> {
return new Promise<void>(resolve => {
setTimeout(() => {
callback?.();
resolve();
}, ms);
/** 应用图标地址:icon 为空时回退本地默认图(避免 new URL("") 抛错) */
public static appIconPath(url?: string | null) {
return url ? this.noProtocolPath(url) : requireImg("defappicon.png");
}
public static formatRealyToMap(arr: StatsRealyConf[]) {
const map: Map<string, StatsRealyConf> = new Map();
arr.forEach(element => {
map.set(element.module, element);
});
return map;
}
/**
* 替换对象key
* @param obj 元数据
* @param keyArr key替换数组
* @returns
*/
public static replaceField<T>(obj: T, keyArr: any[]): T {
let map = new Map(Object.entries(obj as object));
keyArr.forEach(element => {
map.set(element.n, map.get(element.o));
map.delete(element.o);
});
return Object.fromEntries(map) as T;
}
}
+10 -5
View File
@@ -1,12 +1,17 @@
// import { BschIframeType, LightAppModal, IframePageParmas, RoleInfo, StateEnum } from "@/types";
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 }; //重置页面菜单
checkMenuById: number; //选择左侧菜单
updateState: StateEnum; //更改状态
switchRole: RoleInfo; //切换角色
reloadUserSettings: void; //刷新用户设置
showUserInfoModal: boolean | undefined; //打开userinfo模态框
openBschIframePage: IframePageParmas; //打开iframe页面
showLightAppModal: LightAppModal; //打开lightapp模态框
searchReset: void; //重置搜索框
searchChange: string | null;
};
// 类型
-50
View File
@@ -1,50 +0,0 @@
/**
* 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,
};
}
-69
View File
@@ -1,69 +0,0 @@
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;