fix: 修复文件编码问题
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 悬浮按钮 -->
|
<!-- 悬浮按钮 -->
|
||||||
<div class="ai-assistant">
|
<div class="ai-assistant">
|
||||||
<!-- AI 助手图标按钮 -->
|
<!-- AI 助手图标按钮 -->
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="rounded bg-[var(--el-bg-color)] border border-[var(--el-border-color)] p-5 h-full md:flex flex-1 flex-col md:overflow-auto"
|
class="rounded bg-[var(--el-bg-color)] border border-[var(--el-border-color)] p-5 h-full md:flex flex-1 flex-col md:overflow-auto"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- drawer -->
|
<!-- drawer -->
|
||||||
<template v-if="modalConfig.component === 'drawer'">
|
<template v-if="modalConfig.component === 'drawer'">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 自定义æ<EFBFBD>’æ§?-->
|
<!-- 自定义插槽 -->
|
||||||
<slot
|
<slot
|
||||||
v-if="item.type === 'custom'"
|
v-if="item.type === 'custom'"
|
||||||
:name="item.slotName"
|
:name="item.slotName"
|
||||||
@@ -71,14 +71,14 @@ import { ArrowUp, ArrowDown } from "@element-plus/icons-vue";
|
|||||||
import type { FormInstance } from "element-plus";
|
import type { FormInstance } from "element-plus";
|
||||||
import InputTag from "@/components/InputTag/index.vue";
|
import InputTag from "@/components/InputTag/index.vue";
|
||||||
|
|
||||||
// 定义接收的属�
|
// 定义接收的属性
|
||||||
const props = defineProps<{ searchConfig: ISearchConfig }>();
|
const props = defineProps<{ searchConfig: ISearchConfig }>();
|
||||||
// 自定义事�
|
// 自定义事件
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
queryClick: [queryParams: IObject];
|
queryClick: [queryParams: IObject];
|
||||||
resetClick: [queryParams: IObject];
|
resetClick: [queryParams: IObject];
|
||||||
}>();
|
}>();
|
||||||
// ç»„ä»¶æ˜ å°„è¡?
|
// 组件映射表
|
||||||
const componentMap = new Map<ISearchComponent, any>([
|
const componentMap = new Map<ISearchComponent, any>([
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
["input", markRaw(ElInput)], // @ts-ignore
|
["input", markRaw(ElInput)], // @ts-ignore
|
||||||
@@ -105,7 +105,7 @@ const formItems = reactive(props.searchConfig?.formItems ?? []);
|
|||||||
const isExpandable = ref(props.searchConfig?.isExpandable ?? true);
|
const isExpandable = ref(props.searchConfig?.isExpandable ?? true);
|
||||||
// 是否已展开
|
// 是否已展开
|
||||||
const isExpand = ref(false);
|
const isExpand = ref(false);
|
||||||
// 表å<EFBFBD>•项展示数é‡<EFBFBD>,若å<EFBFBD>¯å±•开,超出展示数é‡<EFBFBD>的表å<EFBFBD>•项éš<EFBFBD>è—?
|
// 表单项展示数量,若可展开,超出展示数量的表单项隐藏
|
||||||
const showNumber = computed(() =>
|
const showNumber = computed(() =>
|
||||||
isExpandable.value ? (props.searchConfig?.showNumber ?? 3) : formItems.length
|
isExpandable.value ? (props.searchConfig?.showNumber ?? 3) : formItems.length
|
||||||
);
|
);
|
||||||
@@ -113,7 +113,7 @@ const showNumber = computed(() =>
|
|||||||
const cardAttrs = computed<IObject>(() => {
|
const cardAttrs = computed<IObject>(() => {
|
||||||
return { shadow: "never", style: { "margin-bottom": "12px" }, ...props.searchConfig?.cardAttrs };
|
return { shadow: "never", style: { "margin-bottom": "12px" }, ...props.searchConfig?.cardAttrs };
|
||||||
});
|
});
|
||||||
// 表å<EFBFBD>•组件自定义属性(labelä½<EFBFBD>ç½®ã€<EFBFBD>宽度ã€<EFBFBD>对é½<EFBFBD>æ–¹å¼<EFBFBD>ç‰ï¼?
|
// 表单组件自定义属性(label位置、宽度、对齐方式等)
|
||||||
const formAttrs = computed<IForm>(() => {
|
const formAttrs = computed<IForm>(() => {
|
||||||
return { inline: true, ...props.searchConfig?.form };
|
return { inline: true, ...props.searchConfig?.form };
|
||||||
});
|
});
|
||||||
@@ -124,7 +124,7 @@ const isGrid = computed(() =>
|
|||||||
: "flex flex-wrap gap-x-8 gap-y-4"
|
: "flex flex-wrap gap-x-8 gap-y-4"
|
||||||
);
|
);
|
||||||
|
|
||||||
// 获å<EFBFBD>–tooltipæ<EFBFBD><EFBFBD>示框属æ€?
|
// 获取tooltip提示框属性
|
||||||
const getTooltipProps = (tips: string | IObject) => {
|
const getTooltipProps = (tips: string | IObject) => {
|
||||||
return typeof tips === "string" ? { content: tips } : tips;
|
return typeof tips === "string" ? { content: tips } : tips;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-select
|
<el-select
|
||||||
v-if="type === 'select'"
|
v-if="type === 'select'"
|
||||||
v-model="selectedValue"
|
v-model="selectedValue"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<!--
|
<!--
|
||||||
* 基于 ECharts �Vue3 图表组件
|
* 基于 ECharts 的 Vue3 图表组件
|
||||||
* 版æ<EFBFBD>ƒæ‰€æœ?© 2021-present 有æ<EFBFBD>¥å¼€æº<EFBFBD>组ç»?
|
* 版权所有 © 2021-present 有来开源组织
|
||||||
*
|
*
|
||||||
* 开源协议:https://opensource.org/licenses/MIT
|
* 开源协议:https://opensource.org/licenses/MIT
|
||||||
* 项目地址:https://gitee.com/youlaiorg/vue3-element-admin
|
* 项目地址:https://gitee.com/youlaiorg/vue3-element-admin
|
||||||
* 参考:https://echarts.apache.org/handbook/zh/basics/import/#%E6%8C%89%E9%9C%80%E5%BC%95%E5%85%A5-echarts-%E5%9B%BE%E8%A1%A8%E5%92%8C%E7%BB%84%E4%BB%B6
|
* 参考:https://echarts.apache.org/handbook/zh/basics/import/#%E6%8C%89%E9%9C%80%E5%BC%95%E5%85%A5-echarts-%E5%9B%BE%E8%A1%A8%E5%92%8C%E7%BB%84%E4%BB%B6
|
||||||
*
|
*
|
||||||
* 在使用时,请ä¿<EFBFBD>ç•™æ¤æ³¨é‡Šï¼Œæ„Ÿè°¢æ‚¨å¯¹å¼€æº<EFBFBD>的支æŒ<EFBFBD>ï¼?
|
* 在使用时,请保留此注释,感谢您对开源的支持!
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -14,13 +14,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// 引入 echarts æ ¸å¿ƒæ¨¡å<EFBFBD>—ï¼Œæ ¸å¿ƒæ¨¡å<EFBFBD>—æ<EFBFBD><EFBFBD>供了 echarts 使用必须è¦<C3A8>的接å<C2A5>£ã€?
|
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
|
||||||
import * as echarts from "echarts/core";
|
import * as echarts from "echarts/core";
|
||||||
// 引入柱状、折线和饼图常用图表
|
// 引入柱状、折线和饼图常用图表
|
||||||
import { BarChart, LineChart, PieChart } from "echarts/charts";
|
import { BarChart, LineChart, PieChart } from "echarts/charts";
|
||||||
// å¼•å…¥æ ‡é¢˜ï¼Œæ<EFBFBD><EFBFBD>示框,直角å<EFBFBD><EFBFBD>æ ‡ç³»ï¼Œæ•°æ<EFBFBD>®é›†ï¼Œå†…置数æ<EFBFBD>®è½¬æ<EFBFBD>¢å™¨ç»„ä»¶ï¼?
|
// 引入标题,提示框,直角坐标系,数据集,内置数据转换器组件,
|
||||||
import { GridComponent, TooltipComponent, LegendComponent } from "echarts/components";
|
import { GridComponent, TooltipComponent, LegendComponent } from "echarts/components";
|
||||||
// 引入 Canvas 渲染器,注æ„<EFBFBD>引入 CanvasRenderer 或è€?SVGRenderer 是必须的一æ?
|
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
|
||||||
import { CanvasRenderer } from "echarts/renderers";
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
|
|
||||||
import { useResizeObserver } from "@vueuse/core";
|
import { useResizeObserver } from "@vueuse/core";
|
||||||
@@ -45,7 +45,7 @@ const props = defineProps<{
|
|||||||
const chartRef = ref<HTMLDivElement | null>(null);
|
const chartRef = ref<HTMLDivElement | null>(null);
|
||||||
let chartInstance: echarts.ECharts | null = null;
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
// åˆ<EFBFBD>始化图è¡?
|
// 初始化图表
|
||||||
const initChart = () => {
|
const initChart = () => {
|
||||||
if (chartRef.value) {
|
if (chartRef.value) {
|
||||||
chartInstance = echarts.init(chartRef.value);
|
chartInstance = echarts.init(chartRef.value);
|
||||||
@@ -55,12 +55,12 @@ const initChart = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监å<EFBFBD>¬å°ºå¯¸å<EFBFBD>˜åŒ–,自动调æ•?
|
// 监听尺寸变化,自动调整
|
||||||
useResizeObserver(chartRef, () => {
|
useResizeObserver(chartRef, () => {
|
||||||
chartInstance?.resize();
|
chartInstance?.resize();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监å<EFBFBD>¬ options å<EFBFBD>˜åŒ–,更新图è¡?
|
// 监听 options 变化,更新图表
|
||||||
watch(
|
watch(
|
||||||
() => props.options,
|
() => props.options,
|
||||||
(newOptions) => {
|
(newOptions) => {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const hamburgerClass = computed(() => {
|
|||||||
return "hamburger--white";
|
return "hamburger--white";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是混å<EFBFBD>ˆå¸ƒå±€ && ä¾§è¾¹æ <C3A6>é…<C3A9>色方案是ç»<C3A7>å…¸è“?
|
// 如果是混合布局 && 侧边栏配色方案是经典蓝
|
||||||
if (
|
if (
|
||||||
layout.value === LayoutMode.MIX &&
|
layout.value === LayoutMode.MIX &&
|
||||||
settingsStore.sidebarColorScheme === SidebarColor.CLASSIC_BLUE
|
settingsStore.sidebarColorScheme === SidebarColor.CLASSIC_BLUE
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ onClickOutside(iconSelectRef, () => (popoverVisible.value = false), {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清空已选图æ ?
|
* 清空已选图标
|
||||||
*/
|
*/
|
||||||
function clearSelectedIcon() {
|
function clearSelectedIcon() {
|
||||||
selectedIcon.value = "";
|
selectedIcon.value = "";
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
<!--
|
<!--
|
||||||
* 基于 wangEditor-next 的富文本编辑器组件二次å°<EFBFBD>è£?
|
* 基于 wangEditor-next 的富文本编辑器组件二次封装
|
||||||
* 版æ<EFBFBD>ƒæ‰€æœ?© 2021-present 有æ<EFBFBD>¥å¼€æº<EFBFBD>组ç»?
|
* 版权所属 © 2021-present 有来开源组织
|
||||||
*
|
*
|
||||||
* 开源协议:https://opensource.org/licenses/MIT
|
* 开源协议:https://opensource.org/licenses/MIT
|
||||||
* 项目地址:https://gitee.com/youlaiorg/vue3-element-admin
|
* 项目地址:https://gitee.com/youlaiorg/vue3-element-admin
|
||||||
*
|
*
|
||||||
* 在使用时,请ä¿<EFBFBD>ç•™æ¤æ³¨é‡Šï¼Œæ„Ÿè°¢æ‚¨å¯¹å¼€æº<EFBFBD>的支æŒ<EFBFBD>ï¼?
|
* 在使用时,请保留此注释,感谢您对开源的支持
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div style="z-index: 999; border: 1px solid var(--el-border-color)">
|
<div style="z-index: 999; border: 1px solid var(--el-border-color)">
|
||||||
<!-- 工具æ ?-->
|
<!-- 工具栏 -->
|
||||||
<Toolbar
|
<Toolbar
|
||||||
:editor="editorRef"
|
:editor="editorRef"
|
||||||
mode="simple"
|
mode="simple"
|
||||||
:default-config="toolbarConfig"
|
:default-config="toolbarConfig"
|
||||||
style="border-bottom: 1px solid var(--el-border-color)"
|
style="border-bottom: 1px solid var(--el-border-color)"
|
||||||
/>
|
/>
|
||||||
<!-- 编辑�-->
|
<!-- 编辑器 -->
|
||||||
<Editor
|
<Editor
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
:style="{ height: height, overflowY: 'hidden' }"
|
:style="{ height: height, overflowY: 'hidden' }"
|
||||||
@@ -51,15 +51,15 @@ const modelValue = defineModel("modelValue", {
|
|||||||
required: false,
|
required: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 编辑器实例,必须ç”?shallowRef,é‡<C3A9>è¦<C3A8>ï¼<C3AF>
|
// 编辑器实例,必须用 shallowRef,重要!
|
||||||
const editorRef = shallowRef();
|
const editorRef = shallowRef();
|
||||||
|
|
||||||
// 工具æ <EFBFBD>é…<EFBFBD>ç½?
|
// 工具栏配置
|
||||||
const toolbarConfig = ref<Partial<IToolbarConfig>>({});
|
const toolbarConfig = ref<Partial<IToolbarConfig>>({});
|
||||||
|
|
||||||
// 编辑器é…<EFBFBD>ç½?
|
// 编辑器配置
|
||||||
const editorConfig = ref<Partial<IEditorConfig>>({
|
const editorConfig = ref<Partial<IEditorConfig>>({
|
||||||
placeholder: "请输入内�..",
|
placeholder: "请输入内容..",
|
||||||
MENU_CONF: {
|
MENU_CONF: {
|
||||||
uploadImage: {
|
uploadImage: {
|
||||||
customUpload(file: File, insertFn: InsertFnType) {
|
customUpload(file: File, insertFn: InsertFnType) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<!-- 左侧è<EFBFBD>œå<EFBFBD>•æ ?-->
|
<!-- 左侧菜单 -->
|
||||||
<div class="layout__sidebar" :class="{ 'layout__sidebar--collapsed': !isSidebarOpen }">
|
<div class="layout__sidebar" :class="{ 'layout__sidebar--collapsed': !isSidebarOpen }">
|
||||||
<div :class="{ 'has-logo': showLogo }" class="layout-sidebar">
|
<div :class="{ 'has-logo': showLogo }" class="layout-sidebar">
|
||||||
<LayoutLogo v-if="showLogo" :collapse="!isSidebarOpen" />
|
<LayoutLogo v-if="showLogo" :collapse="!isSidebarOpen" />
|
||||||
@@ -91,7 +91,7 @@ const { showTagsView, showLogo, isSidebarOpen, routes } = useLayout();
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ç§»åŠ¨ç«¯æ ·å¼?*/
|
/* 移动端样式*/
|
||||||
.mobile {
|
.mobile {
|
||||||
.layout__sidebar {
|
.layout__sidebar {
|
||||||
width: $sidebar-width !important;
|
width: $sidebar-width !important;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer
|
<el-drawer
|
||||||
v-model="drawerVisible"
|
v-model="drawerVisible"
|
||||||
size="380"
|
size="380"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="!item.meta || !item.meta.hidden">
|
<div v-if="!item.meta || !item.meta.hidden">
|
||||||
<!--【叶子节点】显示叶子节点或唯一子节点且父节点未配置始终显示 -->
|
<!--【叶子节点】显示叶子节点或唯一子节点且父节点未配置始终显示 -->
|
||||||
<template
|
<template
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tags-container">
|
<div class="tags-container">
|
||||||
<!-- 水平滚动容器 -->
|
<!-- 水平滚动容器 -->
|
||||||
<el-scrollbar
|
<el-scrollbar
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ export interface LogItem {
|
|||||||
/** 日志内容 */
|
/** 日志内容 */
|
||||||
content: string;
|
content: string;
|
||||||
/** 请求路径 */
|
/** 请求路径 */
|
||||||
requestUri: string;
|
requestUri?: string;
|
||||||
/** 请求方法 */
|
/** 请求方法 */
|
||||||
method: string;
|
method?: string;
|
||||||
/** IP地址 */
|
/** IP地址 */
|
||||||
ip: string;
|
ip: string;
|
||||||
/** 地区 */
|
/** 地区 */
|
||||||
@@ -34,6 +34,10 @@ export interface LogItem {
|
|||||||
os: string;
|
os: string;
|
||||||
/** 执行时间(毫秒) */
|
/** 执行时间(毫秒) */
|
||||||
executionTime: number;
|
executionTime: number;
|
||||||
|
/** 创建人ID */
|
||||||
|
createBy?: string;
|
||||||
|
/** 操作时间 */
|
||||||
|
createTime?: string;
|
||||||
/** 操作人 */
|
/** 操作人 */
|
||||||
operator: string;
|
operator: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ export interface NoticeForm {
|
|||||||
type?: number;
|
type?: number;
|
||||||
/** 通知等级 */
|
/** 通知等级 */
|
||||||
level?: string;
|
level?: string;
|
||||||
/** 发布状态(0:草稿;1:已发布;2:已撤回) */
|
/** 发布状态(0:草稿;1:已发布;-1:已撤回) */
|
||||||
publishStatus?: number;
|
status?: number;
|
||||||
/** 目标用户ID(多个以英文逗号(,)分割) */
|
/** 目标用户ID列表 */
|
||||||
targetUserIds?: string;
|
targetUsers?: number[];
|
||||||
/** 目标类型 (1:全部,2:指定用户等) */
|
/** 目标类型 (1:全部,2:指定用户等) */
|
||||||
targetType?: number;
|
targetType?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const http = axios.create({
|
|||||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||||
timeout: 50000,
|
timeout: 50000,
|
||||||
headers: { "Content-Type": "application/json;charset=utf-8" },
|
headers: { "Content-Type": "application/json;charset=utf-8" },
|
||||||
paramsSerializer: (params) => qs.stringify(params),
|
paramsSerializer: (params) => qs.stringify(params, { arrayFormat: "repeat" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -54,12 +54,6 @@ http.interceptors.response.use(
|
|||||||
if (page != null) return { data, page };
|
if (page != null) return { data, page };
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 需要选择租户(特殊业务码,传递给调用方处理)
|
|
||||||
if (code === ApiCodeEnum.CHOOSE_TENANT) {
|
|
||||||
return Promise.reject({ code, data, msg });
|
|
||||||
}
|
|
||||||
|
|
||||||
ElMessage.error(msg || "系统出错");
|
ElMessage.error(msg || "系统出错");
|
||||||
return Promise.reject(new Error(msg || "Error"));
|
return Promise.reject(new Error(msg || "Error"));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
class="mb-[20px]"
|
class="mb-[20px]"
|
||||||
>
|
>
|
||||||
示例æº<EFBFBD>ç <EFBFBD> 请点å‡?>>>
|
示例源码 请点击>>>
|
||||||
</el-link>
|
</el-link>
|
||||||
<el-form>
|
<el-form>
|
||||||
<el-form-item label="性别">
|
<el-form-item label="性别">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- å›¾æ ‡é€‰æ‹©å™¨ç¤ºä¾?-->
|
<!-- 图标选择器示例 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<el-link
|
<el-link
|
||||||
@@ -7,15 +7,15 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
class="mb-10"
|
class="mb-10"
|
||||||
>
|
>
|
||||||
示例æº<EFBFBD>ç <EFBFBD> 请点å‡?>>>
|
示例源码 请点击>>>
|
||||||
</el-link>
|
</el-link>
|
||||||
<icon-select v-model="iconName" />
|
<icon-select v-model="iconName" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// element-plus å›¾æ ‡æ ¼å¼<EFBFBD>以el-icon-å¼€å¤?
|
// element-plus 图标格式以el-icon-开头
|
||||||
const iconName = ref("el-icon-edit");
|
const iconName = ref("el-icon-edit");
|
||||||
// 本地SVGå›¾æ ‡æ ¼å¼<EFBFBD>å<EFBFBD>?src/assets/icons 下的文件å<C2B6><C3A5>,ä¸<C3A4>需è¦<C3A8>svgå<67>Žç¼€
|
// 本地SVG图标格式为src/assets/icons 下的文件名,不需要svg后缀
|
||||||
// const iconName = ref("api");
|
// const iconName = ref("api");
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- 列表选择器示�-->
|
<!-- 列表选择器示例 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<el-link
|
<el-link
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
class="mb-10"
|
class="mb-10"
|
||||||
>
|
>
|
||||||
示例æº<EFBFBD>ç <EFBFBD> 请点å‡?>>>
|
示例源码 请点击>>>
|
||||||
</el-link>
|
</el-link>
|
||||||
<table-select :text="text" :select-config="selectConfig" @confirm-click="handleConfirm">
|
<table-select :text="text" :select-config="selectConfig" @confirm-click="handleConfirm">
|
||||||
<template #status="scope">
|
<template #status="scope">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
class="mb-10"
|
class="mb-10"
|
||||||
>
|
>
|
||||||
示例源码 请点<EFBFBD><EFBFBD>?>>>
|
示例源码 请点击>>>
|
||||||
</el-link>
|
</el-link>
|
||||||
|
|
||||||
<el-form>
|
<el-form>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<!-- 搜索区域 -->
|
<!-- 搜索区域 -->
|
||||||
<div class="search-container">
|
<div class="filter-section">
|
||||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="auto">
|
||||||
<el-form-item prop="keywords" label="关键字">
|
<el-form-item prop="keywords" label="关键字">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.keywords"
|
v-model="queryParams.keywords"
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
start-placeholder="开始时间"
|
start-placeholder="开始时间"
|
||||||
end-placeholder="截止时间"
|
end-placeholder="截止时间"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
style="width: 200px"
|
style="width: 260px"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
border
|
border
|
||||||
class="data-table__content"
|
class="data-table__content"
|
||||||
>
|
>
|
||||||
<el-table-column label="操作时间" prop="createTime" width="180" />
|
<el-table-column label="操作时间" prop="createTime" width="220" />
|
||||||
<el-table-column label="操作人" prop="operator" width="120" />
|
<el-table-column label="操作人" prop="operator" width="120" />
|
||||||
<el-table-column label="日志模块" prop="module" width="100" />
|
<el-table-column label="日志模块" prop="module" width="100" />
|
||||||
<el-table-column label="日志内容" prop="content" min-width="200" />
|
<el-table-column label="日志内容" prop="content" min-width="200" />
|
||||||
@@ -80,7 +80,7 @@ const queryParams = reactive<LogQueryParams>({
|
|||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
keywords: "",
|
keywords: "",
|
||||||
createTime: ["", ""],
|
createTime: undefined as [string, string] | undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 日志表格数据
|
// 日志表格数据
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- 搜索区域 -->
|
<!-- 搜索区域 -->
|
||||||
<div class="filter-section">
|
<div class="filter-section">
|
||||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-suffix=":">
|
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-suffix=":">
|
||||||
<el-form-item label="标题123" prop="title">
|
<el-form-item label="标题" prop="title">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.title"
|
v-model="queryParams.title"
|
||||||
placeholder="标题"
|
placeholder="标题"
|
||||||
@@ -190,8 +190,8 @@
|
|||||||
<el-radio :value="2">指定</el-radio>
|
<el-radio :value="2">指定</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="formData.targetType == 2" label="指定用户" prop="targetUserIds">
|
<el-form-item v-if="formData.targetType == 2" label="指定用户" prop="targetUsers">
|
||||||
<el-select v-model="formData.targetUserIds" multiple search placeholder="请选择指定用户">
|
<el-select v-model="formData.targetUsers" multiple search placeholder="请选择指定用户">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in userOptions"
|
v-for="item in userOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
@@ -359,10 +359,14 @@ function handleOpenDialog(id?: string) {
|
|||||||
if (id) {
|
if (id) {
|
||||||
dialog.title = "修改公告";
|
dialog.title = "修改公告";
|
||||||
NoticeAPI.getFormData(id).then((data) => {
|
NoticeAPI.getFormData(id).then((data) => {
|
||||||
Object.assign(formData, data);
|
const normalized = {
|
||||||
|
...data,
|
||||||
|
targetUsers: normalizeTargetUsers(data?.targetUsers),
|
||||||
|
};
|
||||||
|
Object.assign(formData, normalized);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Object.assign(formData, { level: 0, targetType: 0 });
|
Object.assign(formData, { level: "L", targetType: 1, targetUsers: [] });
|
||||||
dialog.title = "新增公告";
|
dialog.title = "新增公告";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,6 +392,9 @@ function handleSubmit() {
|
|||||||
dataFormRef.value.validate((valid: any) => {
|
dataFormRef.value.validate((valid: any) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
if (formData.targetType !== 2) {
|
||||||
|
formData.targetUsers = [];
|
||||||
|
}
|
||||||
const id = formData.id;
|
const id = formData.id;
|
||||||
if (id) {
|
if (id) {
|
||||||
NoticeAPI.update(id, formData)
|
NoticeAPI.update(id, formData)
|
||||||
@@ -416,6 +423,25 @@ function resetForm() {
|
|||||||
dataFormRef.value.clearValidate();
|
dataFormRef.value.clearValidate();
|
||||||
formData.id = undefined;
|
formData.id = undefined;
|
||||||
formData.targetType = 1;
|
formData.targetType = 1;
|
||||||
|
formData.targetUsers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeTargetUsers(value?: unknown) {
|
||||||
|
if (!value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (typeof value === "string") {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
return Array.isArray(parsed) ? parsed : value.split(",").filter(Boolean);
|
||||||
|
} catch {
|
||||||
|
return value.split(",").filter(Boolean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭通知公告弹窗
|
// 关闭通知公告弹窗
|
||||||
|
|||||||
@@ -96,14 +96,14 @@
|
|||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
v-if="!isPlatformTenantId(scope.row.id)"
|
v-if="!isPlatformTenantId(scope.row.id)"
|
||||||
v-hasPerm="['sys:tenant:assign']"
|
v-hasPerm="['sys:tenant:plan-assign']"
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
link
|
link
|
||||||
icon="menu"
|
icon="menu"
|
||||||
@click="handleOpenTenantMenuDialog(scope.row)"
|
@click="handleOpenTenantPlanDialog(scope.row)"
|
||||||
>
|
>
|
||||||
租户菜单
|
设置套餐
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
v-hasPerm="['sys:tenant:update']"
|
v-hasPerm="['sys:tenant:update']"
|
||||||
@@ -163,7 +163,11 @@
|
|||||||
<el-input v-model="formData.domain" placeholder="demo.youlai.tech(可选)" />
|
<el-input v-model="formData.domain" placeholder="demo.youlai.tech(可选)" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item v-if="!isPlatformTenant" label="租户套餐" prop="planId">
|
<el-form-item
|
||||||
|
v-if="!isPlatformTenant && (formData.id == null || String(formData.id) === '')"
|
||||||
|
label="租户套餐"
|
||||||
|
prop="planId"
|
||||||
|
>
|
||||||
<el-select v-model="formData.planId" placeholder="请选择租户套餐" style="width: 100%">
|
<el-select v-model="formData.planId" placeholder="请选择租户套餐" style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in planOptions"
|
v-for="item in planOptions"
|
||||||
@@ -228,13 +232,39 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 方案菜单配置 -->
|
<!-- 租户套餐设置 -->
|
||||||
<el-drawer
|
<el-drawer
|
||||||
v-model="tenantMenuDialogVisible"
|
v-model="tenantPlanDialogVisible"
|
||||||
:title="'【' + checkedTenant.name + '】租户菜单配置'"
|
:title="'【' + checkedTenant.name + '】设置套餐'"
|
||||||
size="600px"
|
size="640px"
|
||||||
@close="handleCloseTenantMenuDialog"
|
@close="handleCloseTenantPlanDialog"
|
||||||
>
|
>
|
||||||
|
<el-form label-width="90px" class="mb-3">
|
||||||
|
<el-form-item label="租户套餐">
|
||||||
|
<el-select
|
||||||
|
v-model="tenantPlanId"
|
||||||
|
placeholder="请选择租户套餐"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="handlePlanChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in planOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
type="info"
|
||||||
|
show-icon
|
||||||
|
:closable="false"
|
||||||
|
title="默认展示套餐菜单,如需微调请开启自定义"
|
||||||
|
class="mb-3"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="flex-x-between">
|
<div class="flex-x-between">
|
||||||
<el-input v-model="menuKeywords" clearable class="w-[150px]" placeholder="菜单名称">
|
<el-input v-model="menuKeywords" clearable class="w-[150px]" placeholder="菜单名称">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
@@ -249,14 +279,25 @@
|
|||||||
</template>
|
</template>
|
||||||
{{ menuExpanded ? "收缩" : "展开" }}
|
{{ menuExpanded ? "收缩" : "展开" }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-checkbox v-model="menuParentChildLinked" class="ml-5" @change="handleMenuLinkChange">
|
<el-checkbox
|
||||||
|
v-model="menuParentChildLinked"
|
||||||
|
class="ml-5"
|
||||||
|
:disabled="!menuCustomizeEnabled"
|
||||||
|
@change="handleMenuLinkChange"
|
||||||
|
>
|
||||||
父子联动
|
父子联动
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
|
<el-switch
|
||||||
|
v-model="menuCustomizeEnabled"
|
||||||
|
class="ml-5"
|
||||||
|
inline-prompt
|
||||||
|
active-text="自定义"
|
||||||
|
inactive-text="默认"
|
||||||
|
:disabled="!hasPermTenantMenu"
|
||||||
|
@change="handleCustomizeToggle"
|
||||||
|
/>
|
||||||
<el-tooltip placement="bottom">
|
<el-tooltip placement="bottom">
|
||||||
<template #content>
|
<template #content>开启自定义后可覆盖套餐菜单;关闭则仅使用套餐默认菜单</template>
|
||||||
如果只需勾选菜单权限,不需要勾选子菜单或者按钮权限,请关闭父子联动
|
|
||||||
</template>
|
|
||||||
<el-icon class="ml-1 color-[--el-color-primary] inline-block cursor-pointer">
|
<el-icon class="ml-1 color-[--el-color-primary] inline-block cursor-pointer">
|
||||||
<QuestionFilled />
|
<QuestionFilled />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
@@ -268,6 +309,7 @@
|
|||||||
ref="menuTreeRef"
|
ref="menuTreeRef"
|
||||||
node-key="value"
|
node-key="value"
|
||||||
show-checkbox
|
show-checkbox
|
||||||
|
:props="menuTreeProps"
|
||||||
:data="menuPermOptions"
|
:data="menuPermOptions"
|
||||||
:filter-node-method="handleMenuFilter"
|
:filter-node-method="handleMenuFilter"
|
||||||
:default-expand-all="true"
|
:default-expand-all="true"
|
||||||
@@ -282,13 +324,13 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button
|
<el-button
|
||||||
v-hasPerm="['sys:tenant:assign']"
|
v-hasPerm="['sys:tenant:update']"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleTenantMenuSubmit"
|
@click="handleTenantPlanSubmit"
|
||||||
>
|
>
|
||||||
确定
|
保存设置
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="tenantMenuDialogVisible = false">取消</el-button>
|
<el-button @click="tenantPlanDialogVisible = false">取消</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
@@ -341,11 +383,23 @@ const dialog = reactive({
|
|||||||
visible: false,
|
visible: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const tenantMenuDialogVisible = ref(false);
|
const tenantPlanDialogVisible = ref(false);
|
||||||
const checkedTenant = ref<{ id?: number; name?: string; planId?: number }>({});
|
const checkedTenant = ref<{ id?: number; name?: string; planId?: number }>({});
|
||||||
|
const checkedTenantForm = ref<TenantForm | null>(null);
|
||||||
|
const tenantPlanId = ref<number | undefined>();
|
||||||
|
const menuCustomizeEnabled = ref(false);
|
||||||
|
const planMenuIds = ref<number[]>([]);
|
||||||
|
const tenantMenuIds = ref<number[]>([]);
|
||||||
const menuKeywords = ref("");
|
const menuKeywords = ref("");
|
||||||
const menuExpanded = ref(true);
|
const menuExpanded = ref(true);
|
||||||
const menuParentChildLinked = ref(true);
|
const menuParentChildLinked = ref(true);
|
||||||
|
const menuSourceOptions = ref<OptionItem[]>([]);
|
||||||
|
|
||||||
|
const menuTreeProps = {
|
||||||
|
children: "children",
|
||||||
|
label: "label",
|
||||||
|
disabled: "disabled",
|
||||||
|
};
|
||||||
|
|
||||||
const planOptions = ref<OptionItem[]>([]);
|
const planOptions = ref<OptionItem[]>([]);
|
||||||
|
|
||||||
@@ -374,9 +428,10 @@ const rules = reactive({
|
|||||||
code: [{ required: true, message: "请输入租户编码", trigger: "blur" }],
|
code: [{ required: true, message: "请输入租户编码", trigger: "blur" }],
|
||||||
planId: [
|
planId: [
|
||||||
{
|
{
|
||||||
// 平台租户不绑定套餐
|
// 平台租户不绑定套餐,仅创建时校验
|
||||||
validator: (_: unknown, value: number | undefined, callback: (error?: Error) => void) => {
|
validator: (_: unknown, value: number | undefined, callback: (error?: Error) => void) => {
|
||||||
if (isPlatformTenant.value) return callback();
|
if (isPlatformTenant.value) return callback();
|
||||||
|
if (formData.id != null && String(formData.id) !== "") return callback();
|
||||||
if (value == null) return callback(new Error("请选择租户套餐"));
|
if (value == null) return callback(new Error("请选择租户套餐"));
|
||||||
return callback();
|
return callback();
|
||||||
},
|
},
|
||||||
@@ -386,6 +441,7 @@ const rules = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const hasPermChangeStatus = computed(() => hasPerm("sys:tenant:change-status"));
|
const hasPermChangeStatus = computed(() => hasPerm("sys:tenant:change-status"));
|
||||||
|
const hasPermTenantMenu = computed(() => hasPerm("sys:tenant:plan-assign"));
|
||||||
|
|
||||||
function handleStatusChange(tenantId: string | number | undefined, val: string | number | boolean) {
|
function handleStatusChange(tenantId: string | number | undefined, val: string | number | boolean) {
|
||||||
if (tenantId == null) return;
|
if (tenantId == null) return;
|
||||||
@@ -415,60 +471,54 @@ function fetchData() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleOpenTenantMenuDialog(row: TenantItem) {
|
async function handleOpenTenantPlanDialog(row: TenantItem) {
|
||||||
const tenantId = row.id;
|
const tenantId = row.id;
|
||||||
if (tenantId == null || tenantId === "") return;
|
if (tenantId == null || tenantId === "") return;
|
||||||
if (isPlatformTenantId(tenantId)) {
|
if (isPlatformTenantId(tenantId)) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
const planId = row.planId;
|
|
||||||
if (!planId) {
|
|
||||||
ElMessage.warning("请先为租户选择套餐");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tenantMenuDialogVisible.value = true;
|
tenantPlanDialogVisible.value = true;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
menuCustomizeEnabled.value = false;
|
||||||
|
menuKeywords.value = "";
|
||||||
|
menuExpanded.value = true;
|
||||||
|
menuParentChildLinked.value = true;
|
||||||
|
|
||||||
checkedTenant.value = {
|
checkedTenant.value = {
|
||||||
id: Number(tenantId),
|
id: Number(tenantId),
|
||||||
name: row.name || String(tenantId),
|
name: row.name || String(tenantId),
|
||||||
planId,
|
planId: row.planId != null ? Number(row.planId) : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [menuOptions, planMenuIds, menuIds] = await Promise.all([
|
const [tenantForm, menuOptions, menuIds] = await Promise.all([
|
||||||
|
TenantAPI.getFormData(String(tenantId)),
|
||||||
MenuAPI.getOptions(false, MenuScopeEnum.TENANT),
|
MenuAPI.getOptions(false, MenuScopeEnum.TENANT),
|
||||||
TenantPlanAPI.getPlanMenuIds(planId),
|
hasPermTenantMenu.value ? TenantAPI.getTenantMenuIds(Number(tenantId)) : Promise.resolve([]),
|
||||||
TenantAPI.getTenantMenuIds(Number(tenantId)),
|
|
||||||
]);
|
]);
|
||||||
const normalizedPlanMenuIds = planMenuIds
|
checkedTenantForm.value = tenantForm;
|
||||||
.map((menuId) => Number(menuId))
|
tenantPlanId.value = tenantForm.planId != null ? Number(tenantForm.planId) : undefined;
|
||||||
.filter((menuId) => !Number.isNaN(menuId));
|
menuSourceOptions.value = menuOptions;
|
||||||
const allowedMenuIdSet = new Set(normalizedPlanMenuIds);
|
tenantMenuIds.value = normalizeMenuIds(menuIds);
|
||||||
menuPermOptions.value = allowedMenuIdSet.size
|
await handlePlanChange(tenantPlanId.value);
|
||||||
? filterMenuOptionsByIds(menuOptions, allowedMenuIdSet)
|
|
||||||
: menuOptions;
|
|
||||||
const normalizedMenuIds = menuIds
|
|
||||||
.map((menuId) => Number(menuId))
|
|
||||||
.filter((menuId) => !Number.isNaN(menuId));
|
|
||||||
await nextTick();
|
|
||||||
menuTreeRef.value?.setCheckedKeys([], false);
|
|
||||||
const checkedMenuIds = allowedMenuIdSet.size
|
|
||||||
? normalizedMenuIds.filter((menuId) => allowedMenuIdSet.has(menuId))
|
|
||||||
: normalizedMenuIds;
|
|
||||||
checkedMenuIds.forEach((menuId) => menuTreeRef.value?.setChecked(menuId, true, false));
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCloseTenantMenuDialog() {
|
function handleCloseTenantPlanDialog() {
|
||||||
tenantMenuDialogVisible.value = false;
|
tenantPlanDialogVisible.value = false;
|
||||||
menuKeywords.value = "";
|
menuKeywords.value = "";
|
||||||
menuExpanded.value = true;
|
menuExpanded.value = true;
|
||||||
menuParentChildLinked.value = true;
|
menuParentChildLinked.value = true;
|
||||||
menuTreeRef.value?.setCheckedKeys([], false);
|
menuCustomizeEnabled.value = false;
|
||||||
|
tenantPlanId.value = undefined;
|
||||||
|
planMenuIds.value = [];
|
||||||
|
tenantMenuIds.value = [];
|
||||||
|
menuSourceOptions.value = [];
|
||||||
|
menuPermOptions.value = [];
|
||||||
checkedTenant.value = {};
|
checkedTenant.value = {};
|
||||||
|
checkedTenantForm.value = null;
|
||||||
|
menuTreeRef.value?.setCheckedKeys([], false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMenuTree() {
|
function toggleMenuTree() {
|
||||||
@@ -488,6 +538,51 @@ function handleMenuLinkChange(val: string | number | boolean) {
|
|||||||
menuParentChildLinked.value = Boolean(val);
|
menuParentChildLinked.value = Boolean(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleCustomizeToggle() {
|
||||||
|
menuPermOptions.value = applyMenuOptionsDisabled(
|
||||||
|
menuPermOptions.value,
|
||||||
|
!menuCustomizeEnabled.value
|
||||||
|
);
|
||||||
|
updateCheckedMenus();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePlanChange(planId?: number) {
|
||||||
|
if (!planId) {
|
||||||
|
planMenuIds.value = [];
|
||||||
|
menuPermOptions.value = applyMenuOptionsDisabled(menuSourceOptions.value, true);
|
||||||
|
await nextTick();
|
||||||
|
menuTreeRef.value?.setCheckedKeys([], false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const menuIds = await TenantPlanAPI.getPlanMenuIds(planId);
|
||||||
|
planMenuIds.value = normalizeMenuIds(menuIds);
|
||||||
|
const allowedMenuIdSet = new Set(planMenuIds.value);
|
||||||
|
const filteredOptions = allowedMenuIdSet.size
|
||||||
|
? filterMenuOptionsByIds(menuSourceOptions.value, allowedMenuIdSet)
|
||||||
|
: menuSourceOptions.value;
|
||||||
|
menuPermOptions.value = applyMenuOptionsDisabled(filteredOptions, !menuCustomizeEnabled.value);
|
||||||
|
await nextTick();
|
||||||
|
updateCheckedMenus();
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCheckedMenus() {
|
||||||
|
const allowedMenuIdSet = new Set(planMenuIds.value);
|
||||||
|
const baseCheckedIds =
|
||||||
|
menuCustomizeEnabled.value && tenantMenuIds.value.length > 0
|
||||||
|
? tenantMenuIds.value
|
||||||
|
: planMenuIds.value;
|
||||||
|
const checkedMenuIds = allowedMenuIdSet.size
|
||||||
|
? baseCheckedIds.filter((menuId) => allowedMenuIdSet.has(menuId))
|
||||||
|
: baseCheckedIds;
|
||||||
|
menuTreeRef.value?.setCheckedKeys([], false);
|
||||||
|
checkedMenuIds.forEach((menuId) => menuTreeRef.value?.setChecked(menuId, true, false));
|
||||||
|
}
|
||||||
|
|
||||||
watch(menuKeywords, (val) => {
|
watch(menuKeywords, (val) => {
|
||||||
menuTreeRef.value?.filter(val);
|
menuTreeRef.value?.filter(val);
|
||||||
});
|
});
|
||||||
@@ -516,26 +611,58 @@ function filterMenuOptionsByIds(
|
|||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTenantMenuSubmit() {
|
async function handleTenantPlanSubmit() {
|
||||||
const tenantId = checkedTenant.value.id;
|
const tenantId = checkedTenant.value.id;
|
||||||
if (!tenantId) return;
|
if (!tenantId) return;
|
||||||
|
if (!tenantPlanId.value) {
|
||||||
|
ElMessage.warning("请选择租户套餐");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const checkedMenuIds: number[] = menuTreeRef
|
const tenantForm = checkedTenantForm.value;
|
||||||
.value!.getCheckedNodes(false, true)
|
if (!tenantForm) return;
|
||||||
.map((node: any) => node.value);
|
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
await TenantAPI.updateTenantMenus(tenantId, checkedMenuIds);
|
const payload: TenantForm = {
|
||||||
ElMessage.success("租户菜单配置成功");
|
...tenantForm,
|
||||||
tenantMenuDialogVisible.value = false;
|
planId: tenantPlanId.value,
|
||||||
|
};
|
||||||
|
await TenantAPI.update(String(tenantId), payload);
|
||||||
|
|
||||||
|
if (hasPermTenantMenu.value) {
|
||||||
|
const allowedMenuIdSet = new Set(planMenuIds.value);
|
||||||
|
const menuIds = menuCustomizeEnabled.value
|
||||||
|
? menuTreeRef.value!.getCheckedNodes(false, true).map((node: any) => node.value)
|
||||||
|
: planMenuIds.value;
|
||||||
|
const filteredMenuIds = allowedMenuIdSet.size
|
||||||
|
? menuIds.filter((menuId) => allowedMenuIdSet.has(menuId))
|
||||||
|
: menuIds;
|
||||||
|
await TenantAPI.updateTenantMenus(tenantId, filteredMenuIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success("套餐设置成功");
|
||||||
|
tenantPlanDialogVisible.value = false;
|
||||||
|
fetchData();
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.error("租户菜单配置失败");
|
ElMessage.error("套餐设置失败");
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeMenuIds(menuIds: Array<number | string>) {
|
||||||
|
return menuIds.map((menuId) => Number(menuId)).filter((menuId) => !Number.isNaN(menuId));
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyMenuOptionsDisabled(options: OptionItem[], disabled: boolean): OptionItem[] {
|
||||||
|
return options.map((option) => ({
|
||||||
|
...option,
|
||||||
|
disabled,
|
||||||
|
children: option.children ? applyMenuOptionsDisabled(option.children, disabled) : undefined,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
function handleQuery() {
|
function handleQuery() {
|
||||||
queryParams.pageNum = 1;
|
queryParams.pageNum = 1;
|
||||||
fetchData();
|
fetchData();
|
||||||
|
|||||||
Reference in New Issue
Block a user