Merge branch 'main' of github.com:pure-admin/vue-pure-admin into gitee

This commit is contained in:
xiaoxian521
2023-05-24 12:58:59 +08:00
28 changed files with 3032 additions and 6149 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -12,6 +12,7 @@ import type {
const dialogStore = ref<Array<DialogOptions>>([]);
/** 打开弹框 */
const addDialog = (options: DialogOptions) => {
const open = () =>
dialogStore.value.push(Object.assign(options, { visible: true }));
@@ -24,16 +25,40 @@ const addDialog = (options: DialogOptions) => {
}
};
/** 关闭弹框 */
const closeDialog = (options: DialogOptions, index: number, args?: any) => {
dialogStore.value.splice(index, 1);
options.closeCallBack && options.closeCallBack({ options, index, args });
};
/**
* @description 更改弹框自身属性值
* @param value 属性值
* @param key 属性,默认`title`
* @param index 弹框索引(默认`0`,代表只有一个弹框,对于嵌套弹框要改哪个弹框的属性值就把该弹框索引赋给`index`
*/
const updateDialog = (value: any, key = "title", index = 0) => {
dialogStore.value[index][key] = value;
};
/** 关闭所有弹框 */
const closeAllDialog = () => {
dialogStore.value = [];
};
/** 千万别忘了在下面这三处引入并注册下,放心注册,不使用`addDialog`调用就不会被挂载
* https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L4
* https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L13
* https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L18
*/
const ReDialog = withInstall(reDialog);
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };
export { ReDialog, dialogStore, addDialog, closeDialog, closeAllDialog };
export {
ReDialog,
dialogStore,
addDialog,
closeDialog,
updateDialog,
closeAllDialog
};

View File

