15 Commits
2.2.0 ... 2.3.1

Author SHA1 Message Date
lyt
161748a549 'admin-22.11.19:修复v2.3.0版本动态路由事件调用关闭当前tagsview、普通路由刷新界面参数丢失问题' 2022-11-19 00:09:19 +08:00
lyt
f9ce14431d 'admin-22.11.18:优化v2.3.0版本tagsview风格5兼容火狐' 2022-11-18 02:19:03 +08:00
lyt
3a8f31ab89 'admin-22.11.17:优化v2.3.0版本iframe右键菜单刷新问题' 2022-11-17 08:25:32 +08:00
lyt
f00fc0d2f1 'admin-22.11.17:优化v2.3.0版本iframe右键菜单刷新及loading' 2022-11-17 01:18:18 +08:00
lyt
32e2d6cf94 'admin-22.11.17:修复v2.3.0版本iframe右键菜单刷新' 2022-11-17 00:44:00 +08:00
5cb614c277 'admin-22.11.17:修复v2.3.0版本iframe右键菜单刷新'
Signed-off-by: lyt-Top <1105290566@qq.com>
2022-11-16 16:39:38 +00:00
lyt
cdf1b715df 'admin-22.11.16:发布v2.3.0,具体更新内容查看CHANGELOG.md' 2022-11-16 15:34:23 +08:00
lyt
aba9f3947d 'admin-22.07.20:降级vite@2.9.14,防止打包样式丢掉问题' 2022-07-20 23:15:32 +08:00
lyt
e08b4b3782 'admin-22.07.16:修复行内表单最后一个el-form-item位置下移问题' 2022-07-16 22:59:53 +08:00
lyt
356040806d 'admin-22.07.16:修复行内表单最后一个el-form-item位置下移问题' 2022-07-16 22:57:49 +08:00
lyt
f56cae3719 'admin-22.07.11:el-form表单最后一行样式,移动端lable左对齐' 2022-07-11 21:02:36 +08:00
b88fe3957f !32 开启后端控制路由找不到登录路由
Merge pull request !32 from 赵国辉/master
2022-07-11 12:49:53 +00:00
ccdce9b904 v2.2.0版本 src/router/route.ts 静态路由“布局界面”已删除,在“开启后端控制路由”时,未登录无法找到登录路由 2022-07-11 10:02:40 +08:00
134da2bc8c !30 修复多个权限同时校验的BUG
Merge pull request !30 from LostDeer/auth
2022-07-10 12:57:39 +00:00
4841a29c29 多个权限校验的BUG 2022-07-10 20:12:53 +08:00
128 changed files with 3995 additions and 3128 deletions

View File

