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
-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;
}