'admin-21.12.04:同步master分支v1.2.0版本内容,具体查看masterCHANGELOG.md'

This commit is contained in:
lyt 2021-12-04 15:11:30 +08:00
parent 5fdef5fc69
commit 6de2a537c3
59 changed files with 2893 additions and 568 deletions

View File

@ -44,6 +44,7 @@ module.exports = {
'vue/comment-directive': 'off',
'vue/no-parsing-error': 'off',
'vue/no-deprecated-v-on-native-modifier': 'off',
'vue/multi-word-component-names': 'off',
'no-useless-escape': 'off',
'no-sparse-arrays': 'off',
'no-prototype-builtins': 'off',

View File

@ -2,6 +2,12 @@
🎉🎉🔥 `vue-next-admin-template` 基于 vue-next-admin-v1.1.2 版本) vue3.x 、Typescript、vite、Element plus 等适配手机、平板、pc 的后台开源免费模板库vue2.x 请切换 vue-prev-admin 分支)
## 0.2.0
`2021.12.04`
- 🎉 同步 master 分支 v1.2.0 版本内容,具体查看 master CHANGELOG.md
## 0.1.0
`2021.10.17`

View File

@ -20,7 +20,7 @@
<p>&nbsp;</p>
</div>
#### 🌈 介绍(基础版 ts不带国际化)
#### 🌈 介绍 基础版 ts不带国际化
基于 vue3.x + CompositionAPI + typescript + vite + element plus + vue-router-next + next.vuex适配手机、平板、pc 的后台开源免费模板,希望减少工作量,帮助大家实现快速开发。

View File

