feat: 接入 UserHome 前端业务代码(含真实 src/依赖,保留 projman 标准 CI)
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user