@ -2,6 +2,27 @@
🎉🎉🔥 `vue-next-admin` 基于 vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 2.3.0
`2022.11.16`
- 🌟 更新 依赖更新最新版本
- 🎉 新增 新版登录页
- 🎉 新增 tagsview 鼠标中键 `关闭当前 tagsview`
- 🎉 新增 `分栏菜单鼠标悬停预加载`。[分栏模式如何去掉鼠标悬浮父级菜单,分栏菜单自动加载的功能啊](https://gitee.com/lyt-top/vue-next-admin/issues/I5RUY7)。操作路径:`布局配置 -> 分栏设置`
- 🐞 修复 [vue-i18n](https://vue-i18n.intlify.dev/api/general.html#createi18n) 报错,[!39 修复 i18n 兼容性问题](https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/39/files),感谢[@随心](https://toscode.gitee.com/jiangqiang1996)
- 🐞 修复 顶栏搜索功能点击蒙蔽弹窗不关闭
- 🐞 修复 [!38 fix: bug refreshRouterViewKey 值为 null 导致路由缓存第一次无效](https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/38/files),感谢[@P)](https://toscode.gitee.com/foxp8y)
- 🐞 修复 `路由参数 -> 普通路由/动态路由` 国际化演示时,`tagsView``浏览器标题` 显示异常。[演示中:路由参数界面 -> 动态路由,国际化显示时面包屑、浏览器标题有 bug](https://gitee.com/lyt-top/vue-next-admin/issues/I5JRJG)
- 🐞 修复 `路由参数 -> 普通路由/动态路由` 动态设置 `tagsViewName` 时,`tagsView 右键菜单刷新` 功能失效也就是路由后面有参数时query、params。[普通或动态路由新建页面后点击 tagview 刷新无效](https://gitee.com/lyt-top/vue-next-admin/issues/I5K3YO),感谢[@dejavuuuuu](https://gitee.com/zc19951010)
- 🐞 修复 [表单el-form字体图标偏移问题](https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM)
- 🐞 修复 路由 `router.addRoute` 时,一直提示 `No match found for location with path 'xxx'`
- 🎯 优化 全局 `getCurrentInstance` 替换成 [`provide/inject`](https://cn.vuejs.org/api/application.html#app-provide) 或通过 `ref` 处理
- 🎯 优化 引入组件方式 `(import xxx from xxx)` 改成 `defineAsyncComponent(() => import(xxx))`
- 🎯 优化 页面高度 100% 问题,重写布局配置 `界面设置 -> 固定 Header` 多余的 `el-scrollbar` 逻辑、重写各界面需 `计算属性 computed` 设置动态高度问题(改为 css `flex` 设置自适应高度,具体查看文档:[设置可视区高度 100%](https://lyt-top.gitee.io/vue-next-admin-doc-preview/config/otherIssues/#%E8%AE%BE%E7%BD%AE%E5%8F%AF%E8%A7%86%E5%8C%BA%E9%AB%98%E5%BA%A6-100)。[!31 修复页面样式无法通过百分比设置的问题](https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/31),感谢[@LostDeer](https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/31/files)。`(改动较大,删除多余代码)`
- 🎯 优化 [wangeditor](https://www.wangeditor.com/) 组件,`@wangeditor/editor-for-vue`。可自行修改,组件位置:`/src/components/editor`。相关 Issues[wangeditor 编辑器多个菜单不能回弹](https://gitee.com/lyt-top/vue-next-admin/issues/I5M5H7)
- 🌈 重构 外链、内嵌 iframe 逻辑 + 美化iframe 支持缓存
## 2.2.0
`2022.07.10`

View File

@ -68,10 +68,13 @@ cnpm run dev
cnpm run build
```
#### 📚 开发文档
- 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a>
#### 💯 学习交流加 QQ 群
- 若加群了没同意一般秒过那就是群满了500 人群请换一个群试试。群会定期清理半年6 个月)未发言的群友,资源有限,请谅解。建议勿加多群,可能会误伤!
- 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a>
- 若加群了没同意一般秒过那就是群满了500 人群请换一个群试试。群会定期清理半年6 个月)未发言的群友,资源有限,请谅解。建议勿加多群,可能会误伤!微信群由于只有 `7天有效` 就不放这里了。
- 群号码:
1 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi">665452019</a>
2 群:<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=zVfy3gNy7pNWVK3kMduDzwU369PZg2fw&jump_from=webapi">766356862</a>

3438
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "vue-next-admin",
"version": "2.2.0",
"version": "2.3.0",
"description": "vue3 vite next admin template",
"author": "lyt_20201208",
"license": "MIT",
@ -10,48 +10,48 @@
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.6",
"@wangeditor/editor": "^5.1.1",
"axios": "^0.27.2",
"@element-plus/icons-vue": "^2.0.10",
"@wangeditor/editor-for-vue": "^5.1.11",
"axios": "^1.1.3",
"countup.js": "^2.3.2",
"cropperjs": "^1.5.12",
"echarts": "^5.3.3",
"echarts": "^5.4.0",
"echarts-gl": "^2.0.9",
"echarts-wordcloud": "^2.0.0",
"element-plus": "^2.2.9",
"element-plus": "^2.2.21",
"js-cookie": "^3.0.1",
"jsplumb": "^2.15.6",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
"pinia": "^2.0.14",
"pinia": "^2.0.23",
"print-js": "^1.6.0",
"qrcodejs2-fixes": "^0.0.2",
"screenfull": "^6.0.2",
"sortablejs": "^1.15.0",
"splitpanes": "^3.1.1",
"vue": "^3.2.37",
"splitpanes": "^3.1.5",
"vue": "^3.2.45",
"vue-clipboard3": "^2.0.0",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.10",
"vue-router": "^4.1.1"
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@types/node": "^18.0.3",
"@types/node": "^18.11.9",
"@types/nprogress": "^0.2.0",
"@types/sortablejs": "^1.13.0",
"@typescript-eslint/eslint-plugin": "^5.30.5",
"@typescript-eslint/parser": "^5.30.5",
"@vitejs/plugin-vue": "^2.3.3",
"@vue/compiler-sfc": "^3.2.37",
"dotenv": "^16.0.1",
"eslint": "^8.19.0",
"eslint-plugin-vue": "^9.2.0",
"@types/sortablejs": "^1.15.0",
"@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0",
"@vitejs/plugin-vue": "^3.2.0",
"@vue/compiler-sfc": "^3.2.45",
"dotenv": "^16.0.3",
"eslint": "^8.27.0",
"eslint-plugin-vue": "^9.7.0",
"prettier": "^2.7.1",
"sass": "^1.53.0",
"sass-loader": "^13.0.2",
"typescript": "^4.7.4",
"vite": "^2.9.14",
"vue-eslint-parser": "^9.0.3"
"sass": "^1.56.1",
"sass-loader": "^13.2.0",
"typescript": "^4.9.3",
"vite": "^3.2.4",
"vue-eslint-parser": "^9.1.0"
},
"browserslist": [
"> 1%",

View File

@ -1,5 +1,5 @@
<template>
<el-config-provider :size="getGlobalComponentSize" :locale="i18nLocale">
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
<router-view v-show="themeConfig.lockScreenTime > 1" />
<LockScreen v-if="themeConfig.isLockScreen" />
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
@ -8,39 +8,39 @@
</template>
<script lang="ts">
import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch, reactive, toRefs } from 'vue';
import { defineAsyncComponent, computed, ref, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useThemeConfig } from '/@/stores/themeConfig';
import other from '/@/utils/other';
import { Local, Session } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
import setIntroduction from '/@/utils/setIconfont';
import LockScreen from '/@/layout/lockScreen/index.vue';
import Setings from '/@/layout/navBars/breadcrumb/setings.vue';
import CloseFull from '/@/layout/navBars/breadcrumb/closeFull.vue';
export default defineComponent({
name: 'app',
components: { LockScreen, Setings, CloseFull },
components: {
LockScreen: defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue')),
Setings: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/setings.vue')),
CloseFull: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/closeFull.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const { messages, locale } = useI18n();
const setingsRef = ref();
const route = useRoute();
const stores = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive({
i18nLocale: null,
});
// 获取全局组件大小
const getGlobalComponentSize = computed(() => {
return other.globalComponentSize();
});
// 布局配置弹窗打开
const openSetingsDrawer = () => {
setingsRef.value.openDrawer();
};
// 获取全局 i18n
const getGlobalI18n = computed(() => {
return messages.value[locale.value];
});
// 设置初始化,防止刷新时恢复默认
onBeforeMount(() => {
// 设置批量第三方 icon 图标
@ -52,12 +52,8 @@ export default defineComponent({
onMounted(() => {
nextTick(() => {
// 监听布局配置弹窗点击打开
proxy.mittBus.on('openSetingsDrawer', () => {
openSetingsDrawer();
});
// 设置 i18nApp.vue 中的 el-config-provider
proxy.mittBus.on('getI18nConfig', (locale: string) => {
(state.i18nLocale as string | null) = locale;
mittBus.on('openSetingsDrawer', () => {
setingsRef.value.openDrawer();
});
// 获取缓存中的布局配置
if (Local.get('themeConfig')) {
@ -72,8 +68,7 @@ export default defineComponent({
});
// 页面销毁时,关闭监听布局配置/i18n监听
onUnmounted(() => {
proxy.mittBus.off('openSetingsDrawer', () => {});
proxy.mittBus.off('getI18nConfig', () => {});
mittBus.off('openSetingsDrawer', () => {});
});
// 监听路由的变化,设置网站标题
watch(
@ -89,7 +84,7 @@ export default defineComponent({
themeConfig,
setingsRef,
getGlobalComponentSize,
...toRefs(state),
getGlobalI18n,
};
},
});

19
src/assets/login-bg.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -1,40 +1,29 @@
<template>
<div class="editor-container">
<div ref="editorToolbar"></div>
<div ref="editorContent" :style="{ height }"></div>
<Toolbar :editor="editorRef" :mode="mode" />
<Editor :mode="mode" :defaultConfig="editorConfig" :style="{ height }" v-model="editorVal" @onCreated="handleCreated" @onChange="handleChange" />
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, watch, defineComponent } from 'vue';
import { createEditor, createToolbar, IEditorConfig, IToolbarConfig, IDomEditor } from '@wangeditor/editor';
// https://www.wangeditor.com/v5/for-frame.html#vue3
import '@wangeditor/editor/dist/css/style.css';
import { toolbarKeys } from './toolbar';
// 定义接口来定义对象的类型
interface WangeditorState {
editorToolbar: HTMLDivElement | null;
editorContent: HTMLDivElement | null;
editor: any;
}
import { defineComponent, reactive, toRefs, shallowRef, watch, onBeforeUnmount } from 'vue';
import { Toolbar, Editor } from '@wangeditor/editor-for-vue';
export default defineComponent({
name: 'wngEditor',
components: { Toolbar, Editor },
props: {
// 节点 id
id: {
type: String,
default: () => 'wangeditor',
},
// 是否禁用
isDisable: {
disable: {
type: Boolean,
default: () => false,
default: () => true,
},
// 内容框默认 placeholder
placeholder: {
type: String,
default: () => '请输入内容',
default: () => '请输入内容...',
},
// 双向绑定:双向绑定值,字段名为固定,改了之后将不生效
// 参考https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
@ -52,62 +41,46 @@ export default defineComponent({
},
},
setup(props, { emit }) {
const state = reactive<WangeditorState>({
editorToolbar: null,
editor: null,
editorContent: null,
const editorRef = shallowRef();
const state = reactive({
editorConfig: {
placeholder: props.placeholder,
},
editorVal: props.modelValue,
});
// 富文本配置
const wangeditorConfig = () => {
const editorConfig: Partial<IEditorConfig> = { MENU_CONF: {} };
props.isDisable ? (editorConfig.readOnly = true) : (editorConfig.readOnly = false);
editorConfig.placeholder = props.placeholder;
editorConfig.onChange = (editor: IDomEditor) => {
// console.log('content', editor.children);
// console.log('html', editor.getHtml());
emit('update:modelValue', editor.getHtml());
};
(<any>editorConfig).MENU_CONF['uploadImage'] = {
base64LimitSize: 10 * 1024 * 1024,
};
return editorConfig;
// 编辑器回调函数
const handleCreated = (editor: any) => {
editorRef.value = editor;
};
//
const toolbarConfig = () => {
const toolbarConfig: Partial<IToolbarConfig> = {};
toolbarConfig.toolbarKeys = toolbarKeys;
return toolbarConfig;
// 编辑器内容改变时
const handleChange = (editor: any) => {
// console.log(editor.getText());
// console.log(editor.getHtml());
emit('update:modelValue', editor.getHtml());
};
// 初始化富文本
// https://www.wangeditor.com/
const initWangeditor = () => {
state.editor = createEditor({
html: props.modelValue,
selector: state.editorContent!,
config: wangeditorConfig(),
mode: props.mode,
});
createToolbar({
editor: state.editor,
selector: state.editorToolbar!,
mode: props.mode,
config: toolbarConfig(),
});
};
// 页面加载时
onMounted(() => {
initWangeditor();
});
// 监听双向绑定值的改变
// 监听是否禁用改变
// https://gitee.com/lyt-top/vue-next-admin/issues/I4LM7I
watch(
() => props.modelValue,
(value) => {
state.editor.clear();
state.editor.dangerouslyInsertHtml(value);
() => props.disable,
(bool) => {
const editor = editorRef.value;
if (editor == null) return;
bool ? editor.disable() : editor.enable();
},
{
deep: true,
}
);
// 页面销毁时
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
return {
editorRef,
handleCreated,
handleChange,
...toRefs(state),
};
},

View File

@ -1,60 +0,0 @@
/**
* 工具栏配置
*/
export const toolbarKeys = [
'headerSelect',
'blockquote',
'|',
'bold',
'underline',
'italic',
{
key: 'group-more-style',
title: '更多',
iconSvg:
'<svg viewBox="0 0 1024 1024"><path d="M204.8 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M505.6 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path><path d="M806.4 505.6m-76.8 0a76.8 76.8 0 1 0 153.6 0 76.8 76.8 0 1 0-153.6 0Z"></path></svg>',
menuKeys: ['through', 'code', 'sup', 'sub', 'clearStyle'],
},
'color',
'bgColor',
'|',
'fontSize',
'fontFamily',
'lineHeight',
'|',
'bulletedList',
'numberedList',
'todo',
{
key: 'group-justify',
title: '对齐',
iconSvg:
'<svg viewBox="0 0 1024 1024"><path d="M768 793.6v102.4H51.2v-102.4h716.8z m204.8-230.4v102.4H51.2v-102.4h921.6z m-204.8-230.4v102.4H51.2v-102.4h716.8zM972.8 102.4v102.4H51.2V102.4h921.6z"></path></svg>',
menuKeys: ['justifyLeft', 'justifyRight', 'justifyCenter', 'justifyJustify'],
},
{
key: 'group-indent',
title: '缩进',
iconSvg:
'<svg viewBox="0 0 1024 1024"><path d="M0 64h1024v128H0z m384 192h640v128H384z m0 192h640v128H384z m0 192h640v128H384zM0 832h1024v128H0z m0-128V320l256 192z"></path></svg>',
menuKeys: ['indent', 'delIndent'],
},
'|',
'emotion',
'insertLink',
{
key: 'group-image',
title: '图片',
iconSvg:
'<svg viewBox="0 0 1024 1024"><path d="M959.877 128l0.123 0.123v767.775l-0.123 0.122H64.102l-0.122-0.122V128.123l0.122-0.123h895.775zM960 64H64C28.795 64 0 92.795 0 128v768c0 35.205 28.795 64 64 64h896c35.205 0 64-28.795 64-64V128c0-35.205-28.795-64-64-64zM832 288.01c0 53.023-42.988 96.01-96.01 96.01s-96.01-42.987-96.01-96.01S682.967 192 735.99 192 832 234.988 832 288.01zM896 832H128V704l224.01-384 256 320h64l224.01-192z"></path></svg>',
menuKeys: ['uploadImage'],
},
'insertTable',
'codeBlock',
'divider',
'|',
'undo',
'redo',
'|',
'fullScreen',
];

View File

@ -57,6 +57,7 @@ const { themeConfig } = storeToRefs(stores);
// 导出语言国际化
// https://vue-i18n.intlify.dev/guide/essentials/fallback.html#explicit-fallback-with-one-locale
export const i18n = createI18n({
legacy: false,
silentTranslationWarn: true,
missingWarn: false,
silentFallbackWarn: true,

View File

@ -71,7 +71,8 @@ export default {
personal: 'personal',
tools: 'tools',
layoutLinkView: 'LinkView',
layoutIfameView: 'IfameView',
layoutIframeViewOne: 'IframeViewOne',
layoutIframeViewTwo: 'IframeViewTwo',
},
staticRoutes: {
signIn: 'signIn',
@ -139,6 +140,7 @@ export default {
twoColumnsMenuBar: 'Column menu background',
twoColumnsMenuBarColor: 'Default font color bar menu',
twoIsColumnsMenuBarColorGradual: 'Column gradient',
twoIsColumnsMenuHoverPreload: 'Column Menu Hover Preload',
threeTitle: 'Interface settings',
threeIsCollapse: 'Menu horizontal collapse',
threeIsUniqueOpened: 'Menu accordion',

View File

@ -71,7 +71,8 @@ export default {
personal: '个人中心',
tools: '工具类集合',
layoutLinkView: '外链',
layoutIfameView: '内嵌 iframe',
layoutIframeViewOne: '内嵌 iframe1',
layoutIframeViewTwo: '内嵌 iframe2',
},
staticRoutes: {
signIn: '登录',
@ -139,6 +140,7 @@ export default {
twoColumnsMenuBar: '分栏菜单背景',
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
twoIsColumnsMenuBarColorGradual: '分栏菜单背景渐变',
twoIsColumnsMenuHoverPreload: '分栏菜单鼠标悬停预加载',
threeTitle: '界面设置',
threeIsCollapse: '菜单水平折叠',
threeIsUniqueOpened: '菜单手风琴',

View File

@ -71,7 +71,8 @@ export default {
personal: '個人中心',
tools: '工具類集合',
layoutLinkView: '外鏈',
layoutIfameView: '内嵌 iframe',
layoutIframeViewOne: '内嵌 iframe1',
layoutIframeViewTwo: '内嵌 iframe2',
},
staticRoutes: {
signIn: '登入',
@ -139,6 +140,7 @@ export default {
twoColumnsMenuBar: '分欄選單背景',
twoColumnsMenuBarColor: '分欄選單默認字體顏色',
twoIsColumnsMenuBarColorGradual: '分欄選單背景漸變',
twoIsColumnsMenuHoverPreload: '分欄選單滑鼠懸停預加載',
threeTitle: '介面設定',
threeIsCollapse: '選單水准折疊',
threeIsUniqueOpened: '選單手風琴',

View File

@ -10,20 +10,22 @@
</template>
<script lang="ts">
import { toRefs, reactive, computed, watch, getCurrentInstance, onBeforeMount, defineComponent } from 'vue';
import { defineAsyncComponent, toRefs, reactive, computed, watch, onBeforeMount, defineComponent, ref } from 'vue';
import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import Logo from '/@/layout/logo/index.vue';
import Vertical from '/@/layout/navMenu/vertical.vue';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'layoutAside',
components: { Logo, Vertical },
components: {
Logo: defineAsyncComponent(() => import('/@/layout/logo/index.vue')),
Vertical: defineAsyncComponent(() => import('/@/layout/navMenu/vertical.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const layoutAsideScrollbarRef = ref();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
@ -105,14 +107,13 @@ export default defineComponent({
const onAsideEnterLeave = (bool: Boolean) => {
let { layout } = themeConfig.value;
if (layout !== 'columns') return false;
if (!bool) proxy.mittBus.emit('restoreDefault');
if (!bool) mittBus.emit('restoreDefault');
stores.setColumnsMenuHover(bool);
};
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(themeConfig.value, (val) => {
if (val.isShowLogoChange !== val.isShowLogo) {
if (!proxy.$refs.layoutAsideScrollbarRef) return false;
proxy.$refs.layoutAsideScrollbarRef.update();
if (layoutAsideScrollbarRef.value) layoutAsideScrollbarRef.value.update();
}
});
// 监听vuex值的变化动态赋值给菜单中
@ -131,27 +132,28 @@ export default defineComponent({
onBeforeMount(() => {
initMenuFixed(document.body.clientWidth);
setFilterRoutes();
// 此界面不需要取消监听(proxy.mittBus.off('setSendColumnsChildren))
// 此界面不需要取消监听(mittBus.off('setSendColumnsChildren))
// 因为切换布局时有的监听需要使用,取消了监听,某些操作将不生效
proxy.mittBus.on('setSendColumnsChildren', (res: any) => {
mittBus.on('setSendColumnsChildren', (res: any) => {
state.menuList = res.children;
});
proxy.mittBus.on('setSendClassicChildren', (res: any) => {
mittBus.on('setSendClassicChildren', (res: any) => {
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = [];
state.menuList = res.children;
}
});
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
proxy.mittBus.on('layoutMobileResize', (res: any) => {
mittBus.on('layoutMobileResize', (res: any) => {
initMenuFixed(res.clientWidth);
closeLayoutAsideMobileMode();
});
});
return {
layoutAsideScrollbarRef,
setCollapseStyle,
setShowLogo,
isTagsViewCurrenFull,

View File

@ -45,12 +45,13 @@
</template>
<script lang="ts">
import { reactive, toRefs, ref, onMounted, nextTick, getCurrentInstance, watch, onUnmounted, defineComponent } from 'vue';
import { reactive, toRefs, ref, onMounted, nextTick, watch, onUnmounted, defineComponent } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate, RouteRecordRaw } from 'vue-router';
import { storeToRefs } from 'pinia';
import pinia from '/@/stores/index';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import mittBus from '/@/utils/mitt';
// 定义接口来定义对象的类型
interface ColumnsAsideState {
@ -68,7 +69,6 @@ export default defineComponent({
setup() {
const columnsAsideOffsetTopRefs: any = ref([]);
const columnsAsideActiveRef = ref();
const { proxy } = <any>getCurrentInstance();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { routesList, isColumnsMenuHover, isColumnsNavHover } = storeToRefs(stores);
@ -98,11 +98,12 @@ export default defineComponent({
};
// 鼠标移入时,显示当前的子级菜单
const onColumnsAsideMenuMouseenter = (v: RouteRecordRaw, k: number) => {
if (!themeConfig.value.isColumnsMenuHoverPreload) return false;
let { path } = v;
state.liOldPath = path;
state.liOldIndex = k;
state.liHoverIndex = k;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(path));
mittBus.emit('setSendColumnsChildren', setSendChildren(path));
stores.setColumnsMenuHover(false);
stores.setColumnsNavHover(true);
};
@ -111,7 +112,7 @@ export default defineComponent({
await stores.setColumnsNavHover(false);
// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
setTimeout(() => {
if (!isColumnsMenuHover && !isColumnsNavHover) proxy.mittBus.emit('restoreDefault');
if (!isColumnsMenuHover && !isColumnsNavHover) mittBus.emit('restoreDefault');
}, 100);
};
// 设置高亮动态位置
@ -126,7 +127,7 @@ export default defineComponent({
const resData: any = setSendChildren(route.path);
if (Object.keys(resData).length <= 0) return false;
onColumnsAsideDown(resData.item[0].k);
proxy.mittBus.emit('setSendColumnsChildren', resData);
mittBus.emit('setSendColumnsChildren', resData);
};
// 传送当前子级数据到菜单中
const setSendChildren = (path: string) => {
@ -171,11 +172,11 @@ export default defineComponent({
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
if (!val.routesList.isColumnsMenuHover && !val.routesList.isColumnsNavHover) {
state.liHoverIndex = null;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
mittBus.emit('setSendColumnsChildren', setSendChildren(route.path));
} else {
state.liHoverIndex = state.liOldIndex;
if (!state.liOldPath) return false;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
}
},
{
@ -186,19 +187,19 @@ export default defineComponent({
onMounted(() => {
setFilterRoutes();
// 销毁变量,防止鼠标再次移入时,保留了上次的记录
proxy.mittBus.on('restoreDefault', () => {
mittBus.on('restoreDefault', () => {
state.liOldIndex = null;
state.liOldPath = null;
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('restoreDefault', () => {});
mittBus.off('restoreDefault', () => {});
});
// 路由更新时
onBeforeRouteUpdate((to) => {
setColumnsMenuHighlight(to.path);
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
});
return {
themeConfig,
@ -221,6 +222,16 @@ export default defineComponent({
background: var(--next-bg-columnsMenuBar);
ul {
position: relative;
.layout-columns-active {
color: var(--next-bg-columnsMenuBarColor) !important;
transition: 0.3s ease-in-out;
}
.layout-columns-hover {
color: var(--el-color-primary);
a {
color: var(--el-color-primary);
}
}
li {
color: var(--next-bg-columnsMenuBarColor);
width: 100%;
@ -230,6 +241,9 @@ export default defineComponent({
cursor: pointer;
position: relative;
z-index: 1;
&:hover {
@extend .layout-columns-hover;
}
.columns-vertical {
margin: auto;
.columns-vertical-title {
@ -257,16 +271,6 @@ export default defineComponent({
color: var(--next-bg-columnsMenuBarColor);
}
}
.layout-columns-active {
color: var(--next-bg-columnsMenuBarColor) !important;
transition: 0.3s ease-in-out;
}
.layout-columns-hover {
color: var(--el-color-primary);
a {
color: var(--el-color-primary);
}
}
.columns-round {
background: var(--el-color-primary);
color: var(--el-color-white);

View File

@ -1,32 +1,23 @@
<template>
<el-header class="layout-header" :height="setHeaderHeight" v-show="!isTagsViewCurrenFull">
<el-header class="layout-header" v-show="!isTagsViewCurrenFull">
<NavBarsIndex />
</el-header>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { defineAsyncComponent, defineComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import NavBarsIndex from '/@/layout/navBars/index.vue';
export default defineComponent({
name: 'layoutHeader',
components: { NavBarsIndex },
components: {
NavBarsIndex: defineAsyncComponent(() => import('/@/layout/navBars/index.vue')),
},
setup() {
const storesTagsViewRoutes = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
// 设置 header 的高度
const setHeaderHeight = computed(() => {
let { isTagsview, layout } = themeConfig.value;
if (isTagsview && layout !== 'classic') return '84px';
else return '50px';
});
return {
setHeaderHeight,
isTagsViewCurrenFull,
};
},

View File

@ -1,100 +1,58 @@
<template>
<el-main class="layout-main">
<el-scrollbar
ref="layoutScrollbarRef"
:class="{
'layout-scrollbar':
(!isClassicOrTransverse && !currentRouteMeta.isLink && !currentRouteMeta.isIframe) ||
(!isClassicOrTransverse && currentRouteMeta.isLink && !currentRouteMeta.isIframe),
}"
>
<LayoutParentView
:style="{
padding: !isClassicOrTransverse || (currentRouteMeta.isLink && currentRouteMeta.isIframe) ? '0' : '15px',
transition: 'padding 0.3s ease-in-out',
}"
/>
<Footer v-if="themeConfig.isFooter" />
<el-main class="layout-main" :style="isFixedHeader ? `height: calc(100% - ${setMainHeight})` : `minHeight: calc(100% - ${setMainHeight})`">
<el-scrollbar ref="layoutMainScrollbarRef" class="layout-main-scroll" wrap-class="layout-main-scroll" view-class="layout-main-scroll">
<LayoutParentView />
<LayoutFooter v-if="isFooter" />
</el-scrollbar>
<el-backtop target=".layout-backtop .el-scrollbar__wrap" />
</el-main>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive, getCurrentInstance, watch, onMounted, computed } from 'vue';
import { defineAsyncComponent, defineComponent, onMounted, computed, ref } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useThemeConfig } from '/@/stores/themeConfig';
import { NextLoading } from '/@/utils/loading';
import LayoutParentView from '/@/layout/routerView/parent.vue';
import Footer from '/@/layout/footer/index.vue';
// 定义接口来定义对象的类型
interface MainState {
headerHeight: string | number;
currentRouteMeta: any;
}
export default defineComponent({
name: 'layoutMain',
components: { LayoutParentView, Footer },
components: {
LayoutParentView: defineAsyncComponent(() => import('/@/layout/routerView/parent.vue')),
LayoutFooter: defineAsyncComponent(() => import('/@/layout/footer/index.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const layoutMainScrollbarRef = ref('');
const route = useRoute();
const storesTagsViewRoutes = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const state = reactive<MainState>({
headerHeight: '',
currentRouteMeta: {},
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
// 设置 footer 显示/隐藏
const isFooter = computed(() => {
return themeConfig.value.isFooter && !route.meta.isIframe;
});
// 判断布局
const isClassicOrTransverse = computed(() => {
const { layout } = themeConfig.value;
return layout === 'classic' || layout === 'transverse';
// 设置 header 固定
const isFixedHeader = computed(() => {
return themeConfig.value.isFixedHeader;
});
// 设置主内容区的高度
const setMainHeight = computed(() => {
if (isTagsViewCurrenFull.value) return '0px';
const { isTagsview, layout } = themeConfig.value;
if (isTagsview && layout !== 'classic') return '85px';
else return '51px';
});
// 设置 main 的高度
const initHeaderHeight = () => {
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
let { isTagsview } = themeConfig.value;
if (isTagsview) return (state.headerHeight = bool ? `86px` : `115px`);
else return (state.headerHeight = `80px`);
};
// 初始化获取当前路由 meta用于设置 iframes padding
const initGetMeta = () => {
state.currentRouteMeta = route.meta;
};
// 页面加载前
onMounted(async () => {
await initGetMeta();
initHeaderHeight();
NextLoading.done();
onMounted(() => {
NextLoading.done(600);
});
// 监听路由变化
watch(
() => route.path,
() => {
state.currentRouteMeta = route.meta;
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
state.headerHeight = bool ? `86px` : `115px`;
proxy.$refs.layoutScrollbarRef.update();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
(val) => {
state.currentRouteMeta = route.meta;
const bool = state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe;
state.headerHeight = val.isTagsview ? (bool ? `86px` : `115px`) : '51px';
proxy.$refs?.layoutScrollbarRef?.update();
},
{
deep: true,
}
);
return {
themeConfig,
isClassicOrTransverse,
...toRefs(state),
layoutMainScrollbarRef,
isFooter,
isFixedHeader,
setMainHeight,
};
},
});

View File

@ -1,5 +1,5 @@
<template>
<div class="layout-footer mt15" v-show="isDelayFooter">
<div class="layout-footer pb15">
<div class="layout-footer-warp">
<div>vue-next-adminMade by lyt with </div>
<div class="mt5">深圳市 xxx 公司版权所有</div>
@ -8,28 +8,10 @@
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent } from 'vue';
import { onBeforeRouteUpdate } from 'vue-router';
import { defineComponent } from 'vue';
export default defineComponent({
name: 'layoutFooter',
setup() {
const state = reactive({
isDelayFooter: true,
});
// 路由改变时,等主界面动画加载完毕再显示 footer
onBeforeRouteUpdate(() => {
setTimeout(() => {
state.isDelayFooter = false;
setTimeout(() => {
state.isDelayFooter = true;
}, 800);
}, 0);
});
return {
...toRefs(state),
};
},
});
</script>
@ -41,7 +23,7 @@ export default defineComponent({
margin: auto;
color: var(--el-text-color-secondary);
text-align: center;
animation: error-num 1s ease-in-out;
animation: error-num 0.3s ease;
}
}
</style>

View File

@ -3,10 +3,11 @@
</template>
<script lang="ts">
import { onBeforeMount, onUnmounted, getCurrentInstance, defineComponent, defineAsyncComponent } from 'vue';
import { onBeforeMount, onUnmounted, defineComponent, defineAsyncComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Local } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'layout',
@ -17,7 +18,6 @@ export default defineComponent({
columns: defineAsyncComponent(() => import('/@/layout/main/columns.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
// 窗口大小改变时(适配移动端)
@ -26,12 +26,12 @@ export default defineComponent({
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
themeConfig.value.isCollapse = false;
proxy.mittBus.emit('layoutMobileResize', {
mittBus.emit('layoutMobileResize', {
layout: 'defaults',
clientWidth,
});
} else {
proxy.mittBus.emit('layoutMobileResize', {
mittBus.emit('layoutMobileResize', {
layout: Local.get('oldLayout') ? Local.get('oldLayout') : themeConfig.value.layout,
clientWidth,
});

View File

@ -60,7 +60,7 @@
</template>
<script lang="ts">
import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, getCurrentInstance, defineComponent } from 'vue';
import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, defineComponent } from 'vue';
import { formatDate } from '/@/utils/formatTime';
import { Local } from '/@/utils/storage';
import { storeToRefs } from 'pinia';
@ -88,7 +88,7 @@ interface LockScreenState {
export default defineComponent({
name: 'layoutLockScreen',
setup() {
const { proxy } = <any>getCurrentInstance();
const layoutLockScreenDateRef = ref();
const layoutLockScreenInputRef = ref();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
@ -150,7 +150,7 @@ export default defineComponent({
// 获取要拖拽的初始元素
const initGetElement = () => {
nextTick(() => {
state.querySelectorEl = proxy.$refs.layoutLockScreenDateRef;
state.querySelectorEl = layoutLockScreenDateRef.value;
});
};
// 时间初始化
@ -204,6 +204,7 @@ export default defineComponent({
window.clearInterval(state.isShowLockScreenIntervalTime);
});
return {
layoutLockScreenDateRef,
layoutLockScreenInputRef,
onDown,
onMove,

View File

@ -1,34 +1,76 @@
<template>
<el-container class="layout-container flex-center">
<Header />
<LayoutHeader />
<el-container class="layout-mian-height-50">
<Aside />
<LayoutAside />
<div class="flex-center layout-backtop">
<TagsView v-if="themeConfig.isTagsview" />
<Main />
<LayoutTagsView v-if="isTagsview" />
<LayoutMain ref="layoutMainRef" />
</div>
</el-container>
<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { defineAsyncComponent, defineComponent, computed, ref, watch, nextTick, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import Aside from '/@/layout/component/aside.vue';
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
export default defineComponent({
name: 'layoutClassic',
components: { Aside, Header, Main, TagsView },
components: {
LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
LayoutTagsView: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue')),
},
setup() {
const layoutMainRef = ref<any>('');
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
return {
// 判断是否显示 tasgview
const isTagsview = computed(() => {
return themeConfig.value.isTagsview;
});
// 重置滚动条高度,更新子级 scrollbar
const updateScrollbar = () => {
layoutMainRef.value.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
}, 500);
});
};
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
() => {
updateScrollbar();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
initScrollBarHeight();
});
return {
layoutMainRef,
isTagsview,
};
},
});

View File

@ -1,40 +1,77 @@
<template>
<el-container class="layout-container">
<ColumnsAside />
<div class="layout-columns-warp">
<Aside />
<el-container class="flex-center layout-backtop" :class="{ 'layout-backtop': !isFixedHeader }">
<Header v-if="isFixedHeader" />
<el-scrollbar :class="{ 'layout-backtop': isFixedHeader }">
<Header v-if="!isFixedHeader" />
<Main />
</el-scrollbar>
</el-container>
</div>
<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
<el-container class="layout-columns-warp layout-container-view h100">
<LayoutAside />
<el-scrollbar ref="layoutScrollbarRef" class="layout-backtop">
<LayoutHeader />
<LayoutMain ref="layoutMainRef" />
</el-scrollbar>
</el-container>
</el-container>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { defineAsyncComponent, watch, defineComponent, onMounted, nextTick, ref } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import Aside from '/@/layout/component/aside.vue';
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
import ColumnsAside from '/@/layout/component/columnsAside.vue';
export default defineComponent({
name: 'layoutColumns',
components: { Aside, Header, Main, ColumnsAside },
components: {
LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
ColumnsAside: defineAsyncComponent(() => import('/@/layout/component/columnsAside.vue')),
},
setup() {
const layoutScrollbarRef = ref<any>('');
const layoutMainRef = ref<any>('');
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const isFixedHeader = computed(() => {
return themeConfig.value.isFixedHeader;
// 重置滚动条高度
const updateScrollbar = () => {
// 更新父级 scrollbar
layoutScrollbarRef.value.update();
// 更新子级 scrollbar
layoutMainRef.value.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutScrollbarRef.value.wrap$.scrollTop = 0;
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
}, 500);
});
};
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
() => {
updateScrollbar();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
initScrollBarHeight();
});
return {
isFixedHeader,
layoutScrollbarRef,
layoutMainRef,
};
},
});

View File

@ -1,46 +1,77 @@
<template>
<el-container class="layout-container">
<Aside />
<el-container class="flex-center" :class="{ 'layout-backtop': !isFixedHeader }">
<Header v-if="isFixedHeader" />
<el-scrollbar ref="layoutDefaultsScrollbarRef" :class="{ 'layout-backtop': isFixedHeader }">
<Header v-if="!isFixedHeader" />
<Main />
<LayoutAside />
<el-container class="layout-container-view h100">
<el-scrollbar ref="layoutScrollbarRef" class="layout-backtop">
<LayoutHeader />
<LayoutMain ref="layoutMainRef" />
</el-scrollbar>
</el-container>
<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script lang="ts">
import { computed, getCurrentInstance, watch, defineComponent } from 'vue';
import { defineAsyncComponent, watch, defineComponent, onMounted, nextTick, ref } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import Aside from '/@/layout/component/aside.vue';
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
import { NextLoading } from '/@/utils/loading';
export default defineComponent({
name: 'layoutDefaults',
components: { Aside, Header, Main },
components: {
LayoutAside: defineAsyncComponent(() => import('/@/layout/component/aside.vue')),
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const layoutScrollbarRef = ref<any>('');
const layoutMainRef = ref<any>('');
const route = useRoute();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const isFixedHeader = computed(() => {
return themeConfig.value.isFixedHeader;
});
// 监听路由的变化
// 重置滚动条高度
const updateScrollbar = () => {
// 更新父级 scrollbar
layoutScrollbarRef.value.update();
// 更新子级 scrollbar
layoutMainRef.value.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutScrollbarRef.value.wrap$.scrollTop = 0;
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
}, 500);
});
};
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
proxy.$refs.layoutDefaultsScrollbarRef.wrap$.scrollTop = 0;
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
() => {
updateScrollbar();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
initScrollBarHeight();
NextLoading.done(600);
});
return {
isFixedHeader,
layoutScrollbarRef,
layoutMainRef,
};
},
});

View File

@ -1,17 +1,64 @@
<template>
<el-container class="layout-container flex-center layout-backtop">
<Header />
<Main />
<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
<LayoutHeader />
<LayoutMain ref="layoutMainRef" />
</el-container>
</template>
<script lang="ts">
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
import { defineAsyncComponent, defineComponent, ref, watch, nextTick, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
export default {
export default defineComponent({
name: 'layoutTransverse',
components: { Header, Main },
};
components: {
LayoutHeader: defineAsyncComponent(() => import('/@/layout/component/header.vue')),
LayoutMain: defineAsyncComponent(() => import('/@/layout/component/main.vue')),
},
setup() {
const layoutMainRef = ref<any>('');
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
// 重置滚动条高度,更新子级 scrollbar
const updateScrollbar = () => {
layoutMainRef.value.layoutMainScrollbarRef.update();
};
// 重置滚动条高度,由于组件是异步引入的
const initScrollBarHeight = () => {
nextTick(() => {
setTimeout(() => {
updateScrollbar();
layoutMainRef.value.layoutMainScrollbarRef.wrap$.scrollTop = 0;
}, 500);
});
};
// 监听路由的变化,切换界面时,滚动条置顶
watch(
() => route.path,
() => {
initScrollBarHeight();
}
);
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(
themeConfig,
() => {
updateScrollbar();
},
{
deep: true,
}
);
// 页面加载时
onMounted(() => {
initScrollBarHeight();
});
return {
layoutMainRef,
};
},
});
</script>

View File

@ -8,15 +8,12 @@
</template>
<script lang="ts">
import { computed, reactive, toRefs, onMounted, onUnmounted, getCurrentInstance, defineComponent } from 'vue';
import { defineAsyncComponent, computed, reactive, toRefs, onMounted, onUnmounted, defineComponent } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import Breadcrumb from '/@/layout/navBars/breadcrumb/breadcrumb.vue';
import User from '/@/layout/navBars/breadcrumb/user.vue';
import Logo from '/@/layout/logo/index.vue';
import Horizontal from '/@/layout/navMenu/horizontal.vue';
import mittBus from '/@/utils/mitt';
// 定义接口来定义对象的类型
interface IndexState {
@ -25,9 +22,13 @@ interface IndexState {
export default defineComponent({
name: 'layoutBreadcrumbIndex',
components: { Breadcrumb, User, Logo, Horizontal },
components: {
Breadcrumb: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/breadcrumb.vue')),
User: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/user.vue')),
Logo: defineAsyncComponent(() => import('/@/layout/logo/index.vue')),
Horizontal: defineAsyncComponent(() => import('/@/layout/navMenu/horizontal.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
@ -52,7 +53,7 @@ export default defineComponent({
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = delClassicChildren(filterRoutesFun(routesList.value));
const resData = setSendClassicChildren(route.path);
proxy.mittBus.emit('setSendClassicChildren', resData);
mittBus.emit('setSendClassicChildren', resData);
} else {
state.menuList = filterRoutesFun(routesList.value);
}
@ -91,13 +92,13 @@ export default defineComponent({
// 页面加载时
onMounted(() => {
setFilterRoutes();
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
mittBus.off('getBreadcrumbIndexSetFilterRoutes', () => {});
});
return {
setIsShowLogo,

View File

@ -1,26 +1,28 @@
<template>
<div class="layout-search-dialog">
<el-dialog v-model="isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
<el-autocomplete
v-model="menuQuery"
:fetch-suggestions="menuSearch"
:placeholder="$t('message.user.searchPlaceholder')"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
@blur="onSearchBlur"
>
<template #prefix>
<el-icon class="el-input__icon">
<ele-Search />
</el-icon>
</template>
<template #default="{ item }">
<div>
<SvgIcon :name="item.meta.icon" class="mr5" />
{{ $t(item.meta.title) }}
</div>
</template>
</el-autocomplete>
<el-dialog v-model="isShowSearch" destroy-on-close :show-close="false">
<template #footer>
<el-autocomplete
v-model="menuQuery"
:fetch-suggestions="menuSearch"
:placeholder="$t('message.user.searchPlaceholder')"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
:fit-input-width="true"
>
<template #prefix>
<el-icon class="el-input__icon">
<ele-Search />
</el-icon>
</template>
<template #default="{ item }">
<div>
<SvgIcon :name="item.meta.icon" class="mr5" />
{{ $t(item.meta.title) }}
</div>
</template>
</el-autocomplete>
</template>
</el-dialog>
</div>
</template>
@ -103,17 +105,12 @@ export default defineComponent({
else router.push(path);
closeSearch();
};
// input 失去焦点时
const onSearchBlur = () => {
closeSearch();
};
return {
layoutMenuAutocompleteRef,
openSearch,
closeSearch,
menuSearch,
onHandleSelect,
onSearchBlur,
...toRefs(state),
};
},
@ -122,15 +119,23 @@ export default defineComponent({
<style scoped lang="scss">
.layout-search-dialog {
position: relative;
:deep(.el-dialog) {
box-shadow: unset !important;
border-radius: 0 !important;
background: rgba(0, 0, 0, 0.5);
.el-dialog__header,
.el-dialog__body {
display: none;
}
.el-dialog__footer {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: -53vh;
}
}
:deep(.el-autocomplete) {
width: 560px;
position: absolute;
top: 100px;
top: 150px;
left: 50%;
transform: translateX(-50%);
}

View File

@ -105,6 +105,17 @@
></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt14" :style="{ opacity: getThemeConfig.layout !== 'columns' ? 0.5 : 1 }">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.twoIsColumnsMenuHoverPreload') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch
v-model="getThemeConfig.isColumnsMenuHoverPreload"
size="small"
@change="onColumnsMenuHoverPreloadChange"
:disabled="getThemeConfig.layout !== 'columns'"
></el-switch>
</div>
</div>
<!-- 界面设置 -->
<el-divider content-position="left">{{ $t('message.layout.threeTitle') }}</el-divider>
@ -408,8 +419,9 @@
</template>
<script lang="ts">
import { nextTick, onUnmounted, onMounted, getCurrentInstance, defineComponent, computed, reactive, toRefs } from 'vue';
import { nextTick, onUnmounted, onMounted, defineComponent, computed, reactive, toRefs } from 'vue';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { getLightColor, getDarkColor } from '/@/utils/theme';
@ -418,11 +430,12 @@ import { Local } from '/@/utils/storage';
import Watermark from '/@/utils/wartermark';
import commonFunction from '/@/utils/commonFunction';
import other from '/@/utils/other';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'layoutBreadcrumbSeting',
setup() {
const { proxy } = <any>getCurrentInstance();
const { locale } = useI18n();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { copyText } = commonFunction();
@ -479,6 +492,11 @@ export default defineComponent({
setLocalThemeConfig();
}, 200);
};
// 2、分栏设置 ->
const onColumnsMenuHoverPreloadChange = () => {
mittBus.emit('setHoverPreload');
setLocalThemeConfig();
};
// 3、界面设置 --> 菜单水平折叠
const onThemeConfigChange = () => {
setDispatchThemeConfig();
@ -492,7 +510,7 @@ export default defineComponent({
const onClassicSplitMenuChange = () => {
getThemeConfig.value.isBreadcrumb = false;
setLocalThemeConfig();
proxy.mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
};
// 4、界面显示 --> 侧边栏 Logo
const onIsShowLogoChange = () => {
@ -508,12 +526,12 @@ export default defineComponent({
};
// 4、界面显示 --> 开启 TagsView 拖拽
const onSortableTagsViewChange = () => {
proxy.mittBus.emit('openOrCloseSortable');
mittBus.emit('openOrCloseSortable');
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 共用
const onShareTagsViewChange = () => {
proxy.mittBus.emit('openShareTagsView');
mittBus.emit('openShareTagsView');
setLocalThemeConfig();
};
// 4、界面显示 --> 灰色模式/色弱模式
@ -565,7 +583,7 @@ export default defineComponent({
onBgColorPickerChange('columnsMenuBar');
onBgColorPickerChange('columnsMenuBarColor');
};
// 关闭弹窗时,初始化变量。变量用于处理 proxy.$refs.layoutScrollbarRef.update()
// 关闭弹窗时,初始化变量。变量用于处理 layoutScrollbarRef.value.update() 更新滚动条高度
const onDrawerClose = () => {
getThemeConfig.value.isFixedHeaderChange = false;
getThemeConfig.value.isShowLogoChange = false;
@ -618,7 +636,7 @@ export default defineComponent({
if (!Local.get('frequency')) initLayoutChangeFun();
Local.set('frequency', 1);
// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
proxy.mittBus.on('layoutMobileResize', (res: any) => {
mittBus.on('layoutMobileResize', (res: any) => {
getThemeConfig.value.layout = res.layout;
getThemeConfig.value.isDrawer = false;
initLayoutChangeFun();
@ -636,14 +654,14 @@ export default defineComponent({
// 开启水印
onWartermarkChange();
// 语言国际化
if (Local.get('themeConfig')) proxy.$i18n.locale = Local.get('themeConfig').globalI18n;
if (Local.get('themeConfig')) locale.value = Local.get('themeConfig').globalI18n;
// 初始化菜单样式等
initSetStyle();
}, 100);
});
});
onUnmounted(() => {
proxy.mittBus.off('layoutMobileResize', () => {});
mittBus.off('layoutMobileResize', () => {});
});
return {
openDrawer,
@ -652,6 +670,7 @@ export default defineComponent({
onTopBarGradualChange,
onMenuBarGradualChange,
onColumnsMenuBarGradualChange,
onColumnsMenuHoverPreloadChange,
onThemeConfigChange,
onIsFixedHeaderChange,
onIsShowLogoChange,

View File

@ -77,7 +77,7 @@
</template>
<script lang="ts">
import { ref, getCurrentInstance, computed, reactive, toRefs, onMounted, defineComponent } from 'vue';
import { defineAsyncComponent, ref, computed, reactive, toRefs, onMounted, defineComponent } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessageBox, ElMessage } from 'element-plus';
import screenfull from 'screenfull';
@ -86,16 +86,17 @@ import { storeToRefs } from 'pinia';
import { useUserInfo } from '/@/stores/userInfo';
import { useThemeConfig } from '/@/stores/themeConfig';
import other from '/@/utils/other';
import mittBus from '/@/utils/mitt';
import { Session, Local } from '/@/utils/storage';
import UserNews from '/@/layout/navBars/breadcrumb/userNews.vue';
import Search from '/@/layout/navBars/breadcrumb/search.vue';
export default defineComponent({
name: 'layoutBreadcrumbUser',
components: { UserNews, Search },
components: {
UserNews: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/userNews.vue')),
Search: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/search.vue')),
},
setup() {
const { t } = useI18n();
const { proxy } = <any>getCurrentInstance();
const { locale, t } = useI18n();
const router = useRouter();
const stores = useUserInfo();
const storesThemeConfig = useThemeConfig();
@ -130,7 +131,7 @@ export default defineComponent({
};
// 布局配置 icon 点击时
const onLayoutSetingClick = () => {
proxy.mittBus.emit('openSetingsDrawer');
mittBus.emit('openSetingsDrawer');
};
// 下拉菜单点击时
const onHandleCommandClick = (path: string) => {
@ -181,7 +182,7 @@ export default defineComponent({
Local.remove('themeConfig');
themeConfig.value.globalComponentSize = size;
Local.set('themeConfig', themeConfig.value);
initComponentSize();
initI18nOrSize('globalComponentSize', 'disabledSize');
window.location.reload();
};
// 语言切换
@ -189,50 +190,19 @@ export default defineComponent({
Local.remove('themeConfig');
themeConfig.value.globalI18n = lang;
Local.set('themeConfig', themeConfig.value);
proxy.$i18n.locale = lang;
initI18n();
locale.value = lang;
other.useTitle();
initI18nOrSize('globalI18n', 'disabledI18n');
};
// 设置 element plus 组件的国际化
const setI18nConfig = (locale: string) => {
proxy.mittBus.emit('getI18nConfig', proxy.$i18n.messages[locale]);
};
// 初始化言语国际化
const initI18n = () => {
switch (Local.get('themeConfig').globalI18n) {
case 'zh-cn':
state.disabledI18n = 'zh-cn';
setI18nConfig('zh-cn');
break;
case 'en':
state.disabledI18n = 'en';
setI18nConfig('en');
break;
case 'zh-tw':
state.disabledI18n = 'zh-tw';
setI18nConfig('zh-tw');
break;
}
};
// 初始化全局组件大小
const initComponentSize = () => {
switch (Local.get('themeConfig').globalComponentSize) {
case 'large':
state.disabledSize = 'large';
break;
case 'default':
state.disabledSize = 'default';
break;
case 'small':
state.disabledSize = 'small';
break;
}
// 初始化组件大小/i18n
const initI18nOrSize = (value: string, attr: string) => {
(<any>state)[attr] = Local.get('themeConfig')[value];
};
// 页面加载时
onMounted(() => {
if (Local.get('themeConfig')) {
initI18n();
initComponentSize();
initI18nOrSize('globalComponentSize', 'disabledSize');
initI18nOrSize('globalI18n', 'disabledI18n');
}
});
return {

View File

@ -6,15 +6,16 @@
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { defineAsyncComponent, computed, defineComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import BreadcrumbIndex from '/@/layout/navBars/breadcrumb/index.vue';
import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
export default defineComponent({
name: 'layoutNavBars',
components: { BreadcrumbIndex, TagsView },
components: {
BreadcrumbIndex: defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/index.vue')),
TagsView: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/tagsView.vue')),
},
setup() {
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);

View File

@ -9,6 +9,7 @@
:data-url="v.url"
:class="{ 'is-active': isActive(v) }"
@contextmenu.prevent="onContextmenu(v, $event)"
@mousedown="onMousedownMenu(v, $event)"
@click="onTagsClick(v, k)"
:ref="
(el) => {
@ -47,6 +48,7 @@
<script lang="ts">
import {
defineAsyncComponent,
toRefs,
reactive,
onMounted,
@ -56,7 +58,6 @@ import {
onBeforeUpdate,
onBeforeMount,
onUnmounted,
getCurrentInstance,
watch,
defineComponent,
} from 'vue';
@ -71,7 +72,7 @@ import { useKeepALiveNames } from '/@/stores/keepAliveNames';
import { Session } from '/@/utils/storage';
import { isObjectValueEqual } from '/@/utils/arrayOperation';
import other from '/@/utils/other';
import Contextmenu from '/@/layout/navBars/tagsView/contextmenu.vue';
import mittBus from '/@/utils/mitt';
// 定义接口来定义对象的类型
interface TagsViewState {
@ -100,13 +101,15 @@ interface CurrentContextmenu {
query: any;
path: string;
contextMenuClickId: string | number;
commonUrl: string;
}
export default defineComponent({
name: 'layoutTagsView',
components: { Contextmenu },
components: {
Contextmenu: defineAsyncComponent(() => import('/@/layout/navBars/tagsView/contextmenu.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const tagsRefs = ref<any[]>([]);
const scrollbarRef = ref();
const contextmenuRef = ref();
@ -237,13 +240,21 @@ export default defineComponent({
// 动态路由xxx/:id/:name"):参数不同,开启多个 tagsview
if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
else await singleAddTagsView(path, to);
if (state.tagsViewList.some((v: any) => v.path === to.meta.isDynamicPath)) return false;
if (state.tagsViewList.some((v: any) => v.path === to.meta.isDynamicPath)) {
// 防止首次进入界面时(登录进入) tagsViewList 不存浏览器中
addBrowserSetSession(state.tagsViewList);
return false;
}
item = state.tagsViewRoutesList.find((v: any) => v.path === to.meta.isDynamicPath);
} else {
// 普通路由:参数不同,开启多个 tagsview
if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
else await singleAddTagsView(path, to);
if (state.tagsViewList.some((v: any) => v.path === path)) return false;
if (state.tagsViewList.some((v: any) => v.path === path)) {
// 防止首次进入界面时(登录进入) tagsViewList 不存浏览器中
addBrowserSetSession(state.tagsViewList);
return false;
}
item = state.tagsViewRoutesList.find((v: any) => v.path === path);
}
if (!item) return false;
@ -258,12 +269,20 @@ export default defineComponent({
};
// 2、刷新当前 tagsView
const refreshCurrentTagsView = async (fullPath: string) => {
const item = state.tagsViewList.find((v: any) => (getThemeConfig.value.isShareTagsView ? v.path === fullPath : v.url === fullPath));
if (item != null) {
await storesKeepALiveNames.delCachedView(item);
proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
if (item.meta.isKeepAlive) storesKeepALiveNames.addCachedView(item);
}
const decodeURIPath = decodeURI(fullPath);
let item: any = {};
state.tagsViewList.forEach((v: any) => {
v.transUrl = transUrlParams(v);
if (v.transUrl) {
if (v.transUrl === transUrlParams(v)) item = v;
} else {
if (v.path === decodeURIPath) item = v;
}
});
if (!item) return false;
await storesKeepALiveNames.delCachedView(item);
mittBus.emit('onTagsViewRefreshRouterView', fullPath);
if (item.meta.isKeepAlive) storesKeepALiveNames.addCachedView(item);
};
// 3、关闭当前 tagsView如果是设置了固定的isAffix不可以关闭
const closeCurrentTagsView = (path: string) => {
@ -339,29 +358,34 @@ export default defineComponent({
else await router.push({ name: item.name, query: item.query });
stores.setCurrenFullscreen(true);
};
// 当前项右键菜单点击,拿当前点击的路由路径对比 浏览器缓存中的 tagsView 路由数组,取当前点击项的详细路由信息
// 当前项右键菜单点击,拿 `当前点击的路由路径` 对比 `tagsView 路由数组`,取当前点击项的详细路由信息
// 防止 tagsView 非当前页演示时,操作异常
const getCurrentRouteItem = (path: string, cParams: any) => {
const itemRoute = Session.get('tagsViewList') ? Session.get('tagsViewList') : state.tagsViewList;
return itemRoute.find((v: any) => {
if (
v.path === path &&
isObjectValueEqual(
v.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
cParams && Object.keys(cParams ? cParams : {}).length > 0 ? cParams : null
)
) {
return v;
} else if (v.path === path && Object.keys(cParams ? cParams : {}).length <= 0) {
return v;
// https://gitee.com/lyt-top/vue-next-admin/issues/I61VS9
const getCurrentRouteItem = (item: any) => {
let resItem: any = {};
state.tagsViewList.forEach((v: any) => {
v.transUrl = transUrlParams(v);
if (v.transUrl) {
if (v.meta.isDynamic) {
// 路由带参数动态路由xxx/:id/:name"isDynamic
if (v.transUrl === transUrlParams(v) && v.transUrl === decodeURI(item.path)) resItem = v;
} else {
// 路由带参数:普通路由,因为有 tagsView 共用/不共用
if (v.transUrl === transUrlParams(v) && v.transUrl === item.commonUrl) resItem = v;
}
} else {
// 路由不带参数
if (v.path === decodeURI(item.path)) resItem = v;
}
});
if (!resItem) return null;
else return resItem;
};
// 当前项右键菜单点击
const onCurrentContextmenuClick = async (item: CurrentContextmenu) => {
const cParams = item.meta.isDynamic ? item.params : item.query;
if (!getCurrentRouteItem(item.path, cParams)) return ElMessage({ type: 'warning', message: '请正确输入路径及完整参数query、params' });
const { path, name, params, query, meta, url } = getCurrentRouteItem(item.path, cParams);
item.commonUrl = transUrlParams(item);
if (!getCurrentRouteItem(item)) return ElMessage({ type: 'warning', message: '请正确输入路径及完整参数query、params' });
const { path, name, params, query, meta, url } = getCurrentRouteItem(item);
switch (item.contextMenuClickId) {
case 0:
// 刷新当前
@ -396,11 +420,31 @@ export default defineComponent({
state.dropdown.y = clientY;
contextmenuRef.value.openContextmenu(v);
};
// 鼠标按下时,判断是鼠标中键就关闭当前 tasgview
const onMousedownMenu = (v: any, e: any) => {
if (!v.meta.isAffix && e.which === 2) {
const item = Object.assign({}, { contextMenuClickId: 1, ...v });
onCurrentContextmenuClick(item);
}
};
// 当前的 tagsView 项点击时
const onTagsClick = (v: any, k: number) => {
state.tagsRefsIndex = k;
router.push(v);
};
// 处理 url地址栏链接有参数时tagsview 右键菜单刷新功能失效问题
// https://gitee.com/lyt-top/vue-next-admin/issues/I5K3YO
const transUrlParams = (v: any) => {
let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
if (!params) return '';
let path = '';
for (let [key, value] of Object.entries(params)) {
if (v.meta.isDynamic) path += `/${value}`;
else path += `&${key}=${value}`;
}
// 判断是否是动态路由xxx/:id/:name"isDynamic
return v.meta.isDynamic ? `${v.path.split(':')[0]}${path.replace(/^\//, '')}` : `${v.path}${path.replace(/^&/, '?')}`;
};
// 处理 tagsView 高亮(多标签详情时使用,单标签详情未使用)
const setTagsViewHighlight = (v: any) => {
let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
@ -412,13 +456,9 @@ export default defineComponent({
// 判断是否是动态路由xxx/:id/:name"
return `${v.meta.isDynamic ? v.meta.isDynamicPath : v.path}-${path}`;
};
// 更新滚动条显示
const updateScrollbar = () => {
proxy.$refs.scrollbarRef.update();
};
// 鼠标滚轮滚动
const onHandleScroll = (e: any) => {
proxy.$refs.scrollbarRef.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
scrollbarRef.value.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
};
// tagsView 横向滚动
const tagsViewmoveToCurrentTag = () => {
@ -435,7 +475,7 @@ export default defineComponent({
// 最后 li
let liLast: any = tagsRefs.value[tagsRefs.value.length - 1];
// 当前滚动条的值
let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap$;
let scrollRefs = scrollbarRef.value.$refs.wrap$;
// 当前滚动条滚动宽度
let scrollS = scrollRefs.scrollWidth;
// 当前滚动条偏移宽度
@ -469,7 +509,7 @@ export default defineComponent({
}
}
// 更新滚动条,防止不出现
updateScrollbar();
scrollbarRef.value.update();
});
};
// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
@ -520,15 +560,15 @@ export default defineComponent({
// 拖动问题https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
window.addEventListener('resize', onSortableResize);
// 监听非本页面调用 0 刷新当前1 关闭当前2 关闭其它3 关闭全部 4 当前页全屏
proxy.mittBus.on('onCurrentContextmenuClick', (data: CurrentContextmenu) => {
mittBus.on('onCurrentContextmenuClick', (data: CurrentContextmenu) => {
onCurrentContextmenuClick(data);
});
// 监听布局配置界面开启/关闭拖拽
proxy.mittBus.on('openOrCloseSortable', () => {
mittBus.on('openOrCloseSortable', () => {
initSortable();
});
// 监听布局配置开启 TagsView 共用,为了演示还原默认值
proxy.mittBus.on('openShareTagsView', () => {
mittBus.on('openShareTagsView', () => {
if (getThemeConfig.value.isShareTagsView) {
router.push('/home');
state.tagsViewList = [];
@ -544,11 +584,11 @@ export default defineComponent({
// 页面卸载时
onUnmounted(() => {
// 取消非本页面调用监听
proxy.mittBus.off('onCurrentContextmenuClick', () => {});
mittBus.off('onCurrentContextmenuClick', () => {});
// 取消监听布局配置界面开启/关闭拖拽
proxy.mittBus.off('openOrCloseSortable', () => {});
mittBus.off('openOrCloseSortable', () => {});
// 取消监听布局配置开启 TagsView 共用
proxy.mittBus.off('openShareTagsView', () => {});
mittBus.off('openShareTagsView', () => {});
// 取消窗口 resize 监听
window.removeEventListener('resize', onSortableResize);
});
@ -583,6 +623,7 @@ export default defineComponent({
return {
isActive,
onContextmenu,
onMousedownMenu,
onTagsClick,
tagsRefs,
contextmenuRef,
@ -697,8 +738,12 @@ export default defineComponent({
.tags-style-five {
align-items: flex-end;
.tags-style-five-svg {
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='68' height='34' viewBox='0 0 68 34' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m27,0c-7.99582,0 -11.95105,0.00205 -12,12l0,6c0,8.284 -0.48549,16.49691 -8.76949,16.49691l54.37857,-0.11145c-8.284,0 -8.60908,-8.10146 -8.60908,-16.38546l0,-6c0.11145,-12.08445 -4.38441,-12 -12,-12l-13,0z' fill='%23409eff'/%3E%3C/svg%3E")
12 27 15;
-webkit-mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0ibm9uZSI+CgogPGc+CiAgPHRpdGxlPkxheWVyIDE8L3RpdGxlPgogIDxwYXRoIHRyYW5zZm9ybT0icm90YXRlKC0wLjEzMzUwNiA1MC4xMTkyIDUwKSIgaWQ9InN2Z18xIiBkPSJtMTAwLjExOTE5LDEwMGMtNTUuMjI4LDAgLTEwMCwtNDQuNzcyIC0xMDAsLTEwMGwwLDEwMGwxMDAsMHoiIG9wYWNpdHk9InVuZGVmaW5lZCIgc3Ryb2tlPSJudWxsIiBmaWxsPSIjRjhFQUU3Ii8+CiAgPHBhdGggZD0ibS0wLjYzNzY2LDcuMzEyMjhjMC4xMTkxOSwwIDAuMjE3MzcsMC4wNTc5NiAwLjQ3Njc2LDAuMTE5MTljMC4yMzIsMC4wNTQ3NyAwLjI3MzI5LDAuMDM0OTEgMC4zNTc1NywwLjExOTE5YzAuMDg0MjgsMC4wODQyOCAwLjM1NzU3LDAgMC40NzY3NiwwbDAuMTE5MTksMGwwLjIzODM4LDAiIGlkPSJzdmdfMiIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHBhdGggZD0ibTI4LjkyMTM0LDY5LjA1MjQ0YzAsMC4xMTkxOSAwLDAuMjM4MzggMCwwLjM1NzU3bDAsMC4xMTkxOWwwLDAuMTE5MTkiIGlkPSJzdmdfMyIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z180IiBoZWlnaHQ9IjAiIHdpZHRoPSIxLjMxMTA4IiB5PSI2LjgzNTUyIiB4PSItMC4wNDE3MSIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z181IiBoZWlnaHQ9IjEuNzg3ODQiIHdpZHRoPSIwLjExOTE5IiB5PSI2OC40NTY1IiB4PSIyOC45MjEzNCIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z182IiBoZWlnaHQ9IjQuODg2NzciIHdpZHRoPSIxOS4wNzAzMiIgeT0iNTEuMjkzMjEiIHg9IjM2LjY2ODY2IiBzdHJva2U9Im51bGwiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+'),
url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0ibm9uZSI+CiA8Zz4KICA8dGl0bGU+TGF5ZXIgMTwvdGl0bGU+CiAgPHBhdGggdHJhbnNmb3JtPSJyb3RhdGUoLTg5Ljc2MjQgNy4zMzAxNCA1NS4xMjUyKSIgc3Ryb2tlPSJudWxsIiBpZD0ic3ZnXzEiIGZpbGw9IiNGOEVBRTciIGQ9Im02Mi41NzQ0OSwxMTcuNTIwODZjLTU1LjIyOCwwIC0xMDAsLTQ0Ljc3MiAtMTAwLC0xMDBsMCwxMDBsMTAwLDB6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPgogIDxwYXRoIGQ9Im0tMC42Mzc2Niw3LjMxMjI4YzAuMTE5MTksMCAwLjIxNzM3LDAuMDU3OTYgMC40NzY3NiwwLjExOTE5YzAuMjMyLDAuMDU0NzcgMC4yNzMyOSwwLjAzNDkxIDAuMzU3NTcsMC4xMTkxOWMwLjA4NDI4LDAuMDg0MjggMC4zNTc1NywwIDAuNDc2NzYsMGwwLjExOTE5LDBsMC4yMzgzOCwwIiBpZD0ic3ZnXzIiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxwYXRoIGQ9Im0yOC45MjEzNCw2OS4wNTI0NGMwLDAuMTE5MTkgMCwwLjIzODM4IDAsMC4zNTc1N2wwLDAuMTE5MTlsMCwwLjExOTE5IiBpZD0ic3ZnXzMiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNCIgaGVpZ2h0PSIwIiB3aWR0aD0iMS4zMTEwOCIgeT0iNi44MzU1MiIgeD0iLTAuMDQxNzEiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNSIgaGVpZ2h0PSIxLjc4Nzg0IiB3aWR0aD0iMC4xMTkxOSIgeT0iNjguNDU2NSIgeD0iMjguOTIxMzQiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNiIgaGVpZ2h0PSI0Ljg4Njc3IiB3aWR0aD0iMTkuMDcwMzIiIHk9IjUxLjI5MzIxIiB4PSIzNi42Njg2NiIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiA8L2c+Cjwvc3ZnPg=='),
url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect rx='8' width='100%' height='100%' fill='%23F8EAE7'/></svg>");
-webkit-mask-size: 18px 30px, 20px 30px, calc(100% - 30px) calc(100% + 17px);
-webkit-mask-position: right bottom, left bottom, center top;
-webkit-mask-repeat: no-repeat;
}
.layout-navbars-tagsview-ul-li {
padding: 0 5px;

View File

@ -17,7 +17,7 @@
{{ $t(val.meta.title) }}
</template>
<template #title v-else>
<a :href="val.meta.isLink" target="_blank" rel="opener" class="w100">
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="val.meta.icon" />
{{ $t(val.meta.title) }}
</a>
@ -31,16 +31,19 @@
</template>
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, nextTick, onBeforeMount } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { defineAsyncComponent, toRefs, reactive, computed, defineComponent, onMounted, nextTick, onBeforeMount, ref } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import SubItem from '/@/layout/navMenu/subItem.vue';
import { verifyUrl } from '/@/utils/toolsValidate';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'navMenuHorizontal',
components: { SubItem },
components: {
SubItem: defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue')),
},
props: {
menuList: {
type: Array,
@ -48,12 +51,13 @@ export default defineComponent({
},
},
setup(props) {
const { proxy } = <any>getCurrentInstance();
const elMenuHorizontalScrollRef = ref();
const stores = useRoutesList();
const storesThemeConfig = useThemeConfig();
const { routesList } = storeToRefs(stores);
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const router = useRouter();
const state = reactive({
defaultActive: null,
});
@ -64,14 +68,14 @@ export default defineComponent({
// 设置横向滚动条可以鼠标滚轮滚动
const onElMenuHorizontalScroll = (e: any) => {
const eventDelta = e.wheelDelta || -e.deltaY * 40;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap$.scrollLeft = proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap$.scrollLeft + eventDelta / 4;
elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft = elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft + eventDelta / 4;
};
// 初始化数据,页面刷新时,滚动条滚动到对应位置
const initElMenuOffsetLeft = () => {
nextTick(() => {
let els: any = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
if (!els) return false;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap$.scrollLeft = els.offsetLeft;
elMenuHorizontalScrollRef.value.$refs.wrap$.scrollLeft = els.offsetLeft;
});
};
// 路由过滤递归函数
@ -109,6 +113,13 @@ export default defineComponent({
else state.defaultActive = path;
}
};
// 打开外部链接
const onALinkClick = (val: any) => {
const { origin, pathname } = window.location;
router.push(val.path);
if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
else window.open(`${origin}${pathname}#${val.meta.isLink}`);
};
// 页面加载前
onBeforeMount(() => {
setCurrentRouterHighlight(route);
@ -124,12 +135,14 @@ export default defineComponent({
// 修复经典布局开启切割菜单时点击tagsView后左侧导航菜单数据不变的问题
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
proxy.mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
}
});
return {
elMenuHorizontalScrollRef,
menuLists,
onElMenuHorizontalScroll,
onALinkClick,
...toRefs(state),
};
},

View File

@ -14,7 +14,7 @@
<span>{{ $t(val.meta.title) }}</span>
</template>
<template v-else>
<a :href="val.meta.isLink" target="_blank" rel="opener" class="w100">
<a class="w100" @click.prevent="onALinkClick(val)">
<SvgIcon :name="val.meta.icon" />
{{ $t(val.meta.title) }}
</a>
@ -26,6 +26,8 @@
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { useRouter } from 'vue-router';
import { verifyUrl } from '/@/utils/toolsValidate';
export default defineComponent({
name: 'navMenuSubItem',
@ -36,12 +38,21 @@ export default defineComponent({
},
},
setup(props) {
const router = useRouter();
// 获取父级菜单数据
const chils = computed(() => {
return <any>props.chil;
});
// 打开外部链接
const onALinkClick = (val: any) => {
const { origin, pathname } = window.location;
router.push(val.path);
if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
else window.open(`${origin}${pathname}#${val.meta.isLink}`);
};
return {
chils,
onALinkClick,
};
},
});

View File

@ -22,7 +22,7 @@
<span>{{ $t(val.meta.title) }}</span>
</template>
<template #title v-else>
<a :href="val.meta.isLink" target="_blank" rel="opener" class="w100">{{ $t(val.meta.title) }}</a>
<a class="w100" @click.prevent="onALinkClick(val)">{{ $t(val.meta.title) }}</a>
</template>
</el-menu-item>
</template>
@ -31,15 +31,17 @@
</template>
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, onMounted, watch } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { defineAsyncComponent, toRefs, reactive, computed, defineComponent, onMounted, watch } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import SubItem from '/@/layout/navMenu/subItem.vue';
import { verifyUrl } from '/@/utils/toolsValidate';
export default defineComponent({
name: 'navMenuVertical',
components: { SubItem },
components: {
SubItem: defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue')),
},
props: {
menuList: {
type: Array,
@ -50,6 +52,7 @@ export default defineComponent({
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const router = useRouter();
const state = reactive({
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
@ -70,6 +73,13 @@ export default defineComponent({
if (pathSplit.length >= 4 && meta.isHide) return pathSplit.splice(0, 3).join('/');
else return path;
};
// 打开外部链接
const onALinkClick = (val: any) => {
const { origin, pathname } = window.location;
router.push(val.path);
if (verifyUrl(val.meta.isLink)) window.open(val.meta.isLink);
else window.open(`${origin}${pathname}#${val.meta.isLink}`);
};
// 设置菜单的收起/展开
watch(
themeConfig.value,
@ -94,6 +104,7 @@ export default defineComponent({
return {
menuLists,
getThemeConfig,
onALinkClick,
...toRefs(state),
};
},

View File

@ -1,65 +1,103 @@
<template>
<div class="layout-view-bg-white flex mt1" :style="{ height: `calc(100vh - ${setIframeHeight}`, border: 'none' }" v-loading="iframeLoading">
<iframe :src="iframeUrl" frameborder="0" height="100%" width="100%" ref="iframeDom" v-show="!iframeLoading"></iframe>
<div class="layout-padding layout-padding-unset layout-iframe">
<div class="layout-padding-auto layout-padding-view">
<div class="w100" v-for="v in setIframeList" :key="v.path" v-loading="v.meta.loading" element-loading-background="white">
<transition-group :name="name" mode="out-in">
<iframe
:src="v.meta.isLink"
:key="v.path"
frameborder="0"
height="100%"
width="100%"
style="position: absolute"
:data-url="v.path"
v-show="getRoutePath === v.path"
ref="iframeRef"
/>
</transition-group>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, onMounted, nextTick, watch, computed } from 'vue';
import { storeToRefs } from 'pinia';
import { defineComponent, computed, watch, ref, nextTick } from 'vue';
import { useRoute } from 'vue-router';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
export default defineComponent({
name: 'layoutIfameView',
setup() {
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
name: 'layoutIframeView',
props: {
refreshKey: {
type: String,
default: () => '',
},
name: {
type: String,
default: () => 'slide-right',
},
list: {
type: Array,
default: () => [],
},
},
setup(props) {
const iframeRef = ref();
const route = useRoute();
const state = reactive({
iframeDom: null as HTMLIFrameElement | null,
iframeLoading: true,
iframeUrl: '',
// 处理 list 列表,当打开时,才进行加载
const setIframeList = computed(() => {
return (<any>props.list).filter((v: any) => v.meta.isIframeOpen);
});
// 初始化页面加载 loading
const initIframeLoad = () => {
state.iframeUrl = <any>route.meta.isLink;
// 获取 iframe 当前路由 path
const getRoutePath = computed(() => {
return route.path;
});
// 关闭 iframe loading
const closeIframeLoading = (val: string, item: any) => {
nextTick(() => {
state.iframeLoading = true;
const iframe = state.iframeDom;
if (!iframe) return false;
iframe.onload = () => {
state.iframeLoading = false;
};
if (!iframeRef.value) return false;
iframeRef.value.forEach((v: any) => {
if (v.dataset.url === val) {
v.onload = () => {
if (item.meta.isIframeOpen && item.meta.loading) item.meta.loading = false;
};
}
});
});
};
// 设置 iframe 的高度
const setIframeHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `1px`;
} else {
if (isTagsview) return `86px`;
else return `51px`;
}
});
// 页面加载时
onMounted(() => {
initIframeLoad();
});
// 监听路由变化,多个 iframe 时使用
// 监听路由变化,初始化 iframe 数据,防止多个 iframe 时,切换不生效
watch(
() => route.path,
() => route.fullPath,
(val) => {
const item: any = props.list.find((v: any) => v.path === val);
if (!item) return false;
if (!item.meta.isIframeOpen) item.meta.isIframeOpen = true;
closeIframeLoading(val, item);
},
{
immediate: true,
}
);
// 监听 iframe refreshKey 变化,用于 tagsview 右键菜单刷新
watch(
() => props.refreshKey,
() => {
initIframeLoad();
const item: any = props.list.find((v: any) => v.path === route.path);
if (!item) return false;
if (item.meta.isIframeOpen) item.meta.isIframeOpen = false;
setTimeout(() => {
item.meta.isIframeOpen = true;
item.meta.loading = true;
closeIframeLoading(route.fullPath, item);
});
},
{
deep: true,
}
);
return {
setIframeHeight,
...toRefs(state),
iframeRef,
setIframeList,
getRoutePath,
};
},
});

View File

@ -1,16 +1,22 @@
<template>
<div class="layout-view-bg-white flex layout-view-link" :style="{ height: `calc(100vh - ${setLinkHeight}` }">
<a :href="currentRouteMeta.isLink" target="_blank" rel="opener" class="flex-margin">
{{ $t(currentRouteMeta.title) }}{{ currentRouteMeta.isLink }}
</a>
<div class="layout-padding layout-link-container">
<div class="layout-padding-auto layout-padding-view">
<div class="layout-link-warp">
<i class="layout-link-icon iconfont icon-xingqiu"></i>
<div class="layout-link-msg">页面 "{{ $t(currentRouteMeta.title) }}" 已在新窗口中打开</div>
<el-button class="mt30" round size="default" @click="onGotoFullPage">
<i class="iconfont icon-lianjie"></i>
<span>立即前往体验</span>
</el-button>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive, computed, watch } from 'vue';
import { defineComponent, toRefs, reactive, watch } from 'vue';
import { useRoute, RouteMeta } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { verifyUrl } from '/@/utils/toolsValidate';
// 定义接口来定义对象的类型
interface LinkViewState {
@ -27,8 +33,6 @@ interface LinkViewRouteMeta extends RouteMeta {
export default defineComponent({
name: 'layoutLinkView',
setup() {
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const state = reactive<LinkViewState>({
currentRouteMeta: {
@ -36,12 +40,12 @@ export default defineComponent({
title: '',
},
});
// 设置 link 的高度
const setLinkHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsview) return `115px`;
else return `80px`;
});
// 立即前往
const onGotoFullPage = () => {
const { origin, pathname } = window.location;
if (verifyUrl(state.currentRouteMeta.isLink)) window.open(state.currentRouteMeta.isLink);
else window.open(`${origin}${pathname}#${state.currentRouteMeta.isLink}`);
};
// 监听路由的变化,设置内容
watch(
() => route.path,
@ -53,9 +57,57 @@ export default defineComponent({
}
);
return {
setLinkHeight,
onGotoFullPage,
...toRefs(state),
};
},
});
</script>
<style scoped lang="scss">
.layout-link-container {
.layout-link-warp {
margin: auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
i.layout-link-icon {
position: relative;
font-size: 100px;
color: var(--el-color-primary);
&::after {
content: '';
position: absolute;
left: 50px;
top: 0;
width: 15px;
height: 100px;
background: linear-gradient(
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(235, 255, 255, 0.5),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01)
);
transform: rotate(-15deg);
animation: toRight 5s linear infinite;
}
}
.layout-link-msg {
font-size: 12px;
color: var(--next-bg-topBarColor);
opacity: 0.7;
margin-top: 15px;
}
}
}
</style>

View File

@ -1,41 +1,52 @@
<template>
<div class="h100">
<div class="layout-parent">
<router-view v-slot="{ Component }">
<transition :name="setTransitionName" mode="out-in">
<keep-alive :include="getKeepAliveNames">
<component :is="Component" :key="refreshRouterViewKey" class="w100" />
<component :is="Component" :key="refreshRouterViewKey" class="w100" v-show="!isIframePage" />
</keep-alive>
</transition>
</router-view>
<transition :name="setTransitionName" mode="out-in">
<Iframes class="w100" v-show="isIframePage" :refreshKey="iframeRefreshKey" :name="setTransitionName" :list="iframeList" />
</transition>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick, watch, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { defineAsyncComponent, computed, defineComponent, toRefs, reactive, onBeforeMount, onUnmounted, nextTick, watch, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useKeepALiveNames } from '/@/stores/keepAliveNames';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Session } from '/@/utils/storage';
import mittBus from '/@/utils/mitt';
// 定义接口来定义对象的类型
interface ParentViewState {
refreshRouterViewKey: null | string;
refreshRouterViewKey: string;
iframeRefreshKey: string;
keepAliveNameList: string[];
iframeList: string[];
}
export default defineComponent({
name: 'layoutParentView',
components: {
Iframes: defineAsyncComponent(() => import('/@/layout/routerView/iframes.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const route = useRoute();
const router = useRouter();
const storesKeepAliveNames = useKeepALiveNames();
const storesThemeConfig = useThemeConfig();
const { keepAliveNames, cachedViews } = storeToRefs(storesKeepAliveNames);
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive<ParentViewState>({
refreshRouterViewKey: null,
refreshRouterViewKey: '', // 非 iframe tagsview 右键菜单刷新时
iframeRefreshKey: '', // iframe tagsview 右键菜单刷新时
keepAliveNameList: [],
iframeList: [],
});
// 设置主界面切换动画
const setTransitionName = computed(() => {
@ -45,20 +56,37 @@ export default defineComponent({
const getKeepAliveNames = computed(() => {
return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
});
// 设置 iframe 显示/隐藏
const isIframePage = computed(() => {
return route.meta.isIframe;
});
// 获取 iframe 组件列表(未进行渲染)
const getIframeListRoutes = async () => {
router.getRoutes().forEach((v: any) => {
if (v.meta.isIframe) {
v.meta.isIframeOpen = false;
v.meta.loading = true;
state.iframeList.push({ ...v });
}
});
};
// 页面加载前,处理缓存,页面刷新时路由缓存处理
onBeforeMount(() => {
state.keepAliveNameList = keepAliveNames.value;
proxy.mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
state.refreshRouterViewKey = null;
state.refreshRouterViewKey = '';
state.iframeRefreshKey = '';
nextTick(() => {
state.refreshRouterViewKey = fullPath;
state.iframeRefreshKey = fullPath;
state.keepAliveNameList = keepAliveNames.value;
});
});
});
// 页面加载时
onMounted(() => {
getIframeListRoutes();
// https://gitee.com/lyt-top/vue-next-admin/issues/I58U75
// https://gitee.com/lyt-top/vue-next-admin/issues/I59RXK
nextTick(() => {
@ -69,18 +97,24 @@ export default defineComponent({
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('onTagsViewRefreshRouterView', () => {});
mittBus.off('onTagsViewRefreshRouterView', () => {});
});
// 监听路由变化,防止 tagsView 多标签时,切换动画消失
// https://toscode.gitee.com/lyt-top/vue-next-admin/pulls/38/files
watch(
() => route.fullPath,
() => {
state.refreshRouterViewKey = decodeURI(route.fullPath);
},
{
immediate: true,
}
);
return {
route,
setTransitionName,
getKeepAliveNames,
isIframePage,
...toRefs(state),
};
},

View File

@ -9,7 +9,6 @@ import other from '/@/utils/other';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import '/@/theme/index.scss';
import mitt from 'mitt';
import VueGridLayout from 'vue-grid-layout';
const app = createApp(App);
@ -18,5 +17,3 @@ directive(app);
other.elSvg(app);
app.use(pinia).use(router).use(ElementPlus, { i18n: i18n.global.t }).use(i18n).use(VueGridLayout).mount('#app');
app.config.globalProperties.mittBus = mitt();

View File

@ -80,6 +80,8 @@ export function setCacheTagsViewRoutes() {
*/
export function setFilterRouteEnd() {
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
// notFoundAndNoPower 防止 404、401 不在 layout 布局中不设置的话404、401 界面将全屏显示
// 关联问题 No match found for location with path 'xxx'
filterRouteEnd[0].children = [...filterRouteEnd[0].children, ...notFoundAndNoPower];
return filterRouteEnd;
}

View File

@ -64,6 +64,8 @@ export async function frontEndsResetRoute() {
*/
export function setFilterRouteEnd() {
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
// notFoundAndNoPower 防止 404、401 不在 layout 布局中不设置的话404、401 界面将全屏显示
// 关联问题 No match found for location with path 'xxx'
filterRouteEnd[0].children = [...setFilterRoute(filterRouteEnd[0].children), ...notFoundAndNoPower];
return filterRouteEnd;
}

View File

@ -7,7 +7,7 @@ import { useKeepALiveNames } from '/@/stores/keepAliveNames';
import { useRoutesList } from '/@/stores/routesList';
import { useThemeConfig } from '/@/stores/themeConfig';
import { Session } from '/@/utils/storage';
import { staticRoutes } from '/@/router/route';
import { staticRoutes, notFoundAndNoPower } from '/@/router/route';
import { initFrontEndControlRoutes } from '/@/router/frontEnd';
import { initBackEndControlRoutes } from '/@/router/backEnd';
@ -24,7 +24,6 @@ import { initBackEndControlRoutes } from '/@/router/backEnd';
const storesThemeConfig = useThemeConfig(pinia);
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isRequestRoutes } = themeConfig.value;
if (isRequestRoutes) staticRoutes.splice(0, 1);
/**
* 创建一个可以被 Vue 应用程序使用的路由实例
@ -33,7 +32,13 @@ if (isRequestRoutes) staticRoutes.splice(0, 1);
*/
export const router = createRouter({
history: createWebHashHistory(),
routes: staticRoutes,
/**
* 说明:
* 1、notFoundAndNoPower 默认添加 404、401 界面,防止一直提示 No match found for location with path 'xxx'
* 2、backEnd.ts(后端控制路由)、frontEnd.ts(前端控制路由) 中也需要加 notFoundAndNoPower 404、401 界面。
* 防止 404、401 不在 layout 布局中不设置的话404、401 界面将全屏显示
*/
routes: [...notFoundAndNoPower, ...staticRoutes],
});
/**
@ -108,13 +113,13 @@ router.beforeEach(async (to, from, next) => {
if (isRequestRoutes) {
// 后端控制路由:路由数据初始化,防止刷新时丢失
await initBackEndControlRoutes();
// 动态添加路由:防止非首页刷新时跳转回首页的问题
// 确保 addRoute() 时动态添加的路由已经被完全加载上去
next({ ...to, replace: true });
// 解决刷新时,一直跳 404 页面问题,关联问题 No match found for location with path 'xxx'
// to.query 防止页面刷新时普通路由带参数时参数丢失。动态路由xxx/:id/:name"isDynamic 无需处理
next({ path: to.path, query: to.query });
} else {
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
await initFrontEndControlRoutes();
next({ ...to, replace: true });
next({ path: to.path, query: to.query });
}
} else {
next();

View File

@ -4,7 +4,7 @@ import { RouteRecordRaw } from 'vue-router';
* 路由meta对象参数说明
* meta: {
* title: 菜单栏及 tagsView 栏、菜单搜索名称(国际化)
* isLink 是否超链接菜单,开启外链条件,`1、isLink: 链接地址不为空`
* isLink 是否超链接菜单,开启外链条件,`1、isLink: 链接地址不为空 2、isIframe:false`
* isHide 是否隐藏此路由
* isKeepAlive 是否缓存组件状态
* isAffix 是否固定在 tagsView 栏上
@ -1020,6 +1020,11 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
roles: ['admin'],
icon: 'ele-ChatLineRound',
},
/**
* 打开内置全屏
* component 都为 `() => import('/@/layout/routerView/link.vue')`
* isLink 链接为内置的路由地址,地址为 staticRoutes 中定义
*/
children: [
{
path: '/visualizing/visualizingLinkDemo1',
@ -1027,7 +1032,7 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
component: () => import('/@/layout/routerView/link.vue'),
meta: {
title: 'message.router.visualizingLinkDemo1',
isLink: `${import.meta.env.VITE_API_URL}#/visualizingDemo1`,
isLink: '/visualizingDemo1',
isHide: false,
isKeepAlive: false,
isAffix: false,
@ -1042,7 +1047,7 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
component: () => import('/@/layout/routerView/link.vue'),
meta: {
title: 'message.router.visualizingLinkDemo2',
isLink: `${import.meta.env.VITE_API_URL}#/visualizingDemo2`,
isLink: '/visualizingDemo2',
isHide: false,
isKeepAlive: false,
isAffix: false,
@ -1114,14 +1119,29 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
},
},
{
path: '/iframes',
name: 'layoutIfameView',
path: '/iframesOne',
name: 'layoutIframeViewOne',
component: () => import('/@/layout/routerView/iframes.vue'),
meta: {
title: 'message.router.layoutIfameView',
title: 'message.router.layoutIframeViewOne',
isLink: 'https://nodejs.org/zh-cn/',
isHide: false,
isKeepAlive: false,
isKeepAlive: true,
isAffix: true,
isIframe: true,
roles: ['admin'],
icon: 'iconfont icon-neiqianshujuchucun',
},
},
{
path: '/iframesTwo',
name: 'layoutIframeViewTwo',
component: () => import('/@/layout/routerView/iframes.vue'),
meta: {
title: 'message.router.layoutIframeViewTwo',
isLink: 'https://undraw.co/illustrations',
isHide: false,
isKeepAlive: true,
isAffix: true,
isIframe: true,
roles: ['admin'],

View File

@ -52,6 +52,7 @@ export interface ThemeConfigState {
columnsMenuBar: string;
columnsMenuBarColor: string;
isColumnsMenuBarColorGradual: boolean;
isColumnsMenuHoverPreload: boolean;
isCollapse: boolean;
isUniqueOpened: boolean;
isFixedHeader: boolean;
@ -82,6 +83,7 @@ export interface ThemeConfigState {
isRequestRoutes: boolean;
globalTitle: string;
globalViceTitle: string;
globalViceTitleMsg: string;
globalI18n: string;
globalComponentSize: string;
}

View File

@ -19,8 +19,7 @@ export const useKeepALiveNames = defineStore('keepALiveNames', {
this.keepAliveNames = data;
},
async addCachedView(view: any) {
if (this.cachedViews.includes(view.name)) return;
if (view.meta.isKeepAlive) this.cachedViews.push(view.name);
if (view.meta.isKeepAlive) this.cachedViews?.push(view.name);
},
async delCachedView(view: any) {
const index = this.cachedViews.indexOf(view.name);

View File

@ -47,6 +47,8 @@ export const useThemeConfig = defineStore('themeConfig', {
columnsMenuBarColor: '#e6e6e6',
// 是否开启分栏菜单背景颜色渐变
isColumnsMenuBarColorGradual: false,
// 是否开启分栏菜单鼠标悬停预加载(预览菜单)
isColumnsMenuHoverPreload: false,
/**
* 界面设置
@ -54,7 +56,7 @@ export const useThemeConfig = defineStore('themeConfig', {
// 是否开启菜单水平折叠效果
isCollapse: false,
// 是否开启菜单手风琴效果
isUniqueOpened: false,
isUniqueOpened: true,
// 是否开启固定 Header
isFixedHeader: false,
// 初始化变量,用于更新菜单 el-scrollbar 的高度,请勿删除
@ -88,15 +90,15 @@ export const useThemeConfig = defineStore('themeConfig', {
// 是否开启 TagsView 共用
isShareTagsView: false,
// 是否开启 Footer 底部版权信息
isFooter: false,
isFooter: true,
// 是否开启灰色模式
isGrayscale: false,
// 是否开启色弱模式
isInvert: false,
// 是否开启水印
isWartermark: false,
isWartermark: true,
// 水印文案
wartermarkText: 'small@小柒',
wartermarkText: 'vue-next-admin',
/**
* 其它设置
@ -132,6 +134,8 @@ export const useThemeConfig = defineStore('themeConfig', {
globalTitle: 'vue-next-admin',
// 网站副标题(登录页顶部文字)
globalViceTitle: 'vueNextAdmin',
// 网站副标题(登录页顶部文字)
globalViceTitleMsg: '专注、免费、开源、维护、解疑',
// 默认初始语言,可选值"<zh-cn|en|zh-tw>",默认 zh-cn
globalI18n: 'zh-cn',
// 默认全局组件大小,可选值"<large|'default'|small>",默认 'large'

View File

@ -65,7 +65,7 @@ export const useUserInfo = defineStore('userInfo', {
authBtnList: defaultAuthBtnList,
};
resolve(userInfos);
}, 3000);
}, 0);
});
},
},

View File

@ -46,6 +46,14 @@ body,
.layout-container {
width: 100%;
height: 100%;
.layout-pd {
padding: 15px !important;
}
.layout-flex {
display: flex;
flex-direction: column;
flex: 1;
}
.layout-aside {
background: var(--next-bg-menuBar);
box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
@ -61,24 +69,63 @@ body,
}
.layout-header {
padding: 0 !important;
height: auto !important;
}
.layout-main {
padding: 0 !important;
overflow: hidden;
width: 100%;
background-color: var(--next-bg-main-color);
display: flex;
flex-direction: column;
// 内层 el-scrollbar样式用于界面高度自适应main.vue
.layout-main-scroll {
@extend .layout-flex;
.layout-parent {
@extend .layout-flex;
position: relative;
}
}
}
// 用于界面高度自适应
.layout-padding {
@extend .layout-pd;
position: absolute;
left: 0;
top: 0;
height: 100%;
overflow: hidden;
@extend .layout-flex;
&-auto {
height: inherit;
@extend .layout-flex;
}
&-view {
background: var(--el-color-white);
width: 100%;
height: 100%;
border-radius: 4px;
border: 1px solid var(--el-border-color-light, #ebeef5);
overflow: hidden;
}
}
// 用于界面高度自适应,主视图区 main 的内边距,用于 iframe
.layout-padding-unset {
padding: 0 !important;
&-view {
border-radius: 0 !important;
border: none !important;
}
}
// 用于设置 iframe loading 时的高度loading 垂直居中显示)
.layout-iframe {
.el-loading-parent--relative {
height: 100%;
}
}
.el-scrollbar {
width: 100%;
}
// 此字段多次用到,建议不删除,如需修改,请重写覆盖样式
.layout-view-bg-white {
background: var(--el-color-white);
width: 100%;
height: 100%;
border-radius: 4px;
border: 1px solid var(--el-border-color-light, #ebeef5);
}
.layout-el-aside-br-color {
border-right: 1px solid var(--el-border-color-light, #ebeef5);
}
@ -122,10 +169,6 @@ body,
z-index: 9999998;
animation: error-img 0.3s;
}
.layout-scrollbar {
@extend .el-scrollbar;
padding: 15px;
}
.layout-mian-height-50 {
height: calc(100vh - 50px);
}

View File

@ -92,3 +92,56 @@
opacity: 0;
}
}
/* 登录页动画
------------------------------- */
@keyframes loginLeft {
0% {
left: -100%;
}
50%,
100% {
left: 100%;
}
}
@keyframes loginTop {
0% {
top: -100%;
}
50%,
100% {
top: 100%;
}
}
@keyframes loginRight {
0% {
right: -100%;
}
50%,
100% {
right: 100%;
}
}
@keyframes loginBottom {
0% {
bottom: -100%;
}
50%,
100% {
bottom: 100%;
}
}
/* 左右左 link.vue
------------------------------- */
@keyframes toRight {
0% {
left: -5px;
}
50% {
left: 100%;
}
100% {
left: -5px;
}
}

View File

@ -233,4 +233,9 @@
border-color: var(--el-border-color-lighter) !important;
}
}
// loading
.el-loading-mask {
background-color: var(--next-bg-main) !important;
}
}

View File

@ -30,9 +30,24 @@
/* Form 表单
------------------------------- */
.el-form {
// 用于修改弹窗时表单内容间隔太大问题,如系统设置的新增菜单弹窗里的表单内容
.el-form-item:last-of-type {
margin-bottom: 0 !important;
}
// 修复行内表单最后一个 el-form-item 位置下移问题
&.el-form--inline {
.el-form-item--large.el-form-item:last-of-type {
margin-bottom: 22px !important;
}
.el-form-item--default.el-form-item:last-of-type,
.el-form-item--small.el-form-item:last-of-type {
margin-bottom: 18px !important;
}
}
// https://gitee.com/lyt-top/vue-next-admin/issues/I5K1PM
.el-form-item .el-form-item__label .el-icon {
margin-right: 0px;
}
}
/* Alert 警告
@ -248,17 +263,24 @@
.el-scrollbar__bar {
z-index: 4;
}
/*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
.el-scrollbar__wrap {
max-height: 100%; /*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
max-height: 100%;
}
.el-select-dropdown .el-scrollbar__wrap {
overflow-x: scroll !important;
}
/*修复Select 选择器高度问题*/
.el-select-dropdown__wrap {
max-height: 274px !important; /*修复Select 选择器高度问题*/
max-height: 274px !important;
}
/*修复Cascader 级联选择器高度问题*/
.el-cascader-menu__wrap.el-scrollbar__wrap {
height: 204px !important; /*修复Cascader 级联选择器高度问题*/
height: 204px !important;
}
/*用于界面高度自适应main.vue区分 scrollbar__view防止其它使用 scrollbar 的地方出现滚动条消失*/
.layout-container-view .el-scrollbar__view {
height: 100%;
}
/* Drawer 抽屉

View File

@ -6,11 +6,14 @@
.el-form-item__label {
width: 100% !important;
text-align: left !important;
// 移动端 label 右对齐问题
justify-content: flex-start !important;
}
.el-form-item__content {
margin-left: 0 !important;
}
.el-form-item {
// 响应式表单时,登录页需要重新处理
display: unset !important;
}
}

View File

@ -1,32 +1,23 @@
@import './index.scss';
/* 页面宽度小于992px
/* 页面宽度小于1200px
------------------------------- */
@media screen and (max-width: $lg) {
@media screen and (max-width: $lg) and (min-width: $xs) {
.login-container {
.login-icon-group {
&::before {
content: '';
height: 70% !important;
transition: all 0.3s ease;
}
&::after {
content: '';
width: 100px !important;
height: 200px !important;
transition: all 0.3s ease;
.login-left {
.login-left-img {
top: 90% !important;
left: 12% !important;
width: 30% !important;
height: 18% !important;
}
}
}
}
/* 页面宽度小于992px
------------------------------- */
@media screen and (max-width: $md) {
.login-content {
right: unset !important;
left: 50% !important;
transform: translate(-50%, -50%) translate3d(0, 0, 0) !important;
.login-right {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
@ -34,19 +25,34 @@
------------------------------- */
@media screen and (max-width: $xs) {
.login-container {
.login-icon-group {
display: none !important;
.login-left {
display: none;
}
.login-content {
.login-right {
width: 100% !important;
height: 100% !important;
padding: 20px 0 !important;
border-radius: 0 !important;
box-shadow: unset !important;
border: none !important;
}
.el-form-item {
display: flex !important;
.login-right-warp {
width: 100% !important;
height: 100% !important;
border: none !important;
.login-right-warp-mian {
.el-form-item {
display: flex !important;
}
.login-right-warp-main-title {
font-size: 20px !important;
}
}
.login-right-warp-one {
&::after {
right: 0 !important;
}
}
.login-right-warp-two {
&::before {
bottom: 1px !important;
}
}
}
}
}
}
@ -55,9 +61,14 @@
------------------------------- */
@media screen and (max-width: $us) {
.login-container {
.login-content-title {
font-size: 18px !important;
transition: all 0.3s ease;
.login-right {
.login-right-warp {
.login-right-warp-mian {
.login-right-warp-main-title {
font-size: 18px !important;
}
}
}
}
}
}

View File

@ -1,7 +1,7 @@
/* wangeditor富文本编辑器
/* wangeditor 富文本编辑器
------------------------------- */
.editor-container {
z-index: 9999;
z-index: 10; // 用于 wangeditor 点击全屏时
.w-e-toolbar {
border: 1px solid var(--el-border-color-light, #ebeef5) !important;
border-bottom: 1px solid var(--el-border-color-light, #ebeef5) !important;

14
src/types/mitt.ts Normal file
View File

@ -0,0 +1,14 @@
// mitt 事件类型定义
export type MittType = {
openSetingsDrawer?: string; // 打开布局设置弹窗
restoreDefault?: string; // 分栏布局,鼠标移入、移出数据显示
setSendColumnsChildren?: string; // 分栏布局,鼠标移入、移出菜单数据传入到 navMenu 下的菜单中
setSendClassicChildren?: string; // 经典布局,开启切割菜单时,菜单数据传入到 navMenu 下的菜单中
getBreadcrumbIndexSetFilterRoutes?: string; // 布局设置弹窗,开启切割菜单时,菜单数据传入到 navMenu 下的菜单中
layoutMobileResize?: object; // 浏览器窗口改变时,用于适配移动端界面显示
openOrCloseSortable?: string; // 布局设置弹窗,开启 TagsView 拖拽
openShareTagsView?: string; // 布局设置弹窗,开启 TagsView 共用
onTagsViewRefreshRouterView?: any; // tagsview 刷新界面
onCurrentContextmenuClick?: any; // tagsview 右键菜单每项点击时
setHoverPreload?: string; // 分栏菜单鼠标悬停预加载
};

View File

@ -8,7 +8,7 @@ export function judementSameArr(newArr: unknown[] | string[], oldArr: string[]):
const news = removeDuplicate(newArr);
const olds = removeDuplicate(oldArr);
let count = 0;
const leng = olds.length;
const leng = news.length;
for (let i in olds) {
for (let j in news) {
if (olds[i] === news[j]) count++;

View File

@ -32,11 +32,13 @@ export const NextLoading = {
window.nextLoading = true;
},
// 移除 loading
done: () => {
done: (time: number = 0) => {
nextTick(() => {
window.nextLoading = false;
const el = <HTMLElement>document.querySelector('.loading-next');
el?.parentNode?.removeChild(el);
setTimeout(() => {
window.nextLoading = false;
const el = <HTMLElement>document.querySelector('.loading-next');
el?.parentNode?.removeChild(el);
}, time);
});
},
};

9
src/utils/mitt.ts Normal file
View File

@ -0,0 +1,9 @@
// https://www.npmjs.com/package/mitt
import mitt, { Emitter } from 'mitt';
import { MittType } from '/@/types/mitt';
// 类型
const emitter: Emitter<MittType> = mitt<MittType>();
// 导出
export default emitter;

View File

@ -51,10 +51,10 @@ export function setTagsViewNameI18n(item: any) {
let tagsViewName: any = '';
const { query, params, meta } = item;
if (query?.tagsViewName || params?.tagsViewName) {
if (/\/zh-cn|en|zh-tw\//.test(query?.tagsViewName) || /\/(zh-cn|en|zh-tw)\//.test(params?.tagsViewName)) {
if (/\/zh-cn|en|zh-tw\//.test(query?.tagsViewName) || /\/zh-cn|en|zh-tw\//.test(params?.tagsViewName)) {
// 国际化
const urlTagsParams = (query?.tagsViewName && JSON.parse(query?.tagsViewName)) || (params?.tagsViewName && JSON.parse(params?.tagsViewName));
tagsViewName = urlTagsParams[i18n.global.locale];
tagsViewName = urlTagsParams[i18n.global.locale.value];
} else {
// 非国际化
tagsViewName = query?.tagsViewName || params?.tagsViewName;

View File

@ -14,7 +14,7 @@ const setWatermark = (str: string) => {
const div = document.createElement('div');
div.id = id;
div.style.pointerEvents = 'none';
div.style.top = '15px';
div.style.top = '0px';
div.style.left = '0px';
div.style.position = 'fixed';
div.style.zIndex = '10000000';

View File

@ -1,6 +1,6 @@
<template>
<div class="chart-scrollbar layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
<div class="chart-warp">
<div class="chart-scrollbar layout-padding">
<div class="chart-warp layout-padding-auto layout-padding-view">
<div class="chart-warp-top">
<ChartHead />
</div>
@ -202,12 +202,11 @@
</template>
<script lang="ts">
import { toRefs, reactive, computed, onMounted, getCurrentInstance, watch, nextTick, onActivated, defineComponent } from 'vue';
import { toRefs, reactive, onMounted, watch, nextTick, onActivated, defineComponent, ref } from 'vue';
import ChartHead from '/@/views/chart/head.vue';
import * as echarts from 'echarts';
import 'echarts-wordcloud';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { skyList, dBtnList, chartData4List } from '/@/views/chart/chart';
@ -215,10 +214,12 @@ export default defineComponent({
name: 'chartIndex',
components: { ChartHead },
setup() {
const { proxy } = <any>getCurrentInstance();
const storesThemeConfig = useThemeConfig();
const chartsCenterOneRef = ref();
const chartsSevenDaysRef = ref();
const chartsWarningRef = ref();
const chartsMonitorRef = ref();
const chartsInvestmentRef = ref();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
const state = reactive({
skyList,
@ -226,19 +227,9 @@ export default defineComponent({
chartData4List,
myCharts: [],
});
// 设置主内容的高度
const initTagViewHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
// 初始化中间图表1
const initChartsCenterOne = () => {
const myChart = echarts.init(proxy.$refs.chartsCenterOneRef);
const myChart = echarts.init(chartsCenterOneRef.value);
const option = {
grid: {
top: 15,
@ -299,7 +290,7 @@ export default defineComponent({
};
// 初始化近7天产品追溯扫码统计
const initChartsSevenDays = () => {
const myChart = echarts.init(proxy.$refs.chartsSevenDaysRef);
const myChart = echarts.init(chartsSevenDaysRef.value);
const option = {
grid: {
top: 15,
@ -344,7 +335,7 @@ export default defineComponent({
};
// 初始化近30天预警总数
const initChartsWarning = () => {
const myChart = echarts.init(proxy.$refs.chartsWarningRef);
const myChart = echarts.init(chartsWarningRef.value);
const option = {
grid: {
top: 50,
@ -379,7 +370,7 @@ export default defineComponent({
};
// 初始化当前设备监测
const initChartsMonitor = () => {
const myChart = echarts.init(proxy.$refs.chartsMonitorRef);
const myChart = echarts.init(chartsMonitorRef.value);
const option = {
grid: {
top: 15,
@ -419,7 +410,7 @@ export default defineComponent({
};
// 初始化近7天投入品记录
const initChartsInvestment = () => {
const myChart = echarts.init(proxy.$refs.chartsInvestmentRef);
const myChart = echarts.init(chartsInvestmentRef.value);
const option = {
grid: {
top: 15,
@ -480,7 +471,11 @@ export default defineComponent({
}
);
return {
initTagViewHeight,
chartsCenterOneRef,
chartsSevenDaysRef,
chartsWarningRef,
chartsMonitorRef,
chartsInvestmentRef,
...toRefs(state),
};
},

View File

@ -1,39 +1,34 @@
<template>
<div class="error layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
<div class="error-flex">
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">401</div>
<div class="left-item-animation left-item-title">{{ $t('message.noAccess.accessTitle') }}</div>
<div class="left-item-animation left-item-msg">{{ $t('message.noAccess.accessMsg') }}</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" round @click="onSetAuth">{{ $t('message.noAccess.accessBtn') }}</el-button>
<div class="error layout-padding">
<div class="layout-padding-auto layout-padding-view">
<div class="error-flex">
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">401</div>
<div class="left-item-animation left-item-title">{{ $t('message.noAccess.accessTitle') }}</div>
<div class="left-item-animation left-item-msg">{{ $t('message.noAccess.accessMsg') }}</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" size="default" round @click="onSetAuth">{{ $t('message.noAccess.accessBtn') }}</el-button>
</div>
</div>
</div>
</div>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/3333f265772a4fa89287993500ecbf96.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/3333f265772a4fa89287993500ecbf96.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { defineComponent } from 'vue';
import { Session } from '/@/utils/storage';
export default defineComponent({
name: '401',
setup() {
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
const onSetAuth = () => {
// https://gitee.com/lyt-top/vue-next-admin/issues/I5C3JS
// 清除缓存/token等
@ -41,19 +36,8 @@ export default defineComponent({
// 使用 reload 时,不需要调用 resetRoute() 重置路由
window.location.reload();
};
// 设置主内容的高度
const initTagViewHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
return {
onSetAuth,
initTagViewHeight,
};
},
});
@ -62,8 +46,6 @@ export default defineComponent({
<style scoped lang="scss">
.error {
height: 100%;
background-color: var(--el-color-white);
display: flex;
.error-flex {
margin: auto;
display: flex;

View File

@ -1,56 +1,40 @@
<template>
<div class="error layout-view-bg-white" :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
<div class="error-flex">
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">404</div>
<div class="left-item-animation left-item-title">{{ $t('message.notFound.foundTitle') }}</div>
<div class="left-item-animation left-item-msg">{{ $t('message.notFound.foundMsg') }}</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" round @click="onGoHome">{{ $t('message.notFound.foundBtn') }}</el-button>
<div class="error layout-padding">
<div class="layout-padding-auto layout-padding-view">
<div class="error-flex">
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">404</div>
<div class="left-item-animation left-item-title">{{ $t('message.notFound.foundTitle') }}</div>
<div class="left-item-animation left-item-msg">{{ $t('message.notFound.foundMsg') }}</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" size="default" round @click="onGoHome">{{ $t('message.notFound.foundBtn') }}</el-button>
</div>
</div>
</div>
</div>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/9eb1d85a417f4ed1ba7107f149ce3da1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/9eb1d85a417f4ed1ba7107f149ce3da1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { defineComponent } from 'vue';
import { useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
export default defineComponent({
name: '404',
setup() {
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
const router = useRouter();
const onGoHome = () => {
router.push('/');
};
// 设置主内容的高度
const initTagViewHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
return {
onGoHome,
initTagViewHeight,
};
},
});
@ -59,8 +43,6 @@ export default defineComponent({
<style scoped lang="scss">
.error {
height: 100%;
background-color: var(--el-color-white);
display: flex;
.error-flex {
margin: auto;
display: flex;

View File

@ -1,18 +1,20 @@
<template>
<el-card shadow="hover" header="复制剪切演示">
<el-alert
title="感谢优秀的 `vue-clipboard3`项目地址https://github.com/JamieCurnow/vue-clipboard3`"
type="success"
:closable="false"
class="mb15"
></el-alert>
<el-input placeholder="请输入内容" v-model="copyVal">
<template #append>
<el-button @click="copyText(copyVal)">复制链接</el-button>
</template>
</el-input>
<el-input placeholder="先点击上方 `复制链接` 按钮,然后 `Ctrl + V` 进行粘贴! " v-model="shearVal" class="mt15"> </el-input>
</el-card>
<div class="layout-pd">
<el-card shadow="hover" header="复制剪切演示">
<el-alert
title="感谢优秀的 `vue-clipboard3`项目地址https://github.com/JamieCurnow/vue-clipboard3`"
type="success"
:closable="false"
class="mb15"
></el-alert>
<el-input placeholder="请输入内容" v-model="copyVal">
<template #append>
<el-button @click="copyText(copyVal)">复制链接</el-button>
</template>
</el-input>
<el-input placeholder="先点击上方 `复制链接` 按钮,然后 `Ctrl + V` 进行粘贴! " v-model="shearVal" class="mt15"> </el-input>
</el-card>
</div>
</template>
<script lang="ts">

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="layout-pd">
<el-card shadow="hover" header="数字滚动演示">
<el-alert
title="感谢优秀的 `countup.js`项目地址https://github.com/inorganik/countUp.js"

View File

@ -1,5 +1,5 @@
<template>
<div class="croppers-container">
<div class="croppers-container layout-pd">
<el-card shadow="hover" header="cropper 图片裁剪">
<el-alert
title="感谢优秀的 `cropperjs`项目地址https://github.com/fengyuanchen/cropperjs"
@ -24,16 +24,17 @@
</template>
<script lang="ts">
import { ref, toRefs, reactive, defineComponent } from 'vue';
import CropperDialog from '/@/components/cropper/index.vue';
import { defineAsyncComponent, ref, toRefs, reactive, defineComponent } from 'vue';
export default defineComponent({
name: 'funCropper',
components: { CropperDialog },
components: {
CropperDialog: defineAsyncComponent(() => import('/@/components/cropper/index.vue')),
},
setup() {
const cropperDialogRef = ref();
const state = reactive({
cropperImg: 'https://img1.baidu.com/it/u=2813520958,2218166536&fm=26&fmt=auto&gp=0.jpg',
cropperImg: 'https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500',
});
// 打开裁剪弹窗
const onCropperDialogOpen = () => {

View File

@ -1,42 +1,25 @@
<template>
<div :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
<div class="layout-view-bg-white">
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<div ref="echartsMap" style="height: 100%"></div>
</div>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed, onMounted, defineComponent } from 'vue';
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
import * as echarts from 'echarts';
import 'echarts/extension/bmap/bmap';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { echartsMapList, echartsMapData } from './mock';
export default defineComponent({
name: 'funEchartsMap',
setup() {
const storesThemeConfig = useThemeConfig();
const storesTagsViewRoutes = useTagsViewRoutes();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
const state: any = reactive({
echartsMap: null,
echartsMapList,
echartsMapData,
});
// 设置主内容的高度
const initTagViewHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
// echartsMap 将坐标信息和对应物理量的值合在一起
const convertData = (data: any) => {
let res = [];
@ -133,7 +116,6 @@ export default defineComponent({
initEchartsMap();
});
return {
initTagViewHeight,
...toRefs(state),
};
},

View File

@ -1,5 +1,5 @@
<template>
<div class="grid-layout-container">
<div class="grid-layout-container layout-pd">
<el-card shadow="hover" header="vue-grid-layout 拖拽布局演示">
<el-alert
title="感谢优秀的 `vue-grid-layout`项目地址https://github.com/jbaysolutions/vue-grid-layout"

View File

@ -1,5 +1,5 @@
<template>
<div ref="printRef">
<div ref="printRef" class="layout-pd">
<el-card shadow="hover" header="打印演示">
<el-alert
title="感谢优秀的 `print-js`项目地址https://github.com/crabbly/Print.js。请在打印弹窗 `更多设置` 中开启 `背景图形。`"

View File

@ -1,5 +1,5 @@
<template>
<div class="qrcode-container">
<div class="qrcode-container layout-pd">
<el-card shadow="hover" header="qrcodejs2 二维码生成">
<el-alert
title="感谢优秀的 `qrcodejs2`项目地址https://github.com/davidshimjs/qrcodejs"
@ -23,19 +23,19 @@
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, getCurrentInstance, defineComponent } from 'vue';
import { toRefs, reactive, onMounted, defineComponent, ref } from 'vue';
import QRCode from 'qrcodejs2-fixes';
export default defineComponent({
name: 'funQrcode',
setup() {
const { proxy } = getCurrentInstance() as any;
const qrcodeRef = ref();
const state = reactive({
qrcode: '',
});
// 初始化生成二维码
const initQrcode = () => {
new QRCode(proxy.$refs.qrcodeRef, {
new QRCode(qrcodeRef.value, {
text: `https://lyt-top.gitee.io/vue-next-admin-preview/#/login?t=${new Date().getTime()}`,
width: 125,
height: 125,
@ -45,7 +45,7 @@ export default defineComponent({
};
// 重新生成
const onInitQrcode = () => {
proxy.$refs.qrcodeRef.innerHTML = '';
qrcodeRef.value.innerHTML = '';
initQrcode();
};
// 页面加载时
@ -53,6 +53,7 @@ export default defineComponent({
initQrcode();
});
return {
qrcodeRef,
onInitQrcode,
...toRefs(state),
};

View File

@ -1,5 +1,5 @@
<template>
<div class="splitpanes-container">
<div class="splitpanes-container layout-pd">
<el-card shadow="hover" header="splitpanes 窗格拆分器">
<el-alert
title="感谢优秀的 `splitpanes`项目地址https://github.com/antoniandre/splitpanes"

View File

@ -1,5 +1,5 @@
<template>
<div class="fun-tagsview">
<div class="fun-tagsview layout-pd">
<NoticeBar
text="已删除非当前页 tagsView 演示后续有时间可以再加回来tagsview 支持多标签(参数不同)、单标签共用(参数不同)"
background="#ecf5ff"
@ -63,37 +63,38 @@
</template>
<script lang="ts">
import { getCurrentInstance, reactive, toRefs, defineComponent } from 'vue';
import NoticeBar from '/@/components/noticeBar/index.vue';
import { defineAsyncComponent, reactive, toRefs, defineComponent } from 'vue';
import { useRoute } from 'vue-router';
import mittBus from '/@/utils/mitt';
export default defineComponent({
name: 'funTagsView',
components: { NoticeBar },
components: {
NoticeBar: defineAsyncComponent(() => import('/@/components/noticeBar/index.vue')),
},
setup() {
const { proxy } = getCurrentInstance() as any;
const route = useRoute();
const state = reactive({});
// 0 刷新当前1 关闭当前2 关闭其它3 关闭全部 4 当前页全屏
// 1、刷新当前 tagsView
const refreshCurrentTagsView = () => {
proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 0, ...route }));
mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 0, ...route }));
};
// 2、关闭当前 tagsView
const closeCurrentTagsView = () => {
proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 1, ...route }));
};
// 3、关闭其它 tagsView
const closeOtherTagsView = () => {
proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 2, ...route }));
mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 2, ...route }));
};
// 4、关闭全部 tagsView
const closeAllTagsView = () => {
proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 3, ...route }));
mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 3, ...route }));
};
// 5、开启当前页面全屏
const openCurrenFullscreen = () => {
proxy.mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 4, ...route }));
mittBus.emit('onCurrentContextmenuClick', Object.assign({}, { contextMenuClickId: 4, ...route }));
};
return {
refreshCurrentTagsView,

View File

@ -1,5 +1,5 @@
<template>
<div class="editor-container">
<div class="editor-container layout-pd">
<el-card shadow="hover" header="wangeditor富文本编辑器">
<el-alert
title="感谢优秀的 `wangeditor`项目地址https://github.com/wangeditor-team/wangEditor"
@ -7,24 +7,26 @@
:closable="false"
class="mb15"
></el-alert>
<Editor :is-disable="false" v-model="editorVal" />
<Editor v-model="editor.value" :disable="editor.disable" />
</el-card>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
import Editor from '/@/components/editor/index.vue';
import { defineAsyncComponent, toRefs, reactive, defineComponent } from 'vue';
export default defineComponent({
name: 'funWangEditor',
components: { Editor },
components: {
Editor: defineAsyncComponent(() => import('/@/components/editor/index.vue')),
},
setup() {
const state = reactive({
editorVal: '',
editor: {
value: '<p><span style="color: rgb(51, 51, 51); background-color: rgb(255, 255, 255); font-size: 14px;">胡歌1982年9月20日出生于上海市徐汇区中国内地影视男演员、流行乐歌手</span><a href="https://baike.baidu.com/item/%E6%B0%91%E7%9B%9F/1971441?fromModule=lemma_inlink" target="_blank" style="text-indent: 28px; text-align: start;">民盟</a><span style="color: rgb(51, 51, 51); background-color: rgb(255, 255, 255); font-size: 14px;">盟员</span><span style="color: rgb(51, 102, 204); background-color: rgb(255, 255, 255); font-size: 12px;"><sup> [1]</sup></span><a href="" target="" style="text-indent: 28px; text-align: start;"> </a><span style="color: rgb(51, 51, 51); background-color: rgb(255, 255, 255); font-size: 14px;"> ,毕业于</span><a href="https://baike.baidu.com/item/%E4%B8%8A%E6%B5%B7%E6%88%8F%E5%89%A7%E5%AD%A6%E9%99%A2/1736818?fromModule=lemma_inlink" target="_blank" style="text-indent: 28px; text-align: start;">上海戏剧学院</a><span style="color: rgb(51, 51, 51); background-color: rgb(255, 255, 255); font-size: 14px;">表演系。</span></p>',
disable: false,
},
});
// 页面加载时
onMounted(() => {});
return {
...toRefs(state),
};

View File

@ -1,5 +1,5 @@
<template>
<div class="home-container">
<div class="home-container layout-pd">
<el-row :gutter="15" class="home-card-one mb15">
<el-col
:xs="24"

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="layout-pd">
<el-alert
title="温馨提示1、此页面无法模拟后端控制路由因为 `gitee` 上所请求的 `json` 菜单数据线上会出现跨域的情况json地址
https://gitee.com/lyt-top/vue-next-admin-images/raw/master/menu/adminMenu.json。2、本地接口请求文件位置`/src/api/menu/index.ts`。

View File

@ -1,6 +1,6 @@
<template>
<div>
<LimitsFrontEndPage />
<div class="layout-pd">
<LimitsFrontEndPage style="padding: 0 !important" />
<!-- 演示1组件方式 -->
<el-card shadow="hover" header="演示1组件方式" class="mt15">
<el-row class="mb10" style="color: #808080">单个权限验证:value="xxx"</el-row>
@ -335,17 +335,18 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { defineAsyncComponent, defineComponent } from 'vue';
import { ElMessage } from 'element-plus';
import LimitsFrontEndPage from '/@/views/limits/frontEnd/page/index.vue';
import Auth from '/@/components/auth/auth.vue';
import Auths from '/@/components/auth/auths.vue';
import AuthAll from '/@/components/auth/authAll.vue';
import { auth, auths, authAll } from '/@/utils/authFunction';
export default defineComponent({
name: 'limitsFrontEndBtn',
components: { LimitsFrontEndPage, Auth, Auths, AuthAll },
components: {
LimitsFrontEndPage: defineAsyncComponent(() => import('/@/views/limits/frontEnd/page/index.vue')),
Auth: defineAsyncComponent(() => import('/@/components/auth/auth.vue')),
Auths: defineAsyncComponent(() => import('/@/components/auth/auths.vue')),
AuthAll: defineAsyncComponent(() => import('/@/components/auth/authAll.vue')),
},
setup() {
// 单个权限验证
const onAuthClick = () => {

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="layout-pd">
<el-alert
title="温馨提示此权限页面代码及效果只作为演示使用若出现不可逆转的bug请尝试 `F5` 刷新页面。若实际项目中非要实现此用户权限切换功能,
请在切换方法 `onRadioChange` 最后面添加刷新代码 `window.location.reload()`。 请注意按钮权限页面中的演示2指令模式、演示3函数模式

View File

@ -1,7 +1,7 @@
<template>
<el-form size="large" class="login-content-form">
<el-form-item class="login-animation1">
<el-input type="text" :placeholder="$t('message.account.accountPlaceholder1')" v-model="ruleForm.userName" clearable autocomplete="off">
<el-input text :placeholder="$t('message.account.accountPlaceholder1')" v-model="ruleForm.userName" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><ele-User /></el-icon>
</template>
@ -29,14 +29,7 @@
</el-form-item>
<el-form-item class="login-animation3">
<el-col :span="15">
<el-input
type="text"
maxlength="4"
:placeholder="$t('message.account.accountPlaceholder3')"
v-model="ruleForm.code"
clearable
autocomplete="off"
>
<el-input text maxlength="4" :placeholder="$t('message.account.accountPlaceholder3')" v-model="ruleForm.code" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><ele-Position /></el-icon>
</template>
@ -44,11 +37,11 @@
</el-col>
<el-col :span="1"></el-col>
<el-col :span="8">
<el-button class="login-content-code">1234</el-button>
<el-button class="login-content-code" v-waves>1234</el-button>
</el-col>
</el-form-item>
<el-form-item class="login-animation4">
<el-button type="primary" class="login-content-submit" round @click="onSignIn" :loading="loading.signIn">
<el-button type="primary" class="login-content-submit" round v-waves @click="onSignIn" :loading="loading.signIn">
<span>{{ $t('message.account.accountBtnText') }}</span>
</el-button>
</el-form-item>

View File

@ -1,7 +1,7 @@
<template>
<el-form size="large" class="login-content-form">
<el-form-item class="login-animation1">
<el-input type="text" :placeholder="$t('message.mobile.placeholder1')" v-model="ruleForm.userName" clearable autocomplete="off">
<el-input text :placeholder="$t('message.mobile.placeholder1')" v-model="ruleForm.userName" clearable autocomplete="off">
<template #prefix>
<i class="iconfont icon-dianhua el-input__icon"></i>
</template>
@ -9,7 +9,7 @@
</el-form-item>
<el-form-item class="login-animation2">
<el-col :span="15">
<el-input type="text" maxlength="4" :placeholder="$t('message.mobile.placeholder2')" v-model="ruleForm.code" clearable autocomplete="off">
<el-input text maxlength="4" :placeholder="$t('message.mobile.placeholder2')" v-model="ruleForm.code" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><ele-Position /></el-icon>
</template>
@ -17,11 +17,11 @@
</el-col>
<el-col :span="1"></el-col>
<el-col :span="8">
<el-button class="login-content-code">{{ $t('message.mobile.codeText') }}</el-button>
<el-button v-waves class="login-content-code">{{ $t('message.mobile.codeText') }}</el-button>
</el-col>
</el-form-item>
<el-form-item class="login-animation3">
<el-button round type="primary" class="login-content-submit">
<el-button round type="primary" v-waves class="login-content-submit">
<span>{{ $t('message.mobile.btnText') }}</span>
</el-button>
</el-form-item>

View File

@ -1,12 +1,15 @@
<template>
<div class="login-scan-container">
<div ref="qrcodeRef"></div>
<div class="font12 mt20 login-msg">{{ $t('message.scan.text') }}</div>
<div class="font12 mt20 login-msg">
<i class="iconfont icon-saoyisao mr5"></i>
<span>{{ $t('message.scan.text') }}</span>
</div>
</div>
</template>
<script lang="ts">
import { ref, defineComponent, onMounted } from 'vue';
import { defineComponent, ref, onMounted, nextTick } from 'vue';
import QRCode from 'qrcodejs2-fixes';
export default defineComponent({
@ -15,20 +18,24 @@ export default defineComponent({
const qrcodeRef = ref<HTMLElement | null>(null);
// 初始化生成二维码
const initQrcode = () => {
(qrcodeRef.value as HTMLElement).innerHTML = '';
new QRCode(qrcodeRef.value, {
text: `https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi`,
width: 260,
height: 260,
colorDark: '#000000',
colorLight: '#ffffff',
nextTick(() => {
(<HTMLElement>qrcodeRef.value).innerHTML = '';
new QRCode(qrcodeRef.value, {
text: `https://qm.qq.com/cgi-bin/qm/qr?k=RdUY97Vx0T0vZ_1OOu-X1yFNkWgDwbjC&jump_from=webapi`,
width: 260,
height: 260,
colorDark: '#000000',
colorLight: '#ffffff',
});
});
};
// 页面加载时
onMounted(() => {
initQrcode();
});
return { qrcodeRef };
return {
qrcodeRef,
};
},
});
</script>
@ -41,7 +48,7 @@ export default defineComponent({
animation-fill-mode: forwards;
}
.login-scan-container {
padding: 20px;
padding: 0 20px 20px;
display: flex;
flex-direction: column;
text-align: center;
@ -51,6 +58,9 @@ export default defineComponent({
margin: auto;
}
.login-msg {
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-placeholder);
@extend .login-scan-animation;
animation-delay: 0.2s;

View File

@ -1,29 +1,41 @@
<template>
<div class="login-container">
<div class="login-icon-group">
<div class="login-icon-group-title">
<div class="login-container flex">
<div class="login-left">
<div class="login-left-logo">
<img :src="logoMini" />
<div class="login-icon-group-title-text font25">{{ getThemeConfig.globalViceTitle }}</div>
</div>
<img :src="loginIconTwo" class="login-icon-group-icon" />
</div>
<div class="login-content">
<div class="login-content-main">
<h4 class="login-content-title ml15">{{ getThemeConfig.globalTitle }}后台模板</h4>
<div v-if="!isScan">
<el-tabs v-model="tabsActiveName">
<el-tab-pane :label="$t('message.label.one1')" name="account">
<Account />
</el-tab-pane>
<el-tab-pane :label="$t('message.label.two2')" name="mobile">
<Mobile />
</el-tab-pane>
</el-tabs>
<div class="login-left-logo-text">
<span>{{ getThemeConfig.globalViceTitle }}</span>
<span class="login-left-logo-text-msg">{{ getThemeConfig.globalViceTitleMsg }}</span>
</div>
<Scan v-if="isScan" />
<div class="login-content-main-sacn" @click="isScan = !isScan">
<i class="iconfont" :class="isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
<div class="login-content-main-sacn-delta"></div>
</div>
<div class="login-left-img">
<img :src="loginMain" />
</div>
<img :src="loginBg" class="login-left-waves" />
</div>
<div class="login-right flex">
<div class="login-right-warp flex-margin">
<span class="login-right-warp-one"></span>
<span class="login-right-warp-two"></span>
<div class="login-right-warp-mian">
<div class="login-right-warp-main-title">{{ getThemeConfig.globalTitle }} 欢迎您</div>
<div class="login-right-warp-main-form">
<div v-if="!isScan">
<el-tabs v-model="tabsActiveName">
<el-tab-pane :label="$t('message.label.one1')" name="account">
<Account />
</el-tab-pane>
<el-tab-pane :label="$t('message.label.two2')" name="mobile">
<Mobile />
</el-tab-pane>
</el-tabs>
</div>
<Scan v-if="isScan" />
<div class="login-content-main-sacn" @click="isScan = !isScan">
<i class="iconfont" :class="isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
<div class="login-content-main-sacn-delta"></div>
</div>
</div>
</div>
</div>
</div>
@ -31,29 +43,25 @@
</template>
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, onMounted } from 'vue';
import { defineAsyncComponent, defineComponent, onMounted, reactive, toRefs, computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import logoMini from '/@/assets/logo-mini.svg';
import loginIconTwo from '/@/assets/login-icon-two.svg';
import { NextLoading } from '/@/utils/loading';
import Account from '/@/views/login/component/account.vue';
import Mobile from '/@/views/login/component/mobile.vue';
import Scan from '/@/views/login/component/scan.vue';
// 定义接口来定义对象的类型
interface LoginState {
tabsActiveName: string;
isScan: boolean;
}
import logoMini from '/@/assets/logo-mini.svg';
import loginMain from '/@/assets/login-main.svg';
import loginBg from '/@/assets/login-bg.svg';
export default defineComponent({
name: 'loginIndex',
components: { Account, Mobile, Scan },
components: {
Account: defineAsyncComponent(() => import('/@/views/login/component/account.vue')),
Mobile: defineAsyncComponent(() => import('/@/views/login/component/mobile.vue')),
Scan: defineAsyncComponent(() => import('/@/views/login/component/scan.vue')),
},
setup() {
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const state = reactive<LoginState>({
const state = reactive({
tabsActiveName: 'account',
isScan: false,
});
@ -67,7 +75,8 @@ export default defineComponent({
});
return {
logoMini,
loginIconTwo,
loginBg,
loginMain,
getThemeConfig,
...toRefs(state),
};
@ -77,122 +86,176 @@ export default defineComponent({
<style scoped lang="scss">
.login-container {
width: 100%;
height: 100%;
position: relative;
background: var(--el-color-white);
.login-icon-group {
width: 100%;
height: 100%;
.login-left {
flex: 1;
position: relative;
.login-icon-group-title {
background-color: rgba(211, 239, 255, 1);
margin-right: 100px;
.login-left-logo {
display: flex;
align-items: center;
position: absolute;
top: 50px;
left: 80px;
display: flex;
align-items: center;
z-index: 1;
animation: logoAnimation 0.3s ease;
img {
width: 30px;
height: 30px;
width: 52px;
height: 52px;
}
&-text {
padding-left: 15px;
color: var(--el-color-primary);
.login-left-logo-text {
display: flex;
flex-direction: column;
span {
margin-left: 10px;
font-size: 28px;
color: #26a59a;
}
.login-left-logo-text-msg {
font-size: 12px;
color: #32a99e;
}
}
}
&::before {
content: '';
.login-left-img {
position: absolute;
bottom: 0;
left: 0;
width: 60%;
overflow: hidden;
height: 80%;
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='1200' height='770' xmlns='http://www.w3.org/2000/svg' fill='none'%3E%3Cg%3E%3Cpath id='svg_1' d='M58.4 47.77C104.6 59.51 135.26 67.37 162.11 78.04C188.97 88.72 226.33 102.69 265.92 123.55C305.51 144.4 366.96 167.09 441.43 121.52C515.9 75.95 546.48 61.01 577.69 46.27C608.9 31.53 625.86 23.69 680.26 12.28C734.65 0.87 837.29 10.7 867.29 21.8C897.29 32.9 935.51 51.9 962.21 95.45C988.9 139.01 972.91 177.36 951.37 221.39C929.83 265.43 883.49 306 890.44 337.33C897.4 368.66 974.73 412.18 974.73 411.47C974.73 412.18 1066.36 457.62 1106.36 491.06C1146.36 524.5 1178.8 563.36 1184.03 579.63C1189.26 595.9 1200.4 622.49 1181.55 676.88C1162.71 731.26 1127.16 764.32 1115.31 778.64C1103.45 792.96 5.34 783.61 4.32 784.63C3.3 785.65 -172.34 2.38 1.13 35.04L58.4 47.77L58.4 47.77Z' fill='%23409eff'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
background: var(--el-color-primary-light-5);
transition: all 0.3s ease;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 52%;
img {
width: 100%;
height: 100%;
animation: error-num 0.6s ease;
}
}
&::after {
content: '';
width: 150px;
height: 300px;
.login-left-waves {
position: absolute;
right: 0;
top: 0;
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='150' height='300' xmlns='http://www.w3.org/2000/svg' fill='none'%3E%3Cg%3E%3Cpath id='svg_1' d='M-0.56 -0.28C41.94 36.17 67.73 18.94 93.33 33.96C118.93 48.98 107.58 73.56 101.94 89.76C96.29 105.96 50.09 217.83 47.87 231.18C45.64 244.52 46.02 255.2 64.4 270.05C82.79 284.91 121.99 292.31 111.98 289.81C101.97 287.32 153.96 301.48 151.83 299.9C149.69 298.32 149.98 -1.36 149.71 -1.18C149.98 -1.36 -43.06 -36.74 -0.56 -0.28L-0.56 -0.28Z' fill='%23409eff'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
background: var(--el-color-primary-light-5);
transition: all 0.3s ease;
}
&-icon {
width: 60%;
height: 70%;
position: absolute;
left: 0;
bottom: 0;
right: -100px;
}
}
.login-content {
width: 500px;
padding: 20px;
position: absolute;
right: 200px;
top: 50%;
transform: translateY(-50%) translate3d(0, 0, 0);
background-color: var(--el-color-white);
border: 5px solid var(--el-color-primary-light-8);
border-radius: 5px;
overflow: hidden;
z-index: 1;
height: 460px;
.login-content-main {
margin: 0 auto;
width: 80%;
.login-content-title {
color: var(--el-text-color-primary);
font-weight: 500;
font-size: 22px;
text-align: center;
letter-spacing: 4px;
margin: 15px 0 30px;
white-space: nowrap;
z-index: 5;
position: relative;
transition: all 0.3s ease;
}
}
.login-content-main-sacn {
position: absolute;
top: 0;
right: 0;
width: 50px;
height: 50px;
.login-right {
width: 700px;
.login-right-warp {
border: 1px solid var(--el-color-primary-light-3);
border-radius: 3px;
width: 500px;
height: 500px;
position: relative;
overflow: hidden;
cursor: pointer;
transition: all ease 0.3s;
color: var(--el-text-color-primary);
&-delta {
background-color: var(--el-color-white);
.login-right-warp-one,
.login-right-warp-two {
position: absolute;
width: 35px;
height: 70px;
z-index: 2;
top: 2px;
right: 21px;
background: var(--el-color-white);
transform: rotate(-45deg);
display: block;
width: inherit;
height: inherit;
&::before,
&::after {
content: '';
position: absolute;
z-index: 1;
}
}
&:hover {
opacity: 1;
transition: all ease 0.3s;
color: var(--el-color-primary) !important;
.login-right-warp-one {
&::before {
filter: hue-rotate(0deg);
top: 0px;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(90deg, transparent, var(--el-color-primary));
animation: loginLeft 3s linear infinite;
}
&::after {
filter: hue-rotate(60deg);
top: -100%;
right: 2px;
width: 3px;
height: 100%;
background: linear-gradient(180deg, transparent, var(--el-color-primary));
animation: loginTop 3s linear infinite;
animation-delay: 0.7s;
}
}
i {
width: 47px;
height: 50px;
display: inline-block;
font-size: 48px;
position: absolute;
right: 2px;
top: -1px;
.login-right-warp-two {
&::before {
filter: hue-rotate(120deg);
bottom: 2px;
right: -100%;
width: 100%;
height: 3px;
background: linear-gradient(270deg, transparent, var(--el-color-primary));
animation: loginRight 3s linear infinite;
animation-delay: 1.4s;
}
&::after {
filter: hue-rotate(300deg);
bottom: -100%;
left: 0px;
width: 3px;
height: 100%;
background: linear-gradient(360deg, transparent, var(--el-color-primary));
animation: loginBottom 3s linear infinite;
animation-delay: 2.1s;
}
}
.login-right-warp-mian {
display: flex;
flex-direction: column;
height: 100%;
.login-right-warp-main-title {
height: 130px;
line-height: 130px;
font-size: 27px;
text-align: center;
letter-spacing: 3px;
animation: logoAnimation 0.3s ease;
animation-delay: 0.3s;
}
.login-right-warp-main-form {
flex: 1;
padding: 0 50px 50px;
.login-content-main-sacn {
position: absolute;
top: 0;
right: 0;
width: 50px;
height: 50px;
overflow: hidden;
cursor: pointer;
transition: all ease 0.3s;
color: var(--el-color-primary);
&-delta {
position: absolute;
width: 35px;
height: 70px;
z-index: 2;
top: 2px;
right: 21px;
background: var(--el-color-white);
transform: rotate(-45deg);
}
&:hover {
opacity: 1;
transition: all ease 0.3s;
color: var(--el-color-primary) !important;
}
i {
width: 47px;
height: 50px;
display: inline-block;
font-size: 48px;
position: absolute;
right: 1px;
top: 0px;
}
}
}
}
}
}

View File

@ -1,5 +1,5 @@
<template>
<div class="notice-bar-container">
<div class="notice-bar-container layout-pd">
<el-card shadow="hover" header="滚动通知栏:默认">
<NoticeBar
text="🎉🎉🔥基于vue3.x 、Typescript、vite、Element plus等适配手机、平板、pc
@ -48,12 +48,13 @@
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent } from 'vue';
import NoticeBar from '/@/components/noticeBar/index.vue';
import { defineAsyncComponent, toRefs, reactive, defineComponent } from 'vue';
export default defineComponent({
name: 'makeNoticeBar',
components: { NoticeBar },
components: {
NoticeBar: defineAsyncComponent(() => import('/@/components/noticeBar/index.vue')),
},
setup() {
const state = reactive({
noticeList: [

View File

@ -1,5 +1,5 @@
<template>
<div class="selector-container">
<div class="selector-container layout-pd">
<el-card shadow="hover" header="图标选择器(宽度自动)">
<IconSelector @get="onGetIcon" @clear="onClearIcon" v-model="modelIcon" />
</el-card>
@ -26,12 +26,13 @@
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent } from 'vue';
import IconSelector from '/@/components/iconSelector/index.vue';
import { defineAsyncComponent, toRefs, reactive, defineComponent } from 'vue';
export default defineComponent({
name: 'makeSelector',
components: { IconSelector },
components: {
IconSelector: defineAsyncComponent(() => import('/@/components/iconSelector/index.vue')),
},
setup() {
const state = reactive({
modelIcon: '',

View File

@ -1,5 +1,5 @@
<template>
<div class="svg-demo-container">
<div class="svg-demo-container layout-pd">
<el-card shadow="hover" header="svgIcon演示支持本地svg">
<SvgIcon name="iconfont icon-shuju1" color="red" :size="30" />
<SvgIcon name="ele-Trophy" color="var(--el-color-primary)" :size="30" />

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="layout-pd">
<el-input v-model="val" placeholder="menu11请输入内容测试路由缓存"></el-input>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="layout-pd">
<el-input v-model="val" placeholder="menu121请输入内容测试路由缓存"></el-input>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="layout-pd">
<el-input v-model="val" placeholder="menu122请输入内容测试路由缓存"></el-input>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="layout-pd">
<el-input v-model="val" placeholder="menu13请输入内容测试路由缓存"></el-input>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="layout-pd">
<el-input v-model="val" placeholder="menu2请输入内容测试路由缓存"></el-input>
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<div class="awesome-container">
<div class="awesome-container layout-pd">
<el-card shadow="hover" :header="`fontawesome 字体图标(自动载入)${sheetsIconList.length - 24}个`">
<el-row class="iconfont-row">
<el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="2" v-for="(v, k) in sheetsIconList" :key="k">

View File

@ -1,5 +1,5 @@
<template>
<div class="drag-container">
<div class="drag-container layout-pd">
<el-card shadow="hover" header="拖动指令效果v-drag作用于 Dialog 对话框">
<el-button type="primary" @click="dialogVisible = true" size="default">
<el-icon>

View File

@ -1,5 +1,5 @@
<template>
<div class="dynamic-form-container">
<div class="dynamic-form-container layout-pd">
<el-card shadow="hover" header="动态复杂表单">
<el-form :model="form" ref="formRulesOneRef" size="default" label-width="100px" class="mt35">
<el-row :gutter="35">
@ -112,7 +112,8 @@
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, getCurrentInstance, defineComponent } from 'vue';
import { toRefs, reactive, onMounted, defineComponent, ref } from 'vue';
import { ElMessage } from 'element-plus';
import { formData } from './mock';
// 定义接口来定义对象的类型
@ -146,7 +147,7 @@ interface DynamicFormState {
export default defineComponent({
name: 'pagesDynamicForm',
setup() {
const { proxy } = <any>getCurrentInstance();
const formRulesOneRef = ref();
const state = reactive<DynamicFormState>({
formData,
form: {
@ -178,9 +179,9 @@ export default defineComponent({
};
// 表单验证
const onSubmitForm = () => {
proxy.$refs.formRulesOneRef.validate((valid: boolean) => {
formRulesOneRef.value.validate((valid: boolean) => {
if (valid) {
proxy.$message.success('验证成功');
ElMessage.success('验证成功');
} else {
return false;
}
@ -188,11 +189,12 @@ export default defineComponent({
};
// 重置表单
const onResetForm = () => {
proxy.$refs.formRulesOneRef.resetFields();
formRulesOneRef.value.resetFields();
};
// 页面加载时
onMounted(() => {});
return {
formRulesOneRef,
onAddRow,
onDelRow,
onSubmitForm,

View File

@ -1,5 +1,5 @@
<template>
<div class="element-container">
<div class="element-container layout-pd">
<el-card shadow="hover" :header="`element plus 字体图标(自动载入,增加了 ele- 前缀使用时ele-Aim)${sheetsIconList.length}个`">
<el-row class="iconfont-row">
<el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="2" v-for="(v, k) in sheetsIconList" :key="k">

View File

@ -1,6 +1,6 @@
<template>
<div :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
<div class="layout-view-bg-white">
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<div class="w100 h100 flex">
<div class="flex-margin color-primary">filtering-details 测试界面</div>
</div>
@ -9,31 +9,9 @@
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { defineComponent } from 'vue';
export default defineComponent({
name: 'pagesFilteringDetails',
setup() {
const storesTagsViewRoutes = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
// 设置主内容的高度
const initTagViewHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
return {
initTagViewHeight,
};
},
});
</script>

View File

@ -1,6 +1,6 @@
<template>
<div :style="{ height: `calc(100vh - ${initTagViewHeight}` }">
<div class="layout-view-bg-white">
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<div class="w100 h100 flex">
<div class="flex-margin color-primary">测试界面</div>
</div>
@ -9,31 +9,9 @@
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '/@/stores/themeConfig';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { defineComponent } from 'vue';
export default defineComponent({
name: 'pagesFilteringDetails1',
setup() {
const storesTagsViewRoutes = useTagsViewRoutes();
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const { isTagsViewCurrenFull } = storeToRefs(storesTagsViewRoutes);
// 设置主内容的高度
const initTagViewHeight = computed(() => {
let { isTagsview } = themeConfig.value;
if (isTagsViewCurrenFull.value) {
return `30px`;
} else {
if (isTagsview) return `114px`;
else return `80px`;
}
});
return {
initTagViewHeight,
};
},
});
</script>

View File

@ -1,5 +1,5 @@
<template>
<div class="filtering">
<div class="filtering layout-pd">
<el-card
shadow="hover"
class="filtering-list br-top-no"

View File

@ -1,5 +1,5 @@
<template>
<div class="form-adapt-container">
<div class="form-adapt-container layout-pd">
<el-card shadow="hover" header="表单自适应演示(改变窗口查看效果)">
<el-form :model="form" size="default" label-width="100px" class="mt35 mb35">
<el-row :gutter="35">

View File

@ -1,5 +1,5 @@
<template>
<div class="form-i18n-container">
<div class="form-i18n-container layout-pd">
<el-card shadow="hover" header="表单国际化演示(不适用于动态项 form-item)">
<div style="text-align: center; margin-top: 15px">
<el-radio-group v-model="radio" size="default" @change="onRadioChange">
@ -32,12 +32,13 @@
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, getCurrentInstance } from 'vue';
import { toRefs, reactive, defineComponent } from 'vue';
import { useI18n } from 'vue-i18n';
export default defineComponent({
name: 'pagesFormI18n',
setup() {
const { proxy } = <any>getCurrentInstance();
const { locale } = useI18n();
const state = reactive({
radio: 'zh-cn',
form: {
@ -48,7 +49,7 @@ export default defineComponent({
});
// 单选框改变时
const onRadioChange = () => {
proxy.$i18n.locale = state.radio;
locale.value = state.radio;
};
return {
onRadioChange,

View File

@ -1,5 +1,5 @@
<template>
<div class="form-rules-container">
<div class="form-rules-container layout-pd">
<el-card shadow="hover" header="表单组件1"> <FormRulesOne :data="formRulesOneData" ref="pagesFormRulesOneRef" /></el-card>
<el-card shadow="hover" header="表单组件2" class="mt15"><FormRulesTwo ref="pagesFormRulesTwoRef" /> </el-card>
<el-card shadow="hover" header="表单组件3" class="mt15"> <FormRulesThree ref="pagesFormRulesThreeRef" /></el-card>
@ -19,21 +19,20 @@
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, getCurrentInstance } from 'vue';
import { defineAsyncComponent, toRefs, reactive, defineComponent, ref } from 'vue';
import { ElMessage } from 'element-plus';
import FormRulesOne from '/@/views/pages/formRules/component/formRulesOne.vue';
import FormRulesTwo from '/@/views/pages/formRules/component/formRulesTwo.vue';
import FormRulesThree from '/@/views/pages/formRules/component/formRulesThree.vue';
export default defineComponent({
name: 'pagesFormRules',
components: {
FormRulesOne,
FormRulesTwo,
FormRulesThree,
FormRulesOne: defineAsyncComponent(() => import('/@/views/pages/formRules/component/formRulesOne.vue')),
FormRulesTwo: defineAsyncComponent(() => import('/@/views/pages/formRules/component/formRulesTwo.vue')),
FormRulesThree: defineAsyncComponent(() => import('/@/views/pages/formRules/component/formRulesThree.vue')),
},
setup() {
const { proxy } = <any>getCurrentInstance();
const pagesFormRulesOneRef = ref();
const pagesFormRulesTwoRef = ref();
const pagesFormRulesThreeRef = ref();
const state = reactive({
formRulesOneData: {
name: 'lyt',
@ -43,25 +42,25 @@ export default defineComponent({
},
});
// 表单组件验证
const formRulesValidate = (pageRef: string, sonRef: string) => {
const formRulesValidate = (pageRef: any, sonRef: string) => {
return new Promise((resolve) => {
proxy.$refs[pageRef].$refs[sonRef].validate((valid: boolean) => {
pageRef.value.$refs[sonRef].validate((valid: boolean) => {
if (valid) resolve(valid);
});
});
};
// 表单组件重置
const formRulesResetFields = () => {
proxy.$refs.pagesFormRulesOneRef.$refs.formRulesOneRef.resetFields();
proxy.$refs.pagesFormRulesTwoRef.$refs.formRulesTwoRef.resetFields();
proxy.$refs.pagesFormRulesThreeRef.$refs.formRulesThreeRef.resetFields();
pagesFormRulesOneRef.value.$refs.formRulesOneRef.resetFields();
pagesFormRulesTwoRef.value.$refs.formRulesTwoRef.resetFields();
pagesFormRulesThreeRef.value.$refs.formRulesThreeRef.resetFields();
};
// 验证表单
const onSubmitForm = () => {
Promise.all([
formRulesValidate('pagesFormRulesOneRef', 'formRulesOneRef'),
formRulesValidate('pagesFormRulesTwoRef', 'formRulesTwoRef'),
formRulesValidate('pagesFormRulesThreeRef', 'formRulesThreeRef'),
formRulesValidate(pagesFormRulesOneRef, 'formRulesOneRef'),
formRulesValidate(pagesFormRulesTwoRef, 'formRulesTwoRef'),
formRulesValidate(pagesFormRulesThreeRef, 'formRulesThreeRef'),
]).then(() => {
ElMessage.success('表单全部验证成功');
});
@ -71,6 +70,9 @@ export default defineComponent({
formRulesResetFields();
};
return {
pagesFormRulesOneRef,
pagesFormRulesTwoRef,
pagesFormRulesThreeRef,
onSubmitForm,
onResetForm,
...toRefs(state),

View File

@ -1,5 +1,5 @@
<template>
<div class="iconfont-container">
<div class="iconfont-container layout-pd">
<el-card shadow="hover" :header="`iconfont 字体图标(自动载入)${sheetsIconList.length}个`">
<el-row class="iconfont-row">
<el-col :xs="12" :sm="8" :md="6" :lg="4" :xl="2" v-for="(v, k) in sheetsIconList" :key="k">

View File

@ -1,5 +1,5 @@
<template>
<div class="lazy-img-container">
<div class="lazy-img-container layout-pd">
<el-card shadow="hover" header="图片懒加载演示F12 切换到 Network Img下进行图片加载查看">
<div class="flex-warp" v-if="tableData.data.length > 0">
<el-row :gutter="15">

View File

@ -1,5 +1,5 @@
<template>
<div class="list-adapt-container">
<div class="list-adapt-container layout-pd">
<el-card shadow="hover" header="列表自适应演示(改变窗口查看效果)">
<div class="flex-warp" v-if="tableData.data.length > 0">
<el-row :gutter="15">

Some files were not shown because too many files have changed in this diff Show More