diff --git a/.vscode/settings.json b/.vscode/settings.json index 909951ae..441e9d3c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -93,5 +93,6 @@ "yiwen", "zhongyingwen", "zhuti" - ] + ], + "i18n-ally.localesPaths": ["src/languages"] } diff --git a/package.json b/package.json index 25c171d5..cc29d39f 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "echarts": "^5.5.1", "echarts-liquidfill": "^3.1.0", "element-plus": "^2.7.6", + "lodash": "^4.17.21", "md5": "^2.3.0", "mitt": "^3.0.1", "nprogress": "^0.2.0", diff --git a/src/api/modules/login.ts b/src/api/modules/login.ts index 8355cdf6..467ffbe0 100644 --- a/src/api/modules/login.ts +++ b/src/api/modules/login.ts @@ -18,7 +18,7 @@ export const loginApi = (params: Login.ReqLoginForm) => { // 获取菜单列表 export const getAuthMenuListApi = () => { - return http.get(PORT1 + `/menu/list`, {}, { loading: false }); + // return http.get(PORT1 + `/menu/list`, {}, { loading: false }); // 如果想让菜单变为本地数据,注释上一行代码,并引入本地 authMenuList.json 数据 return authMenuList; }; diff --git a/src/assets/json/authMenuList.json b/src/assets/json/authMenuList.json index 4e0f4e2c..bd67ec5f 100644 --- a/src/assets/json/authMenuList.json +++ b/src/assets/json/authMenuList.json @@ -233,6 +233,20 @@ "isKeepAlive": true } }, + { + "path": "/assembly/dialog/useDialog", + "name": "useDialog", + "component": "/assembly/dialog/useDialog/index", + "meta": { + "icon": "Menu", + "title": "useDialog弹窗操作", + "isLink": "", + "isHide": false, + "isFull": false, + "isAffix": false, + "isKeepAlive": true + } + }, { "path": "/assembly/tabs", "name": "tabs", diff --git a/src/hooks/useDialog.ts b/src/hooks/useDialog.ts new file mode 100644 index 00000000..0094288d --- /dev/null +++ b/src/hooks/useDialog.ts @@ -0,0 +1,135 @@ +import { ElDialog } from "element-plus"; +import type { ComponentInternalInstance, Ref } from "vue"; +import { getCurrentInstance, h, isRef, onUnmounted, render } from "vue"; +import { upperFirst } from "lodash"; +import merge from "lodash/merge"; +import type { JSX } from "vue/jsx-runtime"; + +type Content = Parameters[0] | string | Object | JSX.Element; +// 使用 InstanceType 获取 ElDialog 组件实例的类型 +type ElDialogInstance = InstanceType; + +// 从组件实例中提取 Props 类型 +type DialogProps = ElDialogInstance["$props"] & { + onBeforeOpen?: () => boolean | void; // 支持显示之前钩子 +}; + +interface ElDialogSlots { + header?: (...args: any[]) => Content; + footer?: (...args: any[]) => Content; +} + +interface Options

{ + dialogProps?: DialogProps; + dialogSlots?: ElDialogSlots; + contentProps?: P; + callBack?: Function; + closeEventName?: string; +} + +// 定义工具函数,获取计算属性的option +function getOptions

(options?: Ref> | Options

) { + if (!options) return {}; + return isRef(options) ? options.value : options; +} + +export function useDialog

(content: Content, options?: Ref> | Options

) { + let dialogInstance: ComponentInternalInstance | null = null; + let fragment: Element | null = null; + + // 关闭并卸载组件 + const closeAfter = () => { + if (fragment) { + render(null, fragment as unknown as Element); // 卸载组件 + fragment.textContent = ""; // 清空文档片段 + fragment = null; + } + dialogInstance = null; + }; + + // 关闭对话框 + async function closeDialog() { + if (dialogInstance) dialogInstance.props.modelValue = false; + } + + // 获取当前组件实例,用于设置当前dialog的上下文,继承prototype + const instance = getCurrentInstance(); + + // 创建并挂载组件 + function openDialog(modifyOptions?: Partial>) { + if (dialogInstance) { + closeDialog(); + closeAfter(); + } + const _options = getOptions(options); + // 如果有修改,则合并options。替换之前的options变量为 _options + if (modifyOptions) merge(_options, modifyOptions); + const { dialogProps, contentProps } = _options; + + // 调用before钩子,如果为false则不打开 + if (dialogProps?.onBeforeOpen?.() === false) { + return; + } + fragment = document.createDocumentFragment() as unknown as Element; + + // 转换closeEventName事件 + const closeEventName = `on${upperFirst(_options?.closeEventName || "closeDialog")}`; + + // 定义当前块关闭前钩子变量 + let onBeforeClose: (() => Promise | boolean | void) | null; + + const vNode = h( + ElDialog, + { + ...dialogProps, + modelValue: true, + beforeClose: async done => { + // 配置`el-dialog`的关闭回调钩子函数 + const result = await onBeforeClose?.(); + if (result === false) { + return; + } + done(); + }, + onClosed: () => { + dialogProps?.onClosed?.(); + closeAfter(); + // 关闭后回收当前变量 + onBeforeClose = null; + } + }, + { + default: () => [ + typeof content === "string" + ? content + : h(content as any, { + ...contentProps, + [closeEventName]: closeDialog, // 监听自定义关闭事件,并执行关闭 + // 监听自定义回调事件,并执行关闭 + callBack: (data: any) => { + if (_options.callBack) _options.callBack(data); + closeDialog(); + }, + beforeCloseDialog: (fn: () => boolean | void) => { + // 把`beforeCloseDialog`传递给`content`,当组件内部使用`props.beforeCloseDialog(fn)`时,会把fn传递给`onBeforeClose` + onBeforeClose = fn; + } + }) + ], + ..._options.dialogSlots + } + ); + + // 设置当前的上下文为使用者的上下文 + vNode.appContext = instance?.appContext || null; + render(vNode, fragment); + dialogInstance = vNode.component; + document.body.appendChild(fragment); + } + + onUnmounted(() => { + closeDialog(); + }); + + return { openDialog, closeDialog }; +} diff --git a/src/hooks/useECharts.ts b/src/hooks/useECharts.ts new file mode 100644 index 00000000..28461fa7 --- /dev/null +++ b/src/hooks/useECharts.ts @@ -0,0 +1,93 @@ +import * as echarts from "echarts"; +import { onActivated, onDeactivated, onMounted, onUnmounted, reactive, Ref, shallowRef, watch } from "vue"; + +// 定义一个策略接口 , 方便具体策略实现使用 +export interface ChartStrategy { + getOptions: () => echarts.EChartsOption; +} + +// 自定义的 Vue 钩子函数,用于管理 ECharts 实例 +export function useECharts( + chartRef: Ref, // chartRef 是一个引用,指向包含图表的 DOM 元素 + initOptions: echarts.EChartsOption, // strategy 是一个接口,提供获取图表配置的方法 + theme?: string | object | null, // 可选的 theme 参数,用于设置 ECharts 主题 + opts?: echarts.EChartsInitOpts // 可选的 opts 参数,用于初始化 ECharts 的配置 +) { + // 使用 shallowRef 创建一个响应式引用,用于保存 ECharts 实例 + const instance = shallowRef(null); + + // 使用 ref 创建一个响应式引用,用于保存图表的配置选项 + const options = reactive(initOptions); + + // 初始化图表实例的函数 + const initEchart = () => { + // 确保 chartRef 绑定了 DOM 元素且 instance 尚未初始化 + if (chartRef.value && !instance.value) { + // 初始化 ECharts 实例并赋值给 instance + instance.value = echarts.init(chartRef.value, theme, opts); + // 设置图表的初始选项 + instance.value.setOption(options); + } + }; + + // 更新图表配置选项的函数 + const updateEchartOptions = (newOptions: echarts.EChartsOption) => { + if (instance.value) { + // 使用新的选项更新图表,不合并现有选项并延迟更新以优化性能 + instance.value.setOption(newOptions, { notMerge: true, lazyUpdate: true }); + } + }; + + // 处理窗口大小调整的函数,确保图表能够自动调整大小 + const handleResize = () => { + instance.value?.resize(); // 如果 instance 存在,则调用 resize 方法 + }; + + // 销毁图表实例的函数,释放内存并清空引用 + const disposeEchart = () => { + instance.value?.dispose(); // 调用 ECharts 的 dispose 方法销毁实例 + instance.value = null; // 清空 instance 引用,避免内存泄漏 + }; + + // 监听 options 的变化,并在其发生改变时更新图表 + watch( + () => options, + () => updateEchartOptions, + { deep: true } + ); + + // 组件挂载时初始化图表并添加窗口大小调整的事件监听器 + onMounted(() => { + initEchart(); // 初始化图表 + window.addEventListener("resize", handleResize); // 监听窗口大小变化 + }); + + // 组件卸载时移除事件监听器并销毁图表实例 + onUnmounted(() => { + window.removeEventListener("resize", handleResize); // 移除窗口大小调整的监听器 + disposeEchart(); // 销毁图表实例 + }); + + // 组件激活时重新初始化图表并添加事件监听器 + onActivated(() => { + if (!instance.value) { + initEchart(); // 如果图表实例不存在,重新初始化 + } + window.addEventListener("resize", handleResize); // 监听窗口大小变化 + }); + + // 组件停用时移除事件监听器并销毁图表实例 + onDeactivated(() => { + window.removeEventListener("resize", handleResize); // 移除窗口大小调整的监听器 + disposeEchart(); // 销毁图表实例 + }); + + // 返回 instance 和相关的控制方法,供外部组件使用 + return { + instance, // 返回图表实例的引用 + options, // 返回图表配置选项的引用 + initEchart, // 返回初始化图表的方法 + handleResize, // 返回手动触发图表调整大小的方法 + disposeEchart // 返回手动销毁图表实例的方法 + }; +} diff --git a/src/views/assembly/dialog/useDialog/index.vue b/src/views/assembly/dialog/useDialog/index.vue new file mode 100644 index 00000000..e99aa42a --- /dev/null +++ b/src/views/assembly/dialog/useDialog/index.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/src/views/assembly/dialog/useDialog/test.vue b/src/views/assembly/dialog/useDialog/test.vue new file mode 100644 index 00000000..c7f9bb26 --- /dev/null +++ b/src/views/assembly/dialog/useDialog/test.vue @@ -0,0 +1,99 @@ + + + diff --git a/src/views/echarts/lineChart/index.vue b/src/views/echarts/lineChart/index.vue index 4c9c3bf2..02971d8e 100644 --- a/src/views/echarts/lineChart/index.vue +++ b/src/views/echarts/lineChart/index.vue @@ -1,12 +1,130 @@ - -