- sidebar pane 静态示意(drag-list 占位)替换为真实管理:getMenuBarSchList 列表 + 新建/编辑/删除/toggleMenuBarShow 显示开关,列结构与图一 MenuBarModal 完全一致(名称/图标/角色/地址/打开方式/系统页面/是否显示/排序号/操作,isEdit 控制操作权限) - 新建/编辑表单照抄:roleIds 多选(全部/半选态)、名称/网址、图标 iconUpload 直传(.svg + 网关头,可移除重传)、排序号、悬浮底部/系统页面开关、非系统页面时打开方式 Radio(排除 Modal)、IFrame 时显示头部开关;删除走 MyConfirm 二次确认 - 编辑弹窗为页面级 iview Modal(transfer);保存/删除/开关语义与 Header 弹窗一致(Header 入口未删,两处同数据同接口) - 打开 pane 懒加载列表;静态 sidebarItems/drag-list 样式删除
This commit is contained in:
+268
-19
@@ -310,25 +310,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 边栏设置(管理员) -->
|
<!-- 边栏设置(管理员;2026-09-30 二轮:真实管理从 Header MenuBarModal 迁入,接口/字段/校验语义照抄) -->
|
||||||
<div v-else-if="curPane === 'sidebar'" class="pane">
|
<div v-else-if="curPane === 'sidebar'" class="pane">
|
||||||
<div class="pane-head">
|
<div class="pane-head">
|
||||||
<h3>边栏设置</h3>
|
<h3>边栏设置</h3>
|
||||||
<p>配置左侧应用边栏的显示项与排序,拖动左侧手柄调整顺序。</p>
|
<p>配置左侧边栏一级入口的名称、图标、可见角色与排序;保存后用户下次进入门户生效。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="sec-card">
|
<div class="sec-card">
|
||||||
<h4>边栏应用</h4>
|
<h4>边栏列表</h4>
|
||||||
<div class="drag-list">
|
<div class="side-toolbar">
|
||||||
<div v-for="(d, i) in sidebarItems" :key="i" :class="['drag-item', { off: !d.on }]">
|
<button class="btn primary sm" @click="onSidebarEditShow()">新建边栏</button>
|
||||||
<span class="hd">⠿</span>
|
|
||||||
<span class="nm">{{ d.name }}</span>
|
|
||||||
<span :class="['drag-switch', { on: d.on }]" @click="d.on = !d.on"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Table :columns="sidebarColumns" :loading="sidebarLoading" :data="sidebarList" no-data-text="暂无数据"
|
||||||
<div class="pane-foot">
|
class="sidebar-table">
|
||||||
<button class="btn primary">保存排序</button>
|
<template #icon="{ row }">
|
||||||
<button class="btn plain">恢复默认</button>
|
<img class="sidebar-icon" v-if="!!row.icon" :src="$conf.serviceHost + row.icon" alt="" />
|
||||||
|
</template>
|
||||||
|
<template #roleIds="{ row }">
|
||||||
|
<span>{{ row.roleIds.map((it: number) => RoleTypeDict.find(x => x.k == it)?.v).join(",") }}</span>
|
||||||
|
</template>
|
||||||
|
<template #isShow="{ row }">
|
||||||
|
<Switch :model-value="row.isShow" true-color="#5979ea" @on-change="onSidebarShowChange(row.id)" />
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -373,6 +377,71 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 边栏新建/编辑(2026-09-30 二轮自 Header MenuBarModal 迁入:字段/校验/图标直传/保存语义照抄) -->
|
||||||
|
<Modal v-model="sidebarEditShow" scrollable width="550" :title="(!sidebarForm.id ? '添加' : '编辑') + '边栏'" :transfer="true">
|
||||||
|
<Form ref="refSidebarForm" class="mr-30" :model="sidebarForm" :rules="sidebarRules" :label-width="110">
|
||||||
|
<Form-Item label="权限角色:" class="fix" prop="roleIds">
|
||||||
|
<Checkbox class="fl" :indeterminate="sidebarIndeterminate" :model-value="sidebarAllChecked"
|
||||||
|
@click.prevent="onSidebarCheckAll">
|
||||||
|
全部
|
||||||
|
</Checkbox>
|
||||||
|
<CheckboxGroup v-model="sidebarForm.roleIds">
|
||||||
|
<Checkbox v-for="item in RoleTypeDict" :key="item.k" :label="item.k">{{ item.v }}</Checkbox>
|
||||||
|
</CheckboxGroup>
|
||||||
|
</Form-Item>
|
||||||
|
<Form-Item label="名称:" prop="name">
|
||||||
|
<Input maxlength="30" v-model="sidebarForm.name" placeholder="请输入名称" />
|
||||||
|
</Form-Item>
|
||||||
|
<Form-Item label="网址:" prop="url">
|
||||||
|
<Input v-model="sidebarForm.url" placeholder="请输入链接" />
|
||||||
|
</Form-Item>
|
||||||
|
<Form-Item label="图标:" prop="icon">
|
||||||
|
<Upload :on-success="onSidebarIconUploaded" :show-upload-list="false"
|
||||||
|
:action="$conf.serviceHost + '/api/iconUpload'" accept=".svg" :with-credentials="true"
|
||||||
|
:headers="uploadHeaders">
|
||||||
|
<div v-if="!sidebarForm.icon" class="sidebar-icon-upload">
|
||||||
|
<SvgIcon name="upload-add" color="#646566" style="width: 20px; height: 20px" />
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-icon-preview" v-else>
|
||||||
|
<SvgIcon @click.stop="sidebarForm.icon = null" class="sidebar-icon-close" name="widget-close"
|
||||||
|
size="18" color="#fff" />
|
||||||
|
<img :src="$conf.serviceHost + sidebarForm.icon" alt="" />
|
||||||
|
</div>
|
||||||
|
</Upload>
|
||||||
|
</Form-Item>
|
||||||
|
<Form-Item label="排序号:" prop="orderNum">
|
||||||
|
<InputNumber style="width: 120px" v-model="sidebarForm.orderNum" :min="1" placeholder="请输入排序号" />
|
||||||
|
</Form-Item>
|
||||||
|
<Form-Item label="是否悬浮底部:" prop="isBottom">
|
||||||
|
<Switch v-model="sidebarForm.isBottom" true-color="#5979ea" />
|
||||||
|
</Form-Item>
|
||||||
|
<Form-Item label="是否系统页面:" prop="isNative">
|
||||||
|
<Switch v-model="sidebarForm.isNative" true-color="#5979ea" />
|
||||||
|
</Form-Item>
|
||||||
|
<Transition name="fade" :duration="300">
|
||||||
|
<div class="ovh" style="height: 34px">
|
||||||
|
<Form-Item v-if="!sidebarForm.isNative" label="打开方式:" prop="linkOpenType">
|
||||||
|
<RadioGroup v-model="sidebarForm.linkOpenType" type="button" button-style="solid">
|
||||||
|
<Radio :label="item.k" v-for="item in linkOpenTypeDict" :key="item.k">{{ item.v }}</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</Form-Item>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
<Transition name="fade" :duration="300">
|
||||||
|
<div class="ovh mt-24" style="height: 35px">
|
||||||
|
<Form-Item v-if="!sidebarForm.isNative && sidebarForm.linkOpenType === LinkOpenTypeEnum.IFrame"
|
||||||
|
label="是否显示头部:" prop="isHeader">
|
||||||
|
<Switch v-model="sidebarForm.isHeader" true-color="#5979ea" />
|
||||||
|
</Form-Item>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Form>
|
||||||
|
<template #footer>
|
||||||
|
<Button type="primary" :loading="sidebarSaving" @click="onSidebarSave">保存</Button>
|
||||||
|
<Button @click="sidebarEditShow = false">取消</Button>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@@ -385,6 +454,10 @@ import SysSetting from "@/views/SysSetting.vue";
|
|||||||
import BaseApi from "@/api/BaseApi";
|
import BaseApi from "@/api/BaseApi";
|
||||||
import SysSettingApi from "@/api/SysSettingApi";
|
import SysSettingApi from "@/api/SysSettingApi";
|
||||||
import UserSettingApi from "@/api/UserSettingApi";
|
import UserSettingApi from "@/api/UserSettingApi";
|
||||||
|
import MenuBarApi from "@/api/MenuBarApi";
|
||||||
|
import SvgIcon from "@/components/SvgIcon.vue";
|
||||||
|
import MyConfirm from "@/components/MyConfirm";
|
||||||
|
import { LinkOpenTypeDict, LinkOpenTypeEnum, RoleTypeDict } from "@/types";
|
||||||
import Utils from "@/utils";
|
import Utils from "@/utils";
|
||||||
import { Message } from "view-ui-plus";
|
import { Message } from "view-ui-plus";
|
||||||
|
|
||||||
@@ -809,14 +882,140 @@ watch(curPane, pane => {
|
|||||||
if (pane === "mytheme" || pane === "schooltheme") loadSchTheme();
|
if (pane === "mytheme" || pane === "schooltheme") loadSchTheme();
|
||||||
});
|
});
|
||||||
|
|
||||||
const sidebarItems = ref([
|
/* ===== sidebar:真实边栏管理(2026-09-30 二轮自 Header MenuBarModal 迁入:接口/字段/校验/直传/确认删除语义照抄) ===== */
|
||||||
{ name: "通知公告", on: true },
|
const sidebarList = ref<any[]>([]);
|
||||||
{ name: "流程中心", on: true },
|
const sidebarLoading = ref(false);
|
||||||
{ name: "教务管理", on: true },
|
const sidebarEditShow = ref(false);
|
||||||
{ name: "公文申请", on: true },
|
const sidebarSaving = ref(false);
|
||||||
{ name: "场地预约", on: false },
|
const refSidebarForm = ref<any>(null);
|
||||||
|
const sidebarForm = reactive({
|
||||||
|
id: null as any,
|
||||||
|
roleIds: [] as number[],
|
||||||
|
name: "",
|
||||||
|
url: "",
|
||||||
|
linkOpenType: 0,
|
||||||
|
icon: "" as string | null,
|
||||||
|
orderNum: null as number | null,
|
||||||
|
isBottom: false,
|
||||||
|
isNative: false,
|
||||||
|
isHeader: false,
|
||||||
|
});
|
||||||
|
const sidebarRules = {
|
||||||
|
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 sidebarAllChecked = computed(() => sidebarForm.roleIds.length == RoleTypeDict.length && RoleTypeDict.length > 0);
|
||||||
|
const sidebarIndeterminate = computed(() => sidebarForm.roleIds.length > 0 && sidebarForm.roleIds.length < RoleTypeDict.length);
|
||||||
|
|
||||||
|
const sidebarColumns = ref([
|
||||||
|
{ title: "名称", key: "name" },
|
||||||
|
{ title: "图标", slot: "icon" },
|
||||||
|
{ title: "角色", width: 150, slot: "roleIds" },
|
||||||
|
{ title: "地址", key: "url", width: 150, tooltip: true },
|
||||||
|
{ title: "打开方式", key: "linkType" },
|
||||||
|
{
|
||||||
|
title: "系统页面",
|
||||||
|
key: "isNative",
|
||||||
|
width: 90,
|
||||||
|
align: "center",
|
||||||
|
render(h: any, params: any) {
|
||||||
|
return h("span", params.row.isNative ? "是" : "否");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ title: "是否显示", width: 90, align: "center", slot: "isShow" },
|
||||||
|
{ title: "排序号", key: "orderNum", width: 80, align: "center" },
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
width: 110,
|
||||||
|
align: "center",
|
||||||
|
render: (h: any, params: any) => {
|
||||||
|
const disabled = !params.row.isEdit,
|
||||||
|
id = params.row.id;
|
||||||
|
return [
|
||||||
|
Utils.operatingTextHandle(h, "编辑", "mr-2", () => onSidebarEditShow(id), disabled),
|
||||||
|
Utils.operatingTextHandle(h, "删除", "text-error", () => onSidebarDelete(id), disabled),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
async function loadSidebarList() {
|
||||||
|
sidebarLoading.value = true;
|
||||||
|
try {
|
||||||
|
const list = await MenuBarApi.getMenuBarSchList();
|
||||||
|
(list || []).forEach((el: any) => {
|
||||||
|
el.linkType = linkOpenTypeDict.value.find(x => x.k == el.linkOpenType)?.v;
|
||||||
|
});
|
||||||
|
sidebarList.value = list || [];
|
||||||
|
} finally {
|
||||||
|
sidebarLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSidebarCheckAll() {
|
||||||
|
sidebarForm.roleIds = !sidebarAllChecked.value ? RoleTypeDict.map(item => item.k as number) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSidebarIconUploaded(response: any) {
|
||||||
|
if (response.code == 0) sidebarForm.icon = response.data.url;
|
||||||
|
else Message.error("上传错误!");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSidebarEditShow(id?: number) {
|
||||||
|
await refSidebarForm.value?.resetFields();
|
||||||
|
sidebarEditShow.value = false;
|
||||||
|
if (!id) {
|
||||||
|
Object.assign(sidebarForm, {
|
||||||
|
id: null, roleIds: [], name: "", url: "", linkOpenType: LinkOpenTypeEnum.Open,
|
||||||
|
icon: "", orderNum: null, isBottom: false, isNative: false, isHeader: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const row = sidebarList.value.find(x => x.id == id);
|
||||||
|
if (row) Object.assign(sidebarForm, row);
|
||||||
|
}
|
||||||
|
sidebarEditShow.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSidebarDelete(id: number) {
|
||||||
|
await MyConfirm({
|
||||||
|
title: "删除",
|
||||||
|
msg: "确定删除该边栏吗?",
|
||||||
|
info: "删除后无法恢复!",
|
||||||
|
async onOk() {
|
||||||
|
await MenuBarApi.deleteMenuBar(id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await loadSidebarList();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSidebarSave() {
|
||||||
|
const valid = await refSidebarForm.value?.validate();
|
||||||
|
if (!valid) return false;
|
||||||
|
sidebarSaving.value = true;
|
||||||
|
try {
|
||||||
|
await MenuBarApi.saveMenuBar(sidebarForm);
|
||||||
|
Message.success("操作成功!");
|
||||||
|
await loadSidebarList();
|
||||||
|
sidebarEditShow.value = false;
|
||||||
|
} catch (error) {
|
||||||
|
Message.error("操作失败!");
|
||||||
|
} finally {
|
||||||
|
sidebarSaving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSidebarShowChange(id: number) {
|
||||||
|
MenuBarApi.toggleMenuBarShow(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开边栏 pane 时拉真实列表
|
||||||
|
watch(curPane, pane => {
|
||||||
|
if (pane === "sidebar" && isAdmin.value) loadSidebarList();
|
||||||
|
});
|
||||||
|
|
||||||
const statChecks = ref([
|
const statChecks = ref([
|
||||||
{ name: "应用访问量", on: true },
|
{ name: "应用访问量", on: true },
|
||||||
{ name: "消息总量", on: true },
|
{ name: "消息总量", on: true },
|
||||||
@@ -1385,6 +1584,56 @@ function openBackend() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 边栏管理(2026-09-30 二轮自 MenuBarModal 迁入) */
|
||||||
|
.side-toolbar {
|
||||||
|
padding: 2px 0 10px;
|
||||||
|
}
|
||||||
|
.sidebar-table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.sidebar-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.sidebar-icon-upload {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px dashed #dcdee0;
|
||||||
|
background-color: #fafafa;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.sidebar-icon-preview {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #eef0f3;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
img {
|
||||||
|
max-width: 70%;
|
||||||
|
max-height: 70%;
|
||||||
|
}
|
||||||
|
.sidebar-icon-close {
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
right: -8px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
padding: 2px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 统计设置:勾选项 */
|
/* 统计设置:勾选项 */
|
||||||
.check-list {
|
.check-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user