三级重构(S1-S6,方案见 docs/设置中心三级重构-执行方案-20261008.md): - Settings.vue 三层化主体:curLayer=my/school/platform 单 pane 分层,主题/头像/壁纸/桌面/LOGO 按身份只渲染本级 - 桌面应用三级默认 pane(used_app SCHOOL/BASE 读写 + 角色分段 + ≤8 上限)+ 统计设置真身迁入(StatsBarModal 退役) - SysSetting.vue 去基础设置/机构树;ThemePreview 预览壳套 .layout-wrap; UsedAppApi 新增 审计缺陷修复(设置中心三级重构与消息搜索-审计报告-20261008): - 【P0】DESKTOP_ROLE_TYPES 角色 1/2 标签对齐 SysRoleEnum(1教师/2校领导/3学生),桌面默认不再张冠李戴 - 命令面板:Enter/↑↓ 加 isComposing 守卫防中文输入法误触;过滤后高亮索引归零;stagger 延迟 --i 封顶 20 - WS 通知标题移除 frame.appKey 死消费;BaseApi summary todos 类型同步去 appKey
This commit is contained in:
@@ -170,7 +170,6 @@
|
||||
<!-- 审批直批弹窗(B3 §2)已抽共享组件(消息对接 2026-09-30),供 /msg 页复用;Header 内已无直批入口 -->
|
||||
<MsgApprovalModal ref="refMsgApproval" />
|
||||
|
||||
<StatsBarModal ref="refStatsBarModal" />
|
||||
<TeacherCopilotModal ref="refTeacherCopilotModal" />
|
||||
|
||||
<!-- 搜索命令面板(2026-10-08 定稿 B 方案):必须 Teleport 到 body ——
|
||||
@@ -231,7 +230,6 @@ import { useDefaultStore } from "@/store";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { requireImg } from "@/utils/hook/useImg";
|
||||
import ContextMenu from "@/components/ContextMenu.vue";
|
||||
import StatsBarModal from "@/components/StatsBarModal.vue";
|
||||
import TeacherCopilotModal from "@/components/TeacherCopilotModal.vue";
|
||||
import BaseApi from "@/api/BaseApi";
|
||||
import MsgApprovalModal from "@/components/MsgApprovalModal.vue";
|
||||
@@ -257,7 +255,6 @@ const baseUrl = computed(() => $conf.fileHost);
|
||||
* 但摘要拉取与 WS 推送仍需保留 —— /msg 页左栏与 LeftMenu 角标与本store 同源 */
|
||||
|
||||
const refSearchInput = ref<any>();
|
||||
const refStatsBarModal = ref<any>();
|
||||
const refTeacherCopilotModal = ref<any>();
|
||||
|
||||
const uploadHeaders = { yschapirequest: 1, yschreferer: Utils.strToHexCharCode(window.location.href) };
|
||||
@@ -336,13 +333,8 @@ async function connectMsgWs() {
|
||||
};
|
||||
}
|
||||
|
||||
/** WS 帧按 appKey 匹配应用名(store 应用列表);查无回退类型标签(缺失降级,不报错) */
|
||||
/** WS 通知标题用类型标签(appKey 属后端签名标识,帧消费已随 0e57022 口径移除) */
|
||||
function frameAppName(frame: any) {
|
||||
const appKey = frame && frame.appKey;
|
||||
if (appKey) {
|
||||
const hit = store.getFlattenAppList.find((x: any) => x.appKey == appKey || x.appCode == appKey);
|
||||
if (hit && hit.name) return hit.name;
|
||||
}
|
||||
return msgTypeLabel(frame ? frame.msgType : null);
|
||||
}
|
||||
|
||||
@@ -414,6 +406,7 @@ onUnmounted(() => {
|
||||
// 不再 emit("searchChange") —— 背景应用网格保持原样(2026-10-08 用户修订 1)
|
||||
watch(searchAppText, () => {
|
||||
cmdkStagger.value = false; // 输入过滤时不重播逐条入场
|
||||
cmdkIndex.value = 0; // 过滤使结果集变化,高亮回归首项(否则旧索引落空/错位,Enter 哑炮)
|
||||
});
|
||||
// ===== 搜索命令面板(2026-10-08 定稿 B 方案)=====
|
||||
const refCmdkList = ref<any>();
|
||||
@@ -474,6 +467,9 @@ function pickApp(item: any) {
|
||||
|
||||
function onCmdkKeydown(ev: KeyboardEvent) {
|
||||
if (!isSearching.value) return; // 面板未开:绝不拦截全局按键
|
||||
// IME 组合态(拼音候选未上屏)不响应导航/确认:keydown 的 Enter/↑↓ 属输入法内部按键,
|
||||
// 不拦截防误开应用、不 preventDefault 防干扰候选词选择(keyCode 229 为旧式组合态标记)
|
||||
if (ev.isComposing || ev.keyCode === 229) return;
|
||||
if (ev.key === "Escape") {
|
||||
closeSearch();
|
||||
return;
|
||||
@@ -542,11 +538,6 @@ function linkSidebarSetting() {
|
||||
router.push("/settings?pane=sidebar");
|
||||
}
|
||||
|
||||
async function settingsStats() {
|
||||
isUserCardShow.value = false;
|
||||
refStatsBarModal.value.show();
|
||||
}
|
||||
|
||||
async function settingsTeacherCopilot() {
|
||||
isUserCardShow.value = false;
|
||||
refTeacherCopilotModal.value.show();
|
||||
|
||||
@@ -1,301 +0,0 @@
|
||||
<template>
|
||||
<Modal
|
||||
v-model="isTableModalShow"
|
||||
class-name="menuwapper-modal"
|
||||
width="1160"
|
||||
:mask="true"
|
||||
title="统计模块设置"
|
||||
:transfer="false"
|
||||
>
|
||||
<div class="fix mb-10">
|
||||
<Button type="primary" size="small" class="fl ml-10 width-10" ghost @click="onEditFormShow()">
|
||||
新建统计模块
|
||||
</Button>
|
||||
</div>
|
||||
<Table
|
||||
:columns="menuBarColumns"
|
||||
:height="360"
|
||||
:loading="tableLoading"
|
||||
:data="tableList"
|
||||
class="link-table"
|
||||
no-data-text="暂无数据"
|
||||
>
|
||||
<template #icon="{ row }">
|
||||
<img class="icon-box" v-if="!!row.icon" :src="$conf.serviceHost + row.icon" alt="" />
|
||||
</template>
|
||||
<template #isShow="{ row }">
|
||||
<Switch :model-value="row.isShow" true-color="#5979ea" @on-change="onMenuBarShowChange(row.id)" />
|
||||
</template>
|
||||
|
||||
<template #roleIds="{ row }">
|
||||
<span>{{ row.roleIds.map((item: number) => RoleTypeDict.find(x => x.k == item)?.v).join(",") }}</span>
|
||||
</template>
|
||||
<!-- <template #orderNum="{ row }">
|
||||
<InputNumber :model-value="row.orderNum" :min="1" />
|
||||
</template> -->
|
||||
</Table>
|
||||
<template #footer>
|
||||
<Button type="primary" ghost @click="isTableModalShow = false">关闭</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
<Modal
|
||||
v-model="isEditModalShow"
|
||||
scrollable
|
||||
width="550"
|
||||
:title="(!formData.id ? '添加' : '编辑') + '统计模块'"
|
||||
:transfer="true"
|
||||
>
|
||||
<Form ref="refEditForm" class="mr-30" :model="formData" :rules="ruleValidate" :label-width="110">
|
||||
<Form-Item label="权限角色:" class="fix" prop="roleIds">
|
||||
<Checkbox
|
||||
class="fl"
|
||||
:indeterminate="indeterminate"
|
||||
:model-value="compCheckAll"
|
||||
@click.prevent="handleCheckAll"
|
||||
>
|
||||
全部
|
||||
</Checkbox>
|
||||
<CheckboxGroup v-model="formData.roleIds">
|
||||
<Checkbox v-for="item in RoleTypeDict" :label="item.k">{{ item.v }}</Checkbox>
|
||||
</CheckboxGroup>
|
||||
</Form-Item>
|
||||
<Form-Item label="名称:" prop="name">
|
||||
<Input max="4" v-model="formData.name" maxlength="30" placeholder="请输入名称" />
|
||||
</Form-Item>
|
||||
<Form-Item label="网址:" prop="url">
|
||||
<Input v-model="formData.url" placeholder="请输入链接" />
|
||||
</Form-Item>
|
||||
<Form-Item label="图标:" prop="icon">
|
||||
<Upload
|
||||
:on-success="uploadIconSuccess"
|
||||
:show-upload-list="false"
|
||||
:action="$conf.serviceHost + '/api/iconUpload'"
|
||||
:format="['jpg', 'jpeg', 'png', 'gif']"
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png"
|
||||
:with-credentials="true"
|
||||
:headers="uploadHeaders"
|
||||
>
|
||||
<div v-if="!formData.icon" class="icon-upload-wrap">
|
||||
<SvgIcon name="upload-add" color="#646566" style="width: 20px; height: 20px" />
|
||||
</div>
|
||||
<div class="icon-preview-wrap" v-else>
|
||||
<SvgIcon
|
||||
@click.stop="formData.icon = null"
|
||||
class="cloase-btn"
|
||||
name="widget-close"
|
||||
size="18"
|
||||
color="#fff"
|
||||
/>
|
||||
<img :src="$conf.serviceHost + formData.icon" alt="" />
|
||||
</div>
|
||||
</Upload>
|
||||
</Form-Item>
|
||||
|
||||
<Form-Item label="排序号:" prop="orderNum">
|
||||
<InputNumber style="width: 120px" v-model="formData.orderNum" :min="1" placeholder="请输入排序号" />
|
||||
</Form-Item>
|
||||
<Form-Item label="打开方式:" prop="linkOpenType">
|
||||
<RadioGroup v-model="formData.linkOpenType" type="button" button-style="solid">
|
||||
<Radio :label="item.k" v-for="item in linkOpenTypeDict">{{ item.v }}</Radio>
|
||||
</RadioGroup>
|
||||
</Form-Item>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button type="primary" :loading="editLoading" @click="onEditSave">保存</Button>
|
||||
<Button @click="isEditModalShow = false">取消</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import StatsBarApi from "@/api/StatsBarApi";
|
||||
import { LinkOpenTypeDict, LinkOpenTypeEnum, RoleTypeEnum, RoleTypeDict } from "@/types";
|
||||
import Utils from "@/utils";
|
||||
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
|
||||
import { Message } from "view-ui-plus";
|
||||
import { ref, reactive, computed } from "vue";
|
||||
import SvgIcon from "@/components/SvgIcon.vue";
|
||||
import MyConfirm from "@/components/MyConfirm";
|
||||
import { BaseDict } from "@/types/Basics";
|
||||
import { find } from "lodash";
|
||||
|
||||
interface FormData {
|
||||
id: string | null;
|
||||
roleIds: number[];
|
||||
name: string | null;
|
||||
url: string | null;
|
||||
linkOpenType: number;
|
||||
icon: string | null;
|
||||
orderNum: number | null;
|
||||
isShow: boolean;
|
||||
isEdit: boolean;
|
||||
}
|
||||
|
||||
// 初始化 formData 的默认值
|
||||
const defaultFormData: FormData = {
|
||||
id: null,
|
||||
roleIds: [], // 添加 roleIds 属性
|
||||
name: "",
|
||||
url: "",
|
||||
linkOpenType: 0,
|
||||
icon: "",
|
||||
orderNum: null,
|
||||
isShow: true,
|
||||
isEdit: true,
|
||||
};
|
||||
|
||||
const { $conf } = useCurrentInstance();
|
||||
|
||||
const uploadHeaders = { yschapirequest: 1, yschreferer: Utils.strToHexCharCode(window.location.href) };
|
||||
|
||||
const refEditForm = ref<any>(null);
|
||||
|
||||
const isTableModalShow = ref(false),
|
||||
isEditModalShow = ref(false),
|
||||
tableLoading = ref(false),
|
||||
editLoading = ref(false),
|
||||
tableList = ref<any[]>([]),
|
||||
formData = reactive(defaultFormData);
|
||||
|
||||
const ruleValidate = ref({
|
||||
roleIds: { required: true, type: "array", message: "请选择权限角色", trigger: "change,submit" },
|
||||
name: { required: true, message: "请输入名称", trigger: "blur" },
|
||||
url: { required: true, message: "请输入网址", trigger: "blur" },
|
||||
icon: { required: true, message: "请上传图标", trigger: "change,submit" },
|
||||
});
|
||||
|
||||
const linkOpenTypeDict = computed(() => LinkOpenTypeDict.filter(x => x.k != LinkOpenTypeEnum.Modal));
|
||||
const compCheckAll = computed(() => formData.roleIds.length == RoleTypeDict.length && RoleTypeDict.length > 0);
|
||||
const indeterminate = computed(() => formData.roleIds.length > 0 && formData.roleIds.length < RoleTypeDict.length);
|
||||
|
||||
const menuBarColumns = ref([
|
||||
{
|
||||
title: "名称",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "图标",
|
||||
slot: "icon",
|
||||
},
|
||||
{
|
||||
title: "角色",
|
||||
width: 150,
|
||||
slot: "roleIds",
|
||||
},
|
||||
{
|
||||
title: "地址",
|
||||
key: "url",
|
||||
width: 150,
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
title: "打开方式",
|
||||
key: "linkType",
|
||||
},
|
||||
{
|
||||
title: "是否显示",
|
||||
width: 100,
|
||||
align: "center",
|
||||
slot: "isShow",
|
||||
},
|
||||
{
|
||||
title: "排序号",
|
||||
key: "orderNum",
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 120,
|
||||
align: "center",
|
||||
render: (h: any, params: any) => {
|
||||
const disabled = !params.row.isEdit,
|
||||
id = params.row.id;
|
||||
|
||||
return [
|
||||
Utils.operatingTextHandle(h, "编辑", "mr-2", () => onEditFormShow(id), disabled),
|
||||
Utils.operatingTextHandle(h, "删除", "text-error", () => onDeleteMenu(id), disabled),
|
||||
];
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
async function show() {
|
||||
isTableModalShow.value = true;
|
||||
await getMenuBarList();
|
||||
console.log("show");
|
||||
}
|
||||
|
||||
async function getMenuBarList() {
|
||||
tableLoading.value = true;
|
||||
|
||||
const list = await StatsBarApi.getStatsBarSchList();
|
||||
list.forEach(element => {
|
||||
element.linkType = find(linkOpenTypeDict.value, { k: element.linkOpenType })?.v;
|
||||
});
|
||||
tableList.value = list;
|
||||
tableLoading.value = false;
|
||||
}
|
||||
|
||||
function handleCheckAll() {
|
||||
formData.roleIds = !compCheckAll.value ? RoleTypeDict.map(item => item.k) : [];
|
||||
}
|
||||
function uploadIconSuccess(response: any, file: any, fileListt: any) {
|
||||
if (response.code == 0) formData.icon = response.data.url;
|
||||
else Message.error("上传错误!");
|
||||
// console.log(response, file, fileListt);
|
||||
}
|
||||
|
||||
async function onEditFormShow(id?: number) {
|
||||
await refEditForm.value?.resetFields();
|
||||
isEditModalShow.value = false;
|
||||
if (!id) {
|
||||
formData.id = null;
|
||||
formData.linkOpenType = LinkOpenTypeEnum.Open;
|
||||
} else {
|
||||
let find = tableList.value.find(x => x.id == id);
|
||||
Object.assign(formData, find);
|
||||
}
|
||||
isEditModalShow.value = true;
|
||||
}
|
||||
async function onDeleteMenu(id: number) {
|
||||
await MyConfirm({
|
||||
title: "删除",
|
||||
msg: "确定删除该统计模块吗?",
|
||||
info: "删除后无法恢复!",
|
||||
async onOk() {
|
||||
await StatsBarApi.deleteStatsBar(id);
|
||||
},
|
||||
});
|
||||
await getMenuBarList();
|
||||
}
|
||||
|
||||
async function onEditSave() {
|
||||
let validate: boolean = await refEditForm.value?.validate();
|
||||
if (!validate) return false;
|
||||
editLoading.value = true;
|
||||
try {
|
||||
await StatsBarApi.saveStatsBar(formData);
|
||||
Message.success("操作成功!");
|
||||
await getMenuBarList();
|
||||
isEditModalShow.value = false;
|
||||
} catch (error) {
|
||||
Message.success("操作失败!");
|
||||
} finally {
|
||||
editLoading.value = false;
|
||||
}
|
||||
}
|
||||
function onMenuBarShowChange(id: number) {
|
||||
StatsBarApi.toggleStatsBarShow(id);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.icon-box {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user