@ -1,22 +1,22 @@
{
"name": "vue-next-admin-template",
"version": "0.1.0",
"version": "0.2.0",
"description": "vue3 vite next admin template",
"author": "lyt_20201208",
"license": "MIT",
"scripts": {
"dev": "vite",
"dev": "vite --force",
"build": "vite build",
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
},
"dependencies": {
"axios": "^0.23.0",
"echarts": "^5.2.1",
"element-plus": "^1.1.0-beta.20",
"axios": "^0.24.0",
"echarts": "^5.2.2",
"element-plus": "^1.2.0-beta.5",
"qrcodejs2-fixes": "^0.0.2",
"mitt": "^3.0.0",
"nprogress": "^0.2.0",
"screenfull": "^5.1.0",
"screenfull": "^6.0.0",
"sortablejs": "^1.14.0",
"vue": "^3.2.20",
"vue-clipboard3": "^1.0.1",
@ -26,21 +26,21 @@
"devDependencies": {
"@types/axios": "^0.14.0",
"@types/clipboard": "^2.0.1",
"@types/node": "^16.11.0",
"@types/node": "^16.11.11",
"@types/nprogress": "^0.2.0",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"@vitejs/plugin-vue": "^1.9.3",
"@vue/compiler-sfc": "^3.2.20",
"@typescript-eslint/eslint-plugin": "^5.5.0",
"@typescript-eslint/parser": "^5.5.0",
"@vitejs/plugin-vue": "^1.10.1",
"@vue/compiler-sfc": "^3.2.23",
"dotenv": "^10.0.0",
"eslint": "^8.0.1",
"eslint-plugin-vue": "^7.19.1",
"prettier": "^2.4.1",
"sass": "^1.43.2",
"sass-loader": "^12.2.0",
"typescript": "^4.4.4",
"vite": "^2.6.7",
"vue-eslint-parser": "^7.11.0"
"eslint": "^8.4.0",
"eslint-plugin-vue": "^8.1.1",
"prettier": "^2.5.0",
"sass": "^1.44.0",
"sass-loader": "^12.3.0",
"typescript": "^4.5.2",
"vite": "^2.6.14",
"vue-eslint-parser": "^8.0.1"
},
"browserslist": [
"> 1%",

View File

@ -9,7 +9,7 @@
import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, nextTick, defineComponent, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useStore } from '/@/store/index';
import { useTitle } from '/@/utils/setWebTitle';
import other from '/@/utils/other';
import { Local, Session } from '/@/utils/storage';
import setIntroduction from '/@/utils/setIconfont';
import LockScreen from '/@/layout/lockScreen/index.vue';
@ -23,7 +23,6 @@ export default defineComponent({
const setingsRef = ref();
const route = useRoute();
const store = useStore();
const title = useTitle();
//
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
@ -61,7 +60,7 @@ export default defineComponent({
watch(
() => route.path,
() => {
title();
other.useTitle();
}
);
return {

View File

@ -14,29 +14,33 @@
@blur="onIconBlur"
>
<template #prepend>
<i
:class="[
fontIconPrefix === '' ? prepend : fontIconPrefix,
{ iconfont: fontIconTabsIndex === 0 },
{ ele: fontIconTabsIndex === 1 },
{ fa: fontIconTabsIndex === 2 },
]"
<SvgIcon
:name="fontIconPrefix === '' ? prepend : fontIconPrefix"
class="font14"
></i>
v-if="fontIconPrefix === '' ? prepend?.indexOf('element') > -1 : fontIconPrefix?.indexOf('element') > -1"
/>
<i v-else :class="fontIconPrefix === '' ? prepend : 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-title">{{ title }}</div>
<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>
</div>
</div>
<div class="icon-selector-warp-row">
<el-scrollbar>
<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="flex-margin">
<div class="icon-selector-warp-item-value">
<i :class="v"></i>
<SvgIcon :name="v" />
</div>
</div>
</div>
@ -61,7 +65,7 @@ export default {
//
prepend: {
type: String,
default: () => 'el-icon-thumb',
default: () => 'elementPointer',
},
//
placeholder: {
@ -104,6 +108,7 @@ export default {
},
setup(props, { emit }) {
const inputWidthRef = ref();
const selectorScrollbarRef = ref();
const state: any = reactive({
fontIconPrefix: '',
fontIconVisible: false,
@ -112,6 +117,8 @@ export default {
fontIconTabsIndex: 0,
fontIconSheetsList: [],
fontIconPlaceholder: '',
fontIconType: 'ali',
fontIconShow: true,
});
// input modelValue input placeholder
const onIconFocus = () => {
@ -153,21 +160,19 @@ export default {
});
};
//
const initFontIconData = async () => {
if (props.type === 'ali') {
const initFontIconData = async (type: string) => {
state.fontIconSheetsList = [];
if (type === 'ali') {
await initIconfont.ali().then((res: any) => {
state.fontIconTabsIndex = 0;
// 使 `iconfont xxx`
state.fontIconSheetsList = res.map((i) => `iconfont ${i}`);
});
} else if (props.type === 'ele') {
} else if (type === 'ele') {
await initIconfont.ele().then((res: any) => {
state.fontIconTabsIndex = 1;
state.fontIconSheetsList = res;
});
} else if (props.type === 'awe') {
} else if (type === 'awe') {
await initIconfont.awe().then((res: any) => {
state.fontIconTabsIndex = 2;
// fontawesome使 `fa xxx`
state.fontIconSheetsList = res.map((i) => `fa ${i}`);
});
@ -177,14 +182,19 @@ export default {
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;
if (state.fontIconTabsIndex === 0) state.fontIconPrefix = `${v}`;
else if (state.fontIconTabsIndex === 1) state.fontIconPrefix = `${v}`;
else if (state.fontIconTabsIndex === 2) state.fontIconPrefix = `${v}`;
state.fontIconPrefix = v;
emit('get', state.fontIconPrefix);
emit('update:modelValue', state.fontIconPrefix);
};
@ -196,7 +206,15 @@ export default {
};
//
onMounted(() => {
initFontIconData();
// 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();
});
@ -209,8 +227,10 @@ export default {
);
return {
inputWidthRef,
selectorScrollbarRef,
fontIconSheetsFilterList,
onColClick,
onIconChange,
onClearFontIcon,
onIconFocus,
onIconBlur,

View File

@ -0,0 +1,28 @@
<script lang="ts">
// https://v3.cn.vuejs.org/guide/render-function.html
import { h, resolveComponent, defineComponent } from 'vue';
export default defineComponent({
name: 'svgIcon',
props: {
// svg
name: {
type: String,
},
// svg
size: {
type: Number,
},
// svg
color: {
type: String,
},
},
setup(props) {
if (props.name?.indexOf('element') > -1) {
return () => h('i', { class: 'el-icon', style: `--font-size: ${props.size};--color: ${props.color}` }, [h(resolveComponent(props.name))]);
} else {
return () => h('i', { class: props.name, style: `font-size: ${props.size};color: ${props.color}` });
}
},
});
</script>

View File

@ -16,7 +16,7 @@
:title="v.meta.title"
>
<div :class="setColumnsAsidelayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
<i :class="v.meta.icon"></i>
<SvgIcon :name="v.meta.icon" />
<div class="columns-vertical-title font12">
{{
v.meta.title && v.meta.title.length >= 4 ? v.meta.title.substr(0, setColumnsAsidelayout === 'columns-vertical' ? 4 : 3) : v.meta.title
@ -25,7 +25,7 @@
</div>
<div :class="setColumnsAsidelayout" v-else>
<a :href="v.meta.isLink" target="_blank">
<i :class="v.meta.icon"></i>
<SvgIcon :name="v.meta.icon" />
<div class="columns-vertical-title font12">
{{
v.meta.title && v.meta.title.length >= 4

View File

@ -20,7 +20,7 @@
<div class="layout-lock-screen-date-box-info">{{ time.mdq }}</div>
</div>
<div class="layout-lock-screen-date-top">
<i class="el-icon-top"></i>
<SvgIcon name="elementTop" />
<div class="layout-lock-screen-date-top-text">上滑解锁</div>
</div>
</div>
@ -39,15 +39,19 @@
@keyup.enter.native.stop="onLockScreenSubmit()"
>
<template #append>
<el-button icon="el-icon-right" @click="onLockScreenSubmit"></el-button>
<el-button @click="onLockScreenSubmit">
<el-icon>
<elementRight />
</el-icon>
</el-button>
</template>
</el-input>
</div>
</div>
<div class="layout-lock-screen-login-icon">
<i class="el-icon-microphone"></i>
<i class="el-icon-alarm-clock"></i>
<i class="el-icon-switch-button"></i>
<SvgIcon name="elementMicrophone" />
<SvgIcon name="elementClock" />
<SvgIcon name="elementSwitchButton" />
</div>
</div>
</transition>

View File

@ -33,7 +33,7 @@ export default {
watch(
() => route.path,
() => {
proxy.$refs.layoutDefaultsScrollbarRef.wrap.scrollTop = 0;
proxy.$refs.layoutDefaultsScrollbarRef.wrap$.scrollTop = 0;
}
);
return {

View File

@ -9,10 +9,10 @@
<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">
<i :class="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon"></i>{{ v.meta.title }}
<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)">
<i :class="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon"></i>{{ v.meta.title }}
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon" />{{ v.meta.title }}
</a>
</el-breadcrumb-item>
</transition-group>

View File

@ -1,7 +1,7 @@
<template>
<div class="layout-navbars-close-full" v-if="isTagsViewCurrenFull">
<div class="layout-navbars-close-full-box" title="关闭全屏" @click="onCloseFullscreen">
<i class="el-icon-close"></i>
<SvgIcon name="elementClose" />
</div>
</div>
</template>

View File

@ -5,11 +5,15 @@
v-model="menuQuery"
:fetch-suggestions="menuSearch"
placeholder="菜单搜索:支持中文、路由路径"
prefix-icon="el-icon-search"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
@blur="onSearchBlur"
>
<template #prefix>
<el-icon class="el-input__icon">
<elementSearch />
</el-icon>
</template>
<template #default="{ item }">
<div><i :class="item.meta.icon" class="mr10"></i>{{ item.meta.title }}</div>
</template>

View File

@ -38,7 +38,7 @@
<!-- 菜单 / 顶栏 -->
<el-divider content-position="left">菜单 / 顶栏</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ 顶栏背景 }}</div>
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏背景</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.topBar" size="small" @change="onBgColorPickerChange('topBar')"> </el-color-picker>
</div>
@ -355,17 +355,16 @@
</div>
<div class="copy-config">
<el-alert title="点击下方按钮,复制布局配置去 `src/store/modules/themeConfig.ts` 中修改。" type="warning" :closable="false"> </el-alert>
<el-button
size="small"
class="copy-config-btn"
icon="el-icon-document-copy"
type="primary"
ref="copyConfigBtnRef"
@click="onCopyConfigClick"
>
<el-button size="small" class="copy-config-btn" type="primary" ref="copyConfigBtnRef" @click="onCopyConfigClick">
<el-icon>
<elementCopyDocument />
</el-icon>
一键复制配置
</el-button>
<el-button size="small" class="copy-config-btn-reset" icon="el-icon-refresh-right" type="info" @click="onResetConfigClick">
<el-button size="small" class="copy-config-btn-reset" type="info" @click="onResetConfigClick">
<el-icon>
<elementRefreshRight />
</el-icon>
一键恢复默认
</el-button>
</div>

View File

@ -14,7 +14,9 @@
</template>
</el-dropdown>
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
<i class="el-icon-search" title="菜单搜索"></i>
<el-icon title="菜单搜索">
<elementSearch />
</el-icon>
</div>
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
<i class="icon-skin iconfont" title="布局配置"></i>
@ -23,7 +25,9 @@
<el-popover placement="bottom" trigger="click" v-model:visible="isShowUserNewsPopover" :width="300" popper-class="el-popover-pupop-user-news">
<template #reference>
<el-badge :is-dot="true" @click="isShowUserNewsPopover = !isShowUserNewsPopover">
<i class="el-icon-bell" title="消息"></i>
<el-icon title="消息">
<elementBell />
</el-icon>
</el-badge>
</template>
<transition name="el-zoom-in-top">
@ -38,7 +42,9 @@
<span class="layout-navbars-breadcrumb-user-link">
<img :src="getUserInfos.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
{{ getUserInfos.userName === '' ? 'test' : getUserInfos.userName }}
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right">
<elementArrowDown />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>

View File

@ -19,7 +19,7 @@
v-if="!v.affix"
@click="onCurrentContextmenuClick(v.contextMenuClickId)"
>
<i :class="v.icon"></i>
<SvgIcon :name="v.icon" />
<span>{{ v.txt }}</span>
</li>
</template>
@ -42,10 +42,10 @@ export default defineComponent({
const state = reactive({
isShow: false,
dropdownList: [
{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'el-icon-refresh-right' },
{ contextMenuClickId: 1, txt: '关闭', affix: false, icon: 'el-icon-close' },
{ contextMenuClickId: 2, txt: '关闭其它', affix: false, icon: 'el-icon-circle-close' },
{ contextMenuClickId: 3, txt: '全部关闭', affix: false, icon: 'el-icon-folder-delete' },
{ 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: {},

View File

@ -6,7 +6,7 @@
v-for="(v, k) in tagsViewList"
:key="k"
class="layout-navbars-tagsview-ul-li"
:data-name="v.name"
:data-name="v.url"
:class="{ 'is-active': isActive(v) }"
@contextmenu.prevent="onContextmenu(v, $event)"
@click="onTagsClick(v, k)"
@ -20,18 +20,20 @@
<i class="layout-navbars-tagsview-ul-li-iconfont" :class="v.meta.icon" v-if="!isActive(v) && getThemeConfig.isTagsviewIcon"></i>
<span>{{ v.meta.title }}</span>
<template v-if="isActive(v)">
<i class="el-icon-refresh-right ml5" @click.stop="refreshCurrentTagsView($route.fullPath)"></i>
<i
class="el-icon-close layout-navbars-tagsview-ul-li-icon layout-icon-active"
<SvgIcon name="elementRefreshRight" class="ml5" @click.stop="refreshCurrentTagsView($route.fullPath)" />
<SvgIcon
name="elementClose"
class="layout-navbars-tagsview-ul-li-icon layout-icon-active"
v-if="!v.meta.isAffix"
@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
></i>
/>
</template>
<i
class="el-icon-close layout-navbars-tagsview-ul-li-icon layout-icon-three"
<SvgIcon
name="elementClose"
class="layout-navbars-tagsview-ul-li-icon layout-icon-three"
v-if="!v.meta.isAffix"
@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.url)"
></i>
/>
</li>
</ul>
</el-scrollbar>
@ -324,7 +326,7 @@ export default {
};
//
const onHandleScroll = (e: any) => {
proxy.$refs.scrollbarRef.$refs.wrap.scrollLeft += e.wheelDelta / 4;
proxy.$refs.scrollbarRef.$refs.wrap$.scrollLeft += e.wheelDelta / 4;
};
// tagsView
const tagsViewmoveToCurrentTag = () => {
@ -341,7 +343,7 @@ export default {
// li
let liLast: any = tagsRefs.value[tagsRefs.value.length - 1];
//
let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap;
let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap$;
//
let scrollS = scrollRefs.scrollWidth;
//
@ -401,7 +403,7 @@ export default {
state.sortable && state.sortable.destroy();
state.sortable = Sortable.create(el, {
animation: 300,
dataIdAttr: 'data-name',
dataIdAttr: 'data-url',
disabled: getThemeConfig.value.isSortableTagsView ? false : true,
onEnd: () => {
const sortEndList: any = [];

View File

@ -5,19 +5,19 @@
<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>
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<SvgIcon :name="val.meta.icon" />
<span>{{ val.meta.title }}</span>
</template>
<SubItem :chil="val.children" />
</el-sub-menu>
<el-menu-item :index="val.path" :key="val.path" v-else>
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<SvgIcon :name="val.meta.icon" />
{{ val.meta.title }}
</template>
<template #title v-else>
<a :href="val.meta.isLink" target="_blank" rel="opener">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<SvgIcon :name="val.meta.icon" />
{{ val.meta.title }}
</a>
</template>

View File

@ -2,19 +2,19 @@
<template v-for="val in chils">
<el-sub-menu :index="val.path" :key="val.path" v-if="val.children && val.children.length > 0">
<template #title>
<i :class="val.meta.icon"></i>
<SvgIcon :name="val.meta.icon" />
<span>{{ val.meta.title }}</span>
</template>
<sub-item :chil="val.children" />
</el-sub-menu>
<el-menu-item :index="val.path" :key="val.path" v-else>
<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<SvgIcon :name="val.meta.icon" />
<span>{{ val.meta.title }}</span>
</template>
<template v-else>
<a :href="val.meta.isLink" target="_blank" rel="opener">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<SvgIcon :name="val.meta.icon" />
{{ val.meta.title }}
</a>
</template>

View File

@ -10,13 +10,13 @@
<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>
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<SvgIcon :name="val.meta.icon" />
<span>{{ val.meta.title }}</span>
</template>
<SubItem :chil="val.children" />
</el-sub-menu>
<el-menu-item :index="val.path" :key="val.path" v-else>
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<SvgIcon :name="val.meta.icon" />
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<span>{{ val.meta.title }}</span>
</template>

View File

@ -3,7 +3,7 @@ import App from './App.vue';
import router from './router';
import { store, key } from './store';
import { directive } from '/@/utils/directive';
import { globalComponentSize } from '/@/utils/componentSize';
import other from '/@/utils/other';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
@ -11,8 +11,10 @@ import '/@/theme/index.scss';
import mitt from 'mitt';
const app = createApp(App);
app.use(router).use(store, key).use(ElementPlus, { size: globalComponentSize }).mount('#app');
app.config.globalProperties.mittBus = mitt();
directive(app);
other.elSvg(app);
app.use(router).use(store, key).use(ElementPlus, { size: other.globalComponentSize }).mount('#app');
app.config.globalProperties.mittBus = mitt();

View File

@ -48,7 +48,7 @@ export async function initBackEndControlRoutes() {
*/
export function getBackEndControlRoutes() {
// 模拟 admin 与 test
const auth = store.state.userInfos.userInfos.authPageList[0];
const auth = store.state.userInfos.userInfos.roles[0];
// 管理员 admin
if (auth === 'admin') return getMenuAdmin();
// 其它用户 test

View File

@ -81,34 +81,34 @@ export function formatTwoStageRoutes(arr: any) {
*/
export function setCacheTagsViewRoutes() {
// 获取有权限的路由,否则 tagsView、菜单搜索中无权限的路由也将显示
let authsRoutes = setFilterHasAuthMenu(dynamicRoutes, store.state.userInfos.userInfos.authPageList);
let rolesRoutes = setFilterHasRolesMenu(dynamicRoutes, store.state.userInfos.userInfos.roles);
// 添加到 vuex setTagsViewRoutes 中
store.dispatch('tagsViewRoutes/setTagsViewRoutes', formatTwoStageRoutes(formatFlatteningRoutes(authsRoutes))[0].children);
store.dispatch('tagsViewRoutes/setTagsViewRoutes', formatTwoStageRoutes(formatFlatteningRoutes(rolesRoutes))[0].children);
}
/**
* `meta.auth`
* @param auths userInfos authPageList
* `meta.roles`
* @param roles userInfos roles
* @param route
* @returns
*/
export function hasAuth(auths: any, route: any) {
if (route.meta && route.meta.auth) return auths.some((auth: any) => route.meta.auth.includes(auth));
export function hasRoles(roles: any, route: any) {
if (route.meta && route.meta.auth) return roles.some((role: any) => route.meta.roles.includes(role));
else return true;
}
/**
*
* @param routes children
* @param auth userInfos authPageList
* @returns `meta.auth`
* @param roles userInfos roles
* @returns `meta.roles`
*/
export function setFilterHasAuthMenu(routes: any, auth: any) {
export function setFilterHasRolesMenu(routes: any, roles: any) {
const menu: any = [];
routes.forEach((route: any) => {
const item = { ...route };
if (hasAuth(auth, item)) {
if (item.children) item.children = setFilterHasAuthMenu(item.children, auth);
if (hasRoles(roles, item)) {
if (item.children) item.children = setFilterHasRolesMenu(item.children, roles);
menu.push(item);
}
});
@ -121,7 +121,7 @@ export function setFilterHasAuthMenu(routes: any, auth: any) {
* @description tagsView(isHide)
*/
export function setFilterMenuAndCacheTagsViewRoutes() {
store.dispatch('routesList/setRoutesList', setFilterHasAuthMenu(dynamicRoutes[0].children, store.state.userInfos.userInfos.authPageList));
store.dispatch('routesList/setRoutesList', setFilterHasRolesMenu(dynamicRoutes[0].children, store.state.userInfos.userInfos.roles));
setCacheTagsViewRoutes();
}
@ -135,10 +135,10 @@ export function setFilterMenuAndCacheTagsViewRoutes() {
export function setFilterRoute(chil: any) {
let filterRoute: any = [];
chil.forEach((route: any) => {
if (route.meta.auth) {
route.meta.auth.forEach((metaAuth: any) => {
store.state.userInfos.userInfos.authPageList.forEach((auth: any) => {
if (metaAuth === auth) filterRoute.push({ ...route });
if (route.meta.roles) {
route.meta.roles.forEach((metaRoles: any) => {
store.state.userInfos.userInfos.roles.forEach((roles: any) => {
if (metaRoles === roles) filterRoute.push({ ...route });
});
});
}

View File

@ -9,7 +9,7 @@ import { RouteRecordRaw } from 'vue-router';
* isKeepAlive
* isAffix tagsView
* isIframe `1、isIframe:true 2、链接地址不为空`
* auth
* roles admin common
* icon tagsView `iconfont xxx`fontawesome `fa xxx`
* }
*/
@ -41,7 +41,7 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
isKeepAlive: true,
isAffix: true,
isIframe: false,
auth: ['admin', 'test'],
roles: ['admin', 'common'],
icon: 'iconfont icon-shouye',
},
},
@ -57,7 +57,7 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
isKeepAlive: true,
isAffix: false,
isIframe: false,
auth: ['admin'],
roles: ['admin'],
icon: 'iconfont icon-xitongshezhi',
},
children: [
@ -72,10 +72,25 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
isKeepAlive: true,
isAffix: false,
isIframe: false,
auth: ['admin'],
roles: ['admin'],
icon: 'iconfont icon-caidan',
},
},
{
path: '/system/role',
name: 'systemRole',
component: () => import('/@/views/system/role/index.vue'),
meta: {
title: '角色管理',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: false,
isIframe: false,
roles: ['admin'],
icon: 'elementColdDrink',
},
},
{
path: '/system/user',
name: 'systemUser',
@ -87,10 +102,40 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
isKeepAlive: true,
isAffix: false,
isIframe: false,
auth: ['admin'],
roles: ['admin'],
icon: 'iconfont icon-icon-',
},
},
{
path: '/system/dept',
name: 'systemDept',
component: () => import('/@/views/system/dept/index.vue'),
meta: {
title: '部门管理',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: false,
isIframe: false,
roles: ['admin'],
icon: 'elementOfficeBuilding',
},
},
{
path: '/system/dic',
name: 'systemDic',
component: () => import('/@/views/system/dic/index.vue'),
meta: {
title: '字典管理',
isLink: '',
isHide: false,
isKeepAlive: true,
isAffix: false,
isIframe: false,
roles: ['admin'],
icon: 'elementSetUp',
},
},
],
},
],

View File

@ -169,6 +169,25 @@ body,
}
}
/* cursor 鼠标形状
------------------------------- */
// 默认
.cursor-default {
cursor: default !important;
}
// 帮助
.cursor-help {
cursor: help !important;
}
// 手指
.cursor-pointer {
cursor: pointer !important;
}
// 移动
.cursor-move {
cursor: move !important;
}
/* 宽高 100%
------------------------------- */
.w100 {

View File

@ -12,6 +12,7 @@
.error img {
filter: unset;
}
// element plus
.el-radio-button__original-radio:checked + .el-radio-button__inner,
.el-image-viewer__close,
@ -20,6 +21,13 @@
.el-image-viewer__prev {
color: #000000 !important;
}
.el-overlay {
background-color: rgba(0, 0, 0, 0.05) !important;
}
.el-drawer {
box-shadow: 0 8px 10px -5px rgb(0 0 0 / 1%), 0 16px 24px 2px rgb(0 0 0 / 2%), 0 6px 30px 5px rgb(0 0 0 / 1%);
}
// 数据可视化演示
.visualizing-container-head {
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.02)) !important;
@ -39,6 +47,7 @@
.cropper-modal {
background-color: #ffffff;
}
// 其它菜单等
--bg-menuBar: #ffffff !important;
--bg-menuBarColor: #303133 !important;

View File

@ -853,7 +853,7 @@
@include generalIcon;
}
// element plus 本身字体图标
.el-sub-menu [class^='el-icon-'] {
.el-sub-menu .el-icon {
font-size: 14px !important;
}
// 去掉离开浏览器时菜单的默认高亮
@ -1015,6 +1015,9 @@
/* scrollbar
------------------------------- */
.el-scrollbar__bar {
z-index: 4;
}
.el-scrollbar__wrap {
overflow-x: hidden !important;
max-height: 100%; /*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/

View File

@ -9,6 +9,19 @@
height: 40px;
line-height: 40px;
padding: 0 15px;
.icon-selector-warp-title-tab {
span {
cursor: pointer;
&:hover {
color: var(--color-primary);
text-decoration: underline;
}
}
.span-active {
color: var(--color-primary);
text-decoration: underline;
}
}
}
.icon-selector-warp-row {
height: 230px;

View File

@ -1,5 +1,8 @@
@import './app.scss';
@import './base.scss';
@import './other.scss';
@import './element.scss';
@import './iconSelector.scss';
@import './media/media.scss';
@import './waves.scss';
@import './dark.scss';

View File

@ -0,0 +1,94 @@
@import './index.scss';
/* 页面宽度小于768px
------------------------------- */
@media screen and (max-width: $sm) {
.big-data-down-left {
width: 100% !important;
flex-direction: unset !important;
flex-wrap: wrap;
.flex-warp-item {
min-height: 196.24px;
padding: 0 7.5px 15px 15px !important;
.flex-warp-item-box {
border: none !important;
border-bottom: 1px solid #ebeef5 !important;
}
}
}
.big-data-down-center {
width: 100% !important;
.big-data-down-center-one,
.big-data-down-center-two {
min-height: 196.24px;
padding-left: 15px !important;
.big-data-down-center-one-content {
border: none !important;
border-bottom: 1px solid #ebeef5 !important;
}
.flex-warp-item-box {
@extend .big-data-down-center-one-content;
}
}
}
.big-data-down-right {
.flex-warp-item {
.flex-warp-item-box {
border: none !important;
border-bottom: 1px solid #ebeef5 !important;
}
&:nth-of-type(2) {
padding-left: 15px !important;
}
&:last-of-type {
.flex-warp-item-box {
border: none !important;
}
}
}
}
}
/* 页面宽度大于768px小于1200px
------------------------------- */
@media screen and (min-width: $sm) and (max-width: $lg) {
.chart-warp-bottom {
.big-data-down-left {
width: 50% !important;
}
.big-data-down-center {
width: 50% !important;
}
.big-data-down-right {
.flex-warp-item {
width: 50% !important;
&:nth-of-type(2) {
padding-left: 7.5px !important;
}
}
}
}
}
/* 页面宽度小于1200px
------------------------------- */
@media screen and (max-width: $lg) {
.chart-warp-top {
.up-left {
display: none;
}
}
.chart-warp-bottom {
overflow-y: auto !important;
flex-wrap: wrap;
.big-data-down-right {
width: 100% !important;
flex-direction: unset !important;
flex-wrap: wrap;
.flex-warp-item {
min-height: 196.24px;
padding: 0 7.5px 15px 15px !important;
}
}
}
}

View File

@ -1,8 +1,10 @@
@import './login.scss';
@import './error.scss';
@import './layout.scss';
@import './personal.scss';
@import './tagsView.scss';
@import './home.scss';
@import './chart.scss';
@import './form.scss';
@import './scrollbar.scss';
@import './pagination.scss';

View File

@ -0,0 +1,16 @@
@import './index.scss';
/* 页面宽度小于768px
------------------------------- */
@media screen and (max-width: $sm) {
.personal-info {
padding-left: 0 !important;
margin-top: 15px;
}
.personal-recommend-col {
margin-bottom: 15px;
&:last-of-type {
margin-bottom: 0;
}
}
}

28
src/theme/other.scss Normal file
View File

@ -0,0 +1,28 @@
/* wangeditor富文本编辑器
------------------------------- */
.w-e-toolbar {
border: 1px solid #ebeef5 !important;
border-bottom: 1px solid #ebeef5 !important;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
z-index: 2 !important;
}
.w-e-text-container {
border: 1px solid #ebeef5 !important;
border-top: none !important;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
z-index: 1 !important;
}
/* web端自定义截屏
------------------------------- */
#screenShotContainer {
z-index: 9998 !important;
}
#toolPanel {
height: 42px !important;
}
#optionPanel {
height: 37px !important;
}

101
src/theme/waves.scss Normal file
View File

@ -0,0 +1,101 @@
/* Waves v0.6.0
* http://fian.my.id/Waves
*
* Copyright 2014 Alfiana E. Sibuea and other contributors
* Released under the MIT license
* https://github.com/fians/Waves/blob/master/LICENSE
*/
.waves-effect {
position: relative;
cursor: pointer;
display: inline-block;
overflow: hidden;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
vertical-align: middle;
z-index: 1;
will-change: opacity, transform;
transition: all 0.3s ease-out;
}
.waves-effect .waves-ripple {
position: absolute;
border-radius: 50%;
width: 20px;
height: 20px;
margin-top: -10px;
margin-left: -10px;
opacity: 0;
background: rgba(0, 0, 0, 0.2);
transition: all 0.7s ease-out;
transition-property: opacity, -webkit-transform;
transition-property: transform, opacity;
transition-property: transform, opacity, -webkit-transform;
-webkit-transform: scale(0);
transform: scale(0);
pointer-events: none;
}
.waves-effect.waves-light .waves-ripple {
background-color: rgba(255, 255, 255, 0.45);
}
.waves-effect.waves-red .waves-ripple {
background-color: rgba(244, 67, 54, 0.7);
}
.waves-effect.waves-yellow .waves-ripple {
background-color: rgba(255, 235, 59, 0.7);
}
.waves-effect.waves-orange .waves-ripple {
background-color: rgba(255, 152, 0, 0.7);
}
.waves-effect.waves-purple .waves-ripple {
background-color: rgba(156, 39, 176, 0.7);
}
.waves-effect.waves-green .waves-ripple {
background-color: rgba(76, 175, 80, 0.7);
}
.waves-effect.waves-teal .waves-ripple {
background-color: rgba(0, 150, 136, 0.7);
}
.waves-effect input[type='button'],
.waves-effect input[type='reset'],
.waves-effect input[type='submit'] {
border: 0;
font-style: normal;
font-size: inherit;
text-transform: inherit;
background: none;
}
.waves-notransition {
transition: none !important;
}
.waves-circle {
-webkit-transform: translateZ(0);
transform: translateZ(0);
-webkit-mask-image: -webkit-radial-gradient(circle, #fff 100%, #000 100%);
}
.waves-input-wrapper {
border-radius: 0.2em;
vertical-align: bottom;
}
.waves-input-wrapper .waves-button-input {
position: relative;
top: 0;
left: 0;
z-index: 1;
}
.waves-circle {
text-align: center;
width: 2.5em;
height: 2.5em;
line-height: 2.5em;
border-radius: 50%;
-webkit-mask-image: none;
}
.waves-block {
display: block;
}
a.waves-effect .waves-ripple {
z-index: -1;
}

View File

@ -1,7 +0,0 @@
import { Local } from '/@/utils/storage';
/**
*
* @returns `window.localStorage` `globalComponentSize`
*/
export const globalComponentSize: string = Local.get('themeConfig')?.globalComponentSize;

View File

@ -1,21 +0,0 @@
/**
*
* @param obj
* @returns
*/
export function deepClone(obj: any) {
let newObj: any;
try {
newObj = obj.push ? [] : {};
} catch (error) {
newObj = {};
}
for (let attr in obj) {
if (typeof obj[attr] === 'object') {
newObj[attr] = deepClone(obj[attr]);
} else {
newObj[attr] = obj[attr];
}
}
return newObj;
}

View File

@ -1,4 +1,5 @@
import { nextTick } from 'vue';
import * as svg from '@element-plus/icons';
// 获取阿里字体图标
const getAlicdnIconfont = () => {
@ -27,24 +28,16 @@ const getAlicdnIconfont = () => {
});
};
// 初始化获取 css 样式,获取 element plus 自带图标
// 初始化获取 css 样式,获取 element plus 自带 svg 图标,增加了 element 前缀使用时elementAim
const getElementPlusIconfont = () => {
return new Promise((resolve, reject) => {
nextTick(() => {
const styles: any = document.styleSheets;
let sheetsIconList = [];
for (let i = 0; i < styles.length; i++) {
for (let j = 0; j < styles[i].cssRules.length; j++) {
if (styles[i].cssRules[j].selectorText && styles[i].cssRules[j].selectorText.indexOf('.el-icon-') === 0) {
if (/::before/.test(styles[i].cssRules[j].selectorText)) {
sheetsIconList.push(
`${styles[i].cssRules[j].selectorText.substring(1, styles[i].cssRules[j].selectorText.length).replace(/\:\:before/gi, '')}`
);
}
}
}
const icons = svg as any;
const sheetsIconList = [];
for (const i in icons) {
sheetsIconList.push(`element${icons[i].name}`);
}
if (sheetsIconList.length > 0) resolve(sheetsIconList.reverse());
if (sheetsIconList.length > 0) resolve(sheetsIconList);
else reject('未获取到值,请刷新重试');
});
});

View File

@ -1,25 +0,0 @@
import { nextTick } from 'vue';
/**
*
* @param el dom
* @param arr
* @description data-xxx
*/
export const lazyImgLoading = (el: any, arr: any) => {
const io = new IntersectionObserver((res) => {
res.forEach((v: any) => {
if (v.isIntersecting) {
const { img, key } = v.target.dataset;
v.target.src = img;
v.target.onload = () => {
io.unobserve(v.target);
arr[key]['loading'] = false;
};
}
});
});
nextTick(() => {
document.querySelectorAll(el).forEach((img) => io.observe(img));
});
};

118
src/utils/other.ts Normal file
View File

@ -0,0 +1,118 @@
import { nextTick } from 'vue';
import type { App } from 'vue';
import * as svg from '@element-plus/icons';
import router from '/@/router/index';
import { store } from '/@/store/index';
import { Local } from '/@/utils/storage';
import SvgIcon from '/@/components/svgIcon/index.vue';
/**
* element plus svg
* @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]);
}
app.component('SvgIcon', SvgIcon);
}
/**
*
* @method const title = useTitle(); ==> title()
*/
export function useTitle() {
return () => {
nextTick(() => {
let webTitle = '';
let globalTitle: string = store.state.themeConfig.themeConfig.globalTitle;
webTitle = router.currentRoute.value.meta.title as any;
document.title = `${webTitle} - ${globalTitle}` || globalTitle;
});
};
}
/**
*
* @param el dom
* @param arr
* @description data-xxx
*/
export const lazyImg = (el: any, arr: any) => {
const io = new IntersectionObserver((res) => {
res.forEach((v: any) => {
if (v.isIntersecting) {
const { img, key } = v.target.dataset;
v.target.src = img;
v.target.onload = () => {
io.unobserve(v.target);
arr[key]['loading'] = false;
};
}
});
});
nextTick(() => {
document.querySelectorAll(el).forEach((img) => io.observe(img));
});
};
/**
*
* @returns `window.localStorage` `globalComponentSize`
*/
export function globalComponentSize() {
return Local.get('themeConfig')?.globalComponentSize;
}
/**
*
* @param obj
* @returns
*/
export function deepClone(obj: any) {
let newObj: any;
try {
newObj = obj.push ? [] : {};
} catch (error) {
newObj = {};
}
for (let attr in obj) {
if (typeof obj[attr] === 'object') {
newObj[attr] = deepClone(obj[attr]);
} else {
newObj[attr] = obj[attr];
}
}
return newObj;
}
/**
*
* @method elSvg element plus svg
* @method useTitle
* @method lazyImg
* @method globalComponentSize element plus
* @method deepClone
*/
const other = {
elSvg: (app: App) => {
elSvg(app);
},
useTitle: () => {
useTitle();
},
lazyImg: (el: any, arr: any) => {
lazyImg(el, arr);
},
globalComponentSize: () => {
globalComponentSize();
},
deepClone: (obj: any) => {
deepClone(obj);
},
};
// 统一批量导出
export default other;

View File

@ -1,17 +0,0 @@
import { nextTick } from 'vue';
import router from '/@/router/index';
import { store } from '/@/store/index';
/**
*
*/
export function useTitle() {
return () => {
nextTick(() => {
let webTitle = '';
let globalTitle: string = store.state.themeConfig.themeConfig.globalTitle;
webTitle = router.currentRoute.value.meta.title as any;
document.title = `${webTitle} - ${globalTitle}` || globalTitle;
});
};
}

View File

@ -9,7 +9,6 @@ const setWatermark = (str: string) => {
cans.rotate((-20 * Math.PI) / 180);
cans.font = '12px Vedana';
cans.fillStyle = 'rgba(200, 200, 200, 0.30)';
cans.textAlign = 'center';
cans.textBaseline = 'Middle';
cans.fillText(str, can.width / 10, can.height / 2);
const div = document.createElement('div');

View File

@ -20,9 +20,9 @@
<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>
<div class="home-card-item-title-num pb6">{{v.titleNum}}</div>
<div class="home-card-item-title-num pb6">{{ v.titleNum }}</div>
<div class="home-card-item-tip pb3">{{ v.tip }}</div>
<div class="home-card-item-tip-num">{{v.tipNum}}</div>
<div class="home-card-item-tip-num">{{ v.tipNum }}</div>
</div>
<i :class="v.icon" :style="{ color: v.iconColor }"></i>
</div>
@ -74,7 +74,7 @@
</div>
<div class="home-dynamic-item-right">
<div class="home-dynamic-item-right-title mb5">
<i class="el-icon-s-comment"></i>
<SvgIcon name="elementComment" />
<span>{{ v.title }}</span>
</div>
<div class="home-dynamic-item-right-label">{{ v.label }}</div>

View File

@ -1,24 +1,17 @@
<template>
<el-form class="login-content-form">
<el-form-item>
<el-input
type="text"
placeholder="用户名 admin 或不输均为 test"
prefix-icon="el-icon-user"
v-model="ruleForm.userName"
clearable
autocomplete="off"
>
<el-input type="text" placeholder="用户名 admin 或不输均为 test" v-model="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"
prefix-icon="el-icon-lock"
v-model="ruleForm.password"
autocomplete="off"
>
<el-input :type="isShowPassword ? 'text' : 'password'" placeholder="密码123456" v-model="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"
@ -32,15 +25,11 @@
<el-form-item>
<el-row :gutter="15">
<el-col :span="16">
<el-input
type="text"
maxlength="4"
placeholder="请输入验证码"
prefix-icon="el-icon-position"
v-model="ruleForm.code"
clearable
autocomplete="off"
></el-input>
<el-input type="text" maxlength="4" placeholder="请输入验证码" v-model="ruleForm.code" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><elementPosition /></el-icon>
</template>
</el-input>
</el-col>
<el-col :span="8">
<div class="login-content-code">
@ -90,23 +79,24 @@ export default defineComponent({
});
//
const onSignIn = async () => {
//
state.loading.signIn = true;
let defaultAuthPageList: Array<string> = [];
let defaultRoles: Array<string> = [];
let defaultAuthBtnList: Array<string> = [];
// admin meta.auth/
let adminAuthPageList: Array<string> = ['admin'];
// admin meta.roles/
let adminRoles: Array<string> = ['admin'];
// admin
let adminAuthBtnList: Array<string> = ['btn.add', 'btn.del', 'btn.edit', 'btn.link'];
// test meta.auth/
let testAuthPageList: Array<string> = ['test'];
// test meta.roles/
let testAuthPageList: Array<string> = ['common'];
// test
let testAuthBtnList: Array<string> = ['btn.add', 'btn.link'];
//
if (state.ruleForm.userName === 'admin') {
defaultAuthPageList = adminAuthPageList;
defaultRoles = adminRoles;
defaultAuthBtnList = adminAuthBtnList;
} else {
defaultAuthPageList = testAuthPageList;
defaultRoles = testAuthPageList;
defaultAuthBtnList = testAuthBtnList;
}
//
@ -117,7 +107,7 @@ export default defineComponent({
? '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(),
authPageList: defaultAuthPageList,
roles: defaultRoles,
authBtnList: defaultAuthBtnList,
};
// token

View File

@ -1,21 +1,20 @@
<template>
<el-form class="login-content-form">
<el-form-item>
<el-input type="text" placeholder="请输入手机号" prefix-icon="iconfont icon-dianhua" v-model="ruleForm.userName" clearable autocomplete="off">
<el-input type="text" placeholder="请输入手机号" v-model="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-row :gutter="15">
<el-col :span="16">
<el-input
type="text"
maxlength="4"
placeholder="请输入验证码"
prefix-icon="el-icon-position"
v-model="ruleForm.code"
clearable
autocomplete="off"
></el-input>
<el-input type="text" maxlength="4" placeholder="请输入验证码" v-model="ruleForm.code" clearable autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><elementPosition /></el-icon>
</template>
</el-input>
</el-col>
<el-col :span="8">
<el-button class="login-content-code">获取验证码</el-button>
@ -51,9 +50,6 @@ export default defineComponent({
<style scoped lang="scss">
.login-content-form {
margin-top: 20px;
::v-deep(.el-input__icon) {
display: inline-block;
}
.login-content-code {
width: 100%;
padding: 0;

View File

@ -0,0 +1,147 @@
<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-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"
:props="{ checkStrictly: true, value: 'deptName', label: 'deptName' }"
placeholder="请选择部门"
clearable
class="w100"
v-model="ruleForm.deptLevel"
>
<template #default="{ node, data }">
<span>{{ data.deptName }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</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.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-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-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-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-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-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-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="small"> </el-button>
<el-button type="primary" @click="onSubmit" size="small"> </el-button>
</span>
</template>
</el-dialog>
</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',
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: '总部',
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),
};
},
};
</script>

View File

@ -0,0 +1,152 @@
<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-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"
:props="{ checkStrictly: true, value: 'deptName', label: 'deptName' }"
placeholder="请选择部门"
clearable
class="w100"
v-model="ruleForm.deptLevel"
>
<template #default="{ node, data }">
<span>{{ data.deptName }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</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.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-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-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-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-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-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-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="small"> </el-button>
<el-button type="primary" @click="onSubmit" size="small"> </el-button>
</span>
</template>
</el-dialog>
</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',
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: '总部',
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),
};
},
};
</script>

View File

@ -0,0 +1,140 @@
<template>
<div class="system-dept-container">
<el-card shadow="hover">
<div class="system-dept-search mb15">
<el-input size="small" placeholder="请输入部门名称" style="max-width: 180px"> </el-input>
<el-button size="small" type="primary" class="ml10">
<el-icon>
<elementSearch />
</el-icon>
查询
</el-button>
<el-button size="small" type="success" class="ml10" @click="onOpenAddDept">
<el-icon>
<elementFolderAdd />
</el-icon>
新增部门
</el-button>
</div>
<el-table
:data="tableData.data"
style="width: 100%"
row-key="id"
default-expand-all
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="deptName" label="部门名称" show-overflow-tooltip> </el-table-column>
<el-table-column label="排序" show-overflow-tooltip width="80">
<template #default="scope">
{{ scope.$index }}
</template>
</el-table-column>
<el-table-column prop="status" label="部门状态" show-overflow-tooltip>
<template #default="scope">
<el-tag type="success" v-if="scope.row.status">启用</el-tag>
<el-tag type="info" v-else>禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="describe" label="部门描述" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" show-overflow-tooltip width="140">
<template #default="scope">
<el-button size="mini" type="text" @click="onOpenAddDept(scope.row)">新增</el-button>
<el-button size="mini" type="text" @click="onOpenEditDept(scope.row)">修改</el-button>
<el-button size="mini" type="text" @click="onTabelRowDel(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<AddDept ref="addDeptRef" />
<EditDept ref="editDeptRef" />
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, onMounted } from 'vue';
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',
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: '总部',
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),
};
},
};
</script>

View File

@ -0,0 +1,128 @@
<template>
<div class="system-add-dic-container">
<el-dialog title="新增字典" v-model="isShowDialog" width="769px">
<el-alert title="半成品,交互过于复杂,请自行扩展!" type="warning" :closable="false" class="mb20"> </el-alert>
<el-form :model="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-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-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-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-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item :prop="`list[${k}].label`">
<template #label>
<el-button type="primary" circle size="mini" @click="onAddRow" v-if="k === 0">
<el-icon>
<elementPlus />
</el-icon>
</el-button>
<el-button type="danger" circle size="mini" @click="onDelRow(k)" v-else>
<el-icon>
<elementDelete />
</el-icon>
</el-button>
<span class="ml10">字段</span>
</template>
<el-input v-model="v.label" style="width: 100%" placeholder="请输入字段名"> </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="属性" :prop="`list[${k}].value`">
<el-input v-model="v.value" style="width: 100%" placeholder="请输入属性值"> </el-input>
</el-form-item>
</el-col>
</el-row>
</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-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="small"> </el-button>
<el-button type="primary" @click="onSubmit" size="small"> </el-button>
</span>
</template>
</el-dialog>
</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({
id: Math.random(),
label: '',
value: '',
});
};
//
const onDelRow = (k) => {
state.ruleForm.list.splice(k, 1);
};
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
onAddRow,
onDelRow,
...toRefs(state),
};
},
};
</script>

View File

@ -0,0 +1,142 @@
<template>
<div class="system-edit-dic-container">
<el-dialog title="修改字典" v-model="isShowDialog" width="769px">
<el-alert title="半成品,交互过于复杂,请自行扩展!" type="warning" :closable="false" class="mb20"> </el-alert>
<el-form :model="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-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-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-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-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item :prop="`list[${k}].label`">
<template #label>
<el-button type="primary" circle size="mini" @click="onAddRow" v-if="k === 0">
<el-icon>
<elementPlus />
</el-icon>
</el-button>
<el-button type="danger" circle size="mini" @click="onDelRow(k)" v-else>
<el-icon>
<elementDelete />
</el-icon>
</el-button>
<span class="ml10">字段</span>
</template>
<el-input v-model="v.label" style="width: 100%" placeholder="请输入字段名"> </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="属性" :prop="`list[${k}].value`">
<el-input v-model="v.value" style="width: 100%" placeholder="请输入属性值"> </el-input>
</el-form-item>
</el-col>
</el-row>
</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-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="small"> </el-button>
<el-button type="primary" @click="onSubmit" size="small"> </el-button>
</span>
</template>
</el-dialog>
</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({
id: Math.random(),
label: '',
value: '',
});
};
//
const onDelRow = (k) => {
state.ruleForm.list.splice(k, 1);
};
return {
openDialog,
closeDialog,
onCancel,
onSubmit,
onAddRow,
onDelRow,
...toRefs(state),
};
},
};
</script>

View File

@ -0,0 +1,143 @@
<template>
<div class="system-dic-container">
<el-card shadow="hover">
<div class="system-user-search mb15">
<el-input size="small" placeholder="请输入字典名称" style="max-width: 180px"> </el-input>
<el-button size="small" type="primary" class="ml10">
<el-icon>
<elementSearch />
</el-icon>
查询
</el-button>
<el-button size="small" type="success" class="ml10" @click="onOpenAddDic">
<el-icon>
<elementFolderAdd />
</el-icon>
新增字典
</el-button>
</div>
<el-table :data="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>
<el-table-column prop="status" label="字典状态" show-overflow-tooltip>
<template #default="scope">
<el-tag type="success" v-if="scope.row.status">启用</el-tag>
<el-tag type="info" v-else>禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="describe" label="字典描述" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="100">
<template #default="scope">
<el-button size="mini" type="text" @click="onOpenEditDic(scope.row)">修改</el-button>
<el-button size="mini" type="text" @click="onRowDel(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="onHandleSizeChange"
@current-change="onHandleCurrentChange"
class="mt15"
:pager-count="5"
:page-sizes="[10, 20, 30]"
v-model:current-page="tableData.param.pageNum"
background
v-model:page-size="tableData.param.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total"
>
</el-pagination>
</el-card>
<AddDic ref="addDicRef" />
<EditDic ref="editDicRef" />
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, ref } from 'vue';
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),
};
},
};
</script>
<style scoped lang="scss">
.system-dic-container {
}
</style>

View File

@ -1,89 +1,138 @@
<template>
<div class="system-menu-container">
<div class="system-add-menu-container">
<el-dialog title="新增菜单" v-model="isShowDialog" width="769px">
<el-form :model="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"
:props="{ checkStrictly: true, value: 'path', label: 'title' }"
placeholder="请选择上级菜单"
clearable
class="w100"
v-model="ruleForm.menuSuperior"
>
<template #default="{ node, data }">
<span>{{ data.title }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</el-form-item>
</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 label="menu">菜单</el-radio>
<el-radio label="btn">按钮</el-radio>
</el-radio-group>
</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.title" placeholder="格式message.router.xxx" 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.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="菜单图标">
<IconSelector placeholder="请输入菜单图标" v-model="ruleForm.meta.icon" />
</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-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.isHide" placeholder="请选择是否隐藏" clearable class="w100">
<el-option label="是" value="true"></el-option>
<el-option label="否" value="false"></el-option>
</el-select>
</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.isKeepAlive" placeholder="请选择是否缓存" clearable class="w100">
<el-option label="是" value="true"></el-option>
<el-option label="否" value="false"></el-option>
</el-select>
</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.isAffix" placeholder="请选择是否固定" clearable class="w100">
<el-option label="是" value="true"></el-option>
<el-option label="否" value="false"></el-option>
</el-select>
</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.isLink" placeholder="请选择是否外链" clearable class="w100" :disabled="ruleForm.meta.isIframe === 'true'">
<el-option label="是" value="true"></el-option>
<el-option label="否" value="false"></el-option>
</el-select>
</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.isIframe" placeholder="请选择是否iframe" clearable class="w100" @change="onSelectIframeChange">
<el-option label="是" value="true"></el-option>
<el-option label="否" value="false"></el-option>
</el-select>
</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"
placeholder="外链/内嵌时链接地址http:xxx.com"
clearable
:disabled="ruleForm.isLink === '' || ruleForm.isLink === 'false'"
>
</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.auth" placeholder="路由权限标识(多个请用逗号隔开)" clearable></el-input>
</el-form-item>
</el-col>
<template v-if="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-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-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-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" />
</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-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"
placeholder="外链/内嵌时链接地址http:xxx.com"
clearable
:disabled="ruleForm.isLink === '' || !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-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'">
<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-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 v-model="ruleForm.menuSort" placeholder="菜单排序" clearable></el-input>
<el-input-number v-model="ruleForm.menuSort" controls-position="right" placeholder="请输入排序" class="w100" />
</el-form-item>
</el-col>
<template v-if="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 :label="true">隐藏</el-radio>
<el-radio :label="false">不隐藏</el-radio>
</el-radio-group>
</el-form-item>
</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 :label="true">缓存</el-radio>
<el-radio :label="false">不缓存</el-radio>
</el-radio-group>
</el-form-item>
</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 :label="true">固定</el-radio>
<el-radio :label="false">不固定</el-radio>
</el-radio-group>
</el-form-item>
</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 :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
</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 :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<template #footer>
@ -97,52 +146,64 @@
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue';
import { reactive, toRefs, onMounted } from 'vue';
import { useStore } from '/@/store/index';
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` 路由菜单格式请注意参数类型
* 受到 `element plus` 类型 `string/number/object` 影响不可使用 `:value="true"`
* 的写法所以传值到后台时需要转换成布尔值否则页面可能出现玄学
* 路由权限标识为数组格式基本都需要自行转换类型
*/
// `/src/router/route.ts` `dynamicRoutes`
ruleForm: {
menuSuperior: [], //
menuType: 'menu', //
name: '', //
component: '', //
isLink: '', //
menuSort: '', //
component: '', //
isLink: false, //
menuSort: 0, //
path: '', //
redirect: '', // children
meta: {
title: '', //
icon: '', //
isHide: '', //
isKeepAlive: '', //
isAffix: '', //
isLink: '', // `1isLink:true 2`
isIframe: '', // `1isIframe:true 2`
auth: '', //
isHide: false, //
isKeepAlive: true, //
isAffix: false, //
isLink: '', // /http:xxx.com`1isLink:true 2`
isIframe: false, // `1isIframe: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) => {
console.log(row);
const openDialog = () => {
state.isShowDialog = true;
};
//
const closeDialog = (row?: object) => {
console.log(row);
const closeDialog = () => {
state.isShowDialog = false;
};
//
const onSelectIframeChange = () => {
if (state.ruleForm.meta.isIframe === 'true') {
state.ruleForm.isLink = 'true';
if (state.ruleForm.meta.isIframe) {
state.ruleForm.isLink = true;
} else {
state.ruleForm.isLink = '';
}
@ -150,29 +211,16 @@ export default {
//
const onCancel = () => {
closeDialog();
initForm();
};
//
const onSubmit = () => {
console.log(state.ruleForm); //
closeDialog(); //
// setBackEndControlRefreshRoutes() //
};
// `resetFields()` 使
const initForm = () => {
state.ruleForm.name = '';
state.ruleForm.component = '';
state.ruleForm.isLink = '';
state.ruleForm.menuSort = '';
state.ruleForm.meta.title = '';
state.ruleForm.meta.icon = '';
state.ruleForm.meta.isHide = '';
state.ruleForm.meta.isKeepAlive = '';
state.ruleForm.meta.isAffix = '';
state.ruleForm.meta.isLink = '';
state.ruleForm.meta.isIframe = '';
state.ruleForm.meta.auth = '';
};
//
onMounted(() => {
state.menuData = getMenuData(store.state.routesList.routesList);
});
return {
openDialog,
closeDialog,

View File

@ -1,95 +1,144 @@
<template>
<div class="system-menu-container">
<el-dialog title="编辑菜单" v-model="isShowDialog" width="769px">
<div class="system-edit-menu-container">
<el-dialog title="修改菜单" v-model="isShowDialog" width="769px">
<el-form :model="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"
:props="{ checkStrictly: true, value: 'path', label: 'title' }"
placeholder="请选择上级菜单"
clearable
class="w100"
v-model="ruleForm.menuSuperior"
>
<template #default="{ node, data }">
<span>{{ data.title }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</el-form-item>
</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 label="menu">菜单</el-radio>
<el-radio label="btn">按钮</el-radio>
</el-radio-group>
</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.title" placeholder="格式message.router.xxx" 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.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="菜单图标">
<IconSelector placeholder="请输入菜单图标" v-model="ruleForm.meta.icon" />
</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-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.isHide" placeholder="请选择是否隐藏" clearable class="w100">
<el-option label="是" value="true"></el-option>
<el-option label="否" value="false"></el-option>
</el-select>
</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.isKeepAlive" placeholder="请选择是否缓存" clearable class="w100">
<el-option label="是" value="true"></el-option>
<el-option label="否" value="false"></el-option>
</el-select>
</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.isAffix" placeholder="请选择是否固定" clearable class="w100">
<el-option label="是" value="true"></el-option>
<el-option label="否" value="false"></el-option>
</el-select>
</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.isLink" placeholder="请选择是否外链" clearable class="w100" :disabled="ruleForm.meta.isIframe === 'true'">
<el-option label="是" value="true"></el-option>
<el-option label="否" value="false"></el-option>
</el-select>
</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.isIframe" placeholder="请选择是否iframe" clearable class="w100" @change="onSelectIframeChange">
<el-option label="是" value="true"></el-option>
<el-option label="否" value="false"></el-option>
</el-select>
</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"
placeholder="外链/内嵌时链接地址http:xxx.com"
clearable
:disabled="ruleForm.isLink === '' || ruleForm.isLink === 'false'"
>
</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.auth" placeholder="路由权限标识(多个请用逗号隔开)" clearable></el-input>
</el-form-item>
</el-col>
<template v-if="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-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-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-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" />
</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-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"
placeholder="外链/内嵌时链接地址http:xxx.com"
clearable
:disabled="ruleForm.isLink === '' || !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-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'">
<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-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 v-model="ruleForm.menuSort" placeholder="菜单排序" clearable></el-input>
<el-input-number v-model="ruleForm.menuSort" controls-position="right" placeholder="请输入排序" class="w100" />
</el-form-item>
</el-col>
<template v-if="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 :label="true">隐藏</el-radio>
<el-radio :label="false">不隐藏</el-radio>
</el-radio-group>
</el-form-item>
</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 :label="true">缓存</el-radio>
<el-radio :label="false">不缓存</el-radio>
</el-radio-group>
</el-form-item>
</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 :label="true">固定</el-radio>
<el-radio :label="false">不固定</el-radio>
</el-radio-group>
</el-form-item>
</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 :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
</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 :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="small"> </el-button>
<el-button type="primary" @click="onSubmit" size="small"> </el-button>
<el-button type="primary" @click="onSubmit" size="small"></el-button>
</span>
</template>
</el-dialog>
@ -97,64 +146,67 @@
</template>
<script lang="ts">
import { reactive, toRefs } from 'vue';
import { reactive, toRefs, onMounted } from 'vue';
import { useStore } from '/@/store/index';
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` 路由菜单格式请注意参数类型
* 受到 `element plus` 类型 `string/number/object` 影响不可使用 `:value="true"`
* 的写法所以传值到后台时需要转换成布尔值否则页面可能出现玄学
* 路由权限标识为数组格式基本都需要自行转换类型
*/
// `/src/router/route.ts` `dynamicRoutes`
ruleForm: {
menuSuperior: [], //
menuType: 'menu', //
name: '', //
component: '', //
isLink: '', //
menuSort: '', //
component: '', //
isLink: false, //
menuSort: 0, //
path: '', //
redirect: '', // children
meta: {
title: '', //
icon: '', //
isHide: '', //
isKeepAlive: '', //
isAffix: '', //
isLink: '', // `1isLink:true 2`
isIframe: '', // `1isIframe:true 2`
auth: '', //
isHide: false, //
isKeepAlive: true, //
isAffix: false, //
isLink: '', // /http:xxx.com`1isLink:true 2`
isIframe: false, // `1isIframe: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: any) => {
state.ruleForm.name = row.name;
state.ruleForm.component = '';
state.ruleForm.isLink = row.meta.isLink ? 'true' : '';
state.ruleForm.menuSort = '';
state.ruleForm.meta.title = row.meta.title;
// 使element plus
state.ruleForm.meta.icon = row.meta.icon;
state.ruleForm.meta.isHide = row.meta.isHide ? 'true' : 'false';
state.ruleForm.meta.isKeepAlive = row.meta.isKeepAlive ? 'true' : 'false';
state.ruleForm.meta.isAffix = row.meta.isAffix ? 'true' : 'false';
state.ruleForm.meta.isLink = row.meta.isLink ? row.meta.isLink : '';
state.ruleForm.meta.isIframe = row.meta.isIframe ? 'true' : '';
state.ruleForm.meta.auth = row.meta.auth ? row.meta.auth.join(',') : '';
const openDialog = (row: Object) => {
row.menuType = 'menu';
row.menuSort = Math.random();
state.ruleForm = row;
state.isShowDialog = true;
};
//
const closeDialog = (row?: object) => {
console.log(row);
const closeDialog = () => {
state.isShowDialog = false;
};
//
const onSelectIframeChange = () => {
if (state.ruleForm.meta.isIframe === 'true') {
state.ruleForm.isLink = 'true';
if (state.ruleForm.meta.isIframe) {
state.ruleForm.isLink = true;
} else {
state.ruleForm.isLink = '';
}
@ -162,29 +214,16 @@ export default {
//
const onCancel = () => {
closeDialog();
initForm();
};
//
const onSubmit = () => {
console.log(state.ruleForm); //
closeDialog(); //
// setBackEndControlRefreshRoutes() //
};
// `resetFields()` 使
const initForm = () => {
state.ruleForm.name = '';
state.ruleForm.component = '';
state.ruleForm.isLink = '';
state.ruleForm.menuSort = '';
state.ruleForm.meta.title = '';
state.ruleForm.meta.icon = '';
state.ruleForm.meta.isHide = '';
state.ruleForm.meta.isKeepAlive = '';
state.ruleForm.meta.isAffix = '';
state.ruleForm.meta.isLink = '';
state.ruleForm.meta.isIframe = '';
state.ruleForm.meta.auth = '';
};
//
onMounted(() => {
state.menuData = getMenuData(store.state.routesList.routesList);
});
return {
openDialog,
closeDialog,

View File

@ -1,55 +1,50 @@
<template>
<div class="system-menu-container">
<el-card shadow="hover">
<el-table :data="menuTableData" stripe style="width: 100%" row-key="path" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }">
<div class="system-menu-search mb15">
<el-input size="small" placeholder="请输入菜单名称" style="max-width: 180px"> </el-input>
<el-button size="small" type="primary" class="ml10">
<el-icon>
<elementSearch />
</el-icon>
查询
</el-button>
<el-button size="small" type="success" class="ml10" @click="onOpenAddMenu">
<el-icon>
<elementFolderAdd />
</el-icon>
新增菜单
</el-button>
</div>
<el-table :data="menuTableData" style="width: 100%" row-key="path" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }">
<el-table-column label="菜单名称" show-overflow-tooltip>
<template #default="scope">
<i :class="scope.row.meta.icon"></i>
<SvgIcon :name="scope.row.meta.icon" />
<span class="ml10">{{ scope.row.meta.title }}</span>
</template>
</el-table-column>
<el-table-column prop="path" label="路由名称" show-overflow-tooltip width="150"></el-table-column>
<el-table-column label="组件地址" show-overflow-tooltip>
<el-table-column prop="path" label="路由路径" show-overflow-tooltip></el-table-column>
<el-table-column label="组件路径" show-overflow-tooltip>
<template #default="scope">
<span>{{ scope.row.component }}</span>
</template>
</el-table-column>
<el-table-column label="隐藏" show-overflow-tooltip width="70">
<template #default="scope">
<span v-if="scope.row.meta.isHide" class="color-primary"></span>
<span v-else class="color-info"></span>
</template>
</el-table-column>
<el-table-column label="缓存" show-overflow-tooltip width="70">
<template #default="scope">
<span v-if="scope.row.meta.isKeepAlive" class="color-primary"></span>
<span v-else class="color-info"></span>
</template>
</el-table-column>
<el-table-column label="固定" show-overflow-tooltip width="70">
<template #default="scope">
<span v-if="scope.row.meta.isAffix" class="color-primary"></span>
<span v-else class="color-info"></span>
</template>
</el-table-column>
<el-table-column label="外链" show-overflow-tooltip width="70">
<template #default="scope">
<span v-if="scope.row.meta.isLink && !scope.row.meta.isIframe" class="color-primary"></span>
<span v-else class="color-info"></span>
</template>
</el-table-column>
<el-table-column label="iframe" show-overflow-tooltip width="70">
<template #default="scope">
<span v-if="scope.row.meta.isLink && scope.row.meta.isIframe" class="color-primary"></span>
<span v-else class="color-info"></span>
</template>
</el-table-column>
<el-table-column label="权限标识" show-overflow-tooltip>
<template #default="scope">
<span>{{ scope.row.meta.auth }}</span>
<span>{{ scope.row.meta.roles }}</span>
</template>
</el-table-column>
<el-table-column label="操作" show-overflow-tooltip width="125">
<el-table-column label="排序" show-overflow-tooltip width="80">
<template #default="scope">
{{ scope.$index }}
</template>
</el-table-column>
<el-table-column label="类型" show-overflow-tooltip width="80">
<template #default="scope">
<el-tag type="success" size="small">{{ scope.row.xx }}菜单</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" show-overflow-tooltip width="140">
<template #default="scope">
<el-button size="mini" type="text" @click="onOpenAddMenu(scope.row)">新增</el-button>
<el-button size="mini" type="text" @click="onOpenEditMenu(scope.row)">修改</el-button>
@ -65,7 +60,7 @@
<script lang="ts">
import { ref, toRefs, reactive, computed } from 'vue';
import { ElMessageBox } from 'element-plus';
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';
@ -82,8 +77,8 @@ export default {
return store.state.routesList.routesList;
});
//
const onOpenAddMenu = (row: object) => {
addMenuRef.value.openDialog(row);
const onOpenAddMenu = () => {
addMenuRef.value.openDialog();
};
//
const onOpenEditMenu = (row: object) => {
@ -91,13 +86,13 @@ export default {
};
//
const onTabelRowDel = (row: object) => {
ElMessageBox.confirm('此操作将永久删除路由, 是否继续?', '提示', {
ElMessageBox.confirm(`此操作将永久删除路由:${row.path}, 是否继续?`, '提示', {
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
console.log(row);
ElMessage.success('删除成功');
})
.catch(() => {});
};

View File

@ -0,0 +1,216 @@
<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-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-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="角色标识">
<template #label>
<el-tooltip effect="dark" content="用于 `router/route.ts` meta.roles" placement="top-start">
<span>角色标识</span>
</el-tooltip>
</template>
<el-input v-model="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-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-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-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-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="small"> </el-button>
<el-button type="primary" @click="onSubmit" size="small"> </el-button>
</span>
</template>
</el-dialog>
</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>
<style scoped lang="scss">
.system-add-role-container {
.menu-data-tree {
border: var(--el-input-border, var(--el-border-base));
border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
padding: 5px;
}
}
</style>

View File

@ -0,0 +1,217 @@
<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-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-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="角色标识">
<template #label>
<el-tooltip effect="dark" content="用于 `router/route.ts` meta.roles" placement="top-start">
<span>角色标识</span>
</el-tooltip>
</template>
<el-input v-model="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-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-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-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-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="small"> </el-button>
<el-button type="primary" @click="onSubmit" size="small"> </el-button>
</span>
</template>
</el-dialog>
</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>
<style scoped lang="scss">
.system-edit-role-container {
.menu-data-tree {
border: var(--el-input-border, var(--el-border-base));
border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
padding: 5px;
}
}
</style>

View File

@ -0,0 +1,145 @@
<template>
<div class="system-role-container">
<el-card shadow="hover">
<div class="system-user-search mb15">
<el-input size="small" placeholder="请输入角色名称" style="max-width: 180px"> </el-input>
<el-button size="small" type="primary" class="ml10">
<el-icon>
<elementSearch />
</el-icon>
查询
</el-button>
<el-button size="small" type="success" class="ml10" @click="onOpenAddRole">
<el-icon>
<elementFolderAdd />
</el-icon>
新增角色
</el-button>
</div>
<el-table :data="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>
<el-table-column prop="sort" label="排序" show-overflow-tooltip></el-table-column>
<el-table-column prop="status" label="角色状态" show-overflow-tooltip>
<template #default="scope">
<el-tag type="success" v-if="scope.row.status">启用</el-tag>
<el-tag type="info" v-else>禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="describe" label="角色描述" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="100">
<template #default="scope">
<el-button :disabled="scope.row.roleName === '超级管理员'" size="mini" type="text" @click="onOpenEditRole(scope.row)">修改</el-button>
<el-button :disabled="scope.row.roleName === '超级管理员'" size="mini" type="text" @click="onRowDel(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="onHandleSizeChange"
@current-change="onHandleCurrentChange"
class="mt15"
:pager-count="5"
:page-sizes="[10, 20, 30]"
v-model:current-page="tableData.param.pageNum"
background
v-model:page-size="tableData.param.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total"
>
</el-pagination>
</el-card>
<AddRole ref="addRoleRef" />
<EditRole ref="editRoleRef" />
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, ref } from 'vue';
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),
};
},
};
</script>
<style scoped lang="scss">
.system-role-container {
}
</style>

View File

@ -0,0 +1,171 @@
<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-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-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-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-option label="超级管理员" value="admin"></el-option>
<el-option label="普通用户" value="common"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="部门">
<el-cascader
:options="deptData"
:props="{ checkStrictly: true, value: 'deptName', label: 'deptName' }"
placeholder="请选择部门"
clearable
class="w100"
v-model="ruleForm.department"
>
<template #default="{ node, data }">
<span>{{ data.deptName }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</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-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-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-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</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.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-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-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-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="small"> </el-button>
<el-button type="primary" @click="onSubmit" size="small"> </el-button>
</span>
</template>
</el-dialog>
</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',
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: '总部',
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),
};
},
};
</script>

View File

@ -0,0 +1,172 @@
<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-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-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-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-option label="超级管理员" value="admin"></el-option>
<el-option label="普通用户" value="common"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="部门">
<el-cascader
:options="deptData"
:props="{ checkStrictly: true, value: 'deptName', label: 'deptName' }"
placeholder="请选择部门"
clearable
class="w100"
v-model="ruleForm.department"
>
<template #default="{ node, data }">
<span>{{ data.deptName }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
</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-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-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-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</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.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-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-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-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="small"> </el-button>
<el-button type="primary" @click="onSubmit" size="small"> </el-button>
</span>
</template>
</el-dialog>
</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',
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: '总部',
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),
};
},
};
</script>

View File

@ -2,25 +2,40 @@
<div class="system-user-container">
<el-card shadow="hover">
<div class="system-user-search mb15">
<el-input size="small" placeholder="请输入用户名" prefix-icon="el-icon-search" style="max-width: 180px"></el-input>
<el-button size="small" type="primary" class="ml10">查询</el-button>
<el-input size="small" placeholder="请输入用户名称" style="max-width: 180px"> </el-input>
<el-button size="small" type="primary" class="ml10">
<el-icon>
<elementSearch />
</el-icon>
查询
</el-button>
<el-button size="small" type="success" class="ml10" @click="onOpenAddUser">
<el-icon>
<elementFolderAdd />
</el-icon>
新增用户
</el-button>
</div>
<el-table :data="tableData.data" stripe style="width: 100%">
<el-table-column prop="num" label="ID" show-overflow-tooltip></el-table-column>
<el-table-column prop="name" label="用户名" show-overflow-tooltip></el-table-column>
<el-table-column label="头像" show-overflow-tooltip>
<el-table :data="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>
<el-table-column prop="roleSign" label="关联角色" show-overflow-tooltip></el-table-column>
<el-table-column prop="department" label="部门" show-overflow-tooltip></el-table-column>
<el-table-column prop="phone" label="手机号" show-overflow-tooltip></el-table-column>
<el-table-column prop="email" label="邮箱" show-overflow-tooltip></el-table-column>
<el-table-column prop="status" label="用户状态" show-overflow-tooltip>
<template #default="scope">
<el-image class="system-user-photo" :src="scope.row.photo" :preview-src-list="[scope.row.photo]"> </el-image>
<el-tag type="success" v-if="scope.row.status">启用</el-tag>
<el-tag type="info" v-else>禁用</el-tag>
</template>
</el-table-column>
<el-table-column prop="phone" label="手机" show-overflow-tooltip></el-table-column>
<el-table-column prop="email" label="邮箱" show-overflow-tooltip></el-table-column>
<el-table-column prop="sex" label="性别" show-overflow-tooltip></el-table-column>
<el-table-column prop="time" label="加入时间" show-overflow-tooltip></el-table-column>
<el-table-column prop="path" label="操作" width="90">
<el-table-column prop="describe" label="用户描述" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="100">
<template #default="scope">
<el-button size="mini" type="text">修改</el-button>
<el-button size="mini" type="text" @click="onRowDel(scope.row)">删除</el-button>
<el-button :disabled="scope.row.userName === 'admin'" size="mini" type="text" @click="onOpenEditUser(scope.row)">修改</el-button>
<el-button :disabled="scope.row.userName === 'admin'" size="mini" type="text" @click="onRowDel(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
@ -38,14 +53,22 @@
>
</el-pagination>
</el-card>
<AddUer ref="addUserRef" />
<EditUser ref="editUserRef" />
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted } from 'vue';
import { toRefs, reactive, onMounted, ref } from 'vue';
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: [],
@ -60,23 +83,44 @@ export default {
//
const initTableData = () => {
const data: Array<object> = [];
for (let i = 0; i < 20; i++) {
for (let i = 0; i < 2; i++) {
data.push({
num: `00${i + 1}`,
name: (Math.round(Math.random() * 20901) + 19968).toString(16),
photo: 'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1633081619,2004077072&fm=26&gp=0.jpg',
phone: Math.floor(Math.random() * 10000000000),
email: `${Math.floor(Math.random() * 1000)}@qq.com`,
sex: i % 2 === 0 ? '男' : '女',
time: new Date().toLocaleDateString(),
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 onRowDel = (row: object) => {
console.log(row);
//
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) => {
@ -91,6 +135,10 @@ export default {
initTableData();
});
return {
addUserRef,
editUserRef,
onOpenAddUser,
onOpenEditUser,
onRowDel,
onHandleSizeChange,
onHandleCurrentChange,
@ -102,13 +150,5 @@ export default {
<style scoped lang="scss">
.system-user-container {
.system-user-search {
text-align: right;
}
.system-user-photo {
width: 40px;
height: 40px;
border-radius: 100%;
}
}
</style>