'admin:21.12.27:新增基于vue-next-admin-template基础版ts修改的js版'

This commit is contained in:
lyt
2021-12-27 23:42:51 +08:00
parent 2ec65911ee
commit 11c018abd4
100 changed files with 4104 additions and 4908 deletions

View File

@ -6,13 +6,8 @@ module.exports = {
node: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 12,
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
plugins: ['vue', '@typescript-eslint'],
plugins: ['vue'],
rules: {
// http://eslint.cn/docs/rules/
// https://eslint.vuejs.org/rules/
@ -59,5 +54,6 @@ module.exports = {
'no-v-model-argument': 'off',
'no-case-declarations': 'off',
'no-console': 'error',
'no-undef': 'off',
},
};

View File

@ -1,30 +1,9 @@
# <a href="https://gitee.com/lyt-top/vue-next-admin" target="_blank">vue-next-admin 更新日志</a>
🎉🎉🔥 `vue-next-admin-template` 基于 vue-next-admin-v1.1.2 版本) vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 0.2.2
`2021.12.21`
- 🎉 同步 master 分支 v1.2.2 版本内容,具体查看 master CHANGELOG.md
## 0.2.1
`2021.12.12`
- 🌟 更新 依赖更新最新版本
- 🐞 修复 浏览器标题问题
- 🐞 修复 element plus svg 图标引入
- 🐞 修复 默认显示英文问题,改成默认显示中文
## 0.2.0
`2021.12.04`
- 🎉 同步 master 分支 v1.2.0 版本内容,具体查看 master CHANGELOG.md
🎉🎉🔥 `vue-next-admin-template-js` 基于 vue-next-admin-template v0.2.2 版本) vue3.x 、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 0.1.0
`2021.10.17`
`2021.12.27`
- 🎉 新增 vue-next-admin-template 基础版本(不带国际化),切换 `vue-next-admin-template` 分支
- 🎉 新增 vue-next-admin-template-js 基础版本(不带国际化),切换 `vue-next-admin-template-js` 分支

View File