@@ -200,9 +200,13 @@ export default defineComponent({
return () => (
<>
<div {...attrs} class="w-[99/100] mt-4 px-2 pb-2 bg-bg_color">
<div {...attrs} class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
<div class="flex justify-between w-full h-[60px] p-4">
<p class="font-bold truncate">{props.title}</p>
{slots?.title ? (
slots.title()
) : (
<p class="font-bold truncate">{props.title}</p>
)}
<div class="flex items-center justify-around">
{slots?.buttons ? (
<div class="flex mr-4">{slots.buttons()}</div>

View File

@@ -51,20 +51,21 @@ let startPosX = null;
let isHover = false;
let ease = "ease-in";
// eslint-disable-next-line vue/no-setup-props-destructure
const { classOption } = props;
if (classOption["key"] === undefined) {
classOption["key"] = 0;
if (props.classOption["key"] === undefined) {
// eslint-disable-next-line vue/no-mutating-props
props.classOption["key"] = 0;
}
const wrap = templateRef<HTMLElement | null>(`wrap${classOption["key"]}`, null);
const wrap = templateRef<HTMLElement | null>(
`wrap${props.classOption["key"]}`,
null
);
const slotList = templateRef<HTMLElement | null>(
`slotList${classOption["key"]}`,
`slotList${props.classOption["key"]}`,
null
);
const realBox = templateRef<HTMLElement | null>(
`realBox${classOption["key"]}`,
`realBox${props.classOption["key"]}`,
null
);
@@ -107,7 +108,7 @@ const defaultOption = computed(() => {
const options = computed(() => {
// @ts-expect-error
return copyObj({}, unref(defaultOption), classOption);
return copyObj({}, unref(defaultOption), props.classOption);
});
const leftSwitchClass = computed(() => {
@@ -495,7 +496,7 @@ defineExpose({
</script>
<template>
<div :ref="'wrap' + classOption['key']">
<div :ref="'wrap' + props.classOption['key']">
<div
:style="leftSwitch"
v-if="navigation"
@@ -513,7 +514,7 @@ defineExpose({
<slot name="right-switch" />
</div>
<div
:ref="'realBox' + classOption['key']"
:ref="'realBox' + props.classOption['key']"
:style="pos"
@mouseenter="enter"
@mouseleave="leave"
@@ -522,7 +523,7 @@ defineExpose({
@touchend="touchEnd"
@mousewheel.passive="wheel"
>
<div :ref="'slotList' + classOption['key']" :style="float">
<div :ref="'slotList' + props.classOption['key']" :style="float">
<slot />
</div>
<div v-html="copyHtml" :style="float" />

View File

@@ -164,7 +164,7 @@ onKeyStroke("ArrowDown", handleDown);
</template>
</el-input>
<div class="search-result-container">
<el-scrollbar ref="scrollbarRef" max-height="600px">
<el-scrollbar ref="scrollbarRef" max-height="calc(90vh - 140px)">
<el-empty
v-if="resultOptions.length === 0"
description="暂无搜索结果"

View File

@@ -1,8 +1,9 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { computed, getCurrentInstance } from "vue";
import { useResizeObserver } from "@vueuse/core";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { ref, computed, getCurrentInstance, onMounted } from "vue";
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
import Bookmark2Line from "@iconify-icons/ri/bookmark-2-line";
@@ -26,6 +27,8 @@ interface Emits {
(e: "enter"): void;
}
const resultRef = ref();
const innerHeight = ref();
const props = withDefaults(defineProps<Props>(), {});
const emit = defineEmits<Emits>();
const instance = getCurrentInstance()!;
@@ -59,19 +62,32 @@ function handleTo() {
emit("enter");
}
function resizeResult() {
// el-scrollbar max-height="calc(90vh - 140px)"
innerHeight.value = window.innerHeight - window.innerHeight / 10 - 140;
}
useResizeObserver(resultRef, () => {
resizeResult();
});
function handleScroll(index: number) {
const curInstance = instance?.proxy?.$refs[`resultItemRef${index}`];
if (!curInstance) return 0;
const curRef = curInstance[0] as ElRef;
const scrollTop = curRef.offsetTop + 128; // 128 两个result-item56px+56px=112px高度加上下margin8px+8px=16px
return scrollTop > 600 ? scrollTop - 600 : 0; // 600 el-scrollbar max-height="600px"
return scrollTop > innerHeight.value ? scrollTop - innerHeight.value : 0;
}
onMounted(() => {
resizeResult();
});
defineExpose({ handleScroll });
</script>
<template>
<div class="result">
<div ref="resultRef" class="result">
<div
v-for="(item, index) in options"
:key="item.path"

View File

@@ -218,7 +218,6 @@ watch($storage, ({ layout }) => {
});
onBeforeMount(() => {
dataThemeChange();
/* 初始化项目配置 */
nextTick(() => {
settings.greyVal &&

View File

@@ -8,7 +8,15 @@ import { useLayout } from "./hooks/useLayout";
import { useAppStoreHook } from "@/store/modules/app";
import { useSettingStoreHook } from "@/store/modules/settings";
import { deviceDetection, useDark, useGlobal } from "@pureadmin/utils";
import { h, reactive, computed, onMounted, defineComponent } from "vue";
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import {
h,
reactive,
computed,
onMounted,
onBeforeMount,
defineComponent
} from "vue";
import navbar from "./components/navbar.vue";
import tag from "./components/tag/index.vue";
@@ -102,6 +110,10 @@ onMounted(() => {
}
});
onBeforeMount(() => {
useDataThemeChange().dataThemeChange();
});
const layoutHeader = defineComponent({
render() {
return h(

View File

@@ -23,7 +23,7 @@ import {
formatFlatteningRoutes
} from "./utils";
import { buildHierarchyTree } from "@/utils/tree";
import { isUrl, openLink, storageSession } from "@pureadmin/utils";
import { isUrl, openLink, storageSession, isAllEmpty } from "@pureadmin/utils";
import remainingRouter from "./modules/remaining";
@@ -158,11 +158,22 @@ router.beforeEach((to: toRouteType, _from, next) => {
getTopMenu(true);
// query、params模式路由传参数的标签页不在此处处理
if (route && route.meta?.title) {
useMultiTagsStoreHook().handleTags("push", {
path: route.path,
name: route.name,
meta: route.meta
});
if (isAllEmpty(route.parentId) && route.meta?.backstage) {
// 此处为动态顶级路由(目录)
const { path, name, meta } = route.children[0];
useMultiTagsStoreHook().handleTags("push", {
path,
name,
meta
});
} else {
const { path, name, meta } = route;
useMultiTagsStoreHook().handleTags("push", {
path,
name,
meta
});
}
}
}
router.push(to.fullPath);

View File

@@ -3,7 +3,7 @@ import { components } from "@/router/enums";
export default {
path: "/components",
redirect: "/components/video",
redirect: "/components/dialog",
meta: {
icon: "menu",
title: $t("menus.hscomponents"),

View File

@@ -54,14 +54,14 @@ export function useColumns() {
}
},
{
label: "QQ交流群",
label: "精简版",
cellRenderer: () => {
return (
<a
href="https://yiming_chang.gitee.io/pure-admin-doc/pages/support/#qq-%E4%BA%A4%E6%B5%81%E7%BE%A4"
href="https://github.com/pure-admin/pure-admin-thin"
target="_blank"
>
<span style="color: var(--el-color-primary)"></span>
<span style="color: var(--el-color-primary)"></span>
</a>
);
}

View File

@@ -2,9 +2,14 @@
import { useRouter } from "vue-router";
import { h, createVNode, ref } from "vue";
import { message } from "@/utils/message";
import { cloneDeep } from "@pureadmin/utils";
import forms, { type FormProps } from "./form.vue";
import { addDialog, closeDialog, closeAllDialog } from "@/components/ReDialog";
import { cloneDeep, debounce } from "@pureadmin/utils";
import {
addDialog,
closeDialog,
updateDialog,
closeAllDialog
} from "@/components/ReDialog";
defineOptions({
name: "DialogPage"
@@ -60,13 +65,16 @@ function onStyleClick() {
});
}
function onoOpenDelayClick() {
addDialog({
title: "延时2秒打开弹框",
openDelay: 2000,
contentRenderer: () => <p>弹框内容-延时2秒打开弹框</p>
});
}
// 添加 600ms 防抖
const onoOpenDelayClick = debounce(
() =>
addDialog({
title: "延时2秒打开弹框",
openDelay: 2000 - 600,
contentRenderer: () => <p>弹框内容-延时2秒打开弹框</p>
}),
600
);
function onCloseDelayClick() {
addDialog({
@@ -240,6 +248,35 @@ function onNestingClick() {
});
}
// 满足在 contentRenderer 内容区更改弹框自身属性值的场景
function onUpdateClick() {
const curPage = ref(1);
addDialog({
title: `${curPage.value}`,
contentRenderer: () => (
<>
<el-button
disabled={curPage.value > 1 ? false : true}
onClick={() => {
curPage.value -= 1;
updateDialog(`${curPage.value}`);
}}
>
上一页
</el-button>
<el-button
onClick={() => {
curPage.value += 1;
updateDialog(`${curPage.value}`);
}}
>
下一页
</el-button>
</>
)
});
}
// 结合Form表单第一种方式弹框关闭立刻恢复初始值通过 props 属性接收子组件的 prop 并赋值
function onFormOneClick() {
addDialog({
@@ -421,6 +458,7 @@ function onBeforeSureClick() {
<el-button @click="onOpenClick"> 打开后的回调 </el-button>
<el-button @click="onCloseCallBackClick"> 关闭后的回调 </el-button>
<el-button @click="onNestingClick"> 嵌套的弹框 </el-button>
<el-button @click="onUpdateClick"> 更改弹框自身属性值 </el-button>
</el-space>
<el-divider />
<el-space wrap>

View File

@@ -0,0 +1,56 @@
<script setup lang="ts">
import "@wangeditor/editor/dist/css/style.css";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import { onBeforeUnmount, ref, shallowRef, onMounted } from "vue";
defineOptions({
name: "BaseEditor"
});
const mode = "default";
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef();
// 内容 HTML
const valueHtml = ref("<p>你好</p>");
// 模拟 ajax 异步获取内容
onMounted(() => {
setTimeout(() => {
valueHtml.value = "<p>我是模拟的异步数据</p>";
}, 1500);
});
const toolbarConfig: any = { excludeKeys: "fullScreen" };
const editorConfig = { placeholder: "请输入内容..." };
const handleCreated = editor => {
// 记录 editor 实例,重要!
editorRef.value = editor;
};
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
</script>
<template>
<div class="wangeditor">
<Toolbar
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
style="border-bottom: 1px solid #ccc"
/>
<Editor
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
style="height: 500px; overflow-y: hidden"
@onCreated="handleCreated"
/>
</div>
</template>

View File

@@ -0,0 +1,9 @@
import base from "./base.vue";
import multi from "./multi.vue";
import picUpload from "./picUpload.vue";
const Base = base;
const Multi = multi;
const PicUpload = picUpload;
export { Base, Multi, PicUpload };

View File

@@ -0,0 +1,76 @@
<script setup lang="ts">
import ReCol from "@/components/ReCol";
import { onBeforeUnmount, ref, shallowRef } from "vue";
import "@wangeditor/editor/dist/css/style.css";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
defineOptions({
name: "MultiEditor"
});
// 模拟后端返回多个编辑器的数据
const endEditorList = [
{
value: "<p>测试一</p>"
},
{
value: "<p>测试二</p>"
},
{
value: "<p>测试三</p>"
},
{
value: "<p>测试四</p>"
}
];
// 多个编辑器的情况下,前端必须进行处理,满足 Toolbar 组件的 editor 属性 所需的 shallowRef 格式
const editorList = ref([]);
endEditorList.forEach(edit => {
editorList.value.push({
value: edit.value,
// 编辑器实例,必须用 shallowRef
editorRef: shallowRef()
});
});
const mode = "default";
const toolbarConfig: any = { excludeKeys: "fullScreen" };
const editorConfig = { placeholder: "请输入内容..." };
const handleCreated = (editor, index) => {
// 记录 editor 实例,重要!
editorList.value[index].editorRef = editor;
};
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
return editorList.value.map(edit => {
if (edit.editorRef == null) return;
edit.editorRef.destroy();
});
});
</script>
<template>
<el-row :gutter="30" justify="space-around">
<re-col :value="11" v-for="(edit, index) in editorList" :key="index">
<div class="wangeditor">
<Toolbar
:editor="edit.editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
style="border-bottom: 1px solid #ccc"
/>
<Editor
v-model="edit.value"
:defaultConfig="editorConfig"
:mode="mode"
style="height: 300px; overflow-y: hidden"
@onCreated="editor => handleCreated(editor, index)"
/>
</div>
</re-col>
</el-row>
</template>

View File

@@ -0,0 +1,70 @@
<script setup lang="ts">
import { onBeforeUnmount, ref, shallowRef } from "vue";
import "@wangeditor/editor/dist/css/style.css";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
defineOptions({
name: "picUpload"
});
const mode = "default";
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef();
// 内容 HTML
const valueHtml = ref(
"<p>仅提供代码参考,暂不可上传图片,可根据实际业务改写</p>"
);
const toolbarConfig: any = { excludeKeys: "fullScreen" };
const editorConfig = { placeholder: "请输入内容...", MENU_CONF: {} };
// 更多详细配置看 https://www.wangeditor.com/v5/menu-config.html#%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87
editorConfig.MENU_CONF["uploadImage"] = {
// 服务端上传地址,根据实际业务改写
server: "",
// form-data 的 fieldName根据实际业务改写
fieldName: "file",
// 选择文件时的类型限制,根据实际业务改写
allowedFileTypes: ["image/png", "image/jpg", "image/jpeg"],
// 自定义插入图片
customInsert(res: any, insertFn) {
// res.data.url是后端返回的图片地址根据实际业务改写
if (res.data.url) {
setTimeout(() => {
// insertFn插入图片进编辑器
insertFn(res.data.url);
}, 2000);
}
}
};
const handleCreated = editor => {
// 记录 editor 实例,重要!
editorRef.value = editor;
};
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
</script>
<template>
<div class="wangeditor">
<Toolbar
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
style="border-bottom: 1px solid #ccc"
/>
<Editor
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
style="height: 500px; overflow-y: hidden"
@onCreated="handleCreated"
/>
</div>
</template>

View File

@@ -1,41 +1,12 @@
<script setup lang="ts">
import "@wangeditor/editor/dist/css/style.css"; // 引入 css
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { onBeforeUnmount, ref, shallowRef, onMounted } from "vue";
import Edit from "@iconify-icons/ep/edit";
import { ref } from "vue";
import { Base, Multi, PicUpload } from "./components";
defineOptions({
name: "Editor"
});
const mode = "default";
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef();
// 内容 HTML
const valueHtml = ref("<p>hello</p>");
// 模拟 ajax 异步获取内容
onMounted(() => {
setTimeout(() => {
valueHtml.value = "<p>模拟 Ajax 异步设置内容</p>";
}, 1500);
});
const toolbarConfig: any = { excludeKeys: "fullScreen" };
const editorConfig = { placeholder: "请输入内容..." };
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
const handleCreated = editor => {
editorRef.value = editor; // 记录 editor 实例,重要!
};
const activeNames = ref(["1"]);
</script>
<template>
@@ -47,7 +18,6 @@ const handleCreated = editor => {
<el-link
href="https://www.wangeditor.com"
target="_blank"
:icon="useRenderIcon(Edit)"
style="margin: 0 4px 5px; font-size: 16px"
>
Wangeditor
@@ -55,20 +25,22 @@ const handleCreated = editor => {
</span>
</div>
</template>
<div class="wangeditor">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
style="height: 500px; overflow-y: hidden"
v-model="valueHtml"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="handleCreated"
/>
</div>
<el-collapse v-model="activeNames" accordion>
<el-collapse-item title="基础用法" name="1">
<Base />
</el-collapse-item>
<el-collapse-item title="多个富文本" name="2">
<Multi />
</el-collapse-item>
<el-collapse-item title="自定义图片上传" name="3">
<PicUpload />
</el-collapse-item>
</el-collapse>
</el-card>
</template>
<style lang="scss" scoped>
:deep(.el-collapse-item__header) {
padding-left: 10px;
}
</style>