提交 100ab2e0 编写于 作者: H haoxr

refactor: 自动导入修改和项目重构优化

上级 c6d57558
......@@ -111,7 +111,7 @@ export function deleteUsers(ids: string) {
*
* @returns
*/
export function downloadTemplate() {
export function downloadTemplateApi() {
return request({
url: '/api/v1/users/template',
method: 'get',
......
......@@ -10,10 +10,10 @@
item.redirect === 'noredirect' || index === breadcrumbs.length - 1
"
class="text-[#97a8be]"
>{{ generateTitle(item.meta.title) }}</span
>{{ translateRouteTitleI18n(item.meta.title) }}</span
>
<a v-else @click.prevent="handleLink(item)">
{{ generateTitle(item.meta.title) }}
{{ translateRouteTitleI18n(item.meta.title) }}
</a>
</el-breadcrumb-item>
</transition-group>
......@@ -25,7 +25,7 @@ import { onBeforeMount, ref, watch } from 'vue';
import { useRoute, RouteLocationMatched } from 'vue-router';
import { compile } from 'path-to-regexp';
import router from '@/router';
import { generateTitle } from '@/utils/i18n';
import { translateRouteTitleI18n } from '@/utils/i18n';
const currentRoute = useRoute();
const pathCompile = (path: string) => {
......
<template>
<div class="icon-select">
<el-input
v-model="iconName"
clearable
placeholder="请输入图标名称"
@clear="filterIcons"
@input="filterIcons"
>
<template #suffix><i class="el-icon-search el-input__icon" /></template>
</el-input>
<div class="icon-select__list">
<div
v-for="(item, index) in iconList"
:key="index"
@click="selectedIcon(item)"
>
<svg-icon
color="#999"
:icon-class="item"
style="height: 30px; width: 16px; margin-right: 5px"
/>
<span>{{ item }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import SvgIcon from '@/components/SvgIcon/index.vue';
const props = defineProps({
modelValue: {
type: String,
require: false
}
});
const icons = [] as string[];
const modules = import.meta.glob('../../assets/icons/*.svg');
for (const path in modules) {
const p = path.split('assets/icons/')[1].split('.svg')[0];
icons.push(p);
}
const iconList = ref(icons);
const emit = defineEmits(['update:modelValue']);
const visible = ref(false);
const inputValue = toRef(props, 'modelValue');
const iconList = ref<string[]>([]);
const iconName = ref('');
const filterValue = ref('');
const emit = defineEmits(['selected']);
const icon = ref('perm');
function filterIcons() {
iconList.value = icons;
if (iconName.value) {
iconList.value = icons.filter(item => item.indexOf(iconName.value) !== -1);
function loadIcons() {
const modules = import.meta.glob('../../assets/icons/*.svg');
for (const path in modules) {
const icon = path.split('assets/icons/')[1].split('.svg')[0];
iconList.value.push(icon);
}
}
function selectedIcon(name: string) {
emit('selected', name);
document.body.click();
}
function reset() {
iconName.value = '';
iconList.value = icons;
}
defineExpose({
reset
onMounted(() => {
loadIcons();
});
</script>
<style lang="scss" scoped>
.icon-select {
width: 100%;
padding: 10px;
<template>
<div class="w-[400px] relative">
<el-input v-model="inputValue" readonly>
<template #prepend> <svg-icon :iconName="icon"></svg-icon> </template>
</el-input>
<el-popover
shadow="none"
:visible="visible"
placement="bottom-end"
trigger="click"
>
<template #reference>
<div
@click="visible = !visible"
class="cursor-pointer text-[#999] absolute right-[10px] top-0"
>
<i-ep-caret-top v-show="visible"></i-ep-caret-top>
<i-ep-caret-bottom v-show="!visible"></i-ep-caret-bottom>
</div>
</template>
&__list {
height: 200px;
overflow-y: scroll;
<!-- 下拉选择弹窗 -->
<el-input
class="p-2"
v-model="filterValue"
placeholder="搜索图标"
clearable
/>
<el-divider border-style="dashed" />
div {
height: 30px;
line-height: 30px;
margin-bottom: -5px;
cursor: pointer;
width: 33%;
float: left;
}
<el-scrollbar height="300px">
<ul class="icon-list">
<li class="icon-item" v-for="(item, index) in iconList" :key="index">
<svg-icon color="#999" :icon-name="item" />
<span>{{ item }}</span>
</li>
</ul>
</el-scrollbar>
</el-popover>
</div>
</template>
span {
display: inline-block;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
<style scoped lang="scss">
.icon-list {
.icon-item {
&:hover {
border-color: var(--el-color-primary);
color: var(--el-color-primary);
transition: all 0.4s;
transform: scaleX(1.05);
}
}
}
......
<template>
<div :class="{ hidden: hidden }" class="pagination-container">
<div :class="'pagination ' + { hidden: hidden }">
<el-pagination
:background="background"
v-model:current-page="currentPage"
......@@ -89,13 +89,11 @@ function handleCurrentChange(val: number) {
}
</script>
<style scoped>
.pagination-container {
background: #fff;
padding: 32px 16px;
}
.pagination-container.hidden {
display: none;
<style lang="scss" scoped>
.pagination {
padding: 12px;
&.hidden {
display: none;
}
}
</style>
<template>
<div class="cursor-pointer w-[40px] h-[50px] leading-[50px] text-center">
<svg-icon
:icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
:icon-name="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
@click="toggle"
/>
</div>
......
......@@ -22,7 +22,7 @@ function handleSizeChange(size: string) {
<template>
<el-dropdown trigger="click" @command="handleSizeChange">
<div class="cursor-pointerw-[40px] h-[50px] leading-[50px] text-center">
<svg-icon icon-class="size" />
<svg-icon icon-name="size" />
</div>
<template #dropdown>
<el-dropdown-menu>
......
......@@ -9,14 +9,12 @@
</template>
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps({
prefix: {
type: String,
default: 'icon'
},
iconClass: {
iconName: {
type: String,
required: false
},
......@@ -29,12 +27,15 @@ const props = defineProps({
}
});
const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`);
const symbolId = computed(() => `#${props.prefix}-${props.iconName}`);
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em; /* 因icon大小被设置为和字体大小一致,而span等标签的下边缘会和字体的基线对齐,故需设置一个往下的偏移比例,来纠正视觉上的未对齐效果 */
fill: currentColor; /* 定义元素的颜色,currentColor是一个变量,这个变量的值就表示当前元素的color值,如果当前元素未设置color值,则从父元素继承 */
overflow: hidden;
fill: currentColor;
}
</style>
......@@ -15,10 +15,7 @@
/>
</template>
<script setup lang="ts">
import { useSettingsStore } from '@/store/modules/settings';
const settingsStore = useSettingsStore();
</script>
<script setup lang="ts"></script>
<style>
.theme-message,
......
......@@ -11,20 +11,18 @@
:before-upload="handleBeforeUpload"
:http-request="handleUpload"
:on-remove="handleRemove"
:on-preview="handlePreview"
:on-preview="previewImg"
:limit="props.limit"
>
<el-icon><Plus /></el-icon>
<i-ep-plus />
</el-upload>
<el-dialog v-model="dialogVisible">
<img w-full :src="dialogImageUrl" alt="Preview Image" />
<img w-full :src="previewImgUrl" alt="Preview Image" />
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { Plus } from '@element-plus/icons-vue';
import {
ElMessage,
ElUpload,
......@@ -55,7 +53,7 @@ const props = defineProps({
}
});
const dialogImageUrl = ref('');
const previewImgUrl = ref('');
const dialogVisible = ref(false);
const fileList = ref([] as UploadUserFile[]);
......@@ -134,10 +132,10 @@ function handleBeforeUpload(file: UploadRawFile) {
}
/**
* 图片预览
* 预览图片
*/
const handlePreview: UploadProps['onPreview'] = uploadFile => {
dialogImageUrl.value = uploadFile.url!;
const previewImg: UploadProps['onPreview'] = uploadFile => {
previewImgUrl.value = uploadFile.url!;
dialogVisible.value = true;
};
</script>
import { Directive, DirectiveBinding } from 'vue';
import { Directive } from 'vue';
/**
* 按钮防抖
*/
export const deBounce:Directive = {
mounted(el:HTMLElement) {
export const deBounce: Directive = {
mounted(el: HTMLElement) {
el.addEventListener('click', e => {
el.classList.add('is-disabled')
el.classList.add('is-disabled');
setTimeout(() => {
el.classList.remove('is-disabled')
}, 2000)
})
el.classList.remove('is-disabled');
}, 2000);
});
}
}
};
<script setup lang="ts">
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import { useRoute, useRouter } from 'vue-router';
import { ElMessageBox } from 'element-plus';
import Hamburger from '@/components/Hamburger/index.vue';
import Breadcrumb from '@/components/Breadcrumb/index.vue';
import Screenfull from '@/components/Screenfull/index.vue';
import SizeSelect from '@/components/SizeSelect/index.vue';
import LangSelect from '@/components/LangSelect/index.vue';
import MixNav from './Sidebar/MixNav.vue';
import { CaretBottom } from '@element-plus/icons-vue';
import { useAppStore } from '@/store/modules/app';
import { useTagsViewStore } from '@/store/modules/tagsView';
import { useUserStore } from '@/store/modules/user';
......@@ -24,12 +14,14 @@ const settingsStore = useSettingsStore();
const route = useRoute();
const router = useRouter();
const device = computed(() => appStore.device);
const { device } = storeToRefs(appStore); // 设备类型:desktop-宽屏设备 || mobile-窄屏设备
const { layout } = storeToRefs(settingsStore); // 布局模式:left-左侧模式||top-顶部模式||mix-混合模式
function toggleSideBar() {
appStore.toggleSidebar(true);
}
// 注销
function logout() {
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
......@@ -52,7 +44,7 @@ function logout() {
<div class="navbar">
<div
class="flex justify-start"
v-if="device === 'mobile' || settingsStore.layout === 'left'"
v-if="device === 'mobile' || layout === 'left'"
>
<hamburger
:is-active="appStore.sidebar.opened"
......@@ -62,13 +54,24 @@ function logout() {
<breadcrumb />
</div>
<mix-nav v-if="device !== 'mobile' && settingsStore.layout === 'mix'" />
<mix-nav v-if="device !== 'mobile' && layout === 'mix'" />
<!-- 宽屏或左侧模式显示 -->
<div
v-if="device === 'mobile' || settingsStore.layout === 'left'"
v-if="device === 'desktop' || layout === 'left'"
class="flex justify-start"
>
<!-- 左侧窄屏不显示 -->
<div v-if="device !== 'mobile'" class="flex justify-center items-center">
<i-ep-add-location />
<i-ep-aim />
<div i-ep-check />
<el-button>
<template #icon><i-ep-circle-check-filled /></template>
Hello world
</el-button>
<!--全屏 -->
<screenfull id="screenfull" />
......@@ -80,14 +83,14 @@ function logout() {
<!--语言选择-->
<lang-select />
</div>
<!-- 头像 -->
<el-dropdown trigger="click">
<div class="flex justify-center items-center pr-[20px]">
<img
:src="userStore.avatar + '?imageView2/1/w/80/h/80'"
class="w-[40px] h-[40px] rounded-lg"
/>
<CaretBottom class="w-3 h-3" />
<i-ep-caret-bottom class="w-3 h-3" />
</div>
<template #dropdown>
......@@ -115,15 +118,12 @@ function logout() {
</template>
<style lang="scss" scoped>
.el-dropdown {
font-size: 18px;
}
.navbar {
background-color: #fff;
height: 50px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 0px 2px rgba(0, 0, 0, 0.2);
box-shadow: 0 0 1px #0003;
}
</style>
......@@ -3,7 +3,7 @@ import { computed } from 'vue';
import { isExternal } from '@/utils/validate';
import { useRouter } from 'vue-router';
import { DeviceType, useAppStore } from '@/store/modules/app';
import { useAppStore } from '@/store/modules/app';
const appStore = useAppStore();
const sidebar = computed(() => appStore.sidebar);
......
<script setup lang="ts">
import { computed, onMounted } from 'vue';
import { RouterLink, useRoute, useRouter, RouteRecordRaw } from 'vue-router';
import { RouterLink, useRoute, useRouter } from 'vue-router';
import {
ElDropdown,
ElDropdownItem,
......
......@@ -4,7 +4,7 @@ import path from 'path-browserify';
import { isExternal } from '@/utils/validate';
import AppLink from './Link.vue';
import { generateTitle } from '@/utils/i18n';
import { translateRouteTitleI18n } from '@/utils/i18n';
import SvgIcon from '@/components/SvgIcon/index.vue';
const props = defineProps({
......@@ -78,10 +78,10 @@ function resolvePath(routePath: string) {
>
<svg-icon
v-if="onlyOneChild.meta && onlyOneChild.meta.icon"
:icon-class="onlyOneChild.meta.icon"
:icon-name="onlyOneChild.meta.icon"
/>
<template #title>
{{ generateTitle(onlyOneChild.meta.title) }}
{{ translateRouteTitleI18n(onlyOneChild.meta.title) }}
</template>
</el-menu-item>
</app-link>
......@@ -92,10 +92,10 @@ function resolvePath(routePath: string) {
<template #title>
<svg-icon
v-if="item.meta && item.meta.icon"
:icon-class="item.meta.icon"
:icon-name="item.meta.icon"
/>
<span v-if="item.meta && item.meta.title">{{
generateTitle(item.meta.title)
translateRouteTitleI18n(item.meta.title)
}}</span>
</template>
......
<script setup lang="ts">
import {
ref,
computed,
onMounted,
onBeforeUnmount,
getCurrentInstance
} from 'vue';
import { useTagsViewStore, TagView } from '@/store/modules/tagsView';
const tagAndTagSpacing = ref(4);
......@@ -102,8 +95,8 @@ defineExpose({
<template>
<el-scrollbar
ref="scrollContainer"
:vertical="false"
class="scroll-container"
:vertical="false"
@wheel.prevent="handleScroll"
>
<slot />
......
......@@ -7,26 +7,27 @@ import {
onMounted,
ComponentInternalInstance
} from 'vue';
import { storeToRefs } from 'pinia';
import path from 'path-browserify';
import { useRoute, useRouter } from 'vue-router';
import ScrollPane from './ScrollPane.vue';
import SvgIcon from '@/components/SvgIcon/index.vue';
import { generateTitle } from '@/utils/i18n';
import { translateRouteTitleI18n } from '@/utils/i18n';
import { usePermissionStore } from '@/store/modules/permission';
import { useTagsViewStore, TagView } from '@/store/modules/tagsView';
const permissionStore = usePermissionStore();
const tagsViewStore = useTagsViewStore();
import ScrollPane from './ScrollPane.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const router = useRouter();
const route = useRoute();
const visible = ref(false);
const permissionStore = usePermissionStore();
const tagsViewStore = useTagsViewStore();
const { visitedViews } = storeToRefs(tagsViewStore);
const selectedTag = ref({});
const scrollPaneRef = ref();
const left = ref(0);
......@@ -45,11 +46,12 @@ watch(
}
);
watch(visible, value => {
const tagMenuVisible = ref(false); // 标签操作菜单显示状态
watch(tagMenuVisible, value => {
if (value) {
document.body.addEventListener('click', closeMenu);
document.body.addEventListener('click', closeTagMenu);
} else {
document.body.removeEventListener('click', closeMenu);
document.body.removeEventListener('click', closeTagMenu);
}
});
......@@ -78,11 +80,11 @@ function filterAffixTags(routes: any[], basePath = '/') {
}
function initTags() {
const tags = filterAffixTags(permissionStore.routes);
const tags: TagView[] = filterAffixTags(permissionStore.routes);
affixTags.value = tags;
for (const tag of tags) {
// Must have tag name
if ((tag as TagView).name) {
if (tag.name) {
tagsViewStore.addVisitedView(tag);
}
}
......@@ -205,7 +207,7 @@ function closeAllTags(view: TagView) {
});
}
function openMenu(tag: TagView, e: MouseEvent) {
function openTagMenu(tag: TagView, e: MouseEvent) {
const menuMinWidth = 105;
const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
const offsetWidth = proxy?.$el.offsetWidth; // container width
......@@ -219,16 +221,16 @@ function openMenu(tag: TagView, e: MouseEvent) {
}
top.value = e.clientY;
visible.value = true;
tagMenuVisible.value = true;
selectedTag.value = tag;
}
function closeMenu() {
visible.value = false;
function closeTagMenu() {
tagMenuVisible.value = false;
}
function handleScroll() {
closeMenu();
closeTagMenu();
}
onMounted(() => {
......@@ -237,71 +239,71 @@ onMounted(() => {
</script>
<template>
<div
class="h-[34px] w-full border-b-[1px] border-gray-200 shadow-lg shadow-[rgba(0, 21, 41, 0.08)]"
<scroll-pane
class="tags-container"
ref="scrollPaneRef"
@scroll="handleScroll"
>
<scroll-pane
ref="scrollPaneRef"
class="tags-container"
@scroll="handleScroll"
<router-link
:class="'tags-item ' + (isActive(tag) ? 'active' : '')"
v-for="tag in visitedViews"
:key="tag.path"
:data-path="tag.path"
:to="{ path: tag.path, query: tag.query }"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openTagMenu(tag, $event)"
>
<router-link
v-for="tag in tagsViewStore.visitedViews"
:key="tag.path"
:data-path="tag.path"
:class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path, query: tag.query }"
class="tags-item"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)"
{{ translateRouteTitleI18n(tag.meta?.title) }}
<span
v-if="!isAffix(tag)"
class="rounded-[60%] hover:bg-gray-300"
@click.prevent.stop="closeSelectedTag(tag)"
>
{{ generateTitle(tag.meta?.title) }}
<span
v-if="!isAffix(tag)"
class="tags-item-remove"
@click.prevent.stop="closeSelectedTag(tag)"
>
<svg-icon icon-class="close" />
</span>
</router-link>
</scroll-pane>
<ul
v-show="visible"
:style="{ left: left + 'px', top: top + 'px' }"
class="tags-item-menu"
>
<li @click="refreshSelectedTag(selectedTag)">
<svg-icon icon-class="refresh" />
刷新
</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
<svg-icon icon-class="close" />
关闭
</li>
<li @click="closeOtherTags">
<svg-icon icon-class="close_other" />
关闭其它
</li>
<li v-if="!isFirstView()" @click="closeLeftTags">
<svg-icon icon-class="close_left" />
关闭左侧
</li>
<li v-if="!isLastView()" @click="closeRightTags">
<svg-icon icon-class="close_right" />
关闭右侧
</li>
<li @click="closeAllTags(selectedTag)">
<svg-icon icon-class="close_all" />
关闭所有
</li>
</ul>
</div>
<i-ep-close class="text-[10px]" />
</span>
</router-link>
</scroll-pane>
<!-- tag标签操作菜单 -->
<ul
v-show="tagMenuVisible"
class="tag-menu"
:style="{ left: left + 'px', top: top + 'px' }"
>
<li @click="refreshSelectedTag(selectedTag)">
<svg-icon icon-name="refresh" />
刷新
</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
<svg-icon icon-name="close" />
关闭
</li>
<li @click="closeOtherTags">
<svg-icon icon-name="close_other" />
关闭其它
</li>
<li v-if="!isFirstView()" @click="closeLeftTags">
<svg-icon icon-name="close_left" />
关闭左侧
</li>
<li v-if="!isLastView()" @click="closeRightTags">
<svg-icon icon-name="close_right" />
关闭右侧
</li>
<li @click="closeAllTags(selectedTag)">
<svg-icon icon-name="close_all" />
关闭所有
</li>
</ul>
</template>
<style lang="scss" scoped>
.tags-container {
height: 34px;
width: 100%;
border: 1px solid #eee;
box-shadow: 0px 1px 1px #eee;
.tags-item {
display: inline-block;
cursor: pointer;
......@@ -324,7 +326,7 @@ onMounted(() => {
&.active {
background-color: var(--el-color-primary);
color: var(--el-color-primary-light-9);
color: #fff;
border-color: var(--el-color-primary);
&::before {
content: '';
......@@ -333,22 +335,13 @@ onMounted(() => {
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 5px;
}
}
&-remove {
border-radius: 50%;
&:hover {
color: #fff;
background-color: #ccc;
}
}
}
}
.tags-item-menu {
.tag-menu {
background: #fff;
z-index: 99;
position: absolute;
......
......@@ -5,7 +5,7 @@ import { AppMain, Navbar, Settings, TagsView } from './components/index';
import Sidebar from './components/Sidebar/index.vue';
import RightPanel from '@/components/RightPanel/index.vue';
import { DeviceType, useAppStore } from '@/store/modules/app';
import { useAppStore } from '@/store/modules/app';
import { useSettingsStore } from '@/store/modules/settings';
const { width } = useWindowSize();
......
......@@ -5,10 +5,9 @@ import { setupStore } from '@/store';
import ElementPlus from 'element-plus';
import Pagination from '@/components/Pagination/index.vue';
import '@/permission';
// 引入svg注册脚本
// 本地SVG图标
import 'virtual:svg-icons-register';
// 国际化
......@@ -31,9 +30,4 @@ app.config.globalProperties.$getDictionaries = getDictionaries;
// 全局挂载
setupStore(app);
app
.component('Pagination', Pagination)
.use(router)
.use(ElementPlus)
.use(i18n)
.mount('#app');
app.use(router).use(ElementPlus).use(i18n).mount('#app');
......@@ -3,13 +3,13 @@ import { defineStore } from 'pinia';
import { constantRoutes } from '@/router';
import { store } from '@/store';
import { listRoutes } from '@/api/menu';
import { ref } from 'vue';
const modules = import.meta.glob('../../views/**/**.vue');
export const Layout = () => import('@/layout/index.vue');
const hasPermission = (roles: string[], route: RouteRecordRaw) => {
if (route.meta && route.meta.roles) {
// 默认超级管理员角色拥有所有权限,忽略校验
if (roles.includes('ROOT')) {
return true;
}
......@@ -51,11 +51,9 @@ const filterAsyncRoutes = (routes: RouteRecordRaw[], roles: string[]) => {
export const usePermissionStore = defineStore('permission', () => {
// state
const routes = ref<RouteRecordRaw[]>([]);
const addRoutes = ref<RouteRecordRaw[]>([]);
// actions
function setRoutes(newRoutes: RouteRecordRaw[]) {
addRoutes.value = newRoutes;
routes.value = constantRoutes.concat(newRoutes);
}
......
......@@ -10,41 +10,13 @@
font-weight: 400 !important;
}
.el-upload {
input[type='file'] {
display: none !important;
}
}
.el-upload__input {
display: none;
}
// dropdown
.el-dropdown-menu {
a {
display: block;
}
}
// to fix el-date-picker css style
.el-range-separator {
box-sizing: content-box;
}
// 选中行背景色值
.el-table__body tr.current-row td {
background-color: #e1f3d8b5 !important;
}
// card 的header统一高度
.el-card__header {
height: 60px !important;
}
// 表格表头和表体未对齐
.el-table__header col[name='gutter'] {
display: table-cell !important;
}
......@@ -16,8 +16,8 @@ html,body,#app{
padding: 18px 0 0 10px;
margin-bottom: 10px;
border-radius: 5px;
border: 1px solid #ddd;
box-shadow: 6px 2px 6px #CCC;
border: 1px solid #eee;
box-shadow: 1px 1px 1px #eee;
}
svg{
......
......@@ -6,20 +6,6 @@
}
}
@mixin scrollBar {
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
@mixin relative {
position: relative;
......
此差异已折叠。
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
Breadcrumb: typeof import('./../components/Breadcrumb/index.vue')['default']
ElAlert: typeof import('element-plus/es')['ElAlert']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCol: typeof import('element-plus/es')['ElCol']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
ElUpload: typeof import('element-plus/es')['ElUpload']
GithubCorner: typeof import('./../components/GithubCorner/index.vue')['default']
Hamburger: typeof import('./../components/Hamburger/index.vue')['default']
IconSelect: typeof import('./../components/IconSelect/index.vue')['default']
IEpAddLocation: typeof import('~icons/ep/add-location')['default']
IEpAim: typeof import('~icons/ep/aim')['default']
IEpCaretBottom: typeof import('~icons/ep/caret-bottom')['default']
IEpCaretTop: typeof import('~icons/ep/caret-top')['default']
IEpCircleCheckFilled: typeof import('~icons/ep/circle-check-filled')['default']
IEpClose: typeof import('~icons/ep/close')['default']
IEpDownload: typeof import('~icons/ep/download')['default']
IEpEdit: typeof import('~icons/ep/edit')['default']
IEpIcon: typeof import('~icons/ep/icon')['default']
IEpTop: typeof import('~icons/ep/top')['default']
LangSelect: typeof import('./../components/LangSelect/index.vue')['default']
MultiUpload: typeof import('./../components/Upload/MultiUpload.vue')['default']
Pagination: typeof import('./../components/Pagination/index.vue')['default']
RightPanel: typeof import('./../components/RightPanel/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Screenfull: typeof import('./../components/Screenfull/index.vue')['default']
SingleUpload: typeof import('./../components/Upload/SingleUpload.vue')['default']
SizeSelect: typeof import('./../components/SizeSelect/index.vue')['default']
SvgIcon: typeof import('./../components/SvgIcon/index.vue')['default']
ThemePicker: typeof import('./../components/ThemePicker/index.vue')['default']
WangEditor: typeof import('./../components/WangEditor/index.vue')['default']
}
export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}
// translate router.meta.title, be used in breadcrumb sidebar tagsview
import i18n from '@/lang/index';
export function generateTitle(title: any) {
export function translateRouteTitleI18n(title: any) {
// 判断是否存在国际化配置,如果没有原生返回
const hasKey = i18n.global.te('route.' + title);
if (hasKey) {
......
......@@ -23,7 +23,7 @@ import Team from './components/Team/index.vue';
<el-col :xs="24" :sm="12" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper icon-user">
<svg-icon icon-class="uv" size="4em" />
<svg-icon icon-name="uv" size="4em" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">访问数</div>
......@@ -35,7 +35,7 @@ import Team from './components/Team/index.vue';
<el-col :xs="24" :sm="12" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper icon-message">
<svg-icon icon-class="message" size="4em" />
<svg-icon icon-name="message" size="4em" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">消息数</div>
......@@ -47,7 +47,7 @@ import Team from './components/Team/index.vue';
<el-col :xs="24" :sm="12" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper icon-money">
<svg-icon icon-class="money" size="4em" />
<svg-icon icon-name="money" size="4em" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">收入金额</div>
......@@ -58,7 +58,7 @@ import Team from './components/Team/index.vue';
<el-col :xs="24" :sm="12" :lg="6" class="card-panel__col">
<div class="card-panel">
<div class="card-panel-icon-wrapper icon-shopping">
<svg-icon icon-class="shopping" size="4em" />
<svg-icon icon-name="shopping" size="4em" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">订单数</div>
......
......@@ -15,7 +15,7 @@
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
<svg-icon icon-name="user" />
</span>
<el-input
ref="username"
......@@ -35,7 +35,7 @@
>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
<svg-icon icon-name="password" />
</span>
<el-input
ref="passwordRef"
......@@ -52,7 +52,7 @@
/>
<span class="show-pwd" @click="showPwd">
<svg-icon
:icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
:icon-name="passwordType === 'password' ? 'eye' : 'eye-open'"
/>
</span>
</el-form-item>
......
......@@ -15,7 +15,7 @@ import {
updateDictItem,
deleteDictItems
} from '@/api/dict';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import { Search, Plus, Refresh, Delete } from '@element-plus/icons-vue';
import { DictItem, DictItemForm, DictItemQuery } from '@/api/dict/types';
const props = defineProps({
......
......@@ -29,7 +29,7 @@ const handleDictTypeClick = (row: any) => {
<el-col :span="10" :xs="24">
<el-card class="box-card">
<template #header>
<svg-icon icon-class="dict" />
<svg-icon icon-name="dict" />
字典类型
</template>
<dict-type @dictClick="handleDictTypeClick" />
......@@ -39,7 +39,7 @@ const handleDictTypeClick = (row: any) => {
<el-col :span="14" :xs="24">
<el-card class="box-card">
<template #header>
<svg-icon icon-class="dict_item" />
<svg-icon icon-name="dict_item" />
<span style="margin: 0 5px">字典数据项</span>
<el-tag type="success" v-if="typeCode" size="small">{{
typeName
......
......@@ -40,7 +40,7 @@
<el-table-column label="菜单名称">
<template #default="scope">
<svg-icon
:icon-class="
:icon-name="
scope.row.type === 'BUTTON' ? 'button' : scope.row.icon
"
/>
......@@ -121,6 +121,7 @@
</el-table-column>
</el-table>
</el-card>
<!-- dialog -->
<el-dialog
:title="dialog.title"
......@@ -214,27 +215,8 @@
prop="icon"
v-if="formData.type !== 'BUTTON'"
>
<el-popover
ref="popoverRef"
placement="bottom-start"
:width="570"
trigger="click"
>
<template #reference>
<el-input
v-model="formData.icon"
placeholder="点击选择图标"
readonly
@click="iconSelectVisible = true"
>
<template #prefix>
<svg-icon :icon-class="formData.icon" />
</template>
</el-input>
</template>
<icon-select @selected="selected" />
</el-popover>
<!-- 图标选择器 -->
<icon-select v-model="formData.icon" />
</el-form-item>
<el-form-item label="跳转路由" v-if="formData.type == 'CATEGORY'">
......@@ -271,8 +253,8 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRefs } from 'vue';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
import { ElForm, ElMessage, ElMessageBox, ElPopover } from 'element-plus';
import { Search, Plus, Refresh } from '@element-plus/icons-vue';
import { ElForm, ElMessage, ElMessageBox } from 'element-plus';
import { MenuQuery, MenuForm, Menu } from '@/api/menu/types';
// API 依赖
......@@ -291,7 +273,6 @@ import IconSelect from '@/components/IconSelect/index.vue';
const emit = defineEmits(['menuClick']);
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
const popoverRef = ref(ElPopover);
const state = reactive({
loading: true,
......@@ -339,7 +320,6 @@ const {
formData,
rules,
menuOptions,
iconSelectVisible,
cacheData
} = toRefs(state);
......
......@@ -292,7 +292,6 @@ onMounted(() => {
>
资源分配
</el-button>
<el-button
type="primary"
link
......
......@@ -23,7 +23,7 @@ import {
updateUser,
updateUserStatus,
updateUserPassword,
downloadTemplate,
downloadTemplateApi,
exportUser,
importUser
} from '@/api/user';
......@@ -353,8 +353,8 @@ function getGenderOptions() {
/**
* 下载导入模板
*/
function handleDownloadTemplate() {
downloadTemplate().then((response: any) => {
function downloadTemplate() {
downloadTemplateApi().then((response: any) => {
const blob = new Blob([response.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
});
......@@ -520,46 +520,43 @@ onMounted(() => {
<el-card shadow="never">
<template #header>
<el-form-item style="float: left">
<el-button
type="success"
:icon="Plus"
@click="handleAdd"
v-hasPerm="['sys:user:add']"
>新增</el-button
>
<el-button
type="danger"
:icon="Delete"
:disabled="ids.length === 0"
@click="handleDelete"
v-hasPerm="['sys:user:delete']"
>删除</el-button
>
</el-form-item>
<el-form-item style="float: right">
<el-dropdown split-button style="margin-left: 12px">
导入
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
:icon="Download"
@click="handleDownloadTemplate"
>下载模板</el-dropdown-item
>
<el-dropdown-item :icon="Top" @click="showImportDialog"
>导入数据</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button
:icon="Download"
style="margin-left: 12px"
@click="handleExport"
>导出</el-button
>
</el-form-item>
<div class="flex justify-between">
<div>
<el-button
type="success"
:icon="Plus"
@click="handleAdd"
v-hasPerm="['sys:user:add']"
>新增</el-button
>
<el-button
type="danger"
:icon="Delete"
:disabled="ids.length === 0"
@click="handleDelete"
v-hasPerm="['sys:user:delete']"
>删除</el-button
>
</div>
<div>
<el-dropdown split-button>
导入
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="downloadTemplate">
<i-ep-download />下载模板</el-dropdown-item
>
<el-dropdown-item @click="showImportDialog">
<i-ep-top />导入数据</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button class="ml-3" @click="handleExport"
><template #icon><i-ep-download /></template>导出</el-button
>
</div>
</div>
</template>
<el-table
......
......@@ -21,9 +21,9 @@
"types": ["element-plus/global"],
"typeRoots": [
"./node_modules/@types/",
"./types"
"src/types"
] /* 指定多个文件夹,这些文件夹的作用类似于 './node_modules/@types'. */
},
"include": ["src/**/*.ts", "src/**/*.vue", "types/**/*.d.ts"],
"include": ["src/**/*.ts", "src/**/*.vue", "src/types/**/*.d.ts"],
"exclude": ["node_modules", "dist", "**/*.js"]
}
// 全局组件类型声明
import Pagination from '@/components/Pagination/index.vue';
declare module '@vue/runtime-core' {
export interface GlobalComponents {
Pagination: typeof Pagination;
}
}
export {};
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册