@ -20,9 +20,9 @@
<p>&nbsp;</p>
</div>
#### 🌈 介绍 基础版 ts不带国际化
#### 🌈 介绍 基础版 js不带国际化基于vue-next-admin-template V0.2.2版
基于 vue3.x + CompositionAPI + typescript + vite + element plus + vue-router-next + next.vuex适配手机、平板、pc 的后台开源免费模板,希望减少工作量,帮助大家实现快速开发。
基于 vue3.x + CompositionAPI + vite + element plus + vue-router-next适配手机、平板、pc 的后台开源免费模板,希望减少工作量,帮助大家实现快速开发。
#### ⛱️ 线上预览
@ -59,7 +59,7 @@ git clone https://gitee.com/lyt-top/vue-next-admin.git
cd vue-next-admin
# 切换分支
git checkout vue-next-admin-template
git checkout vue-next-admin-template-js
# 安装依赖
cnpm install
@ -71,19 +71,10 @@ cnpm run dev
cnpm run build
```
#### 🍉 git 命令
- 在本地新建一个分支:`git branch newBranch`
- 切换到你的新分支:`git checkout newBranch`
- 将新分支发布在 github、gitee 上:`git push origin newBranch`
- 在本地删除一个分支:`git branch -d newBranch`
- 在 github 远程端删除一个分支:`git push origin :newBranch (分支名前的冒号代表删除)`
- 注意删除远程分支后,如果有对应的本地分支,本地分支并不会同步删除!
#### 💯 学习交流加 QQ 群
- 若加群了没同意(一般不会超过一天),那就是群满了,请换一个群试试
- 查看开发文档<a href="https://lyt-top.gitee.io/vue-next-admin-preview/#/login" target="_blank">vue-next-admin</a> 开发文档正在编写中...
- 查看开发文档<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a>
- 群号码:
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>
@ -94,6 +85,11 @@ cnpm run build
<a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=zVfy3gNy7pNWVK3kMduDzwU369PZg2fw&jump_from=webapi">
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/user/qq2.png" width="220" height="220" alt="vue-next-admin 讨论群" title="vue-next-admin 讨论群2"/>
</a>
#### 💒 集成后端
- <a target="_blank" href="https://github.com/PandaGoAdmin/PandaX">@熊猫 PandaGoAdmin</a>
- <a target="_blank" href="https://www.gnet.top/public">@甜蜜蜜 GoPro 平台</a>
#### ❤️ 鸣谢列表

View File

@ -26,6 +26,6 @@
s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="module" src="/src/main.ts"></script>
<script type="module" src="/src/main.js"></script>
</body>
</html>

19
jsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"lib": ["esnext", "dom", "dom.iterable", "scripthost"],
"jsx": "preserve",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"types": ["vite/client"],
"paths": {
"/@/*": ["src/*"]
},
"moduleResolution": "node"
}
}

View File

@ -1,7 +1,7 @@
{
"name": "vue-next-admin-template",
"version": "0.2.2",
"description": "vue3 vite next admin template",
"name": "vue-next-admin-template-js",
"version": "0.1.0",
"description": "vue3 vite next admin template js setup",
"author": "lyt_20201208",
"license": "MIT",
"scripts": {
@ -25,12 +25,6 @@
"vuex": "^4.0.2"
},
"devDependencies": {
"@types/axios": "^0.14.0",
"@types/clipboard": "^2.0.1",
"@types/node": "^17.0.2",
"@types/nprogress": "^0.2.0",
"@typescript-eslint/eslint-plugin": "^5.8.0",
"@typescript-eslint/parser": "^5.8.0",
"@vitejs/plugin-vue": "^2.0.1",
"@vue/compiler-sfc": "^3.2.26",
"dotenv": "^10.0.0",
@ -39,8 +33,9 @@
"prettier": "^2.5.1",
"sass": "^1.45.1",
"sass-loader": "^12.4.0",
"typescript": "^4.5.4",
"unplugin-auto-import": "^0.5.4",
"vite": "^2.7.4",
"vite-plugin-vue-setup-extend": "^0.1.0",
"vue-eslint-parser": "^8.0.1"
},
"browserslist": [
@ -60,6 +55,7 @@
"vue3",
"vuejs/vue-next",
"vuejs/vue-next-template",
"vuejs/vue-next-template-js",
"element-ui",
"element-plus",
"vue-next-admin",

1
plugins.d.ts vendored
View File

@ -1 +0,0 @@
declare module 'vue-grid-layout';

13
shim.d.ts vendored
View File

@ -1,13 +0,0 @@
/* eslint-disable */
// 声明文件,*.vue 后缀的文件交给 vue 模块来处理
declare module '*.vue' {
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
// 声明文件,定义全局变量。其它 app.config.globalProperties.xxx使用 getCurrentInstance() 来获取
interface Window {
nextLoading: boolean;
}

6
source.d.ts vendored
View File

@ -1,6 +0,0 @@
declare module '*.json';
declare module '*.png';
declare module '*.jpg';
declare module '*.scss';
declare module '*.ts';
declare module '*.js';

View File

@ -5,68 +5,56 @@
<CloseFull />
</template>
<script lang="ts">
import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, nextTick, defineComponent, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
<script setup name="app">
import other from '/@/utils/other';
import { Local, Session } from '/@/utils/storage';
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 },
setup() {
const { proxy } = getCurrentInstance() as any;
const setingsRef = ref();
const route = useRoute();
const store = useStore();
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 布局配置弹窗打开
const openSetingsDrawer = () => {
setingsRef.value.openDrawer();
};
// 设置初始化,防止刷新时恢复默认
onBeforeMount(() => {
// 设置批量第三方 icon 图标
setIntroduction.cssCdn();
// 设置批量第三方 js
setIntroduction.jsCdn();
});
// 页面加载时
onMounted(() => {
nextTick(() => {
// 监听布局配置弹窗点击打开
proxy.mittBus.on('openSetingsDrawer', () => {
openSetingsDrawer();
});
// 获取缓存中的布局配置
if (Local.get('themeConfig')) {
store.dispatch('themeConfig/setThemeConfig', Local.get('themeConfig'));
document.documentElement.style.cssText = Local.get('themeConfigStyle');
}
// 获取缓存中的全屏配置
if (Session.get('isTagsViewCurrenFull')) {
store.dispatch('tagsViewRoutes/setCurrenFullscreen', Session.get('isTagsViewCurrenFull'));
}
});
});
// 监听路由的变化,设置网站标题
watch(
() => route.path,
() => {
other.useTitle();
}
);
return {
setingsRef,
getThemeConfig,
};
},
const { proxy } = getCurrentInstance();
const setingsRef = ref();
const route = useRoute();
const store = useStore();
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 布局配置弹窗打开
const openSetingsDrawer = () => {
setingsRef.value.openDrawer();
};
// 设置初始化,防止刷新时恢复默认
onBeforeMount(() => {
// 设置批量第三方 icon 图标
setIntroduction.cssCdn();
// 设置批量第三方 js
setIntroduction.jsCdn();
});
// 页面加载时
onMounted(() => {
nextTick(() => {
// 监听布局配置弹窗点击打开
proxy.mittBus.on('openSetingsDrawer', () => {
openSetingsDrawer();
});
// 获取缓存中的布局配置
if (Local.get('themeConfig')) {
store.dispatch('themeConfig/setThemeConfig', Local.get('themeConfig'));
document.documentElement.style.cssText = Local.get('themeConfigStyle');
}
// 获取缓存中的全屏配置
if (Session.get('isTagsViewCurrenFull')) {
store.dispatch('tagsViewRoutes/setCurrenFullscreen', Session.get('isTagsViewCurrenFull'));
}
});
});
// 监听路由的变化,设置网站标题
watch(
() => route.path,
() => {
other.useTitle();
}
);
</script>

View File

@ -5,7 +5,7 @@ import request from '/@/utils/request';
* @param params 要传的参数值
* @returns 返回接口数据
*/
export function signIn(params: object) {
export function signIn(params) {
return request({
url: '/user/signIn',
method: 'post',
@ -18,7 +18,7 @@ export function signIn(params: object) {
* @param params 要传的参数值
* @returns 返回接口数据
*/
export function signOut(params: object) {
export function signOut(params) {
return request({
url: '/user/signOut',
method: 'post',

View File

@ -11,7 +11,7 @@ import request from '/@/utils/request';
* @param params 要传的参数值非必传
* @returns 返回接口数据
*/
export function getMenuAdmin(params?: object) {
export function getMenuAdmin(params) {
return request({
url: '/gitee/lyt-top/vue-next-admin-images/raw/master/menu/adminMenu-template.json',
method: 'get',
@ -25,7 +25,7 @@ export function getMenuAdmin(params?: object) {
* @param params 要传的参数值非必传
* @returns 返回接口数据
*/
export function getMenuTest(params?: object) {
export function getMenuTest(params) {
return request({
url: '/gitee/lyt-top/vue-next-admin-images/raw/master/menu/testMenu-template.json',
method: 'get',

View File

@ -1,10 +1,10 @@
<template>
<div class="icon-selector">
<el-popover placement="bottom" :width="fontIconWidth" v-model:visible="fontIconVisible" popper-class="icon-selector-popper">
<el-popover placement="bottom" :width="state.fontIconWidth" v-model:visible="state.fontIconVisible" popper-class="icon-selector-popper">
<template #reference>
<el-input
v-model="fontIconSearch"
:placeholder="fontIconPlaceholder"
v-model="state.fontIconSearch"
:placeholder="state.fontIconPlaceholder"
:clearable="clearable"
:disabled="disabled"
:size="size"
@ -15,29 +15,35 @@
>
<template #prepend>
<SvgIcon
:name="fontIconPrefix === '' ? prepend : fontIconPrefix"
:name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
class="font14"
v-if="fontIconPrefix === '' ? prepend?.indexOf('element') > -1 : fontIconPrefix?.indexOf('element') > -1"
v-if="state.fontIconPrefix === '' ? prepend?.indexOf('element') > -1 : state.fontIconPrefix?.indexOf('element') > -1"
/>
<i v-else :class="fontIconPrefix === '' ? prepend : fontIconPrefix" class="font14"></i>
<i v-else :class="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14"></i>
</template>
</el-input>
</template>
<transition name="el-zoom-in-top">
<div class="icon-selector-warp" v-show="fontIconVisible">
<div class="icon-selector-warp" v-show="state.fontIconVisible">
<div class="icon-selector-warp-title flex">
<div class="flex-auto">{{ title }}</div>
<div class="icon-selector-warp-title-tab" v-if="type === 'all'">
<span :class="{ 'span-active': fontIconType === 'ali' }" @click="onIconChange('ali')" class="ml10" title="iconfont 图标">ali</span>
<span :class="{ 'span-active': fontIconType === 'ele' }" @click="onIconChange('ele')" class="ml10" title="elementPlus 图标">ele</span>
<span :class="{ 'span-active': fontIconType === 'awe' }" @click="onIconChange('awe')" class="ml10" title="fontawesome 图标">awe</span>
<span :class="{ 'span-active': state.fontIconType === 'ali' }" @click="onIconChange('ali')" class="ml10" title="iconfont 图标"
>ali</span
>
<span :class="{ 'span-active': state.fontIconType === 'ele' }" @click="onIconChange('ele')" class="ml10" title="elementPlus 图标"
>ele</span
>
<span :class="{ 'span-active': state.fontIconType === 'awe' }" @click="onIconChange('awe')" class="ml10" title="fontawesome 图标"
>awe</span
>
</div>
</div>
<div class="icon-selector-warp-row">
<el-scrollbar ref="selectorScrollbarRef">
<el-row :gutter="10" v-if="fontIconSheetsFilterList.length > 0">
<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" @click="onColClick(v)" v-for="(v, k) in fontIconSheetsFilterList" :key="k">
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': fontIconPrefix === v }">
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': state.fontIconPrefix === v }">
<div class="flex-margin">
<div class="icon-selector-warp-item-value">
<SvgIcon :name="v" />
@ -55,187 +61,185 @@
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, onMounted, nextTick, computed, watch } from 'vue';
<script setup name="iconSelector">
import initIconfont from '/@/utils/getStyleSheets';
export default {
name: 'iconSelector',
emits: ['update:modelValue', 'get', 'clear'],
props: {
// 输入框前置内容
prepend: {
type: String,
default: () => 'elementPointer',
},
// 输入框占位文本
placeholder: {
type: String,
default: () => '请输入内容搜索图标或者选择图标',
},
// 输入框占位文本
size: {
type: String,
default: () => 'small',
},
// 弹窗标题
title: {
type: String,
default: () => '请选择图标',
},
// icon 图标类型
type: {
type: String,
default: () => 'ele',
},
// 禁用
disabled: {
type: Boolean,
default: () => false,
},
// 是否可清空
clearable: {
type: Boolean,
default: () => true,
},
// 自定义空状态描述文字
emptyDescription: {
type: String,
default: () => '无相关图标',
},
// 双向绑定值,字段名为固定,改了之后将不生效
// 参考https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
modelValue: String,
// 传值 setup="props"
const props = defineProps({
// 输入框前置内容
prepend: {
type: String,
default: () => 'elementPointer',
},
setup(props, { emit }) {
const inputWidthRef = ref();
const selectorScrollbarRef = ref();
const state: any = reactive({
fontIconPrefix: '',
fontIconVisible: false,
fontIconWidth: 0,
fontIconSearch: '',
fontIconTabsIndex: 0,
fontIconSheetsList: [],
fontIconPlaceholder: '',
fontIconType: 'ali',
fontIconShow: true,
});
// 处理 input 获取焦点时modelValue 有值时,改变 input 的 placeholder 值
const onIconFocus = () => {
if (!props.modelValue) return false;
state.fontIconSearch = '';
state.fontIconPlaceholder = props.modelValue;
};
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
const onIconBlur = () => {
setTimeout(() => {
const icon = state.fontIconSheetsList.filter((icon: string) => icon === state.fontIconSearch);
if (icon.length <= 0) state.fontIconSearch = '';
}, 300);
};
// 处理 icon 双向绑定数值回显
const initModeValueEcho = () => {
if (props.modelValue === '') return false;
state.fontIconPlaceholder = props.modelValue;
state.fontIconPrefix = props.modelValue;
};
// 图标搜索及图标数据显示
const fontIconSheetsFilterList = computed(() => {
if (!state.fontIconSearch) return state.fontIconSheetsList;
let search = state.fontIconSearch.trim().toLowerCase();
return state.fontIconSheetsList.filter((item: any) => {
if (item.toLowerCase().indexOf(search) !== -1) return item;
});
});
// 获取 input 的宽度
const getInputWidth = () => {
nextTick(() => {
state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
});
};
// 监听页面宽度改变
const initResize = () => {
window.addEventListener('resize', () => {
getInputWidth();
});
};
// 初始化数据
const initFontIconData = async (type: string) => {
state.fontIconSheetsList = [];
if (type === 'ali') {
await initIconfont.ali().then((res: any) => {
// 阿里字体图标使用 `iconfont xxx`
state.fontIconSheetsList = res.map((i) => `iconfont ${i}`);
});
} else if (type === 'ele') {
await initIconfont.ele().then((res: any) => {
state.fontIconSheetsList = res;
});
} else if (type === 'awe') {
await initIconfont.awe().then((res: any) => {
// fontawesome字体图标使用 `fa xxx`
state.fontIconSheetsList = res.map((i) => `fa ${i}`);
});
}
// 初始化 input 的 placeholder
// 参考单项数据流https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
state.fontIconPlaceholder = props.placeholder;
// 初始化双向绑定回显
initModeValueEcho();
// 切换时,滚动条置顶。感兴趣可以使用 keep-alive <component :is="xxx"/> 进行缓存
selectorScrollbarRef.value.wrap$.scrollTop = 0;
};
// 图标点击切换
const onIconChange = (type: string) => {
state.fontIconType = type;
initFontIconData(type);
};
// 获取当前点击的 icon 图标
const onColClick = (v: any) => {
state.fontIconPlaceholder = v;
state.fontIconVisible = false;
state.fontIconPrefix = v;
emit('get', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 清空当前点击的 icon 图标
const onClearFontIcon = () => {
state.fontIconPrefix = '';
emit('clear', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 页面加载时
onMounted(() => {
// 判断默认进来是什么类型图标,进行 tab 回显
if (props.type === 'all') {
if (props.modelValue?.indexOf('iconfont') > -1) onIconChange('ali');
else if (props.modelValue?.indexOf('element') > -1) onIconChange('ele');
else if (props.modelValue?.indexOf('fa') > -1) onIconChange('awe');
else onIconChange('ali');
} else {
onIconChange(props.type);
}
initResize();
getInputWidth();
});
// 监听双向绑定 modelValue 的变化
watch(
() => props.modelValue,
() => {
initModeValueEcho();
}
);
return {
inputWidthRef,
selectorScrollbarRef,
fontIconSheetsFilterList,
onColClick,
onIconChange,
onClearFontIcon,
onIconFocus,
onIconBlur,
...toRefs(state),
};
// 输入框占位文本
placeholder: {
type: String,
default: () => '请输入内容搜索图标或者选择图标',
},
// 输入框占位文本
size: {
type: String,
default: () => 'small',
},
// 弹窗标题
title: {
type: String,
default: () => '请选择图标',
},
// icon 图标类型
type: {
type: String,
default: () => 'ele',
},
// 禁用
disabled: {
type: Boolean,
default: () => false,
},
// 是否可清空
clearable: {
type: Boolean,
default: () => true,
},
// 自定义空状态描述文字
emptyDescription: {
type: String,
default: () => '无相关图标',
},
// 双向绑定值,字段名为固定,改了之后将不生效
// 参考https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
modelValue: String,
});
// 事件
const emit = defineEmits(['update:modelValue', 'get', 'clear']);
const inputWidthRef = ref();
const selectorScrollbarRef = ref();
const state = reactive({
fontIconPrefix: '',
fontIconVisible: false,
fontIconWidth: 0,
fontIconSearch: '',
fontIconSheetsList: [],
fontIconPlaceholder: '',
fontIconType: 'ali',
});
// 处理 input 获取焦点时modelValue 有值时,改变 input 的 placeholder 值
const onIconFocus = () => {
if (!props.modelValue) return false;
state.fontIconSearch = '';
state.fontIconPlaceholder = props.modelValue;
};
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
const onIconBlur = () => {
setTimeout(() => {
const icon = state.fontIconSheetsList.filter((icon) => icon === state.fontIconSearch);
if (icon.length <= 0) state.fontIconSearch = '';
}, 300);
};
// 处理 icon 双向绑定数值回显
const initModeValueEcho = () => {
if (props.modelValue === '') return false;
state.fontIconPlaceholder = props.modelValue;
state.fontIconPrefix = props.modelValue;
};
// 图标搜索及图标数据显示
const fontIconSheetsFilterList = computed(() => {
if (!state.fontIconSearch) return state.fontIconSheetsList;
let search = state.fontIconSearch.trim().toLowerCase();
return state.fontIconSheetsList.filter((item) => {
if (item.toLowerCase().indexOf(search) !== -1) return item;
});
});
// 获取 input 的宽度
const getInputWidth = () => {
nextTick(() => {
state.fontIconWidth = inputWidthRef.value.$el.offsetWidth;
});
};
// 监听页面宽度改变
const initResize = () => {
window.addEventListener('resize', () => {
getInputWidth();
});
};
// 初始化数据
const initFontIconData = async (type) => {
state.fontIconSheetsList = [];
if (type === 'ali') {
await initIconfont.ali().then((res) => {
// 阿里字体图标使用 `iconfont xxx`
state.fontIconSheetsList = res.map((i) => `iconfont ${i}`);
});
} else if (type === 'ele') {
await initIconfont.ele().then((res) => {
state.fontIconSheetsList = res;
});
} else if (type === 'awe') {
await initIconfont.awe().then((res) => {
// fontawesome字体图标使用 `fa xxx`
state.fontIconSheetsList = res.map((i) => `fa ${i}`);
});
}
// 初始化 input 的 placeholder
// 参考单项数据流https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
state.fontIconPlaceholder = props.placeholder;
// 初始化双向绑定回显
initModeValueEcho();
// 切换时,滚动条置顶。感兴趣可以使用 keep-alive <component :is="xxx"/> 进行缓存
selectorScrollbarRef.value.wrap$.scrollTop = 0;
};
// 图标点击切换
const onIconChange = (type) => {
state.fontIconType = type;
initFontIconData(type);
};
// 获取当前点击的 icon 图标
const onColClick = (v) => {
state.fontIconPlaceholder = v;
state.fontIconVisible = false;
state.fontIconPrefix = v;
emit('get', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 清空当前点击的 icon 图标
const onClearFontIcon = () => {
state.fontIconPrefix = '';
emit('clear', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
// 页面加载时
onMounted(() => {
// 判断默认进来是什么类型图标,进行 tab 回显
if (props.type === 'all') {
if (props.modelValue?.indexOf('iconfont') > -1) onIconChange('ali');
else if (props.modelValue?.indexOf('element') > -1) onIconChange('ele');
else if (props.modelValue?.indexOf('fa') > -1) onIconChange('awe');
else onIconChange('ali');
} else {
onIconChange(props.type);
}
initResize();
getInputWidth();
});
// 监听双向绑定 modelValue 的变化
watch(
() => props.modelValue,
() => {
initModeValueEcho();
}
);
</script>

View File

@ -1,6 +1,5 @@
<script lang="ts">
<script>
// 渲染函数https://v3.cn.vuejs.org/guide/render-function.html
import { h, resolveComponent, defineComponent } from 'vue';
export default defineComponent({
name: 'svgIcon',
props: {

View File

@ -3,157 +3,137 @@
<el-aside class="layout-aside" :class="setCollapseStyle">
<Logo v-if="setShowLogo" />
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef" @mouseenter="onAsideEnterLeave(true)" @mouseleave="onAsideEnterLeave(false)">
<Vertical :menuList="menuList" />
<Vertical :menuList="state.menuList" />
</el-scrollbar>
</el-aside>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed, watch, getCurrentInstance, onBeforeMount } from 'vue';
import { useStore } from '/@/store/index';
<script setup name="layoutAside">
import Logo from '/@/layout/logo/index.vue';
import Vertical from '/@/layout/navMenu/vertical.vue';
export default {
name: 'layoutAside',
components: { Logo, Vertical },
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const state: any = reactive({
menuList: [],
clientWidth: '',
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 获取卡片全屏信息
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
// 设置菜单展开/收起时的宽度
const setCollapseStyle = computed(() => {
const { layout, isCollapse, menuBar } = store.state.themeConfig.themeConfig;
const asideBrColor =
menuBar === '#FFFFFF' || menuBar === '#FFF' || menuBar === '#fff' || menuBar === '#ffffff' ? 'layout-el-aside-br-color' : '';
// 判断是否是手机端
if (state.clientWidth <= 1000) {
if (isCollapse) {
document.body.setAttribute('class', 'el-popup-parent--hidden');
const asideEle = document.querySelector('.layout-container') as HTMLElement;
const modeDivs = document.createElement('div');
modeDivs.setAttribute('class', 'layout-aside-mobile-mode');
asideEle.appendChild(modeDivs);
modeDivs.addEventListener('click', closeLayoutAsideMobileMode);
return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-open'];
} else {
// 关闭弹窗
closeLayoutAsideMobileMode();
return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-close'];
}
const { proxy } = getCurrentInstance();
const store = useStore();
const state = reactive({
menuList: [],
clientWidth: '',
});
// 获取卡片全屏信息
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
// 设置菜单展开/收起时的宽度
const setCollapseStyle = computed(() => {
const { layout, isCollapse, menuBar } = store.state.themeConfig.themeConfig;
const asideBrColor = menuBar === '#FFFFFF' || menuBar === '#FFF' || menuBar === '#fff' || menuBar === '#ffffff' ? 'layout-el-aside-br-color' : '';
// 判断是否是手机端
if (state.clientWidth <= 1000) {
if (isCollapse) {
document.body.setAttribute('class', 'el-popup-parent--hidden');
const asideEle = document.querySelector('.layout-container');
const modeDivs = document.createElement('div');
modeDivs.setAttribute('class', 'layout-aside-mobile-mode');
asideEle.appendChild(modeDivs);
modeDivs.addEventListener('click', closeLayoutAsideMobileMode);
return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-open'];
} else {
// 关闭弹窗
closeLayoutAsideMobileMode();
return [asideBrColor, 'layout-aside-mobile', 'layout-aside-mobile-close'];
}
} else {
if (layout === 'columns') {
// 分栏布局,菜单收起时宽度给 1px
if (isCollapse) {
return [asideBrColor, 'layout-aside-pc-1'];
} else {
if (layout === 'columns') {
// 分栏布局,菜单收起时宽度给 1px
if (isCollapse) {
return [asideBrColor, 'layout-aside-pc-1'];
} else {
return [asideBrColor, 'layout-aside-pc-220'];
}
} else {
// 其它布局给 64px
if (isCollapse) {
return [asideBrColor, 'layout-aside-pc-64'];
} else {
return [asideBrColor, 'layout-aside-pc-220'];
}
}
return [asideBrColor, 'layout-aside-pc-220'];
}
});
// 关闭移动端蒙版
const closeLayoutAsideMobileMode = () => {
const el = document.querySelector('.layout-aside-mobile-mode');
el && el.parentNode?.removeChild(el);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) store.state.themeConfig.themeConfig.isCollapse = false;
document.body.setAttribute('class', '');
};
// 设置显示/隐藏 logo
const setShowLogo = computed(() => {
let { layout, isShowLogo } = store.state.themeConfig.themeConfig;
return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
});
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
if (store.state.themeConfig.themeConfig.layout === 'columns') return false;
state.menuList = filterRoutesFun(store.state.routesList.routesList);
};
// 路由过滤递归函数
const filterRoutesFun = (arr: Array<object>) => {
return arr
.filter((item: any) => !item.meta.isHide)
.map((item: any) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// 设置菜单导航是否固定(移动端)
const initMenuFixed = (clientWidth: number) => {
state.clientWidth = clientWidth;
};
// 鼠标移入、移出
const onAsideEnterLeave = (bool: Boolean) => {
let { layout } = store.state.themeConfig.themeConfig;
if (layout !== 'columns') return false;
if (!bool) proxy.mittBus.emit('restoreDefault');
store.dispatch('routesList/setColumnsMenuHover', bool);
};
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(store.state.themeConfig.themeConfig, (val) => {
if (val.isShowLogoChange !== val.isShowLogo) {
if (!proxy.$refs.layoutAsideScrollbarRef) return false;
proxy.$refs.layoutAsideScrollbarRef.update();
} else {
// 其它布局给 64px
if (isCollapse) {
return [asideBrColor, 'layout-aside-pc-64'];
} else {
return [asideBrColor, 'layout-aside-pc-220'];
}
});
// 监听vuex值的变化动态赋值给菜单中
watch(store.state, (val) => {
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) return false;
setFilterRoutes();
});
// 页面加载前
onBeforeMount(() => {
initMenuFixed(document.body.clientWidth);
setFilterRoutes();
// 此界面不需要取消监听(proxy.mittBus.off('setSendColumnsChildren))
// 因为切换布局时有的监听需要使用,取消了监听,某些操作将不生效
proxy.mittBus.on('setSendColumnsChildren', (res: any) => {
state.menuList = res.children;
});
proxy.mittBus.on('setSendClassicChildren', (res: any) => {
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = [];
state.menuList = res.children;
}
});
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
proxy.mittBus.on('layoutMobileResize', (res: any) => {
initMenuFixed(res.clientWidth);
closeLayoutAsideMobileMode();
});
});
return {
setCollapseStyle,
setShowLogo,
getThemeConfig,
isTagsViewCurrenFull,
onAsideEnterLeave,
...toRefs(state),
};
},
}
}
});
// 关闭移动端蒙版
const closeLayoutAsideMobileMode = () => {
const el = document.querySelector('.layout-aside-mobile-mode');
el && el.parentNode?.removeChild(el);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) store.state.themeConfig.themeConfig.isCollapse = false;
document.body.setAttribute('class', '');
};
// 设置显示/隐藏 logo
const setShowLogo = computed(() => {
let { layout, isShowLogo } = store.state.themeConfig.themeConfig;
return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
});
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
if (store.state.themeConfig.themeConfig.layout === 'columns') return false;
state.menuList = filterRoutesFun(store.state.routesList.routesList);
};
// 路由过滤递归函数
const filterRoutesFun = (arr) => {
return arr
.filter((item) => !item.meta.isHide)
.map((item) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// 设置菜单导航是否固定(移动端)
const initMenuFixed = (clientWidth) => {
state.clientWidth = clientWidth;
};
// 鼠标移入、移出
const onAsideEnterLeave = (bool) => {
let { layout } = store.state.themeConfig.themeConfig;
if (layout !== 'columns') return false;
if (!bool) proxy.mittBus.emit('restoreDefault');
store.dispatch('routesList/setColumnsMenuHover', bool);
};
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(store.state.themeConfig.themeConfig, (val) => {
if (val.isShowLogoChange !== val.isShowLogo) {
if (!proxy.$refs.layoutAsideScrollbarRef) return false;
proxy.$refs.layoutAsideScrollbarRef.update();
}
});
// 监听vuex值的变化动态赋值给菜单中
watch(store.state, (val) => {
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) return false;
setFilterRoutes();
});
// 页面加载前
onBeforeMount(() => {
initMenuFixed(document.body.clientWidth);
setFilterRoutes();
// 此界面不需要取消监听(proxy.mittBus.off('setSendColumnsChildren))
// 因为切换布局时有的监听需要使用,取消了监听,某些操作将不生效
proxy.mittBus.on('setSendColumnsChildren', (res) => {
state.menuList = res.children;
});
proxy.mittBus.on('setSendClassicChildren', (res) => {
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = [];
state.menuList = res.children;
}
});
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
proxy.mittBus.on('layoutMobileResize', (res) => {
initMenuFixed(res.clientWidth);
closeLayoutAsideMobileMode();
});
});
</script>

View File

@ -3,7 +3,7 @@
<el-scrollbar>
<ul @mouseleave="onColumnsAsideMenuMouseleave()">
<li
v-for="(v, k) in columnsAsideList"
v-for="(v, k) in state.columnsAsideList"
:key="k"
@click="onColumnsAsideMenuClick(v, k)"
@mouseenter="onColumnsAsideMenuMouseenter(v, k)"
@ -12,7 +12,7 @@
if (el) columnsAsideOffsetTopRefs[k] = el;
}
"
:class="{ 'layout-columns-active': liIndex === k, 'layout-columns-hover': liHoverIndex === k }"
:class="{ 'layout-columns-active': state.liIndex === k, 'layout-columns-hover': state.liHoverIndex === k }"
:title="v.meta.title"
>
<div :class="setColumnsAsidelayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
@ -42,163 +42,142 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs, ref, computed, onMounted, nextTick, getCurrentInstance, watch, onUnmounted } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
import { useStore } from '/@/store/index';
export default {
name: 'layoutColumnsAside',
setup() {
const columnsAsideOffsetTopRefs: any = ref([]);
const columnsAsideActiveRef = ref();
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const router = useRouter();
const state: any = reactive({
columnsAsideList: [],
liIndex: 0,
liOldIndex: null,
liHoverIndex: null,
liOldPath: null,
difference: 0,
routeSplit: [],
isNavHover: false,
});
// 设置分栏高亮风格
const setColumnsAsideStyle = computed(() => {
return store.state.themeConfig.themeConfig.columnsAsideStyle;
});
// 设置分栏布局风格
const setColumnsAsidelayout = computed(() => {
return store.state.themeConfig.themeConfig.columnsAsideLayout;
});
// 设置菜单高亮位置移动
const setColumnsAsideMove = (k: number) => {
state.liIndex = k;
columnsAsideActiveRef.value.style.top = `${columnsAsideOffsetTopRefs.value[k].offsetTop + state.difference}px`;
};
// 菜单高亮点击事件
const onColumnsAsideMenuClick = (v: Object, k: number) => {
setColumnsAsideMove(k);
let { path, redirect } = v as any;
if (redirect) router.push(redirect);
else router.push(path);
};
// 鼠标移入时,显示当前的子级菜单
const onColumnsAsideMenuMouseenter = (v: Object, k: number) => {
let { path } = v;
state.liOldPath = path;
state.liOldIndex = k;
state.liHoverIndex = k;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(path));
store.dispatch('routesList/setColumnsMenuHover', false);
store.dispatch('routesList/setColumnsNavHover', true);
state.isNavHover = true;
};
// 鼠标移走时,显示原来的子级菜单
const onColumnsAsideMenuMouseleave = async () => {
await store.dispatch('routesList/setColumnsNavHover', false);
// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
setTimeout(() => {
const { isColumnsMenuHover, isColumnsNavHover } = store.state.routesList;
if (!isColumnsMenuHover && !isColumnsNavHover) proxy.mittBus.emit('restoreDefault');
}, 100);
// state.isNavHover = false;
};
// 设置高亮动态位置
const onColumnsAsideDown = (k: number) => {
nextTick(() => {
setColumnsAsideMove(k);
});
};
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
state.columnsAsideList = filterRoutesFun(store.state.routesList.routesList);
const resData: any = setSendChildren(route.path);
if (Object.keys(resData).length <= 0) return false;
onColumnsAsideDown(resData.item[0].k);
proxy.mittBus.emit('setSendColumnsChildren', resData);
};
// 传送当前子级数据到菜单中
const setSendChildren = (path: string) => {
const currentPathSplit = path.split('/');
let currentData: any = {};
state.columnsAsideList.map((v: any, k: number) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
};
// 路由过滤递归函数
const filterRoutesFun = (arr: Array<object>) => {
return arr
.filter((item: any) => !item.meta.isHide)
.map((item: any) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// tagsView 点击时,根据路由查找下标 columnsAsideList实现左侧菜单高亮
const setColumnsMenuHighlight = (path: string) => {
state.routeSplit = path.split('/');
state.routeSplit.shift();
const routeFirst = `/${state.routeSplit[0]}`;
const currentSplitRoute = state.columnsAsideList.find((v: any) => v.path === routeFirst);
if (!currentSplitRoute) return false;
// 延迟拿值,防止取不到
setTimeout(() => {
onColumnsAsideDown(currentSplitRoute.k);
}, 0);
};
// 监听布局配置信息的变化,动态增加菜单高亮位置移动像素
watch(store.state, (val) => {
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));
} else {
state.liHoverIndex = state.liOldIndex;
if (!state.liOldPath) return false;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
}
});
// 页面加载时
onMounted(() => {
setFilterRoutes();
// 销毁变量,防止鼠标再次移入时,保留了上次的记录
proxy.mittBus.on('restoreDefault', () => {
state.liOldIndex = null;
state.liOldPath = null;
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('restoreDefault', () => {});
});
// 路由更新时
onBeforeRouteUpdate((to) => {
setColumnsMenuHighlight(to.path);
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
});
return {
columnsAsideOffsetTopRefs,
columnsAsideActiveRef,
onColumnsAsideDown,
setColumnsAsideStyle,
setColumnsAsidelayout,
onColumnsAsideMenuClick,
onColumnsAsideMenuMouseenter,
onColumnsAsideMenuMouseleave,
...toRefs(state),
};
},
<script setup name="layoutColumnsAside">
import { onBeforeRouteUpdate } from 'vue-router';
const columnsAsideOffsetTopRefs = ref([]);
const columnsAsideActiveRef = ref();
const { proxy } = getCurrentInstance();
const store = useStore();
const route = useRoute();
const router = useRouter();
const state = reactive({
columnsAsideList: [],
liIndex: 0,
liOldIndex: null,
liHoverIndex: null,
liOldPath: null,
difference: 0,
routeSplit: [],
});
// 设置分栏高亮风格
const setColumnsAsideStyle = computed(() => {
return store.state.themeConfig.themeConfig.columnsAsideStyle;
});
// 设置分栏布局风格
const setColumnsAsidelayout = computed(() => {
return store.state.themeConfig.themeConfig.columnsAsideLayout;
});
// 设置菜单高亮位置移动
const setColumnsAsideMove = (k) => {
state.liIndex = k;
columnsAsideActiveRef.value.style.top = `${columnsAsideOffsetTopRefs.value[k].offsetTop + state.difference}px`;
};
// 菜单高亮点击事件
const onColumnsAsideMenuClick = (v, k) => {
setColumnsAsideMove(k);
let { path, redirect } = v;
if (redirect) router.push(redirect);
else router.push(path);
};
// 鼠标移入时,显示当前的子级菜单
const onColumnsAsideMenuMouseenter = (v, k) => {
let { path } = v;
state.liOldPath = path;
state.liOldIndex = k;
state.liHoverIndex = k;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(path));
store.dispatch('routesList/setColumnsMenuHover', false);
store.dispatch('routesList/setColumnsNavHover', true);
};
// 鼠标移走时,显示原来的子级菜单
const onColumnsAsideMenuMouseleave = async () => {
await store.dispatch('routesList/setColumnsNavHover', false);
// 添加延时器,防止拿到的 store.state.routesList 值不是最新的
setTimeout(() => {
const { isColumnsMenuHover, isColumnsNavHover } = store.state.routesList;
if (!isColumnsMenuHover && !isColumnsNavHover) proxy.mittBus.emit('restoreDefault');
}, 100);
};
// 设置高亮动态位置
const onColumnsAsideDown = (k) => {
nextTick(() => {
setColumnsAsideMove(k);
});
};
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
state.columnsAsideList = filterRoutesFun(store.state.routesList.routesList);
const resData = setSendChildren(route.path);
if (Object.keys(resData).length <= 0) return false;
onColumnsAsideDown(resData.item[0].k);
proxy.mittBus.emit('setSendColumnsChildren', resData);
};
// 传送当前子级数据到菜单中
const setSendChildren = (path) => {
const currentPathSplit = path.split('/');
let currentData = {};
state.columnsAsideList.map((v, k) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
};
// 路由过滤递归函数
const filterRoutesFun = (arr) => {
return arr
.filter((item) => !item.meta.isHide)
.map((item) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// tagsView 点击时,根据路由查找下标 columnsAsideList实现左侧菜单高亮
const setColumnsMenuHighlight = (path) => {
state.routeSplit = path.split('/');
state.routeSplit.shift();
const routeFirst = `/${state.routeSplit[0]}`;
const currentSplitRoute = state.columnsAsideList.find((v) => v.path === routeFirst);
if (!currentSplitRoute) return false;
// 延迟拿值,防止取不到
setTimeout(() => {
onColumnsAsideDown(currentSplitRoute.k);
}, 0);
};
// 监听布局配置信息的变化,动态增加菜单高亮位置移动像素
watch(store.state, (val) => {
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));
} else {
state.liHoverIndex = state.liOldIndex;
if (!state.liOldPath) return false;
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(state.liOldPath));
}
});
// 页面加载时
onMounted(() => {
setFilterRoutes();
// 销毁变量,防止鼠标再次移入时,保留了上次的记录
proxy.mittBus.on('restoreDefault', () => {
state.liOldIndex = null;
state.liOldPath = null;
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('restoreDefault', () => {});
});
// 路由更新时
onBeforeRouteUpdate((to) => {
setColumnsMenuHighlight(to.path);
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
});
</script>
<style scoped lang="scss">

View File

@ -4,29 +4,18 @@
</el-header>
</template>
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index';
<script setup name="layoutHeader">
import NavBarsIndex from '/@/layout/navBars/index.vue';
export default {
name: 'layoutHeader',
components: { NavBarsIndex },
setup() {
const store = useStore();
// 设置 header 的高度
const setHeaderHeight = computed(() => {
let { isTagsview, layout } = store.state.themeConfig.themeConfig;
if (isTagsview && layout !== 'classic') return '84px';
else return '50px';
});
// 获取卡片全屏信息
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
return {
setHeaderHeight,
isTagsViewCurrenFull,
};
},
};
const store = useStore();
// 设置 header 的高度
const setHeaderHeight = computed(() => {
let { isTagsview, layout } = store.state.themeConfig.themeConfig;
if (isTagsview && layout !== 'classic') return '84px';
else return '50px';
});
// 获取卡片全屏信息
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
</script>

View File

@ -4,8 +4,8 @@
class="layout-scrollbar"
ref="layoutScrollbarRef"
:style="{
minHeight: `calc(100vh - ${headerHeight})`,
padding: currentRouteMeta.isLink && currentRouteMeta.isIframe ? 0 : '',
minHeight: `calc(100vh - ${state.headerHeight})`,
padding: state.currentRouteMeta.isLink && state.currentRouteMeta.isIframe ? 0 : '',
transition: 'padding 0.3s ease-in-out',
}"
>
@ -15,61 +15,49 @@
</el-main>
</template>
<script lang="ts">
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, watch, onBeforeMount } from 'vue';
import { useStore } from '/@/store/index';
import { useRoute } from 'vue-router';
<script setup name="layoutMain">
import LayoutParentView from '/@/layout/routerView/parent.vue';
import Footer from '/@/layout/footer/index.vue';
export default defineComponent({
name: 'layoutMain',
components: { LayoutParentView, Footer },
setup() {
const { proxy } = getCurrentInstance() as any;
const route = useRoute();
const store = useStore();
const state = reactive({
headerHeight: '',
currentRouteMeta: {},
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置 main 的高度
const initHeaderHeight = () => {
let { isTagsview } = store.state.themeConfig.themeConfig;
if (isTagsview) return (state.headerHeight = `84px`);
else return (state.headerHeight = `50px`);
};
// 初始化获取当前路由 meta用于设置 iframes padding
const initGetMeta = () => {
state.currentRouteMeta = route.meta;
};
// 页面加载前
onBeforeMount(() => {
initHeaderHeight();
initGetMeta();
});
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(store.state.themeConfig.themeConfig, (val) => {
state.headerHeight = val.isTagsview ? '84px' : '50px';
if (val.isFixedHeaderChange !== val.isFixedHeader) {
if (!proxy.$refs.layoutScrollbarRef) return false;
proxy.$refs.layoutScrollbarRef.update();
}
});
// 监听路由变化
watch(
() => route.path,
() => {
state.currentRouteMeta = route.meta;
}
);
return {
getThemeConfig,
...toRefs(state),
};
},
const { proxy } = getCurrentInstance();
const route = useRoute();
const store = useStore();
const state = reactive({
headerHeight: '',
currentRouteMeta: {},
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置 main 的高度
const initHeaderHeight = () => {
let { isTagsview } = store.state.themeConfig.themeConfig;
if (isTagsview) return (state.headerHeight = `84px`);
else return (state.headerHeight = `50px`);
};
// 初始化获取当前路由 meta用于设置 iframes padding
const initGetMeta = () => {
state.currentRouteMeta = route.meta;
};
// 页面加载前
onBeforeMount(() => {
initHeaderHeight();
initGetMeta();
});
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
watch(store.state.themeConfig.themeConfig, (val) => {
state.headerHeight = val.isTagsview ? '84px' : '50px';
if (val.isFixedHeaderChange !== val.isFixedHeader) {
if (!proxy.$refs.layoutScrollbarRef) return false;
proxy.$refs.layoutScrollbarRef.update();
}
});
// 监听路由变化
watch(
() => route.path,
() => {
state.currentRouteMeta = route.meta;
}
);
</script>

View File

@ -1,5 +1,5 @@
<template>
<div class="layout-footer mt15" v-show="isDelayFooter">
<div class="layout-footer mt15" v-show="state.isDelayFooter">
<div class="layout-footer-warp">
<div>vue-next-adminMade by lyt with </div>
<div class="mt5">版权所有深圳市xxx软件科技有限公司</div>
@ -7,27 +7,19 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive } from 'vue';
<script setup name="layoutFooter">
import { onBeforeRouteUpdate } from 'vue-router';
export default {
name: 'layoutFooter',
setup() {
const state = reactive({
isDelayFooter: true,
});
// 路由改变时,等主界面动画加载完毕再显示 footer
onBeforeRouteUpdate(() => {
state.isDelayFooter = false;
setTimeout(() => {
state.isDelayFooter = true;
}, 800);
});
return {
...toRefs(state),
};
},
};
const state = reactive({
isDelayFooter: true,
});
// 路由改变时,等主界面动画加载完毕再显示 footer
onBeforeRouteUpdate(() => {
state.isDelayFooter = false;
setTimeout(() => {
state.isDelayFooter = true;
}, 800);
});
</script>
<style scoped lang="scss">

View File

@ -5,53 +5,43 @@
<Columns v-else-if="getThemeConfig.layout === 'columns'" />
</template>
<script lang="ts">
import { computed, onBeforeMount, onUnmounted, getCurrentInstance } from 'vue';
import { useStore } from '/@/store/index';
<script setup name="layout">
import { Local } from '/@/utils/storage';
import Defaults from '/@/layout/main/defaults.vue';
import Classic from '/@/layout/main/classic.vue';
import Transverse from '/@/layout/main/transverse.vue';
import Columns from '/@/layout/main/columns.vue';
export default {
name: 'layout',
components: { Defaults, Classic, Transverse, Columns },
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
const { proxy } = getCurrentInstance();
const store = useStore();
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 窗口大小改变时(适配移动端)
const onLayoutResize = () => {
if (!Local.get('oldLayout')) Local.set('oldLayout', getThemeConfig.value.layout);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
getThemeConfig.value.isCollapse = false;
proxy.mittBus.emit('layoutMobileResize', {
layout: 'defaults',
clientWidth,
});
// 窗口大小改变时(适配移动端)
const onLayoutResize = () => {
if (!Local.get('oldLayout')) Local.set('oldLayout', getThemeConfig.value.layout);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
getThemeConfig.value.isCollapse = false;
proxy.mittBus.emit('layoutMobileResize', {
layout: 'defaults',
clientWidth,
});
} else {
proxy.mittBus.emit('layoutMobileResize', {
layout: Local.get('oldLayout') ? Local.get('oldLayout') : getThemeConfig.value.layout,
clientWidth,
});
}
};
// 页面加载前
onBeforeMount(() => {
onLayoutResize();
window.addEventListener('resize', onLayoutResize);
} else {
proxy.mittBus.emit('layoutMobileResize', {
layout: Local.get('oldLayout') ? Local.get('oldLayout') : getThemeConfig.value.layout,
clientWidth,
});
// 页面卸载时
onUnmounted(() => {
window.removeEventListener('resize', onLayoutResize);
});
return {
getThemeConfig,
};
},
}
};
// 页面加载前
onBeforeMount(() => {
onLayoutResize();
window.addEventListener('resize', onLayoutResize);
});
// 页面卸载时
onUnmounted(() => {
window.removeEventListener('resize', onLayoutResize);
});
</script>

View File

@ -1,7 +1,7 @@
<template>
<div v-show="isShowLockScreen">
<div class="layout-lock-screen-mask"></div>
<div class="layout-lock-screen-img" :class="{ 'layout-lock-screen-filter': isShowLoockLogin }"></div>
<div class="layout-lock-screen-img" :class="{ 'layout-lock-screen-filter': state.isShowLoockLogin }"></div>
<div class="layout-lock-screen">
<div
class="layout-lock-screen-date"
@ -15,9 +15,9 @@
>
<div class="layout-lock-screen-date-box">
<div class="layout-lock-screen-date-box-time">
{{ time.hm }}<span class="layout-lock-screen-date-box-minutes">{{ time.s }}</span>
{{ state.time.hm }}<span class="layout-lock-screen-date-box-minutes">{{ state.time.s }}</span>
</div>
<div class="layout-lock-screen-date-box-info">{{ time.mdq }}</div>
<div class="layout-lock-screen-date-box-info">{{ state.time.mdq }}</div>
</div>
<div class="layout-lock-screen-date-top">
<SvgIcon name="elementTop" />
@ -25,7 +25,7 @@
</div>
</div>
<transition name="el-zoom-in-center">
<div v-show="isShowLoockLogin" class="layout-lock-screen-login">
<div v-show="state.isShowLoockLogin" class="layout-lock-screen-login">
<div class="layout-lock-screen-login-box">
<div class="layout-lock-screen-login-box-img">
<img src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&fm=26&gp=0.jpg" />
@ -35,7 +35,7 @@
<el-input
placeholder="请输入密码"
ref="layoutLockScreenInputRef"
v-model="lockScreenPassword"
v-model="state.lockScreenPassword"
@keyup.enter.native.stop="onLockScreenSubmit()"
>
<template #append>
@ -59,137 +59,123 @@
</div>
</template>
<script lang="ts">
import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, getCurrentInstance, defineComponent } from 'vue';
import { useStore } from '/@/store/index';
<script setup name="layoutLockScreen">
import { formatDate } from '/@/utils/formatTime';
import { Local } from '/@/utils/storage';
export default defineComponent({
name: 'layoutLockScreen',
setup() {
const { proxy } = getCurrentInstance() as any;
const layoutLockScreenInputRef = ref();
const store = useStore();
const state: any = reactive({
transparency: 1,
downClientY: 0,
moveDifference: 0,
isShowLoockLogin: false,
isFlags: false,
querySelectorEl: '',
time: {
hm: '',
s: '',
mdq: '',
},
setIntervalTime: 0,
isShowLockScreen: false,
isShowLockScreenIntervalTime: 0,
lockScreenPassword: '',
});
// 鼠标按下
const onDown = (down: any) => {
state.isFlags = true;
state.downClientY = down.touches ? down.touches[0].clientY : down.clientY;
};
// 鼠标移动
const onMove = (move: any) => {
if (state.isFlags) {
const el = state.querySelectorEl;
const opacitys = (state.transparency -= 1 / 200);
if (move.touches) {
state.moveDifference = move.touches[0].clientY - state.downClientY;
} else {
state.moveDifference = move.clientY - state.downClientY;
}
if (state.moveDifference >= 0) return false;
el.setAttribute('style', `top:${state.moveDifference}px;cursor:pointer;opacity:${opacitys};`);
if (state.moveDifference < -400) {
el.setAttribute('style', `top:${-el.clientHeight}px;cursor:pointer;transition:all 0.3s ease;`);
state.moveDifference = -el.clientHeight;
setTimeout(() => {
el && el.parentNode?.removeChild(el);
}, 300);
}
if (state.moveDifference === -el.clientHeight) {
state.isShowLoockLogin = true;
layoutLockScreenInputRef.value.focus();
}
}
};
// 鼠标松开
const onEnd = () => {
state.isFlags = false;
state.transparency = 1;
if (state.moveDifference >= -400) {
state.querySelectorEl.setAttribute('style', `top:0px;opacity:1;transition:all 0.3s ease;`);
}
};
// 获取要拖拽的初始元素
const initGetElement = () => {
nextTick(() => {
state.querySelectorEl = proxy.$refs.layoutLockScreenDateRef;
});
};
// 时间初始化
const initTime = () => {
state.time.hm = formatDate(new Date(), 'HH:MM');
state.time.s = formatDate(new Date(), 'SS');
state.time.mdq = formatDate(new Date(), 'mm月dd日WWW');
};
// 时间初始化定时器
const initSetTime = () => {
initTime();
state.setIntervalTime = window.setInterval(() => {
initTime();
}, 1000);
};
// 锁屏时间定时器
const initLockScreen = () => {
if (store.state.themeConfig.themeConfig.isLockScreen) {
state.isShowLockScreenIntervalTime = window.setInterval(() => {
if (store.state.themeConfig.themeConfig.lockScreenTime <= 1) {
state.isShowLockScreen = true;
setLocalThemeConfig();
return false;
}
store.state.themeConfig.themeConfig.lockScreenTime--;
}, 1000);
} else {
clearInterval(state.isShowLockScreenIntervalTime);
}
};
// 存储布局配置
const setLocalThemeConfig = () => {
store.state.themeConfig.themeConfig.isDrawer = false;
Local.set('themeConfig', store.state.themeConfig.themeConfig);
};
// 密码输入点击事件
const onLockScreenSubmit = () => {
store.state.themeConfig.themeConfig.isLockScreen = false;
store.state.themeConfig.themeConfig.lockScreenTime = 30;
setLocalThemeConfig();
};
// 页面加载时
onMounted(() => {
initGetElement();
initSetTime();
initLockScreen();
});
// 页面卸载时
onUnmounted(() => {
window.clearInterval(state.setIntervalTime);
window.clearInterval(state.isShowLockScreenIntervalTime);
});
return {
layoutLockScreenInputRef,
onDown,
onMove,
onEnd,
onLockScreenSubmit,
...toRefs(state),
};
const { proxy } = getCurrentInstance();
const layoutLockScreenInputRef = ref();
const store = useStore();
const state = reactive({
transparency: 1,
downClientY: 0,
moveDifference: 0,
isShowLoockLogin: false,
isFlags: false,
querySelectorEl: '',
time: {
hm: '',
s: '',
mdq: '',
},
setIntervalTime: 0,
isShowLockScreen: false,
isShowLockScreenIntervalTime: 0,
lockScreenPassword: '',
});
// 鼠标按下
const onDown = (down) => {
state.isFlags = true;
state.downClientY = down.touches ? down.touches[0].clientY : down.clientY;
};
// 鼠标移动
const onMove = (move) => {
if (state.isFlags) {
const el = state.querySelectorEl;
const opacitys = (state.transparency -= 1 / 200);
if (move.touches) {
state.moveDifference = move.touches[0].clientY - state.downClientY;
} else {
state.moveDifference = move.clientY - state.downClientY;
}
if (state.moveDifference >= 0) return false;
el.setAttribute('style', `top:${state.moveDifference}px;cursor:pointer;opacity:${opacitys};`);
if (state.moveDifference < -400) {
el.setAttribute('style', `top:${-el.clientHeight}px;cursor:pointer;transition:all 0.3s ease;`);
state.moveDifference = -el.clientHeight;
setTimeout(() => {
el && el.parentNode?.removeChild(el);
}, 300);
}
if (state.moveDifference === -el.clientHeight) {
state.isShowLoockLogin = true;
layoutLockScreenInputRef.value.focus();
}
}
};
// 鼠标松开
const onEnd = () => {
state.isFlags = false;
state.transparency = 1;
if (state.moveDifference >= -400) {
state.querySelectorEl.setAttribute('style', `top:0px;opacity:1;transition:all 0.3s ease;`);
}
};
// 获取要拖拽的初始元素
const initGetElement = () => {
nextTick(() => {
state.querySelectorEl = proxy.$refs.layoutLockScreenDateRef;
});
};
// 时间初始化
const initTime = () => {
state.time.hm = formatDate(new Date(), 'HH:MM');
state.time.s = formatDate(new Date(), 'SS');
state.time.mdq = formatDate(new Date(), 'mm月dd日WWW');
};
// 时间初始化定时器
const initSetTime = () => {
initTime();
state.setIntervalTime = window.setInterval(() => {
initTime();
}, 1000);
};
// 锁屏时间定时器
const initLockScreen = () => {
if (store.state.themeConfig.themeConfig.isLockScreen) {
state.isShowLockScreenIntervalTime = window.setInterval(() => {
if (store.state.themeConfig.themeConfig.lockScreenTime <= 1) {
state.isShowLockScreen = true;
setLocalThemeConfig();
return false;
}
store.state.themeConfig.themeConfig.lockScreenTime--;
}, 1000);
} else {
clearInterval(state.isShowLockScreenIntervalTime);
}
};
// 存储布局配置
const setLocalThemeConfig = () => {
store.state.themeConfig.themeConfig.isDrawer = false;
Local.set('themeConfig', store.state.themeConfig.themeConfig);
};
// 密码输入点击事件
const onLockScreenSubmit = () => {
store.state.themeConfig.themeConfig.isLockScreen = false;
store.state.themeConfig.themeConfig.lockScreenTime = 30;
setLocalThemeConfig();
};
// 页面加载时
onMounted(() => {
initGetElement();
initSetTime();
initLockScreen();
});
// 页面卸载时
onUnmounted(() => {
window.clearInterval(state.setIntervalTime);
window.clearInterval(state.isShowLockScreenIntervalTime);
});
</script>

View File

@ -8,35 +8,23 @@
</div>
</template>
<script lang="ts">
import { computed, getCurrentInstance } from 'vue';
import { useStore } from '/@/store/index';
export default {
name: 'layoutLogo',
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置 logo 的显示。classic 经典布局默认显示 logo
const setShowLogo = computed(() => {
let { isCollapse, layout } = store.state.themeConfig.themeConfig;
return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
});
// logo 点击实现菜单展开/收起
const onThemeConfigChange = () => {
if (store.state.themeConfig.themeConfig.layout === 'transverse') return false;
proxy.mittBus.emit('onMenuClick');
store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
};
return {
setShowLogo,
getThemeConfig,
onThemeConfigChange,
};
},
<script setup name="layoutLogo">
const { proxy } = getCurrentInstance();
const store = useStore();
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置 logo 的显示。classic 经典布局默认显示 logo
const setShowLogo = computed(() => {
let { isCollapse, layout } = store.state.themeConfig.themeConfig;
return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
});
// logo 点击实现菜单展开/收起
const onThemeConfigChange = () => {
if (store.state.themeConfig.themeConfig.layout === 'transverse') return false;
proxy.mittBus.emit('onMenuClick');
store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
};
</script>

View File

@ -12,25 +12,15 @@
</el-container>
</template>
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index';
<script setup name="layoutClassic">
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 {
name: 'layoutClassic',
components: { Aside, Header, Main, TagsView },
setup() {
const store = useStore();
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
return {
getThemeConfig,
};
},
};
const store = useStore();
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
</script>

View File

@ -15,24 +15,14 @@
</el-container>
</template>
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index';
<script setup name="layoutColumns">
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 {
name: 'layoutColumns',
components: { Aside, Header, Main, ColumnsAside },
setup() {
const store = useStore();
const isFixedHeader = computed(() => {
return store.state.themeConfig.themeConfig.isFixedHeader;
});
return {
isFixedHeader,
};
},
};
const store = useStore();
const isFixedHeader = computed(() => {
return store.state.themeConfig.themeConfig.isFixedHeader;
});
</script>

View File

@ -12,33 +12,22 @@
</el-container>
</template>
<script lang="ts">
import { computed, getCurrentInstance, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
<script setup name="layoutDefaults">
import Aside from '/@/layout/component/aside.vue';
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
export default {
name: 'layoutDefaults',
components: { Aside, Header, Main },
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const isFixedHeader = computed(() => {
return store.state.themeConfig.themeConfig.isFixedHeader;
});
// 监听路由的变化
watch(
() => route.path,
() => {
proxy.$refs.layoutDefaultsScrollbarRef.wrap$.scrollTop = 0;
}
);
return {
isFixedHeader,
};
},
};
const { proxy } = getCurrentInstance();
const store = useStore();
const route = useRoute();
const isFixedHeader = computed(() => {
return store.state.themeConfig.themeConfig.isFixedHeader;
});
// 监听路由的变化
watch(
() => route.path,
() => {
proxy.$refs.layoutDefaultsScrollbarRef.wrap$.scrollTop = 0;
}
);
</script>

View File

@ -6,11 +6,7 @@
</el-container>
</template>
<script lang="ts">
<script setup name="layoutTransverse">
import Header from '/@/layout/component/header.vue';
import Main from '/@/layout/component/main.vue';
export default {
name: 'layoutTransverse',
components: { Header, Main },
};
</script>

View File

@ -7,8 +7,8 @@
/>
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
<transition-group name="breadcrumb" mode="out-in">
<el-breadcrumb-item v-for="(v, k) in breadcrumbList" :key="v.meta.title">
<span v-if="k === breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
<el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="v.meta.title">
<span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon" />{{ v.meta.title }}
</span>
<a v-else @click.prevent="onBreadcrumbClick(v)">
@ -20,88 +20,74 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed, getCurrentInstance, onMounted } from 'vue';
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
import { useStore } from '/@/store/index';
export default {
name: 'layoutBreadcrumb',
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const router = useRouter();
const state: any = reactive({
breadcrumbList: [],
routeSplit: [],
routeSplitFirst: '',
routeSplitIndex: 1,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 动态设置经典、横向布局不显示
const isShowBreadcrumb = computed(() => {
initRouteSplit(route.path);
const { layout, isBreadcrumb } = store.state.themeConfig.themeConfig;
if (layout === 'classic' || layout === 'transverse') {
return 'none';
} else {
return isBreadcrumb ? '' : 'none';
<script setup name="layoutBreadcrumb">
import { onBeforeRouteUpdate } from 'vue-router';
const { proxy } = getCurrentInstance();
const store = useStore();
const route = useRoute();
const router = useRouter();
const state = reactive({
breadcrumbList: [],
routeSplit: [],
routeSplitFirst: '',
routeSplitIndex: 1,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 动态设置经典、横向布局不显示
const isShowBreadcrumb = computed(() => {
initRouteSplit(route.path);
const { layout, isBreadcrumb } = store.state.themeConfig.themeConfig;
if (layout === 'classic' || layout === 'transverse') {
return 'none';
} else {
return isBreadcrumb ? '' : 'none';
}
});
// 面包屑点击时
const onBreadcrumbClick = (v) => {
const { redirect, path } = v;
if (redirect) router.push(redirect);
else router.push(path);
};
// 展开/收起左侧菜单点击
const onThemeConfigChange = () => {
proxy.mittBus.emit('onMenuClick');
store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
};
// 处理面包屑数据
const getBreadcrumbList = (arr) => {
arr.map((item) => {
state.routeSplit.map((v, k, arrs) => {
if (state.routeSplitFirst === item.path) {
state.routeSplitFirst += `/${arrs[state.routeSplitIndex]}`;
state.breadcrumbList.push(item);
state.routeSplitIndex++;
if (item.children) getBreadcrumbList(item.children);
}
});
// 面包屑点击时
const onBreadcrumbClick = (v: any) => {
const { redirect, path } = v;
if (redirect) router.push(redirect);
else router.push(path);
};
// 展开/收起左侧菜单点击
const onThemeConfigChange = () => {
proxy.mittBus.emit('onMenuClick');
store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
};
// 处理面包屑数据
const getBreadcrumbList = (arr: Array<object>) => {
arr.map((item: any) => {
state.routeSplit.map((v: any, k: number, arrs: any) => {
if (state.routeSplitFirst === item.path) {
state.routeSplitFirst += `/${arrs[state.routeSplitIndex]}`;
state.breadcrumbList.push(item);
state.routeSplitIndex++;
if (item.children) getBreadcrumbList(item.children);
}
});
});
};
// 当前路由字符串切割成数组,并删除第一项空内容
const initRouteSplit = (path: string) => {
if (!store.state.themeConfig.themeConfig.isBreadcrumb) return false;
state.breadcrumbList = [store.state.routesList.routesList[0]];
state.routeSplit = path.split('/');
state.routeSplit.shift();
state.routeSplitFirst = `/${state.routeSplit[0]}`;
state.routeSplitIndex = 1;
getBreadcrumbList(store.state.routesList.routesList);
};
// 页面加载时
onMounted(() => {
initRouteSplit(route.path);
});
// 路由更新时
onBeforeRouteUpdate((to) => {
initRouteSplit(to.path);
});
return {
onThemeConfigChange,
isShowBreadcrumb,
getThemeConfig,
onBreadcrumbClick,
...toRefs(state),
};
},
});
};
// 当前路由字符串切割成数组,并删除第一项空内容
const initRouteSplit = (path) => {
if (!store.state.themeConfig.themeConfig.isBreadcrumb) return false;
state.breadcrumbList = [store.state.routesList.routesList[0]];
state.routeSplit = path.split('/');
state.routeSplit.shift();
state.routeSplitFirst = `/${state.routeSplit[0]}`;
state.routeSplitIndex = 1;
getBreadcrumbList(store.state.routesList.routesList);
};
// 页面加载时
onMounted(() => {
initRouteSplit(route.path);
});
// 路由更新时
onBeforeRouteUpdate((to) => {
initRouteSplit(to.path);
});
</script>
<style scoped lang="scss">

View File

@ -6,28 +6,15 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed } from 'vue';
import { useStore } from '/@/store/index';
export default {
name: 'layoutCloseFull',
setup() {
const store = useStore();
const state: any = reactive({});
// 获取卡片全屏信息
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
// 关闭当前全屏
const onCloseFullscreen = () => {
store.dispatch('tagsViewRoutes/setCurrenFullscreen', false);
};
return {
isTagsViewCurrenFull,
onCloseFullscreen,
...toRefs(state),
};
},
<script setup name="layoutCloseFull">
const store = useStore();
// 获取卡片全屏信息
const isTagsViewCurrenFull = computed(() => {
return store.state.tagsViewRoutes.isTagsViewCurrenFull;
});
// 关闭当前全屏
const onCloseFullscreen = () => {
store.dispatch('tagsViewRoutes/setCurrenFullscreen', false);
};
</script>

View File

@ -2,104 +2,86 @@
<div class="layout-navbars-breadcrumb-index">
<Logo v-if="setIsShowLogo" />
<Breadcrumb />
<Horizontal :menuList="menuList" v-if="isLayoutTransverse" />
<Horizontal :menuList="state.menuList" v-if="isLayoutTransverse" />
<User />
</div>
</template>
<script lang="ts">
import { computed, reactive, toRefs, onMounted, onUnmounted, getCurrentInstance } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
<script setup name="layoutBreadcrumbIndex">
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';
export default {
name: 'layoutBreadcrumbIndex',
components: { Breadcrumb, User, Logo, Horizontal },
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const state: any = reactive({
menuList: [],
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置 logo 显示/隐藏
const setIsShowLogo = computed(() => {
let { isShowLogo, layout } = store.state.themeConfig.themeConfig;
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
});
// 设置是否显示横向导航菜单
const isLayoutTransverse = computed(() => {
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
});
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = delClassicChildren(filterRoutesFun(store.state.routesList.routesList));
const resData = setSendClassicChildren(route.path);
proxy.mittBus.emit('setSendClassicChildren', resData);
} else {
state.menuList = filterRoutesFun(store.state.routesList.routesList);
}
};
// 设置了分割菜单时,删除底下 children
const delClassicChildren = (arr: Array<object>) => {
arr.map((v: any) => {
if (v.children) delete v.children;
});
return arr;
};
// 路由过滤递归函数
const filterRoutesFun = (arr: Array<object>) => {
return arr
.filter((item: any) => !item.meta.isHide)
.map((item: any) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// 传送当前子级数据到菜单中
const setSendClassicChildren = (path: string) => {
const currentPathSplit = path.split('/');
let currentData: any = {};
filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
};
// 页面加载时
onMounted(() => {
setFilterRoutes();
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes');
});
return {
getThemeConfig,
setIsShowLogo,
isLayoutTransverse,
...toRefs(state),
};
},
const { proxy } = getCurrentInstance();
const store = useStore();
const route = useRoute();
const state = reactive({
menuList: [],
});
// 设置 logo 显示/隐藏
const setIsShowLogo = computed(() => {
let { isShowLogo, layout } = store.state.themeConfig.themeConfig;
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
});
// 设置是否显示横向导航菜单
const isLayoutTransverse = computed(() => {
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
});
// 设置/过滤路由(非静态路由/是否显示在菜单中)
const setFilterRoutes = () => {
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = delClassicChildren(filterRoutesFun(store.state.routesList.routesList));
const resData = setSendClassicChildren(route.path);
proxy.mittBus.emit('setSendClassicChildren', resData);
} else {
state.menuList = filterRoutesFun(store.state.routesList.routesList);
}
};
// 设置了分割菜单时,删除底下 children
const delClassicChildren = (arr) => {
arr.map((v) => {
if (v.children) delete v.children;
});
return arr;
};
// 路由过滤递归函数
const filterRoutesFun = (arr) => {
return arr
.filter((item) => !item.meta.isHide)
.map((item) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// 传送当前子级数据到菜单中
const setSendClassicChildren = (path) => {
const currentPathSplit = path.split('/');
let currentData = {};
filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
};
// 页面加载时
onMounted(() => {
setFilterRoutes();
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
setFilterRoutes();
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes');
});
</script>
<style scoped lang="scss">

View File

@ -1,8 +1,8 @@
<template>
<div class="layout-search-dialog">
<el-dialog v-model="isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
<el-dialog v-model="state.isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
<el-autocomplete
v-model="menuQuery"
v-model="state.menuQuery"
:fetch-suggestions="menuSearch"
placeholder="菜单搜索:支持中文、路由路径"
ref="layoutMenuAutocompleteRef"
@ -22,77 +22,65 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs, defineComponent, ref, nextTick } from 'vue';
import { useRouter } from 'vue-router';
import { useStore } from '/@/store/index';
export default defineComponent({
name: 'layoutBreadcrumbSearch',
setup() {
const layoutMenuAutocompleteRef = ref();
const store = useStore();
const router = useRouter();
const state: any = reactive({
isShowSearch: false,
menuQuery: '',
tagsViewList: [],
});
// 搜索弹窗打开
const openSearch = () => {
state.menuQuery = '';
state.isShowSearch = true;
initTageView();
nextTick(() => {
layoutMenuAutocompleteRef.value.focus();
});
};
// 搜索弹窗关闭
const closeSearch = () => {
state.isShowSearch = false;
};
// 菜单搜索数据过滤
const menuSearch = (queryString: any, cb: any) => {
let results = queryString ? state.tagsViewList.filter(createFilter(queryString)) : state.tagsViewList;
cb(results);
};
// 菜单搜索过滤
const createFilter = (queryString: any) => {
return (restaurant: any) => {
return (
restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1
);
};
};
// 初始化菜单数据
const initTageView = () => {
if (state.tagsViewList.length > 0) return false;
store.state.tagsViewRoutes.tagsViewRoutes.map((v: any) => {
if (!v.meta.isHide) state.tagsViewList.push({ ...v });
});
};
// 当前菜单选中时
const onHandleSelect = (item: any) => {
let { path, redirect } = item;
if (item.meta.isLink && !item.meta.isIframe) window.open(item.meta.isLink);
else if (redirect) router.push(redirect);
else router.push(path);
closeSearch();
};
// input 失去焦点时
const onSearchBlur = () => {
closeSearch();
};
return {
layoutMenuAutocompleteRef,
openSearch,
closeSearch,
menuSearch,
onHandleSelect,
onSearchBlur,
...toRefs(state),
};
},
<script setup name="layoutBreadcrumbSearch">
const layoutMenuAutocompleteRef = ref();
const store = useStore();
const router = useRouter();
const state = reactive({
isShowSearch: false,
menuQuery: '',
tagsViewList: [],
});
// 搜索弹窗打开
const openSearch = () => {
state.menuQuery = '';
state.isShowSearch = true;
initTageView();
nextTick(() => {
layoutMenuAutocompleteRef.value.focus();
});
};
// 搜索弹窗关闭
const closeSearch = () => {
state.isShowSearch = false;
};
// 菜单搜索数据过滤
const menuSearch = (queryString, cb) => {
let results = queryString ? state.tagsViewList.filter(createFilter(queryString)) : state.tagsViewList;
cb(results);
};
// 菜单搜索过滤
const createFilter = (queryString) => {
return (restaurant) => {
return (
restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1
);
};
};
// 初始化菜单数据
const initTageView = () => {
if (state.tagsViewList.length > 0) return false;
store.state.tagsViewRoutes.tagsViewRoutes.map((v) => {
if (!v.meta.isHide) state.tagsViewList.push({ ...v });
});
};
// 当前菜单选中时
const onHandleSelect = (item) => {
let { path, redirect } = item;
if (item.meta.isLink && !item.meta.isIframe) window.open(item.meta.isLink);
else if (redirect) router.push(redirect);
else router.push(path);
closeSearch();
};
// input 失去焦点时
const onSearchBlur = () => {
closeSearch();
};
// 暴露变量
defineExpose({
openSearch,
});
</script>

View File

@ -194,10 +194,14 @@
<el-switch v-model="getThemeConfig.isCacheTagsView" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: isMobile ? 0.5 : 1 }">
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: state.isMobile ? 0.5 : 1 }">
<div class="layout-breadcrumb-seting-bar-flex-label">开启 TagsView 拖拽</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isSortableTagsView" :disabled="isMobile ? true : false" @change="onSortableTagsViewChange"></el-switch>
<el-switch
v-model="getThemeConfig.isSortableTagsView"
:disabled="state.isMobile ? true : false"
@change="onSortableTagsViewChange"
></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
@ -373,293 +377,265 @@
</div>
</template>
<script lang="ts">
import { nextTick, onUnmounted, onMounted, getCurrentInstance, defineComponent, computed, reactive, toRefs } from 'vue';
import { useStore } from '/@/store/index';
<script setup name="layoutBreadcrumbSeting">
import { getLightColor } from '/@/utils/theme';
import { verifyAndSpace } from '/@/utils/toolsValidate';
import { Local } from '/@/utils/storage';
import Watermark from '/@/utils/wartermark';
import commonFunction from '/@/utils/commonFunction';
import other from '/@/utils/other';
export default defineComponent({
name: 'layoutBreadcrumbSeting',
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const { copyText } = commonFunction();
const state = reactive({
isMobile: false,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 1、全局主题
const onColorPickerChange = (color: string) => {
setPropertyFun(`--color-${color}`, getThemeConfig.value[color]);
setDispatchThemeConfig();
};
// 1、全局主题设置函数
const setPropertyFun = (color: string, targetVal: any) => {
document.documentElement.style.setProperty(color, targetVal);
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`${color}-light-${i}`, getLightColor(targetVal, i / 10));
}
};
// 2、菜单 / 顶栏
const onBgColorPickerChange = (bg: string) => {
document.documentElement.style.setProperty(`--bg-${bg}`, getThemeConfig.value[bg]);
onTopBarGradualChange();
onMenuBarGradualChange();
onColumnsMenuBarGradualChange();
setDispatchThemeConfig();
};
// 2、菜单 / 顶栏 --> 顶栏背景渐变
const onTopBarGradualChange = () => {
setGraduaFun('.layout-navbars-breadcrumb-index', getThemeConfig.value.isTopBarColorGradual, getThemeConfig.value.topBar);
};
// 2、菜单 / 顶栏 --> 菜单背景渐变
const onMenuBarGradualChange = () => {
setGraduaFun('.layout-container .el-aside', getThemeConfig.value.isMenuBarColorGradual, getThemeConfig.value.menuBar);
};
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
const onColumnsMenuBarGradualChange = () => {
setGraduaFun('.layout-container .layout-columns-aside', getThemeConfig.value.isColumnsMenuBarColorGradual, getThemeConfig.value.columnsMenuBar);
};
// 2、菜单 / 顶栏 --> 背景渐变函数
const setGraduaFun = (el: string, bool: boolean, color: string) => {
nextTick(() => {
let els = document.querySelector(el);
if (!els) return false;
if (bool) els.setAttribute('style', `background-image:linear-gradient(to bottom left , ${color}, ${getLightColor(color, 0.6)})`);
else els.setAttribute('style', `background-image:${color}`);
setLocalThemeConfig();
});
};
// 2、菜单 / 顶栏 --> 菜单字体背景高亮
const onMenuBarHighlightChange = () => {
nextTick(() => {
setTimeout(() => {
let elsItems = document.querySelectorAll('.el-menu-item');
let elActive = document.querySelector('.el-menu-item.is-active');
if (!elActive) return false;
if (getThemeConfig.value.isMenuBarColorHighlight) {
elsItems.forEach((el: any) => el.setAttribute('id', ``));
elActive.setAttribute('id', `add-is-active`);
Local.set('menuBarHighlightId', elActive.getAttribute('id'));
} else {
elActive.setAttribute('id', ``);
}
setLocalThemeConfig();
}, 0);
});
};
// 3、界面设置 --> 菜单水平折叠
const onThemeConfigChange = () => {
onMenuBarHighlightChange();
setDispatchThemeConfig();
};
// 3、界面设置 --> 固定 Header
const onIsFixedHeaderChange = () => {
getThemeConfig.value.isFixedHeaderChange = getThemeConfig.value.isFixedHeader ? false : true;
setLocalThemeConfig();
};
// 3、界面设置 --> 经典布局分割菜单
const onClassicSplitMenuChange = () => {
getThemeConfig.value.isBreadcrumb = false;
setLocalThemeConfig();
proxy.mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
};
// 4、界面显示 --> 侧边栏 Logo
const onIsShowLogoChange = () => {
getThemeConfig.value.isShowLogoChange = getThemeConfig.value.isShowLogo ? false : true;
setLocalThemeConfig();
};
// 4、界面显示 --> 面包屑 Breadcrumb
const onIsBreadcrumbChange = () => {
if (getThemeConfig.value.layout === 'classic') {
getThemeConfig.value.isClassicSplitMenu = false;
}
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 拖拽
const onSortableTagsViewChange = () => {
proxy.mittBus.emit('openOrCloseSortable');
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 共用
const onShareTagsViewChange = () => {
proxy.mittBus.emit('openShareTagsView');
setLocalThemeConfig();
};
// 4、界面显示 --> 灰色模式/色弱模式
const onAddFilterChange = (attr: string) => {
if (attr === 'grayscale') {
if (getThemeConfig.value.isGrayscale) getThemeConfig.value.isInvert = false;
const { proxy } = getCurrentInstance();
const store = useStore();
const { copyText } = commonFunction();
const state = reactive({
isMobile: false,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 1、全局主题
const onColorPickerChange = (color) => {
setPropertyFun(`--color-${color}`, getThemeConfig.value[color]);
setDispatchThemeConfig();
};
// 1、全局主题设置函数
const setPropertyFun = (color, targetVal) => {
document.documentElement.style.setProperty(color, targetVal);
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`${color}-light-${i}`, getLightColor(targetVal, i / 10));
}
};
// 2、菜单 / 顶栏
const onBgColorPickerChange = (bg) => {
document.documentElement.style.setProperty(`--bg-${bg}`, getThemeConfig.value[bg]);
onTopBarGradualChange();
onMenuBarGradualChange();
onColumnsMenuBarGradualChange();
setDispatchThemeConfig();
};
// 2、菜单 / 顶栏 --> 顶栏背景渐变
const onTopBarGradualChange = () => {
setGraduaFun('.layout-navbars-breadcrumb-index', getThemeConfig.value.isTopBarColorGradual, getThemeConfig.value.topBar);
};
// 2、菜单 / 顶栏 --> 菜单背景渐变
const onMenuBarGradualChange = () => {
setGraduaFun('.layout-container .el-aside', getThemeConfig.value.isMenuBarColorGradual, getThemeConfig.value.menuBar);
};
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
const onColumnsMenuBarGradualChange = () => {
setGraduaFun('.layout-container .layout-columns-aside', getThemeConfig.value.isColumnsMenuBarColorGradual, getThemeConfig.value.columnsMenuBar);
};
// 2、菜单 / 顶栏 --> 背景渐变函数
const setGraduaFun = (el, bool, color) => {
nextTick(() => {
let els = document.querySelector(el);
if (!els) return false;
if (bool) els.setAttribute('style', `background-image:linear-gradient(to bottom left , ${color}, ${getLightColor(color, 0.6)})`);
else els.setAttribute('style', `background-image:${color}`);
setLocalThemeConfig();
});
};
// 2、菜单 / 顶栏 --> 菜单字体背景高亮
const onMenuBarHighlightChange = () => {
nextTick(() => {
setTimeout(() => {
let elsItems = document.querySelectorAll('.el-menu-item');
let elActive = document.querySelector('.el-menu-item.is-active');
if (!elActive) return false;
if (getThemeConfig.value.isMenuBarColorHighlight) {
elsItems.forEach((el) => el.setAttribute('id', ``));
elActive.setAttribute('id', `add-is-active`);
Local.set('menuBarHighlightId', elActive.getAttribute('id'));
} else {
if (getThemeConfig.value.isInvert) getThemeConfig.value.isGrayscale = false;
elActive.setAttribute('id', ``);
}
const cssAttr =
attr === 'grayscale' ? `grayscale(${getThemeConfig.value.isGrayscale ? 1 : 0})` : `invert(${getThemeConfig.value.isInvert ? '80%' : '0%'})`;
const appEle: any = document.body;
appEle.setAttribute('style', `filter: ${cssAttr}`);
setLocalThemeConfig();
};
// 4、界面显示 --> 深色模式
const onAddDarkChange = () => {
const body = document.documentElement as HTMLElement;
if (getThemeConfig.value.isIsDark) body.setAttribute('data-theme', 'dark');
else body.setAttribute('data-theme', '');
};
// 4、界面显示 --> 开启水印
const onWartermarkChange = () => {
getThemeConfig.value.isWartermark ? Watermark.set(getThemeConfig.value.wartermarkText) : Watermark.del();
setLocalThemeConfig();
};
// 4、界面显示 --> 水印文案
const onWartermarkTextInput = (val: string) => {
getThemeConfig.value.wartermarkText = verifyAndSpace(val);
if (getThemeConfig.value.wartermarkText === '') return false;
if (getThemeConfig.value.isWartermark) Watermark.set(getThemeConfig.value.wartermarkText);
setLocalThemeConfig();
};
// 5、布局切换
const onSetLayout = (layout: string) => {
Local.set('oldLayout', layout);
if (getThemeConfig.value.layout === layout) return false;
getThemeConfig.value.layout = layout;
}, 0);
});
};
// 3、界面设置 --> 菜单水平折叠
const onThemeConfigChange = () => {
onMenuBarHighlightChange();
setDispatchThemeConfig();
};
// 3、界面设置 --> 固定 Header
const onIsFixedHeaderChange = () => {
getThemeConfig.value.isFixedHeaderChange = getThemeConfig.value.isFixedHeader ? false : true;
setLocalThemeConfig();
};
// 3、界面设置 --> 经典布局分割菜单
const onClassicSplitMenuChange = () => {
getThemeConfig.value.isBreadcrumb = false;
setLocalThemeConfig();
proxy.mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
};
// 4、界面显示 --> 侧边栏 Logo
const onIsShowLogoChange = () => {
getThemeConfig.value.isShowLogoChange = getThemeConfig.value.isShowLogo ? false : true;
setLocalThemeConfig();
};
// 4、界面显示 --> 面包屑 Breadcrumb
const onIsBreadcrumbChange = () => {
if (getThemeConfig.value.layout === 'classic') {
getThemeConfig.value.isClassicSplitMenu = false;
}
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 拖拽
const onSortableTagsViewChange = () => {
proxy.mittBus.emit('openOrCloseSortable');
setLocalThemeConfig();
};
// 4、界面显示 --> 开启 TagsView 共用
const onShareTagsViewChange = () => {
proxy.mittBus.emit('openShareTagsView');
setLocalThemeConfig();
};
// 4、界面显示 --> 灰色模式/色弱模式
const onAddFilterChange = (attr) => {
if (attr === 'grayscale') {
if (getThemeConfig.value.isGrayscale) getThemeConfig.value.isInvert = false;
} else {
if (getThemeConfig.value.isInvert) getThemeConfig.value.isGrayscale = false;
}
const cssAttr =
attr === 'grayscale' ? `grayscale(${getThemeConfig.value.isGrayscale ? 1 : 0})` : `invert(${getThemeConfig.value.isInvert ? '80%' : '0%'})`;
const appEle = document.body;
appEle.setAttribute('style', `filter: ${cssAttr}`);
setLocalThemeConfig();
};
// 4、界面显示 --> 深色模式
const onAddDarkChange = () => {
const body = document.documentElement;
if (getThemeConfig.value.isIsDark) body.setAttribute('data-theme', 'dark');
else body.setAttribute('data-theme', '');
};
// 4、界面显示 --> 开启水印
const onWartermarkChange = () => {
getThemeConfig.value.isWartermark ? Watermark.set(getThemeConfig.value.wartermarkText) : Watermark.del();
setLocalThemeConfig();
};
// 4、界面显示 --> 水印文案
const onWartermarkTextInput = (val) => {
getThemeConfig.value.wartermarkText = verifyAndSpace(val);
if (getThemeConfig.value.wartermarkText === '') return false;
if (getThemeConfig.value.isWartermark) Watermark.set(getThemeConfig.value.wartermarkText);
setLocalThemeConfig();
};
// 5、布局切换
const onSetLayout = (layout) => {
Local.set('oldLayout', layout);
if (getThemeConfig.value.layout === layout) return false;
getThemeConfig.value.layout = layout;
getThemeConfig.value.isDrawer = false;
initLayoutChangeFun();
onMenuBarHighlightChange();
};
// 设置布局切换函数
const initLayoutChangeFun = () => {
onBgColorPickerChange('menuBar');
onBgColorPickerChange('menuBarColor');
onBgColorPickerChange('topBar');
onBgColorPickerChange('topBarColor');
};
// 关闭弹窗时,初始化变量。变量用于处理 proxy.$refs.layoutScrollbarRef.update()
const onDrawerClose = () => {
getThemeConfig.value.isFixedHeaderChange = false;
getThemeConfig.value.isShowLogoChange = false;
getThemeConfig.value.isDrawer = false;
setLocalThemeConfig();
};
// 布局配置弹窗打开
const openDrawer = () => {
getThemeConfig.value.isDrawer = true;
};
// 触发 store 布局配置更新
const setDispatchThemeConfig = () => {
setLocalThemeConfig();
setLocalThemeConfigStyle();
};
// 存储布局配置
const setLocalThemeConfig = () => {
Local.remove('themeConfig');
Local.set('themeConfig', getThemeConfig.value);
};
// 存储布局配置全局主题样式html根标签
const setLocalThemeConfigStyle = () => {
Local.set('themeConfigStyle', document.documentElement.style.cssText);
};
// 一键复制配置
const onCopyConfigClick = () => {
let copyThemeConfig = Local.get('themeConfig');
copyThemeConfig.isDrawer = false;
copyText(JSON.stringify(copyThemeConfig)).then(() => {
getThemeConfig.value.isDrawer = false;
});
};
// 一键恢复默认
const onResetConfigClick = () => {
Local.clear();
window.location.reload();
};
// 修复防止退出登录再进入界面时,需要刷新样式才生效的问题,初始化布局样式等(登录的时候触发,目前方案)
const initSetStyle = () => {
setTimeout(() => {
// 2、菜单 / 顶栏 --> 顶栏背景渐变
onTopBarGradualChange();
// 2、菜单 / 顶栏 --> 菜单背景渐变
onMenuBarGradualChange();
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
onColumnsMenuBarGradualChange();
// 2、菜单 / 顶栏 --> 菜单字体背景高亮
onMenuBarHighlightChange();
}, 1300);
};
onMounted(() => {
nextTick(() => {
// 判断当前布局是否不相同不相同则初始化当前布局的样式防止监听窗口大小改变时布局配置logo、菜单背景等部分布局失效问题
if (!Local.get('frequency')) initLayoutChangeFun();
Local.set('frequency', 1);
// 修复防止退出登录再进入界面时,需要刷新样式才生效的问题,初始化布局样式等(登录的时候触发,目前方案)
proxy.mittBus.on('onSignInClick', () => {
initSetStyle();
});
// 监听菜单点击,菜单字体背景高亮
proxy.mittBus.on('onMenuClick', () => {
onMenuBarHighlightChange();
});
// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
proxy.mittBus.on('layoutMobileResize', (res) => {
getThemeConfig.value.layout = res.layout;
getThemeConfig.value.isDrawer = false;
initLayoutChangeFun();
onMenuBarHighlightChange();
};
// 设置布局切换函数
const initLayoutChangeFun = () => {
onBgColorPickerChange('menuBar');
onBgColorPickerChange('menuBarColor');
onBgColorPickerChange('topBar');
onBgColorPickerChange('topBarColor');
};
// 关闭弹窗时,初始化变量。变量用于处理 proxy.$refs.layoutScrollbarRef.update()
const onDrawerClose = () => {
getThemeConfig.value.isFixedHeaderChange = false;
getThemeConfig.value.isShowLogoChange = false;
getThemeConfig.value.isDrawer = false;
setLocalThemeConfig();
};
// 布局配置弹窗打开
const openDrawer = () => {
getThemeConfig.value.isDrawer = true;
};
// 触发 store 布局配置更新
const setDispatchThemeConfig = () => {
setLocalThemeConfig();
setLocalThemeConfigStyle();
};
// 存储布局配置
const setLocalThemeConfig = () => {
Local.remove('themeConfig');
Local.set('themeConfig', getThemeConfig.value);
};
// 存储布局配置全局主题样式html根标签
const setLocalThemeConfigStyle = () => {
Local.set('themeConfigStyle', document.documentElement.style.cssText);
};
// 一键复制配置
const onCopyConfigClick = () => {
let copyThemeConfig = Local.get('themeConfig');
copyThemeConfig.isDrawer = false;
copyText(JSON.stringify(copyThemeConfig)).then(() => {
getThemeConfig.value.isDrawer = false;
});
};
// 一键恢复默认
const onResetConfigClick = () => {
Local.clear();
window.location.reload();
};
// 修复防止退出登录再进入界面时,需要刷新样式才生效的问题,初始化布局样式等(登录的时候触发,目前方案)
const initSetStyle = () => {
setTimeout(() => {
// 2、菜单 / 顶栏 --> 顶栏背景渐变
onTopBarGradualChange();
// 2、菜单 / 顶栏 --> 菜单背景渐变
onMenuBarGradualChange();
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
onColumnsMenuBarGradualChange();
// 2、菜单 / 顶栏 --> 菜单字体背景高亮
onMenuBarHighlightChange();
}, 1300);
};
onMounted(() => {
nextTick(() => {
// 判断当前布局是否不相同不相同则初始化当前布局的样式防止监听窗口大小改变时布局配置logo、菜单背景等部分布局失效问题
if (!Local.get('frequency')) initLayoutChangeFun();
Local.set('frequency', 1);
// 修复防止退出登录再进入界面时,需要刷新样式才生效的问题,初始化布局样式等(登录的时候触发,目前方案)
proxy.mittBus.on('onSignInClick', () => {
initSetStyle();
});
// 监听菜单点击,菜单字体背景高亮
proxy.mittBus.on('onMenuClick', () => {
onMenuBarHighlightChange();
});
// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
proxy.mittBus.on('layoutMobileResize', (res: any) => {
getThemeConfig.value.layout = res.layout;
getThemeConfig.value.isDrawer = false;
initLayoutChangeFun();
onMenuBarHighlightChange();
state.isMobile = other.isMobile();
});
setTimeout(() => {
// 修复防止退出登录再进入界面时,需要刷新样式才生效的问题,初始化布局样式等(登录的时候触发,目前方案)
initSetStyle();
// 灰色模式
if (getThemeConfig.value.isGrayscale) onAddFilterChange('grayscale');
// 色弱模式
if (getThemeConfig.value.isInvert) onAddFilterChange('invert');
// 深色模式
if (getThemeConfig.value.isIsDark) onAddDarkChange();
// 开启水印
onWartermarkChange();
}, 100);
});
state.isMobile = other.isMobile();
});
onUnmounted(() => {
// 取消监听菜单点击,菜单字体背景高亮
proxy.mittBus.off('onMenuClick');
proxy.mittBus.off('onSignInClick');
proxy.mittBus.off('layoutMobileResize');
});
return {
openDrawer,
onColorPickerChange,
onBgColorPickerChange,
onTopBarGradualChange,
onMenuBarGradualChange,
onColumnsMenuBarGradualChange,
onMenuBarHighlightChange,
onThemeConfigChange,
onIsFixedHeaderChange,
onIsShowLogoChange,
getThemeConfig,
onDrawerClose,
onAddFilterChange,
onAddDarkChange,
onWartermarkChange,
onWartermarkTextInput,
onSetLayout,
setLocalThemeConfig,
onClassicSplitMenuChange,
onIsBreadcrumbChange,
onSortableTagsViewChange,
onShareTagsViewChange,
onCopyConfigClick,
onResetConfigClick,
...toRefs(state),
};
},
setTimeout(() => {
// 修复防止退出登录再进入界面时,需要刷新样式才生效的问题,初始化布局样式等(登录的时候触发,目前方案)
initSetStyle();
// 灰色模式
if (getThemeConfig.value.isGrayscale) onAddFilterChange('grayscale');
// 色弱模式
if (getThemeConfig.value.isInvert) onAddFilterChange('invert');
// 深色模式
if (getThemeConfig.value.isIsDark) onAddDarkChange();
// 开启水印
onWartermarkChange();
}, 100);
});
});
onUnmounted(() => {
// 取消监听菜单点击,菜单字体背景高亮
proxy.mittBus.off('onMenuClick');
proxy.mittBus.off('onSignInClick');
proxy.mittBus.off('layoutMobileResize');
});
// 暴露变量
defineExpose({
openDrawer,
});
</script>

View File

@ -6,10 +6,10 @@
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="" :disabled="disabledSize === ''">默认</el-dropdown-item>
<el-dropdown-item command="medium" :disabled="disabledSize === 'medium'">中等</el-dropdown-item>
<el-dropdown-item command="small" :disabled="disabledSize === 'small'">小型</el-dropdown-item>
<el-dropdown-item command="mini" :disabled="disabledSize === 'mini'">超小</el-dropdown-item>
<el-dropdown-item command="" :disabled="state.disabledSize === ''">默认</el-dropdown-item>
<el-dropdown-item command="medium" :disabled="state.disabledSize === 'medium'">中等</el-dropdown-item>
<el-dropdown-item command="small" :disabled="state.disabledSize === 'small'">小型</el-dropdown-item>
<el-dropdown-item command="mini" :disabled="state.disabledSize === 'mini'">超小</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@ -22,21 +22,31 @@
<i class="icon-skin iconfont" title="布局配置"></i>
</div>
<div class="layout-navbars-breadcrumb-user-icon">
<el-popover placement="bottom" trigger="click" v-model:visible="isShowUserNewsPopover" :width="300" popper-class="el-popover-pupop-user-news">
<el-popover
placement="bottom"
trigger="click"
v-model:visible="state.isShowUserNewsPopover"
:width="300"
popper-class="el-popover-pupop-user-news"
>
<template #reference>
<el-badge :is-dot="true" @click="isShowUserNewsPopover = !isShowUserNewsPopover">
<el-badge :is-dot="true" @click="state.isShowUserNewsPopover = !state.isShowUserNewsPopover">
<el-icon title="消息">
<elementBell />
</el-icon>
</el-badge>
</template>
<transition name="el-zoom-in-top">
<UserNews v-show="isShowUserNewsPopover" />
<UserNews v-show="state.isShowUserNewsPopover" />
</transition>
</el-popover>
</div>
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
<i class="iconfont" :title="isScreenfull ? '开全屏' : '关全屏'" :class="!isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"></i>
<i
class="iconfont"
:title="state.isScreenfull ? '开全屏' : '关全屏'"
:class="!state.isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
></i>
</div>
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
<span class="layout-navbars-breadcrumb-user-link">
@ -60,151 +70,132 @@
</div>
</template>
<script lang="ts">
import { ref, getCurrentInstance, computed, reactive, toRefs, onMounted } from 'vue';
import { useRouter } from 'vue-router';
<script setup name="layoutBreadcrumbUser">
import { ElMessageBox, ElMessage } from 'element-plus';
import screenfull from 'screenfull';
import { resetRoute } from '/@/router/index';
import { useStore } from '/@/store/index';
import { Session, Local } from '/@/utils/storage';
import UserNews from '/@/layout/navBars/breadcrumb/userNews.vue';
import Search from '/@/layout/navBars/breadcrumb/search.vue';
export default {
name: 'layoutBreadcrumbUser',
components: { UserNews, Search },
setup() {
const { proxy } = getCurrentInstance() as any;
const router = useRouter();
const store = useStore();
const searchRef = ref();
const state = reactive({
isScreenfull: false,
isShowUserNewsPopover: false,
disabledSize: '',
});
// 获取用户信息 vuex
const getUserInfos = computed(() => {
return store.state.userInfos.userInfos;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置分割样式
const layoutUserFlexNum = computed(() => {
let { layout, isClassicSplitMenu } = getThemeConfig.value;
let num = '';
if (layout === 'defaults' || (layout === 'classic' && !isClassicSplitMenu) || layout === 'columns') num = 1;
else num = null;
return num;
});
// 全屏点击时
const onScreenfullClick = () => {
if (!screenfull.isEnabled) {
ElMessage.warning('暂不不支持全屏');
return false;
}
screenfull.toggle();
screenfull.on('change', () => {
if (screenfull.isFullscreen) state.isScreenfull = true;
else state.isScreenfull = false;
});
};
// 布局配置 icon 点击时
const onLayoutSetingClick = () => {
proxy.mittBus.emit('openSetingsDrawer');
};
// 下拉菜单点击时
const onHandleCommandClick = (path: string) => {
if (path === 'logOut') {
ElMessageBox({
closeOnClickModal: false,
closeOnPressEscape: false,
title: '提示',
message: '此操作将退出登录, 是否继续?',
showCancelButton: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
instance.confirmButtonText = '退出中';
setTimeout(() => {
done();
setTimeout(() => {
instance.confirmButtonLoading = false;
}, 300);
}, 700);
} else {
done();
}
},
})
.then(() => {
Session.clear(); // 清除缓存/token等
resetRoute(); // 删除/重置路由
router.push('/login');
setTimeout(() => {
ElMessage.success('安全退出成功!');
}, 300);
})
.catch(() => {});
} else if (path === 'wareHouse') {
window.open('https://gitee.com/lyt-top/vue-next-admin');
} else {
router.push(path);
}
};
// 菜单搜索点击
const onSearchClick = () => {
searchRef.value.openSearch();
};
// 组件大小改变
const onComponentSizeChange = (size: string) => {
Local.remove('themeConfig');
getThemeConfig.value.globalComponentSize = size;
Local.set('themeConfig', getThemeConfig.value);
proxy.$ELEMENT.size = size;
initComponentSize();
window.location.reload();
};
// 初始化全局组件大小
const initComponentSize = () => {
switch (Local.get('themeConfig').globalComponentSize) {
case '':
state.disabledSize = '';
break;
case 'medium':
state.disabledSize = 'medium';
break;
case 'small':
state.disabledSize = 'small';
break;
case 'mini':
state.disabledSize = 'mini';
break;
}
};
// 页面加载时
onMounted(() => {
if (Local.get('themeConfig')) {
initComponentSize();
}
});
return {
getUserInfos,
onLayoutSetingClick,
onHandleCommandClick,
onScreenfullClick,
onSearchClick,
onComponentSizeChange,
searchRef,
layoutUserFlexNum,
...toRefs(state),
};
},
const { proxy } = getCurrentInstance();
const router = useRouter();
const store = useStore();
const searchRef = ref();
const state = reactive({
isScreenfull: false,
isShowUserNewsPopover: false,
disabledSize: '',
});
// 获取用户信息 vuex
const getUserInfos = computed(() => {
return store.state.userInfos.userInfos;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置分割样式
const layoutUserFlexNum = computed(() => {
let { layout, isClassicSplitMenu } = getThemeConfig.value;
let num = '';
if (layout === 'defaults' || (layout === 'classic' && !isClassicSplitMenu) || layout === 'columns') num = 1;
else num = null;
return num;
});
// 全屏点击时
const onScreenfullClick = () => {
if (!screenfull.isEnabled) {
ElMessage.warning('暂不不支持全屏');
return false;
}
screenfull.toggle();
screenfull.on('change', () => {
if (screenfull.isFullscreen) state.isScreenfull = true;
else state.isScreenfull = false;
});
};
// 布局配置 icon 点击时
const onLayoutSetingClick = () => {
proxy.mittBus.emit('openSetingsDrawer');
};
// 下拉菜单点击时
const onHandleCommandClick = (path) => {
if (path === 'logOut') {
ElMessageBox({
closeOnClickModal: false,
closeOnPressEscape: false,
title: '提示',
message: '此操作将退出登录, 是否继续?',
showCancelButton: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
instance.confirmButtonText = '退出中';
setTimeout(() => {
done();
setTimeout(() => {
instance.confirmButtonLoading = false;
}, 300);
}, 700);
} else {
done();
}
},
})
.then(() => {
Session.clear(); // 清除缓存/token等
resetRoute(); // 删除/重置路由
router.push('/login');
setTimeout(() => {
ElMessage.success('安全退出成功!');
}, 300);
})
.catch(() => {});
} else if (path === 'wareHouse') {
window.open('https://gitee.com/lyt-top/vue-next-admin');
} else {
router.push(path);
}
};
// 菜单搜索点击
const onSearchClick = () => {
searchRef.value.openSearch();
};
// 组件大小改变
const onComponentSizeChange = (size) => {
Local.remove('themeConfig');
getThemeConfig.value.globalComponentSize = size;
Local.set('themeConfig', getThemeConfig.value);
proxy.$ELEMENT.size = size;
initComponentSize();
window.location.reload();
};
// 初始化全局组件大小
const initComponentSize = () => {
switch (Local.get('themeConfig').globalComponentSize) {
case '':
state.disabledSize = '';
break;
case 'medium':
state.disabledSize = 'medium';
break;
case 'small':
state.disabledSize = 'small';
break;
case 'mini':
state.disabledSize = 'mini';
break;
}
};
// 页面加载时
onMounted(() => {
if (Local.get('themeConfig')) {
initComponentSize();
}
});
</script>
<style scoped lang="scss">

View File

@ -2,11 +2,11 @@
<div class="layout-navbars-breadcrumb-user-news">
<div class="head-box">
<div class="head-box-title">通知</div>
<div class="head-box-btn" v-if="newsList.length > 0" @click="onAllReadClick">全部已读</div>
<div class="head-box-btn" v-if="state.newsList.length > 0" @click="onAllReadClick">全部已读</div>
</div>
<div class="content-box">
<template v-if="newsList.length > 0">
<div class="content-box-item" v-for="(v, k) in newsList" :key="k">
<template v-if="state.newsList.length > 0">
<div class="content-box-item" v-for="(v, k) in state.newsList" :key="k">
<div>{{ v.label }}</div>
<div class="content-box-msg">
{{ v.value }}
@ -16,43 +16,32 @@
</template>
<el-empty description="暂无通知" v-else></el-empty>
</div>
<div class="foot-box" @click="onGoToGiteeClick" v-if="newsList.length > 0">前往通知中心</div>
<div class="foot-box" @click="onGoToGiteeClick" v-if="state.newsList.length > 0">前往通知中心</div>
</div>
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue';
export default {
name: 'layoutBreadcrumbUserNews',
setup() {
const state = reactive({
newsList: [
{
label: '关于版本发布的通知',
value: 'vue-next-admin基于 vue3 + CompositionAPI + typescript + vite + element plus正式发布时间2021年02月28日',
time: '2020-12-08',
},
{
label: '关于学习交流的通知',
value: 'QQ群号码 665452019欢迎小伙伴入群学习交流探讨',
time: '2020-12-08',
},
],
});
// 全部已读点击
const onAllReadClick = () => {
state.newsList = [];
};
// 前往通知中心点击
const onGoToGiteeClick = () => {
window.open('https://gitee.com/lyt-top/vue-next-admin');
};
return {
onAllReadClick,
onGoToGiteeClick,
...toRefs(state),
};
},
<script setup name="layoutBreadcrumbUserNews">
const state = reactive({
newsList: [
{
label: '关于版本发布的通知',
value: 'vue-next-admin基于 vue3 + CompositionAPI + typescript + vite + element plus正式发布时间2021年02月28日',
time: '2020-12-08',
},
{
label: '关于学习交流的通知',
value: 'QQ群号码 665452019欢迎小伙伴入群学习交流探讨',
time: '2020-12-08',
},
],
});
// 全部已读点击
const onAllReadClick = () => {
state.newsList = [];
};
// 前往通知中心点击
const onGoToGiteeClick = () => {
window.open('https://gitee.com/lyt-top/vue-next-admin');
};
</script>

View File

@ -5,26 +5,16 @@
</div>
</template>
<script lang="ts">
import { computed } from 'vue';
import { useStore } from '/@/store/index';
<script setup name="layoutNavBars">
import BreadcrumbIndex from '/@/layout/navBars/breadcrumb/index.vue';
import TagsView from '/@/layout/navBars/tagsView/tagsView.vue';
export default {
name: 'layoutNavBars',
components: { BreadcrumbIndex, TagsView },
setup() {
const store = useStore();
// 是否显示 tagsView
const setShowTagsView = computed(() => {
let { layout, isTagsview } = store.state.themeConfig.themeConfig;
return layout !== 'classic' && isTagsview;
});
return {
setShowTagsView,
};
},
};
const store = useStore();
// 是否显示 tagsView
const setShowTagsView = computed(() => {
let { layout, isTagsview } = store.state.themeConfig.themeConfig;
return layout !== 'classic' && isTagsview;
});
</script>
<style scoped lang="scss">

View File

@ -7,10 +7,10 @@
data-popper-placement="bottom"
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
:key="Math.random()"
v-show="isShow"
v-show="state.isShow"
>
<ul class="el-dropdown-menu">
<template v-for="(v, k) in dropdownList">
<template v-for="(v, k) in state.dropdownList">
<li
class="el-dropdown-menu__item"
aria-disabled="false"
@ -24,90 +24,78 @@
</li>
</template>
</ul>
<div class="el-popper__arrow" :style="{ left: `${arrowLeft}px` }"></div>
<div class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
</div>
</transition>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, toRefs, onMounted, onUnmounted, watch } from 'vue';
export default defineComponent({
name: 'layoutTagsViewContextmenu',
props: {
dropdown: {
type: Object,
},
},
setup(props, { emit }) {
const state = reactive({
isShow: false,
dropdownList: [
{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'elementRefreshRight' },
{ contextMenuClickId: 1, txt: '关闭', affix: false, icon: 'elementClose' },
{ contextMenuClickId: 2, txt: '关闭其它', affix: false, icon: 'elementCircleClose' },
{ contextMenuClickId: 3, txt: '全部关闭', affix: false, icon: 'elementFolderDelete' },
{ contextMenuClickId: 4, txt: '当前页全屏', affix: false, icon: 'iconfont icon-fullscreen' },
],
item: {},
arrowLeft: 10,
});
// 父级传过来的坐标 x,y 值
const dropdowns = computed(() => {
// 117 为 `Dropdown 下拉菜单` 的宽度
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
return {
x: document.documentElement.clientWidth - 117 - 5,
y: props.dropdown.y,
};
} else {
return props.dropdown;
}
});
// 当前项菜单点击
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
emit('currentContextmenuClick', Object.assign({}, { contextMenuClickId }, state.item));
};
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
const openContextmenu = (item: any) => {
state.item = item;
item.meta.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
closeContextmenu();
setTimeout(() => {
state.isShow = true;
}, 10);
};
// 关闭右键菜单
const closeContextmenu = () => {
state.isShow = false;
};
// 监听页面监听进行右键菜单的关闭
onMounted(() => {
document.body.addEventListener('click', closeContextmenu);
});
// 页面卸载时,移除右键菜单监听事件
onUnmounted(() => {
document.body.removeEventListener('click', closeContextmenu);
});
// 监听下拉菜单位置
watch(
() => props.dropdown,
({ x }) => {
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
else state.arrowLeft = 10;
},
{
deep: true,
}
);
return {
dropdowns,
openContextmenu,
closeContextmenu,
onCurrentContextmenuClick,
...toRefs(state),
};
<script setup name="layoutTagsViewContextmenu">
const props = defineProps({
dropdown: {
type: Object,
},
});
const emit = defineEmits(['currentContextmenuClick']);
const state = reactive({
isShow: false,
dropdownList: [
{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'elementRefreshRight' },
{ contextMenuClickId: 1, txt: '关闭', affix: false, icon: 'elementClose' },
{ contextMenuClickId: 2, txt: '关闭其它', affix: false, icon: 'elementCircleClose' },
{ contextMenuClickId: 3, txt: '全部关闭', affix: false, icon: 'elementFolderDelete' },
{ contextMenuClickId: 4, txt: '当前页全屏', affix: false, icon: 'iconfont icon-fullscreen' },
],
item: {},
arrowLeft: 10,
});
// 父级传过来的坐标 x,y 值
const dropdowns = computed(() => {
// 117 为 `Dropdown 下拉菜单` 的宽度
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
return {
x: document.documentElement.clientWidth - 117 - 5,
y: props.dropdown.y,
};
} else {
return props.dropdown;
}
});
// 当前项菜单点击
const onCurrentContextmenuClick = (contextMenuClickId) => {
emit('currentContextmenuClick', Object.assign({}, { contextMenuClickId }, state.item));
};
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
const openContextmenu = (item) => {
state.item = item;
item.meta.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
closeContextmenu();
setTimeout(() => {
state.isShow = true;
}, 10);
};
// 关闭右键菜单
const closeContextmenu = () => {
state.isShow = false;
};
// 监听页面监听进行右键菜单的关闭
onMounted(() => {
document.body.addEventListener('click', closeContextmenu);
});
// 页面卸载时,移除右键菜单监听事件
onUnmounted(() => {
document.body.removeEventListener('click', closeContextmenu);
});
// 监听下拉菜单位置
watch(
() => props.dropdown,
({ x }) => {
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
else state.arrowLeft = 10;
},
{
deep: true,
}
);
</script>
<style scoped lang="scss">

View File

@ -3,7 +3,7 @@
<el-scrollbar ref="scrollbarRef" @wheel.native.prevent="onHandleScroll">
<ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
<li
v-for="(v, k) in tagsViewList"
v-for="(v, k) in state.tagsViewList"
:key="k"
class="layout-navbars-tagsview-ul-li"
:data-url="v.url"
@ -37,471 +37,447 @@
</li>
</ul>
</el-scrollbar>
<Contextmenu :dropdown="dropdown" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
<Contextmenu :dropdown="state.dropdown" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, computed, ref, nextTick, onBeforeUpdate, onBeforeMount, onUnmounted, getCurrentInstance, watch } from 'vue';
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
<script setup name="layoutTagsView">
import { onBeforeRouteUpdate } from 'vue-router';
import Sortable from 'sortablejs';
import { ElMessage } from 'element-plus';
import { useStore } from '/@/store/index';
import { Session } from '/@/utils/storage';
import { isObjectValueEqual } from '/@/utils/arrayOperation';
import other from '/@/utils/other';
import Contextmenu from '/@/layout/navBars/tagsView/contextmenu.vue';
export default {
name: 'layoutTagsView',
components: { Contextmenu },
setup() {
const { proxy } = getCurrentInstance() as any;
const tagsRefs = ref([]);
const scrollbarRef = ref();
const contextmenuRef = ref();
const tagsUlRef = ref();
const store = useStore();
const route = useRoute();
const router = useRouter();
const state: any = reactive({
routeActive: '',
routePath: route.path,
dropdown: { x: '', y: '' },
tagsRefsIndex: 0,
tagsViewList: [],
sortable: '',
tagsViewRoutesList: [],
});
// 动态设置 tagsView 风格样式
const setTagsStyle = computed(() => {
return store.state.themeConfig.themeConfig.tagsStyle;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置 tagsView 高亮
const isActive = (v) => {
if (getThemeConfig.value.isShareTagsView) {
return v.path === state.routePath;
} else {
return v.url ? v.url === state.routeActive : v.path === state.routeActive;
}
};
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
const addBrowserSetSession = (tagsViewList: Array<object>) => {
Session.set('tagsViewList', tagsViewList);
};
// 获取 vuex 中的 tagsViewRoutes 列表
const getTagsViewRoutes = async () => {
state.routeActive = await setTagsViewHighlight(route);
state.routePath = (await route.meta.isDynamic) ? route.meta.isDynamicPath : route.path;
state.tagsViewList = [];
state.tagsViewRoutesList = store.state.tagsViewRoutes.tagsViewRoutes;
initTagsView();
};
// vuex 中获取路由信息如果是设置了固定的isAffix进行初始化显示
const initTagsView = async () => {
if (Session.get('tagsViewList') && getThemeConfig.value.isCacheTagsView) {
state.tagsViewList = await Session.get('tagsViewList');
} else {
await state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) {
v.url = setTagsViewHighlight(v);
state.tagsViewList.push({ ...v });
}
});
await addTagsView(route.path, route);
}
// 初始化当前元素(li)的下标
getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
};
// 处理可开启多标签详情单标签详情动态路由xxx/:id/:name"),普通路由处理)
const solveAddTagsView = async (path: string, to?: any) => {
let isDynamicPath = to.meta.isDynamic ? to.meta.isDynamicPath : path;
let current = state.tagsViewList.filter(
(v: any) =>
v.path === isDynamicPath &&
isObjectValueEqual(
to.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
to.meta.isDynamic ? (to?.params ? to?.params : null) : to?.query ? to?.query : null
)
);
if (current.length <= 0) {
// 防止Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead.
let findItem = state.tagsViewRoutesList.find((v: any) => v.path === isDynamicPath);
if (findItem.meta.isAffix) return false;
if (findItem.meta.isLink && !findItem.meta.isIframe) return false;
to.meta.isDynamic ? (findItem.params = to.params) : (findItem.query = to.query);
findItem.url = setTagsViewHighlight(findItem);
state.tagsViewList.push({ ...findItem });
addBrowserSetSession(state.tagsViewList);
}
};
// 处理单标签时,第二次的值未覆盖第一次的 tagsViewList 值Session Storage
const singleAddTagsView = (path: string, to?: any) => {
let isDynamicPath = to.meta.isDynamic ? to.meta.isDynamicPath : path;
state.tagsViewList.forEach((v) => {
if (
v.path === isDynamicPath &&
!isObjectValueEqual(
to.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
to.meta.isDynamic ? (to?.params ? to?.params : null) : to?.query ? to?.query : null
)
) {
to.meta.isDynamic ? (v.params = to.params) : (v.query = to.query);
v.url = setTagsViewHighlight(v);
addBrowserSetSession(state.tagsViewList);
}
});
};
// 1、添加 tagsView未设置隐藏isHide也添加到在 tagsView 中(可开启多标签详情,单标签详情)
const addTagsView = (path: string, to?: any) => {
// 防止拿取不到路由信息
nextTick(async () => {
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
let item = '';
if (to && to.meta.isDynamic) {
// 动态路由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;
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;
item = state.tagsViewRoutesList.find((v: any) => v.path === path);
}
if (item.meta.isLink && !item.meta.isIframe) return false;
if (to && to.meta.isDynamic) item.params = to?.params ? to?.params : route.params;
else item.query = to?.query ? to?.query : route.query;
item.url = setTagsViewHighlight(item);
await state.tagsViewList.push({ ...item });
await addBrowserSetSession(state.tagsViewList);
});
};
// 2、刷新当前 tagsView
const refreshCurrentTagsView = (fullPath: string) => {
proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
};
// 3、关闭当前 tagsView如果是设置了固定的isAffix不可以关闭
const closeCurrentTagsView = (path: string) => {
state.tagsViewList.map((v: any, k: number, arr: any) => {
if (!v.meta.isAffix) {
if (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path) {
state.tagsViewList.splice(k, 1);
setTimeout(() => {
if (state.tagsViewList.length === k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
// 最后一个且高亮时
if (arr[arr.length - 1].meta.isDynamic) {
// 动态路由xxx/:id/:name"
if (k !== arr.length) router.push({ name: arr[k].name, params: arr[k].params });
else router.push({ name: arr[arr.length - 1].name, params: arr[arr.length - 1].params });
} else {
// 普通路由
if (k !== arr.length) router.push({ path: arr[k].path, query: arr[k].query });
else router.push({ path: arr[arr.length - 1].path, query: arr[arr.length - 1].query });
}
} else {
// 非最后一个且高亮时,跳转到下一个
if (state.tagsViewList.length !== k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
if (arr[k].meta.isDynamic) {
// 动态路由xxx/:id/:name"
router.push({ name: arr[k].name, params: arr[k].params });
} else {
// 普通路由
router.push({ path: arr[k].path, query: arr[k].query });
}
}
}
}, 0);
}
}
});
addBrowserSetSession(state.tagsViewList);
};
// 4、关闭其它 tagsView如果是设置了固定的isAffix不进行关闭
const closeOtherTagsView = (path: string) => {
state.tagsViewList = [];
state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
});
addTagsView(path, route);
};
// 5、关闭全部 tagsView如果是设置了固定的isAffix不进行关闭
const closeAllTagsView = () => {
state.tagsViewList = [];
state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) {
state.tagsViewList.push({ ...v });
router.push({ path: state.tagsViewList[state.tagsViewList.length - 1].path });
}
});
addBrowserSetSession(state.tagsViewList);
};
// 6、开启当前页面全屏
const openCurrenFullscreen = async (path: string) => {
const item = state.tagsViewList.find((v: any) => (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path));
if (item.meta.isDynamic) await router.push({ name: item.name, params: item.params });
else await router.push({ name: item.name, query: item.query });
store.dispatch('tagsViewRoutes/setCurrenFullscreen', true);
};
// 当前项右键菜单点击,拿当前点击的路由路径对比 浏览器缓存中的 tagsView 路由数组,取当前点击项的详细路由信息
// 防止 tagsView 非当前页演示时,操作异常
const getCurrentRouteItem = (path: string, cParams: { [key: string]: 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;
}
});
};
// 当前项右键菜单点击
const onCurrentContextmenuClick = async (item) => {
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);
switch (item.contextMenuClickId) {
case 0:
// 刷新当前
if (meta.isDynamic) await router.push({ name, params });
else await router.push({ path, query });
refreshCurrentTagsView(route.fullPath);
break;
case 1:
// 关闭当前
closeCurrentTagsView(getThemeConfig.value.isShareTagsView ? path : url);
break;
case 2:
// 关闭其它
if (meta.isDynamic) await router.push({ name, params });
else await router.push({ path, query });
closeOtherTagsView(path);
break;
case 3:
// 关闭全部
closeAllTagsView();
break;
case 4:
// 开启当前页面全屏
openCurrenFullscreen(getThemeConfig.value.isShareTagsView ? path : url);
break;
}
};
// 右键点击时:传 x,y 坐标值到子组件中props
const onContextmenu = (v: any, e: any) => {
const { clientX, clientY } = e;
state.dropdown.x = clientX;
state.dropdown.y = clientY;
contextmenuRef.value.openContextmenu(v);
};
// 当前的 tagsView 项点击时
const onTagsClick = (v: any, k: number) => {
state.tagsRefsIndex = k;
router.push(v);
};
// 处理 tagsView 高亮(多标签详情时使用,单标签详情未使用)
const setTagsViewHighlight = (v: any) => {
let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
if (!params || Object.keys(params).length <= 0) return v.path;
let path = '';
for (let i in params) {
path += params[i];
}
// 判断是否是动态路由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;
};
// tagsView 横向滚动
const tagsViewmoveToCurrentTag = () => {
nextTick(() => {
if (tagsRefs.value.length <= 0) return false;
// 当前 li 元素
let liDom = tagsRefs.value[state.tagsRefsIndex];
// 当前 li 元素下标
let liIndex = state.tagsRefsIndex;
// 当前 ul 下 li 元素总长度
let liLength = tagsRefs.value.length;
// 最前 li
let liFirst: any = tagsRefs.value[0];
// 最后 li
let liLast: any = tagsRefs.value[tagsRefs.value.length - 1];
// 当前滚动条的值
let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap$;
// 当前滚动条滚动宽度
let scrollS = scrollRefs.scrollWidth;
// 当前滚动条偏移宽度
let offsetW = scrollRefs.offsetWidth;
// 当前滚动条偏移距离
let scrollL = scrollRefs.scrollLeft;
// 上一个 tags li dom
let liPrevTag: any = tagsRefs.value[state.tagsRefsIndex - 1];
// 下一个 tags li dom
let liNextTag: any = tagsRefs.value[state.tagsRefsIndex + 1];
// 上一个 tags li dom 的偏移距离
let beforePrevL: any = '';
// 下一个 tags li dom 的偏移距离
let afterNextL: any = '';
if (liDom === liFirst) {
// 头部
scrollRefs.scrollLeft = 0;
} else if (liDom === liLast) {
// 尾部
scrollRefs.scrollLeft = scrollS - offsetW;
} else {
// 非头/尾部
if (liIndex === 0) beforePrevL = liFirst.offsetLeft - 5;
else beforePrevL = liPrevTag?.offsetLeft - 5;
if (liIndex === liLength) afterNextL = liLast.offsetLeft + liLast.offsetWidth + 5;
else afterNextL = liNextTag.offsetLeft + liNextTag.offsetWidth + 5;
if (afterNextL > scrollL + offsetW) {
scrollRefs.scrollLeft = afterNextL - offsetW;
} else if (beforePrevL < scrollL) {
scrollRefs.scrollLeft = beforePrevL;
}
}
// 更新滚动条,防止不出现
updateScrollbar();
});
};
// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
const getTagsRefsIndex = (path: string) => {
nextTick(async () => {
// await 使用该写法,防止拿取不到 tagsViewList 列表数据不完整
let tagsViewList = await state.tagsViewList;
state.tagsRefsIndex = tagsViewList.findIndex((v: any) => {
if (getThemeConfig.value.isShareTagsView) {
return v.path === path;
} else {
return v.url === path;
}
});
// 添加初始化横向滚动条移动到对应位置
tagsViewmoveToCurrentTag();
});
};
// 设置 tagsView 可以进行拖拽
const initSortable = async () => {
const el = document.querySelector('.layout-navbars-tagsview-ul') as HTMLElement;
if (!el) return false;
state.sortable.el && state.sortable.destroy();
state.sortable = Sortable.create(el, {
animation: 300,
dataIdAttr: 'data-url',
disabled: getThemeConfig.value.isSortableTagsView ? false : true,
onEnd: () => {
const sortEndList: any = [];
state.sortable.toArray().map((val: any) => {
state.tagsViewList.map((v: any) => {
if (v.url === val) sortEndList.push({ ...v });
});
});
addBrowserSetSession(sortEndList);
},
});
};
// 拖动问题https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
const onSortableResize = async () => {
await initSortable();
if (other.isMobile()) state.sortable.el && state.sortable.destroy();
};
// 页面加载前
onBeforeMount(() => {
// 初始化,防止手机端直接访问时还可以拖拽
onSortableResize();
// 拖动问题https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
window.addEventListener('resize', onSortableResize);
// 监听非本页面调用 0 刷新当前1 关闭当前2 关闭其它3 关闭全部 4 当前页全屏
proxy.mittBus.on('onCurrentContextmenuClick', (data: object) => {
onCurrentContextmenuClick(data);
});
// 监听布局配置界面开启/关闭拖拽
proxy.mittBus.on('openOrCloseSortable', () => {
initSortable();
});
// 监听布局配置开启 TagsView 共用,为了演示还原默认值
proxy.mittBus.on('openShareTagsView', () => {
if (getThemeConfig.value.isShareTagsView) {
router.push('/home');
state.tagsViewList = [];
state.tagsViewRoutesList.map((v: any) => {
if (v.meta.isAffix && !v.meta.isHide) {
v.url = setTagsViewHighlight(v);
state.tagsViewList.push({ ...v });
}
});
}
});
});
// 页面卸载时
onUnmounted(() => {
// 取消非本页面调用监听
proxy.mittBus.off('onCurrentContextmenuClick');
// 取消监听布局配置界面开启/关闭拖拽
proxy.mittBus.off('openOrCloseSortable');
// 取消监听布局配置开启 TagsView 共用
proxy.mittBus.off('openShareTagsView');
// 取消窗口 resize 监听
window.removeEventListener('resize', onSortableResize);
});
// 页面更新时
onBeforeUpdate(() => {
tagsRefs.value = [];
});
// 页面加载时
onMounted(() => {
// 初始化 vuex 中的 tagsViewRoutes 列表
getTagsViewRoutes();
initSortable();
});
// 路由更新时
onBeforeRouteUpdate(async (to) => {
state.routeActive = setTagsViewHighlight(to);
state.routePath = to.meta.isDynamic ? to.meta.isDynamicPath : to.path;
await addTagsView(to.path, to);
getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
});
// 监听路由的变化,动态赋值给 tagsView
watch(store.state, (val) => {
if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
getTagsViewRoutes();
});
return {
isActive,
onContextmenu,
getTagsViewRoutes,
onTagsClick,
tagsRefs,
contextmenuRef,
scrollbarRef,
tagsUlRef,
onHandleScroll,
getThemeConfig,
setTagsStyle,
refreshCurrentTagsView,
closeCurrentTagsView,
onCurrentContextmenuClick,
...toRefs(state),
};
},
const { proxy } = getCurrentInstance();
const tagsRefs = ref([]);
const scrollbarRef = ref();
const contextmenuRef = ref();
const tagsUlRef = ref();
const store = useStore();
const route = useRoute();
const router = useRouter();
const state = reactive({
routeActive: '',
routePath: route.path,
dropdown: { x: '', y: '' },
tagsRefsIndex: 0,
tagsViewList: [],
sortable: '',
tagsViewRoutesList: [],
});
// 动态设置 tagsView 风格样式
const setTagsStyle = computed(() => {
return store.state.themeConfig.themeConfig.tagsStyle;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 设置 tagsView 高亮
const isActive = (v) => {
if (getThemeConfig.value.isShareTagsView) {
return v.path === state.routePath;
} else {
return v.url ? v.url === state.routeActive : v.path === state.routeActive;
}
};
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
const addBrowserSetSession = (tagsViewList) => {
Session.set('tagsViewList', tagsViewList);
};
// 获取 vuex 中的 tagsViewRoutes 列表
const getTagsViewRoutes = async () => {
state.routeActive = await setTagsViewHighlight(route);
state.routePath = (await route.meta.isDynamic) ? route.meta.isDynamicPath : route.path;
state.tagsViewList = [];
state.tagsViewRoutesList = store.state.tagsViewRoutes.tagsViewRoutes;
initTagsView();
};
// vuex 中获取路由信息如果是设置了固定的isAffix进行初始化显示
const initTagsView = async () => {
if (Session.get('tagsViewList') && getThemeConfig.value.isCacheTagsView) {
state.tagsViewList = await Session.get('tagsViewList');
} else {
await state.tagsViewRoutesList.map((v) => {
if (v.meta.isAffix && !v.meta.isHide) {
v.url = setTagsViewHighlight(v);
state.tagsViewList.push({ ...v });
}
});
await addTagsView(route.path, route);
}
// 初始化当前元素(li)的下标
getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
};
// 处理可开启多标签详情单标签详情动态路由xxx/:id/:name"),普通路由处理)
const solveAddTagsView = async (path, to) => {
let isDynamicPath = to.meta.isDynamic ? to.meta.isDynamicPath : path;
let current = state.tagsViewList.filter(
(v) =>
v.path === isDynamicPath &&
isObjectValueEqual(
to.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
to.meta.isDynamic ? (to?.params ? to?.params : null) : to?.query ? to?.query : null
)
);
if (current.length <= 0) {
// 防止Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead.
let findItem = state.tagsViewRoutesList.find((v) => v.path === isDynamicPath);
if (findItem.meta.isAffix) return false;
if (findItem.meta.isLink && !findItem.meta.isIframe) return false;
to.meta.isDynamic ? (findItem.params = to.params) : (findItem.query = to.query);
findItem.url = setTagsViewHighlight(findItem);
state.tagsViewList.push({ ...findItem });
addBrowserSetSession(state.tagsViewList);
}
};
// 处理单标签时,第二次的值未覆盖第一次的 tagsViewList 值Session Storage
const singleAddTagsView = (path, to) => {
let isDynamicPath = to.meta.isDynamic ? to.meta.isDynamicPath : path;
state.tagsViewList.forEach((v) => {
if (
v.path === isDynamicPath &&
!isObjectValueEqual(
to.meta.isDynamic ? (v.params ? v.params : null) : v.query ? v.query : null,
to.meta.isDynamic ? (to?.params ? to?.params : null) : to?.query ? to?.query : null
)
) {
to.meta.isDynamic ? (v.params = to.params) : (v.query = to.query);
v.url = setTagsViewHighlight(v);
addBrowserSetSession(state.tagsViewList);
}
});
};
// 1、添加 tagsView未设置隐藏isHide也添加到在 tagsView 中(可开启多标签详情,单标签详情)
const addTagsView = (path, to) => {
// 防止拿取不到路由信息
nextTick(async () => {
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
let item = '';
if (to && to.meta.isDynamic) {
// 动态路由xxx/:id/:name"):参数不同,开启多个 tagsview
if (!getThemeConfig.value.isShareTagsView) await solveAddTagsView(path, to);
else await singleAddTagsView(path, to);
if (state.tagsViewList.some((v) => v.path === to.meta.isDynamicPath)) return false;
item = state.tagsViewRoutesList.find((v) => 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) => v.path === path)) return false;
item = state.tagsViewRoutesList.find((v) => v.path === path);
}
if (item.meta.isLink && !item.meta.isIframe) return false;
if (to && to.meta.isDynamic) item.params = to?.params ? to?.params : route.params;
else item.query = to?.query ? to?.query : route.query;
item.url = setTagsViewHighlight(item);
await state.tagsViewList.push({ ...item });
await addBrowserSetSession(state.tagsViewList);
});
};
// 2、刷新当前 tagsView
const refreshCurrentTagsView = (fullPath) => {
proxy.mittBus.emit('onTagsViewRefreshRouterView', fullPath);
};
// 3、关闭当前 tagsView如果是设置了固定的isAffix不可以关闭
const closeCurrentTagsView = (path) => {
state.tagsViewList.map((v, k, arr) => {
if (!v.meta.isAffix) {
if (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path) {
state.tagsViewList.splice(k, 1);
setTimeout(() => {
if (state.tagsViewList.length === k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
// 最后一个且高亮时
if (arr[arr.length - 1].meta.isDynamic) {
// 动态路由xxx/:id/:name"
if (k !== arr.length) router.push({ name: arr[k].name, params: arr[k].params });
else router.push({ name: arr[arr.length - 1].name, params: arr[arr.length - 1].params });
} else {
// 普通路由
if (k !== arr.length) router.push({ path: arr[k].path, query: arr[k].query });
else router.push({ path: arr[arr.length - 1].path, query: arr[arr.length - 1].query });
}
} else {
// 非最后一个且高亮时,跳转到下一个
if (state.tagsViewList.length !== k && getThemeConfig.value.isShareTagsView ? state.routePath === path : state.routeActive === path) {
if (arr[k].meta.isDynamic) {
// 动态路由xxx/:id/:name"
router.push({ name: arr[k].name, params: arr[k].params });
} else {
// 普通路由
router.push({ path: arr[k].path, query: arr[k].query });
}
}
}
}, 0);
}
}
});
addBrowserSetSession(state.tagsViewList);
};
// 4、关闭其它 tagsView如果是设置了固定的isAffix不进行关闭
const closeOtherTagsView = (path) => {
state.tagsViewList = [];
state.tagsViewRoutesList.map((v) => {
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
});
addTagsView(path, route);
};
// 5、关闭全部 tagsView如果是设置了固定的isAffix不进行关闭
const closeAllTagsView = () => {
state.tagsViewList = [];
state.tagsViewRoutesList.map((v) => {
if (v.meta.isAffix && !v.meta.isHide) {
state.tagsViewList.push({ ...v });
router.push({ path: state.tagsViewList[state.tagsViewList.length - 1].path });
}
});
addBrowserSetSession(state.tagsViewList);
};
// 6、开启当前页面全屏
const openCurrenFullscreen = async (path) => {
const item = state.tagsViewList.find((v) => (getThemeConfig.value.isShareTagsView ? v.path === path : v.url === path));
if (item.meta.isDynamic) await router.push({ name: item.name, params: item.params });
else await router.push({ name: item.name, query: item.query });
store.dispatch('tagsViewRoutes/setCurrenFullscreen', true);
};
// 当前项右键菜单点击,拿当前点击的路由路径对比 浏览器缓存中的 tagsView 路由数组,取当前点击项的详细路由信息
// 防止 tagsView 非当前页演示时,操作异常
const getCurrentRouteItem = (path, cParams) => {
const itemRoute = Session.get('tagsViewList') ? Session.get('tagsViewList') : state.tagsViewList;
return itemRoute.find((v) => {
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;
}
});
};
// 当前项右键菜单点击
const onCurrentContextmenuClick = async (item) => {
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);
switch (item.contextMenuClickId) {
case 0:
// 刷新当前
if (meta.isDynamic) await router.push({ name, params });
else await router.push({ path, query });
refreshCurrentTagsView(route.fullPath);
break;
case 1:
// 关闭当前
closeCurrentTagsView(getThemeConfig.value.isShareTagsView ? path : url);
break;
case 2:
// 关闭其它
if (meta.isDynamic) await router.push({ name, params });
else await router.push({ path, query });
closeOtherTagsView(path);
break;
case 3:
// 关闭全部
closeAllTagsView();
break;
case 4:
// 开启当前页面全屏
openCurrenFullscreen(getThemeConfig.value.isShareTagsView ? path : url);
break;
}
};
// 右键点击时:传 x,y 坐标值到子组件中props
const onContextmenu = (v, e) => {
const { clientX, clientY } = e;
state.dropdown.x = clientX;
state.dropdown.y = clientY;
contextmenuRef.value.openContextmenu(v);
};
// 当前的 tagsView 项点击时
const onTagsClick = (v, k) => {
state.tagsRefsIndex = k;
router.push(v);
};
// 处理 tagsView 高亮(多标签详情时使用,单标签详情未使用)
const setTagsViewHighlight = (v) => {
let params = v.query && Object.keys(v.query).length > 0 ? v.query : v.params;
if (!params || Object.keys(params).length <= 0) return v.path;
let path = '';
for (let i in params) {
path += params[i];
}
// 判断是否是动态路由xxx/:id/:name"
return `${v.meta.isDynamic ? v.meta.isDynamicPath : v.path}-${path}`;
};
// 更新滚动条显示
const updateScrollbar = () => {
proxy.$refs.scrollbarRef.update();
};
// 鼠标滚轮滚动
const onHandleScroll = (e) => {
proxy.$refs.scrollbarRef.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
};
// tagsView 横向滚动
const tagsViewmoveToCurrentTag = () => {
nextTick(() => {
if (tagsRefs.value.length <= 0) return false;
// 当前 li 元素
let liDom = tagsRefs.value[state.tagsRefsIndex];
// 当前 li 元素下标
let liIndex = state.tagsRefsIndex;
// 当前 ul 下 li 元素总长度
let liLength = tagsRefs.value.length;
// 最前 li
let liFirst = tagsRefs.value[0];
// 最后 li
let liLast = tagsRefs.value[tagsRefs.value.length - 1];
// 当前滚动条的值
let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap$;
// 当前滚动条滚动宽度
let scrollS = scrollRefs.scrollWidth;
// 当前滚动条偏移宽度
let offsetW = scrollRefs.offsetWidth;
// 当前滚动条偏移距离
let scrollL = scrollRefs.scrollLeft;
// 上一个 tags li dom
let liPrevTag = tagsRefs.value[state.tagsRefsIndex - 1];
// 下一个 tags li dom
let liNextTag = tagsRefs.value[state.tagsRefsIndex + 1];
// 上一个 tags li dom 的偏移距离
let beforePrevL = '';
// 下一个 tags li dom 的偏移距离
let afterNextL = '';
if (liDom === liFirst) {
// 头部
scrollRefs.scrollLeft = 0;
} else if (liDom === liLast) {
// 尾部
scrollRefs.scrollLeft = scrollS - offsetW;
} else {
// 非头/尾部
if (liIndex === 0) beforePrevL = liFirst.offsetLeft - 5;
else beforePrevL = liPrevTag?.offsetLeft - 5;
if (liIndex === liLength) afterNextL = liLast.offsetLeft + liLast.offsetWidth + 5;
else afterNextL = liNextTag.offsetLeft + liNextTag.offsetWidth + 5;
if (afterNextL > scrollL + offsetW) {
scrollRefs.scrollLeft = afterNextL - offsetW;
} else if (beforePrevL < scrollL) {
scrollRefs.scrollLeft = beforePrevL;
}
}
// 更新滚动条,防止不出现
updateScrollbar();
});
};
// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
const getTagsRefsIndex = (path) => {
nextTick(async () => {
// await 使用该写法,防止拿取不到 tagsViewList 列表数据不完整
let tagsViewList = await state.tagsViewList;
state.tagsRefsIndex = tagsViewList.findIndex((v) => {
if (getThemeConfig.value.isShareTagsView) {
return v.path === path;
} else {
return v.url === path;
}
});
// 添加初始化横向滚动条移动到对应位置
tagsViewmoveToCurrentTag();
});
};
// 设置 tagsView 可以进行拖拽
const initSortable = async () => {
const el = document.querySelector('.layout-navbars-tagsview-ul');
if (!el) return false;
state.sortable.el && state.sortable.destroy();
state.sortable = Sortable.create(el, {
animation: 300,
dataIdAttr: 'data-url',
disabled: getThemeConfig.value.isSortableTagsView ? false : true,
onEnd: () => {
const sortEndList = [];
state.sortable.toArray().map((val) => {
state.tagsViewList.map((v) => {
if (v.url === val) sortEndList.push({ ...v });
});
});
addBrowserSetSession(sortEndList);
},
});
};
// 拖动问题https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
const onSortableResize = async () => {
await initSortable();
if (other.isMobile()) state.sortable.el && state.sortable.destroy();
};
// 页面加载前
onBeforeMount(() => {
// 初始化,防止手机端直接访问时还可以拖拽
onSortableResize();
// 拖动问题https://gitee.com/lyt-top/vue-next-admin/issues/I3ZRRI
window.addEventListener('resize', onSortableResize);
// 监听非本页面调用 0 刷新当前1 关闭当前2 关闭其它3 关闭全部 4 当前页全屏
proxy.mittBus.on('onCurrentContextmenuClick', (data) => {
onCurrentContextmenuClick(data);
});
// 监听布局配置界面开启/关闭拖拽
proxy.mittBus.on('openOrCloseSortable', () => {
initSortable();
});
// 监听布局配置开启 TagsView 共用,为了演示还原默认值
proxy.mittBus.on('openShareTagsView', () => {
if (getThemeConfig.value.isShareTagsView) {
router.push('/home');
state.tagsViewList = [];
state.tagsViewRoutesList.map((v) => {
if (v.meta.isAffix && !v.meta.isHide) {
v.url = setTagsViewHighlight(v);
state.tagsViewList.push({ ...v });
}
});
}
});
});
// 页面卸载时
onUnmounted(() => {
// 取消非本页面调用监听
proxy.mittBus.off('onCurrentContextmenuClick');
// 取消监听布局配置界面开启/关闭拖拽
proxy.mittBus.off('openOrCloseSortable');
// 取消监听布局配置开启 TagsView 共用
proxy.mittBus.off('openShareTagsView');
// 取消窗口 resize 监听
window.removeEventListener('resize', onSortableResize);
});
// 页面更新时
onBeforeUpdate(() => {
tagsRefs.value = [];
});
// 页面加载时
onMounted(() => {
// 初始化 vuex 中的 tagsViewRoutes 列表
getTagsViewRoutes();
initSortable();
});
// 路由更新时
onBeforeRouteUpdate(async (to) => {
state.routeActive = setTagsViewHighlight(to);
state.routePath = to.meta.isDynamic ? to.meta.isDynamicPath : to.path;
await addTagsView(to.path, to);
getTagsRefsIndex(getThemeConfig.value.isShareTagsView ? state.routePath : state.routeActive);
});
// 监听路由的变化,动态赋值给 tagsView
watch(store.state, (val) => {
if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
getTagsViewRoutes();
});
</script>
<style scoped lang="scss">

View File

@ -1,7 +1,7 @@
<template>
<div class="el-menu-horizontal-warp">
<el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
<el-menu router :default-active="defaultActive" background-color="transparent" mode="horizontal">
<el-menu router :default-active="state.defaultActive" background-color="transparent" mode="horizontal">
<template v-for="val in menuLists">
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
@ -28,104 +28,92 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, nextTick, onBeforeMount } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { useStore } from '/@/store/index';
<script setup name="navMenuHorizontal">
import { onBeforeRouteUpdate } from 'vue-router';
import SubItem from '/@/layout/navMenu/subItem.vue';
export default defineComponent({
name: 'navMenuHorizontal',
components: { SubItem },
props: {
menuList: {
type: Array,
default: () => [],
},
const props = defineProps({
menuList: {
type: Array,
default: () => [],
},
setup(props) {
const { proxy } = getCurrentInstance() as any;
const route = useRoute();
const store = useStore();
const state = reactive({
defaultActive: null,
});
const { proxy } = getCurrentInstance();
const route = useRoute();
const store = useStore();
const state = reactive({
defaultActive: null,
});
// 获取父级菜单数据
const menuLists = computed(() => {
return props.menuList;
});
// 设置横向滚动条可以鼠标滚轮滚动
const onElMenuHorizontalScroll = (e) => {
const eventDelta = e.wheelDelta || -e.deltaY * 40;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft + eventDelta / 4;
};
// 初始化数据,页面刷新时,滚动条滚动到对应位置
const initElMenuOffsetLeft = () => {
nextTick(() => {
let els = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
if (!els) return false;
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = els.offsetLeft;
});
};
// 路由过滤递归函数
const filterRoutesFun = (arr) => {
return arr
.filter((item) => !item.meta.isHide)
.map((item) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
// 获取父级菜单数据
const menuLists = computed(() => {
return props.menuList;
});
// 设置横向滚动条可以鼠标滚轮滚动
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;
};
// 初始化数据,页面刷新时,滚动条滚动到对应位置
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;
});
};
// 路由过滤递归函数
const filterRoutesFun = (arr: Array<object>) => {
return arr
.filter((item: any) => !item.meta.isHide)
.map((item: any) => {
item = Object.assign({}, item);
if (item.children) item.children = filterRoutesFun(item.children);
return item;
});
};
// 传送当前子级数据到菜单中
const setSendClassicChildren = (path: string) => {
const currentPathSplit = path.split('/');
let currentData: any = {};
filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
};
// 设置页面当前路由高亮
const setCurrentRouterHighlight = (currentRoute) => {
const { path, meta } = currentRoute;
if (store.state.themeConfig.themeConfig.layout === 'classic') {
state.defaultActive = `/${path.split('/')[1]}`;
} else {
const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
if (pathSplit.length >= 4 && meta.isHide) state.defaultActive = pathSplit.splice(0, 3).join('/');
else state.defaultActive = path;
}
};
// 页面加载前
onBeforeMount(() => {
setCurrentRouterHighlight(route);
});
// 页面加载时
onMounted(() => {
initElMenuOffsetLeft();
});
// 路由更新时
onBeforeRouteUpdate((to) => {
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
setCurrentRouterHighlight(to);
proxy.mittBus.emit('onMenuClick');
// 修复经典布局开启切割菜单时点击tagsView后左侧导航菜单数据不变的问题
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) {
proxy.mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
}
});
return {
menuLists,
onElMenuHorizontalScroll,
...toRefs(state),
};
},
};
// 传送当前子级数据到菜单中
const setSendClassicChildren = (path) => {
const currentPathSplit = path.split('/');
let currentData = {};
filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
};
// 设置页面当前路由高亮
const setCurrentRouterHighlight = (currentRoute) => {
const { path, meta } = currentRoute;
if (store.state.themeConfig.themeConfig.layout === 'classic') {
state.defaultActive = `/${path.split('/')[1]}`;
} else {
const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
if (pathSplit.length >= 4 && meta.isHide) state.defaultActive = pathSplit.splice(0, 3).join('/');
else state.defaultActive = path;
}
};
// 页面加载前
onBeforeMount(() => {
setCurrentRouterHighlight(route);
});
// 页面加载时
onMounted(() => {
initElMenuOffsetLeft();
});
// 路由更新时
onBeforeRouteUpdate((to) => {
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
setCurrentRouterHighlight(to);
proxy.mittBus.emit('onMenuClick');
// 修复经典布局开启切割菜单时点击tagsView后左侧导航菜单数据不变的问题
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
if (layout === 'classic' && isClassicSplitMenu) {
proxy.mittBus.emit('setSendClassicChildren', setSendClassicChildren(to.path));
}
});
</script>

View File

@ -22,24 +22,16 @@
</template>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
export default defineComponent({
name: 'navMenuSubItem',
props: {
chil: {
type: Array,
default: () => [],
},
},
setup(props) {
// 获取父级菜单数据
const chils = computed(() => {
return props.chil;
});
return {
chils,
};
<script setup name="navMenuSubItem">
const props = defineProps({
chil: {
type: Array,
default: () => [],
},
});
// 获取父级菜单数据
const chils = computed(() => {
return props.chil;
});
</script>

View File

@ -1,9 +1,9 @@
<template>
<el-menu
router
:default-active="defaultActive"
:default-active="state.defaultActive"
background-color="transparent"
:collapse="isCollapse"
:collapse="state.isCollapse"
:unique-opened="getThemeConfig.isUniqueOpened"
:collapse-transition="false"
>
@ -28,71 +28,59 @@
</el-menu>
</template>
<script lang="ts">
import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, watch } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { useStore } from '/@/store/index';
<script setup name="navMenuVertical">
import { onBeforeRouteUpdate } from 'vue-router';
import SubItem from '/@/layout/navMenu/subItem.vue';
export default defineComponent({
name: 'navMenuVertical',
components: { SubItem },
props: {
menuList: {
type: Array,
default: () => [],
},
},
setup(props) {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const state = reactive({
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
isCollapse: false,
});
// 获取父级菜单数据
const menuLists = computed(() => {
return props.menuList;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 菜单高亮(详情时,父级高亮)
const setParentHighlight = (currentRoute) => {
const { path, meta } = currentRoute;
const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
if (pathSplit.length >= 4 && meta.isHide) return pathSplit.splice(0, 3).join('/');
else return path;
};
// 设置菜单的收起/展开
watch(
store.state.themeConfig.themeConfig,
() => {
document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = getThemeConfig.value.isCollapse);
},
{
immediate: true,
}
);
// 页面加载时
onMounted(() => {
state.defaultActive = setParentHighlight(route);
});
// 路由更新时
onBeforeRouteUpdate((to) => {
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
state.defaultActive = setParentHighlight(to);
proxy.mittBus.emit('onMenuClick');
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) getThemeConfig.value.isCollapse = false;
});
return {
menuLists,
getThemeConfig,
...toRefs(state),
};
const props = defineProps({
menuList: {
type: Array,
default: () => [],
},
});
const { proxy } = getCurrentInstance();
const store = useStore();
const route = useRoute();
const state = reactive({
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
isCollapse: false,
});
// 获取父级菜单数据
const menuLists = computed(() => {
return props.menuList;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 菜单高亮(详情时,父级高亮)
const setParentHighlight = (currentRoute) => {
const { path, meta } = currentRoute;
const pathSplit = meta.isDynamic ? meta.isDynamicPath.split('/') : path.split('/');
if (pathSplit.length >= 4 && meta.isHide) return pathSplit.splice(0, 3).join('/');
else return path;
};
// 设置菜单的收起/展开
watch(
store.state.themeConfig.themeConfig,
() => {
document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = getThemeConfig.value.isCollapse);
},
{
immediate: true,
}
);
// 页面加载时
onMounted(() => {
state.defaultActive = setParentHighlight(route);
});
// 路由更新时
onBeforeRouteUpdate((to) => {
// 修复https://gitee.com/lyt-top/vue-next-admin/issues/I3YX6G
state.defaultActive = setParentHighlight(to);
proxy.mittBus.emit('onMenuClick');
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) getThemeConfig.value.isCollapse = false;
});
</script>

View File

@ -1,60 +1,48 @@
<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%" id="iframe" v-show="!iframeLoading"></iframe>
<div class="layout-view-bg-white flex mt1" :style="{ height: `calc(100vh - ${setIframeHeight}`, border: 'none' }" v-loading="state.iframeLoading">
<iframe :src="state.iframeUrl" frameborder="0" height="100%" width="100%" id="iframe" v-show="!state.iframeLoading"></iframe>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, onMounted, nextTick, watch, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
export default defineComponent({
name: 'layoutIfameView',
setup() {
const route = useRoute();
const store = useStore();
const state = reactive({
iframeLoading: true,
iframeUrl: '',
});
// 初始化页面加载 loading
const initIframeLoad = () => {
state.iframeUrl = route.meta.isLink;
nextTick(() => {
state.iframeLoading = true;
const iframe = document.getElementById('iframe');
if (!iframe) return false;
iframe.onload = () => {
state.iframeLoading = false;
};
});
};
// 设置 iframe 的高度
const setIframeHeight = computed(() => {
let { isTagsview } = store.state.themeConfig.themeConfig;
let { isTagsViewCurrenFull } = store.state.tagsViewRoutes;
if (isTagsViewCurrenFull) {
return `1px`;
} else {
if (isTagsview) return `85px`;
else return `51px`;
}
});
// 页面加载时
onMounted(() => {
initIframeLoad();
});
// 监听路由变化,多个 iframe 时使用
watch(
() => route.path,
() => {
initIframeLoad();
}
);
return {
setIframeHeight,
...toRefs(state),
};
},
<script setup name="layoutIfameView">
const route = useRoute();
const store = useStore();
const state = reactive({
iframeLoading: true,
iframeUrl: '',
});
// 初始化页面加载 loading
const initIframeLoad = () => {
state.iframeUrl = route.meta.isLink;
nextTick(() => {
state.iframeLoading = true;
const iframe = document.getElementById('iframe');
if (!iframe) return false;
iframe.onload = () => {
state.iframeLoading = false;
};
});
};
// 设置 iframe 的高度
const setIframeHeight = computed(() => {
let { isTagsview } = store.state.themeConfig.themeConfig;
let { isTagsViewCurrenFull } = store.state.tagsViewRoutes;
if (isTagsViewCurrenFull) {
return `1px`;
} else {
if (isTagsview) return `85px`;
else return `51px`;
}
});
// 页面加载时
onMounted(() => {
initIframeLoad();
});
// 监听路由变化,多个 iframe 时使用
watch(
() => route.path,
() => {
initIframeLoad();
}
);
</script>

View File

@ -1,43 +1,31 @@
<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"
>{{ currentRouteMeta.title }}{{ currentRouteMeta.isLink }}</a
<a :href="state.currentRouteMeta.isLink" target="_blank" rel="opener" class="flex-margin"
>{{ state.currentRouteMeta.title }}{{ state.currentRouteMeta.isLink }}</a
>
</div>
</template>
<script lang="ts">
import { defineComponent, toRefs, reactive, computed, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
export default defineComponent({
name: 'layoutLinkView',
setup() {
const route = useRoute();
const store = useStore();
const state = reactive({
currentRouteMeta: {},
});
// 设置 link 的高度
const setLinkHeight = computed(() => {
let { isTagsview } = store.state.themeConfig.themeConfig;
if (isTagsview) return `114px`;
else return `80px`;
});
// 监听路由的变化,设置内容
watch(
() => route.path,
() => {
state.currentRouteMeta = route.meta;
},
{
immediate: true,
}
);
return {
setLinkHeight,
...toRefs(state),
};
},
<script setup name="layoutLinkView">
const route = useRoute();
const store = useStore();
const state = reactive({
currentRouteMeta: {},
});
// 设置 link 的高度
const setLinkHeight = computed(() => {
let { isTagsview } = store.state.themeConfig.themeConfig;
if (isTagsview) return `114px`;
else return `80px`;
});
// 监听路由的变化,设置内容
watch(
() => route.path,
() => {
state.currentRouteMeta = route.meta;
},
{
immediate: true,
}
);
</script>

View File

@ -2,70 +2,51 @@
<div class="h100">
<router-view v-slot="{ Component }">
<transition :name="setTransitionName" mode="out-in">
<keep-alive :include="keepAliveNameList">
<component :is="Component" :key="refreshRouterViewKey" class="w100" />
<keep-alive :include="state.keepAliveNameList">
<component :is="Component" :key="state.refreshRouterViewKey" class="w100" />
</keep-alive>
</transition>
</router-view>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
export default defineComponent({
name: 'layoutParentView',
setup() {
const { proxy } = getCurrentInstance() as any;
const route = useRoute();
const store = useStore();
const state: any = reactive({
refreshRouterViewKey: null,
keepAliveNameList: [],
keepAliveNameNewList: [],
});
// 设置主界面切换动画
const setTransitionName = computed(() => {
return store.state.themeConfig.themeConfig.animation;
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 获取组件缓存列表(name值)
const getKeepAliveNames = computed(() => {
return store.state.keepAliveNames.keepAliveNames;
});
// 页面加载前,处理缓存,页面刷新时路由缓存处理
onBeforeMount(() => {
state.keepAliveNameList = getKeepAliveNames.value;
proxy.mittBus.on('onTagsViewRefreshRouterView', (fullPath: string) => {
state.keepAliveNameList = getKeepAliveNames.value.filter((name: string) => route.name !== name);
state.refreshRouterViewKey = null;
nextTick(() => {
state.refreshRouterViewKey = fullPath;
state.keepAliveNameList = getKeepAliveNames.value;
});
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('onTagsViewRefreshRouterView');
});
// 监听路由变化,防止 tagsView 多标签时,切换动画消失
watch(
() => route.fullPath,
() => {
state.refreshRouterViewKey = route.fullPath;
}
);
return {
getThemeConfig,
getKeepAliveNames,
setTransitionName,
...toRefs(state),
};
},
<script setup name="layoutParentView">
const { proxy } = getCurrentInstance();
const route = useRoute();
const store = useStore();
const state = reactive({
refreshRouterViewKey: null,
keepAliveNameList: [],
});
// 设置主界面切换动画
const setTransitionName = computed(() => {
return store.state.themeConfig.themeConfig.animation;
});
// 获取组件缓存列表(name值)
const getKeepAliveNames = computed(() => {
return store.state.keepAliveNames.keepAliveNames;
});
// 页面加载前,处理缓存,页面刷新时路由缓存处理
onBeforeMount(() => {
state.keepAliveNameList = getKeepAliveNames.value;
proxy.mittBus.on('onTagsViewRefreshRouterView', (fullPath) => {
state.keepAliveNameList = getKeepAliveNames.value.filter((name) => route.name !== name);
state.refreshRouterViewKey = null;
nextTick(() => {
state.refreshRouterViewKey = fullPath;
state.keepAliveNameList = getKeepAliveNames.value;
});
});
});
// 页面卸载时
onUnmounted(() => {
proxy.mittBus.off('onTagsViewRefreshRouterView');
});
// 监听路由变化,防止 tagsView 多标签时,切换动画消失
watch(
() => route.fullPath,
() => {
state.refreshRouterViewKey = route.fullPath;
}
);
</script>

View File

@ -1,7 +1,7 @@
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { store, key } from './store';
import { store } from './store';
import { directive } from '/@/utils/directive';
import other from '/@/utils/other';
@ -16,6 +16,6 @@ const app = createApp(App);
directive(app);
other.elSvg(app);
app.use(router).use(store, key).use(ElementPlus, { size: other.globalComponentSize, locale: zhCn }).mount('#app');
app.use(router).use(store).use(ElementPlus, { size: other.globalComponentSize, locale: zhCn }).mount('#app');
app.config.globalProperties.mittBus = mitt();

View File

@ -1,18 +1,18 @@
import { store } from '/@/store/index.ts';
import { store } from '/@/store/index';
import { Session } from '/@/utils/storage';
import { NextLoading } from '/@/utils/loading';
import { setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/index';
import { dynamicRoutes } from '/@/router/route';
import { getMenuAdmin, getMenuTest } from '/@/api/menu/index';
const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}');
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
const layouModules = import.meta.glob('../layout/routerView/*.{vue,tsx}');
const viewsModules = import.meta.glob('../views/**/*.{vue,tsx}');
/**
* 获取目录下的 .vue.tsx 全部文件
* @method import.meta.glob
* @link 参考https://cn.vitejs.dev/guide/features.html#json
*/
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...layouModules }, { ...viewsModules });
const dynamicViewsModules = Object.assign({}, { ...layouModules }, { ...viewsModules });
/**
* 后端控制路由初始化方法防止刷新时路由丢失
@ -69,10 +69,10 @@ export function setBackEndControlRefreshRoutes() {
* @param routes 后端返回的路由表数组
* @returns 返回处理成函数后的 component
*/
export function backEndComponent(routes: any) {
export function backEndComponent(routes) {
if (!routes) return;
return routes.map((item: any) => {
if (item.component) item.component = dynamicImport(dynamicViewsModules, item.component as string);
return routes.map((item) => {
if (item.component) item.component = dynamicImport(dynamicViewsModules, item.component);
item.children && backEndComponent(item.children);
return item;
});
@ -84,7 +84,7 @@ export function backEndComponent(routes: any) {
* @param component 当前要处理项 component
* @returns 返回处理成函数后的 component
*/
export function dynamicImport(dynamicViewsModules: Record<string, Function>, component: string) {
export function dynamicImport(dynamicViewsModules, component) {
const keys = Object.keys(dynamicViewsModules);
const matchKeys = keys.filter((key) => {
const k = key.replace(/..\/views|../, '');

View File

@ -1,7 +1,7 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { createRouter, createWebHashHistory } from 'vue-router';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import { store } from '/@/store/index.ts';
import { store } from '/@/store/index';
import { Session } from '/@/utils/storage';
import { NextLoading } from '/@/utils/loading';
import { staticRoutes, dynamicRoutes } from '/@/router/route';
@ -32,7 +32,7 @@ const pathMatch = {
* @param arr 传入路由菜单数据数组
* @returns 返回处理后的一维路由菜单数组
*/
export function formatFlatteningRoutes(arr: any) {
export function formatFlatteningRoutes(arr) {
if (arr.length <= 0) return false;
for (let i = 0; i < arr.length; i++) {
if (arr[i].children) {
@ -49,11 +49,11 @@ export function formatFlatteningRoutes(arr: any) {
* @param arr 处理后的一维路由菜单数组
* @returns 返回将一维数组重新处理成 `定义动态路由dynamicRoutes` 的格式
*/
export function formatTwoStageRoutes(arr: any) {
export function formatTwoStageRoutes(arr) {
if (arr.length <= 0) return false;
const newArr: any = [];
const cacheList: Array<string> = [];
arr.forEach((v: any) => {
const newArr = [];
const cacheList = [];
arr.forEach((v) => {
if (v.path === '/') {
newArr.push({ component: v.component, name: v.name, path: v.path, redirect: v.redirect, meta: v.meta, children: [] });
} else {
@ -92,8 +92,8 @@ export function setCacheTagsViewRoutes() {
* @param route 当前循环时的路由项
* @returns 返回对比后有权限的路由项
*/
export function hasRoles(roles: any, route: any) {
if (route.meta && route.meta.roles) return roles.some((role: any) => route.meta.roles.includes(role));
export function hasRoles(roles, route) {
if (route.meta && route.meta.roles) return roles.some((role) => route.meta.roles.includes(role));
else return true;
}
@ -103,9 +103,9 @@ export function hasRoles(roles: any, route: any) {
* @param roles 用户权限标识 userInfos用户信息 roles登录页登录时缓存到浏览器数组
* @returns 返回有权限的路由数组 `meta.roles` 中控制
*/
export function setFilterHasRolesMenu(routes: any, roles: any) {
const menu: any = [];
routes.forEach((route: any) => {
export function setFilterHasRolesMenu(routes, roles) {
const menu = [];
routes.forEach((route) => {
const item = { ...route };
if (hasRoles(roles, item)) {
if (item.children) item.children = setFilterHasRolesMenu(item.children, roles);
@ -132,12 +132,12 @@ export function setFilterMenuAndCacheTagsViewRoutes() {
* @param chil dynamicRoutes/@/router/route第一个顶级 children 的下路由集合
* @returns 返回有当前用户权限标识的路由数组
*/
export function setFilterRoute(chil: any) {
let filterRoute: any = [];
chil.forEach((route: any) => {
export function setFilterRoute(chil) {
let filterRoute = [];
chil.forEach((route) => {
if (route.meta.roles) {
route.meta.roles.forEach((metaRoles: any) => {
store.state.userInfos.userInfos.roles.forEach((roles: any) => {
route.meta.roles.forEach((metaRoles) => {
store.state.userInfos.userInfos.roles.forEach((roles) => {
if (metaRoles === roles) filterRoute.push({ ...route });
});
});
@ -152,7 +152,7 @@ export function setFilterRoute(chil: any) {
* @returns 返回替换后的路由数组
*/
export function setFilterRouteEnd() {
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
let filterRouteEnd = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
filterRouteEnd[0].children = [...setFilterRoute(filterRouteEnd[0].children), { ...pathMatch }];
return filterRouteEnd;
}
@ -164,8 +164,8 @@ export function setFilterRouteEnd() {
* @link 参考https://next.router.vuejs.org/zh/api/#addroute
*/
export function setAddRoute() {
setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
const routeName: any = route.name;
setFilterRouteEnd().forEach((route) => {
const routeName = route.name;
if (!router.hasRoute(routeName)) router.addRoute(route);
});
}
@ -177,8 +177,8 @@ export function setAddRoute() {
* @link 参考https://next.router.vuejs.org/zh/api/#push
*/
export function resetRoute() {
setFilterRouteEnd().forEach((route: RouteRecordRaw) => {
const routeName: any = route.name;
setFilterRouteEnd().forEach((route) => {
const routeName = route.name;
router.hasRoute(routeName) && router.removeRoute(routeName);
});
}

View File

@ -1,5 +1,3 @@
import { RouteRecordRaw } from 'vue-router';
/**
* 路由meta对象参数说明
* meta: {
@ -20,7 +18,7 @@ import { RouteRecordRaw } from 'vue-router';
* @description 各字段请查看 `/@/views/system/menu/component/addMenu.vue 下的 ruleForm`
* @returns 返回路由菜单数据
*/
export const dynamicRoutes: Array<RouteRecordRaw> = [
export const dynamicRoutes = [
{
path: '/',
name: '/',
@ -147,7 +145,7 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
* @description 前端控制直接改 dynamicRoutes 中的路由后端控制不需要修改请求接口路由数据时会覆盖 dynamicRoutes 第一个顶级 children 的内容全屏不包含 layout 中的路由出口
* @returns 返回路由菜单数据
*/
export const staticRoutes: Array<RouteRecordRaw> = [
export const staticRoutes = [
{
path: '/login',
name: 'login',

19
src/store/index.js Normal file
View File

@ -0,0 +1,19 @@
import { createStore } from 'vuex';
// Vite supports importing multiple modules from the file system using the special import.meta.glob function
// see https://cn.vitejs.dev/guide/features.html#glob-import
const modulesFiles = import.meta.globEager('./modules/*.js');
const pathList = [];
for (const path in modulesFiles) {
pathList.push(path);
}
const modules = pathList.reduce((modules, modulePath) => {
const moduleName = modulePath.replace(/^\.\/modules\/(.*)\.\w+$/, '$1');
const value = modulesFiles[modulePath];
modules[moduleName] = value.default;
return modules;
}, {});
export const store = createStore({ modules });

View File

@ -1,27 +0,0 @@
import { InjectionKey } from 'vue';
import { createStore, useStore as baseUseStore, Store } from 'vuex';
import { RootStateTypes } from '/@/store/interface/index';
// Vite supports importing multiple modules from the file system using the special import.meta.glob function
// see https://cn.vitejs.dev/guide/features.html#glob-import
const modulesFiles = import.meta.globEager('./modules/*.ts');
const pathList: string[] = [];
for (const path in modulesFiles) {
pathList.push(path);
}
const modules = pathList.reduce((modules: { [x: string]: any }, modulePath: string) => {
const moduleName = modulePath.replace(/^\.\/modules\/(.*)\.\w+$/, '$1');
const value = modulesFiles[modulePath];
modules[moduleName] = value.default;
return modules;
}, {});
export const key: InjectionKey<Store<RootStateTypes>> = Symbol();
export const store = createStore<RootStateTypes>({ modules });
export function useStore() {
return baseUseStore(key);
}

View File

@ -1,92 +0,0 @@
// 接口类型声明
// 布局配置
export interface ThemeConfigState {
themeConfig: {
isDrawer: boolean;
primary: string;
success: string;
info: string;
warning: string;
danger: string;
topBar: string;
menuBar: string;
columnsMenuBar: string;
topBarColor: string;
menuBarColor: string;
columnsMenuBarColor: string;
isTopBarColorGradual: boolean;
isMenuBarColorGradual: boolean;
isColumnsMenuBarColorGradual: boolean;
isMenuBarColorHighlight: boolean;
isCollapse: boolean;
isUniqueOpened: boolean;
isFixedHeader: boolean;
isFixedHeaderChange: boolean;
isClassicSplitMenu: boolean;
isLockScreen: boolean;
lockScreenTime: number;
isShowLogo: boolean;
isShowLogoChange: boolean;
isBreadcrumb: boolean;
isTagsview: boolean;
isBreadcrumbIcon: boolean;
isTagsviewIcon: boolean;
isCacheTagsView: boolean;
isSortableTagsView: boolean;
isShareTagsView: boolean;
isFooter: boolean;
isGrayscale: boolean;
isInvert: boolean;
isIsDark: boolean;
isWartermark: boolean;
wartermarkText: string;
tagsStyle: string;
animation: string;
columnsAsideStyle: string;
columnsAsideLayout: string;
layout: string;
isRequestRoutes: boolean;
globalTitle: string;
globalViceTitle: string;
globalComponentSize: string;
};
}
// 路由列表
export interface RoutesListState {
routesList: Array<object>;
isColumnsMenuHover: Boolean;
isColumnsNavHover: Boolean;
}
// 路由缓存列表
export interface KeepAliveNamesState {
keepAliveNames: Array<string>;
}
// TagsView 路由列表
export interface TagsViewRoutesState {
tagsViewRoutes: Array<object>;
isTagsViewCurrenFull: Boolean;
}
// 用户信息
export interface UserInfosState {
userInfos: object;
}
// 后端返回原始路由(未处理时)
export interface RequestOldRoutesState {
requestOldRoutes: Array<object>;
}
// 主接口(顶级类型声明)
export interface RootStateTypes {
themeConfig: ThemeConfigState;
routesList: RoutesListState;
keepAliveNames: KeepAliveNamesState;
tagsViewRoutes: TagsViewRoutesState;
userInfos: UserInfosState;
requestOldRoutes: RequestOldRoutesState;
}

View File

@ -0,0 +1,20 @@
const keepAliveNamesModule = {
namespaced: true,
state: {
keepAliveNames: [],
},
mutations: {
// 设置路由缓存name字段
getCacheKeepAlive(state, data) {
state.keepAliveNames = data;
},
},
actions: {
// 设置路由缓存name字段
async setCacheKeepAlive({ commit }, data) {
commit('getCacheKeepAlive', data);
},
},
};
export default keepAliveNamesModule;

View File

@ -1,24 +0,0 @@
import { Module } from 'vuex';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { KeepAliveNamesState, RootStateTypes } from '/@/store/interface/index';
const keepAliveNamesModule: Module<KeepAliveNamesState, RootStateTypes> = {
namespaced: true,
state: {
keepAliveNames: [],
},
mutations: {
// 设置路由缓存name字段
getCacheKeepAlive(state: any, data: Array<string>) {
state.keepAliveNames = data;
},
},
actions: {
// 设置路由缓存name字段
async setCacheKeepAlive({ commit }, data: Array<string>) {
commit('getCacheKeepAlive', data);
},
},
};
export default keepAliveNamesModule;

View File

@ -0,0 +1,20 @@
const requestOldRoutesModule = {
namespaced: true,
state: {
requestOldRoutes: [],
},
mutations: {
// 后端控制路由
getBackEndControlRoutes(state, data) {
state.requestOldRoutes = data;
},
},
actions: {
// 后端控制路由
setBackEndControlRoutes({ commit }, routes) {
commit('getBackEndControlRoutes', routes);
},
},
};
export default requestOldRoutesModule;

View File

@ -1,24 +0,0 @@
import { Module } from 'vuex';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { RequestOldRoutesState, RootStateTypes } from '/@/store/interface/index';
const requestOldRoutesModule: Module<RequestOldRoutesState, RootStateTypes> = {
namespaced: true,
state: {
requestOldRoutes: [],
},
mutations: {
// 后端控制路由
getBackEndControlRoutes(state: any, data: object) {
state.requestOldRoutes = data;
},
},
actions: {
// 后端控制路由
setBackEndControlRoutes({ commit }, routes: Array<string>) {
commit('getBackEndControlRoutes', routes);
},
},
};
export default requestOldRoutesModule;

View File

@ -1,8 +1,4 @@
import { Module } from 'vuex';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { RoutesListState, RootStateTypes } from '/@/store/interface/index';
const routesListModule: Module<RoutesListState, RootStateTypes> = {
const routesListModule = {
namespaced: true,
state: {
routesList: [],
@ -11,29 +7,29 @@ const routesListModule: Module<RoutesListState, RootStateTypes> = {
},
mutations: {
// 设置路由,菜单中使用到
getRoutesList(state: any, data: Array<object>) {
getRoutesList(state, data) {
state.routesList = data;
},
// 设置分栏布局,鼠标是否移入移出(菜单)
getColumnsMenuHover(state: any, bool: Boolean) {
getColumnsMenuHover(state, bool) {
state.isColumnsMenuHover = bool;
},
// 设置分栏布局,鼠标是否移入移出(导航)
getColumnsNavHover(state: any, bool: Boolean) {
getColumnsNavHover(state, bool) {
state.isColumnsNavHover = bool;
},
},
actions: {
// 设置路由,菜单中使用到
async setRoutesList({ commit }, data: any) {
async setRoutesList({ commit }, data) {
commit('getRoutesList', data);
},
// 设置分栏布局,鼠标是否移入移出(菜单)
async setColumnsMenuHover({ commit }, bool: Boolean) {
async setColumnsMenuHover({ commit }, bool) {
commit('getColumnsMenuHover', bool);
},
// 设置分栏布局,鼠标是否移入移出(菜单)
async setColumnsNavHover({ commit }, bool: Boolean) {
async setColumnsNavHover({ commit }, bool) {
commit('getColumnsNavHover', bool);
},
},

View File

@ -1,9 +1,6 @@
import { Module } from 'vuex';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { TagsViewRoutesState, RootStateTypes } from '/@/store/interface/index';
import { Session } from '/@/utils/storage';
const tagsViewRoutesModule: Module<TagsViewRoutesState, RootStateTypes> = {
const tagsViewRoutesModule = {
namespaced: true,
state: {
tagsViewRoutes: [],
@ -11,22 +8,22 @@ const tagsViewRoutesModule: Module<TagsViewRoutesState, RootStateTypes> = {
},
mutations: {
// 设置 TagsView 路由
getTagsViewRoutes(state: any, data: Array<string>) {
getTagsViewRoutes(state, data) {
state.tagsViewRoutes = data;
},
// 设置卡片全屏
getCurrenFullscreen(state: any, bool: boolean) {
getCurrenFullscreen(state, bool) {
Session.set('isTagsViewCurrenFull', bool);
state.isTagsViewCurrenFull = bool;
},
},
actions: {
// 设置 TagsView 路由
async setTagsViewRoutes({ commit }, data: Array<string>) {
async setTagsViewRoutes({ commit }, data) {
commit('getTagsViewRoutes', data);
},
// 设置卡片全屏
setCurrenFullscreen({ commit }, bool: Boolean) {
setCurrenFullscreen({ commit }, bool) {
commit('getCurrenFullscreen', bool);
},
},

View File

@ -1,13 +1,9 @@
import { Module } from 'vuex';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { ThemeConfigState, RootStateTypes } from '/@/store/interface/index';
/**
* 2020.05.28 by lyt 优化
* 修改一下配置时需要每次都清理 `window.localStorage` 浏览器永久缓存配置才会生效
* 哪个大佬有解决办法欢迎pr感谢💕
*/
const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
const themeConfigModule = {
namespaced: true,
state: {
themeConfig: {
@ -147,13 +143,13 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
},
mutations: {
// 设置布局配置
getThemeConfig(state: any, data: object) {
getThemeConfig(state, data) {
state.themeConfig = data;
},
},
actions: {
// 设置布局配置
setThemeConfig({ commit }, data: object) {
setThemeConfig({ commit }, data) {
commit('getThemeConfig', data);
},
},

View File

@ -1,22 +1,19 @@
import { Module } from 'vuex';
import { Session } from '/@/utils/storage';
// 此处加上 `.ts` 后缀报错,具体原因不详
import { UserInfosState, RootStateTypes } from '/@/store/interface/index';
const userInfosModule: Module<UserInfosState, RootStateTypes> = {
const userInfosModule = {
namespaced: true,
state: {
userInfos: {},
},
mutations: {
// 设置用户信息
getUserInfos(state: any, data: object) {
getUserInfos(state, data) {
state.userInfos = data;
},
},
actions: {
// 设置用户信息
async setUserInfos({ commit }, data: object) {
async setUserInfos({ commit }, data) {
if (data) {
commit('getUserInfos', data);
} else {

View File

@ -4,7 +4,7 @@
* @param old 源数据
* @returns 两数组相同返回 `true`反之则反
*/
export function judementSameArr(news: Array<string>, old: Array<string>): boolean {
export function judementSameArr(news, old) {
let count = 0;
const leng = old.length;
for (let i in old) {
@ -21,7 +21,7 @@ export function judementSameArr(news: Array<string>, old: Array<string>): boolea
* @param b 要比较的对象二
* @returns 相同返回 true反之则反
*/
export function isObjectValueEqual(a: { [key: string]: any }, b: { [key: string]: any }) {
export function isObjectValueEqual(a, b) {
if (!a || !b) return false;
let aProps = Object.getOwnPropertyNames(a);
let bProps = Object.getOwnPropertyNames(b);

View File

@ -1,5 +1,4 @@
import type { App } from 'vue';
import { store } from '/@/store/index.ts';
import { store } from '/@/store/index';
import { judementSameArr } from '/@/utils/arrayOperation';
/**
@ -8,19 +7,19 @@ import { judementSameArr } from '/@/utils/arrayOperation';
* @directive 多个权限验证满足一个则显示v-auths="[xxx,xxx]"
* @directive 多个权限验证全部满足则显示v-auth-all="[xxx,xxx]"
*/
export function authDirective(app: App) {
export function authDirective(app) {
// 单个权限验证v-auth="xxx"
app.directive('auth', {
mounted(el, binding) {
if (!store.state.userInfos.userInfos.authBtnList.some((v: string) => v === binding.value)) el.parentNode.removeChild(el);
if (!store.state.userInfos.userInfos.authBtnList.some((v) => v === binding.value)) el.parentNode.removeChild(el);
},
});
// 多个权限验证满足一个则显示v-auths="[xxx,xxx]"
app.directive('auths', {
mounted(el, binding) {
let flag = false;
store.state.userInfos.userInfos.authBtnList.map((val: string) => {
binding.value.map((v: string) => {
store.state.userInfos.userInfos.authBtnList.map((val) => {
binding.value.map((v) => {
if (val === v) flag = true;
});
});

View File

@ -1,4 +1,4 @@
import { store } from '/@/store/index.ts';
import { store } from '/@/store/index';
import { judementSameArr } from '/@/utils/arrayOperation';
/**
@ -6,8 +6,8 @@ import { judementSameArr } from '/@/utils/arrayOperation';
* @param value 权限值
* @returns 有权限返回 `true`反之则反
*/
export function auth(value: string): boolean {
return store.state.userInfos.userInfos.authBtnList.some((v: string) => v === value);
export function auth(value) {
return store.state.userInfos.userInfos.authBtnList.some((v) => v === value);
}
/**
@ -15,10 +15,10 @@ export function auth(value: string): boolean {
* @param value 权限值
* @returns 有权限返回 `true`反之则反
*/
export function auths(value: Array<string>): boolean {
export function auths(value) {
let flag = false;
store.state.userInfos.userInfos.authBtnList.map((val: string) => {
value.map((v: string) => {
store.state.userInfos.userInfos.authBtnList.map((val) => {
value.map((v) => {
if (val === v) flag = true;
});
});
@ -30,6 +30,6 @@ export function auths(value: Array<string>): boolean {
* @param value 权限值
* @returns 有权限返回 `true`反之则反
*/
export function authAll(value: Array<string>): boolean {
export function authAll(value) {
return judementSameArr(value, store.state.userInfos.userInfos.authBtnList);
}

View File

@ -7,24 +7,24 @@ export default function () {
const { toClipboard } = useClipboard();
// 百分比格式化
const percentFormat = (row: any, column: number, cellValue: any) => {
const percentFormat = (row, column, cellValue) => {
return cellValue ? `${cellValue}%` : '-';
};
// 列表日期时间格式化
const dateFormatYMD = (row: any, column: number, cellValue: any) => {
const dateFormatYMD = (row, column, cellValue) => {
if (!cellValue) return '-';
return formatDate(new Date(cellValue), 'YYYY-mm-dd');
};
// 列表日期时间格式化
const dateFormatYMDHMS = (row: any, column: number, cellValue: any) => {
const dateFormatYMDHMS = (row, column, cellValue) => {
if (!cellValue) return '-';
return formatDate(new Date(cellValue), 'YYYY-mm-dd HH:MM:SS');
};
// 列表日期时间格式化
const dateFormatHMS = (row: any, column: number, cellValue: any) => {
const dateFormatHMS = (row, column, cellValue) => {
if (!cellValue) return '-';
let time = 0;
if (typeof row === 'number') time = row;
@ -33,17 +33,17 @@ export default function () {
};
// 小数格式化
const scaleFormat = (value: any = 0, scale: number = 4) => {
const scaleFormat = (value = 0, scale = 4) => {
return Number.parseFloat(value).toFixed(scale);
};
// 小数格式化
const scale2Format = (value: any = 0) => {
const scale2Format = (value = 0) => {
return Number.parseFloat(value).toFixed(2);
};
// 点击复制文本
const copyText = (text: string) => {
const copyText = (text) => {
return new Promise((resolve, reject) => {
try {
// 复制

View File

@ -1,23 +1,21 @@
import type { App } from 'vue';
/**
* 按钮波浪指令
* @directive 默认方式v-waves `<div v-waves></div>`
* @directive 参数方式v-waves=" |light|red|orange|purple|green|teal" `<div v-waves="'light'"></div>`
*/
export function wavesDirective(app: App) {
export function wavesDirective(app) {
app.directive('waves', {
mounted(el, binding) {
el.classList.add('waves-effect');
binding.value && el.classList.add(`waves-${binding.value}`);
function setConvertStyle(obj: { [key: string]: unknown }) {
let style: string = '';
function setConvertStyle(obj) {
let style = '';
for (let i in obj) {
if (obj.hasOwnProperty(i)) style += `${i}:${obj[i]};`;
}
return style;
}
function onCurrentClick(e: { [key: string]: unknown }) {
function onCurrentClick(e) {
let elDiv = document.createElement('div');
elDiv.classList.add('waves-ripple');
el.appendChild(elDiv);
@ -60,17 +58,17 @@ export function wavesDirective(app: App) {
* @link 注意https://github.com/element-plus/element-plus/issues/522
* @lick 参考https://blog.csdn.net/weixin_46391323/article/details/105228020?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-10&spm=1001.2101.3001.4242
*/
export function dragDirective(app: App) {
export function dragDirective(app) {
app.directive('drag', {
mounted(el, binding) {
if (!binding.value) return false;
const dragDom = document.querySelector(binding.value[0]) as HTMLElement;
const dragHeader = document.querySelector(binding.value[1]) as HTMLElement;
const dragDom = document.querySelector(binding.value[0]);
const dragHeader = document.querySelector(binding.value[1]);
dragHeader.onmouseover = () => (dragHeader.style.cursor = `move`);
function down(e: any, type: string) {
function down(e, type) {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = type === 'pc' ? e.clientX - dragHeader.offsetLeft : e.touches[0].clientX - dragHeader.offsetLeft;
const disY = type === 'pc' ? e.clientY - dragHeader.offsetTop : e.touches[0].clientY - dragHeader.offsetTop;
@ -92,8 +90,8 @@ export function dragDirective(app: App) {
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
// 获取到的值带px 正则匹配替换
let styL: any = getComputedStyle(dragDom).left;
let styT: any = getComputedStyle(dragDom).top;
let styL = getComputedStyle(dragDom).left;
let styT = getComputedStyle(dragDom).top;
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (styL.includes('%')) {
@ -116,7 +114,7 @@ export function dragDirective(app: App) {
};
}
function move(e: any, type: string, obj: any) {
function move(e, type, obj) {
let { disX, disY, minDragDomLeft, maxDragDomLeft, minDragDomTop, maxDragDomTop, styL, styT } = obj;
// 通过事件委托,计算移动的距离

View File

@ -1,4 +1,3 @@
import type { App } from 'vue';
import { authDirective } from '/@/utils/authDirective';
import { wavesDirective, dragDirective } from '/@/utils/customDirective';
@ -8,7 +7,7 @@ import { wavesDirective, dragDirective } from '/@/utils/customDirective';
* @methods wavesDirective 按钮波浪指令用法v-waves
* @methods dragDirective 自定义拖动指令用法v-drag
*/
export function directive(app: App) {
export function directive(app) {
// 用户权限指令
authDirective(app);
// 按钮波浪指令

View File

@ -9,11 +9,11 @@
* @description format 季度 + 星期 + 几周"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
* @returns 返回拼接后的时间字符串
*/
export function formatDate(date: Date, format: string): string {
export function formatDate(date, format) {
let we = date.getDay(); // 星期
let z = getWeek(date); // 周
let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
const opt: { [key: string]: string } = {
const opt = {
'Y+': date.getFullYear().toString(), // 年
'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始要+1)
'd+': date.getDate().toString(), // 日
@ -23,21 +23,21 @@ export function formatDate(date: Date, format: string): string {
'q+': qut, // 季度
};
// 中文数字 (星期)
const week: { [key: string]: string } = {
'0': '日',
'1': '一',
'2': '二',
'3': '三',
'4': '四',
'5': '五',
'6': '六',
const week = {
0: '日',
1: '一',
2: '二',
3: '三',
4: '四',
5: '五',
6: '六',
};
// 中文数字(季度)
const quarter: { [key: string]: string } = {
'1': '一',
'2': '二',
'3': '三',
'4': '四',
const quarter = {
1: '一',
2: '二',
3: '三',
4: '四',
};
if (/(W+)/.test(format))
format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
@ -56,7 +56,7 @@ export function formatDate(date: Date, format: string): string {
* @param dateTime 当前传入的日期值
* @returns 返回第几周数字值
*/
export function getWeek(dateTime: Date): number {
export function getWeek(dateTime) {
let temptTime = new Date(dateTime.getTime());
// 周几
let weekday = temptTime.getDay() || 7;
@ -83,11 +83,11 @@ export function getWeek(dateTime: Date): number {
* @description param 3 60 * 60* 24 * 1000 * 3
* @returns 返回拼接后的时间字符串
*/
export function formatPast(param: string | Date, format: string = 'YYYY-mm-dd'): string {
export function formatPast(param, format = 'YYYY-mm-dd') {
// 传入格式处理、存储转换值
let t: any, s: number;
let t, s;
// 获取js 时间戳
let time: number = new Date().getTime();
let time = new Date().getTime();
// 是否是对象
typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
// 当前时间戳 - 传入时间戳
@ -124,8 +124,8 @@ export function formatPast(param: string | Date, format: string = 'YYYY-mm-dd'):
* @description param 调用 `formatAxis(new Date())` 输出 `上午好`
* @returns 返回拼接后的时间字符串
*/
export function formatAxis(param: Date): string {
let hour: number = new Date(param).getHours();
export function formatAxis(param) {
let hour = new Date(param).getHours();
if (hour < 6) return '凌晨好';
else if (hour < 9) return '早上好';
else if (hour < 12) return '上午好';

View File

@ -5,7 +5,7 @@ import * as svg from '@element-plus/icons-vue';
const getAlicdnIconfont = () => {
return new Promise((resolve, reject) => {
nextTick(() => {
const styles: any = document.styleSheets;
const styles = document.styleSheets;
let sheetsList = [];
let sheetsIconList = [];
for (let i = 0; i < styles.length; i++) {
@ -32,7 +32,7 @@ const getAlicdnIconfont = () => {
const getElementPlusIconfont = () => {
return new Promise((resolve, reject) => {
nextTick(() => {
const icons = svg as any;
const icons = svg;
const sheetsIconList = [];
for (const i in icons) {
sheetsIconList.push(`element${icons[i].name}`);
@ -47,7 +47,7 @@ const getElementPlusIconfont = () => {
const getAwesomeIconfont = () => {
return new Promise((resolve, reject) => {
nextTick(() => {
const styles: any = document.styleSheets;
const styles = document.styleSheets;
let sheetsList = [];
let sheetsIconList = [];
for (let i = 0; i < styles.length; i++) {

View File

@ -18,7 +18,7 @@ export const NextLoading = {
},
// 创建 loading
start: () => {
const bodys: Element = document.body;
const bodys = document.body;
const div = document.createElement('div');
div.setAttribute('class', 'loading-next');
const htmls = `

View File

@ -1,5 +1,4 @@
import { nextTick } from 'vue';
import type { App } from 'vue';
import * as svg from '@element-plus/icons-vue';
import router from '/@/router/index';
import { store } from '/@/store/index';
@ -11,10 +10,9 @@ import SvgIcon from '/@/components/svgIcon/index.vue';
* @param app vue 实例
* @description 使用https://element-plus.gitee.io/zh-CN/component/icon.html
*/
export function elSvg(app: App) {
const icons = svg as any;
for (const i in icons) {
app.component(`element${icons[i].name}`, icons[i]);
export function elSvg(app) {
for (const i in svg) {
app.component(`element${svg[i].name}`, svg[i]);
}
app.component('SvgIcon', SvgIcon);
}
@ -26,8 +24,8 @@ export function elSvg(app: App) {
export function useTitle() {
nextTick(() => {
let webTitle = '';
let globalTitle: string = store.state.themeConfig.themeConfig.globalTitle;
webTitle = router.currentRoute.value.meta.title as any;
let globalTitle = store.state.themeConfig.themeConfig.globalTitle;
webTitle = router.currentRoute.value.meta.title;
document.title = `${webTitle} - ${globalTitle}` || globalTitle;
});
}
@ -38,9 +36,9 @@ export function useTitle() {
* @param arr 列表数据
* @description data-xxx 属性用于存储页面或应用程序的私有自定义数据
*/
export const lazyImg = (el: any, arr: any) => {
export const lazyImg = (el, arr) => {
const io = new IntersectionObserver((res) => {
res.forEach((v: any) => {
res.forEach((v) => {
if (v.isIntersecting) {
const { img, key } = v.target.dataset;
v.target.src = img;
@ -60,15 +58,15 @@ export const lazyImg = (el: any, arr: any) => {
* 全局组件大小
* @returns 返回 `window.localStorage` 中读取的缓存值 `globalComponentSize`
*/
export const globalComponentSize: string = Local.get('themeConfig')?.globalComponentSize || store.state.themeConfig.themeConfig?.globalComponentSize;
export const globalComponentSize = Local.get('themeConfig')?.globalComponentSize || store.state.themeConfig.themeConfig?.globalComponentSize;
/**
* 对象深克隆
* @param obj 源对象
* @returns 克隆后的对象
*/
export function deepClone(obj: any) {
let newObj: any;
export function deepClone(obj) {
let newObj;
try {
newObj = obj.push ? [] : {};
} catch (error) {
@ -109,17 +107,17 @@ export function isMobile() {
* @method isMobile 判断是否是移动端
*/
const other = {
elSvg: (app: App) => {
elSvg: (app) => {
elSvg(app);
},
useTitle: () => {
useTitle();
},
lazyImg: (el: any, arr: any) => {
lazyImg: (el, arr) => {
lazyImg(el, arr);
},
globalComponentSize,
deepClone: (obj: any) => {
deepClone: (obj) => {
deepClone(obj);
},
isMobile: () => {

View File

@ -4,7 +4,7 @@ import { Session } from '/@/utils/storage';
// 配置新建一个 axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_API_URL as any,
baseURL: import.meta.env.VITE_API_URL,
timeout: 50000,
headers: { 'Content-Type': 'application/json' },
});

View File

@ -1,10 +1,7 @@
// 字体图标 url
const cssCdnUrlList: Array<string> = [
'//at.alicdn.com/t/font_2298093_y6u00apwst.css',
'//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
];
const cssCdnUrlList = ['//at.alicdn.com/t/font_2298093_y6u00apwst.css', '//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css'];
// 第三方 js url
const jsCdnUrlList: Array<string> = [];
const jsCdnUrlList = [];
// 动态批量设置字体图标
export function setCssCdn() {

View File

@ -7,16 +7,16 @@
*/
export const Local = {
// 设置永久缓存
set(key: string, val: any) {
set(key, val) {
window.localStorage.setItem(key, JSON.stringify(val));
},
// 获取永久缓存
get(key: string) {
let json: any = window.localStorage.getItem(key);
get(key) {
let json = window.localStorage.getItem(key);
return JSON.parse(json);
},
// 移除永久缓存
remove(key: string) {
remove(key) {
window.localStorage.removeItem(key);
},
// 移除全部永久缓存
@ -34,16 +34,16 @@ export const Local = {
*/
export const Session = {
// 设置临时缓存
set(key: string, val: any) {
set(key, val) {
window.sessionStorage.setItem(key, JSON.stringify(val));
},
// 获取临时缓存
get(key: string) {
let json: any = window.sessionStorage.getItem(key);
get(key) {
let json = window.sessionStorage.getItem(key);
return JSON.parse(json);
},
// 移除临时缓存
remove(key: string) {
remove(key) {
window.sessionStorage.removeItem(key);
},
// 移除全部临时缓存

View File

@ -5,8 +5,8 @@ import { ElMessage } from 'element-plus';
* @param str 颜色值字符串
* @returns 返回处理后的颜色值
*/
export function hexToRgb(str: any) {
let hexs: any = '';
export function hexToRgb(str) {
let hexs = '';
let reg = /^\#?[0-9A-Fa-f]{6}$/;
if (!reg.test(str)) return ElMessage.warning('输入错误的hex');
str = str.replace('#', '');
@ -22,7 +22,7 @@ export function hexToRgb(str: any) {
* @param b 代表蓝色
* @returns 返回处理后的颜色值
*/
export function rgbToHex(r: any, g: any, b: any) {
export function rgbToHex(r, g, b) {
let reg = /^\d{1,3}$/;
if (!reg.test(r) || !reg.test(g) || !reg.test(b)) return ElMessage.warning('输入错误的rgb颜色值');
let hexs = [r.toString(16), g.toString(16), b.toString(16)];
@ -36,7 +36,7 @@ export function rgbToHex(r: any, g: any, b: any) {
* @param level 加深的程度限0-1之间
* @returns 返回处理后的颜色值
*/
export function getDarkColor(color: any, level: number) {
export function getDarkColor(color, level) {
let reg = /^\#?[0-9A-Fa-f]{6}$/;
if (!reg.test(color)) return ElMessage.warning('输入错误的hex颜色值');
let rgb = hexToRgb(color);
@ -50,7 +50,7 @@ export function getDarkColor(color: any, level: number) {
* @param level 加深的程度限0-1之间
* @returns 返回处理后的颜色值
*/
export function getLightColor(color: any, level: number) {
export function getLightColor(color, level) {
let reg = /^\#?[0-9A-Fa-f]{6}$/;
if (!reg.test(color)) return ElMessage.warning('输入错误的hex颜色值');
let rgb = hexToRgb(color);

View File

@ -9,7 +9,7 @@
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyNumberPercentage(val: string): string {
export function verifyNumberPercentage(val) {
// 匹配空格
let v = val.replace(/(^\s*)|(\s*$)/g, '');
// 只能是数字和小数点,不能是其他输入
@ -27,7 +27,7 @@ export function verifyNumberPercentage(val: string): string {
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyNumberPercentageFloat(val: string): string {
export function verifyNumberPercentageFloat(val) {
let v = verifyNumberIntegerAndFloat(val);
// 数字超过100赋值成最大值100
v = v.replace(/^[1-9]\d\d{1,3}$/, '100');
@ -42,7 +42,7 @@ export function verifyNumberPercentageFloat(val: string): string {
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyNumberIntegerAndFloat(val: string) {
export function verifyNumberIntegerAndFloat(val) {
// 匹配空格
let v = val.replace(/(^\s*)|(\s*$)/g, '');
// 只能是数字和小数点,不能是其他输入
@ -64,7 +64,7 @@ export function verifyNumberIntegerAndFloat(val: string) {
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifiyNumberInteger(val: string) {
export function verifiyNumberInteger(val) {
// 匹配空格
let v = val.replace(/(^\s*)|(\s*$)/g, '');
// 去掉 '.' , 防止贴贴的时候出现问题 如 0.1.12.12
@ -84,7 +84,7 @@ export function verifiyNumberInteger(val: string) {
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyCnAndSpace(val: string) {
export function verifyCnAndSpace(val) {
// 匹配中文与空格
let v = val.replace(/[\u4e00-\u9fa5\s]+/g, '');
// 匹配空格
@ -98,7 +98,7 @@ export function verifyCnAndSpace(val: string) {
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyEnAndSpace(val: string) {
export function verifyEnAndSpace(val) {
// 匹配英文与空格
let v = val.replace(/[a-zA-Z]+/g, '');
// 匹配空格
@ -112,7 +112,7 @@ export function verifyEnAndSpace(val: string) {
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyAndSpace(val: string) {
export function verifyAndSpace(val) {
// 匹配空格
let v = val.replace(/(^\s*)|(\s*$)/g, '');
// 返回结果
@ -124,9 +124,9 @@ export function verifyAndSpace(val: string) {
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyNumberComma(val: string) {
export function verifyNumberComma(val) {
// 调用小数或整数(不可以负数)方法
let v: any = verifyNumberIntegerAndFloat(val);
let v = verifyNumberIntegerAndFloat(val);
// 字符串转成数组
v = v.toString().split('.');
// \B 匹配非单词边界,两边都是单词字符或者两边都是非单词字符
@ -144,7 +144,7 @@ export function verifyNumberComma(val: string) {
* @param color 搜索到时字体高亮颜色
* @returns 返回处理后的字符串
*/
export function verifyTextColor(val: string, text = '', color = 'red') {
export function verifyTextColor(val, text = '', color = 'red') {
// 返回内容,添加颜色
let v = text.replace(new RegExp(val, 'gi'), `<span style='color: ${color}'>${val}</span>`);
// 返回结果
@ -157,7 +157,7 @@ export function verifyTextColor(val: string, text = '', color = 'red') {
* @param unit 默认仟佰拾亿仟佰拾万仟佰拾元角分
* @returns 返回处理后的字符串
*/
export function verifyNumberCnUppercase(val: any, unit = '仟佰拾亿仟佰拾万仟佰拾元角分', v = '') {
export function verifyNumberCnUppercase(val, unit = '仟佰拾亿仟佰拾万仟佰拾元角分', v = '') {
// 当前内容字符串添加 2个0为什么??
val += '00';
// 返回某个指定的字符串值在字符串中首次出现的位置,没有出现,则该方法返回 -1
@ -188,7 +188,7 @@ export function verifyNumberCnUppercase(val: any, unit = '仟佰拾亿仟佰拾
* @param val 当前值字符串
* @returns 返回 true: 手机号码正确
*/
export function verifyPhone(val: string) {
export function verifyPhone(val) {
// false: 手机号码不正确
if (!/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\d{8}$/.test(val)) return false;
// true: 手机号码正确
@ -200,7 +200,7 @@ export function verifyPhone(val: string) {
* @param val 当前值字符串
* @returns 返回 true: 国内电话号码正确
*/
export function verifyTelPhone(val: string) {
export function verifyTelPhone(val) {
// false: 国内电话号码不正确
if (!/\d{3}-\d{8}|\d{4}-\d{7}/.test(val)) return false;
// true: 国内电话号码正确
@ -212,7 +212,7 @@ export function verifyTelPhone(val: string) {
* @param val 当前值字符串
* @returns 返回 true: 登录账号正确
*/
export function verifyAccount(val: string) {
export function verifyAccount(val) {
// false: 登录账号不正确
if (!/^[a-zA-Z][a-zA-Z0-9_]{4,15}$/.test(val)) return false;
// true: 登录账号正确
@ -224,7 +224,7 @@ export function verifyAccount(val: string) {
* @param val 当前值字符串
* @returns 返回 true: 密码正确
*/
export function verifyPassword(val: string) {
export function verifyPassword(val) {
// false: 密码不正确
if (!/^[a-zA-Z]\w{5,15}$/.test(val)) return false;
// true: 密码正确
@ -236,7 +236,7 @@ export function verifyPassword(val: string) {
* @param val 当前值字符串
* @returns 返回 true: 强密码正确
*/
export function verifyPasswordPowerful(val: string) {
export function verifyPasswordPowerful(val) {
// false: 强密码不正确
if (!/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\.*]+$)(?![\d!@#$%^&\.*]+$)[a-zA-Z\d!@#$%^&\.*]{6,16}$/.test(val))
return false;
@ -252,7 +252,7 @@ export function verifyPasswordPowerful(val: string) {
* @description 字母+数字+特殊字符
* @returns 返回处理后的字符串
*/
export function verifyPasswordStrength(val: string) {
export function verifyPasswordStrength(val) {
let v = '';
// 弱:纯数字,纯字母,纯特殊字符
if (/^(?:\d+|[a-zA-Z]+|[!@#$%^&\.*]+){6,16}$/.test(val)) v = '弱';
@ -270,7 +270,7 @@ export function verifyPasswordStrength(val: string) {
* @param val 当前值字符串
* @returns 返回 true: IP地址正确
*/
export function verifyIPAddress(val: string) {
export function verifyIPAddress(val) {
// false: IP地址不正确
if (
!/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(
@ -287,7 +287,7 @@ export function verifyIPAddress(val: string) {
* @param val 当前值字符串
* @returns 返回 true: 邮箱正确
*/
export function verifyEmail(val: string) {
export function verifyEmail(val) {
// false: 邮箱不正确
if (
!/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
@ -304,7 +304,7 @@ export function verifyEmail(val: string) {
* @param val 当前值字符串
* @returns 返回 true: 身份证正确
*/
export function verifyIdCard(val: string) {
export function verifyIdCard(val) {
// false: 身份证不正确
if (!/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(val)) return false;
// true: 身份证正确
@ -316,7 +316,7 @@ export function verifyIdCard(val: string) {
* @param val 当前值字符串
* @returns 返回 true: 姓名正确
*/
export function verifyFullName(val: string) {
export function verifyFullName(val) {
// false: 姓名不正确
if (!/^[\u4e00-\u9fa5]{1,6}(·[\u4e00-\u9fa5]{1,6}){0,2}$/.test(val)) return false;
// true: 姓名正确
@ -328,7 +328,7 @@ export function verifyFullName(val: string) {
* @param val 当前值字符串
* @returns 返回 true: 邮政编码正确
*/
export function verifyPostalCode(val: string) {
export function verifyPostalCode(val) {
// false: 邮政编码不正确
if (!/^[1-9][0-9]{5}$/.test(val)) return false;
// true: 邮政编码正确
@ -340,7 +340,7 @@ export function verifyPostalCode(val: string) {
* @param val 当前值字符串
* @returns 返回 true: url 正确
*/
export function verifyUrl(val: string) {
export function verifyUrl(val) {
// false: url不正确
if (
!/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
@ -357,7 +357,7 @@ export function verifyUrl(val: string) {
* @param val 当前值字符串
* @returns 返回 true车牌号正确
*/
export function verifyCarNum(val: string) {
export function verifyCarNum(val) {
// false: 车牌号不正确
if (
!/^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$/.test(

View File

@ -1,26 +1,19 @@
import dotenv from 'dotenv';
// 定义接口类型声明
export interface ViteEnv {
VITE_PORT: number;
VITE_OPEN: boolean;
VITE_PUBLIC_PATH: string;
}
/**
* vite 打包相关
* @link 参考https://cn.vitejs.dev/guide/env-and-mode.html
* @returns 返回 `VITE_xxx` 环境变量和模式信息
*/
export function loadEnv(): ViteEnv {
export function loadEnv() {
const env = process.env.NODE_ENV;
const ret: any = {};
const ret = {};
const envList = [`.env.${env}.local`, `.env.${env}`, '.env.local', '.env'];
envList.forEach((e) => {
dotenv.config({ path: e });
});
for (const envName of Object.keys(process.env)) {
let realName = (process.env as any)[envName].replace(/\\n/g, '\n');
let realName = process.env[envName].replace(/\\n/g, '\n');
realName = realName === 'true' ? true : realName === 'false' ? false : realName;
if (envName === 'VITE_PORT') realName = Number(realName);
if (envName === 'VITE_OPEN') realName = Boolean(realName);

View File

@ -1,11 +1,11 @@
// 页面添加水印效果
const setWatermark = (str: string) => {
const setWatermark = (str) => {
const id = '1.23452384164.123412416';
if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id) as any);
if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id));
const can = document.createElement('canvas');
can.width = 200;
can.height = 130;
const cans: any = can.getContext('2d');
const cans = can.getContext('2d');
cans.rotate((-20 * Math.PI) / 180);
cans.font = '12px Vedana';
cans.fillStyle = 'rgba(200, 200, 200, 0.30)';
@ -32,14 +32,14 @@ const setWatermark = (str: string) => {
*/
const watermark = {
// 设置水印
set: (str: string) => {
set: (str) => {
let id = setWatermark(str);
if (document.getElementById(id) === null) id = setWatermark(str);
},
// 删除水印
del: () => {
let id = '1.23452384164.123412416';
if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id) as any);
if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id));
},
};

View File

@ -18,21 +18,13 @@
</div>
</template>
<script lang="ts">
import { useRouter } from 'vue-router';
<script setup name="401">
import { Session } from '/@/utils/storage';
export default {
name: '401',
setup() {
const router = useRouter();
const onSetAuth = () => {
Session.clear();
router.push('/login');
};
return {
onSetAuth,
};
},
const router = useRouter();
const onSetAuth = () => {
Session.clear();
router.push('/login');
};
</script>

View File

@ -18,19 +18,10 @@
</div>
</template>
<script lang="ts">
import { useRouter } from 'vue-router';
export default {
name: '404',
setup() {
const router = useRouter();
const onGoHome = () => {
router.push('/');
};
return {
onGoHome,
};
},
<script setup name="404">
const router = useRouter();
const onGoHome = () => {
router.push('/');
};
</script>

View File

@ -16,7 +16,7 @@
</div>
</div>
</el-col>
<el-col :sm="6" class="mb15" v-for="(v, k) in topCardItemList" :key="k">
<el-col :sm="6" class="mb15" v-for="(v, k) in state.topCardItemList" :key="k">
<div class="home-card-item home-card-item-box" :style="{ background: v.color }">
<div class="home-card-item-flex">
<div class="home-card-item-title pb3">{{ v.title }}</div>
@ -38,7 +38,7 @@
<el-card shadow="hover" header="环境监测">
<div class="home-monitor">
<div class="flex-warp">
<div class="flex-warp-item" v-for="(v, k) in environmentList" :key="k">
<div class="flex-warp-item" v-for="(v, k) in state.environmentList" :key="k">
<div class="flex-warp-item-box">
<i :class="v.icon" :style="{ color: v.iconColor }"></i>
<span class="pl5">{{ v.label }}</span>
@ -53,7 +53,7 @@
<el-row :gutter="15">
<el-col :xs="24" :sm="14" :md="14" :lg="16" :xl="16" class="home-warning-media">
<el-card shadow="hover" header="预警信息" class="home-warning-card">
<el-table :data="tableData.data" style="width: 100%" stripe>
<el-table :data="state.tableData.data" style="width: 100%" stripe>
<el-table-column prop="date" label="时间"></el-table-column>
<el-table-column prop="name" label="实验室名称"></el-table-column>
<el-table-column prop="address" label="报警内容"></el-table-column>
@ -64,7 +64,7 @@
<el-card shadow="hover" header="动态信息">
<div class="home-dynamic">
<el-scrollbar>
<div class="home-dynamic-item" v-for="(v, k) in activitiesList" :key="k">
<div class="home-dynamic-item" v-for="(v, k) in state.activitiesList" :key="k">
<div class="home-dynamic-item-left">
<div class="home-dynamic-item-left-time1 mb5">{{ v.time1 }}</div>
<div class="home-dynamic-item-left-time2">{{ v.time2 }}</div>
@ -95,224 +95,213 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, nextTick, computed, getCurrentInstance, watch, onActivated } from 'vue';
<script setup name="home">
import * as echarts from 'echarts';
import { formatAxis } from '/@/utils/formatTime';
import { useStore } from '/@/store/index';
import { topCardItemList, environmentList, activitiesList } from './mock';
export default {
name: 'home',
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const state = reactive({
topCardItemList,
environmentList,
activitiesList,
tableData: {
data: [
{
date: '2016-05-02',
name: '1号实验室',
address: '烟感2.1%OBS/M',
},
{
date: '2016-05-04',
name: '2号实验室',
address: '温度30℃',
},
{
date: '2016-05-01',
name: '3号实验室',
address: '湿度57%RH',
},
],
const { proxy } = getCurrentInstance();
const store = useStore();
const state = reactive({
topCardItemList,
environmentList,
activitiesList,
tableData: {
data: [
{
date: '2016-05-02',
name: '1号实验室',
address: '烟感2.1%OBS/M',
},
myCharts: [],
});
// 获取用户信息 vuex
const getUserInfos = computed(() => {
return store.state.userInfos.userInfos;
});
// 当前时间提示语
const currentTime = computed(() => {
return formatAxis(new Date());
});
// 商品销售情
const initHomeLaboratory = () => {
const myChart = echarts.init(proxy.$refs.homeLaboratoryRef);
const option = {
grid: {
top: 50,
right: 20,
bottom: 30,
left: 30,
},
tooltip: {
trigger: 'axis',
},
legend: {
data: ['预购队列', '最新成交价'],
right: 13,
},
color: [
'#63caff',
'#49beff',
'#03387a',
'#03387a',
'#03387a',
'#6c93ee',
'#a9abff',
'#f7a23f',
'#27bae7',
'#ff6d9d',
'#cb79ff',
'#f95b5a',
'#ccaf27',
'#38b99c',
'#93d0ff',
'#bd74e0',
'#fd77da',
'#dea700',
],
xAxis: {
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
},
yAxis: [
{
type: 'value',
name: '价格',
},
],
series: [
{
name: '预购队列',
type: 'bar',
data: [200, 85, 112, 275, 305, 415, 441, 405, 275, 305, 415, 441],
itemStyle: {
barBorderRadius: [4, 4, 0, 0],
color: {
x: 0,
y: 0,
x2: 0,
y2: 1,
type: 'linear',
global: false,
colorStops: [
{
offset: 0,
color: '#0b9eff',
},
{
offset: 1,
color: '#63caff',
},
],
},
},
},
{
name: '最新成交价',
type: 'line',
data: [50, 85, 22, 155, 170, 25, 224, 245, 285, 300, 415, 641],
itemStyle: {
color: '#febb50',
},
},
],
};
myChart.setOption(option);
state.myCharts.push(myChart);
};
// 履约超时预警
const initHomeOvertime = () => {
const myChart = echarts.init(proxy.$refs.homeOvertimeRef);
const option = {
grid: {
top: 50,
right: 20,
bottom: 30,
left: 30,
},
tooltip: {
trigger: 'axis',
},
legend: {
data: ['订单数量', '超时数量', '在线数量', '预警数量'],
right: 13,
},
xAxis: {
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
},
yAxis: [
{
type: 'value',
name: '数量',
},
],
series: [
{
name: '订单数量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20, 11, 13, 10, 9, 17, 19],
},
{
name: '超时数量',
type: 'bar',
data: [15, 12, 26, 15, 11, 16, 31, 13, 5, 16, 13, 15],
},
{
name: '在线数量',
type: 'line',
data: [15, 20, 16, 20, 30, 8, 16, 19, 12, 18, 19, 14],
},
{
name: '预警数量',
type: 'line',
data: [10, 10, 13, 12, 15, 18, 19, 10, 12, 15, 11, 17],
},
],
};
myChart.setOption(option);
state.myCharts.push(myChart);
};
// 批量设置 echarts resize
const initEchartsResizeFun = () => {
nextTick(() => {
for (let i = 0; i < state.myCharts.length; i++) {
state.myCharts[i].resize();
}
});
};
// 批量设置 echarts resize
const initEchartsResize = () => {
window.addEventListener('resize', initEchartsResizeFun);
};
// 页面加载时
onMounted(() => {
initHomeLaboratory();
initHomeOvertime();
initEchartsResize();
});
// 由于页面缓存原因keep-alive
onActivated(() => {
initEchartsResizeFun();
});
// 监听 vuex 中的 tagsview 开启全屏变化,重新 resize 图表,防止不出现/大小不变等
watch(
() => store.state.tagsViewRoutes.isTagsViewCurrenFull,
() => {
initEchartsResizeFun();
}
);
return {
getUserInfos,
currentTime,
...toRefs(state),
};
{
date: '2016-05-04',
name: '2号实验室',
address: '温度30℃',
},
{
date: '2016-05-01',
name: '3号实验室',
address: '湿度57%RH',
},
],
},
myCharts: [],
});
// 获取用户信息 vuex
const getUserInfos = computed(() => {
return store.state.userInfos.userInfos;
});
// 当前时间提示语
const currentTime = computed(() => {
return formatAxis(new Date());
});
// 商品销售情
const initHomeLaboratory = () => {
const myChart = echarts.init(proxy.$refs.homeLaboratoryRef);
const option = {
grid: {
top: 50,
right: 20,
bottom: 30,
left: 30,
},
tooltip: {
trigger: 'axis',
},
legend: {
data: ['预购队列', '最新成交价'],
right: 13,
},
color: [
'#63caff',
'#49beff',
'#03387a',
'#03387a',
'#03387a',
'#6c93ee',
'#a9abff',
'#f7a23f',
'#27bae7',
'#ff6d9d',
'#cb79ff',
'#f95b5a',
'#ccaf27',
'#38b99c',
'#93d0ff',
'#bd74e0',
'#fd77da',
'#dea700',
],
xAxis: {
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
},
yAxis: [
{
type: 'value',
name: '价格',
},
],
series: [
{
name: '预购队列',
type: 'bar',
data: [200, 85, 112, 275, 305, 415, 441, 405, 275, 305, 415, 441],
itemStyle: {
barBorderRadius: [4, 4, 0, 0],
color: {
x: 0,
y: 0,
x2: 0,
y2: 1,
type: 'linear',
global: false,
colorStops: [
{
offset: 0,
color: '#0b9eff',
},
{
offset: 1,
color: '#63caff',
},
],
},
},
},
{
name: '最新成交价',
type: 'line',
data: [50, 85, 22, 155, 170, 25, 224, 245, 285, 300, 415, 641],
itemStyle: {
color: '#febb50',
},
},
],
};
myChart.setOption(option);
state.myCharts.push(myChart);
};
// 履约超时预警
const initHomeOvertime = () => {
const myChart = echarts.init(proxy.$refs.homeOvertimeRef);
const option = {
grid: {
top: 50,
right: 20,
bottom: 30,
left: 30,
},
tooltip: {
trigger: 'axis',
},
legend: {
data: ['订单数量', '超时数量', '在线数量', '预警数量'],
right: 13,
},
xAxis: {
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
},
yAxis: [
{
type: 'value',
name: '数量',
},
],
series: [
{
name: '订单数量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20, 11, 13, 10, 9, 17, 19],
},
{
name: '超时数量',
type: 'bar',
data: [15, 12, 26, 15, 11, 16, 31, 13, 5, 16, 13, 15],
},
{
name: '在线数量',
type: 'line',
data: [15, 20, 16, 20, 30, 8, 16, 19, 12, 18, 19, 14],
},
{
name: '预警数量',
type: 'line',
data: [10, 10, 13, 12, 15, 18, 19, 10, 12, 15, 11, 17],
},
],
};
myChart.setOption(option);
state.myCharts.push(myChart);
};
// 批量设置 echarts resize
const initEchartsResizeFun = () => {
nextTick(() => {
for (let i = 0; i < state.myCharts.length; i++) {
state.myCharts[i].resize();
}
});
};
// 批量设置 echarts resize
const initEchartsResize = () => {
window.addEventListener('resize', initEchartsResizeFun);
};
// 页面加载时
onMounted(() => {
initHomeLaboratory();
initHomeOvertime();
initEchartsResize();
});
// 由于页面缓存原因keep-alive
onActivated(() => {
initEchartsResizeFun();
});
// 监听 vuex 中的 tagsview 开启全屏变化,重新 resize 图表,防止不出现/大小不变等
watch(
() => store.state.tagsViewRoutes.isTagsViewCurrenFull,
() => {
initEchartsResizeFun();
}
);
</script>
<style scoped lang="scss">

View File

@ -1,31 +1,31 @@
<template>
<el-form class="login-content-form">
<el-form-item>
<el-input type="text" placeholder="用户名 admin 或不输均为 test" v-model="ruleForm.userName" clearable autocomplete="off">
<el-form-item class="login-animation-one">
<el-input type="text" placeholder="用户名 admin 或不输均为 test" v-model="state.ruleForm.userName" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><elementUser /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-input :type="isShowPassword ? 'text' : 'password'" placeholder="密码123456" v-model="ruleForm.password" autocomplete="off">
<el-form-item class="login-animation-two">
<el-input :type="state.isShowPassword ? 'text' : 'password'" placeholder="密码123456" v-model="state.ruleForm.password" autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><elementUnlock /></el-icon>
</template>
<template #suffix>
<i
class="iconfont el-input__icon login-content-password"
:class="isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
@click="isShowPassword = !isShowPassword"
:class="state.isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
@click="state.isShowPassword = !state.isShowPassword"
>
</i>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-form-item class="login-animation-three">
<el-row :gutter="15">
<el-col :span="16">
<el-input type="text" maxlength="4" placeholder="请输入验证码" v-model="ruleForm.code" clearable autocomplete="off">
<el-input type="text" maxlength="4" placeholder="请输入验证码" v-model="state.ruleForm.code" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><elementPosition /></el-icon>
</template>
@ -38,132 +38,150 @@
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<el-button type="primary" class="login-content-submit" round @click="onSignIn" :loading="loading.signIn">
<el-form-item class="login-animation-four">
<el-button type="primary" class="login-content-submit" round @click="onSignIn" :loading="state.loading.signIn">
<span> </span>
</el-button>
</el-form-item>
<el-form-item class="login-animation-five">
<el-button type="text" size="small">第三方登录</el-button>
<el-button type="text" size="small">友情链接</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, computed, getCurrentInstance } from 'vue';
import { useRoute, useRouter } from 'vue-router';
<script setup name="loginAccount">
import { ElMessage } from 'element-plus';
import { initFrontEndControlRoutes } from '/@/router/frontEnd';
import { initBackEndControlRoutes } from '/@/router/backEnd';
import { useStore } from '/@/store/index';
import { Session } from '/@/utils/storage';
import { formatAxis } from '/@/utils/formatTime';
export default defineComponent({
name: 'login',
setup() {
const { proxy } = getCurrentInstance() as any;
const store = useStore();
const route = useRoute();
const router = useRouter();
const state = reactive({
isShowPassword: false,
ruleForm: {
userName: 'admin',
password: '123456',
code: '1234',
},
loading: {
signIn: false,
},
});
// 时间获取
const currentTime = computed(() => {
return formatAxis(new Date());
});
// 登录
const onSignIn = async () => {
// 模拟数据
state.loading.signIn = true;
let defaultRoles: Array<string> = [];
let defaultAuthBtnList: Array<string> = [];
// admin 页面权限标识,对应路由 meta.roles用于控制路由的显示/隐藏
let adminRoles: Array<string> = ['admin'];
// admin 按钮权限标识
let adminAuthBtnList: Array<string> = ['btn.add', 'btn.del', 'btn.edit', 'btn.link'];
// test 页面权限标识,对应路由 meta.roles用于控制路由的显示/隐藏
let testAuthPageList: Array<string> = ['common'];
// test 按钮权限标识
let testAuthBtnList: Array<string> = ['btn.add', 'btn.link'];
// 不同用户模拟不同的用户权限
if (state.ruleForm.userName === 'admin') {
defaultRoles = adminRoles;
defaultAuthBtnList = adminAuthBtnList;
} else {
defaultRoles = testAuthPageList;
defaultAuthBtnList = testAuthBtnList;
}
// 用户信息模拟数据
const userInfos = {
userName: state.ruleForm.userName,
photo:
state.ruleForm.userName === 'admin'
? 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&fm=26&gp=0.jpg'
: 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=317673774,2961727727&fm=26&gp=0.jpg',
time: new Date().getTime(),
roles: defaultRoles,
authBtnList: defaultAuthBtnList,
};
// 存储 token 到浏览器缓存
Session.set('token', Math.random().toString(36).substr(0));
// 存储用户信息到浏览器缓存
Session.set('userInfo', userInfos);
// 1、请注意执行顺序(存储用户信息到vuex)
store.dispatch('userInfos/setUserInfos', userInfos);
if (!store.state.themeConfig.themeConfig.isRequestRoutes) {
// 前端控制路由2、请注意执行顺序
await initFrontEndControlRoutes();
signInSuccess();
} else {
// 模拟后端控制路由isRequestRoutes 为 true则开启后端控制路由
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
await initBackEndControlRoutes();
// 执行完 initBackEndControlRoutes再执行 signInSuccess
signInSuccess();
}
};
// 登录成功后的跳转
const signInSuccess = () => {
// 初始化登录成功时间问候语
let currentTimeInfo = currentTime.value;
// 登录成功,跳到转首页
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
if (route.query?.redirect) {
router.push({
path: route.query?.redirect,
query: Object.keys(route.query?.params).length > 0 ? JSON.parse(route.query?.params) : '',
});
} else {
router.push('/');
}
// 登录成功提示
setTimeout(() => {
// 关闭 loading
state.loading.signIn = true;
ElMessage.success(`${currentTimeInfo},欢迎回来!`);
// 修复防止退出登录再进入界面时,需要刷新样式才生效的问题,初始化布局样式等(登录的时候触发,目前方案)
proxy.mittBus.emit('onSignInClick');
}, 300);
};
return {
currentTime,
onSignIn,
...toRefs(state),
};
const { proxy } = getCurrentInstance();
const store = useStore();
const route = useRoute();
const router = useRouter();
const state = reactive({
isShowPassword: false,
ruleForm: {
userName: 'admin',
password: '123456',
code: '1234',
},
loading: {
signIn: false,
},
});
// 时间获取
const currentTime = computed(() => {
return formatAxis(new Date());
});
// 登录
const onSignIn = async () => {
// 模拟数据
state.loading.signIn = true;
let defaultRoles = [];
let defaultAuthBtnList = [];
// admin 页面权限标识,对应路由 meta.roles用于控制路由的显示/隐藏
let adminRoles = ['admin'];
// admin 按钮权限标识
let adminAuthBtnList = ['btn.add', 'btn.del', 'btn.edit', 'btn.link'];
// test 页面权限标识,对应路由 meta.roles用于控制路由的显示/隐藏
let testAuthPageList = ['common'];
// test 按钮权限标识
let testAuthBtnList = ['btn.add', 'btn.link'];
// 不同用户模拟不同的用户权限
if (state.ruleForm.userName === 'admin') {
defaultRoles = adminRoles;
defaultAuthBtnList = adminAuthBtnList;
} else {
defaultRoles = testAuthPageList;
defaultAuthBtnList = testAuthBtnList;
}
// 用户信息模拟数据
const userInfos = {
userName: state.ruleForm.userName,
photo:
state.ruleForm.userName === 'admin'
? 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&fm=26&gp=0.jpg'
: 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=317673774,2961727727&fm=26&gp=0.jpg',
time: new Date().getTime(),
roles: defaultRoles,
authBtnList: defaultAuthBtnList,
};
// 存储 token 到浏览器缓存
Session.set('token', Math.random().toString(36).substr(0));
// 存储用户信息到浏览器缓存
Session.set('userInfo', userInfos);
// 1、请注意执行顺序(存储用户信息到vuex)
store.dispatch('userInfos/setUserInfos', userInfos);
if (!store.state.themeConfig.themeConfig.isRequestRoutes) {
// 前端控制路由2、请注意执行顺序
await initFrontEndControlRoutes();
signInSuccess();
} else {
// 模拟后端控制路由isRequestRoutes 为 true则开启后端控制路由
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
await initBackEndControlRoutes();
// 执行完 initBackEndControlRoutes再执行 signInSuccess
signInSuccess();
}
};
// 登录成功后的跳转
const signInSuccess = () => {
// 初始化登录成功时间问候语
let currentTimeInfo = currentTime.value;
// 登录成功,跳到转首页
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
if (route.query?.redirect) {
router.push({
path: route.query?.redirect,
query: Object.keys(route.query?.params).length > 0 ? JSON.parse(route.query?.params) : '',
});
} else {
router.push('/');
}
// 登录成功提示
setTimeout(() => {
// 关闭 loading
state.loading.signIn = true;
ElMessage.success(`${currentTimeInfo},欢迎回来!`);
// 修复防止退出登录再进入界面时,需要刷新样式才生效的问题,初始化布局样式等(登录的时候触发,目前方案)
proxy.mittBus.emit('onSignInClick');
}, 300);
};
</script>
<style scoped lang="scss">
.login-content-form {
margin-top: 20px;
.login-animation-one,
.login-animation-two,
.login-animation-three,
.login-animation-four,
.login-animation-five {
opacity: 0;
animation-name: error-num;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
.login-animation-one {
animation-delay: 0.1s;
}
.login-animation-two {
animation-delay: 0.2s;
}
.login-animation-three {
animation-delay: 0.3s;
}
.login-animation-four {
animation-delay: 0.4s;
margin-bottom: 5px;
}
.login-animation-five {
animation-delay: 0.5s;
}
.login-content-password {
display: inline-block;
width: 25px;

View File

@ -1,16 +1,16 @@
<template>
<el-form class="login-content-form">
<el-form-item>
<el-input type="text" placeholder="请输入手机号" v-model="ruleForm.userName" clearable autocomplete="off">
<el-form-item class="login-animation-one">
<el-input type="text" placeholder="请输入手机号" v-model="state.ruleForm.userName" clearable autocomplete="off">
<template #prefix>
<i class="iconfont icon-dianhua el-input__icon"></i>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-form-item class="login-animation-two">
<el-row :gutter="15">
<el-col :span="16">
<el-input type="text" maxlength="4" placeholder="请输入验证码" v-model="ruleForm.code" clearable autocomplete="off">
<el-input type="text" maxlength="4" placeholder="请输入验证码" v-model="state.ruleForm.code" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><elementPosition /></el-icon>
</template>
@ -21,28 +21,23 @@
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<el-form-item class="login-animation-three">
<el-button type="primary" class="login-content-submit" round>
<span> </span>
</el-button>
</el-form-item>
<el-form-item class="login-animation-four">
<el-button type="text" size="small">第三方登录</el-button>
<el-button type="text" size="small">友情链接</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent } from 'vue';
export default defineComponent({
name: 'login',
setup() {
const state = reactive({
ruleForm: {
userName: '',
code: '',
},
});
return {
...toRefs(state),
};
<script setup name="loginMobile">
const state = reactive({
ruleForm: {
userName: '',
code: '',
},
});
</script>
@ -50,6 +45,28 @@ export default defineComponent({
<style scoped lang="scss">
.login-content-form {
margin-top: 20px;
.login-animation-one,
.login-animation-two,
.login-animation-three,
.login-animation-four {
opacity: 0;
animation-name: error-num;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
.login-animation-one {
animation-delay: 0.1s;
}
.login-animation-two {
animation-delay: 0.2s;
}
.login-animation-three {
animation-delay: 0.3s;
margin-bottom: 5px;
}
.login-animation-four {
animation-delay: 0.4s;
}
.login-content-code {
width: 100%;
padding: 0;

View File

@ -1,46 +1,35 @@
<template>
<div class="login-scan-container">
<div class="login-scan-qrcode" ref="qrcodeRef"></div>
<div ref="qrcodeRef"></div>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, onMounted, getCurrentInstance } from 'vue';
<script setup name="loginScan">
import QRCode from 'qrcodejs2-fixes';
export default defineComponent({
name: 'login11',
setup() {
const { proxy } = getCurrentInstance() as any;
const state = reactive({});
// 初始化生成二维码
const initQrcode = () => {
proxy.$refs.qrcodeRef.innerHTML = '';
new QRCode(proxy.$refs.qrcodeRef, {
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 {
...toRefs(state),
};
},
const { proxy } = getCurrentInstance();
// 初始化生成二维码
const initQrcode = () => {
proxy.$refs.qrcodeRef.innerHTML = '';
new QRCode(proxy.$refs.qrcodeRef, {
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();
});
</script>
<style scoped lang="scss">
.login-scan-container {
.login-scan-qrcode {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -40%);
}
padding: 20px;
display: flex;
justify-content: center;
animation: logoAnimation 0.3s ease;
}
</style>

View File

@ -3,30 +3,22 @@
<div class="login-logo">
<span>{{ getThemeConfig.globalViceTitle }}</span>
</div>
<div class="login-content" :class="{ 'login-content-mobile': tabsActiveName === 'mobile' }">
<div class="login-content">
<div class="login-content-main">
<h4 class="login-content-title">{{ getThemeConfig.globalTitle }}后台模板</h4>
<div v-if="!isScan">
<el-tabs v-model="tabsActiveName" @tab-click="onTabsClick">
<el-tab-pane label="账号密码登录" name="account" :disabled="tabsActiveName === 'account'">
<transition name="el-zoom-in-center">
<Account v-show="isTabPaneShow" />
</transition>
<div v-if="!state.isScan">
<el-tabs v-model="state.tabsActiveName">
<el-tab-pane label="账号密码登录" name="account">
<Account />
</el-tab-pane>
<el-tab-pane label="手机号登录" name="mobile" :disabled="tabsActiveName === 'mobile'">
<transition name="el-zoom-in-center">
<Mobile v-show="!isTabPaneShow" />
</transition>
<el-tab-pane label="手机号登录" name="mobile">
<Mobile />
</el-tab-pane>
</el-tabs>
<div class="mt10">
<el-button type="text" size="small">第三方登录</el-button>
<el-button type="text" size="small">友情链接</el-button>
</div>
</div>
<Scan v-else />
<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" @click="state.isScan = !state.isScan">
<i class="iconfont" :class="state.isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
<div class="login-content-main-sacn-delta"></div>
</div>
</div>
@ -38,37 +30,21 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed } from 'vue';
<script setup name="login">
import Account from '/@/views/login/component/account.vue';
import Mobile from '/@/views/login/component/mobile.vue';
import Scan from '/@/views/login/component/scan.vue';
import { useStore } from '/@/store/index';
export default {
name: 'login',
components: { Account, Mobile, Scan },
setup() {
const store = useStore();
const state = reactive({
tabsActiveName: 'account',
isTabPaneShow: true,
isScan: false,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 切换密码、手机登录
const onTabsClick = () => {
state.isTabPaneShow = !state.isTabPaneShow;
};
return {
onTabsClick,
getThemeConfig,
...toRefs(state),
};
},
};
const store = useStore();
const state = reactive({
tabsActiveName: 'account',
isTabPaneShow: true,
isScan: false,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
</script>
<style scoped lang="scss">
@ -100,8 +76,7 @@ export default {
background-color: rgba(255, 255, 255, 0.99);
border: 5px solid var(--color-primary-light-8);
border-radius: 4px;
transition: height 0.2s linear;
height: 480px;
transition: all 0.3s ease;
overflow: hidden;
z-index: 1;
.login-content-main {
@ -155,9 +130,6 @@ export default {
}
}
}
.login-content-mobile {
height: 418px;
}
.login-copyright {
position: absolute;
left: 50%;

View File

@ -1,17 +1,17 @@
<template>
<div class="system-add-dept-container">
<el-dialog title="新增部门" v-model="isShowDialog" width="769px">
<el-form :model="ruleForm" size="small" label-width="90px">
<el-dialog title="新增部门" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="small" label-width="90px">
<el-row :gutter="35">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="上级部门">
<el-cascader
:options="deptData"
:options="state.deptData"
:props="{ checkStrictly: true, value: 'deptName', label: 'deptName' }"
placeholder="请选择部门"
clearable
class="w100"
v-model="ruleForm.deptLevel"
v-model="state.ruleForm.deptLevel"
>
<template #default="{ node, data }">
<span>{{ data.deptName }}</span>
@ -22,37 +22,37 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="部门名称">
<el-input v-model="ruleForm.deptName" placeholder="请输入部门名称" clearable></el-input>
<el-input v-model="state.ruleForm.deptName" placeholder="请输入部门名称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="负责人">
<el-input v-model="ruleForm.person" placeholder="请输入负责人" clearable></el-input>
<el-input v-model="state.ruleForm.person" placeholder="请输入负责人" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="手机号">
<el-input v-model="ruleForm.phone" placeholder="请输入手机号" clearable></el-input>
<el-input v-model="state.ruleForm.phone" placeholder="请输入手机号" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="邮箱">
<el-input v-model="ruleForm.email" placeholder="请输入" clearable></el-input>
<el-input v-model="state.ruleForm.email" placeholder="请输入" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="排序">
<el-input-number v-model="ruleForm.sort" :min="0" :max="999" controls-position="right" placeholder="请输入排序" class="w100" />
<el-input-number v-model="state.ruleForm.sort" :min="0" :max="999" controls-position="right" placeholder="请输入排序" class="w100" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="部门状态">
<el-switch v-model="ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
<el-switch v-model="state.ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="部门描述">
<el-input v-model="ruleForm.describe" type="textarea" placeholder="请输入部门描述" maxlength="150"></el-input>
<el-input v-model="state.ruleForm.describe" type="textarea" placeholder="请输入部门描述" maxlength="150"></el-input>
</el-form-item>
</el-col>
</el-row>
@ -67,81 +67,73 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs, onMounted } from 'vue';
export default {
name: 'systemAddDept',
setup() {
const state = reactive({
isShowDialog: false,
ruleForm: {
deptLevel: [], // 上级部门
deptName: '', // 部门名称
person: '', // 负责人
phone: '', // 手机号
email: '', // 邮箱
sort: 0, // 排序
status: true, // 部门状态
describe: '', // 部门描述
},
deptData: [], // 部门数据
});
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 初始化部门数据
const initTableData = () => {
state.deptData.push({
deptName: 'vueNextAdmin',
<script setup name="systemAddDept">
const state = reactive({
isShowDialog: false,
ruleForm: {
deptLevel: [], // 上级部门
deptName: '', // 部门名称
person: '', // 负责人
phone: '', // 手机号
email: '', // 邮箱
sort: 0, // 排序
status: true, // 部门状态
describe: '', // 部门描述
},
deptData: [], // 部门数据
});
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 初始化部门数据
const initTableData = () => {
state.deptData.push({
deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '顶级部门',
id: Math.random(),
children: [
{
deptName: 'IT外包服务',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '顶级部门',
describe: '总部',
id: Math.random(),
children: [
{
deptName: 'IT外包服务',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '部',
id: Math.random(),
},
{
deptName: '资本控股',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '分部',
id: Math.random(),
},
],
});
};
// 页面加载时
onMounted(() => {
initTableData();
});
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
...toRefs(state),
};
},
},
{
deptName: '资本控股',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '部',
id: Math.random(),
},
],
});
};
// 页面加载时
onMounted(() => {
initTableData();
});
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@ -1,17 +1,17 @@
<template>
<div class="system-edit-dept-container">
<el-dialog title="修改部门" v-model="isShowDialog" width="769px">
<el-form :model="ruleForm" size="small" label-width="90px">
<el-dialog title="修改部门" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="small" label-width="90px">
<el-row :gutter="35">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="上级部门">
<el-cascader
:options="deptData"
:options="state.deptData"
:props="{ checkStrictly: true, value: 'deptName', label: 'deptName' }"
placeholder="请选择部门"
clearable
class="w100"
v-model="ruleForm.deptLevel"
v-model="state.ruleForm.deptLevel"
>
<template #default="{ node, data }">
<span>{{ data.deptName }}</span>
@ -22,37 +22,37 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="部门名称">
<el-input v-model="ruleForm.deptName" placeholder="请输入部门名称" clearable></el-input>
<el-input v-model="state.ruleForm.deptName" placeholder="请输入部门名称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="负责人">
<el-input v-model="ruleForm.person" placeholder="请输入负责人" clearable></el-input>
<el-input v-model="state.ruleForm.person" placeholder="请输入负责人" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="手机号">
<el-input v-model="ruleForm.phone" placeholder="请输入手机号" clearable></el-input>
<el-input v-model="state.ruleForm.phone" placeholder="请输入手机号" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="邮箱">
<el-input v-model="ruleForm.email" placeholder="请输入" clearable></el-input>
<el-input v-model="state.ruleForm.email" placeholder="请输入" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="排序">
<el-input-number v-model="ruleForm.sort" :min="0" :max="999" controls-position="right" placeholder="请输入排序" class="w100" />
<el-input-number v-model="state.ruleForm.sort" :min="0" :max="999" controls-position="right" placeholder="请输入排序" class="w100" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="部门状态">
<el-switch v-model="ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
<el-switch v-model="state.ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="部门描述">
<el-input v-model="ruleForm.describe" type="textarea" placeholder="请输入部门描述" maxlength="150"></el-input>
<el-input v-model="state.ruleForm.describe" type="textarea" placeholder="请输入部门描述" maxlength="150"></el-input>
</el-form-item>
</el-col>
</el-row>
@ -67,86 +67,78 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs, onMounted } from 'vue';
export default {
name: 'systemEditDept',
setup() {
const state = reactive({
isShowDialog: false,
ruleForm: {
deptLevel: [], // 上级部门
deptName: '', // 部门名称
person: '', // 负责人
phone: '', // 手机号
email: '', // 邮箱
sort: 0, // 排序
status: true, // 部门状态
describe: '', // 部门描述
},
deptData: [], // 部门数据
});
// 打开弹窗
const openDialog = (row: Object) => {
row.deptLevel = ['vueNextAdmin'];
row.person = 'lyt';
row.phone = '12345678910';
row.email = 'vueNextAdmin@123.com';
state.ruleForm = row;
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 初始化部门数据
const initTableData = () => {
state.deptData.push({
deptName: 'vueNextAdmin',
<script setup name="logsystemEditDeptin">
const state = reactive({
isShowDialog: false,
ruleForm: {
deptLevel: [], // 上级部门
deptName: '', // 部门名称
person: '', // 负责人
phone: '', // 手机号
email: '', // 邮箱
sort: 0, // 排序
status: true, // 部门状态
describe: '', // 部门描述
},
deptData: [], // 部门数据
});
// 打开弹窗
const openDialog = (row) => {
row.deptLevel = ['vueNextAdmin'];
row.person = 'lyt';
row.phone = '12345678910';
row.email = 'vueNextAdmin@123.com';
state.ruleForm = row;
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 初始化部门数据
const initTableData = () => {
state.deptData.push({
deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '顶级部门',
id: Math.random(),
children: [
{
deptName: 'IT外包服务',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '顶级部门',
describe: '总部',
id: Math.random(),
children: [
{
deptName: 'IT外包服务',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '部',
id: Math.random(),
},
{
deptName: '资本控股',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '分部',
id: Math.random(),
},
],
});
};
// 页面加载时
onMounted(() => {
initTableData();
});
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
...toRefs(state),
};
},
},
{
deptName: '资本控股',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '部',
id: Math.random(),
},
],
});
};
// 页面加载时
onMounted(() => {
initTableData();
});
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@ -17,7 +17,7 @@
</el-button>
</div>
<el-table
:data="tableData.data"
:data="state.tableData.data"
style="width: 100%"
row-key="id"
default-expand-all
@ -51,90 +51,76 @@
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, onMounted } from 'vue';
<script setup name="systemDept">
import { ElMessageBox, ElMessage } from 'element-plus';
import AddDept from '/@/views/system/dept/component/addDept.vue';
import EditDept from '/@/views/system/dept/component/editDept.vue';
export default {
name: 'systemDept',
components: { AddDept, EditDept },
setup() {
const addDeptRef = ref();
const editDeptRef = ref();
const state = reactive({
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
// 初始化表格数据
const initTableData = () => {
state.tableData.data.push({
deptName: 'vueNextAdmin',
const addDeptRef = ref();
const editDeptRef = ref();
const state = reactive({
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
// 初始化表格数据
const initTableData = () => {
state.tableData.data.push({
deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '顶级部门',
id: Math.random(),
children: [
{
deptName: 'IT外包服务',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '顶级部门',
describe: '总部',
id: Math.random(),
children: [
{
deptName: 'IT外包服务',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '部',
id: Math.random(),
},
{
deptName: '资本控股',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '分部',
id: Math.random(),
},
],
});
state.tableData.total = state.tableData.data.length;
};
// 打开新增菜单弹窗
const onOpenAddDept = () => {
addDeptRef.value.openDialog();
};
// 打开编辑菜单弹窗
const onOpenEditDept = (row: object) => {
editDeptRef.value.openDialog(row);
};
// 删除当前行
const onTabelRowDel = (row: object) => {
ElMessageBox.confirm(`此操作将永久删除部门:${row.deptName}, 是否继续?`, '提示', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
ElMessage.success('删除成功');
})
.catch(() => {});
};
// 页面加载时
onMounted(() => {
initTableData();
});
return {
addDeptRef,
editDeptRef,
onOpenAddDept,
onOpenEditDept,
onTabelRowDel,
...toRefs(state),
};
},
},
{
deptName: '资本控股',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '部',
id: Math.random(),
},
],
});
state.tableData.total = state.tableData.data.length;
};
// 打开新增菜单弹窗
const onOpenAddDept = () => {
addDeptRef.value.openDialog();
};
// 打开编辑菜单弹窗
const onOpenEditDept = (row) => {
editDeptRef.value.openDialog(row);
};
// 删除当前行
const onTabelRowDel = (row) => {
ElMessageBox.confirm(`此操作将永久删除部门:${row.deptName}, 是否继续?`, '提示', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
ElMessage.success('删除成功');
})
.catch(() => {});
};
// 页面加载时
onMounted(() => {
initTableData();
});
</script>

View File

@ -1,26 +1,26 @@
<template>
<div class="system-add-dic-container">
<el-dialog title="新增字典" v-model="isShowDialog" width="769px">
<el-dialog title="新增字典" v-model="state.isShowDialog" width="769px">
<el-alert title="半成品,交互过于复杂,请自行扩展!" type="warning" :closable="false" class="mb20"> </el-alert>
<el-form :model="ruleForm" size="small" label-width="90px">
<el-form :model="state.ruleForm" size="small" label-width="90px">
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="字典名称">
<el-input v-model="ruleForm.dicName" placeholder="请输入字典名称" clearable></el-input>
<el-input v-model="state.ruleForm.dicName" placeholder="请输入字典名称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="字段名">
<el-input v-model="ruleForm.fieldName" placeholder="请输入字段名,拼接 ruleForm.list" clearable></el-input>
<el-input v-model="state.ruleForm.fieldName" placeholder="请输入字段名,拼接 state.ruleForm.list" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="字典状态">
<el-switch v-model="ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
<el-switch v-model="state.ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-row :gutter="35" v-for="(v, k) in ruleForm.list" :key="k">
<el-row :gutter="35" v-for="(v, k) in state.ruleForm.list" :key="k">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item :prop="`list[${k}].label`">
<template #label>
@ -48,7 +48,7 @@
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="字典描述">
<el-input v-model="ruleForm.describe" type="textarea" placeholder="请输入字典描述" maxlength="150"></el-input>
<el-input v-model="state.ruleForm.describe" type="textarea" placeholder="请输入字典描述" maxlength="150"></el-input>
</el-form-item>
</el-col>
</el-row>
@ -63,66 +63,56 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue';
export default {
name: 'systemAddDic',
setup() {
const state = reactive({
isShowDialog: false,
ruleForm: {
dicName: '', // 字典名称
fieldName: '', // 字段名
status: true, // 字典状态
list: [
// 子集字段 + 属性值
{
id: Math.random(),
label: '',
value: '',
},
],
describe: '', // 字典描述
fieldNameList: [], // 字段名: [{子集字段 + 属性值}]
},
});
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 新增行
const onAddRow = () => {
state.ruleForm.list.push({
<script setup name="systemAddDic">
const state = reactive({
isShowDialog: false,
ruleForm: {
dicName: '', // 字典名称
fieldName: '', // 字段名
status: true, // 字典状态
list: [
// 子集字段 + 属性值
{
id: Math.random(),
label: '',
value: '',
});
};
// 删除行
const onDelRow = (k) => {
state.ruleForm.list.splice(k, 1);
};
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
onAddRow,
onDelRow,
...toRefs(state),
};
},
],
describe: '', // 字典描述
fieldNameList: [], // 字段名: [{子集字段 + 属性值}]
},
});
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 新增行
const onAddRow = () => {
state.ruleForm.list.push({
id: Math.random(),
label: '',
value: '',
});
};
// 删除行
const onDelRow = (k) => {
state.ruleForm.list.splice(k, 1);
};
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@ -1,26 +1,26 @@
<template>
<div class="system-edit-dic-container">
<el-dialog title="修改字典" v-model="isShowDialog" width="769px">
<el-dialog title="修改字典" v-model="state.isShowDialog" width="769px">
<el-alert title="半成品,交互过于复杂,请自行扩展!" type="warning" :closable="false" class="mb20"> </el-alert>
<el-form :model="ruleForm" size="small" label-width="90px">
<el-form :model="state.ruleForm" size="small" label-width="90px">
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="字典名称">
<el-input v-model="ruleForm.dicName" placeholder="请输入字典名称" clearable></el-input>
<el-input v-model="state.ruleForm.dicName" placeholder="请输入字典名称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="字段名">
<el-input v-model="ruleForm.fieldName" placeholder="请输入字段名,拼接 ruleForm.list" clearable></el-input>
<el-input v-model="state.ruleForm.fieldName" placeholder="请输入字段名,拼接 state.ruleForm.list" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="字典状态">
<el-switch v-model="ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
<el-switch v-model="state.ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-row :gutter="35" v-for="(v, k) in ruleForm.list" :key="k">
<el-row :gutter="35" v-for="(v, k) in state.ruleForm.list" :key="k">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item :prop="`list[${k}].label`">
<template #label>
@ -48,7 +48,7 @@
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="字典描述">
<el-input v-model="ruleForm.describe" type="textarea" placeholder="请输入字典描述" maxlength="150"></el-input>
<el-input v-model="state.ruleForm.describe" type="textarea" placeholder="请输入字典描述" maxlength="150"></el-input>
</el-form-item>
</el-col>
</el-row>
@ -63,80 +63,70 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue';
export default {
name: 'systemEditDic',
setup() {
const state = reactive({
isShowDialog: false,
ruleForm: {
dicName: '', // 字典名称
fieldName: '', // 字段名
status: true, // 字典状态
list: [
// 子集字段 + 属性值
{
id: Math.random(),
label: '',
value: '',
},
],
describe: '', // 字典描述
fieldNameList: [], // 字段名: [{子集字段 + 属性值}]
},
});
// 打开弹窗
const openDialog = (row: Object) => {
if (row.fieldName === 'SYS_UERINFO') {
row.list = [
{ id: Math.random(), label: 'sex', value: '1' },
{ id: Math.random(), label: 'sex', value: '0' },
];
} else {
row.list = [
{ id: Math.random(), label: 'role', value: 'admin' },
{ id: Math.random(), label: 'role', value: 'common' },
{ id: Math.random(), label: 'roleName', value: '超级管理员' },
{ id: Math.random(), label: 'roleName', value: '普通用户' },
];
}
state.ruleForm = row;
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 新增行
const onAddRow = () => {
state.ruleForm.list.push({
<script setup name="systemEditDic">
const state = reactive({
isShowDialog: false,
ruleForm: {
dicName: '', // 字典名称
fieldName: '', // 字段名
status: true, // 字典状态
list: [
// 子集字段 + 属性值
{
id: Math.random(),
label: '',
value: '',
});
};
// 删除行
const onDelRow = (k) => {
state.ruleForm.list.splice(k, 1);
};
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
onAddRow,
onDelRow,
...toRefs(state),
};
},
],
describe: '', // 字典描述
fieldNameList: [], // 字段名: [{子集字段 + 属性值}]
},
});
// 打开弹窗
const openDialog = (row) => {
if (row.fieldName === 'SYS_UERINFO') {
row.list = [
{ id: Math.random(), label: 'sex', value: '1' },
{ id: Math.random(), label: 'sex', value: '0' },
];
} else {
row.list = [
{ id: Math.random(), label: 'role', value: 'admin' },
{ id: Math.random(), label: 'role', value: 'common' },
{ id: Math.random(), label: 'roleName', value: '超级管理员' },
{ id: Math.random(), label: 'roleName', value: '普通用户' },
];
}
state.ruleForm = row;
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 新增行
const onAddRow = () => {
state.ruleForm.list.push({
id: Math.random(),
label: '',
value: '',
});
};
// 删除行
const onDelRow = (k) => {
state.ruleForm.list.splice(k, 1);
};
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@ -16,7 +16,7 @@
新增字典
</el-button>
</div>
<el-table :data="tableData.data" style="width: 100%">
<el-table :data="state.tableData.data" style="width: 100%">
<el-table-column type="index" label="序号" width="50" />
<el-table-column prop="dicName" label="字典名称" show-overflow-tooltip></el-table-column>
<el-table-column prop="fieldName" label="字段名" show-overflow-tooltip></el-table-column>
@ -41,11 +41,11 @@
class="mt15"
:pager-count="5"
:page-sizes="[10, 20, 30]"
v-model:current-page="tableData.param.pageNum"
v-model:current-page="state.tableData.param.pageNum"
background
v-model:page-size="tableData.param.pageSize"
v-model:page-size="state.tableData.param.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total"
:total="state.tableData.total"
>
</el-pagination>
</el-card>
@ -54,87 +54,71 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, ref } from 'vue';
<script setup name="systemDic">
import { ElMessageBox, ElMessage } from 'element-plus';
import AddDic from '/@/views/system/dic/component/addDic.vue';
import EditDic from '/@/views/system/dic/component/editDic.vue';
export default {
name: 'systemDic',
components: { AddDic, EditDic },
setup() {
const addDicRef = ref();
const editDicRef = ref();
const state: any = reactive({
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
// 初始化表格数据
const initTableData = () => {
const data: Array<object> = [];
for (let i = 0; i < 2; i++) {
data.push({
dicName: i === 0 ? '角色标识' : '用户性别',
fieldName: i === 0 ? 'SYS_ROLE' : 'SYS_UERINFO',
describe: i === 0 ? '这是角色字典' : '这是用户性别字典',
status: true,
createTime: new Date().toLocaleString(),
});
}
state.tableData.data = data;
state.tableData.total = state.tableData.data.length;
};
// 打开新增字典弹窗
const onOpenAddDic = () => {
addDicRef.value.openDialog();
};
// 打开修改字典弹窗
const onOpenEditDic = (row: Object) => {
editDicRef.value.openDialog(row);
};
// 删除字典
const onRowDel = (row: Object) => {
ElMessageBox.confirm(`此操作将永久删除字典名称:“${row.dicName}”,是否继续?`, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
ElMessage.success('删除成功');
})
.catch(() => {});
};
// 分页改变
const onHandleSizeChange = (val: number) => {
state.tableData.param.pageSize = val;
};
// 分页改变
const onHandleCurrentChange = (val: number) => {
state.tableData.param.pageNum = val;
};
// 页面加载时
onMounted(() => {
initTableData();
});
return {
addDicRef,
editDicRef,
onOpenAddDic,
onOpenEditDic,
onRowDel,
onHandleSizeChange,
onHandleCurrentChange,
...toRefs(state),
};
const addDicRef = ref();
const editDicRef = ref();
const state = reactive({
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
// 初始化表格数据
const initTableData = () => {
const data = [];
for (let i = 0; i < 2; i++) {
data.push({
dicName: i === 0 ? '角色标识' : '用户性别',
fieldName: i === 0 ? 'SYS_ROLE' : 'SYS_UERINFO',
describe: i === 0 ? '这是角色字典' : '这是用户性别字典',
status: true,
createTime: new Date().toLocaleString(),
});
}
state.tableData.data = data;
state.tableData.total = state.tableData.data.length;
};
// 打开新增字典弹窗
const onOpenAddDic = () => {
addDicRef.value.openDialog();
};
// 打开修改字典弹窗
const onOpenEditDic = (row) => {
editDicRef.value.openDialog(row);
};
// 删除字典
const onRowDel = (row) => {
ElMessageBox.confirm(`此操作将永久删除字典名称:“${row.dicName}”,是否继续?`, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
ElMessage.success('删除成功');
})
.catch(() => {});
};
// 分页改变
const onHandleSizeChange = (val) => {
state.tableData.param.pageSize = val;
};
// 分页改变
const onHandleCurrentChange = (val) => {
state.tableData.param.pageNum = val;
};
// 页面加载时
onMounted(() => {
initTableData();
});
</script>
<style scoped lang="scss">

View File

@ -1,17 +1,17 @@
<template>
<div class="system-add-menu-container">
<el-dialog title="新增菜单" v-model="isShowDialog" width="769px">
<el-form :model="ruleForm" size="small" label-width="80px">
<el-dialog title="新增菜单" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="small" label-width="80px">
<el-row :gutter="35">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="上级菜单">
<el-cascader
:options="menuData"
:options="state.menuData"
:props="{ checkStrictly: true, value: 'path', label: 'title' }"
placeholder="请选择上级菜单"
clearable
class="w100"
v-model="ruleForm.menuSuperior"
v-model="state.ruleForm.menuSuperior"
>
<template #default="{ node, data }">
<span>{{ data.title }}</span>
@ -22,7 +22,7 @@
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="菜单类型">
<el-radio-group v-model="ruleForm.menuType">
<el-radio-group v-model="state.ruleForm.menuType">
<el-radio label="menu">菜单</el-radio>
<el-radio label="btn">按钮</el-radio>
</el-radio-group>
@ -30,71 +30,71 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="菜单名称">
<el-input v-model="ruleForm.meta.title" placeholder="格式message.router.xxx" clearable></el-input>
<el-input v-model="state.ruleForm.meta.title" placeholder="格式message.router.xxx" clearable></el-input>
</el-form-item>
</el-col>
<template v-if="ruleForm.menuType === 'menu'">
<template v-if="state.ruleForm.menuType === 'menu'">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="路由名称">
<el-input v-model="ruleForm.name" placeholder="路由中的 name 值" clearable></el-input>
<el-input v-model="state.ruleForm.name" placeholder="路由中的 name 值" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="路由路径">
<el-input v-model="ruleForm.path" placeholder="路由中的 path 值" clearable></el-input>
<el-input v-model="state.ruleForm.path" placeholder="路由中的 path 值" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="重定向">
<el-input v-model="ruleForm.redirect" placeholder="请输入路由重定向" clearable></el-input>
<el-input v-model="state.ruleForm.redirect" placeholder="请输入路由重定向" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="菜单图标">
<IconSelector placeholder="请输入菜单图标" v-model="ruleForm.meta.icon" type="all" />
<IconSelector placeholder="请输入菜单图标" v-model="state.ruleForm.meta.icon" type="all" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="组件路径">
<el-input v-model="ruleForm.component" placeholder="组件路径" clearable></el-input>
<el-input v-model="state.ruleForm.component" placeholder="组件路径" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="链接地址">
<el-input
v-model="ruleForm.meta.isLink"
v-model="state.ruleForm.meta.isLink"
placeholder="外链/内嵌时链接地址http:xxx.com"
clearable
:disabled="ruleForm.isLink === '' || !ruleForm.isLink"
:disabled="state.ruleForm.isLink === '' || !state.ruleForm.isLink"
>
</el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="权限标识">
<el-select v-model="ruleForm.meta.roles" multiple placeholder="取角色管理" clearable class="w100">
<el-select v-model="state.ruleForm.meta.roles" multiple placeholder="取角色管理" clearable class="w100">
<el-option label="admin" value="admin"></el-option>
<el-option label="common" value="common"></el-option>
</el-select>
</el-form-item>
</el-col>
</template>
<template v-if="ruleForm.menuType === 'btn'">
<template v-if="state.ruleForm.menuType === 'btn'">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="权限标识">
<el-input v-model="ruleForm.btnPower" placeholder="请输入权限标识" clearable></el-input>
<el-input v-model="state.ruleForm.btnPower" placeholder="请输入权限标识" clearable></el-input>
</el-form-item>
</el-col>
</template>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="菜单排序">
<el-input-number v-model="ruleForm.menuSort" controls-position="right" placeholder="请输入排序" class="w100" />
<el-input-number v-model="state.ruleForm.menuSort" controls-position="right" placeholder="请输入排序" class="w100" />
</el-form-item>
</el-col>
<template v-if="ruleForm.menuType === 'menu'">
<template v-if="state.ruleForm.menuType === 'menu'">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否隐藏">
<el-radio-group v-model="ruleForm.meta.isHide">
<el-radio-group v-model="state.ruleForm.meta.isHide">
<el-radio :label="true">隐藏</el-radio>
<el-radio :label="false">不隐藏</el-radio>
</el-radio-group>
@ -102,7 +102,7 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="页面缓存">
<el-radio-group v-model="ruleForm.meta.isKeepAlive">
<el-radio-group v-model="state.ruleForm.meta.isKeepAlive">
<el-radio :label="true">缓存</el-radio>
<el-radio :label="false">不缓存</el-radio>
</el-radio-group>
@ -110,7 +110,7 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否固定">
<el-radio-group v-model="ruleForm.meta.isAffix">
<el-radio-group v-model="state.ruleForm.meta.isAffix">
<el-radio :label="true">固定</el-radio>
<el-radio :label="false">不固定</el-radio>
</el-radio-group>
@ -118,7 +118,7 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否外链">
<el-radio-group v-model="ruleForm.isLink" :disabled="ruleForm.meta.isIframe">
<el-radio-group v-model="state.ruleForm.isLink" :disabled="state.ruleForm.meta.isIframe">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
@ -126,7 +126,7 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否内嵌">
<el-radio-group v-model="ruleForm.meta.isIframe" @change="onSelectIframeChange">
<el-radio-group v-model="state.ruleForm.meta.isIframe" @change="onSelectIframeChange">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
@ -145,90 +145,80 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs, onMounted } from 'vue';
import { useStore } from '/@/store/index';
<script setup name="systemAddMenu">
import IconSelector from '/@/components/iconSelector/index.vue';
// import { setBackEndControlRefreshRoutes } from "/@/router/backEnd";
export default {
name: 'systemAddMenu',
components: { IconSelector },
setup() {
const store = useStore();
const state = reactive({
isShowDialog: false,
// 参数请参考 `/src/router/route.ts` 中的 `dynamicRoutes` 路由菜单格式
ruleForm: {
menuSuperior: [], // 上级菜单
menuType: 'menu', // 菜单类型
name: '', // 路由名称
component: '', // 组件路径
isLink: false, // 是否外链
menuSort: 0, // 菜单排序
path: '', // 路由路径
redirect: '', // 路由重定向,有子集 children 时
meta: {
title: '', // 菜单名称
icon: '', // 菜单图标
isHide: false, // 是否隐藏
isKeepAlive: true, // 是否缓存
isAffix: false, // 是否固定
isLink: '', // 外链/内嵌时链接地址http:xxx.com开启外链条件`1、isLink:true 2、链接地址不为空`
isIframe: false, // 是否内嵌,开启条件,`1、isIframe:true 2、链接地址不为空`
roles: '', // 权限标识,取角色管理
},
btnPower: '', // 菜单类型为按钮时,权限标识
},
menuData: [], // 上级菜单数据
});
// 获取 vuex 中的路由
const getMenuData = (routes) => {
const arr = [];
routes.map((val) => {
val['title'] = val.meta.title;
val['id'] = Math.random();
arr.push({ ...val });
if (val.children) getMenuData(val.children);
});
return arr;
};
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 是否内嵌下拉改变
const onSelectIframeChange = () => {
if (state.ruleForm.meta.isIframe) {
state.ruleForm.isLink = true;
} else {
state.ruleForm.isLink = '';
}
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog(); // 关闭弹窗
// setBackEndControlRefreshRoutes() // 刷新菜单,未进行后端接口测试
};
// 页面加载时
onMounted(() => {
state.menuData = getMenuData(store.state.routesList.routesList);
});
return {
openDialog,
closeDialog,
onSelectIframeChange,
onCancel,
onSubmit,
...toRefs(state),
};
const store = useStore();
const state = reactive({
isShowDialog: false,
// 参数请参考 `/src/router/route.ts` 中的 `dynamicRoutes` 路由菜单格式
ruleForm: {
menuSuperior: [], // 上级菜单
menuType: 'menu', // 菜单类型
name: '', // 路由名称
component: '', // 组件路径
isLink: false, // 是否外链
menuSort: 0, // 菜单排序
path: '', // 路由路径
redirect: '', // 路由重定向,有子集 children 时
meta: {
title: '', // 菜单名称
icon: '', // 菜单图标
isHide: false, // 是否隐藏
isKeepAlive: true, // 是否缓存
isAffix: false, // 是否固定
isLink: '', // 外链/内嵌时链接地址http:xxx.com开启外链条件`1、isLink:true 2、链接地址不为空`
isIframe: false, // 是否内嵌,开启条件,`1、isIframe:true 2、链接地址不为空`
roles: '', // 权限标识,取角色管理
},
btnPower: '', // 菜单类型为按钮时,权限标识
},
menuData: [], // 上级菜单数据
});
// 获取 vuex 中的路由
const getMenuData = (routes) => {
const arr = [];
routes.map((val) => {
val['title'] = val.meta.title;
val['id'] = Math.random();
arr.push({ ...val });
if (val.children) getMenuData(val.children);
});
return arr;
};
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 是否内嵌下拉改变
const onSelectIframeChange = () => {
if (state.ruleForm.meta.isIframe) {
state.ruleForm.isLink = true;
} else {
state.ruleForm.isLink = '';
}
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog(); // 关闭弹窗
// setBackEndControlRefreshRoutes() // 刷新菜单,未进行后端接口测试
};
// 页面加载时
onMounted(() => {
state.menuData = getMenuData(store.state.routesList.routesList);
});
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@ -1,17 +1,17 @@
<template>
<div class="system-edit-menu-container">
<el-dialog title="修改菜单" v-model="isShowDialog" width="769px">
<el-form :model="ruleForm" size="small" label-width="80px">
<el-dialog title="修改菜单" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="small" label-width="80px">
<el-row :gutter="35">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="上级菜单">
<el-cascader
:options="menuData"
:options="state.menuData"
:props="{ checkStrictly: true, value: 'path', label: 'title' }"
placeholder="请选择上级菜单"
clearable
class="w100"
v-model="ruleForm.menuSuperior"
v-model="state.ruleForm.menuSuperior"
>
<template #default="{ node, data }">
<span>{{ data.title }}</span>
@ -22,7 +22,7 @@
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="菜单类型">
<el-radio-group v-model="ruleForm.menuType">
<el-radio-group v-model="state.ruleForm.menuType">
<el-radio label="menu">菜单</el-radio>
<el-radio label="btn">按钮</el-radio>
</el-radio-group>
@ -30,71 +30,71 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="菜单名称">
<el-input v-model="ruleForm.meta.title" placeholder="格式message.router.xxx" clearable></el-input>
<el-input v-model="state.ruleForm.meta.title" placeholder="格式message.router.xxx" clearable></el-input>
</el-form-item>
</el-col>
<template v-if="ruleForm.menuType === 'menu'">
<template v-if="state.ruleForm.menuType === 'menu'">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="路由名称">
<el-input v-model="ruleForm.name" placeholder="路由中的 name 值" clearable></el-input>
<el-input v-model="state.ruleForm.name" placeholder="路由中的 name 值" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="路由路径">
<el-input v-model="ruleForm.path" placeholder="路由中的 path 值" clearable></el-input>
<el-input v-model="state.ruleForm.path" placeholder="路由中的 path 值" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="重定向">
<el-input v-model="ruleForm.redirect" placeholder="请输入路由重定向" clearable></el-input>
<el-input v-model="state.ruleForm.redirect" placeholder="请输入路由重定向" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="菜单图标">
<IconSelector placeholder="请输入菜单图标" v-model="ruleForm.meta.icon" type="all" />
<IconSelector placeholder="请输入菜单图标" v-model="state.ruleForm.meta.icon" type="all" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="组件路径">
<el-input v-model="ruleForm.component" placeholder="组件路径" clearable></el-input>
<el-input v-model="state.ruleForm.component" placeholder="组件路径" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="链接地址">
<el-input
v-model="ruleForm.meta.isLink"
v-model="state.ruleForm.meta.isLink"
placeholder="外链/内嵌时链接地址http:xxx.com"
clearable
:disabled="ruleForm.isLink === '' || !ruleForm.isLink"
:disabled="state.ruleForm.isLink === '' || !state.ruleForm.isLink"
>
</el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="权限标识">
<el-select v-model="ruleForm.meta.roles" multiple placeholder="取角色管理" clearable class="w100">
<el-select v-model="state.ruleForm.meta.roles" multiple placeholder="取角色管理" clearable class="w100">
<el-option label="admin" value="admin"></el-option>
<el-option label="common" value="common"></el-option>
</el-select>
</el-form-item>
</el-col>
</template>
<template v-if="ruleForm.menuType === 'btn'">
<template v-if="state.ruleForm.menuType === 'btn'">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="权限标识">
<el-input v-model="ruleForm.btnPower" placeholder="请输入权限标识" clearable></el-input>
<el-input v-model="state.ruleForm.btnPower" placeholder="请输入权限标识" clearable></el-input>
</el-form-item>
</el-col>
</template>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="菜单排序">
<el-input-number v-model="ruleForm.menuSort" controls-position="right" placeholder="请输入排序" class="w100" />
<el-input-number v-model="state.ruleForm.menuSort" controls-position="right" placeholder="请输入排序" class="w100" />
</el-form-item>
</el-col>
<template v-if="ruleForm.menuType === 'menu'">
<template v-if="state.ruleForm.menuType === 'menu'">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否隐藏">
<el-radio-group v-model="ruleForm.meta.isHide">
<el-radio-group v-model="state.ruleForm.meta.isHide">
<el-radio :label="true">隐藏</el-radio>
<el-radio :label="false">不隐藏</el-radio>
</el-radio-group>
@ -102,7 +102,7 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="页面缓存">
<el-radio-group v-model="ruleForm.meta.isKeepAlive">
<el-radio-group v-model="state.ruleForm.meta.isKeepAlive">
<el-radio :label="true">缓存</el-radio>
<el-radio :label="false">不缓存</el-radio>
</el-radio-group>
@ -110,7 +110,7 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否固定">
<el-radio-group v-model="ruleForm.meta.isAffix">
<el-radio-group v-model="state.ruleForm.meta.isAffix">
<el-radio :label="true">固定</el-radio>
<el-radio :label="false">不固定</el-radio>
</el-radio-group>
@ -118,7 +118,7 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否外链">
<el-radio-group v-model="ruleForm.isLink" :disabled="ruleForm.meta.isIframe">
<el-radio-group v-model="state.ruleForm.isLink" :disabled="state.ruleForm.meta.isIframe">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
@ -126,7 +126,7 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="是否内嵌">
<el-radio-group v-model="ruleForm.meta.isIframe" @change="onSelectIframeChange">
<el-radio-group v-model="state.ruleForm.meta.isIframe" @change="onSelectIframeChange">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
@ -145,93 +145,83 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs, onMounted } from 'vue';
import { useStore } from '/@/store/index';
<script setup name="systemEditMenu">
import IconSelector from '/@/components/iconSelector/index.vue';
// import { setBackEndControlRefreshRoutes } from "/@/router/backEnd";
export default {
name: 'systemEditMenu',
components: { IconSelector },
setup() {
const store = useStore();
const state = reactive({
isShowDialog: false,
// 参数请参考 `/src/router/route.ts` 中的 `dynamicRoutes` 路由菜单格式
ruleForm: {
menuSuperior: [], // 上级菜单
menuType: 'menu', // 菜单类型
name: '', // 路由名称
component: '', // 组件路径
isLink: false, // 是否外链
menuSort: 0, // 菜单排序
path: '', // 路由路径
redirect: '', // 路由重定向,有子集 children 时
meta: {
title: '', // 菜单名称
icon: '', // 菜单图标
isHide: false, // 是否隐藏
isKeepAlive: true, // 是否缓存
isAffix: false, // 是否固定
isLink: '', // 外链/内嵌时链接地址http:xxx.com开启外链条件`1、isLink:true 2、链接地址不为空`
isIframe: false, // 是否内嵌,开启条件,`1、isIframe:true 2、链接地址不为空`
roles: '', // 权限标识,取角色管理
},
btnPower: '', // 菜单类型为按钮时,权限标识
},
menuData: [], // 上级菜单数据
});
// 获取 vuex 中的路由
const getMenuData = (routes) => {
const arr = [];
routes.map((val) => {
val['title'] = val.meta.title;
val['id'] = Math.random();
arr.push({ ...val });
if (val.children) getMenuData(val.children);
});
return arr;
};
// 打开弹窗
const openDialog = (row: Object) => {
row.menuType = 'menu';
row.menuSort = Math.random();
state.ruleForm = row;
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 是否内嵌下拉改变
const onSelectIframeChange = () => {
if (state.ruleForm.meta.isIframe) {
state.ruleForm.isLink = true;
} else {
state.ruleForm.isLink = '';
}
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog(); // 关闭弹窗
// setBackEndControlRefreshRoutes() // 刷新菜单,未进行后端接口测试
};
// 页面加载时
onMounted(() => {
state.menuData = getMenuData(store.state.routesList.routesList);
});
return {
openDialog,
closeDialog,
onSelectIframeChange,
onCancel,
onSubmit,
...toRefs(state),
};
const store = useStore();
const state = reactive({
isShowDialog: false,
// 参数请参考 `/src/router/route.ts` 中的 `dynamicRoutes` 路由菜单格式
ruleForm: {
menuSuperior: [], // 上级菜单
menuType: 'menu', // 菜单类型
name: '', // 路由名称
component: '', // 组件路径
isLink: false, // 是否外链
menuSort: 0, // 菜单排序
path: '', // 路由路径
redirect: '', // 路由重定向,有子集 children 时
meta: {
title: '', // 菜单名称
icon: '', // 菜单图标
isHide: false, // 是否隐藏
isKeepAlive: true, // 是否缓存
isAffix: false, // 是否固定
isLink: '', // 外链/内嵌时链接地址http:xxx.com开启外链条件`1、isLink:true 2、链接地址不为空`
isIframe: false, // 是否内嵌,开启条件,`1、isIframe:true 2、链接地址不为空`
roles: '', // 权限标识,取角色管理
},
btnPower: '', // 菜单类型为按钮时,权限标识
},
menuData: [], // 上级菜单数据
});
// 获取 vuex 中的路由
const getMenuData = (routes) => {
const arr = [];
routes.map((val) => {
val['title'] = val.meta.title;
val['id'] = Math.random();
arr.push({ ...val });
if (val.children) getMenuData(val.children);
});
return arr;
};
// 打开弹窗
const openDialog = (row) => {
row.menuType = 'menu';
row.menuSort = Math.random();
state.ruleForm = row;
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 是否内嵌下拉改变
const onSelectIframeChange = () => {
if (state.ruleForm.meta.isIframe) {
state.ruleForm.isLink = true;
} else {
state.ruleForm.isLink = '';
}
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog(); // 关闭弹窗
// setBackEndControlRefreshRoutes() // 刷新菜单,未进行后端接口测试
};
// 页面加载时
onMounted(() => {
state.menuData = getMenuData(store.state.routesList.routesList);
});
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@ -58,53 +58,36 @@
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, computed } from 'vue';
<script setup name="systemMenu">
import { ElMessageBox, ElMessage } from 'element-plus';
import { useStore } from '/@/store/index';
import AddMenu from '/@/views/system/menu/component/addMenu.vue';
import EditMenu from '/@/views/system/menu/component/editMenu.vue';
export default {
name: 'systemMenu',
components: { AddMenu, EditMenu },
setup() {
const store = useStore();
const addMenuRef = ref();
const editMenuRef = ref();
const state = reactive({});
// 获取 vuex 中的路由
const menuTableData = computed(() => {
return store.state.routesList.routesList;
});
// 打开新增菜单弹窗
const onOpenAddMenu = () => {
addMenuRef.value.openDialog();
};
// 打开编辑菜单弹窗
const onOpenEditMenu = (row: object) => {
editMenuRef.value.openDialog(row);
};
// 删除当前行
const onTabelRowDel = (row: object) => {
ElMessageBox.confirm(`此操作将永久删除路由:${row.path}, 是否继续?`, '提示', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
ElMessage.success('删除成功');
})
.catch(() => {});
};
return {
addMenuRef,
editMenuRef,
onOpenAddMenu,
onOpenEditMenu,
menuTableData,
onTabelRowDel,
...toRefs(state),
};
},
const store = useStore();
const addMenuRef = ref();
const editMenuRef = ref();
// 获取 vuex 中的路由
const menuTableData = computed(() => {
return store.state.routesList.routesList;
});
// 打开新增菜单弹窗
const onOpenAddMenu = () => {
addMenuRef.value.openDialog();
};
// 打开编辑菜单弹窗
const onOpenEditMenu = (row) => {
editMenuRef.value.openDialog(row);
};
// 删除当前行
const onTabelRowDel = (row) => {
ElMessageBox.confirm(`此操作将永久删除路由:${row.path}, 是否继续?`, '提示', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
ElMessage.success('删除成功');
})
.catch(() => {});
};
</script>

View File

@ -1,11 +1,11 @@
<template>
<div class="system-add-role-container">
<el-dialog title="新增角色" v-model="isShowDialog" width="769px">
<el-form :model="ruleForm" size="small" label-width="90px">
<el-dialog title="新增角色" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="small" label-width="90px">
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="角色名称">
<el-input v-model="ruleForm.roleName" placeholder="请输入角色名称" clearable></el-input>
<el-input v-model="state.ruleForm.roleName" placeholder="请输入角色名称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
@ -15,27 +15,27 @@
<span>角色标识</span>
</el-tooltip>
</template>
<el-input v-model="ruleForm.roleSign" placeholder="请输入角色标识" clearable></el-input>
<el-input v-model="state.ruleForm.roleSign" placeholder="请输入角色标识" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="排序">
<el-input-number v-model="ruleForm.sort" :min="0" :max="999" controls-position="right" placeholder="请输入排序" class="w100" />
<el-input-number v-model="state.ruleForm.sort" :min="0" :max="999" controls-position="right" placeholder="请输入排序" class="w100" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="角色状态">
<el-switch v-model="ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
<el-switch v-model="state.ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="角色描述">
<el-input v-model="ruleForm.describe" type="textarea" placeholder="请输入角色描述" maxlength="150"></el-input>
<el-input v-model="state.ruleForm.describe" type="textarea" placeholder="请输入角色描述" maxlength="150"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="菜单权限">
<el-tree :data="menuData" :props="menuProps" show-checkbox class="menu-data-tree" />
<el-tree :data="state.menuData" :props="state.menuProps" show-checkbox class="menu-data-tree" />
</el-form-item>
</el-col>
</el-row>
@ -50,159 +50,151 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue';
export default {
name: 'systemAddRole',
setup() {
const state = reactive({
isShowDialog: false,
ruleForm: {
roleName: '', // 角色名称
roleSign: '', // 角色标识
sort: 0, // 排序
status: true, // 角色状态
describe: '', // 角色描述
},
menuData: [],
menuProps: {
children: 'children',
label: 'label',
},
});
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
getMenuData();
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 获取菜单结构数据
const getMenuData = () => {
state.menuData = [
{
id: 1,
label: '系统管理',
children: [
{
id: 11,
label: '菜单管理',
children: [
{
id: 111,
label: '菜单新增',
},
{
id: 112,
label: '菜单修改',
},
{
id: 113,
label: '菜单删除',
},
{
id: 114,
label: '菜单查询',
},
],
},
{
id: 12,
label: '角色管理',
children: [
{
id: 121,
label: '角色新增',
},
{
id: 122,
label: '角色修改',
},
{
id: 123,
label: '角色删除',
},
{
id: 124,
label: '角色查询',
},
],
},
{
id: 13,
label: '用户管理',
children: [
{
id: 131,
label: '用户新增',
},
{
id: 132,
label: '用户修改',
},
{
id: 133,
label: '用户删除',
},
{
id: 134,
label: '用户查询',
},
],
},
],
},
{
id: 2,
label: '权限管理',
children: [
{
id: 21,
label: '前端控制',
children: [
{
id: 211,
label: '页面权限',
},
{
id: 212,
label: '页面权限',
},
],
},
{
id: 22,
label: '后端控制',
children: [
{
id: 221,
label: '页面权限',
},
],
},
],
},
];
};
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
...toRefs(state),
};
<script setup name="systemAddRole">
const state = reactive({
isShowDialog: false,
ruleForm: {
roleName: '', // 角色名称
roleSign: '', // 角色标识
sort: 0, // 排序
status: true, // 角色状态
describe: '', // 角色描述
},
menuData: [],
menuProps: {
children: 'children',
label: 'label',
},
});
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
getMenuData();
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 获取菜单结构数据
const getMenuData = () => {
state.menuData = [
{
id: 1,
label: '系统管理',
children: [
{
id: 11,
label: '菜单管理',
children: [
{
id: 111,
label: '菜单新增',
},
{
id: 112,
label: '菜单修改',
},
{
id: 113,
label: '菜单删除',
},
{
id: 114,
label: '菜单查询',
},
],
},
{
id: 12,
label: '角色管理',
children: [
{
id: 121,
label: '角色新增',
},
{
id: 122,
label: '角色修改',
},
{
id: 123,
label: '角色删除',
},
{
id: 124,
label: '角色查询',
},
],
},
{
id: 13,
label: '用户管理',
children: [
{
id: 131,
label: '用户新增',
},
{
id: 132,
label: '用户修改',
},
{
id: 133,
label: '用户删除',
},
{
id: 134,
label: '用户查询',
},
],
},
],
},
{
id: 2,
label: '权限管理',
children: [
{
id: 21,
label: '前端控制',
children: [
{
id: 211,
label: '页面权限',
},
{
id: 212,
label: '页面权限',
},
],
},
{
id: 22,
label: '后端控制',
children: [
{
id: 221,
label: '页面权限',
},
],
},
],
},
];
};
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">

View File

@ -1,11 +1,11 @@
<template>
<div class="system-edit-role-container">
<el-dialog title="修改角色" v-model="isShowDialog" width="769px">
<el-form :model="ruleForm" size="small" label-width="90px">
<el-dialog title="修改角色" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="small" label-width="90px">
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="角色名称">
<el-input v-model="ruleForm.roleName" placeholder="请输入角色名称" clearable></el-input>
<el-input v-model="state.ruleForm.roleName" placeholder="请输入角色名称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
@ -15,27 +15,34 @@
<span>角色标识</span>
</el-tooltip>
</template>
<el-input v-model="ruleForm.roleSign" placeholder="请输入角色标识" clearable></el-input>
<el-input v-model="state.ruleForm.roleSign" placeholder="请输入角色标识" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="排序">
<el-input-number v-model="ruleForm.sort" :min="0" :max="999" controls-position="right" placeholder="请输入排序" class="w100" />
<el-input-number v-model="state.ruleForm.sort" :min="0" :max="999" controls-position="right" placeholder="请输入排序" class="w100" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="角色状态">
<el-switch v-model="ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
<el-switch v-model="state.ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="角色描述">
<el-input v-model="ruleForm.describe" type="textarea" placeholder="请输入角色描述" maxlength="150"></el-input>
<el-input v-model="state.ruleForm.describe" type="textarea" placeholder="请输入角色描述" maxlength="150"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="菜单权限">
<el-tree :data="menuData" :props="menuProps" :default-checked-keys="[112, 113]" node-key="id" show-checkbox class="menu-data-tree" />
<el-tree
:data="state.menuData"
:props="state.menuProps"
:default-checked-keys="[112, 113]"
node-key="id"
show-checkbox
class="menu-data-tree"
/>
</el-form-item>
</el-col>
</el-row>
@ -50,160 +57,152 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue';
export default {
name: 'systemEditRole',
setup() {
const state = reactive({
isShowDialog: false,
ruleForm: {
roleName: '', // 角色名称
roleSign: '', // 角色标识
sort: 0, // 排序
status: true, // 角色状态
describe: '', // 角色描述
},
menuData: [],
menuProps: {
children: 'children',
label: 'label',
},
});
// 打开弹窗
const openDialog = (row: Object) => {
state.ruleForm = row;
state.isShowDialog = true;
getMenuData();
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 获取菜单结构数据
const getMenuData = () => {
state.menuData = [
{
id: 1,
label: '系统管理',
children: [
{
id: 11,
label: '菜单管理',
children: [
{
id: 111,
label: '菜单新增',
},
{
id: 112,
label: '菜单修改',
},
{
id: 113,
label: '菜单删除',
},
{
id: 114,
label: '菜单查询',
},
],
},
{
id: 12,
label: '角色管理',
children: [
{
id: 121,
label: '角色新增',
},
{
id: 122,
label: '角色修改',
},
{
id: 123,
label: '角色删除',
},
{
id: 124,
label: '角色查询',
},
],
},
{
id: 13,
label: '用户管理',
children: [
{
id: 131,
label: '用户新增',
},
{
id: 132,
label: '用户修改',
},
{
id: 133,
label: '用户删除',
},
{
id: 134,
label: '用户查询',
},
],
},
],
},
{
id: 2,
label: '权限管理',
children: [
{
id: 21,
label: '前端控制',
children: [
{
id: 211,
label: '页面权限',
},
{
id: 212,
label: '页面权限',
},
],
},
{
id: 22,
label: '后端控制',
children: [
{
id: 221,
label: '页面权限',
},
],
},
],
},
];
};
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
...toRefs(state),
};
<script setup name="systemEditRole">
const state = reactive({
isShowDialog: false,
ruleForm: {
roleName: '', // 角色名称
roleSign: '', // 角色标识
sort: 0, // 排序
status: true, // 角色状态
describe: '', // 角色描述
},
menuData: [],
menuProps: {
children: 'children',
label: 'label',
},
});
// 打开弹窗
const openDialog = (row) => {
state.ruleForm = row;
state.isShowDialog = true;
getMenuData();
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 获取菜单结构数据
const getMenuData = () => {
state.menuData = [
{
id: 1,
label: '系统管理',
children: [
{
id: 11,
label: '菜单管理',
children: [
{
id: 111,
label: '菜单新增',
},
{
id: 112,
label: '菜单修改',
},
{
id: 113,
label: '菜单删除',
},
{
id: 114,
label: '菜单查询',
},
],
},
{
id: 12,
label: '角色管理',
children: [
{
id: 121,
label: '角色新增',
},
{
id: 122,
label: '角色修改',
},
{
id: 123,
label: '角色删除',
},
{
id: 124,
label: '角色查询',
},
],
},
{
id: 13,
label: '用户管理',
children: [
{
id: 131,
label: '用户新增',
},
{
id: 132,
label: '用户修改',
},
{
id: 133,
label: '用户删除',
},
{
id: 134,
label: '用户查询',
},
],
},
],
},
{
id: 2,
label: '权限管理',
children: [
{
id: 21,
label: '前端控制',
children: [
{
id: 211,
label: '页面权限',
},
{
id: 212,
label: '页面权限',
},
],
},
{
id: 22,
label: '后端控制',
children: [
{
id: 221,
label: '页面权限',
},
],
},
],
},
];
};
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">

View File

@ -16,7 +16,7 @@
新增角色
</el-button>
</div>
<el-table :data="tableData.data" style="width: 100%">
<el-table :data="state.tableData.data" style="width: 100%">
<el-table-column type="index" label="序号" width="50" />
<el-table-column prop="roleName" label="角色名称" show-overflow-tooltip></el-table-column>
<el-table-column prop="roleSign" label="角色标识" show-overflow-tooltip></el-table-column>
@ -42,11 +42,11 @@
class="mt15"
:pager-count="5"
:page-sizes="[10, 20, 30]"
v-model:current-page="tableData.param.pageNum"
v-model:current-page="state.tableData.param.pageNum"
background
v-model:page-size="tableData.param.pageSize"
v-model:page-size="state.tableData.param.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total"
:total="state.tableData.total"
>
</el-pagination>
</el-card>
@ -55,88 +55,72 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, ref } from 'vue';
<script setup name="systemRole">
import { ElMessageBox, ElMessage } from 'element-plus';
import AddRole from '/@/views/system/role/component/addRole.vue';
import EditRole from '/@/views/system/role/component/editRole.vue';
export default {
name: 'systemRole',
components: { AddRole, EditRole },
setup() {
const addRoleRef = ref();
const editRoleRef = ref();
const state: any = reactive({
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
// 初始化表格数据
const initTableData = () => {
const data: Array<object> = [];
for (let i = 0; i < 2; i++) {
data.push({
roleName: i === 0 ? '超级管理员' : '普通用户',
roleSign: i === 0 ? 'admin' : 'common',
describe: `测试角色${i + 1}`,
sort: i,
status: true,
createTime: new Date().toLocaleString(),
});
}
state.tableData.data = data;
state.tableData.total = state.tableData.data.length;
};
// 打开新增角色弹窗
const onOpenAddRole = () => {
addRoleRef.value.openDialog();
};
// 打开修改角色弹窗
const onOpenEditRole = (row: Object) => {
editRoleRef.value.openDialog(row);
};
// 删除角色
const onRowDel = (row: Object) => {
ElMessageBox.confirm(`此操作将永久删除角色名称:“${row.roleName}”,是否继续?`, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
ElMessage.success('删除成功');
})
.catch(() => {});
};
// 分页改变
const onHandleSizeChange = (val: number) => {
state.tableData.param.pageSize = val;
};
// 分页改变
const onHandleCurrentChange = (val: number) => {
state.tableData.param.pageNum = val;
};
// 页面加载时
onMounted(() => {
initTableData();
});
return {
addRoleRef,
editRoleRef,
onOpenAddRole,
onOpenEditRole,
onRowDel,
onHandleSizeChange,
onHandleCurrentChange,
...toRefs(state),
};
const addRoleRef = ref();
const editRoleRef = ref();
const state = reactive({
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
// 初始化表格数据
const initTableData = () => {
const data = [];
for (let i = 0; i < 2; i++) {
data.push({
roleName: i === 0 ? '超级管理员' : '普通用户',
roleSign: i === 0 ? 'admin' : 'common',
describe: `测试角色${i + 1}`,
sort: i,
status: true,
createTime: new Date().toLocaleString(),
});
}
state.tableData.data = data;
state.tableData.total = state.tableData.data.length;
};
// 打开新增角色弹窗
const onOpenAddRole = () => {
addRoleRef.value.openDialog();
};
// 打开修改角色弹窗
const onOpenEditRole = (row) => {
editRoleRef.value.openDialog(row);
};
// 删除角色
const onRowDel = (row) => {
ElMessageBox.confirm(`此操作将永久删除角色名称:“${row.roleName}”,是否继续?`, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
ElMessage.success('删除成功');
})
.catch(() => {});
};
// 分页改变
const onHandleSizeChange = (val) => {
state.tableData.param.pageSize = val;
};
// 分页改变
const onHandleCurrentChange = (val) => {
state.tableData.param.pageNum = val;
};
// 页面加载时
onMounted(() => {
initTableData();
});
</script>
<style scoped lang="scss">

View File

@ -1,21 +1,21 @@
<template>
<div class="system-add-user-container">
<el-dialog title="新增用户" v-model="isShowDialog" width="769px">
<el-form :model="ruleForm" size="small" label-width="90px">
<el-dialog title="新增用户" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="small" label-width="90px">
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="账户名称">
<el-input v-model="ruleForm.userName" placeholder="请输入账户名称" clearable></el-input>
<el-input v-model="state.ruleForm.userName" placeholder="请输入账户名称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="用户昵称">
<el-input v-model="ruleForm.userNickname" placeholder="请输入用户昵称" clearable></el-input>
<el-input v-model="state.ruleForm.userNickname" placeholder="请输入用户昵称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="关联角色">
<el-select v-model="ruleForm.roleSign" placeholder="请选择" clearable class="w100">
<el-select v-model="state.ruleForm.roleSign" placeholder="请选择" clearable class="w100">
<el-option label="超级管理员" value="admin"></el-option>
<el-option label="普通用户" value="common"></el-option>
</el-select>
@ -24,12 +24,12 @@
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="部门">
<el-cascader
:options="deptData"
:options="state.deptData"
:props="{ checkStrictly: true, value: 'deptName', label: 'deptName' }"
placeholder="请选择部门"
clearable
class="w100"
v-model="ruleForm.department"
v-model="state.ruleForm.department"
>
<template #default="{ node, data }">
<span>{{ data.deptName }}</span>
@ -40,17 +40,17 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="手机号">
<el-input v-model="ruleForm.phone" placeholder="请输入手机号" clearable></el-input>
<el-input v-model="state.ruleForm.phone" placeholder="请输入手机号" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="邮箱">
<el-input v-model="ruleForm.email" placeholder="请输入" clearable></el-input>
<el-input v-model="state.ruleForm.email" placeholder="请输入" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="性别">
<el-select v-model="ruleForm.sex" placeholder="请选择" clearable class="w100">
<el-select v-model="state.ruleForm.sex" placeholder="请选择" clearable class="w100">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
@ -58,22 +58,22 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="账户密码">
<el-input v-model="ruleForm.password" placeholder="请输入" type="password" clearable></el-input>
<el-input v-model="state.ruleForm.password" placeholder="请输入" type="password" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="账户过期">
<el-date-picker v-model="ruleForm.overdueTime" type="date" placeholder="请选择" class="w100"> </el-date-picker>
<el-date-picker v-model="state.ruleForm.overdueTime" type="date" placeholder="请选择" class="w100"> </el-date-picker>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="用户状态">
<el-switch v-model="ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
<el-switch v-model="state.ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="用户描述">
<el-input v-model="ruleForm.describe" type="textarea" placeholder="请输入用户描述" maxlength="150"></el-input>
<el-input v-model="state.ruleForm.describe" type="textarea" placeholder="请输入用户描述" maxlength="150"></el-input>
</el-form-item>
</el-col>
</el-row>
@ -88,84 +88,76 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs, onMounted } from 'vue';
export default {
name: 'systemAddUser',
setup() {
const state = reactive({
isShowDialog: false,
ruleForm: {
userName: '', // 账户名称
userNickname: '', // 用户昵称
roleSign: '', // 关联角色
department: [], // 部门
phone: '', // 手机号
email: '', // 邮箱
sex: '', // 性别
password: '', // 账户密码
overdueTime: '', // 账户过期
status: true, // 用户状态
describe: '', // 用户描述
},
deptData: [], // 部门数据
});
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 初始化部门数据
const initTableData = () => {
state.deptData.push({
deptName: 'vueNextAdmin',
<script setup name="systemAddUser">
const state = reactive({
isShowDialog: false,
ruleForm: {
userName: '', // 账户名称
userNickname: '', // 用户昵称
roleSign: '', // 关联角色
department: [], // 部门
phone: '', // 手机号
email: '', // 邮箱
sex: '', // 性别
password: '', // 账户密码
overdueTime: '', // 账户过期
status: true, // 用户状态
describe: '', // 用户描述
},
deptData: [], // 部门数据
});
// 打开弹窗
const openDialog = () => {
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 初始化部门数据
const initTableData = () => {
state.deptData.push({
deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '顶级部门',
id: Math.random(),
children: [
{
deptName: 'IT外包服务',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '顶级部门',
describe: '总部',
id: Math.random(),
children: [
{
deptName: 'IT外包服务',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '部',
id: Math.random(),
},
{
deptName: '资本控股',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '分部',
id: Math.random(),
},
],
});
};
// 页面加载时
onMounted(() => {
initTableData();
});
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
...toRefs(state),
};
},
},
{
deptName: '资本控股',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '部',
id: Math.random(),
},
],
});
};
// 页面加载时
onMounted(() => {
initTableData();
});
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@ -1,21 +1,21 @@
<template>
<div class="system-edit-user-container">
<el-dialog title="修改用户" v-model="isShowDialog" width="769px">
<el-form :model="ruleForm" size="small" label-width="90px">
<el-dialog title="修改用户" v-model="state.isShowDialog" width="769px">
<el-form :model="state.ruleForm" size="small" label-width="90px">
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="账户名称">
<el-input v-model="ruleForm.userName" placeholder="请输入账户名称" clearable></el-input>
<el-input v-model="state.ruleForm.userName" placeholder="请输入账户名称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="用户昵称">
<el-input v-model="ruleForm.userNickname" placeholder="请输入用户昵称" clearable></el-input>
<el-input v-model="state.ruleForm.userNickname" placeholder="请输入用户昵称" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="关联角色">
<el-select v-model="ruleForm.roleSign" placeholder="请选择" clearable class="w100">
<el-select v-model="state.ruleForm.roleSign" placeholder="请选择" clearable class="w100">
<el-option label="超级管理员" value="admin"></el-option>
<el-option label="普通用户" value="common"></el-option>
</el-select>
@ -24,12 +24,12 @@
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="部门">
<el-cascader
:options="deptData"
:options="state.deptData"
:props="{ checkStrictly: true, value: 'deptName', label: 'deptName' }"
placeholder="请选择部门"
clearable
class="w100"
v-model="ruleForm.department"
v-model="state.ruleForm.department"
>
<template #default="{ node, data }">
<span>{{ data.deptName }}</span>
@ -40,17 +40,17 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="手机号">
<el-input v-model="ruleForm.phone" placeholder="请输入手机号" clearable></el-input>
<el-input v-model="state.ruleForm.phone" placeholder="请输入手机号" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="邮箱">
<el-input v-model="ruleForm.email" placeholder="请输入" clearable></el-input>
<el-input v-model="state.ruleForm.email" placeholder="请输入" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="性别">
<el-select v-model="ruleForm.sex" placeholder="请选择" clearable class="w100">
<el-select v-model="state.ruleForm.sex" placeholder="请选择" clearable class="w100">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
@ -58,22 +58,22 @@
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="账户密码">
<el-input v-model="ruleForm.password" placeholder="请输入" type="password" clearable></el-input>
<el-input v-model="state.ruleForm.password" placeholder="请输入" type="password" clearable></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="账户过期">
<el-date-picker v-model="ruleForm.overdueTime" type="date" placeholder="请选择" class="w100"> </el-date-picker>
<el-date-picker v-model="state.ruleForm.overdueTime" type="date" placeholder="请选择" class="w100"> </el-date-picker>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="用户状态">
<el-switch v-model="ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
<el-switch v-model="state.ruleForm.status" inline-prompt active-text="启" inactive-text="禁"></el-switch>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
<el-form-item label="用户描述">
<el-input v-model="ruleForm.describe" type="textarea" placeholder="请输入用户描述" maxlength="150"></el-input>
<el-input v-model="state.ruleForm.describe" type="textarea" placeholder="请输入用户描述" maxlength="150"></el-input>
</el-form-item>
</el-col>
</el-row>
@ -88,85 +88,77 @@
</div>
</template>
<script lang="ts">
import { reactive, toRefs, onMounted } from 'vue';
export default {
name: 'systemEditUser',
setup() {
const state = reactive({
isShowDialog: false,
ruleForm: {
userName: '', // 账户名称
userNickname: '', // 用户昵称
roleSign: '', // 关联角色
department: [], // 部门
phone: '', // 手机号
email: '', // 邮箱
sex: '', // 性别
password: '', // 账户密码
overdueTime: '', // 账户过期
status: true, // 用户状态
describe: '', // 用户描述
},
deptData: [], // 部门数据
});
// 打开弹窗
const openDialog = (row: Object) => {
state.ruleForm = row;
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 初始化部门数据
const initTableData = () => {
state.deptData.push({
deptName: 'vueNextAdmin',
<script setup name="systemEditUser">
const state = reactive({
isShowDialog: false,
ruleForm: {
userName: '', // 账户名称
userNickname: '', // 用户昵称
roleSign: '', // 关联角色
department: [], // 部门
phone: '', // 手机号
email: '', // 邮箱
sex: '', // 性别
password: '', // 账户密码
overdueTime: '', // 账户过期
status: true, // 用户状态
describe: '', // 用户描述
},
deptData: [], // 部门数据
});
// 打开弹窗
const openDialog = (row) => {
state.ruleForm = row;
state.isShowDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 新增
const onSubmit = () => {
closeDialog();
};
// 初始化部门数据
const initTableData = () => {
state.deptData.push({
deptName: 'vueNextAdmin',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '顶级部门',
id: Math.random(),
children: [
{
deptName: 'IT外包服务',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '顶级部门',
describe: '总部',
id: Math.random(),
children: [
{
deptName: 'IT外包服务',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '部',
id: Math.random(),
},
{
deptName: '资本控股',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '分部',
id: Math.random(),
},
],
});
};
// 页面加载时
onMounted(() => {
initTableData();
});
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
...toRefs(state),
};
},
},
{
deptName: '资本控股',
createTime: new Date().toLocaleString(),
status: true,
sort: Number.parseInt(Math.random()),
describe: '部',
id: Math.random(),
},
],
});
};
// 页面加载时
onMounted(() => {
initTableData();
});
// 暴露变量
defineExpose({
openDialog,
});
</script>

View File

@ -16,7 +16,7 @@
新增用户
</el-button>
</div>
<el-table :data="tableData.data" style="width: 100%">
<el-table :data="state.tableData.data" style="width: 100%">
<el-table-column type="index" label="序号" width="50" />
<el-table-column prop="userName" label="账户名称" show-overflow-tooltip></el-table-column>
<el-table-column prop="userNickname" label="用户昵称" show-overflow-tooltip></el-table-column>
@ -45,11 +45,11 @@
class="mt15"
:pager-count="5"
:page-sizes="[10, 20, 30]"
v-model:current-page="tableData.param.pageNum"
v-model:current-page="state.tableData.param.pageNum"
background
v-model:page-size="tableData.param.pageSize"
v-model:page-size="state.tableData.param.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total"
:total="state.tableData.total"
>
</el-pagination>
</el-card>
@ -58,94 +58,78 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, ref } from 'vue';
<script setup name="systemUser">
import { ElMessageBox, ElMessage } from 'element-plus';
import AddUer from '/@/views/system/user/component/addUser.vue';
import EditUser from '/@/views/system/user/component/editUser.vue';
export default {
name: 'systemUser',
components: { AddUer, EditUser },
setup() {
const addUserRef = ref();
const editUserRef = ref();
const state: any = reactive({
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
// 初始化表格数据
const initTableData = () => {
const data: Array<object> = [];
for (let i = 0; i < 2; i++) {
data.push({
userName: i === 0 ? 'admin' : 'test',
userNickname: i === 0 ? '我是管理员' : '我是普通用户',
roleSign: i === 0 ? 'admin' : 'common',
department: i === 0 ? ['vueNextAdmin', 'IT外包服务'] : ['vueNextAdmin', '资本控股'],
phone: '12345678910',
email: 'vueNextAdmin@123.com',
sex: '女',
password: '123456',
overdueTime: new Date(),
status: true,
describe: i === 0 ? '不可删除' : '测试用户',
createTime: new Date().toLocaleString(),
});
}
state.tableData.data = data;
state.tableData.total = state.tableData.data.length;
};
// 打开新增用户弹窗
const onOpenAddUser = () => {
addUserRef.value.openDialog();
};
// 打开修改用户弹窗
const onOpenEditUser = (row: Object) => {
editUserRef.value.openDialog(row);
};
// 删除用户
const onRowDel = (row: Object) => {
ElMessageBox.confirm(`此操作将永久删除账户名称:“${row.userName}”,是否继续?`, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
ElMessage.success('删除成功');
})
.catch(() => {});
};
// 分页改变
const onHandleSizeChange = (val: number) => {
state.tableData.param.pageSize = val;
};
// 分页改变
const onHandleCurrentChange = (val: number) => {
state.tableData.param.pageNum = val;
};
// 页面加载时
onMounted(() => {
initTableData();
});
return {
addUserRef,
editUserRef,
onOpenAddUser,
onOpenEditUser,
onRowDel,
onHandleSizeChange,
onHandleCurrentChange,
...toRefs(state),
};
const addUserRef = ref();
const editUserRef = ref();
const state = reactive({
tableData: {
data: [],
total: 0,
loading: false,
param: {
pageNum: 1,
pageSize: 10,
},
},
});
// 初始化表格数据
const initTableData = () => {
const data = [];
for (let i = 0; i < 2; i++) {
data.push({
userName: i === 0 ? 'admin' : 'test',
userNickname: i === 0 ? '我是管理员' : '我是普通用户',
roleSign: i === 0 ? 'admin' : 'common',
department: i === 0 ? ['vueNextAdmin', 'IT外包服务'] : ['vueNextAdmin', '资本控股'],
phone: '12345678910',
email: 'vueNextAdmin@123.com',
sex: '女',
password: '123456',
overdueTime: new Date(),
status: true,
describe: i === 0 ? '不可删除' : '测试用户',
createTime: new Date().toLocaleString(),
});
}
state.tableData.data = data;
state.tableData.total = state.tableData.data.length;
};
// 打开新增用户弹窗
const onOpenAddUser = () => {
addUserRef.value.openDialog();
};
// 打开修改用户弹窗
const onOpenEditUser = (row) => {
editUserRef.value.openDialog(row);
};
// 删除用户
const onRowDel = (row) => {
ElMessageBox.confirm(`此操作将永久删除账户名称:“${row.userName}”,是否继续?`, '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
ElMessage.success('删除成功');
})
.catch(() => {});
};
// 分页改变
const onHandleSizeChange = (val) => {
state.tableData.param.pageSize = val;
};
// 分页改变
const onHandleCurrentChange = (val) => {
state.tableData.param.pageNum = val;
};
// 页面加载时
onMounted(() => {
initTableData();
});
</script>
<style scoped lang="scss">

View File

@ -1,72 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"lib": ["esnext", "dom", "dom.iterable", "scripthost"] /* Specify library files to be included in the compilation. */,
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"jsx": "preserve" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
// "declaration": true /* Generates corresponding '.d.ts' file. */,
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true /* Import emit helpers from 'tslib'. */,
// "downlevelIteration": true /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */,
// "isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */,
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
/* Module Resolution Options */
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
"baseUrl": "." /* Base directory to resolve non-absolute module names. */,
"paths": {
"/@/*": ["src/*"]
} /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */,
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
"types": ["vite/client"] /* Type declaration files to be included in compilation. */,
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

View File

@ -1,20 +1,28 @@
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
import AutoImport from 'unplugin-auto-import/vite';
import vueSetupExtend from 'vite-plugin-vue-setup-extend';
import { resolve } from 'path';
import type { UserConfig } from 'vite';
import { loadEnv } from './src/utils/viteBuild';
const pathResolve = (dir: string): any => {
const pathResolve = (dir) => {
return resolve(__dirname, '.', dir);
};
const { VITE_PORT, VITE_OPEN, VITE_PUBLIC_PATH } = loadEnv();
const alias: Record<string, string> = {
const alias = {
'/@': pathResolve('./src/'),
};
const viteConfig: UserConfig = {
plugins: [vue()],
const viteConfig = {
plugins: [
vue(),
vueSetupExtend(),
AutoImport({
imports: ['vue', 'vue-router', 'vuex'],
}),
],
root: process.cwd(),
resolve: { alias },
base: process.env.NODE_ENV === 'production' ? VITE_PUBLIC_PATH : './',
@ -62,4 +70,4 @@ const viteConfig: UserConfig = {
},
};
export default viteConfig;
export default defineConfig(viteConfig);